jquery.jsPlumb-1.1.0-RC1.js 48 KB

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