jquery.jsPlumb-1.1.0.js 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275
  1. /*
  2. * jsPlumb 1.1.0
  3. *
  4. * Provides a way to visually connect elements on an HTML page.
  5. *
  6. * 1.1.0 contains the first released version of the code that supports draggable connectors.
  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 ie = (/MSIE/.test(navigator.userAgent) && !window.opera);
  22. var log = null;
  23. var repaintFunction = function() { jsPlumb.repaintEverything(); };
  24. var automaticRepaint = true;
  25. function repaintEverything() {
  26. if (automaticRepaint)
  27. repaintFunction();
  28. };
  29. var resizeTimer = null;
  30. $(window).bind('resize', function() {
  31. if (resizeTimer) clearTimeout(resizeTimer);
  32. resizeTimer = setTimeout(repaintEverything, 100);
  33. });
  34. /**
  35. * map of element id -> endpoint lists. an element can have an arbitrary number of endpoints on it,
  36. * and not all of them have to be connected to anything.
  37. */
  38. var endpointsByElement = {};
  39. var offsets = [];
  40. var floatingConnections = {};
  41. var draggableStates = {};
  42. var _draggableByDefault = true;
  43. var sizes = [];
  44. var _jsPlumbContextNode = null;
  45. 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.)
  46. /**
  47. * helper method to add an item to a list, creating the list if it does not yet exist.
  48. */
  49. var _addToList = function(map, key, value) {
  50. var l = map[key];
  51. if (l == null) {
  52. l = [];
  53. map[key] = l;
  54. }
  55. l.push(value);
  56. };
  57. /**
  58. * Handles the dragging of an element.
  59. * @param element jQuery element
  60. * @param ui UI object from jQuery's event system
  61. */
  62. var _draw = function(element, ui) {
  63. var id = $(element).attr("id");
  64. var endpoints = endpointsByElement[id];
  65. if (endpoints) {
  66. //if (ui == null) _updateOffset(id, ui);
  67. _updateOffset(id, ui);
  68. var myOffset = offsets[id];
  69. var myWH = sizes[id];
  70. // loop through endpoints for this element
  71. for (var i = 0; i < endpoints.length; i++) {
  72. var e = endpoints[i];
  73. // first, paint the endpoint
  74. var anchorLoc = endpoints[i].anchor.compute([myOffset.left, myOffset.top], myWH);
  75. // var anchorOrientation = endpoints[i].anchor.getOrientation();
  76. //todo...connector paint style? we have lost that with the move to endpoint-centric.
  77. // perhaps we only paint the endpoint here if it has no connections; it can use its own style.
  78. //if (!e.connections || e.connections.length == 0)
  79. e.paint(anchorLoc);
  80. // else {
  81. //if (e.connections && e.connections.length > 0) {
  82. // get all connections for the endpoint...
  83. var l = e.connections;
  84. for (var j = 0; j < l.length; j++)
  85. l[j].paint(id, ui); // ...and paint them.
  86. //}
  87. }
  88. }
  89. };
  90. /**
  91. * helper function: the second argument is a function taking two args - the first is a
  92. * jquery element, and the second is the element's id.
  93. *
  94. * the first argument may be one of three things:
  95. *
  96. * 1. a string, in the form "window1", for example. an element's id. if your id starts with a
  97. * hash then jsPlumb does not append its own hash too...
  98. * 2. a jquery element, already resolved using $(...).
  99. * 3. a list of strings/jquery elements.
  100. */
  101. var _elementProxy = function(element, fn) {
  102. var retVal = null;
  103. if (typeof element == 'object' && element.length) {
  104. retVal = [];
  105. for (var i = 0; i < element.length; i++) {
  106. var el = typeof element[i] == 'string' ? $("#" + element[i]) : element[i];
  107. var id = el.attr("id");
  108. retVal.push(fn(el, id)); // append return values to what we will return
  109. }
  110. }
  111. else {
  112. var el = typeof element == 'string' ?
  113. element.indexOf("#") == 0 ? $(element) : $("#" + element)
  114. : element;
  115. var id = el.attr("id");
  116. retVal = fn(el, id);
  117. }
  118. return retVal;
  119. };
  120. /**
  121. * Returns (creating if necessary) the DIV element that jsPlumb uses as the context for all of its
  122. * canvases. having this makes it possible to makes calls like $("selector", context), which are
  123. * faster than if you provide no context. also we can clear out everything easily like this, either
  124. * on a detachEverything() call or during unload().
  125. */
  126. var _getContextNode = function() {
  127. if (_jsPlumbContextNode == null) {
  128. _jsPlumbContextNode= document.createElement("div");
  129. document.body.appendChild(_jsPlumbContextNode);
  130. _jsPlumbContextNode.className = "_jsPlumb_context";
  131. }
  132. return $(_jsPlumbContextNode);
  133. };
  134. /**
  135. * gets an id for the given element, creating and setting one if necessary.
  136. */
  137. var _getId = function(element) {
  138. var id = $(element).attr("id");
  139. if (!id) {
  140. id = "_jsPlumb_" + new String((new Date()).getTime());
  141. $(element).attr("id", id);
  142. }
  143. return id;
  144. };
  145. /**
  146. * inits a draggable if it's not already initialised.
  147. * todo: if the element was draggable already, like from some non-jsPlumb call, wrap the drag function.
  148. */
  149. var _initDraggableIfNecessary = function(element, elementId, isDraggable, dragOptions) {
  150. // dragging
  151. var draggable = isDraggable == null ? _draggableByDefault : isDraggable;
  152. if (draggable && element.draggable) {
  153. var options = dragOptions || jsPlumb.Defaults.DragOptions;
  154. var dragCascade = options.drag || function(e,u) {};
  155. var initDrag = function(element, elementId, dragFunc) {
  156. var opts = $.extend({drag:dragFunc}, options);
  157. var draggable = draggableStates[elementId];
  158. opts.disabled = draggable == null ? false : !draggable;
  159. element.draggable(opts);
  160. };
  161. initDrag(element, elementId, function(event, ui) {
  162. _draw(element, ui);
  163. $(element).addClass("jsPlumb_dragged");
  164. dragCascade(event, ui);
  165. });
  166. }
  167. };
  168. var _log = function(msg) {
  169. // not implemented. yet.
  170. }
  171. /**
  172. * helper to create a canvas.
  173. * @param clazz optional class name for the canvas.
  174. */
  175. var _newCanvas = function(clazz) {
  176. var canvas = document.createElement("canvas");
  177. _getContextNode().append(canvas);
  178. canvas.style.position="absolute";
  179. if (clazz) { canvas.className=clazz; }
  180. if (/MSIE/.test(navigator.userAgent) && !window.opera) {
  181. // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels
  182. // is not big enough for the biggest connector/endpoint canvas you have at startup.
  183. jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE);
  184. canvas = G_vmlCanvasManager.initElement(canvas);
  185. }
  186. return canvas;
  187. };
  188. /**
  189. * performs the given function operation on all the connections found for the given element
  190. * id; this means we find all the endpoints for the given element, and then for each endpoint
  191. * find the connectors connected to it. then we pass each connection in to the given
  192. * function.
  193. */
  194. var _operation = function(elId, func) {
  195. var endpoints = endpointsByElement[elId];
  196. if (endpoints && endpoints.length) {
  197. for (var i = 0; i < endpoints.length; i++) {
  198. for (var j = 0; j < endpoints[i].connections.length; j++) {
  199. var retVal = func(endpoints[i].connections[j]);
  200. // if the function passed in returns true, we exit.
  201. // most functions return false.
  202. if (retVal) return;
  203. }
  204. }
  205. }
  206. };
  207. /**
  208. * perform an operation on all elements.
  209. */
  210. var _operationOnAll = function(func) {
  211. for (var elId in endpointsByElement) {
  212. _operation(elId, func);
  213. }
  214. };
  215. /**
  216. * helper to remove an element from the DOM.
  217. */
  218. var _removeElement = function(element) {
  219. if (element != null) {
  220. try { _jsPlumbContextNode.removeChild(element); }
  221. catch (e) { }
  222. }
  223. };
  224. /**
  225. * helper to remove a list of elements from the DOM.
  226. */
  227. var _removeElements = function(elements) {
  228. for (var i in elements)
  229. _removeElement(elements[i]);
  230. };
  231. /**
  232. * helper method to remove an item from a list.
  233. */
  234. var _removeFromList = function(map, key, value) {
  235. var l = map[key];
  236. if (l != null) {
  237. var i = l.indexOf(value);
  238. if (i >= 0) {
  239. delete( l[i] );
  240. l.splice( i, 1 );
  241. return true;
  242. }
  243. }
  244. return false;
  245. };
  246. /**
  247. * Sets whether or not the given element(s) should be draggable, regardless of what a particular
  248. * plumb command may request.
  249. *
  250. * @param element May be a string, a jQuery elements, or a list of strings/jquery elements.
  251. * @param draggable Whether or not the given element(s) should be draggable.
  252. */
  253. var _setDraggable = function(element, draggable) {
  254. var _helper = function(el, id) {
  255. draggableStates[id] = draggable;
  256. if (el.draggable) {
  257. el.draggable("option", "disabled", !draggable);
  258. }
  259. };
  260. return _elementProxy(element, _helper);
  261. };
  262. /**
  263. * private method to do the business of hiding/showing.
  264. * @param elId Id of the element in question
  265. * @param state String specifying a value for the css 'display' property ('block' or 'none').
  266. */
  267. var _setVisible = function(elId, state) {
  268. var f = function(jpc) {
  269. //todo should we find all the endpoints instead of going by connection? this will
  270. jpc.canvas.style.display = state;
  271. /*jpc.sourceEndpointCanvas.style.display = state;
  272. jpc.targetEndpointCanvas.style.display = state;*/
  273. };
  274. _operation(elId, f);
  275. };
  276. /**
  277. * toggles the draggable state of the element with the given id.
  278. */
  279. var _toggleDraggable = function(el) {
  280. var fn = function(el, elId) {
  281. var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId];
  282. state = !state;
  283. draggableStates[elId] = state;
  284. el.draggable("option", "disabled", !state);
  285. return state;
  286. };
  287. return _elementProxy(el, fn);
  288. };
  289. /**
  290. * private method to do the business of toggling hiding/showing.
  291. * @param elId Id of the element in question
  292. */
  293. var _toggleVisible = function(elId) {
  294. var f = function(jpc) {;
  295. var state = ('none' == jpc.canvas.style.display);
  296. jpc.canvas.style.display = state ? "block" : "none";
  297. /*jpc.sourceEndpointCanvas.style.display = state;
  298. jpc.targetEndpointCanvas.style.display = state;*/
  299. };
  300. _operation(elId, f);
  301. };
  302. /**
  303. * updates the offset and size for a given element, and stores the values.
  304. * 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,
  305. * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get
  306. * the current values.
  307. */
  308. var _updateOffset = function(elId, ui, recalc) {
  309. if (log) log.debug("updating offset for element [" + elId + "]; ui is [" + ui + "]; recalc is [" + recalc + "]");
  310. if (recalc || ui == null) { // if forced repaint or no ui helper available, we recalculate.
  311. // get the current size and offset, and store them
  312. var s = $("#" + elId);
  313. sizes[elId] = [s.outerWidth(), s.outerHeight()];
  314. offsets[elId] = s.offset();
  315. } else {
  316. // faster to use the ui element if it was passed in.
  317. // fix for change in 1.8 (absolutePosition renamed to offset). plugin is compatible with
  318. // 1.8 and 1.7.
  319. // todo: when the drag axis is supplied, the ui object passed in has incorrect values
  320. // for the other axis - like say you set axis='x'. when you move the mouse up and down
  321. // while dragging, the y values are for where the window would be if it was not
  322. // just constrained to x. not sure if this is a jquery bug or whether there's a known
  323. // trick or whatever.
  324. var pos = ui.absolutePosition || ui.offset;
  325. var anOffset = ui != null ? pos : $("#" + elId).offset();
  326. offsets[elId] = anOffset;
  327. }
  328. };
  329. /**
  330. * wraps one function with another, creating a placeholder for the wrapped function
  331. * if it was null. this is used to wrap the various drag/drop event functions - to allow
  332. * jsPlumb to be notified of important lifecycle events without imposing itself on the user's
  333. * drap/drop functionality.
  334. * TODO: determine whether or not we should try/catch the plumb function, so that the cascade function is always executed.
  335. */
  336. var _wrap = function(cascadeFunction, plumbFunction) {
  337. cascadeFunction = cascadeFunction || function(e, ui) { };
  338. return function(e, ui) {
  339. plumbFunction(e, ui);
  340. cascadeFunction(e, ui);
  341. };
  342. }
  343. /**
  344. * Anchor class. Anchors can be situated anywhere.
  345. * params should contain three values, and may optionally have an 'offsets' argument:
  346. *
  347. * x : the x location of the anchor as a fraction of the total width.
  348. *
  349. * y : the y location of the anchor as a fraction of the total height.
  350. *
  351. * orientation : an [x,y] array indicating the general direction a connection
  352. * from the anchor should go in. for more info on this, see the documentation,
  353. * or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors.
  354. *
  355. * offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null.
  356. *
  357. */
  358. var Anchor = function(params) {
  359. var self = this;
  360. this.x = params.x || 0; this.y = params.y || 0;
  361. var orientation = params.orientation || [0,0];
  362. this.offsets = params.offsets || [0,0];
  363. this.compute = function(xy, wh, txy, twh) {
  364. return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ];
  365. }
  366. this.getOrientation = function() { return orientation; };
  367. };
  368. /**
  369. * an anchor that floats. its orientation is computed dynamically from its position relative
  370. * to the anchor it is floating relative to.
  371. */
  372. var FloatingAnchor = function(params) {
  373. // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation.
  374. var ref = params.reference;
  375. // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate
  376. // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read
  377. // by the getOrientation method.
  378. var xDir = 0, yDir = 0;
  379. // temporary member used to store an orientation when the floating anchor is hovering over another anchor.
  380. var orientation = null;
  381. this.compute = function(xy, wh, txy, twh) {
  382. // set these for the getOrientation method to use.
  383. xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1;
  384. yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1;
  385. 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.
  386. };
  387. this.getOrientation = function() {
  388. if (orientation) return orientation;
  389. else {
  390. var o = ref.getOrientation();
  391. // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too.
  392. // 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
  393. // like it makes sense. maybe this wont make sense.
  394. return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1];
  395. }
  396. };
  397. /**
  398. * notification the endpoint associated with this anchor is hovering over another anchor;
  399. * we want to assume that anchor's orientation for the duration of the hover.
  400. */
  401. this.over = function(anchor) {
  402. orientation = anchor.getOrientation();
  403. };
  404. /**
  405. * notification the endpoint associated with this anchor is no longer hovering
  406. * over another anchor; we should resume calculating orientation as we normally do.
  407. */
  408. this.out = function() {
  409. orientation = null;
  410. };
  411. };
  412. // ************** connection
  413. // ****************************************
  414. /**
  415. * allowed params:
  416. * source: source element (string or a jQuery element) (required)
  417. * target: target element (string or a jQuery element) (required)
  418. *
  419. * anchors: optional array of anchor placements. defaults to BottomCenter for source
  420. * and TopCenter for target.
  421. */
  422. var Connection = function(params) {
  423. // ************** get the source and target and register the connection. *******************
  424. var self = this;
  425. // get source and target as jQuery objects
  426. this.source = (typeof params.source == 'string') ? $("#" + params.source) : params.source;
  427. this.target = (typeof params.target == 'string') ? $("#" + params.target) : params.target;
  428. this.sourceId = $(this.source).attr("id");
  429. this.targetId = $(this.target).attr("id");
  430. this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true;
  431. // init endpoints
  432. this.endpoints = [];
  433. this.endpointStyles = [];
  434. var prepareEndpoint = function(existing, index, params) {
  435. if (existing) self.endpoints[index] = existing;
  436. else {
  437. if(!params.endpoints) params.endpoints = [null,null];
  438. var ep = params.endpoints[index] || params.endpoint || jsPlumb.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoint|| new jsPlumb.Endpoints.Dot();
  439. if (!params.endpointStyles) params.endpointStyles = [null,null];
  440. var es = params.endpointStyles[index] || params.endpointStyle || jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyle;
  441. var a = params.anchors ? params.anchors[index] : jsPlumb.Defaults.Anchors[index] || jsPlumb.Anchors.BottomCenter;
  442. self.endpoints[index] = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a });
  443. }
  444. };
  445. prepareEndpoint(params.sourceEndpoint, 0, params);
  446. prepareEndpoint(params.targetEndpoint, 1, params);
  447. // make connector. if an endpoint has a connector + paintstyle to use, we use that.
  448. // otherwise we use sensible defaults.
  449. //this.connector = params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier();
  450. this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier();
  451. //this.paintStyle = params.paintStyle || jsPlumb.Defaults.PaintStyle;
  452. this.paintStyle = this.endpoints[0].connectionStyle || this.endpoints[1].connectionStyle || params.paintStyle || jsPlumb.Defaults.PaintStyle;
  453. _updateOffset(this.sourceId);
  454. _updateOffset(this.targetId);
  455. // paint the endpoints
  456. var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId];
  457. var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH);
  458. this.endpoints[0].paint(anchorLoc);
  459. myOffset = offsets[this.targetId]; myWH = sizes[this.targetId];
  460. anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH);
  461. this.endpoints[1].paint(anchorLoc);
  462. // *************** create canvas on which the connection will be drawn ************
  463. var canvas = _newCanvas(jsPlumb.connectorClass);
  464. this.canvas = canvas;
  465. /**
  466. * paints the connection.
  467. * @param elId Id of the element that is in motion
  468. * @param ui jQuery's event system ui object (present if we came from a drag to get here)
  469. * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted.
  470. */
  471. this.paint = function(elId, ui, recalc) {
  472. if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]");
  473. var fai = self.floatingAnchorIndex;
  474. // if the moving object is not the source we must transpose the two references.
  475. var swap = !(elId == this.sourceId);
  476. var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId;
  477. var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0;
  478. var el = swap ? this.target : this.source;
  479. if (this.canvas.getContext) {
  480. _updateOffset(elId, ui, recalc);
  481. if (recalc) _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved.
  482. var myOffset = offsets[elId];
  483. var otherOffset = offsets[tId];
  484. var myWH = sizes[elId];
  485. var otherWH = sizes[tId];
  486. var ctx = canvas.getContext('2d');
  487. var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH);
  488. var sAnchorO = this.endpoints[sIdx].anchor.getOrientation();
  489. var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH);
  490. var tAnchorO = this.endpoints[tIdx].anchor.getOrientation();
  491. var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth);
  492. jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]);
  493. $.extend(ctx, this.paintStyle);
  494. if (this.paintStyle.gradient && !ie) {
  495. var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]);
  496. for (var i = 0; i < this.paintStyle.gradient.stops.length; i++)
  497. g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]);
  498. ctx.strokeStyle = g;
  499. }
  500. this.connector.paint(dim, ctx);
  501. // this.endpoints[swap ? 1 : 0].paint(sAnchorP, this.paintStyle);
  502. //this.endpoints[swap ? 0 : 1].paint(tAnchorP, this.paintStyle);
  503. }
  504. };
  505. this.repaint = function() {
  506. this.paint(this.sourceId, null, true);
  507. };
  508. _initDraggableIfNecessary(self.source, self.sourceId, params.draggable, params.dragOptions);
  509. _initDraggableIfNecessary(self.target, self.targetId, params.draggable, params.dragOptions);
  510. // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not.
  511. if (this.source.resize) {
  512. this.source.resize(function(e) {
  513. jsPlumb.repaint(self.sourceId);
  514. });
  515. }
  516. };
  517. /**
  518. * models an endpoint. can have one to N connections emanating from it (although how to handle that in the UI is
  519. * a very good question). also has a Canvas and paint style.
  520. *
  521. * params:
  522. *
  523. * anchor : anchor for the endpoint, of type jsPlumb.Anchor. may be null.
  524. * endpoint : endpoint object, of type jsPlumb.Endpoint. may be null.
  525. * style : endpoint style, a js object. may be null.
  526. * source : element the endpoint is attached to, of type jquery object. Required.
  527. * canvas : canvas element to use. may be, and most often is, null.
  528. * connections : optional list of connections to configure the endpoint with.
  529. * isSource : boolean. indicates the endpoint can act as a source of new connections. optional.
  530. * dragOptions : if isSource is set to true, you can supply arguments for the jquery draggable method. optional.
  531. * connectionStyle : if isSource is set to true, this is the paint style for connections from this endpoint. optional.
  532. * connector : optional connector type to use.
  533. * isTarget : boolean. indicates the endpoint can act as a target of new connections. optional.
  534. * dropOptions : if isTarget is set to true, you can supply arguments for the jquery droppable method. optional.
  535. * reattach : optional boolean that determines whether or not the connections reattach after they
  536. * have been dragged off an endpoint and left floating. defaults to false - connections
  537. * dropped in this way will just be deleted.
  538. */
  539. var Endpoint = function(params) {
  540. params = params || {};
  541. // make a copy. then we can use the wrapper function.
  542. params = $.extend({}, params);
  543. var self = this;
  544. self.anchor = params.anchor || jsPlumb.Anchors.TopCenter;
  545. var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot();
  546. var _style = params.style || jsPlumb.Defaults.EndpointStyle;
  547. this.connectionStyle = params.connectionStyle;
  548. this.connector = params.connector;
  549. var _element = params.source;
  550. var _elementId = $(_element).attr("id");
  551. var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of.
  552. this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass);
  553. this.connections = params.connections || [];
  554. var _reattach = params.reattach || false;
  555. var floatingEndpoint = null;
  556. this.addConnection = function(connection) {
  557. self.connections.push(connection);
  558. };
  559. this.removeConnection = function(connection) {
  560. var idx = self.connections.indexOf(connection);
  561. if (idx >= 0)
  562. self.connections.splice(idx, 1);
  563. };
  564. this.isFloating = function() { return floatingEndpoint != null; };
  565. /**
  566. * first pass at default ConnectorSelector: returns the first connection, if we have any.
  567. * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors
  568. * 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
  569. * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also
  570. * be cool.
  571. */
  572. var connectorSelector = function() {
  573. return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0];
  574. };
  575. // get the jsplumb context...lookups are faster with a context.
  576. var contextNode = _getContextNode();
  577. this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); };
  578. /**
  579. * paints the Endpoint, recalculating offset and anchor positions if necessary.
  580. */
  581. this.paint = function(anchorPoint, connectorPaintStyle, canvas) {
  582. if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]");
  583. if (anchorPoint == null) {
  584. // do we always want to force a repaint here? i dont think so!
  585. var xy = offsets[_elementId];
  586. var wh = sizes[_elementId];
  587. if (xy == null || wh == null) {
  588. _updateOffset(_elementId);
  589. xy = offsets[_elementId];
  590. wh = sizes[_elementId];
  591. }
  592. anchorPoint = self.anchor.compute([xy.left, xy.top], wh);
  593. }
  594. _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style);
  595. };
  596. // is this a connection source? we make it draggable and have the drag listener
  597. // maintain a connection with a floating endpoint.
  598. if (params.isSource && _element.draggable) {
  599. var n = null, id = null,
  600. jpc = null,
  601. existingJpc = false, existingJpcParams = null;
  602. // 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
  603. // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably
  604. // 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
  605. // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all,
  606. // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector
  607. // interface returns a List, with the default implementation just returning everything. i think i like that.
  608. //
  609. // 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.
  610. // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the
  611. // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint
  612. // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc
  613. // 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
  614. // 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.
  615. var start = function(e, ui) {
  616. //if (!isFull()) {
  617. n = document.createElement("div");
  618. contextNode.append(n);
  619. // create and assign an id, and initialize the offset.
  620. id = new String(new Date().getTime());
  621. $(n, contextNode).attr("id", id);
  622. _updateOffset(id);
  623. // store the id of the dragging div and the source element. the drop function
  624. // will pick these up.
  625. $(self.canvas, contextNode).attr("dragId", id);
  626. $(self.canvas, contextNode).attr("elId", _elementId);
  627. // create a floating anchor
  628. var floatingAnchor = new FloatingAnchor({reference:self.anchor});
  629. floatingEndpoint = new Endpoint({
  630. style:_style,
  631. endpoint:_endpoint,
  632. anchor:floatingAnchor,
  633. source:n
  634. });
  635. //floatingEndpoint.originalAnchor =
  636. jpc = connectorSelector();
  637. if (jpc == null) {
  638. // create a connection. one end is this endpoint, the other is a floating endpoint.
  639. jpc = new Connection({
  640. sourceEndpoint:self,
  641. targetEndpoint:floatingEndpoint,
  642. source:$(_element),
  643. target:$(n, contextNode),
  644. anchors:[self.anchor, floatingAnchor],
  645. paintStyle : params.connectionStyle, // this can be null. Connection will use the default.
  646. connector: params.connector
  647. });
  648. // todo ...unregister on stop
  649. self.addConnection(jpc);
  650. } else {
  651. existingJpc = true;
  652. var anchorIdx = jpc.sourceId == _elementId ? 0 : 1;
  653. jpc.floatingAnchorIndex = anchorIdx;
  654. // probably we should remove the connection? and add it back if the user
  655. // does not drop it somewhere proper.
  656. self.removeConnection(jpc);
  657. if (anchorIdx == 0){
  658. existingJpcParams = [jpc.source, jpc.sourceId];
  659. jpc.source = $(n, contextNode);
  660. jpc.sourceId = id;
  661. }else {
  662. existingJpcParams = [jpc.target, jpc.targetId];
  663. jpc.target = $(n, contextNode);
  664. jpc.targetId = id;
  665. }
  666. jpc.suspendedEndpoint = jpc.endpoints[anchorIdx];
  667. jpc.endpoints[anchorIdx] = floatingEndpoint;
  668. }
  669. // register it.
  670. floatingConnections[id] = jpc;
  671. // todo unregister on stop
  672. floatingEndpoint.addConnection(jpc);
  673. // only register for the target endpoint; we will not be dragging the source at any time
  674. // before this connection is either discarded or made into a permanent connection.
  675. _addToList(endpointsByElement, id, floatingEndpoint);
  676. }
  677. //};
  678. var dragOptions = params.dragOptions || { };
  679. dragOptions = $.extend({ opacity:0.5, revert:true, helper:'clone' }, dragOptions);
  680. dragOptions.start = _wrap(dragOptions.start, start);
  681. dragOptions.drag = _wrap(dragOptions.drag, function(e, ui) {
  682. _draw($(n, contextNode), ui);
  683. });
  684. dragOptions.stop = _wrap(dragOptions.stop,
  685. function(e, ui) {
  686. _removeFromList(endpointsByElement, id, floatingEndpoint);
  687. _removeElements([floatingEndpoint.canvas, n]);
  688. var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
  689. if (jpc.endpoints[idx] == floatingEndpoint) {
  690. // if the connection was an existing one:
  691. if (existingJpc && jpc.suspendedEndpoint) {
  692. if (_reattach) {
  693. jpc.floatingAnchorIndex = null;
  694. if (idx == 0) {
  695. jpc.source = existingJpcParams[0];
  696. jpc.sourceId = existingJpcParams[1];
  697. } else {
  698. jpc.target = existingJpcParams[0];
  699. jpc.targetId = existingJpcParams[1];
  700. }
  701. jpc.endpoints[idx] = jpc.suspendedEndpoint;
  702. jpc.suspendedEndpoint.addConnection(jpc);
  703. jsPlumb.repaint(existingJpcParams[1]);
  704. }
  705. else {
  706. jpc.endpoints[0].removeConnection(jpc);
  707. jpc.endpoints[1].removeConnection(jpc);
  708. _removeElement(jpc.canvas);
  709. }
  710. } else {
  711. _removeElement(jpc.canvas);
  712. self.removeConnection(jpc);
  713. }
  714. }
  715. jpc = null;
  716. delete floatingEndpoint;
  717. }
  718. );
  719. $(self.canvas, contextNode).draggable(dragOptions);
  720. }
  721. // connector target
  722. if (params.isTarget && _element.droppable) {
  723. var dropOptions = params.dropOptions || jsPlumb.Defaults.DropOptions;
  724. dropOptions = $.extend({}, dropOptions);
  725. var originalAnchor = null;
  726. dropOptions.drop = _wrap(dropOptions.drop, function(e, ui) {
  727. var id = $(ui.draggable, contextNode).attr("dragId");
  728. var elId = $(ui.draggable, contextNode).attr("elId");
  729. var jpc = floatingConnections[id];
  730. var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
  731. if (idx == 0) {
  732. jpc.source = _element;
  733. jpc.sourceId = _elementId;
  734. } else {
  735. jpc.target = _element;
  736. jpc.targetId = _elementId;
  737. }
  738. // remove this jpc from the current endpoint
  739. jpc.endpoints[idx].removeConnection(jpc);
  740. if (jpc.suspendedEndpoint)
  741. jpc.suspendedEndpoint.removeConnection(jpc);
  742. jpc.endpoints[idx] = self;
  743. self.addConnection(jpc);
  744. _initDraggableIfNecessary(_element, _elementId, params.draggable, {});
  745. jsPlumb.repaint($(ui.draggable, contextNode).attr("elId"));
  746. delete floatingConnections[id];
  747. });
  748. // what to do when something is dropped.
  749. // 1. find the jpc that is being dragged. the target endpoint of the jpc will be the
  750. // one that is being dragged.
  751. // 2. arrange for the floating endpoint to be replaced with this endpoint; make sure
  752. // everything gets registered ok etc.
  753. // 3. arrange for the floating endpoint to be deleted.
  754. // 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.
  755. // other considerations: when in the hover mode, we should switch the floating endpoint's
  756. // orientation to be the same as the drop target. this will cause the connector to snap
  757. // into the shape it will take if the user drops at that point.
  758. dropOptions.over = _wrap(dropOptions.over, function(event, ui) {
  759. var id = $(ui.draggable, contextNode).attr("dragId");
  760. var jpc = floatingConnections[id];
  761. var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
  762. jpc.endpoints[idx].anchor.over(self.anchor);
  763. });
  764. dropOptions.out = _wrap(dropOptions.out, function(event, ui) {
  765. var id = $(ui.draggable, contextNode).attr("dragId");
  766. var jpc = floatingConnections[id];
  767. var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
  768. jpc.endpoints[idx].anchor.out();
  769. });
  770. $(self.canvas, contextNode).droppable(dropOptions);
  771. }
  772. // woo...add a plumb command to Endpoint.
  773. this.plumb = function(params) {
  774. // not much to think about. the target should be an Endpoint, but what else?
  775. // all the style stuff is on the endpoint itself already.
  776. //todo this should call the main plumb method, just with some different args.
  777. };
  778. return self;
  779. };
  780. /**
  781. * jsPlumb public API
  782. */
  783. var jsPlumb = window.jsPlumb = {
  784. Defaults : {
  785. Anchors : [ null, null ],
  786. Connector : null,
  787. DragOptions: { },
  788. DropOptions: { },
  789. Endpoint : null,
  790. Endpoints : [ null, null ],
  791. EndpointStyle : { fillStyle : null },
  792. EndpointStyles : [ null, null ],
  793. MaxConnections : null,
  794. PaintStyle : { lineWidth : 10, strokeStyle : 'red' }
  795. },
  796. connectorClass : '_jsPlumb_connector',
  797. endpointClass : '_jsPlumb_endpoint',
  798. Anchors : {},
  799. Connectors : {},
  800. Endpoints : {},
  801. /**
  802. * adds an endpoint to the element
  803. */
  804. addEndpoint : function(target, params) {
  805. params = $.extend({}, params);
  806. var el = typeof target == 'string' ? $("#" + target) : target;
  807. var id = $(el).attr("id");
  808. params.source = el;
  809. _updateOffset(id);
  810. var e = new Endpoint(params);
  811. _addToList(endpointsByElement, id, e);
  812. var myOffset = offsets[id];
  813. var myWH = sizes[id];
  814. var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH);
  815. e.paint(anchorLoc);
  816. return e;
  817. },
  818. /**
  819. * wrapper around standard jquery animate function; injects a call to jsPlumb in the
  820. * 'step' function (creating it if necessary). this only supports the two-arg version
  821. * of the animate call in jquery - the one that takes an object as the second arg.
  822. */
  823. animate : function(el, properties, options) {
  824. var ele = typeof(el)=='string' ? $("#" + el) : el;
  825. var id = ele.attr("id");
  826. options = options || {};
  827. options.step = _wrap(options.step, function() { jsPlumb.repaint(id); });
  828. ele.animate(properties, options);
  829. },
  830. /**
  831. * adds a list of endpoints to the element
  832. */
  833. addEndpoints : function(target, endpoints) {
  834. var results = [];
  835. for (var i = 0; i < endpoints.length; i++) {
  836. results.push(jsPlumb.addEndpoint(target, endpoints[i]));
  837. }
  838. return results;
  839. },
  840. /**
  841. * establishes a connection between two elements.
  842. * @param params object containing setup for the connection. see documentation.
  843. */
  844. connect : function(params) {
  845. if (params.sourceEndpoint && params.sourceEndpoint.isFull()) {
  846. _log("could not add connection; source endpoint is full");
  847. return;
  848. }
  849. if (params.targetEndpoint && params.targetEndpoint.isFull()) {
  850. _log("could not add connection; target endpoint is full");
  851. return;
  852. }
  853. var jpc = new Connection(params);
  854. // register endpoints for the element
  855. _addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]);
  856. _addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]);
  857. jpc.endpoints[0].addConnection(jpc);
  858. jpc.endpoints[1].addConnection(jpc);
  859. // force a paint
  860. _draw(jpc.source);
  861. },
  862. connectEndpoints : function(params) {
  863. var jpc = Connection(params);
  864. },
  865. /**
  866. * Remove one connection to an element.
  867. * @param sourceId id of the first window in the connection
  868. * @param targetId id of the second window in the connection
  869. * @return true if successful, false if not.
  870. */
  871. detach : function(sourceId, targetId) {
  872. var f = function(jpc) {
  873. if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) {
  874. _removeElement(jpc.canvas);
  875. /*_removeElement(jpc.targetEndpointCanvas);
  876. _removeElement(jpc.sourceEndpointCanvas);*/
  877. jpc.endpoints[0].removeConnection(jpc);
  878. jpc.endpoints[1].removeConnection(jpc);
  879. return true;
  880. }
  881. };
  882. // todo: how to cleanup the actual storage? a third arg to _operation?
  883. _operation(sourceId, f);
  884. },
  885. /**
  886. * remove all an element's connections.
  887. * @param elId id of the
  888. */
  889. detachAll : function(elId) {
  890. var f = function(jpc) {
  891. // todo replace with _cleanupConnection call here.
  892. _removeElement(jpc.canvas);
  893. /*_removeElement(jpc.targetEndpointCanvas);
  894. _removeElement(jpc.sourceEndpointCanvas);*/
  895. jpc.endpoints[0].removeConnection(jpc);
  896. jpc.endpoints[1].removeConnection(jpc);
  897. };
  898. _operation(elId, f);
  899. //delete endpointsByElement[elId];
  900. },
  901. /**
  902. * remove all connections. and endpoints? probably not.
  903. */
  904. detachEverything : function() {
  905. var f = function(jpc) {
  906. _removeElement(jpc.canvas);
  907. /*_removeElement(jpc.targetEndpointCanvas);
  908. _removeElement(jpc.sourceEndpointCanvas);*/
  909. jpc.endpoints[0].removeConnection(jpc);
  910. jpc.endpoints[1].removeConnection(jpc);
  911. };
  912. _operationOnAll(f);
  913. /*delete endpointsByElement;
  914. endpointsByElement = {};*/
  915. },
  916. /**
  917. * Set an element's connections to be hidden.
  918. */
  919. hide : function(elId) {
  920. _setVisible(elId, "none");
  921. },
  922. /**
  923. * Creates an anchor with the given params.
  924. * x - the x location of the anchor as a fraction of the total width.
  925. * y - the y location of the anchor as a fraction of the total height.
  926. * orientation - an [x,y] array indicating the general direction a connection from the anchor should go in.
  927. * 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].
  928. */
  929. makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) {
  930. // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use
  931. // 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.
  932. var params = {};
  933. if (arguments.length == 1) $.extend(params, x);
  934. else {
  935. params = {x:x, y:y};
  936. if (arguments.length >= 4) {
  937. params.orientation = [arguments[2], arguments[3]];
  938. }
  939. if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]];
  940. }
  941. return new Anchor(params);
  942. },
  943. /**
  944. * repaint element and its connections. element may be an id or the actual jQuery object.
  945. * this method gets new sizes for the elements before painting anything.
  946. */
  947. repaint : function(el) {
  948. var _processElement = function(el) {
  949. var ele = typeof(el)=='string' ? $("#" + el) : el;
  950. _draw(ele);
  951. };
  952. // TODO: support a jQuery result object too!
  953. // support both lists...
  954. if (typeof el =='object') {
  955. for (var i = 0; i < el.length; i++)
  956. _processElement(el[i]);
  957. } // ...and single strings.
  958. else _processElement(el);
  959. },
  960. /**
  961. * repaint all connections.
  962. */
  963. repaintEverything : function() {
  964. for (var elId in endpointsByElement) {
  965. _draw($("#" + elId));
  966. }
  967. },
  968. removeEndpoint : function(elId, endpoint) {
  969. var ebe = endpointsByElement[elId];
  970. if (ebe) {
  971. // var i = ebe.indexOf(endpoint);
  972. // alert('element has ' + ebe.length);
  973. //if (i > -1) {
  974. //ebe.splice(i, 1);
  975. if(_removeFromList(endpointsByElement, elId, endpoint))
  976. _removeElement(endpoint.canvas);
  977. //}
  978. }
  979. },
  980. /**
  981. * sets/unsets automatic repaint on window resize.
  982. */
  983. setAutomaticRepaint : function(value) {
  984. automaticRepaint = value;
  985. },
  986. /**
  987. * Sets the default size jsPlumb will use for a new canvas (we create a square canvas so
  988. * one value is all that is required). This is a hack for IE, because ExplorerCanvas seems
  989. * to need for a canvas to be larger than what you are going to draw on it at initialisation
  990. * time. The default value of this is 1200 pixels, which is quite large, but if for some
  991. * reason you're drawing connectors that are bigger, you should adjust this value appropriately.
  992. */
  993. setDefaultNewCanvasSize : function(size) {
  994. DEFAULT_NEW_CANVAS_SIZE = size;
  995. },
  996. /**
  997. * Sets whether or not a given element is draggable, regardless of what any plumb command
  998. * may request.
  999. */
  1000. setDraggable: _setDraggable,
  1001. /**
  1002. * Sets whether or not elements are draggable by default. Default for this is true.
  1003. */
  1004. setDraggableByDefault: function(draggable) {
  1005. _draggableByDefault = draggable;
  1006. },
  1007. setDebugLog: function(debugLog) {
  1008. log = debugLog;
  1009. },
  1010. /**
  1011. * Sets the function to fire when the window size has changed and a repaint was fired.
  1012. */
  1013. setRepaintFunction : function(f) {
  1014. repaintFunction = f;
  1015. },
  1016. /**
  1017. * Set an element's connections to be visible.
  1018. */
  1019. show : function(elId) {
  1020. _setVisible(elId, "block");
  1021. },
  1022. /**
  1023. * helper to size a canvas.
  1024. */
  1025. sizeCanvas : function(canvas, x, y, w, h) {
  1026. canvas.style.height = h + "px"; canvas.height = h;
  1027. canvas.style.width = w + "px"; canvas.width = w;
  1028. canvas.style.left = x + "px"; canvas.style.top = y + "px";
  1029. },
  1030. /**
  1031. * gets some test hooks. nothing writable.
  1032. */
  1033. getTestHarness : function() {
  1034. return {
  1035. endpointCount : function(elId) {
  1036. var e = endpointsByElement[elId];
  1037. return e ? e.length : 0;
  1038. }
  1039. };
  1040. },
  1041. /**
  1042. * Toggles visibility of an element's connections. kept for backwards compatibility
  1043. */
  1044. toggle : _toggleVisible,
  1045. /**
  1046. * new name for the old toggle method.
  1047. */
  1048. toggleVisible : _toggleVisible,
  1049. /**
  1050. * Toggles draggability (sic) of an element.
  1051. */
  1052. toggleDraggable : _toggleDraggable,
  1053. /**
  1054. * Unloads jsPlumb, deleting all storage. You should call this
  1055. */
  1056. unload : function() {
  1057. delete endpointsByElement;
  1058. delete offsets;
  1059. delete sizes;
  1060. delete floatingConnections;
  1061. delete draggableStates;
  1062. document.body.removeChild(_jsPlumbContextNode);
  1063. }
  1064. };
  1065. })();
  1066. // jQuery plugin code
  1067. (function($){
  1068. /**
  1069. * plumbs the results of the selector to some target, using the given options if supplied,
  1070. * or the defaults otherwise.
  1071. */
  1072. $.fn.plumb = function(options) {
  1073. var options = $.extend({}, options);
  1074. return this.each(function()
  1075. {
  1076. var params = $.extend({source:$(this)}, options);
  1077. jsPlumb.connect(params);
  1078. });
  1079. };
  1080. /**
  1081. * detaches the results of the selector from the given target or list of targets - 'target'
  1082. * may be a String or a List.
  1083. */
  1084. $.fn.detach = function(target) {
  1085. return this.each(function()
  1086. {
  1087. var id = $(this).attr("id");
  1088. if (typeof target == 'string') target = [target];
  1089. for (var i = 0; i < target.length; i++)
  1090. jsPlumb.detach(id, target[i]);
  1091. });
  1092. };
  1093. /**
  1094. * detaches the results from the selector from all connections.
  1095. */
  1096. $.fn.detachAll = function() {
  1097. return this.each(function()
  1098. {
  1099. var id = $(this).attr("id");
  1100. jsPlumb.detachAll(id);
  1101. });
  1102. };
  1103. /**
  1104. * adds an endpoint to the elements resulting from the selector. options may be null,
  1105. * in which case jsPlumb will use the default options. see documentation.
  1106. */
  1107. $.fn.addEndpoint = function(options) {
  1108. var addedEndpoints = [];
  1109. this.each(function()
  1110. {
  1111. //var params = $.extend({source:$(this)}, options);
  1112. addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options));
  1113. });
  1114. return addedEndpoints;
  1115. };
  1116. /**
  1117. * adds a list of endpoints to the elements resulting from the selector. options may be null,
  1118. * in which case jsPlumb will use the default options. see documentation.
  1119. */
  1120. $.fn.addEndpoints = function(endpoints) {
  1121. var addedEndpoints = [];
  1122. return this.each(function()
  1123. {
  1124. var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints);
  1125. for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]);
  1126. });
  1127. return addedEndpoints;
  1128. };
  1129. /**
  1130. * remove the endpoint, if it exists, deleting its UI elements etc.
  1131. */
  1132. $.fn.removeEndpoint = function(endpoint) {
  1133. this.each(function()
  1134. {
  1135. jsPlumb.removeEndpoint($(this).attr("id"), endpoint);
  1136. });
  1137. };
  1138. })(jQuery);