jsPlumb-0.0.4-RC4.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  1. /*
  2. * jsPlumb 0.0.4-RC2
  3. *
  4. * gradients are new in this version. and many things have been renamed to more pleasing looking words.
  5. *
  6. */
  7. if (!Array.prototype.indexOf) {
  8. Array.prototype.indexOf = function( v, b, s ) {
  9. for( var i = +b || 0, l = this.length; i < l; i++ ) {
  10. if( this[i]===v || s && this[i]==v ) { return i; }
  11. }
  12. return -1;
  13. };
  14. }
  15. (function() {
  16. var connections = {};
  17. var offsets = [];
  18. var sizes = [];
  19. 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.)
  20. /**
  21. * applies all the styles to the given context. this just wraps the $.extend function.
  22. *
  23. * @param context
  24. * @param styles
  25. */
  26. var applyPaintStyle = function(context, styles) {
  27. $.extend(context, styles);
  28. };
  29. /**
  30. * Handles the dragging of an element.
  31. * @param element jQuery element
  32. * @param ui UI object from jQuery's event system
  33. */
  34. var drag = function(element, ui) {
  35. var id = element.attr("id");
  36. var l = connections[id];
  37. for (var i = 0; i < l.length; i++)
  38. l[i].paint(id, ui);
  39. };
  40. /**
  41. * private method to do the business of hiding/showing.
  42. * @param elId Id of the element in question
  43. * @param state String specifying a value for the css 'display' property ('block' or 'none').
  44. */
  45. var setVisible = function(elId, state) {
  46. var jpcs = connections[elId];
  47. for (var i = 0; i < jpcs.length; i++) {
  48. jpcs[i].canvas.style.display=state;
  49. if (jpcs[i].drawEndpoints) {
  50. jpcs[i].sourceEndpointCanvas.style.display=state;
  51. jpcs[i].targetEndpointCanvas.style.display=state;
  52. }
  53. }
  54. };
  55. /**
  56. * helper to size a canvas.
  57. */
  58. var sizeCanvas = function(canvas, x, y, w, h) {
  59. canvas.style.height = h + "px"; canvas.height = h;
  60. canvas.style.width = w + "px"; canvas.width = w;
  61. canvas.style.left = x + "px"; canvas.style.top = y + "px";
  62. };
  63. /**
  64. * helper to create a canvas.
  65. * @param clazz optional class name for the canvas.
  66. */
  67. var newCanvas = function(clazz) {
  68. var canvas = document.createElement("canvas");
  69. document.body.appendChild(canvas);
  70. canvas.style.position="absolute";
  71. if (clazz) { canvas.className=clazz; }
  72. if (/MSIE/.test(navigator.userAgent) && !window.opera) {
  73. // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels
  74. // is not big enough for the biggest connector/endpoint canvas you have at startup.
  75. sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE);
  76. canvas = G_vmlCanvasManager.initElement(canvas);
  77. }
  78. return canvas;
  79. };
  80. /**
  81. * helper to remove a canvas from the DOM.
  82. */
  83. var removeCanvas = function(canvas) {
  84. if (canvas != null) document.body.removeChild(canvas);
  85. };
  86. /**
  87. * generic anchor - can be situated anywhere. params should contain three values, and may optionally have an 'offsets' argument:
  88. *
  89. * x - the x location of the anchor as a percentage of the total width.
  90. * y - the y location of the anchor as a percentage of the total height.
  91. * orientation - an [x,y] array indicating the general direction a connection from the anchor should go in.
  92. * offsets - an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null.
  93. *
  94. */
  95. var Anchor = function(params) {
  96. var self = this;
  97. this.x = params.x || 0; this.y = params.y || 0; this.orientation = params.orientation || [0,0]; this.offsets = params.offsets || [0,0];
  98. this.compute = function(xy, wh, txy, twh) {
  99. return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ];
  100. }
  101. };
  102. /**
  103. * jsPlumb public API
  104. */
  105. var jsPlumb = window.jsPlumb = {
  106. connectorClass : '_jsPlumb_connector',
  107. endpointClass : '_jsPlumb_endpoint',
  108. DEFAULT_PAINT_STYLE : { lineWidth : 10, strokeStyle : "red" },
  109. DEFAULT_ENDPOINT_STYLE : { fillStyle : null }, // meaning it will be derived from the stroke style of the connector.
  110. DEFAULT_ENDPOINT_STYLES : [ null, null ], // meaning it will be derived from the stroke style of the connector.
  111. DEFAULT_DRAG_OPTIONS : { },
  112. DEFAULT_CONNECTOR : null,
  113. DEFAULT_ENDPOINT : null,
  114. DEFAULT_ENDPOINTS : [null, null], // new in 0.0.4, the ability to specify diff. endpoints. DEFAULT_ENDPOINT is here for backwards compatibility.
  115. /**
  116. * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance
  117. * of Anchor that has been configured appropriately.
  118. *
  119. * You can write your own one of these; you
  120. * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this:
  121. *
  122. * jsPlumb.Anchors.MY_ANCHOR = {
  123. * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; },
  124. * orientation : [ox, oy]
  125. * };
  126. *
  127. * compute takes the [x,y] position of the top left corner of the anchored element,
  128. * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to,
  129. * and returns where the anchor should be located.
  130. *
  131. * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor
  132. * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference,
  133. * -1 means go in a negative direction for the given axis, and 1 means go in a positive
  134. * direction. so consider a TOP_CENTER anchor: the orientation matrix for it is [0,-1],
  135. * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example,
  136. * the curve would start out going in that direction, before bending towards the target anchor.
  137. */
  138. Anchors :
  139. {
  140. TOP_CENTER : new Anchor({x:0.5, y:0, orientation:[0,-1] }),
  141. BOTTOM_CENTER : new Anchor({x:0.5, y:1, orientation:[0, 1] }),
  142. LEFT_MIDDLE: new Anchor({x:0, y:0.5, orientation:[-1,0] }),
  143. RIGHT_MIDDLE : new Anchor({x:1, y:0.5, orientation:[1,0] }),
  144. CENTER : new Anchor({x:0.5, y:0.5, orientation:[0,0] }),
  145. TOP_RIGHT : new Anchor({x:1, y:0, orientation:[0,-1] }),
  146. BOTTOM_RIGHT : new Anchor({x:1, y:1, orientation:[0,1] }),
  147. TOP_LEFT : new Anchor({x:0, y:0, orientation:[0,-1] }),
  148. BOTTOM_LEFT : new Anchor({x:0, y:1, orientation:[0,1] })
  149. },
  150. /**
  151. * Types of connectors, eg. Straight, Bezier.
  152. */
  153. Connectors :
  154. {
  155. /**
  156. * The Straight connector draws a simple straight line between the two anchor points.
  157. */
  158. Straight : function() {
  159. var self = this;
  160. /**
  161. * Computes the new size and position of the canvas.
  162. * @param sourceAnchor Absolute position on screen of the source object's anchor.
  163. * @param targetAnchor Absolute position on screen of the target object's anchor.
  164. * @param positionMatrix Indicates the relative positions of the left,top of the
  165. * two plumbed objects. so [0,0] indicates that the source is to the left of, and
  166. * above, the target. [1,0] means the source is to the right and above. [0,1] means
  167. * the source is to the left and below. [1,1] means the source is to the right
  168. * and below. this is used to figure out which direction to draw the connector in.
  169. * @returns an array of positioning information. the first two values are
  170. * the [left, top] absolute position the canvas should be placed on screen. the
  171. * next two values are the [width,height] the canvas should be. after that each
  172. * Connector can put whatever it likes into the array:it will be passed back in
  173. * to the paint call. This particular function stores the origin and destination of
  174. * the line it is going to draw. a more involved implementation, like a Bezier curve,
  175. * would store the control point info in this array too.
  176. */
  177. this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
  178. var w = Math.abs(sourcePos[0] - targetPos[0]);
  179. var h = Math.abs(sourcePos[1] - targetPos[1]);
  180. var widthAdjusted = false, heightAdjusted = false;
  181. // these are padding to ensure the whole connector line appears
  182. var xo = 0.45 * w, yo = 0.45 * h;
  183. // these are padding to ensure the whole connector line appears
  184. w *= 1.9; h *=1.9;
  185. var x = Math.min(sourcePos[0], targetPos[0]) - xo;
  186. var y = Math.min(sourcePos[1], targetPos[1]) - yo;
  187. if (w < 2 * lineWidth) {
  188. // minimum size is 2 * line Width
  189. w = 2 * lineWidth;
  190. // if we set this then we also have to place the canvas
  191. x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth;
  192. xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2;//lineWidth/2;//lineWidth / 2;
  193. }
  194. if (h < 2 * lineWidth) {
  195. // minimum size is 2 * line Width
  196. h = 2 * lineWidth;
  197. // if we set this then we also have to place the canvas
  198. y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth;
  199. yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2;//lineWidth/2;//lineWidth / 2;
  200. }
  201. // here we check to see if the delta was very small and so the line in
  202. // one direction can be considered straight.
  203. var sx = sourcePos[0] < targetPos[0] ? w-xo : xo;
  204. var sy = sourcePos[1] < targetPos[1] ? h-yo : yo;
  205. var tx = sourcePos[0] < targetPos[0] ? xo : w-xo;
  206. var ty = sourcePos[1] < targetPos[1] ? yo : h-yo;
  207. var retVal = [ x, y, w, h, sx, sy, tx, ty ];
  208. // return [canvasX, canvasY, canvasWidth, canvasHeight,
  209. // sourceX, sourceY, targetX, targetY]
  210. return retVal;
  211. };
  212. this.paint = function(dimensions, ctx)
  213. {
  214. ctx.beginPath();
  215. ctx.moveTo(dimensions[4], dimensions[5]);
  216. ctx.lineTo(dimensions[6], dimensions[7]);
  217. ctx.stroke();
  218. };
  219. },
  220. /**
  221. * This Connector draws a Bezier curve with two control points.
  222. * @param curviness How 'curvy' you want the curve to be! This is a directive for the
  223. * placement of control points, not endpoints of the curve, so your curve does not
  224. * actually touch the given point, but it has the tendency to lean towards it. the larger
  225. * this value, the greater the curve is pulled from a straight line.
  226. *
  227. * a future implementation of this could take the control points as arguments, rather
  228. * than fixing the curve to one basic shape.
  229. */
  230. Bezier : function(curviness) {
  231. var self = this;
  232. this.majorAnchor = curviness || 150;
  233. this.minorAnchor = 10;
  234. this._findControlPoint = function(point, anchor1Position, anchor2Position, anchor1, anchor2) {
  235. var p = [];
  236. var ma = self.majorAnchor, mi = self.minorAnchor;
  237. if (anchor1.orientation[0] == 0) // X
  238. p.push(anchor1Position[0] < anchor2Position[0] ? point[0] + mi : point[0] - mi);
  239. else p.push(point[0] - (ma * anchor1.orientation[0]));
  240. if (anchor1.orientation[1] == 0) // Y
  241. p.push(anchor1Position[1] < anchor2Position[1] ? point[1] + mi : point[1] - mi);
  242. else p.push(point[1] + (ma * anchor2.orientation[1]));
  243. return p;
  244. };
  245. this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth)
  246. {
  247. lineWidth = lineWidth || 0;
  248. var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth;
  249. var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2);
  250. var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2);
  251. var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2);
  252. var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor);
  253. var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor);
  254. var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2);
  255. var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2);
  256. if (maxx > w) w = maxx;
  257. if (minx < 0) {
  258. canvasX += minx; var ox = Math.abs(minx);
  259. w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox;
  260. }
  261. var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2);
  262. var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2);
  263. if (maxy > h) h = maxy;
  264. if (miny < 0) {
  265. canvasY += miny; var oy = Math.abs(miny);
  266. h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy;
  267. }
  268. // return [ canvasx, canvasy, canvasWidth, canvasHeight,
  269. // sourceX, sourceY, targetX, targetY,
  270. // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y
  271. return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ];
  272. };
  273. this.paint = function(d, ctx) {
  274. ctx.beginPath();
  275. ctx.moveTo(d[4],d[5]);
  276. ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]);
  277. ctx.stroke();
  278. }
  279. }
  280. },
  281. /**
  282. * Types of endpoint UIs. we supply three - a circle of default radius 10px, a rectangle of
  283. * default size 20x20, and an image (with no default). you can supply others of these if you want to - see the documentation
  284. * for a howto.
  285. */
  286. Endpoints : {
  287. /**
  288. * a round endpoint, with default radius 10 pixels.
  289. */
  290. Dot : function(params) {
  291. params = params || { radius:10 };
  292. var self = this;
  293. this.radius = params.radius;
  294. this.paint = function(anchorPoint, canvas, endpointStyle, connectorPaintStyle) {
  295. var radius = endpointStyle.radius || self.radius;
  296. var x = anchorPoint[0] - radius;
  297. var y = anchorPoint[1] - radius;
  298. sizeCanvas(canvas, x, y, radius * 2, radius * 2);
  299. var ctx = canvas.getContext('2d');
  300. var style = {};
  301. applyPaintStyle(style, endpointStyle);
  302. if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle;
  303. applyPaintStyle(ctx, style);
  304. ctx.beginPath();
  305. ctx.arc(radius, radius, radius, 0, Math.PI*2, true);
  306. ctx.closePath();
  307. ctx.fill();
  308. };
  309. },
  310. /**
  311. * A Rectangular endpoint, with default size 20x20.
  312. */
  313. Rectangle : function(params) {
  314. params = params || { width:20, height:20 };
  315. var self = this;
  316. this.width = params.width;
  317. this.height = params.height;
  318. this.paint = function(anchorPoint, canvas, endpointStyle, connectorPaintStyle) {
  319. var width = endpointStyle.width || self.width;
  320. var height = endpointStyle.height || self.height;
  321. var x = anchorPoint[0] - (width/2);
  322. var y = anchorPoint[1] - (height/2);
  323. sizeCanvas(canvas, x, y, width, height);
  324. var ctx = canvas.getContext('2d');
  325. //todo: the fillStyle needs some thought. we want to support a few options:
  326. // 1. nothing supplied; use the stroke color or the default if no stroke color.
  327. // 2. a fill color supplied - use it
  328. // 3. a gradient supplied - use it
  329. // 4. setting the endpoint to the same color as the bg of the element it is attached to.
  330. var style = {};
  331. applyPaintStyle(style, endpointStyle);
  332. if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle;
  333. applyPaintStyle(ctx, style);
  334. ctx.beginPath();
  335. ctx.rect(0, 0, width, height);
  336. ctx.closePath();
  337. ctx.fill();
  338. };
  339. },
  340. /**
  341. * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object..
  342. */
  343. Image : function(params) {
  344. var self = this;
  345. this.img = new Image();
  346. this.img.src = params.url;
  347. this.paint = function(anchorPoint, canvas, endpointStyle, connectorPaintStyle) {
  348. var width = self.img.width || endpointStyle.width;
  349. var height = self.img.height || endpointStyle.height;
  350. var x = anchorPoint[0] - (width/2);
  351. var y = anchorPoint[1] - (height/2);
  352. sizeCanvas(canvas, x, y, width, height);
  353. var ctx = canvas.getContext('2d');
  354. ctx.drawImage(self.img,0,0);
  355. };
  356. }
  357. },
  358. /**
  359. * establishes a connection between two elements.
  360. * @param params object containing setup for the connection. see documentation.
  361. */
  362. connect : function(params) {
  363. var jpc = new jsPlumbConnection(params);
  364. var key = jpc.sourceId + "_" + jpc.targetId;
  365. connections[key] = jpc;
  366. var addToList = function(elId, jpc) {
  367. var l = connections[elId];
  368. if (l == null) {
  369. l = [];
  370. connections[elId] = l;
  371. }
  372. l.push(jpc);
  373. };
  374. // register this connection.
  375. addToList(jpc.sourceId, jpc);
  376. addToList(jpc.targetId, jpc);
  377. },
  378. /**
  379. * Remove one connection to an element.
  380. * @param sourceId id of the first window in the connection
  381. * @param targetId id of the second window in the connection
  382. */
  383. detach : function(sourceId, targetId) {
  384. var jpcs = connections[sourceId];
  385. var idx = -1;
  386. for (var i = 0; i < jpcs.length; i++) {
  387. if ((jpcs[i].sourceId == sourceId && jpcs[i].targetId == targetId) || (jpcs[i].targetId == sourceId && jpcs[i].sourceId == targetId)) {
  388. removeCanvas(jpcs[i].canvas);
  389. if (jpcs[i].drawEndpoints) {
  390. removeCanvas(jpcs[i].targetEndpointCanvas);
  391. removeCanvas(jpcs[i].sourceEndpointCanvas);
  392. }
  393. idx = i;
  394. break;
  395. }
  396. }
  397. if (idx != -1)
  398. jpcs.splice(idx, 1);
  399. // todo - dragging? if no more connections for an object turn off dragging by default, but
  400. // allow an override on it?
  401. },
  402. /**
  403. * remove all an element's connections.
  404. */
  405. detachAll : function(elId) {
  406. var jpcs = connections[elId];
  407. for (var i = 0; i < jpcs.length; i++) {
  408. removeCanvas(jpcs[i].canvas);
  409. if (jpcs[i].drawEndpoints) {
  410. removeCanvas(jpcs[i].targetEndpointCanvas);
  411. removeCanvas(jpcs[i].sourceEndpointCanvas);
  412. }
  413. }
  414. connections[elId] = [];
  415. },
  416. /**
  417. * Set an element's connections to be hidden.
  418. */
  419. hide : function(elId) {
  420. setVisible(elId, "none");
  421. },
  422. /**
  423. * Creates an anchor with the given params.
  424. * x - the x location of the anchor as a percentage of the total width.
  425. * y - the y location of the anchor as a percentage of the total height.
  426. * orientation - an [x,y] array indicating the general direction a connection from the anchor should go in.
  427. * 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].
  428. */
  429. makeAnchor : function(params) {
  430. return new Anchor(params);
  431. },
  432. /**
  433. * repaint element and its connections. element may be an id or the actual jQuery object.
  434. * this method gets new sizes for the elements before painting anything.
  435. */
  436. repaint : function(el) {
  437. var _repaint = function(el, elId) {
  438. var jpcs = connections[elId];
  439. var idx = -1;
  440. var loc = {'absolutePosition': el.offset()};
  441. for (var i = 0; i < jpcs.length; i++) {
  442. jpcs[i].paint(elId, loc, true);
  443. }
  444. };
  445. var _processElement = function(el) {
  446. var ele = typeof(el)=='string' ? $("#" + el) : el;
  447. var eleId = ele.attr("id");
  448. _repaint(ele, eleId);
  449. };
  450. // TODO: support a jQuery result object too!
  451. // support both lists...
  452. if (typeof el =='object') {
  453. for (var i = 0; i < el.length; i++)
  454. _processElement(el[i]);
  455. } // ...and single strings.
  456. else _processElement(el);
  457. },
  458. /**
  459. * Sets the default size jsPlumb will use for a new canvas (we create a square canvas so
  460. * one value is all that is required). This is a hack for IE, because ExplorerCanvas seems
  461. * to need for a canvas to be larger than what you are going to draw on it at initialisation
  462. * time. The default value of this is 1200 pixels, which is quite large, but if for some
  463. * reason you're drawing connectors that are bigger, you should adjust this value appropriately.
  464. */
  465. setDefaultNewCanvasSize : function(size) {
  466. DEFAULT_NEW_CANVAS_SIZE = size;
  467. },
  468. /**
  469. * Set an element's connections to be visible.
  470. */
  471. show : function(elId) {
  472. setVisible(elId, "block");
  473. },
  474. /**
  475. * Toggles visibility of an element's connections.
  476. */
  477. toggle : function(elId) {
  478. var jpcs = connections[elId];
  479. if (jpcs.length > 0)
  480. setVisible(elId, "none" == jpcs[0].canvas.style.display ? "block" : "none");
  481. }
  482. };
  483. // ************** connection
  484. // ****************************************
  485. /**
  486. * allowed params:
  487. * source: source element (string or a jQuery element) (required)
  488. * target: target element (string or a jQuery element) (required)
  489. * anchors: optional array of anchor placements. defaults to BOTTOM_CENTER for source
  490. * and TOP_CENTER for target.
  491. */
  492. var jsPlumbConnection = function(params) {
  493. // ************** get the source and target and register the connection. *******************
  494. var self = this;
  495. // get source and target as jQuery objects
  496. this.source = (typeof params.source == 'string') ? $("#" + params.source) : params.source;
  497. this.target = (typeof params.target == 'string') ? $("#" + params.target) : params.target;
  498. this.sourceId = $(this.source).attr("id");
  499. this.targetId = $(this.target).attr("id");
  500. this.drawEndpoints = params.drawEndpoints != null ? params.drawEndpoints : true;
  501. this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true;
  502. // get anchor
  503. this.anchors = params.anchors || jsPlumb.DEFAULT_ANCHORS || [jsPlumb.Anchors.BOTTOM_CENTER, jsPlumb.Anchors.TOP_CENTER];
  504. // make connector
  505. this.connector = params.connector || jsPlumb.DEFAULT_CONNECTOR || new jsPlumb.Connectors.Bezier();
  506. this.paintStyle = params.paintStyle || jsPlumb.DEFAULT_PAINT_STYLE;
  507. // init endpoints
  508. this.endpoints = [];
  509. if(!params.endpoints) params.endpoints = [null,null];
  510. this.endpoints[0] = params.endpoints[0] || params.endpoint || jsPlumb.DEFAULT_ENDPOINTS[0] || jsPlumb.DEFAULT_ENDPOINT || new jsPlumb.Endpoints.Dot();
  511. this.endpoints[1] = params.endpoints[1] || params.endpoint || jsPlumb.DEFAULT_ENDPOINTS[1] ||jsPlumb.DEFAULT_ENDPOINT || new jsPlumb.Endpoints.Dot();
  512. this.endpointStyles = [];
  513. if (!params.endpointStyles) params.endpointStyles = [null,null];
  514. this.endpointStyles[0] = params.endpointStyles[0] || params.endpointStyle || jsPlumb.DEFAULT_ENDPOINT_STYLES[0] || jsPlumb.DEFAULT_ENDPOINT_STYLE;
  515. this.endpointStyles[1] = params.endpointStyles[1] || params.endpointStyle || jsPlumb.DEFAULT_ENDPOINT_STYLES[1] || jsPlumb.DEFAULT_ENDPOINT_STYLE;
  516. offsets[this.sourceId] = this.source.offset();
  517. sizes[this.sourceId] = [this.source.outerWidth(), this.source.outerHeight()];
  518. offsets[this.targetId] = this.target.offset();
  519. sizes[this.targetId] = [this.target.outerWidth(), this.target.outerHeight()];
  520. // *************** create canvases on which the connection will be drawn ************
  521. var canvas = newCanvas(jsPlumb.connectorClass);
  522. this.canvas = canvas;
  523. // create endpoint canvases
  524. if (this.drawEndpoints) {
  525. this.sourceEndpointCanvas = newCanvas(jsPlumb.endpointClass);
  526. this.targetEndpointCanvas = newCanvas(jsPlumb.endpointClass);
  527. // sit them on top of the underlying element?
  528. if (this.endpointsOnTop) {
  529. $(this.sourceEndpointCanvas).css("zIndex", this.source.css("zIndex") + 1);
  530. $(this.targetEndpointCanvas).css("zIndex", this.target.css("zIndex") + 1);
  531. }
  532. }
  533. // ************** store the anchors
  534. /**
  535. * paints the connection.
  536. * @param elId Id of the element that is in motion
  537. * @param ui jQuery's event system ui object (present if we came from a drag to get here)
  538. * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted.
  539. */
  540. this.paint = function(elId, ui, recalc) {
  541. // if the moving object is not the source we must transpose the two references.
  542. var swap = !(elId == this.sourceId);
  543. var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId;
  544. var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0;
  545. if (this.canvas.getContext) {
  546. // faster to use the ui element if it was passed in. offset is a fallback.
  547. var myOffset = ui != null ? ui.absolutePosition : $("#" + elId).offset();
  548. offsets[elId] = myOffset;
  549. var otherOffset = offsets[tId];
  550. if (recalc) {
  551. // a test. this gets the dynamic sizes, not the sizes we got when first registered.
  552. // slower but probably better really. well - almost certainly. if we're going to be
  553. // able to respond to window sizes changing. perhaps we can find ways of reducing how
  554. // often we call this.
  555. var s = $("#" + elId);
  556. var t = $("#" + tId);
  557. sizes[elId] = [s.outerWidth(), s.outerHeight()];
  558. sizes[tId] = [t.outerWidth(), t.outerHeight()];
  559. }
  560. var myWH = sizes[elId];
  561. var otherWH = sizes[tId];
  562. var ctx = canvas.getContext('2d');
  563. var sAnchorP = this.anchors[sIdx].compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH);
  564. var tAnchorP = this.anchors[tIdx].compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH);
  565. var dim = this.connector.compute(sAnchorP, tAnchorP, this.anchors[sIdx], this.anchors[tIdx], this.paintStyle.lineWidth);
  566. sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]);
  567. applyPaintStyle(ctx, this.paintStyle);
  568. // gradient experiment.
  569. var ie = (/MSIE/.test(navigator.userAgent) && !window.opera);
  570. if (!ie && this.paintStyle.gradient) {
  571. var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]);
  572. for (var i = 0; i < this.paintStyle.gradient.stops.length; i++)
  573. g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]);
  574. ctx.strokeStyle = g;
  575. }
  576. this.connector.paint(dim, ctx);
  577. if (this.drawEndpoints) {
  578. // todo support endpoints here.
  579. var style = this.endpointStyle || this.paintStyle;
  580. var sourceCanvas = swap ? this.targetEndpointCanvas : this.sourceEndpointCanvas;
  581. var targetCanvas = swap ? this.sourceEndpointCanvas : this.targetEndpointCanvas;
  582. this.endpoints[swap ? 1 : 0].paint(sAnchorP, sourceCanvas, this.endpointStyles[swap ? 1 : 0] || this.paintStyle, this.paintStyle);
  583. this.endpoints[swap ? 0 : 1].paint(tAnchorP, targetCanvas, this.endpointStyles[swap ? 0 : 1] || this.paintStyle, this.paintStyle);
  584. }
  585. }
  586. };
  587. // dragging
  588. var draggable = params.draggable == null ? true : params.draggable;
  589. if (draggable) {
  590. var dragOptions = params.dragOptions || jsPlumb.DEFAULT_DRAG_OPTIONS;
  591. var dragCascade = dragOptions.drag || function(e,u) {};
  592. var initDrag = function(element, dragFunc) {
  593. var opts = {};
  594. for (var i in dragOptions) {
  595. opts[i] = dragOptions[i];
  596. }
  597. opts.drag = dragFunc;
  598. element.draggable(opts);
  599. };
  600. initDrag(this.source, function(event, ui) {
  601. drag(self.source, ui);
  602. dragCascade(event, ui);
  603. });
  604. initDrag(this.target, function(event, ui) {
  605. drag(self.target, ui);
  606. dragCascade(event, ui);
  607. });
  608. }
  609. // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not.
  610. if (this.source.resize) {
  611. this.source.resize(function(e) {
  612. jsPlumb.repaint(self.sourceId);
  613. });
  614. }
  615. // finally, draw it.
  616. var o = this.source.offset();
  617. this.paint(this.sourceId, {'absolutePosition': this.source.offset()});
  618. };
  619. })();
  620. // jQuery plugin code
  621. (function($){
  622. $.fn.plumb = function(options) {
  623. var defaults = { };
  624. var options = $.extend(defaults, options);
  625. return this.each(function()
  626. {
  627. var obj = $(this);
  628. var params = {};
  629. params.source = obj;
  630. for (var i in options) {
  631. params[i] = options[i];
  632. }
  633. jsPlumb.connect(params);
  634. });
  635. };
  636. $.fn.detach = function(options) {
  637. return this.each(function()
  638. {
  639. var id = $(this).attr("id");
  640. if (typeof options == 'string') options = [options];
  641. for (var i = 0; i < options.length; i++)
  642. jsPlumb.detach(id, options[i]);
  643. });
  644. };
  645. $.fn.detachAll = function(options) {
  646. return this.each(function()
  647. {
  648. var id = $(this).attr("id");
  649. jsPlumb.detachAll(id);
  650. });
  651. };
  652. })(jQuery);