jquery.jsPlumb-all-1.1.0.js 64 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622
  1. /*
  2. * jsPlumb-all-1.1.0
  3. *
  4. * This script contains the main jsPlumb script and the default anchors, connectors and endpoints.
  5. &
  6. * http://morrisonpitt.com/jsPlumb/demo.html
  7. * http://code.google.com/p/jsPlumb
  8. *
  9. */
  10. if (!Array.prototype.indexOf) {
  11. Array.prototype.indexOf = function( v, b, s ) {
  12. for( var i = +b || 0, l = this.length; i < l; i++ ) {
  13. if( this[i]===v || s && this[i]==v ) { return i; }
  14. }
  15. return -1;
  16. };
  17. }
  18. (function() {
  19. var ie = (/MSIE/.test(navigator.userAgent) && !window.opera);
  20. var log = null;
  21. var repaintFunction = function() { jsPlumb.repaintEverything(); };
  22. var automaticRepaint = true;
  23. function repaintEverything() {
  24. if (automaticRepaint)
  25. repaintFunction();
  26. };
  27. var resizeTimer = null;
  28. $(window).bind('resize', function() {
  29. if (resizeTimer) clearTimeout(resizeTimer);
  30. resizeTimer = setTimeout(repaintEverything, 100);
  31. });
  32. /**
  33. * map of element id -> endpoint lists. an element can have an arbitrary number of endpoints on it,
  34. * and not all of them have to be connected to anything.
  35. */
  36. var endpointsByElement = {};
  37. var offsets = [];
  38. var floatingConnections = {};
  39. var draggableStates = {};
  40. var _draggableByDefault = true;
  41. var sizes = [];
  42. var _jsPlumbContextNode = null;
  43. 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.)
  44. /**
  45. * helper method to add an item to a list, creating the list if it does not yet exist.
  46. */
  47. var _addToList = function(map, key, value) {
  48. var l = map[key];
  49. if (l == null) {
  50. l = [];
  51. map[key] = l;
  52. }
  53. l.push(value);
  54. };
  55. /**
  56. * Handles the dragging of an element.
  57. * @param element jQuery element
  58. * @param ui UI object from jQuery's event system
  59. */
  60. var _draw = function(element, ui) {
  61. var id = $(element).attr("id");
  62. var endpoints = endpointsByElement[id];
  63. if (endpoints) {
  64. //if (ui == null) _updateOffset(id, ui);
  65. _updateOffset(id, ui);
  66. var myOffset = offsets[id];
  67. var myWH = sizes[id];
  68. // loop through endpoints for this element
  69. for (var i = 0; i < endpoints.length; i++) {
  70. var e = endpoints[i];
  71. // first, paint the endpoint
  72. var anchorLoc = endpoints[i].anchor.compute([myOffset.left, myOffset.top], myWH);
  73. // var anchorOrientation = endpoints[i].anchor.getOrientation();
  74. //todo...connector paint style? we have lost that with the move to endpoint-centric.
  75. // perhaps we only paint the endpoint here if it has no connections; it can use its own style.
  76. //if (!e.connections || e.connections.length == 0)
  77. e.paint(anchorLoc);
  78. // else {
  79. //if (e.connections && e.connections.length > 0) {
  80. // get all connections for the endpoint...
  81. var l = e.connections;
  82. for (var j = 0; j < l.length; j++)
  83. l[j].paint(id, ui); // ...and paint them.
  84. //}
  85. }
  86. }
  87. };
  88. /**
  89. * helper function: the second argument is a function taking two args - the first is a
  90. * jquery element, and the second is the element's id.
  91. *
  92. * the first argument may be one of three things:
  93. *
  94. * 1. a string, in the form "window1", for example. an element's id. if your id starts with a
  95. * hash then jsPlumb does not append its own hash too...
  96. * 2. a jquery element, already resolved using $(...).
  97. * 3. a list of strings/jquery elements.
  98. */
  99. var _elementProxy = function(element, fn) {
  100. var retVal = null;
  101. if (typeof element == 'object' && element.length) {
  102. retVal = [];
  103. for (var i = 0; i < element.length; i++) {
  104. var el = typeof element[i] == 'string' ? $("#" + element[i]) : element[i];
  105. var id = el.attr("id");
  106. retVal.push(fn(el, id)); // append return values to what we will return
  107. }
  108. }
  109. else {
  110. var el = typeof element == 'string' ?
  111. element.indexOf("#") == 0 ? $(element) : $("#" + element)
  112. : element;
  113. var id = el.attr("id");
  114. retVal = fn(el, id);
  115. }
  116. return retVal;
  117. };
  118. /**
  119. * Returns (creating if necessary) the DIV element that jsPlumb uses as the context for all of its
  120. * canvases. having this makes it possible to makes calls like $("selector", context), which are
  121. * faster than if you provide no context. also we can clear out everything easily like this, either
  122. * on a detachEverything() call or during unload().
  123. */
  124. var _getContextNode = function() {
  125. if (_jsPlumbContextNode == null) {
  126. _jsPlumbContextNode= document.createElement("div");
  127. document.body.appendChild(_jsPlumbContextNode);
  128. _jsPlumbContextNode.className = "_jsPlumb_context";
  129. }
  130. return $(_jsPlumbContextNode);
  131. };
  132. /**
  133. * gets an id for the given element, creating and setting one if necessary.
  134. */
  135. var _getId = function(element) {
  136. var id = $(element).attr("id");
  137. if (!id) {
  138. id = "_jsPlumb_" + new String((new Date()).getTime());
  139. $(element).attr("id", id);
  140. }
  141. return id;
  142. };
  143. /**
  144. * inits a draggable if it's not already initialised.
  145. * todo: if the element was draggable already, like from some non-jsPlumb call, wrap the drag function.
  146. */
  147. var _initDraggableIfNecessary = function(element, elementId, isDraggable, dragOptions) {
  148. // dragging
  149. var draggable = isDraggable == null ? _draggableByDefault : isDraggable;
  150. if (draggable && element.draggable) {
  151. var options = dragOptions || jsPlumb.Defaults.DragOptions;
  152. var dragCascade = options.drag || function(e,u) {};
  153. var initDrag = function(element, elementId, dragFunc) {
  154. var opts = $.extend({drag:dragFunc}, options);
  155. var draggable = draggableStates[elementId];
  156. opts.disabled = draggable == null ? false : !draggable;
  157. element.draggable(opts);
  158. };
  159. initDrag(element, elementId, function(event, ui) {
  160. _draw(element, ui);
  161. $(element).addClass("jsPlumb_dragged");
  162. dragCascade(event, ui);
  163. });
  164. }
  165. };
  166. var _log = function(msg) {
  167. // not implemented. yet.
  168. }
  169. /**
  170. * helper to create a canvas.
  171. * @param clazz optional class name for the canvas.
  172. */
  173. var _newCanvas = function(clazz) {
  174. var canvas = document.createElement("canvas");
  175. _getContextNode().append(canvas);
  176. canvas.style.position="absolute";
  177. if (clazz) { canvas.className=clazz; }
  178. if (/MSIE/.test(navigator.userAgent) && !window.opera) {
  179. // for IE we have to set a big canvas size. actually you can override this, too, if 1200 pixels
  180. // is not big enough for the biggest connector/endpoint canvas you have at startup.
  181. jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE);
  182. canvas = G_vmlCanvasManager.initElement(canvas);
  183. }
  184. return canvas;
  185. };
  186. /**
  187. * performs the given function operation on all the connections found for the given element
  188. * id; this means we find all the endpoints for the given element, and then for each endpoint
  189. * find the connectors connected to it. then we pass each connection in to the given
  190. * function.
  191. */
  192. var _operation = function(elId, func) {
  193. var endpoints = endpointsByElement[elId];
  194. if (endpoints && endpoints.length) {
  195. for (var i = 0; i < endpoints.length; i++) {
  196. for (var j = 0; j < endpoints[i].connections.length; j++) {
  197. var retVal = func(endpoints[i].connections[j]);
  198. // if the function passed in returns true, we exit.
  199. // most functions return false.
  200. if (retVal) return;
  201. }
  202. }
  203. }
  204. };
  205. /**
  206. * perform an operation on all elements.
  207. */
  208. var _operationOnAll = function(func) {
  209. for (var elId in endpointsByElement) {
  210. _operation(elId, func);
  211. }
  212. };
  213. /**
  214. * helper to remove an element from the DOM.
  215. */
  216. var _removeElement = function(element) {
  217. if (element != null) {
  218. try { _jsPlumbContextNode.removeChild(element); }
  219. catch (e) { }
  220. }
  221. };
  222. /**
  223. * helper to remove a list of elements from the DOM.
  224. */
  225. var _removeElements = function(elements) {
  226. for (var i in elements)
  227. _removeElement(elements[i]);
  228. };
  229. /**
  230. * helper method to remove an item from a list.
  231. */
  232. var _removeFromList = function(map, key, value) {
  233. var l = map[key];
  234. if (l != null) {
  235. var i = l.indexOf(value);
  236. if (i >= 0) {
  237. delete( l[i] );
  238. l.splice( i, 1 );
  239. return true;
  240. }
  241. }
  242. return false;
  243. };
  244. /**
  245. * Sets whether or not the given element(s) should be draggable, regardless of what a particular
  246. * plumb command may request.
  247. *
  248. * @param element May be a string, a jQuery elements, or a list of strings/jquery elements.
  249. * @param draggable Whether or not the given element(s) should be draggable.
  250. */
  251. var _setDraggable = function(element, draggable) {
  252. var _helper = function(el, id) {
  253. draggableStates[id] = draggable;
  254. if (el.draggable) {
  255. el.draggable("option", "disabled", !draggable);
  256. }
  257. };
  258. return _elementProxy(element, _helper);
  259. };
  260. /**
  261. * private method to do the business of hiding/showing.
  262. * @param elId Id of the element in question
  263. * @param state String specifying a value for the css 'display' property ('block' or 'none').
  264. */
  265. var _setVisible = function(elId, state) {
  266. var f = function(jpc) {
  267. //todo should we find all the endpoints instead of going by connection? this will
  268. jpc.canvas.style.display = state;
  269. /*jpc.sourceEndpointCanvas.style.display = state;
  270. jpc.targetEndpointCanvas.style.display = state;*/
  271. };
  272. _operation(elId, f);
  273. };
  274. /**
  275. * toggles the draggable state of the element with the given id.
  276. */
  277. var _toggleDraggable = function(el) {
  278. var fn = function(el, elId) {
  279. var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId];
  280. state = !state;
  281. draggableStates[elId] = state;
  282. el.draggable("option", "disabled", !state);
  283. return state;
  284. };
  285. return _elementProxy(el, fn);
  286. };
  287. /**
  288. * private method to do the business of toggling hiding/showing.
  289. * @param elId Id of the element in question
  290. */
  291. var _toggleVisible = function(elId) {
  292. var f = function(jpc) {;
  293. var state = ('none' == jpc.canvas.style.display);
  294. jpc.canvas.style.display = state ? "block" : "none";
  295. /*jpc.sourceEndpointCanvas.style.display = state;
  296. jpc.targetEndpointCanvas.style.display = state;*/
  297. };
  298. _operation(elId, f);
  299. };
  300. /**
  301. * updates the offset and size for a given element, and stores the values.
  302. * 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,
  303. * or if 'recalc' is true in order to force a recalculation, we use the offset, outerWidth and outerHeight methods to get
  304. * the current values.
  305. */
  306. var _updateOffset = function(elId, ui, recalc) {
  307. if (log) log.debug("updating offset for element [" + elId + "]; ui is [" + ui + "]; recalc is [" + recalc + "]");
  308. if (recalc || ui == null) { // if forced repaint or no ui helper available, we recalculate.
  309. // get the current size and offset, and store them
  310. var s = $("#" + elId);
  311. sizes[elId] = [s.outerWidth(), s.outerHeight()];
  312. offsets[elId] = s.offset();
  313. } else {
  314. // faster to use the ui element if it was passed in.
  315. // fix for change in 1.8 (absolutePosition renamed to offset). plugin is compatible with
  316. // 1.8 and 1.7.
  317. // todo: when the drag axis is supplied, the ui object passed in has incorrect values
  318. // for the other axis - like say you set axis='x'. when you move the mouse up and down
  319. // while dragging, the y values are for where the window would be if it was not
  320. // just constrained to x. not sure if this is a jquery bug or whether there's a known
  321. // trick or whatever.
  322. var pos = ui.absolutePosition || ui.offset;
  323. var anOffset = ui != null ? pos : $("#" + elId).offset();
  324. offsets[elId] = anOffset;
  325. }
  326. };
  327. /**
  328. * wraps one function with another, creating a placeholder for the wrapped function
  329. * if it was null. this is used to wrap the various drag/drop event functions - to allow
  330. * jsPlumb to be notified of important lifecycle events without imposing itself on the user's
  331. * drap/drop functionality.
  332. * TODO: determine whether or not we should try/catch the plumb function, so that the cascade function is always executed.
  333. */
  334. var _wrap = function(cascadeFunction, plumbFunction) {
  335. cascadeFunction = cascadeFunction || function(e, ui) { };
  336. return function(e, ui) {
  337. plumbFunction(e, ui);
  338. cascadeFunction(e, ui);
  339. };
  340. }
  341. /**
  342. * Anchor class. Anchors can be situated anywhere.
  343. * params should contain three values, and may optionally have an 'offsets' argument:
  344. *
  345. * x : the x location of the anchor as a fraction of the total width.
  346. *
  347. * y : the y location of the anchor as a fraction of the total height.
  348. *
  349. * orientation : an [x,y] array indicating the general direction a connection
  350. * from the anchor should go in. for more info on this, see the documentation,
  351. * or the docs in jquery-jsPlumb-defaults-XXX.js for the default Anchors.
  352. *
  353. * offsets : an [x,y] array of fixed offsets that should be applied after the x,y position has been figured out. may be null.
  354. *
  355. */
  356. var Anchor = function(params) {
  357. var self = this;
  358. this.x = params.x || 0; this.y = params.y || 0;
  359. var orientation = params.orientation || [0,0];
  360. this.offsets = params.offsets || [0,0];
  361. this.compute = function(xy, wh, txy, twh) {
  362. return [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ];
  363. }
  364. this.getOrientation = function() { return orientation; };
  365. };
  366. /**
  367. * an anchor that floats. its orientation is computed dynamically from its position relative
  368. * to the anchor it is floating relative to.
  369. */
  370. var FloatingAnchor = function(params) {
  371. // this is the anchor that this floating anchor is referenced to for purposes of calculating the orientation.
  372. var ref = params.reference;
  373. // these are used to store the current relative position of our anchor wrt the reference anchor. they only indicate
  374. // direction, so have a value of 1 or -1 (or, very rarely, 0). these values are written by the compute method, and read
  375. // by the getOrientation method.
  376. var xDir = 0, yDir = 0;
  377. // temporary member used to store an orientation when the floating anchor is hovering over another anchor.
  378. var orientation = null;
  379. this.compute = function(xy, wh, txy, twh) {
  380. // set these for the getOrientation method to use.
  381. xDir = 0;//xy[0] < txy[0] ? -1 : xy[0] == txy[0] ? 0 : 1;
  382. yDir = 0;//xy[1] < txy[1] ? -1 : xy[1] == txy[1] ? 0 : 1;
  383. 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.
  384. };
  385. this.getOrientation = function() {
  386. if (orientation) return orientation;
  387. else {
  388. var o = ref.getOrientation();
  389. // here we take into account the orientation of the other anchor: if it declares zero for some direction, we declare zero too.
  390. // 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
  391. // like it makes sense. maybe this wont make sense.
  392. return [Math.abs(o[0]) * xDir * -1, Math.abs(o[1]) * yDir * -1];
  393. }
  394. };
  395. /**
  396. * notification the endpoint associated with this anchor is hovering over another anchor;
  397. * we want to assume that anchor's orientation for the duration of the hover.
  398. */
  399. this.over = function(anchor) {
  400. orientation = anchor.getOrientation();
  401. };
  402. /**
  403. * notification the endpoint associated with this anchor is no longer hovering
  404. * over another anchor; we should resume calculating orientation as we normally do.
  405. */
  406. this.out = function() {
  407. orientation = null;
  408. };
  409. };
  410. // ************** connection
  411. // ****************************************
  412. /**
  413. * allowed params:
  414. * source: source element (string or a jQuery element) (required)
  415. * target: target element (string or a jQuery element) (required)
  416. *
  417. * anchors: optional array of anchor placements. defaults to BottomCenter for source
  418. * and TopCenter for target.
  419. */
  420. var Connection = function(params) {
  421. // ************** get the source and target and register the connection. *******************
  422. var self = this;
  423. // get source and target as jQuery objects
  424. this.source = (typeof params.source == 'string') ? $("#" + params.source) : params.source;
  425. this.target = (typeof params.target == 'string') ? $("#" + params.target) : params.target;
  426. this.sourceId = $(this.source).attr("id");
  427. this.targetId = $(this.target).attr("id");
  428. this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true;
  429. // init endpoints
  430. this.endpoints = [];
  431. this.endpointStyles = [];
  432. var prepareEndpoint = function(existing, index, params) {
  433. if (existing) self.endpoints[index] = existing;
  434. else {
  435. if(!params.endpoints) params.endpoints = [null,null];
  436. var ep = params.endpoints[index] || params.endpoint || jsPlumb.Defaults.Endpoints[index] || jsPlumb.Defaults.Endpoint|| new jsPlumb.Endpoints.Dot();
  437. if (!params.endpointStyles) params.endpointStyles = [null,null];
  438. var es = params.endpointStyles[index] || params.endpointStyle || jsPlumb.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyle;
  439. var a = params.anchors ? params.anchors[index] : jsPlumb.Defaults.Anchors[index] || jsPlumb.Anchors.BottomCenter;
  440. self.endpoints[index] = new Endpoint({style:es, endpoint:ep, connections:[self], anchor:a });
  441. }
  442. };
  443. prepareEndpoint(params.sourceEndpoint, 0, params);
  444. prepareEndpoint(params.targetEndpoint, 1, params);
  445. // make connector. if an endpoint has a connector + paintstyle to use, we use that.
  446. // otherwise we use sensible defaults.
  447. //this.connector = params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier();
  448. this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier();
  449. //this.paintStyle = params.paintStyle || jsPlumb.Defaults.PaintStyle;
  450. this.paintStyle = this.endpoints[0].connectionStyle || this.endpoints[1].connectionStyle || params.paintStyle || jsPlumb.Defaults.PaintStyle;
  451. _updateOffset(this.sourceId);
  452. _updateOffset(this.targetId);
  453. // paint the endpoints
  454. var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId];
  455. var anchorLoc = this.endpoints[0].anchor.compute([myOffset.left, myOffset.top], myWH);
  456. this.endpoints[0].paint(anchorLoc);
  457. myOffset = offsets[this.targetId]; myWH = sizes[this.targetId];
  458. anchorLoc = this.endpoints[1].anchor.compute([myOffset.left, myOffset.top], myWH);
  459. this.endpoints[1].paint(anchorLoc);
  460. // *************** create canvas on which the connection will be drawn ************
  461. var canvas = _newCanvas(jsPlumb.connectorClass);
  462. this.canvas = canvas;
  463. /**
  464. * paints the connection.
  465. * @param elId Id of the element that is in motion
  466. * @param ui jQuery's event system ui object (present if we came from a drag to get here)
  467. * @param recalc whether or not to recalculate element sizes. this is true if a repaint caused this to be painted.
  468. */
  469. this.paint = function(elId, ui, recalc) {
  470. if (log) log.debug("Painting Connection; element in motion is " + elId + "; ui is [" + ui + "]; recalc is [" + recalc + "]");
  471. var fai = self.floatingAnchorIndex;
  472. // if the moving object is not the source we must transpose the two references.
  473. var swap = !(elId == this.sourceId);
  474. var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId;
  475. var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0;
  476. var el = swap ? this.target : this.source;
  477. if (this.canvas.getContext) {
  478. _updateOffset(elId, ui, recalc);
  479. if (recalc) _updateOffset(tId); // update the target if this is a forced repaint. otherwise, only the source has been moved.
  480. var myOffset = offsets[elId];
  481. var otherOffset = offsets[tId];
  482. var myWH = sizes[elId];
  483. var otherWH = sizes[tId];
  484. var ctx = canvas.getContext('2d');
  485. var sAnchorP = this.endpoints[sIdx].anchor.compute([myOffset.left, myOffset.top], myWH, [otherOffset.left, otherOffset.top], otherWH);
  486. var sAnchorO = this.endpoints[sIdx].anchor.getOrientation();
  487. var tAnchorP = this.endpoints[tIdx].anchor.compute([otherOffset.left, otherOffset.top], otherWH, [myOffset.left, myOffset.top], myWH);
  488. var tAnchorO = this.endpoints[tIdx].anchor.getOrientation();
  489. var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth);
  490. jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]);
  491. $.extend(ctx, this.paintStyle);
  492. if (this.paintStyle.gradient && !ie) {
  493. var g = swap ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]);
  494. for (var i = 0; i < this.paintStyle.gradient.stops.length; i++)
  495. g.addColorStop(this.paintStyle.gradient.stops[i][0],this.paintStyle.gradient.stops[i][1]);
  496. ctx.strokeStyle = g;
  497. }
  498. this.connector.paint(dim, ctx);
  499. // this.endpoints[swap ? 1 : 0].paint(sAnchorP, this.paintStyle);
  500. //this.endpoints[swap ? 0 : 1].paint(tAnchorP, this.paintStyle);
  501. }
  502. };
  503. this.repaint = function() {
  504. this.paint(this.sourceId, null, true);
  505. };
  506. _initDraggableIfNecessary(self.source, self.sourceId, params.draggable, params.dragOptions);
  507. _initDraggableIfNecessary(self.target, self.targetId, params.draggable, params.dragOptions);
  508. // resizing (using the jquery.ba-resize plugin). todo: decide whether to include or not.
  509. if (this.source.resize) {
  510. this.source.resize(function(e) {
  511. jsPlumb.repaint(self.sourceId);
  512. });
  513. }
  514. };
  515. /**
  516. * models an endpoint. can have one to N connections emanating from it (although how to handle that in the UI is
  517. * a very good question). also has a Canvas and paint style.
  518. *
  519. * params:
  520. *
  521. * anchor : anchor for the endpoint, of type jsPlumb.Anchor. may be null.
  522. * endpoint : endpoint object, of type jsPlumb.Endpoint. may be null.
  523. * style : endpoint style, a js object. may be null.
  524. * source : element the endpoint is attached to, of type jquery object. Required.
  525. * canvas : canvas element to use. may be, and most often is, null.
  526. * connections : optional list of connections to configure the endpoint with.
  527. * isSource : boolean. indicates the endpoint can act as a source of new connections. optional.
  528. * dragOptions : if isSource is set to true, you can supply arguments for the jquery draggable method. optional.
  529. * connectionStyle : if isSource is set to true, this is the paint style for connections from this endpoint. optional.
  530. * connector : optional connector type to use.
  531. * isTarget : boolean. indicates the endpoint can act as a target of new connections. optional.
  532. * dropOptions : if isTarget is set to true, you can supply arguments for the jquery droppable method. optional.
  533. * reattach : optional boolean that determines whether or not the connections reattach after they
  534. * have been dragged off an endpoint and left floating. defaults to false - connections
  535. * dropped in this way will just be deleted.
  536. */
  537. var Endpoint = function(params) {
  538. params = params || {};
  539. // make a copy. then we can use the wrapper function.
  540. params = $.extend({}, params);
  541. var self = this;
  542. self.anchor = params.anchor || jsPlumb.Anchors.TopCenter;
  543. var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot();
  544. var _style = params.style || jsPlumb.Defaults.EndpointStyle;
  545. this.connectionStyle = params.connectionStyle;
  546. this.connector = params.connector;
  547. var _element = params.source;
  548. var _elementId = $(_element).attr("id");
  549. var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of.
  550. this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass);
  551. this.connections = params.connections || [];
  552. var _reattach = params.reattach || false;
  553. var floatingEndpoint = null;
  554. this.addConnection = function(connection) {
  555. self.connections.push(connection);
  556. };
  557. this.removeConnection = function(connection) {
  558. var idx = self.connections.indexOf(connection);
  559. if (idx >= 0)
  560. self.connections.splice(idx, 1);
  561. };
  562. this.isFloating = function() { return floatingEndpoint != null; };
  563. /**
  564. * first pass at default ConnectorSelector: returns the first connection, if we have any.
  565. * modified a little, 5/10/2010: now it only returns a connector if we have not got equal to or more than _maxConnector connectors
  566. * 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
  567. * one. what if this could return a list? that implies everything should work with a list - dragging etc. could be nasty. could also
  568. * be cool.
  569. */
  570. var connectorSelector = function() {
  571. return self.connections.length == 0 || self.connections.length < _maxConnections ? null : self.connections[0];
  572. };
  573. // get the jsplumb context...lookups are faster with a context.
  574. var contextNode = _getContextNode();
  575. this.isFull = function() { return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections); };
  576. /**
  577. * paints the Endpoint, recalculating offset and anchor positions if necessary.
  578. */
  579. this.paint = function(anchorPoint, connectorPaintStyle, canvas) {
  580. if (log) log.debug("Painting Endpoint with elementId [" + _elementId + "]; anchorPoint is [" + anchorPoint + "]");
  581. if (anchorPoint == null) {
  582. // do we always want to force a repaint here? i dont think so!
  583. var xy = offsets[_elementId];
  584. var wh = sizes[_elementId];
  585. if (xy == null || wh == null) {
  586. _updateOffset(_elementId);
  587. xy = offsets[_elementId];
  588. wh = sizes[_elementId];
  589. }
  590. anchorPoint = self.anchor.compute([xy.left, xy.top], wh);
  591. }
  592. _endpoint.paint(anchorPoint, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style);
  593. };
  594. // is this a connection source? we make it draggable and have the drag listener
  595. // maintain a connection with a floating endpoint.
  596. if (params.isSource && _element.draggable) {
  597. var n = null, id = null,
  598. jpc = null,
  599. existingJpc = false, existingJpcParams = null;
  600. // 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
  601. // to select one connection from the list, or drag them all. if we had a pluggable 'ConnectorSelector' interface we could probably
  602. // 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
  603. // information the interface needs from jsPlumb at execution time. if, however, we leave out the connector selection, and drag them all,
  604. // that wouldn't be too hard to organise. perhaps that behaviour would be on a switch for the endpoint, or perhaps the ConnectorSelector
  605. // interface returns a List, with the default implementation just returning everything. i think i like that.
  606. //
  607. // 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.
  608. // the major difference between that case and the case of a new endpoint is actually quite small - it's a question of where the
  609. // Connection comes from. for a new one, we create a new one. obviously. otherwise we just get the jpc from the Endpoint
  610. // (remember we're only assuming one connection right now). so all of the UI stuff we do to create the floating endpoint etc
  611. // 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
  612. // 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.
  613. var start = function(e, ui) {
  614. //if (!isFull()) {
  615. n = document.createElement("div");
  616. contextNode.append(n);
  617. // create and assign an id, and initialize the offset.
  618. id = new String(new Date().getTime());
  619. $(n, contextNode).attr("id", id);
  620. _updateOffset(id);
  621. // store the id of the dragging div and the source element. the drop function
  622. // will pick these up.
  623. $(self.canvas, contextNode).attr("dragId", id);
  624. $(self.canvas, contextNode).attr("elId", _elementId);
  625. // create a floating anchor
  626. var floatingAnchor = new FloatingAnchor({reference:self.anchor});
  627. floatingEndpoint = new Endpoint({
  628. style:_style,
  629. endpoint:_endpoint,
  630. anchor:floatingAnchor,
  631. source:n
  632. });
  633. //floatingEndpoint.originalAnchor =
  634. jpc = connectorSelector();
  635. if (jpc == null) {
  636. // create a connection. one end is this endpoint, the other is a floating endpoint.
  637. jpc = new Connection({
  638. sourceEndpoint:self,
  639. targetEndpoint:floatingEndpoint,
  640. source:$(_element),
  641. target:$(n, contextNode),
  642. anchors:[self.anchor, floatingAnchor],
  643. paintStyle : params.connectionStyle, // this can be null. Connection will use the default.
  644. connector: params.connector
  645. });
  646. // todo ...unregister on stop
  647. self.addConnection(jpc);
  648. } else {
  649. existingJpc = true;
  650. var anchorIdx = jpc.sourceId == _elementId ? 0 : 1;
  651. jpc.floatingAnchorIndex = anchorIdx;
  652. // probably we should remove the connection? and add it back if the user
  653. // does not drop it somewhere proper.
  654. self.removeConnection(jpc);
  655. if (anchorIdx == 0){
  656. existingJpcParams = [jpc.source, jpc.sourceId];
  657. jpc.source = $(n, contextNode);
  658. jpc.sourceId = id;
  659. }else {
  660. existingJpcParams = [jpc.target, jpc.targetId];
  661. jpc.target = $(n, contextNode);
  662. jpc.targetId = id;
  663. }
  664. jpc.suspendedEndpoint = jpc.endpoints[anchorIdx];
  665. jpc.endpoints[anchorIdx] = floatingEndpoint;
  666. }
  667. // register it.
  668. floatingConnections[id] = jpc;
  669. // todo unregister on stop
  670. floatingEndpoint.addConnection(jpc);
  671. // only register for the target endpoint; we will not be dragging the source at any time
  672. // before this connection is either discarded or made into a permanent connection.
  673. _addToList(endpointsByElement, id, floatingEndpoint);
  674. }
  675. //};
  676. var dragOptions = params.dragOptions || { };
  677. dragOptions = $.extend({ opacity:0.5, revert:true, helper:'clone' }, dragOptions);
  678. dragOptions.start = _wrap(dragOptions.start, start);
  679. dragOptions.drag = _wrap(dragOptions.drag, function(e, ui) {
  680. _draw($(n, contextNode), ui);
  681. });
  682. dragOptions.stop = _wrap(dragOptions.stop,
  683. function(e, ui) {
  684. _removeFromList(endpointsByElement, id, floatingEndpoint);
  685. _removeElements([floatingEndpoint.canvas, n]);
  686. var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
  687. if (jpc.endpoints[idx] == floatingEndpoint) {
  688. // if the connection was an existing one:
  689. if (existingJpc && jpc.suspendedEndpoint) {
  690. if (_reattach) {
  691. jpc.floatingAnchorIndex = null;
  692. if (idx == 0) {
  693. jpc.source = existingJpcParams[0];
  694. jpc.sourceId = existingJpcParams[1];
  695. } else {
  696. jpc.target = existingJpcParams[0];
  697. jpc.targetId = existingJpcParams[1];
  698. }
  699. jpc.endpoints[idx] = jpc.suspendedEndpoint;
  700. jpc.suspendedEndpoint.addConnection(jpc);
  701. jsPlumb.repaint(existingJpcParams[1]);
  702. }
  703. else {
  704. jpc.endpoints[0].removeConnection(jpc);
  705. jpc.endpoints[1].removeConnection(jpc);
  706. _removeElement(jpc.canvas);
  707. }
  708. } else {
  709. _removeElement(jpc.canvas);
  710. self.removeConnection(jpc);
  711. }
  712. }
  713. jpc = null;
  714. delete floatingEndpoint;
  715. }
  716. );
  717. $(self.canvas, contextNode).draggable(dragOptions);
  718. }
  719. // connector target
  720. if (params.isTarget && _element.droppable) {
  721. var dropOptions = params.dropOptions || jsPlumb.Defaults.DropOptions;
  722. dropOptions = $.extend({}, dropOptions);
  723. var originalAnchor = null;
  724. dropOptions.drop = _wrap(dropOptions.drop, function(e, ui) {
  725. var id = $(ui.draggable, contextNode).attr("dragId");
  726. var elId = $(ui.draggable, contextNode).attr("elId");
  727. var jpc = floatingConnections[id];
  728. var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
  729. if (idx == 0) {
  730. jpc.source = _element;
  731. jpc.sourceId = _elementId;
  732. } else {
  733. jpc.target = _element;
  734. jpc.targetId = _elementId;
  735. }
  736. // remove this jpc from the current endpoint
  737. jpc.endpoints[idx].removeConnection(jpc);
  738. if (jpc.suspendedEndpoint)
  739. jpc.suspendedEndpoint.removeConnection(jpc);
  740. jpc.endpoints[idx] = self;
  741. self.addConnection(jpc);
  742. _initDraggableIfNecessary(_element, _elementId, params.draggable, {});
  743. jsPlumb.repaint($(ui.draggable, contextNode).attr("elId"));
  744. delete floatingConnections[id];
  745. });
  746. // what to do when something is dropped.
  747. // 1. find the jpc that is being dragged. the target endpoint of the jpc will be the
  748. // one that is being dragged.
  749. // 2. arrange for the floating endpoint to be replaced with this endpoint; make sure
  750. // everything gets registered ok etc.
  751. // 3. arrange for the floating endpoint to be deleted.
  752. // 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.
  753. // other considerations: when in the hover mode, we should switch the floating endpoint's
  754. // orientation to be the same as the drop target. this will cause the connector to snap
  755. // into the shape it will take if the user drops at that point.
  756. dropOptions.over = _wrap(dropOptions.over, function(event, ui) {
  757. var id = $(ui.draggable, contextNode).attr("dragId");
  758. var jpc = floatingConnections[id];
  759. var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
  760. jpc.endpoints[idx].anchor.over(self.anchor);
  761. });
  762. dropOptions.out = _wrap(dropOptions.out, function(event, ui) {
  763. var id = $(ui.draggable, contextNode).attr("dragId");
  764. var jpc = floatingConnections[id];
  765. var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
  766. jpc.endpoints[idx].anchor.out();
  767. });
  768. $(self.canvas, contextNode).droppable(dropOptions);
  769. }
  770. // woo...add a plumb command to Endpoint.
  771. this.plumb = function(params) {
  772. // not much to think about. the target should be an Endpoint, but what else?
  773. // all the style stuff is on the endpoint itself already.
  774. //todo this should call the main plumb method, just with some different args.
  775. };
  776. return self;
  777. };
  778. /**
  779. * jsPlumb public API
  780. */
  781. var jsPlumb = window.jsPlumb = {
  782. Defaults : {
  783. Anchors : [ null, null ],
  784. Connector : null,
  785. DragOptions: { },
  786. DropOptions: { },
  787. Endpoint : null,
  788. Endpoints : [ null, null ],
  789. EndpointStyle : { fillStyle : null },
  790. EndpointStyles : [ null, null ],
  791. MaxConnections : null,
  792. PaintStyle : { lineWidth : 10, strokeStyle : 'red' }
  793. },
  794. connectorClass : '_jsPlumb_connector',
  795. endpointClass : '_jsPlumb_endpoint',
  796. Anchors : {},
  797. Connectors : {},
  798. Endpoints : {},
  799. /**
  800. * adds an endpoint to the element
  801. */
  802. addEndpoint : function(target, params) {
  803. params = $.extend({}, params);
  804. var el = typeof target == 'string' ? $("#" + target) : target;
  805. var id = $(el).attr("id");
  806. params.source = el;
  807. _updateOffset(id);
  808. var e = new Endpoint(params);
  809. _addToList(endpointsByElement, id, e);
  810. var myOffset = offsets[id];
  811. var myWH = sizes[id];
  812. var anchorLoc = e.anchor.compute([myOffset.left, myOffset.top], myWH);
  813. e.paint(anchorLoc);
  814. return e;
  815. },
  816. /**
  817. * wrapper around standard jquery animate function; injects a call to jsPlumb in the
  818. * 'step' function (creating it if necessary). this only supports the two-arg version
  819. * of the animate call in jquery - the one that takes an object as the second arg.
  820. */
  821. animate : function(el, properties, options) {
  822. var ele = typeof(el)=='string' ? $("#" + el) : el;
  823. var id = ele.attr("id");
  824. options = options || {};
  825. options.step = _wrap(options.step, function() { jsPlumb.repaint(id); });
  826. ele.animate(properties, options);
  827. },
  828. /**
  829. * adds a list of endpoints to the element
  830. */
  831. addEndpoints : function(target, endpoints) {
  832. var results = [];
  833. for (var i = 0; i < endpoints.length; i++) {
  834. results.push(jsPlumb.addEndpoint(target, endpoints[i]));
  835. }
  836. return results;
  837. },
  838. /**
  839. * establishes a connection between two elements.
  840. * @param params object containing setup for the connection. see documentation.
  841. */
  842. connect : function(params) {
  843. if (params.sourceEndpoint && params.sourceEndpoint.isFull()) {
  844. _log("could not add connection; source endpoint is full");
  845. return;
  846. }
  847. if (params.targetEndpoint && params.targetEndpoint.isFull()) {
  848. _log("could not add connection; target endpoint is full");
  849. return;
  850. }
  851. var jpc = new Connection(params);
  852. // register endpoints for the element
  853. _addToList(endpointsByElement, jpc.sourceId, jpc.endpoints[0]);
  854. _addToList(endpointsByElement, jpc.targetId, jpc.endpoints[1]);
  855. jpc.endpoints[0].addConnection(jpc);
  856. jpc.endpoints[1].addConnection(jpc);
  857. // force a paint
  858. _draw(jpc.source);
  859. },
  860. connectEndpoints : function(params) {
  861. var jpc = Connection(params);
  862. },
  863. /**
  864. * Remove one connection to an element.
  865. * @param sourceId id of the first window in the connection
  866. * @param targetId id of the second window in the connection
  867. * @return true if successful, false if not.
  868. */
  869. detach : function(sourceId, targetId) {
  870. var f = function(jpc) {
  871. if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) {
  872. _removeElement(jpc.canvas);
  873. /*_removeElement(jpc.targetEndpointCanvas);
  874. _removeElement(jpc.sourceEndpointCanvas);*/
  875. jpc.endpoints[0].removeConnection(jpc);
  876. jpc.endpoints[1].removeConnection(jpc);
  877. return true;
  878. }
  879. };
  880. // todo: how to cleanup the actual storage? a third arg to _operation?
  881. _operation(sourceId, f);
  882. },
  883. /**
  884. * remove all an element's connections.
  885. * @param elId id of the
  886. */
  887. detachAll : function(elId) {
  888. var f = function(jpc) {
  889. // todo replace with _cleanupConnection call here.
  890. _removeElement(jpc.canvas);
  891. /*_removeElement(jpc.targetEndpointCanvas);
  892. _removeElement(jpc.sourceEndpointCanvas);*/
  893. jpc.endpoints[0].removeConnection(jpc);
  894. jpc.endpoints[1].removeConnection(jpc);
  895. };
  896. _operation(elId, f);
  897. //delete endpointsByElement[elId];
  898. },
  899. /**
  900. * remove all connections. and endpoints? probably not.
  901. */
  902. detachEverything : function() {
  903. var f = function(jpc) {
  904. _removeElement(jpc.canvas);
  905. /*_removeElement(jpc.targetEndpointCanvas);
  906. _removeElement(jpc.sourceEndpointCanvas);*/
  907. jpc.endpoints[0].removeConnection(jpc);
  908. jpc.endpoints[1].removeConnection(jpc);
  909. };
  910. _operationOnAll(f);
  911. /*delete endpointsByElement;
  912. endpointsByElement = {};*/
  913. },
  914. /**
  915. * Set an element's connections to be hidden.
  916. */
  917. hide : function(elId) {
  918. _setVisible(elId, "none");
  919. },
  920. /**
  921. * Creates an anchor with the given params.
  922. * x - the x location of the anchor as a fraction of the total width.
  923. * y - the y location of the anchor as a fraction of the total height.
  924. * orientation - an [x,y] array indicating the general direction a connection from the anchor should go in.
  925. * 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].
  926. */
  927. makeAnchor : function(x, y, xOrientation, yOrientation, xOffset, yOffset) {
  928. // backwards compatibility here. we used to require an object passed in but that makes the call very verbose. easier to use
  929. // 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.
  930. var params = {};
  931. if (arguments.length == 1) $.extend(params, x);
  932. else {
  933. params = {x:x, y:y};
  934. if (arguments.length >= 4) {
  935. params.orientation = [arguments[2], arguments[3]];
  936. }
  937. if (arguments.length == 6) params.offsets = [arguments[4], arguments[5]];
  938. }
  939. return new Anchor(params);
  940. },
  941. /**
  942. * repaint element and its connections. element may be an id or the actual jQuery object.
  943. * this method gets new sizes for the elements before painting anything.
  944. */
  945. repaint : function(el) {
  946. var _processElement = function(el) {
  947. var ele = typeof(el)=='string' ? $("#" + el) : el;
  948. _draw(ele);
  949. };
  950. // TODO: support a jQuery result object too!
  951. // support both lists...
  952. if (typeof el =='object') {
  953. for (var i = 0; i < el.length; i++)
  954. _processElement(el[i]);
  955. } // ...and single strings.
  956. else _processElement(el);
  957. },
  958. /**
  959. * repaint all connections.
  960. */
  961. repaintEverything : function() {
  962. for (var elId in endpointsByElement) {
  963. _draw($("#" + elId));
  964. }
  965. },
  966. removeEndpoint : function(elId, endpoint) {
  967. var ebe = endpointsByElement[elId];
  968. if (ebe) {
  969. // var i = ebe.indexOf(endpoint);
  970. // alert('element has ' + ebe.length);
  971. //if (i > -1) {
  972. //ebe.splice(i, 1);
  973. if(_removeFromList(endpointsByElement, elId, endpoint))
  974. _removeElement(endpoint.canvas);
  975. //}
  976. }
  977. },
  978. /**
  979. * sets/unsets automatic repaint on window resize.
  980. */
  981. setAutomaticRepaint : function(value) {
  982. automaticRepaint = value;
  983. },
  984. /**
  985. * Sets the default size jsPlumb will use for a new canvas (we create a square canvas so
  986. * one value is all that is required). This is a hack for IE, because ExplorerCanvas seems
  987. * to need for a canvas to be larger than what you are going to draw on it at initialisation
  988. * time. The default value of this is 1200 pixels, which is quite large, but if for some
  989. * reason you're drawing connectors that are bigger, you should adjust this value appropriately.
  990. */
  991. setDefaultNewCanvasSize : function(size) {
  992. DEFAULT_NEW_CANVAS_SIZE = size;
  993. },
  994. /**
  995. * Sets whether or not a given element is draggable, regardless of what any plumb command
  996. * may request.
  997. */
  998. setDraggable: _setDraggable,
  999. /**
  1000. * Sets whether or not elements are draggable by default. Default for this is true.
  1001. */
  1002. setDraggableByDefault: function(draggable) {
  1003. _draggableByDefault = draggable;
  1004. },
  1005. setDebugLog: function(debugLog) {
  1006. log = debugLog;
  1007. },
  1008. /**
  1009. * Sets the function to fire when the window size has changed and a repaint was fired.
  1010. */
  1011. setRepaintFunction : function(f) {
  1012. repaintFunction = f;
  1013. },
  1014. /**
  1015. * Set an element's connections to be visible.
  1016. */
  1017. show : function(elId) {
  1018. _setVisible(elId, "block");
  1019. },
  1020. /**
  1021. * helper to size a canvas.
  1022. */
  1023. sizeCanvas : function(canvas, x, y, w, h) {
  1024. canvas.style.height = h + "px"; canvas.height = h;
  1025. canvas.style.width = w + "px"; canvas.width = w;
  1026. canvas.style.left = x + "px"; canvas.style.top = y + "px";
  1027. },
  1028. /**
  1029. * gets some test hooks. nothing writable.
  1030. */
  1031. getTestHarness : function() {
  1032. return {
  1033. endpointCount : function(elId) {
  1034. var e = endpointsByElement[elId];
  1035. return e ? e.length : 0;
  1036. }
  1037. };
  1038. },
  1039. /**
  1040. * Toggles visibility of an element's connections. kept for backwards compatibility
  1041. */
  1042. toggle : _toggleVisible,
  1043. /**
  1044. * new name for the old toggle method.
  1045. */
  1046. toggleVisible : _toggleVisible,
  1047. /**
  1048. * Toggles draggability (sic) of an element.
  1049. */
  1050. toggleDraggable : _toggleDraggable,
  1051. /**
  1052. * Unloads jsPlumb, deleting all storage. You should call this
  1053. */
  1054. unload : function() {
  1055. delete endpointsByElement;
  1056. delete offsets;
  1057. delete sizes;
  1058. delete floatingConnections;
  1059. delete draggableStates;
  1060. document.body.removeChild(_jsPlumbContextNode);
  1061. }
  1062. };
  1063. })();
  1064. // jQuery plugin code
  1065. (function($){
  1066. /**
  1067. * plumbs the results of the selector to some target, using the given options if supplied,
  1068. * or the defaults otherwise.
  1069. */
  1070. $.fn.plumb = function(options) {
  1071. var options = $.extend({}, options);
  1072. return this.each(function()
  1073. {
  1074. var params = $.extend({source:$(this)}, options);
  1075. jsPlumb.connect(params);
  1076. });
  1077. };
  1078. /**
  1079. * detaches the results of the selector from the given target or list of targets - 'target'
  1080. * may be a String or a List.
  1081. */
  1082. $.fn.detach = function(target) {
  1083. return this.each(function()
  1084. {
  1085. var id = $(this).attr("id");
  1086. if (typeof target == 'string') target = [target];
  1087. for (var i = 0; i < target.length; i++)
  1088. jsPlumb.detach(id, target[i]);
  1089. });
  1090. };
  1091. /**
  1092. * detaches the results from the selector from all connections.
  1093. */
  1094. $.fn.detachAll = function() {
  1095. return this.each(function()
  1096. {
  1097. var id = $(this).attr("id");
  1098. jsPlumb.detachAll(id);
  1099. });
  1100. };
  1101. /**
  1102. * adds an endpoint to the elements resulting from the selector. options may be null,
  1103. * in which case jsPlumb will use the default options. see documentation.
  1104. */
  1105. $.fn.addEndpoint = function(options) {
  1106. var addedEndpoints = [];
  1107. this.each(function()
  1108. {
  1109. //var params = $.extend({source:$(this)}, options);
  1110. addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options));
  1111. });
  1112. return addedEndpoints;
  1113. };
  1114. /**
  1115. * adds a list of endpoints to the elements resulting from the selector. options may be null,
  1116. * in which case jsPlumb will use the default options. see documentation.
  1117. */
  1118. $.fn.addEndpoints = function(endpoints) {
  1119. var addedEndpoints = [];
  1120. return this.each(function()
  1121. {
  1122. var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints);
  1123. for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]);
  1124. });
  1125. return addedEndpoints;
  1126. };
  1127. /**
  1128. * remove the endpoint, if it exists, deleting its UI elements etc.
  1129. */
  1130. $.fn.removeEndpoint = function(endpoint) {
  1131. this.each(function()
  1132. {
  1133. jsPlumb.removeEndpoint($(this).attr("id"), endpoint);
  1134. });
  1135. };
  1136. })(jQuery);
  1137. /**
  1138. * jsPlumb-defaults-1.1.0
  1139. *
  1140. * This script contains the default Anchors, Endpoints and Connectors for jsPlumb. It should be used with jsPlumb 1.1.0 and above;
  1141. * prior to version 1.1.0 of jsPlumb the defaults were included inside the main script.
  1142. *
  1143. * NOTE: for production usage you should use jsPlumb-all-1.1.0-min.js, which contains the main jsPlumb script and this script together,
  1144. * in a minified file.
  1145. */
  1146. (function() {
  1147. /**
  1148. * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance
  1149. * of Anchor that has been configured appropriately.
  1150. *
  1151. * You can write your own one of these; you
  1152. * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this:
  1153. *
  1154. * jsPlumb.Anchors.MY_ANCHOR = {
  1155. * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; },
  1156. * getOrientation : function() { return [ox, oy]; }
  1157. * };
  1158. *
  1159. * compute takes the [x,y] position of the top left corner of the anchored element,
  1160. * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to,
  1161. * and returns where the anchor should be located.
  1162. *
  1163. * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor
  1164. * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference,
  1165. * -1 means go in a negative direction for the given axis, and 1 means go in a positive
  1166. * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1],
  1167. * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example,
  1168. * the curve would start out going in that direction, before bending towards the target anchor.
  1169. */
  1170. jsPlumb.Anchors.TopCenter = jsPlumb.makeAnchor(0.5, 0, 0,-1);
  1171. jsPlumb.Anchors.BottomCenter = jsPlumb.makeAnchor(0.5, 1, 0, 1);
  1172. jsPlumb.Anchors.LeftMiddle = jsPlumb.makeAnchor(0, 0.5, -1, 0);
  1173. jsPlumb.Anchors.RightMiddle = jsPlumb.makeAnchor(1, 0.5, 1, 0);
  1174. jsPlumb.Anchors.Center = jsPlumb.makeAnchor(0.5, 0.5, 0, 0);
  1175. jsPlumb.Anchors.TopRight = jsPlumb.makeAnchor(1, 0, 0,-1);
  1176. jsPlumb.Anchors.BottomRight = jsPlumb.makeAnchor(1, 1, 0, 1);
  1177. jsPlumb.Anchors.TopLeft = jsPlumb.makeAnchor(0, 0, 0, -1);
  1178. jsPlumb.Anchors.BottomLeft = jsPlumb.makeAnchor(0, 1, 0, 1);
  1179. /**
  1180. * The Straight connector draws a simple straight line between the two anchor points.
  1181. */
  1182. jsPlumb.Connectors.Straight = function() {
  1183. var self = this;
  1184. /**
  1185. * Computes the new size and position of the canvas.
  1186. * @param sourceAnchor Absolute position on screen of the source object's anchor.
  1187. * @param targetAnchor Absolute position on screen of the target object's anchor.
  1188. * @param positionMatrix Indicates the relative positions of the left,top of the
  1189. * two plumbed objects. so [0,0] indicates that the source is to the left of, and
  1190. * above, the target. [1,0] means the source is to the right and above. [0,1] means
  1191. * the source is to the left and below. [1,1] means the source is to the right
  1192. * and below. this is used to figure out which direction to draw the connector in.
  1193. * @returns an array of positioning information. the first two values are
  1194. * the [left, top] absolute position the canvas should be placed on screen. the
  1195. * next two values are the [width,height] the canvas should be. after that each
  1196. * Connector can put whatever it likes into the array:it will be passed back in
  1197. * to the paint call. This particular function stores the origin and destination of
  1198. * the line it is going to draw. a more involved implementation, like a Bezier curve,
  1199. * would store the control point info in this array too.
  1200. */
  1201. this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth) {
  1202. var w = Math.abs(sourcePos[0] - targetPos[0]);
  1203. var h = Math.abs(sourcePos[1] - targetPos[1]);
  1204. var widthAdjusted = false, heightAdjusted = false;
  1205. // these are padding to ensure the whole connector line appears
  1206. var xo = 0.45 * w, yo = 0.45 * h;
  1207. // these are padding to ensure the whole connector line appears
  1208. w *= 1.9; h *=1.9;
  1209. var x = Math.min(sourcePos[0], targetPos[0]) - xo;
  1210. var y = Math.min(sourcePos[1], targetPos[1]) - yo;
  1211. if (w < 2 * lineWidth) {
  1212. w = 2 * lineWidth;
  1213. x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - lineWidth;
  1214. xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2;
  1215. }
  1216. if (h < 2 * lineWidth) {
  1217. // minimum size is 2 * line Width
  1218. h = 2 * lineWidth;
  1219. y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - lineWidth;
  1220. yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2;
  1221. }
  1222. var sx = sourcePos[0] < targetPos[0] ? w-xo : xo;
  1223. var sy = sourcePos[1] < targetPos[1] ? h-yo : yo;
  1224. var tx = sourcePos[0] < targetPos[0] ? xo : w-xo;
  1225. var ty = sourcePos[1] < targetPos[1] ? yo : h-yo;
  1226. var retVal = [ x, y, w, h, sx, sy, tx, ty ];
  1227. return retVal;
  1228. };
  1229. this.paint = function(dimensions, ctx)
  1230. {
  1231. ctx.beginPath();
  1232. ctx.moveTo(dimensions[4], dimensions[5]);
  1233. ctx.lineTo(dimensions[6], dimensions[7]);
  1234. ctx.stroke();
  1235. };
  1236. };
  1237. /**
  1238. * This Connector draws a Bezier curve with two control points.
  1239. * @param curviness How 'curvy' you want the curve to be! This is a directive for the
  1240. * placement of control points, not endpoints of the curve, so your curve does not
  1241. * actually touch the given point, but it has the tendency to lean towards it. the larger
  1242. * this value, the greater the curve is pulled from a straight line.
  1243. *
  1244. * a future implementation of this could take the control points as arguments, rather
  1245. * than fixing the curve to one basic shape.
  1246. */
  1247. jsPlumb.Connectors.Bezier = function(curviness) {
  1248. var self = this;
  1249. this.majorAnchor = curviness || 150;
  1250. this.minorAnchor = 10;
  1251. this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) {
  1252. // determine if the two anchors are perpendicular to each other in their orientation. we swap the control
  1253. // points around if so (code could be tightened up)
  1254. var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation();
  1255. var perpendicular = soo[0] != too[0] || soo[1] == too[1];
  1256. var p = [];
  1257. var ma = self.majorAnchor, mi = self.minorAnchor;
  1258. if (!perpendicular) {
  1259. if (soo[0] == 0) // X
  1260. p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi);
  1261. else p.push(point[0] - (ma * soo[0]));
  1262. if (soo[1] == 0) // Y
  1263. p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi);
  1264. else p.push(point[1] + (ma * too[1]));
  1265. }
  1266. else {
  1267. if (too[0] == 0) // X
  1268. p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi);
  1269. else p.push(point[0] + (ma * too[0]));
  1270. if (too[1] == 0) // Y
  1271. p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi);
  1272. else p.push(point[1] + (ma * soo[1]));
  1273. }
  1274. return p;
  1275. };
  1276. this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth)
  1277. {
  1278. lineWidth = lineWidth || 0;
  1279. var w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth, h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth;
  1280. var canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2), canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2);
  1281. var sx = sourcePos[0] < targetPos[0] ? w - (lineWidth/2): (lineWidth/2), sy = sourcePos[1] < targetPos[1] ? h-(lineWidth/2) : (lineWidth/2);
  1282. var tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : w-(lineWidth/2), ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : h-(lineWidth/2);
  1283. var CP = self._findControlPoint([sx,sy], sourcePos, targetPos, sourceAnchor, targetAnchor);
  1284. var CP2 = self._findControlPoint([tx,ty], targetPos, sourcePos, targetAnchor, sourceAnchor);
  1285. var minx1 = Math.min(sx,tx); var minx2 = Math.min(CP[0], CP2[0]); var minx = Math.min(minx1,minx2);
  1286. var maxx1 = Math.max(sx,tx); var maxx2 = Math.max(CP[0], CP2[0]); var maxx = Math.max(maxx1,maxx2);
  1287. if (maxx > w) w = maxx;
  1288. if (minx < 0) {
  1289. canvasX += minx; var ox = Math.abs(minx);
  1290. w += ox; CP[0] += ox; sx += ox; tx +=ox; CP2[0] += ox;
  1291. }
  1292. var miny1 = Math.min(sy,ty); var miny2 = Math.min(CP[1], CP2[1]); var miny = Math.min(miny1,miny2);
  1293. var maxy1 = Math.max(sy,ty); var maxy2 = Math.max(CP[1], CP2[1]); var maxy = Math.max(maxy1,maxy2);
  1294. if (maxy > h) h = maxy;
  1295. if (miny < 0) {
  1296. canvasY += miny; var oy = Math.abs(miny);
  1297. h += oy; CP[1] += oy; sy += oy; ty +=oy; CP2[1] += oy;
  1298. }
  1299. // return [ canvasx, canvasy, canvasWidth, canvasHeight,
  1300. // sourceX, sourceY, targetX, targetY,
  1301. // controlPoint1_X, controlPoint1_Y, controlPoint2_X, controlPoint2_Y
  1302. return [canvasX, canvasY, w, h, sx, sy, tx, ty, CP[0], CP[1], CP2[0], CP2[1] ];
  1303. };
  1304. this.paint = function(d, ctx) {
  1305. /*var img = new Image();
  1306. img.src = "../img/pattern.jpg";
  1307. ctx.fillStyle = ctx.createPattern(img, 'repeat-y');*/
  1308. ctx.beginPath();
  1309. ctx.moveTo(d[4],d[5]);
  1310. ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]);
  1311. ctx.stroke();
  1312. //ctx.fill();
  1313. }
  1314. };
  1315. /**
  1316. * Types of endpoint UIs. we supply three - a circle of default radius 10px, a rectangle of
  1317. * default size 20x20, and an image (with no default). you can supply others of these if you want to - see the documentation
  1318. * for a howto.
  1319. */
  1320. /**
  1321. * a round endpoint, with default radius 10 pixels.
  1322. */
  1323. jsPlumb.Endpoints.Dot = function(params) {
  1324. params = params || { radius:10 };
  1325. var self = this;
  1326. this.radius = params.radius;
  1327. var defaultOffset = 0.5 * this.radius;
  1328. var defaultInnerRadius = this.radius / 3;
  1329. var parseValue = function(value) {
  1330. try {
  1331. return parseInt(value);
  1332. }
  1333. catch(e) {
  1334. if (value.substring(value.length - 1) == '%')
  1335. return parseInt(value.substring(0, value - 1));
  1336. }
  1337. }
  1338. var calculateAdjustments = function(gradient) {
  1339. var offsetAdjustment = defaultOffset;
  1340. var innerRadius = defaultInnerRadius;
  1341. if (gradient.offset) offsetAdjustment = parseValue(gradient.offset);
  1342. if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius);
  1343. return [offsetAdjustment, innerRadius];
  1344. };
  1345. this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {
  1346. var radius = endpointStyle.radius || self.radius;
  1347. var x = anchorPoint[0] - radius;
  1348. var y = anchorPoint[1] - radius;
  1349. jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2);
  1350. var ctx = canvas.getContext('2d');
  1351. var style = $.extend({}, endpointStyle);
  1352. if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle;
  1353. $.extend(ctx, style);
  1354. var ie = (/MSIE/.test(navigator.userAgent) && !window.opera);
  1355. if (endpointStyle.gradient && !ie) {
  1356. var adjustments = calculateAdjustments(endpointStyle.gradient);
  1357. var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0];
  1358. var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0];
  1359. var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]);
  1360. for (var i = 0; i < endpointStyle.gradient.stops.length; i++)
  1361. g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]);
  1362. ctx.fillStyle = g;
  1363. }
  1364. ctx.beginPath();
  1365. ctx.arc(radius, radius, radius, 0, Math.PI*2, true);
  1366. ctx.closePath();
  1367. ctx.fill();
  1368. };
  1369. };
  1370. /**
  1371. * A Rectangular endpoint, with default size 20x20.
  1372. */
  1373. jsPlumb.Endpoints.Rectangle = function(params) {
  1374. params = params || { width:20, height:20 };
  1375. var self = this;
  1376. this.width = params.width;
  1377. this.height = params.height;
  1378. this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {
  1379. var width = endpointStyle.width || self.width;
  1380. var height = endpointStyle.height || self.height;
  1381. var x = anchorPoint[0] - (width/2);
  1382. var y = anchorPoint[1] - (height/2);
  1383. jsPlumb.sizeCanvas(canvas, x, y, width, height);
  1384. var ctx = canvas.getContext('2d');
  1385. //todo: the fillStyle needs some thought. we want to support a few options:
  1386. // 1. nothing supplied; use the stroke color or the default if no stroke color.
  1387. // 2. a fill color supplied - use it
  1388. // 3. a gradient supplied - use it
  1389. // 4. setting the endpoint to the same color as the bg of the element it is attached to.
  1390. var style = $.extend({}, endpointStyle);
  1391. if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle;
  1392. $.extend(ctx, style);
  1393. var ie = (/MSIE/.test(navigator.userAgent) && !window.opera);
  1394. if (endpointStyle.gradient && !ie) {
  1395. // first figure out which direction to run the gradient in (it depends on the orientation of the anchors)
  1396. var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0;
  1397. var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0;
  1398. var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0;
  1399. var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0;
  1400. var g = ctx.createLinearGradient(x1,y1,x2,y2);
  1401. for (var i = 0; i < endpointStyle.gradient.stops.length; i++)
  1402. g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]);
  1403. ctx.fillStyle = g;
  1404. }
  1405. ctx.beginPath();
  1406. ctx.rect(0, 0, width, height);
  1407. ctx.closePath();
  1408. ctx.fill();
  1409. };
  1410. };
  1411. /**
  1412. * Image endpoint - draws an image as the endpoint. You must provide a 'url' property in the params object..
  1413. */
  1414. jsPlumb.Endpoints.Image = function(params) {
  1415. var self = this;
  1416. this.img = new Image();
  1417. var ready = false;
  1418. this.img.onload = function() {
  1419. self.ready = true;
  1420. };
  1421. this.img.src = params.url;
  1422. var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {
  1423. var width = self.img.width || endpointStyle.width;
  1424. var height = self.img.height || endpointStyle.height;
  1425. var x = anchorPoint[0] - (width/2);
  1426. var y = anchorPoint[1] - (height/2);
  1427. jsPlumb.sizeCanvas(canvas, x, y, width, height);
  1428. var ctx = canvas.getContext('2d');
  1429. ctx.drawImage(self.img,0,0);
  1430. };
  1431. this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {
  1432. if (self.ready) {
  1433. actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle)
  1434. }
  1435. else
  1436. window.setTimeout(function() {
  1437. self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle);
  1438. }, 200);
  1439. };
  1440. };
  1441. })();