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;n
g)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: