jquery.jsPlumb-1.0.2-RC2.js 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985
  1. /*
  2. * jsPlumb 1.0.2-RC2
  3. *
  4. * experimental work on ways to select the actual connector lines. shelved for the moment. do not use.
  5. *
  6. * Provides a way to visually connect elements on an HTML page.
  7. *
  8. * http://morrisonpitt.com/jsPlumb/demo.html
  9. * http://code.google.com/p/jsPlumb
  10. *
  11. */
  12. if (!Array.prototype.indexOf) {
  13. Array.prototype.indexOf = function( v, b, s ) {
  14. for( var i = +b || 0, l = this.length; i < l; i++ ) {
  15. if( this[i]===v || s && this[i]==v ) { return i; }
  16. }
  17. return -1;
  18. };
  19. }
  20. (function() {
  21. var repaintFunction = function() { jsPlumb.repaintEverything(); };
  22. var automaticRepaint = true;
  23. function repaintEverything() {
  24. if (automaticRepaint)
  25. repaintFunction();
  26. };
  27. var resizeTimer = null;
  28. $(window).bind('resize', function() {
  29. if (resizeTimer) clearTimeout(resizeTimer);
  30. resizeTimer = setTimeout(repaintEverything, 100);
  31. });
  32. var connections = {};
  33. var connectionPairs = [];
  34. var offsets = [];
  35. var sizes = [];
  36. 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.)
  37. /**
  38. * applies all the styles to the given context. this just wraps the $.extend function.
  39. *
  40. * @param context
  41. * @param styles
  42. */
  43. var applyPaintStyle = function(context, styles) {
  44. $.extend(context, styles);
  45. };
  46. /**
  47. * Handles the dragging of an element.
  48. * @param element jQuery element
  49. * @param ui UI object from jQuery's event system
  50. */
  51. var drag = function(element, ui) {
  52. var id = element.attr("id");
  53. if (id) {
  54. var l = connections[id];
  55. if (l) {
  56. for (var i = 0; i < l.length; i++)
  57. l[i].paint(id, ui);
  58. }
  59. }
  60. };
  61. /**
  62. * private method to do the business of hiding/showing.
  63. * @param elId Id of the element in question
  64. * @param state String specifying a value for the css 'display' property ('block' or 'none').
  65. */
  66. var setVisible = function(elId, state) {
  67. var jpcs = connections[elId];
  68. for (var i = 0; i < jpcs.length; i++) {
  69. jpcs[i].canvas.style.display=state;
  70. if (jpcs[i].drawEndpoints) {
  71. jpcs[i].sourceEndpointCanvas.style.display=state;
  72. jpcs[i].targetEndpointCanvas.style.display=state;
  73. }
  74. }
  75. /*for (var i in connectionPairs) {
  76. //alert(pairId);
  77. var pair = connectionPairs[i];
  78. if (pair.source == elId || pair.target == elId) {
  79. pair.jpc.canvas.style.display = state;
  80. if (pair.jpc.drawEndpoints) {
  81. pair.jpc.sourceEndpointCanvas.style.display = state;
  82. pair.jpc.targetEndpointCanvas.style.display = state;
  83. }
  84. }
  85. }*/
  86. };
  87. var canvasMouseOverHelper = function(jpc, x, y, lineWidth) {
  88. $("._jsPlumb_connector").css("border", "none");
  89. var jpcs = findMatchingConnectors(x, y);
  90. for (var i = 0; i < jpcs.length; i++) {
  91. if (jpcs[i].connector.isMouseOver(5, x, y, lineWidth))
  92. $(jpcs[i].canvas).css("border", "1px solid red");
  93. }
  94. };
  95. /**
  96. * helper to compute intersection of point and box.
  97. */
  98. var containsPoint = function(jpc, x, y) {
  99. return (jpc.currentX <= x && jpc.currentX + jpc.currentWidth >= x) && (jpc.currentY <= y && jpc.currentY + jpc.currentHeight >= y);
  100. };
  101. /**
  102. * Finds all the connectors whose canvas covers the given page location.
  103. */
  104. var findMatchingConnectors = function(x, y) {
  105. var results = [];
  106. for (var pairId in connectionPairs) {
  107. var pair = connectionPairs[pairId];
  108. if (containsPoint(pair.jpc, x, y)) results.push(pair.jpc);
  109. }
  110. return results;
  111. };
  112. var debug = function(text, append) {
  113. var t = (append ? $("#debug").html() : "") + "<br/>";
  114. $("#debug").html(t + text);
  115. };
  116. /**
  117. * helper to create a canvas.
  118. * @param clazz optional class name for the canvas.
  119. */
  120. var newCanvas = function(clazz) {
  121. var canvas = document.createElement("canvas");
  122. document.body.appendChild(canvas);
  123. canvas.style.position="absolute";
  124. if (clazz) { canvas.className=clazz; }
  125. if (/MSIE/.test(navigator.userAgent) && !window.opera) {
  126. // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels
  127. // is not big enough for the biggest connector/endpoint canvas you have at startup.
  128. jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE);
  129. canvas = G_vmlCanvasManager.initElement(canvas);
  130. }
  131. return canvas;
  132. };
  133. /**
  134. * helper to remove a canvas from the DOM.
  135. */
  136. var removeCanvas = function(canvas) {
  137. if (canvas != null) {
  138. try { document.body.removeChild(canvas); }
  139. catch (e) { }
  140. }
  141. };
  142. /**
  143. * generic anchor - can be situated anywhere. params should contain three values, and may optionally have an 'offsets' argument:
  144. *
  145. * x - the x location of the anchor as a percentage of the total width.
  146. * y - the y location of the anchor as a percentage of the total height.
  147. * orientation - an [x,y] array indicating the general direction a connection from the anchor should go in.
  148. * offsets - an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null.
  149. *
  150. */
  151. var Anchor = function(params) {
  152. var self = this;
  153. this.x = params.x || 0; this.y = params.y || 0; this.orientation = params.orientation || [0,0]; this.offsets = params.offsets || [0,0];
  154. this.compute = function(xy, wh, txy, twh) {
  155. return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ];
  156. }
  157. };
  158. /**
  159. * jsPlumb public API
  160. */
  161. var jsPlumb = window.jsPlumb = {
  162. connectorClass : '_jsPlumb_connector',
  163. endpointClass : '_jsPlumb_endpoint',
  164. DEFAULT_PAINT_STYLE : { lineWidth : 10, strokeStyle : "red" },
  165. DEFAULT_ENDPOINT_STYLE : { fillStyle : null }, // meaning it will be derived from the stroke style of the connector.
  166. DEFAULT_ENDPOINT_STYLES : [ null, null ], // meaning it will be derived from the stroke style of the connector.
  167. DEFAULT_DRAG_OPTIONS : { },
  168. DEFAULT_CONNECTOR : null,
  169. DEFAULT_ENDPOINT : null,
  170. DEFAULT_ENDPOINTS : [null, null], // new in 0.0.4, the ability to specify diff. endpoints. DEFAULT_ENDPOINT is here for backwards compatibility.
  171. /**
  172. * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance
  173. * of Anchor that has been configured appropriately.
  174. *
  175. * You can write your own one of these; you
  176. * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this:
  177. *
  178. * jsPlumb.Anchors.MY_ANCHOR = {
  179. * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; },
  180. * orientation : [ox, oy]
  181. * };
  182. *
  183. * compute takes the [x,y] position of the top left corner of the anchored element,
  184. * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to,
  185. * and returns where the anchor should be located.
  186. *
  187. * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor
  188. * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference,
  189. * -1 means go in a negative direction for the given axis, and 1 means go in a positive
  190. * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1],
  191. * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example,
  192. * the curve would start out going in that direction, before bending towards the target anchor.
  193. */
  194. Anchors :
  195. {
  196. TopCenter : new Anchor({x:0.5, y:0, orientation:[0,-1] }),
  197. BottomCenter : new Anchor({x:0.5, y:1, orientation:[0, 1] }),
  198. LeftMiddle: new Anchor({x:0, y:0.5, orientation:[-1,0] }),
  199. RightMiddle : new Anchor({x:1, y:0.5, orientation:[1,0] }),
  200. Center : new Anchor({x:0.5, y:0.5, orientation:[0,0] }),
  201. TopRight : new Anchor({x:1, y:0, orientation:[0,-1] }),
  202. BottomRight : new Anchor({x:1, y:1, orientation:[0,1] }),
  203. TopLeft : new Anchor({x:0, y:0, orientation:[0,-1] }),
  204. BottomLeft : new Anchor({x:0, y:1, orientation:[0,1] })
  205. },
  206. /**
  207. * Types of connectors, eg. Straight, Bezier.
  208. */
  209. Connectors :
  210. {
  211. /**
  212. * The Straight connector draws a simple straight line between the two anchor points.
  213. */
  214. Straight : function() {
  215. var self = this;
  216. /**
  217. * Computes the new size and position of the canvas.
  218. * @param sourceAnchor Absolute position on screen of the source object's anchor.
  219. * @param targetAnchor Absolute position on screen of the target object's anchor.
  220. * @param positionMatrix Indicates the relative positions of the left,top of the
  221. * two plumbed objects. so [0,0] indicates that the source is to the left of, and
  222. * above, the target. [1,0] means the source is to the right and above. [0,1] means
  223. * the source is to the left and below. [1,1] means the source is to the right
  224. * and below. this is used to figure out which direction to draw the connector in.
  225. * @returns an array of positioning information. the first two values are
  226. * the [left, top] absolute position the canvas should be placed on screen. the
  227. * next two values are the [width,height] the canvas should be. after that each
  228. * Connector can put whatever it likes into the array:it will be passed back in
  229. * to the paint call. This particular function stores the origin and destination of
  230. * the line it is going to draw. a more involved implementation, like a Bezier curve,
  231. * would store the control point info in this array too.
  232. */
  233. this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
  234. var w = Math.abs(sourcePos[0] - targetPos[0]);
  235. var h = Math.abs(sourcePos[1] - targetPos[1]);
  236. var widthAdjusted = false, heightAdjusted = false;
  237. // these are padding to ensure the whole connector line appears
  238. var xo = 0.45 * w, yo = 0.45 * h;
  239. // these are padding to ensure the whole connector line appears
  240. w *= 1.9; h *=1.9;
  241. var x = Math.min(sourcePos[0], targetPos[0]) - xo;
  242. var y = Math.min(sourcePos[1], targetPos[1]) - yo;
  243. if (w < 2 * lineWidth) {
  244. // minimum size is 2 * line Width
  245. w = 2 * lineWidth;
  246. // if we set this then we also have to place the canvas
  247. x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth;
  248. xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2;//lineWidth/2;//lineWidth / 2;
  249. }
  250. if (h < 2 * lineWidth) {
  251. // minimum size is 2 * line Width
  252. h = 2 * lineWidth;
  253. // if we set this then we also have to place the canvas
  254. y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth;
  255. yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2;//lineWidth/2;//lineWidth / 2;
  256. }
  257. // here we check to see if the delta was very small and so the line in
  258. // one direction can be considered straight.
  259. var sx = sourcePos[0] < targetPos[0] ? w-xo : xo;
  260. var sy = sourcePos[1] < targetPos[1] ? h-yo : yo;
  261. var tx = sourcePos[0] < targetPos[0] ? xo : w-xo;
  262. var ty = sourcePos[1] < targetPos[1] ? yo : h-yo;
  263. var retVal = [ x, y, w, h, sx, sy, tx, ty ];
  264. // return [canvasX, canvasY, canvasWidth, canvasHeight,
  265. // sourceX, sourceY, targetX, targetY]
  266. return retVal;
  267. };
  268. this.paint = function(dimensions, ctx)
  269. {
  270. ctx.beginPath();
  271. ctx.moveTo(dimensions[4], dimensions[5]);
  272. ctx.lineTo(dimensions[6], dimensions[7]);
  273. ctx.stroke();
  274. };
  275. /**
  276. * Returns whether or not the given x,y is within 'threshold' of the
  277. * actual connector. this is different for each type of connector because
  278. * they inscribe different lines. bezier is more complex than straight line of course.
  279. */
  280. this.isMouseOver = function(threshold, x, y, lineWidth) {
  281. return false;
  282. };
  283. },
  284. /**
  285. * This Connector draws a Bezier curve with two control points.
  286. * @param curviness How 'curvy' you want the curve to be! This is a directive for the
  287. * placement of control points, not endpoints of the curve, so your curve does not
  288. * actually touch the given point, but it has the tendency to lean towards it. the larger
  289. * this value, the greater the curve is pulled from a straight line.
  290. *
  291. * a future implementation of this could take the control points as arguments, rather
  292. * than fixing the curve to one basic shape.
  293. */
  294. Bezier : function(curviness) {
  295. var self = this;
  296. this.majorAnchor = curviness || 150;
  297. this.minorAnchor = 10;
  298. this._findControlPoint = function(point, anchor1Position, anchor2Position, anchor1, anchor2) {
  299. var p = [];
  300. var ma = self.majorAnchor, mi = self.minorAnchor;
  301. if (anchor1.orientation[0] == 0) // X
  302. p.push(anchor1Position[0] < anchor2Position[0] ? point[0] + mi : point[0] - mi);
  303. else p.push(point[0] - (ma * anchor1.orientation[0]));
  304. if (anchor1.orientation[1] == 0) // Y
  305. p.push(anchor1Position[1] < anchor2Position[1] ? point[1] + mi : point[1] - mi);
  306. else p.push(point[1] + (ma * anchor2.orientation[1]));
  307. return p;
  308. };
  309. this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth)
  310. {
  311. lineWidth = lineWidth || 0;
  312. var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth;
  313. var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2);
  314. var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2);
  315. var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2);
  316. var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor);
  317. var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor);
  318. var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2);
  319. var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2);
  320. if (maxx > w) w = maxx;
  321. if (minx < 0) {
  322. canvasX += minx; var ox = Math.abs(minx);
  323. w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox;
  324. }
  325. var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2);
  326. var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2);
  327. if (maxy > h) h = maxy;
  328. if (miny < 0) {
  329. canvasY += miny; var oy = Math.abs(miny);
  330. h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy;
  331. }
  332. // return [ canvasx, canvasy, canvasWidth, canvasHeight,
  333. // sourceX, sourceY, targetX, targetY,
  334. // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y
  335. this.lastValues = [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ];
  336. return this.lastValues;
  337. };
  338. this.paint = function(d, ctx) {
  339. ctx.beginPath();
  340. ctx.moveTo(d[4],d[5]);
  341. ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]);
  342. ctx.stroke();
  343. };
  344. /**
  345. * Returns whether or not the given x,y is within 'threshold' of the
  346. * actual connector. this is different for each type of connector because
  347. * they inscribe different lines. bezier is more complex than straight line of course.
  348. */
  349. this.isMouseOver = function(threshold, x, y, lineWidth) {
  350. var p = {x:x, y:y};
  351. var curve = {
  352. start:{x:this.lastValues[0] + this.lastValues[4], y:this.lastValues[1] + this.lastValues[5]},
  353. end:{ x:this.lastValues[0] + this.lastValues[6], y:this.lastValues[1] + this.lastValues[7] },
  354. cp1:{ x:this.lastValues[0] + this.lastValues[8], y: this.lastValues[1] + this.lastValues[9] },
  355. cp2:{ x:this.lastValues[0] + this.lastValues[10], y:this.lastValues[1] + this.lastValues[11] }
  356. };
  357. var t = closestPointToBezier(curve, p);
  358. return true;
  359. };
  360. }
  361. },
  362. /**
  363. * Types of endpoint UIs. we supply three - a circle of default radius 10px, a rectangle of
  364. * default size 20x20, and an image (with no default). you can supply others of these if you want to - see the documentation
  365. * for a howto.
  366. */
  367. Endpoints : {
  368. /**
  369. * a round endpoint, with default radius 10 pixels.
  370. */
  371. Dot : function(params) {
  372. params = params || { radius:10 };
  373. var self = this;
  374. this.radius = params.radius;
  375. var defaultOffset = 0.5 * this.radius;
  376. var defaultInnerRadius = this.radius / 3;
  377. var parseValue = function(value) {
  378. try {
  379. return parseInt(value);
  380. }
  381. catch(e) {
  382. if (value.substring(value.length - 1) == '%')
  383. return parseInt(value.substring(0, value - 1));
  384. }
  385. }
  386. var calculateAdjustments = function(gradient) {
  387. var offsetAdjustment = defaultOffset;
  388. var innerRadius = defaultInnerRadius;
  389. if (gradient.offset) offsetAdjustment = parseValue(gradient.offset);
  390. if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius);
  391. return [offsetAdjustment, innerRadius];
  392. };
  393. this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {
  394. var radius = endpointStyle.radius || self.radius;
  395. var x = anchorPoint[0] - radius;
  396. var y = anchorPoint[1] - radius;
  397. jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2);
  398. var ctx = canvas.getContext('2d');
  399. var style = {};
  400. applyPaintStyle(style, endpointStyle);
  401. if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle;
  402. applyPaintStyle(ctx, style);
  403. var ie = (/MSIE/.test(navigator.userAgent) && !window.opera);
  404. if (endpointStyle.gradient && !ie) {
  405. var adjustments = calculateAdjustments(endpointStyle.gradient);
  406. var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0];
  407. var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0];
  408. var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]);
  409. for (var i = 0; i < endpointStyle.gradient.stops.length; i++)
  410. g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]);
  411. ctx.fillStyle = g;
  412. }
  413. ctx.beginPath();
  414. ctx.arc(radius, radius, radius, 0, Math.PI*2, true);
  415. ctx.closePath();
  416. ctx.fill();
  417. };
  418. },
  419. /**
  420. * A Rectangular endpoint, with default size 20x20.
  421. */
  422. Rectangle : function(params) {
  423. params = params || { width:20, height:20 };
  424. var self = this;
  425. this.width = params.width;
  426. this.height = params.height;
  427. this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {
  428. var width = endpointStyle.width || self.width;
  429. var height = endpointStyle.height || self.height;
  430. var x = anchorPoint[0] - (width/2);
  431. var y = anchorPoint[1] - (height/2);
  432. jsPlumb.sizeCanvas(canvas, x, y, width, height);
  433. var ctx = canvas.getContext('2d');
  434. //todo: the fillStyle needs some thought. we want to support a few options:
  435. // 1. nothing supplied; use the stroke color or the default if no stroke color.
  436. // 2. a fill color supplied - use it
  437. // 3. a gradient supplied - use it
  438. // 4. setting the endpoint to the same color as the bg of the element it is attached to.
  439. var style = {};
  440. applyPaintStyle(style, endpointStyle);
  441. if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle;
  442. applyPaintStyle(ctx, style);
  443. var ie = (/MSIE/.test(navigator.userAgent) && !window.opera);
  444. if (endpointStyle.gradient && !ie) {
  445. // first figure out which direction to run the gradient in (it depends on the orientation of the anchors)
  446. var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0;
  447. var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0;
  448. var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0;
  449. var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0;
  450. var g = ctx.createLinearGradient(x1,y1,x2,y2);
  451. for (var i = 0; i < endpointStyle.gradient.stops.length; i++)
  452. g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]);
  453. ctx.fillStyle = g;
  454. }
  455. ctx.beginPath();
  456. ctx.rect(0, 0, width, height);
  457. ctx.closePath();
  458. ctx.fill();
  459. };
  460. },
  461. /**
  462. * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object..
  463. */
  464. Image : function(params) {
  465. var self = this;
  466. this.img = new Image();
  467. var ready = false;
  468. this.img.onload = function() {
  469. self.ready = true;
  470. };
  471. this.img.src = params.url;
  472. var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {
  473. var width = self.img.width || endpointStyle.width;
  474. var height = self.img.height || endpointStyle.height;
  475. var x = anchorPoint[0] - (width/2);
  476. var y = anchorPoint[1] - (height/2);
  477. jsPlumb.sizeCanvas(canvas, x, y, width, height);
  478. var ctx = canvas.getContext('2d');
  479. ctx.drawImage(self.img,0,0);
  480. };
  481. this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {
  482. if (self.ready) {
  483. actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle)
  484. }
  485. else
  486. window.setTimeout(function() {
  487. self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle);
  488. }, 200);
  489. };
  490. }
  491. },
  492. /**
  493. * establishes a connection between two elements.
  494. * @param params object containing setup for the connection. see documentation.
  495. */
  496. connect : function(params) {
  497. var jpc = new jsPlumbConnection(params);
  498. var data = { source:jpc.sourceId, target:jpc.targetId, jpc:jpc };
  499. connectionPairs.push(data);
  500. var addToList = function(elId, jpc) {
  501. var l = connections[elId];
  502. if (l == null) {
  503. l = [];
  504. connections[elId] = l;
  505. }
  506. l.push(jpc);
  507. };
  508. // register this connection.
  509. addToList(jpc.sourceId, jpc);
  510. addToList(jpc.targetId, jpc);
  511. },
  512. /**
  513. * Remove one connection to an element.
  514. * @param sourceId id of the first window in the connection
  515. * @param targetId id of the second window in the connection
  516. */
  517. detach : function(sourceId, targetId) {
  518. var jpcs = connections[sourceId];
  519. var idx = -1;
  520. for (var i = 0; i < jpcs.length; i++) {
  521. if ((jpcs[i].sourceId == sourceId && jpcs[i].targetId == targetId) || (jpcs[i].targetId == sourceId && jpcs[i].sourceId == targetId)) {
  522. removeCanvas(jpcs[i].canvas);
  523. if (jpcs[i].drawEndpoints) {
  524. removeCanvas(jpcs[i].targetEndpointCanvas);
  525. removeCanvas(jpcs[i].sourceEndpointCanvas);
  526. }
  527. idx = i;
  528. break;
  529. }
  530. }
  531. if (idx != -1)
  532. jpcs.splice(idx, 1);
  533. // todo - dragging? if no more connections for an object turn off dragging by default, but
  534. // allow an override on it?
  535. },
  536. /**
  537. * remove all an element's connections.
  538. */
  539. detachAll : function(elId) {
  540. var jpcs = connections[elId];
  541. for (var i = 0; i < jpcs.length; i++) {
  542. removeCanvas(jpcs[i].canvas);
  543. if (jpcs[i].drawEndpoints) {
  544. removeCanvas(jpcs[i].targetEndpointCanvas);
  545. removeCanvas(jpcs[i].sourceEndpointCanvas);
  546. }
  547. }
  548. delete connections[elId];
  549. connections[elId] = [];
  550. },
  551. /**
  552. * remove all connections.
  553. */
  554. detachEverything : function() {
  555. for (var elId in connections) {
  556. var jpcs = connections[elId];
  557. if (jpcs.length) {
  558. try {
  559. for (var i = 0; i < jpcs.length; i++) {
  560. removeCanvas(jpcs[i].canvas);
  561. if (jpcs[i].drawEndpoints) {
  562. removeCanvas(jpcs[i].targetEndpointCanvas);
  563. removeCanvas(jpcs[i].sourceEndpointCanvas);
  564. }
  565. }
  566. } catch (e) { }
  567. }
  568. }
  569. delete connections;
  570. connections = [];
  571. },
  572. getConnections : function(elId) {
  573. return connections[elId];
  574. },
  575. /**
  576. * Set an element's connections to be hidden.
  577. */
  578. hide : function(elId) {
  579. setVisible(elId, "none");
  580. },
  581. /**
  582. * Creates an anchor with the given params.
  583. * x - the x location of the anchor as a percentage of the total width.
  584. * y - the y location of the anchor as a percentage of the total height.
  585. * orientation - an [x,y] array indicating the general direction a connection from the anchor should go in.
  586. * 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].
  587. */
  588. makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) {
  589. // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use
  590. // 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.
  591. var params = {};
  592. if (arguments.length == 1) $.extend(params, x);
  593. else {
  594. params = {x:x, y:y};
  595. if (arguments.length >= 4) {
  596. params.orientation = [arguments[2], arguments[3]];
  597. }
  598. if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]];
  599. }
  600. return new Anchor(params);
  601. },
  602. /**
  603. * repaint element and its connections. element may be an id or the actual jQuery object.
  604. * this method gets new sizes for the elements before painting anything.
  605. */
  606. repaint : function(el) {
  607. var _repaint = function(el, elId) {
  608. var jpcs = connections[elId];
  609. var idx = -1;
  610. var loc = {'absolutePosition': el.offset()};
  611. for (var i = 0; i < jpcs.length; i++) {
  612. jpcs[i].paint(elId, loc, true);
  613. }
  614. };
  615. var _processElement = function(el) {
  616. var ele = typeof(el)=='string' ? $("#" + el) : el;
  617. var eleId = ele.attr("id");
  618. _repaint(ele, eleId);
  619. };
  620. // TODO: support a jQuery result object too!
  621. // support both lists...
  622. if (typeof el =='object') {
  623. for (var i = 0; i < el.length; i++)
  624. _processElement(el[i]);
  625. } // ...and single strings.
  626. else _processElement(el);
  627. },
  628. /**
  629. * repaint all connections.
  630. */
  631. repaintEverything : function() {
  632. for (var elId in connections) {
  633. var jpcs = connections[elId];
  634. if (jpcs.length) {
  635. try {
  636. for (var i = 0; i < jpcs.length; i++) {
  637. jpcs[i].repaint();
  638. }
  639. } catch (e) { }
  640. }
  641. }
  642. },
  643. /**
  644. * sets/unsets automatic repaint on window resize.
  645. */
  646. setAutomaticRepaint : function(value) {
  647. automaticRepaint = value;
  648. },
  649. /**
  650. * Sets the default size jsPlumb will use for a new canvas (we create a square canvas so
  651. * one value is all that is required). This is a hack for IE, because ExplorerCanvas seems
  652. * to need for a canvas to be larger than what you are going to draw on it at initialisation
  653. * time. The default value of this is 1200 pixels, which is quite large, but if for some
  654. * reason you're drawing connectors that are bigger, you should adjust this value appropriately.
  655. */
  656. setDefaultNewCanvasSize : function(size) {
  657. DEFAULT_NEW_CANVAS_SIZE = size;
  658. },
  659. /**
  660. * Sets the function to fire when the window size has changed and a repaint was fired.
  661. */
  662. setRepaintFunction : function(f) {
  663. repaintFunction = f;
  664. },
  665. /**
  666. * Set an element's connections to be visible.
  667. */
  668. show : function(elId) {
  669. setVisible(elId, "block");
  670. },
  671. /**
  672. * helper to size a canvas.
  673. */
  674. sizeCanvas : function(canvas, x, y, w, h) {
  675. canvas.style.height = h + "px"; canvas.height = h;
  676. canvas.style.width = w + "px"; canvas.width = w;
  677. canvas.style.left = x + "px"; canvas.style.top = y + "px";
  678. },
  679. /**
  680. * Toggles visibility of an element's connections.
  681. */
  682. toggle : function(elId) {
  683. var jpcs = connections[elId];
  684. if (jpcs.length > 0)
  685. setVisible(elId, "none" == jpcs[0].canvas.style.display ? "block" : "none");
  686. },
  687. /**
  688. * Unloads jsPlumb, deleting all storage. You should call this
  689. */
  690. unload : function() {
  691. delete connections;
  692. delete offsets;
  693. delete sizes;
  694. }
  695. };
  696. // ************** connection
  697. // ****************************************
  698. /**
  699. * allowed params:
  700. * source: source element (string or a jQuery element) (required)
  701. * target: target element (string or a jQuery element) (required)
  702. * anchors: optional array of anchor placements. defaults to BottomCenter for source
  703. * and TopCenter for target.
  704. */
  705. var jsPlumbConnection = function(params) {
  706. // ************** get the source and target and register the connection. *******************
  707. var self = this;
  708. // get source and target as jQuery objects
  709. this.source = (typeof params.source == 'string') ? $("#" + params.source) : params.source;
  710. this.target = (typeof params.target == 'string') ? $("#" + params.target) : params.target;
  711. this.sourceId = $(this.source).attr("id");
  712. this.targetId = $(this.target).attr("id");
  713. this.drawEndpoints = params.drawEndpoints != null ? params.drawEndpoints : true;
  714. this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true;
  715. this.currentX = null, this.currentWidth = null, this.currentY = null, this.currentHeight = null;
  716. // get anchor
  717. this.anchors = params.anchors || jsPlumb.DEFAULT_ANCHORS || [jsPlumb.Anchors.BottomCenter, jsPlumb.Anchors.TopCenter];
  718. // make connector
  719. this.connector = params.connector || jsPlumb.DEFAULT_CONNECTOR || new jsPlumb.Connectors.Bezier();
  720. this.paintStyle = params.paintStyle || jsPlumb.DEFAULT_PAINT_STYLE;
  721. // init endpoints
  722. this.endpoints = [];
  723. if(!params.endpoints) params.endpoints = [null,null];
  724. this.endpoints[0] = params.endpoints[0] || params.endpoint || jsPlumb.DEFAULT_ENDPOINTS[0] || jsPlumb.DEFAULT_ENDPOINT || new jsPlumb.Endpoints.Dot();
  725. this.endpoints[1] = params.endpoints[1] || params.endpoint || jsPlumb.DEFAULT_ENDPOINTS[1] ||jsPlumb.DEFAULT_ENDPOINT || new jsPlumb.Endpoints.Dot();
  726. this.endpointStyles = [];
  727. if (!params.endpointStyles) params.endpointStyles = [null,null];
  728. this.endpointStyles[0] = params.endpointStyles[0] || params.endpointStyle || jsPlumb.DEFAULT_ENDPOINT_STYLES[0] || jsPlumb.DEFAULT_ENDPOINT_STYLE;
  729. this.endpointStyles[1] = params.endpointStyles[1] || params.endpointStyle || jsPlumb.DEFAULT_ENDPOINT_STYLES[1] || jsPlumb.DEFAULT_ENDPOINT_STYLE;
  730. offsets[this.sourceId] = this.source.offset();
  731. sizes[this.sourceId] = [this.source.outerWidth(), this.source.outerHeight()];
  732. offsets[this.targetId] = this.target.offset();
  733. sizes[this.targetId] = [this.target.outerWidth(), this.target.outerHeight()];
  734. // *************** create canvases on which the connection will be drawn ************
  735. var canvas = newCanvas(jsPlumb.connectorClass);
  736. this.canvas = canvas;
  737. // attach move and mouse out listeners
  738. $(canvas).mousemove(function(e) { canvasMouseOverHelper(self, e.pageX, e.pageY); });
  739. $(canvas).mouseout(function(e) { canvasMouseOverHelper(self, e.pageX, e.pageY); });
  740. // create endpoint canvases
  741. if (this.drawEndpoints) {
  742. this.sourceEndpointCanvas = newCanvas(jsPlumb.endpointClass);
  743. this.targetEndpointCanvas = newCanvas(jsPlumb.endpointClass);
  744. // sit them on top of the underlying element?
  745. if (this.endpointsOnTop) {
  746. $(this.sourceEndpointCanvas).css("zIndex", this.source.css("zIndex") + 1);
  747. $(this.targetEndpointCanvas).css("zIndex", this.target.css("zIndex") + 1);
  748. } else {
  749. $(this.sourceEndpointCanvas).css("zIndex", this.source.css("zIndex") - 1);
  750. $(this.targetEndpointCanvas).css("zIndex", this.target.css("zIndex") - 1);
  751. }
  752. }
  753. // ************** store the anchors
  754. /**
  755. * paints the connection.
  756. * @param elId Id of the element that is in motion
  757. * @param ui jQuery's event system ui object (present if we came from a drag to get here)
  758. * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted.
  759. */
  760. this.paint = function(elId, ui, recalc) {
  761. // if the moving object is not the source we must transpose the two references.
  762. var swap = !(elId == this.sourceId);
  763. var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId;
  764. var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0;
  765. if (this.canvas.getContext) {
  766. if (recalc) {
  767. // get the current sizes of the two elements.
  768. var s = $("#" + elId);
  769. var t = $("#" + tId);
  770. sizes[elId] = [s.outerWidth(), s.outerHeight()];
  771. sizes[tId] = [t.outerWidth(), t.outerHeight()];
  772. offsets[elId] = s.offset();
  773. offsets[tId] = t.offset();
  774. } else {
  775. // faster to use the ui element if it was passed in. offset is a fallback.
  776. // fix for change in 1.8 (absolutePosition renamed to offset). plugin is compatible with
  777. // 1.8 and 1.7.
  778. var pos = ui.absolutePosition || ui.offset;
  779. var anOffset = ui != null ? pos : $("#" + elId).offset();
  780. offsets[elId] = anOffset;
  781. }
  782. var myOffset = offsets[elId];
  783. var otherOffset = offsets[tId];
  784. var myWH = sizes[elId];
  785. var otherWH = sizes[tId];
  786. var ctx = canvas.getContext('2d');
  787. var sAnchorP = this.anchors[sIdx].compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH);
  788. var sAnchorO = this.anchors[sIdx].orientation;
  789. var tAnchorP = this.anchors[tIdx].compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH);
  790. var tAnchorO = this.anchors[tIdx].orientation;
  791. var dim = this.connector.compute(sAnchorP, tAnchorP, this.anchors[sIdx], this.anchors[tIdx], this.paintStyle.lineWidth);
  792. jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]);
  793. this.currentX = dim[0]; this.currentY = dim[1]; this.currentWidth = dim[2]; this.currentHeight = dim[3];
  794. applyPaintStyle(ctx, this.paintStyle);
  795. var ie = (/MSIE/.test(navigator.userAgent) && !window.opera);
  796. if (this.paintStyle.gradient && !ie) {
  797. var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]);
  798. for (var i = 0; i < this.paintStyle.gradient.stops.length; i++)
  799. g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]);
  800. ctx.strokeStyle = g;
  801. }
  802. this.connector.paint(dim, ctx);
  803. if (this.drawEndpoints) {
  804. var style = this.endpointStyle || this.paintStyle;
  805. var sourceCanvas = swap ? this.targetEndpointCanvas : this.sourceEndpointCanvas;
  806. var targetCanvas = swap ? this.sourceEndpointCanvas : this.targetEndpointCanvas;
  807. this.endpoints[swap ? 1 : 0].paint(sAnchorP, sAnchorO, sourceCanvas, this.endpointStyles[swap ? 1 : 0] || this.paintStyle, this.paintStyle);
  808. this.endpoints[swap ? 0 : 1].paint(tAnchorP, tAnchorO, targetCanvas, this.endpointStyles[swap ? 0 : 1] || this.paintStyle, this.paintStyle);
  809. }
  810. }
  811. };
  812. this.repaint = function() {
  813. this.paint(this.sourceId, null, true);
  814. };
  815. // dragging
  816. var draggable = params.draggable == null ? true : params.draggable;
  817. if (draggable) {
  818. var dragOptions = params.dragOptions || jsPlumb.DEFAULT_DRAG_OPTIONS;
  819. var dragCascade = dragOptions.drag || function(e,u) {};
  820. var initDrag = function(element, dragFunc) {
  821. var opts = {};
  822. for (var i in dragOptions) {
  823. opts[i] = dragOptions[i];
  824. }
  825. opts.drag = dragFunc;
  826. element.draggable(opts);
  827. };
  828. initDrag(this.source, function(event, ui) {
  829. drag(self.source, ui);
  830. dragCascade(event, ui);
  831. });
  832. initDrag(this.target, function(event, ui) {
  833. drag(self.target, ui);
  834. dragCascade(event, ui);
  835. });
  836. }
  837. // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not.
  838. if (this.source.resize) {
  839. this.source.resize(function(e) {
  840. jsPlumb.repaint(self.sourceId);
  841. });
  842. }
  843. // finally, draw it.
  844. var o = this.source.offset();
  845. this.paint(this.sourceId, {'offset': this.source.offset()});
  846. };
  847. })();
  848. // jQuery plugin code
  849. (function($){
  850. $.fn.plumb = function(options) {
  851. var defaults = { };
  852. var options = $.extend(defaults, options);
  853. return this.each(function()
  854. {
  855. var obj = $(this);
  856. var params = {};
  857. params.source = obj;
  858. for (var i in options) {
  859. params[i] = options[i];
  860. }
  861. jsPlumb.connect(params);
  862. });
  863. };
  864. $.fn.detach = function(options) {
  865. return this.each(function()
  866. {
  867. var id = $(this).attr("id");
  868. if (typeof options == 'string') options = [options];
  869. for (var i = 0; i < options.length; i++)
  870. jsPlumb.detach(id, options[i]);
  871. });
  872. };
  873. $.fn.detachAll = function(options) {
  874. return this.each(function()
  875. {
  876. var id = $(this).attr("id");
  877. jsPlumb.detachAll(id);
  878. });
  879. };
  880. })(jQuery);