jquery.jsPlumb-1.2.4-all.js 130 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575
  1. /*
  2. * jsPlumb 1.2.4
  3. *
  4. * Provides a way to visually connect elements on an HTML page.
  5. *
  6. * http://morrisonpitt.com/jsPlumb/demo.html
  7. * http://code.google.com/p/jsPlumb
  8. *
  9. * Dual licensed under MIT and GPL2.
  10. */
  11. (function() {
  12. var jsPlumbInstance = function() {
  13. /**
  14. * helper class for objects that generate events
  15. */
  16. var EventGenerator = function() {
  17. var _listeners = {};
  18. this.bind = function(event, listener) {
  19. var doOne = function(e, l) { _addToList(_listeners, e, l); };
  20. if (event.constructor == Array) {
  21. for ( var i = 0; i < event.length; i++)
  22. doOne(event[i], listener);
  23. } else
  24. doOne(event, listener);
  25. };
  26. this.fireUpdate = function(event, value) {
  27. if (_listeners[event]) {
  28. for ( var i = 0; i < _listeners[event].length; i++) {
  29. try {
  30. _listeners[event][i][event](value);
  31. } catch (e) {
  32. _log("jsPlumb: fireUpdate failed for event "
  33. + event + ";not fatal.");
  34. }
  35. }
  36. }
  37. };
  38. this.clearListeners = function() {
  39. delete _listeners
  40. _listeners = {};
  41. };
  42. };
  43. var _currentInstance = this;
  44. var ie = !!!document.createElement('canvas').getContext;
  45. var log = null;
  46. var repaintFunction = function() {
  47. jsPlumb.repaintEverything();
  48. };
  49. var automaticRepaint = true;
  50. function repaintEverything() {
  51. if (automaticRepaint)
  52. repaintFunction();
  53. };
  54. var resizeTimer = null;
  55. /**
  56. * map of element id -> endpoint lists. an element can have an arbitrary
  57. * number of endpoints on it, and not all of them have to be connected
  58. * to anything.
  59. */
  60. var endpointsByElement = {};
  61. var endpointsByUUID = {};
  62. var connectionsByScope = {};
  63. var offsets = {};
  64. var offsetTimestamps = {};
  65. var floatingConnections = {};
  66. var draggableStates = {};
  67. var _draggableByDefault = true;
  68. var sizes = [];
  69. var listeners = {}; // a map: keys are event types, values are lists of listeners.
  70. var DEFAULT_SCOPE = 'DEFAULT';
  71. 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.)
  72. var _findIndex = function(a, v, b, s) {
  73. var _eq = function(o1, o2) {
  74. if (o1 === o2)
  75. return true;
  76. else if (typeof o1 == 'object' && typeof o2 == 'object') {
  77. var same = true;
  78. for ( var propertyName in o1) {
  79. if (!_eq(o1[propertyName], o2[propertyName])) {
  80. same = false;
  81. break;
  82. }
  83. }
  84. for ( var propertyName in o2) {
  85. if (!_eq(o2[propertyName], o1[propertyName])) {
  86. same = false;
  87. break;
  88. }
  89. }
  90. return same;
  91. }
  92. };
  93. for ( var i = +b || 0, l = a.length; i < l; i++) {
  94. if (_eq(a[i], v))
  95. return i;
  96. }
  97. return -1;
  98. };
  99. /**
  100. * helper method to add an item to a list, creating the list if it does
  101. * not yet exist.
  102. */
  103. var _addToList = function(map, key, value) {
  104. var l = map[key];
  105. if (l == null) {
  106. l = [];
  107. map[key] = l;
  108. }
  109. l.push(value);
  110. return l;
  111. };
  112. /**
  113. * appends an element to the given parent, or the document body if no
  114. * parent given.
  115. */
  116. var _appendElement = function(canvas, parent) {
  117. if (!parent)
  118. document.body.appendChild(canvas);
  119. else
  120. jsPlumb.CurrentLibrary.appendElement(canvas, parent);
  121. };
  122. /**
  123. * creates a timestamp, using milliseconds since 1970, but as a string.
  124. */
  125. var _timestamp = function() {
  126. return "" + (new Date()).getTime();
  127. };
  128. /**
  129. * Draws an endpoint and its connections.
  130. *
  131. * @param element element to draw (of type library specific element object)
  132. * @param ui UI object from current library's event system. optional.
  133. * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do.
  134. */
  135. var _draw = function(element, ui, timestamp) {
  136. var id = _getAttribute(element, "id");
  137. var endpoints = endpointsByElement[id];
  138. if (!timestamp) timestamp = _timestamp();
  139. if (endpoints) {
  140. _updateOffset( { elId : id, offset : ui, recalc : false, timestamp : timestamp }); // timestamp is checked against last update cache; it is
  141. // valid for one paint cycle.
  142. var myOffset = offsets[id], myWH = sizes[id];
  143. for ( var i = 0; i < endpoints.length; i++) {
  144. endpoints[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH });
  145. var l = endpoints[i].connections;
  146. for ( var j = 0; j < l.length; j++) {
  147. l[j].paint( { elId : id, ui : ui, recalc : false, timestamp : timestamp }); // ...paint each connection.
  148. // then, check for dynamic endpoint; need to repaint it.
  149. var oIdx = l[j].endpoints[0] == endpoints[i] ? 1 : 0;
  150. if (l[j].endpoints[oIdx].anchor.isDynamic && !l[j].endpoints[oIdx].isFloating()) {
  151. var oId = oIdx == 0 ? l[j].sourceId : l[j].targetId;
  152. var oOffset = offsets[oId], oWH = sizes[oId];
  153. // TODO i still want to make this faster.
  154. var anchorLoc = l[j].endpoints[oIdx].anchor.compute( {
  155. xy : [ oOffset.left, oOffset.top ],
  156. wh : oWH,
  157. element : l[j].endpoints[oIdx],
  158. txy : [ myOffset.left, myOffset.top ],
  159. twh : myWH,
  160. tElement : endpoints[i]
  161. });
  162. l[j].endpoints[oIdx].paint({ anchorLoc : anchorLoc });
  163. }
  164. }
  165. }
  166. }
  167. };
  168. /**
  169. * executes the given function against the given element if the first
  170. * argument is an object, or the list of elements, if the first argument
  171. * is a list. the function passed in takes (element, elementId) as
  172. * arguments.
  173. */
  174. var _elementProxy = function(element, fn) {
  175. var retVal = null;
  176. if (element.constructor == Array) {
  177. retVal = [];
  178. for ( var i = 0; i < element.length; i++) {
  179. var el = _getElementObject(element[i]), id = _getAttribute(el, "id");
  180. retVal.push(fn(el, id)); // append return values to what we will return
  181. }
  182. } else {
  183. var el = _getElementObject(element), id = _getAttribute(el, "id");
  184. retVal = fn(el, id);
  185. }
  186. return retVal;
  187. };
  188. /**
  189. *
  190. */
  191. var _log = function(msg) {
  192. if (_currentInstance.logEnabled && typeof console != "undefined")
  193. console.log(msg);
  194. };
  195. /**
  196. * gets the named attribute from the given element (id or element
  197. * object)
  198. */
  199. var _getAttribute = function(el, attName) {
  200. return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName);
  201. };
  202. var _setAttribute = function(el, attName, attValue) {
  203. jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue);
  204. };
  205. var _addClass = function(el, clazz) {
  206. jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz);
  207. };
  208. var _removeClass = function(el, clazz) {
  209. jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz);
  210. };
  211. var _getElementObject = function(el) {
  212. return jsPlumb.CurrentLibrary.getElementObject(el);
  213. };
  214. var _getOffset = function(el) {
  215. return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el));
  216. };
  217. var _getSize = function(el) {
  218. return jsPlumb.CurrentLibrary.getSize(_getElementObject(el));
  219. };
  220. /**
  221. * gets an id for the given element, creating and setting one if
  222. * necessary.
  223. */
  224. var _getId = function(element, uuid) {
  225. var ele = _getElementObject(element);
  226. var id = _getAttribute(ele, "id");
  227. if (!id) {
  228. // check if fixed uuid parameter is given
  229. if (arguments.length == 2)
  230. id = uuid;
  231. else
  232. id = "_jsPlumb_" + _timestamp();
  233. _setAttribute(ele, "id", id);
  234. }
  235. return id;
  236. };
  237. /**
  238. * gets an Endpoint by uuid.
  239. */
  240. var _getEndpoint = function(uuid) {
  241. return endpointsByUUID[uuid];
  242. };
  243. /**
  244. * inits a draggable if it's not already initialised.
  245. */
  246. var _initDraggableIfNecessary = function(element, isDraggable, dragOptions) {
  247. var draggable = isDraggable == null ? _draggableByDefault : isDraggable;
  248. if (draggable) {
  249. if (jsPlumb.CurrentLibrary.isDragSupported(element)) {
  250. var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions;
  251. options = jsPlumb.extend( {}, options); // make a copy.
  252. var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag'];
  253. var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop'];
  254. options[dragEvent] = _wrap(options[dragEvent], function() {
  255. var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments);
  256. _draw(element, ui);
  257. _addClass(element, "jsPlumb_dragged");
  258. });
  259. options[stopEvent] = _wrap(options[stopEvent], function() {
  260. var ui = jsPlumb.CurrentLibrary.getUIPosition(arguments);
  261. _draw(element, ui);
  262. _removeClass(element, "jsPlumb_dragged");
  263. });
  264. var draggable = draggableStates[_getId(element)];
  265. options.disabled = draggable == null ? false : !draggable;
  266. jsPlumb.CurrentLibrary.initDraggable(element, options);
  267. }
  268. }
  269. };
  270. /**
  271. * helper to create a canvas.
  272. *
  273. * @param clazz
  274. * optional class name for the canvas.
  275. */
  276. var _newCanvas = function(clazz, parent, uuid) {
  277. var canvas = document.createElement("canvas");
  278. _appendElement(canvas, parent);
  279. canvas.style.position = "absolute";
  280. if (clazz) canvas.className = clazz;
  281. // set an id. if no id on the element and if uuid was supplied it
  282. // will be used, otherwise we'll create one.
  283. _getId(canvas, uuid);
  284. if (ie) {
  285. // for IE we have to set a big canvas size. actually you can
  286. // override this, too, if 1200 pixels
  287. // is not big enough for the biggest connector/endpoint canvas
  288. // you have at startup.
  289. jsPlumb.sizeCanvas(canvas, 0, 0, DEFAULT_NEW_CANVAS_SIZE, DEFAULT_NEW_CANVAS_SIZE);
  290. canvas = G_vmlCanvasManager.initElement(canvas);
  291. }
  292. return canvas;
  293. };
  294. /**
  295. * performs the given function operation on all the connections found
  296. * for the given element id; this means we find all the endpoints for
  297. * the given element, and then for each endpoint find the connectors
  298. * connected to it. then we pass each connection in to the given
  299. * function.
  300. */
  301. var _operation = function(elId, func) {
  302. var endpoints = endpointsByElement[elId];
  303. if (endpoints && endpoints.length) {
  304. for ( var i = 0; i < endpoints.length; i++) {
  305. for ( var j = 0; j < endpoints[i].connections.length; j++) {
  306. var retVal = func(endpoints[i].connections[j]);
  307. // if the function passed in returns true, we exit.
  308. // most functions return false.
  309. if (retVal) return;
  310. }
  311. }
  312. }
  313. };
  314. /**
  315. * perform an operation on all elements.
  316. */
  317. var _operationOnAll = function(func) {
  318. for ( var elId in endpointsByElement) {
  319. _operation(elId, func);
  320. }
  321. };
  322. /**
  323. * helper to remove an element from the DOM.
  324. */
  325. var _removeElement = function(element, parent) {
  326. if (element != null) {
  327. if (!parent) {
  328. try {
  329. document.body.removeChild(element);
  330. } catch (e) {
  331. }
  332. } else {
  333. jsPlumb.CurrentLibrary.removeElement(element, parent);
  334. }
  335. }
  336. };
  337. /**
  338. * helper to remove a list of elements from the DOM.
  339. */
  340. var _removeElements = function(elements, parent) {
  341. for ( var i = 0; i < elements.length; i++)
  342. _removeElement(elements[i], parent);
  343. };
  344. /**
  345. * helper method to remove an item from a list.
  346. */
  347. var _removeFromList = function(map, key, value) {
  348. var l = map[key];
  349. if (l != null) {
  350. var i = _findIndex(l, value);
  351. if (i >= 0) {
  352. delete (l[i]);
  353. l.splice(i, 1);
  354. return true;
  355. }
  356. }
  357. return false;
  358. };
  359. /**
  360. * Sets whether or not the given element(s) should be draggable,
  361. * regardless of what a particular plumb command may request.
  362. *
  363. * @param element
  364. * May be a string, a element objects, or a list of
  365. * strings/elements.
  366. * @param draggable
  367. * Whether or not the given element(s) should be draggable.
  368. */
  369. var _setDraggable = function(element, draggable) {
  370. return _elementProxy(element, function(el, id) {
  371. draggableStates[id] = draggable;
  372. if (jsPlumb.CurrentLibrary.isDragSupported(el)) {
  373. jsPlumb.CurrentLibrary.setDraggable(el, draggable);
  374. }
  375. });
  376. };
  377. /**
  378. * private method to do the business of hiding/showing.
  379. *
  380. * @param el
  381. * either Id of the element in question or a library specific
  382. * object for the element.
  383. * @param state
  384. * String specifying a value for the css 'display' property
  385. * ('block' or 'none').
  386. */
  387. var _setVisible = function(el, state) {
  388. _operation(_getAttribute(el, "id"), function(jpc) {
  389. jpc.canvas.style.display = state;
  390. });
  391. };
  392. /**
  393. * toggles the draggable state of the given element(s).
  394. *
  395. * @param el
  396. * either an id, or an element object, or a list of
  397. * ids/element objects.
  398. */
  399. var _toggleDraggable = function(el) {
  400. return _elementProxy(el, function(el, elId) {
  401. var state = draggableStates[elId] == null ? _draggableByDefault : draggableStates[elId];
  402. state = !state;
  403. draggableStates[elId] = state;
  404. jsPlumb.CurrentLibrary.setDraggable(el, state);
  405. return state;
  406. });
  407. };
  408. /**
  409. * private method to do the business of toggling hiding/showing.
  410. *
  411. * @param elId
  412. * Id of the element in question
  413. */
  414. var _toggleVisible = function(elId) {
  415. _operation(elId, function(jpc) {
  416. var state = ('none' == jpc.canvas.style.display);
  417. jpc.canvas.style.display = state ? "block" : "none";
  418. });
  419. // todo this should call _elementProxy, and pass in the
  420. // _operation(elId, f) call as a function. cos _toggleDraggable does
  421. // that.
  422. };
  423. /**
  424. * updates the offset and size for a given element, and stores the
  425. * values. if 'offset' is not null we use that (it would have been
  426. * passed in from a drag call) because it's faster; but if it is null,
  427. * or if 'recalc' is true in order to force a recalculation, we use the
  428. * offset, outerWidth and outerHeight methods to get the current values.
  429. */
  430. var _updateOffset = function(params) {
  431. var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId;
  432. if (!recalc) {
  433. if (timestamp && timestamp === offsetTimestamps[elId])
  434. return;
  435. }
  436. if (recalc || offset == null) { // if forced repaint or no offset
  437. // available, we recalculate.
  438. // get the current size and offset, and store them
  439. var s = _getElementObject(elId);
  440. sizes[elId] = _getSize(s);
  441. offsets[elId] = _getOffset(s);
  442. offsetTimestamps[elId] = timestamp;
  443. } else {
  444. offsets[elId] = offset;
  445. }
  446. };
  447. /**
  448. * wraps one function with another, creating a placeholder for the
  449. * wrapped function if it was null. this is used to wrap the various
  450. * drag/drop event functions - to allow jsPlumb to be notified of
  451. * important lifecycle events without imposing itself on the user's
  452. * drag/drop functionality. TODO: determine whether or not we should
  453. * support an error handler concept, if one of the functions fails.
  454. */
  455. var _wrap = function(wrappedFunction, newFunction) {
  456. wrappedFunction = wrappedFunction || function() { };
  457. newFunction = newFunction || function() { };
  458. return function() {
  459. var r = null;
  460. try {
  461. r = newFunction.apply(this, arguments);
  462. } catch (e) {
  463. _log('jsPlumb function failed : ' + e);
  464. }
  465. try {
  466. wrappedFunction.apply(this, arguments);
  467. } catch (e) {
  468. _log('wrapped function failed : ' + e);
  469. }
  470. return r;
  471. };
  472. };
  473. /*
  474. * Class: Anchor Models a position relative to the origin of some
  475. * element that an Endpoint can be located.
  476. */
  477. /*
  478. * Function: Anchor
  479. *
  480. * Constructor for the Anchor class
  481. *
  482. * Parameters:
  483. * - x : the x location of the anchor as a fraction of the total width. -
  484. * y : the y location of the anchor as a fraction of the total height. -
  485. * orientation : an [x,y] array indicating the general direction a
  486. * connection from the anchor should go in. for more info on this, see
  487. * the documentation, or the docs in jquery-jsPlumb-defaults-XXX.js for
  488. * the default Anchors. - offsets : an [x,y] array of fixed offsets that
  489. * should be applied after the x,y position has been figured out. may be
  490. * null.
  491. */
  492. var Anchor = function(params) {
  493. var self = this;
  494. this.x = params.x || 0;
  495. this.y = params.y || 0;
  496. var orientation = params.orientation || [ 0, 0 ];
  497. var lastTimestamp = null;
  498. var lastReturnValue = null;
  499. this.offsets = params.offsets || [ 0, 0 ];
  500. self.timestamp = null;
  501. this.compute = function(params) {
  502. var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp;
  503. if (timestamp && timestamp === self.timestamp) {
  504. return lastReturnValue;
  505. }
  506. lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ];
  507. var container = element ? element.container : null;
  508. var containerAdjustment = { left : 0, top : 0 };
  509. if (container != null) {
  510. var eo = _getElementObject(container);
  511. var o = _getOffset(eo);
  512. var sl = jsPlumb.CurrentLibrary.getScrollLeft(eo);
  513. var st = jsPlumb.CurrentLibrary.getScrollTop(eo);
  514. containerAdjustment.left = o.left - sl;
  515. containerAdjustment.top = o.top - st;
  516. lastReturnValue[0] = lastReturnValue[0] - containerAdjustment.left;
  517. lastReturnValue[1] = lastReturnValue[1] - containerAdjustment.top;
  518. }
  519. self.timestamp = timestamp;
  520. return lastReturnValue;
  521. };
  522. this.getOrientation = function() {
  523. return orientation;
  524. };
  525. this.equals = function(anchor) {
  526. if (!anchor) return false;
  527. var ao = anchor.getOrientation();
  528. var o = this.getOrientation();
  529. return this.x == anchor.x && this.y == anchor.y
  530. && this.offsets[0] == anchor.offsets[0]
  531. && this.offsets[1] == anchor.offsets[1]
  532. && o[0] == ao[0] && o[1] == ao[1];
  533. };
  534. this.getCurrentLocation = function() {
  535. return lastReturnValue;
  536. };
  537. };
  538. /**
  539. * An Anchor that floats. its orientation is computed dynamically from
  540. * its position relative to the anchor it is floating relative to.
  541. *
  542. */
  543. var FloatingAnchor = function(params) {
  544. // this is the anchor that this floating anchor is referenced to for
  545. // purposes of calculating the orientation.
  546. var ref = params.reference;
  547. // the canvas this refers to.
  548. var refCanvas = params.referenceCanvas;
  549. var size = _getSize(_getElementObject(refCanvas));
  550. // these are used to store the current relative position of our
  551. // anchor wrt the reference anchor. they only indicate
  552. // direction, so have a value of 1 or -1 (or, very rarely, 0). these
  553. // values are written by the compute method, and read
  554. // by the getOrientation method.
  555. var xDir = 0, yDir = 0;
  556. // temporary member used to store an orientation when the floating
  557. // anchor is hovering over another anchor.
  558. var orientation = null;
  559. var _lastResult = null;
  560. this.compute = function(params) {
  561. var xy = params.xy, el = params.element;
  562. var result = [ xy[0] + (size[0] / 2), xy[1] + (size[1] / 2) ]; // return origin of the element. we may wish to improve this so that any object can be the drag proxy.
  563. if (el.container != null) {
  564. var o = _getOffset(el.container);
  565. result[0] = result[0] - o.left;
  566. result[1] = result[1] - o.top;
  567. }
  568. _lastResult = result;
  569. return result;
  570. };
  571. this.getOrientation = function() {
  572. if (orientation) return orientation;
  573. else {
  574. var o = ref.getOrientation();
  575. // here we take into account the orientation of the other
  576. // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come
  577. // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense.
  578. return [ Math.abs(o[0]) * xDir * -1,
  579. Math.abs(o[1]) * yDir * -1 ];
  580. }
  581. };
  582. /**
  583. * notification the endpoint associated with this anchor is hovering
  584. * over another anchor; we want to assume that anchor's orientation
  585. * for the duration of the hover.
  586. */
  587. this.over = function(anchor) {
  588. orientation = anchor.getOrientation();
  589. };
  590. /**
  591. * notification the endpoint associated with this anchor is no
  592. * longer hovering over another anchor; we should resume calculating
  593. * orientation as we normally do.
  594. */
  595. this.out = function() {
  596. orientation = null;
  597. };
  598. this.getCurrentLocation = function() {
  599. return _lastResult;
  600. };
  601. };
  602. /*
  603. * Class: DynamicAnchor
  604. *
  605. * An Anchor that contains a list of other Anchors, which it cycles
  606. * through at compute time to find the one that is located closest to
  607. * the center of the target element, and returns that Anchor's compute
  608. * method result. this causes endpoints to follow each other with
  609. * respect to the orientation of their target elements - a useful
  610. * feature for some applications.
  611. *
  612. * there has been a request to make the anchor selection process
  613. * pluggable: closest centers does not work for everyone.
  614. */
  615. var DynamicAnchor = function(anchors) {
  616. this.isSelective = true;
  617. this.isDynamic = true;
  618. var _anchors = anchors || [];
  619. var _convert = function(anchor) {
  620. return anchor.constructor == Array ? jsPlumb.makeAnchor(anchor) : anchor;
  621. };
  622. for (var i = 0; i < _anchors.length; i++)
  623. _anchors[i] = _convert(_anchors[i]);
  624. this.addAnchor = function(anchor) {
  625. _anchors.push(_convert(anchor));
  626. };
  627. this.getAnchors = function() { return _anchors; };
  628. var _curAnchor = _anchors.length > 0 ? _anchors[0] : null;
  629. var _curIndex = _anchors.length > 0 ? 0 : -1;
  630. this.locked = false;
  631. var self = this;
  632. var _distance = function(anchor, cx, cy, xy, wh) {
  633. var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1]
  634. + (anchor.y * wh[1]);
  635. return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2));
  636. };
  637. this.compute = function(params) {
  638. var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp, txy = params.txy, twh = params.twh, tElement = params.tElement;
  639. // if anchor is locked or an opposite element was not given, we
  640. // maintain our state. anchor will be locked
  641. // if it is the source of a drag and drop.
  642. if (self.locked || txy == null || twh == null)
  643. return _curAnchor.compute(params);
  644. else
  645. params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute.
  646. var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2);
  647. var minIdx = -1, minDist = Infinity;
  648. for ( var i = 0; i < _anchors.length; i++) {
  649. var d = _distance(_anchors[i], cx, cy, xy, wh);
  650. if (d < minDist) {
  651. minIdx = i + 0;
  652. minDist = d;
  653. }
  654. }
  655. _curAnchor = _anchors[minIdx];
  656. var pos = _curAnchor.compute(params);
  657. return pos;
  658. };
  659. this.getCurrentLocation = function() {
  660. var cl = _curAnchor != null ? _curAnchor.getCurrentLocation() : null;
  661. return cl;
  662. };
  663. this.getOrientation = function() {
  664. return _curAnchor != null ? _curAnchor.getOrientation() : [ 0, 0 ];
  665. };
  666. this.over = function(anchor) {
  667. if (_curAnchor != null)
  668. _curAnchor.over(anchor);
  669. };
  670. this.out = function() {
  671. if (_curAnchor != null)
  672. _curAnchor.out();
  673. };
  674. };
  675. /*
  676. * Class:Connection
  677. *
  678. * The connecting line between two Endpoints.
  679. *
  680. */
  681. var Connection = function(params) {
  682. EventGenerator.apply(this);
  683. // ************** get the source and target and register the
  684. // connection. *******************
  685. var self = this;
  686. var id = new String('_jsplumb_c_' + (new Date()).getTime());
  687. this.getId = function() { return id; };
  688. this.container = params.container || _currentInstance.Defaults.Container; // may be null; we will append to the body if so.
  689. // get source and target as jQuery objects
  690. this.source = _getElementObject(params.source);
  691. this.target = _getElementObject(params.target);
  692. // sourceEndpoint and targetEndpoint override source/target, if they are present.
  693. if (params.sourceEndpoint) this.source = params.sourceEndpoint.getElement();
  694. if (params.targetEndpoint) this.target = params.targetEndpoint.getElement();
  695. this.sourceId = _getAttribute(this.source, "id");
  696. this.targetId = _getAttribute(this.target, "id");
  697. this.endpointsOnTop = params.endpointsOnTop != null ? params.endpointsOnTop : true;
  698. this.scope = params.scope; // scope may have been passed in to the connect call.
  699. // if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints. init endpoints
  700. this.endpoints = [];
  701. this.endpointStyles = [];
  702. // wrapped the main function to return null if no input given. this lets us cascade defaults properly.
  703. var _makeAnchor = function(anchorParams) {
  704. if (anchorParams)
  705. return jsPlumb.makeAnchor(anchorParams);
  706. };
  707. var prepareEndpoint = function(existing, index, params, element) {
  708. if (existing) {
  709. self.endpoints[index] = existing;
  710. existing.addConnection(self);
  711. } else {
  712. if (!params.endpoints) params.endpoints = [ null, null ];
  713. var ep = params.endpoints[index] || params.endpoint
  714. || _currentInstance.Defaults.Endpoints[index]
  715. || jsPlumb.Defaults.Endpoints[index]
  716. || _currentInstance.Defaults.Endpoint
  717. || jsPlumb.Defaults.Endpoint
  718. || new jsPlumb.Endpoints.Dot();
  719. if (ep.constructor == String) ep = new jsPlumb.Endpoints[ep]();
  720. if (!params.endpointStyles) params.endpointStyles = [ null, null ];
  721. var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle;
  722. var a = params.anchors ? params.anchors[index] : _makeAnchor(_currentInstance.Defaults.Anchors[index]) || _makeAnchor(jsPlumb.Defaults.Anchors[index]) || _makeAnchor(_currentInstance.Defaults.Anchor) || _makeAnchor(jsPlumb.Defaults.Anchor) || _makeAnchor("BottomCenter");
  723. var u = params.uuids ? params.uuids[index] : null;
  724. var e = new Endpoint( { style : es, endpoint : ep, connections : [ self ], uuid : u, anchor : a, source : element, container : self.container });
  725. self.endpoints[index] = e;
  726. return e;
  727. }
  728. };
  729. var eS = prepareEndpoint(params.sourceEndpoint, 0, params, self.source);
  730. if (eS) _addToList(endpointsByElement, this.sourceId, eS);
  731. var eT = prepareEndpoint(params.targetEndpoint, 1, params, self.target);
  732. if (eT) _addToList(endpointsByElement, this.targetId, eT);
  733. // if scope not set, set it to be the scope for the source endpoint.
  734. if (!this.scope) this.scope = this.endpoints[0].scope;
  735. this.connector = this.endpoints[0].connector || this.endpoints[1].connector || params.connector || _currentInstance.Defaults.Connector || jsPlumb.Defaults.Connector || new jsPlumb.Connectors.Bezier();
  736. if (this.connector.constructor == String) this.connector = new jsPlumb.Connectors[this.connector](); // lets you use a string as shorthand.
  737. this.paintStyle = this.endpoints[0].connectorStyle || this.endpoints[1].connectorStyle || params.paintStyle || _currentInstance.Defaults.PaintStyle || jsPlumb.Defaults.PaintStyle;
  738. this.backgroundPaintStyle = this.endpoints[0].connectorBackgroundStyle || this.endpoints[1].connectorBackgroundStyle || params.backgroundPaintStyle || _currentInstance.Defaults.BackgroundPaintStyle || jsPlumb.Defaults.BackgroundPaintStyle;
  739. // init overlays
  740. this.overlays = params.overlays || [];
  741. this.addOverlay = function(overlay) {
  742. overlays.push(overlay);
  743. };
  744. // this is a shortcut helper method to let people add a label as
  745. // overlay.
  746. this.labelStyle = params.labelStyle || _currentInstance.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle;
  747. this.label = params.label;
  748. if (this.label) {
  749. this.overlays.push(new jsPlumb.Overlays.Label( {
  750. labelStyle : this.labelStyle,
  751. label : this.label
  752. }));
  753. }
  754. _updateOffset( { elId : this.sourceId });
  755. _updateOffset( { elId : this.targetId });
  756. // functions for mouse hover/select functionality
  757. this.distanceFrom = function(point) {
  758. return self.connector.distanceFrom(point);
  759. };
  760. this.setLabel = function(l) {
  761. self.label = l;
  762. _currentInstance.repaint(self.source);
  763. };
  764. // paint the endpoints
  765. var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId];
  766. var otherOffset = offsets[this.targetId];
  767. otherWH = sizes[this.targetId];
  768. // todo: why not fold this and the paint call that follows into one
  769. // call?
  770. var anchorLoc = this.endpoints[0].anchor.compute( {
  771. xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0],
  772. txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1]
  773. });
  774. this.endpoints[0].paint( { anchorLoc : anchorLoc });
  775. anchorLoc = this.endpoints[1].anchor.compute( {
  776. xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1],
  777. txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0]
  778. });
  779. this.endpoints[1].paint({ anchorLoc : anchorLoc });
  780. var canvas = _newCanvas(jsPlumb.connectorClass, self.container);
  781. this.canvas = canvas;
  782. /*
  783. * Function: paint paints the connection. Parameters: - elId Id of
  784. * the element that is in motion - ui current library's event system
  785. * ui object (present if we came from a drag to get here) - recalc
  786. * whether or not to recalculate element sizes. this is true if a
  787. * repaint caused this to be painted.
  788. */
  789. this.paint = function(params) {
  790. params = params || {};
  791. var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp;
  792. var fai = self.floatingAnchorIndex;
  793. // if the moving object is not the source we must transpose the
  794. // two references.
  795. var swap = false;// !(elId == this.sourceId);
  796. var tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId;
  797. var tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0;
  798. var el = swap ? this.target : this.source;
  799. if (this.canvas.getContext) {
  800. _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp });
  801. _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved.
  802. var myOffset = offsets[sId], otherOffset = offsets[tId], myWH = sizes[sId], otherWH = sizes[tId];
  803. var ctx = canvas.getContext('2d');
  804. var sAnchorP = this.endpoints[sIdx].anchor.getCurrentLocation();
  805. var sAnchorO = this.endpoints[sIdx].anchor.getOrientation();
  806. var tAnchorP = this.endpoints[tIdx].anchor.getCurrentLocation();
  807. var tAnchorO = this.endpoints[tIdx].anchor.getOrientation();
  808. // paint overlays
  809. var maxSize = 0;
  810. for ( var i = 0; i < self.overlays.length; i++) {
  811. var o = self.overlays[i];
  812. var s = o.computeMaxSize(self.connector, ctx);
  813. if (s > maxSize)
  814. maxSize = s;
  815. }
  816. var dim = this.connector.compute(sAnchorP, tAnchorP, this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor, this.paintStyle.lineWidth, maxSize);
  817. jsPlumb.sizeCanvas(canvas, dim[0], dim[1], dim[2], dim[3]);
  818. var _paintOneStyle = function(ctx, paintStyle) {
  819. ctx.save();
  820. jsPlumb.extend(ctx, paintStyle);
  821. if (paintStyle.gradient && !ie) {
  822. var g = self.connector.createGradient(dim, ctx, (elId == this.sourceId));
  823. for ( var i = 0; i < paintStyle.gradient.stops.length; i++)
  824. g.addColorStop(paintStyle.gradient.stops[i][0], paintStyle.gradient.stops[i][1]);
  825. ctx.strokeStyle = g;
  826. }
  827. self.connector.paint(dim, ctx);
  828. ctx.restore();
  829. };
  830. // first check for the background style
  831. if (this.backgroundPaintStyle != null) {
  832. _paintOneStyle(ctx, this.backgroundPaintStyle);
  833. }
  834. _paintOneStyle(ctx, this.paintStyle);
  835. // paint overlays
  836. for ( var i = 0; i < self.overlays.length; i++) {
  837. var o = self.overlays[i];
  838. o.draw(self.connector, ctx);
  839. }
  840. }
  841. };
  842. // TODO: should this take a timestamp? probably. it reduces the
  843. // amount of time
  844. // spent figuring out anchor locations.
  845. this.repaint = function() {
  846. this.paint({ elId : this.sourceId, recalc : true });
  847. };
  848. _initDraggableIfNecessary(self.source, params.draggable, params.dragOptions);
  849. _initDraggableIfNecessary(self.target, params.draggable, params.dragOptions);
  850. // resizing (using the jquery.ba-resize plugin). todo: decide
  851. // whether to include or not.
  852. if (this.source.resize) {
  853. this.source.resize(function(e) {
  854. jsPlumb.repaint(self.sourceId);
  855. });
  856. }
  857. };
  858. /*
  859. * Class: Endpoint Models an endpoint. Can have one to N connections
  860. * emanating from it (although how to handle that in the UI is a very
  861. * good question). also has a Canvas and paint style.
  862. */
  863. /*
  864. * Function: Endpoint This is the Endpoint class constructor.
  865. * Parameters: anchor - anchor for the endpoint, of type jsPlumb.Anchor.
  866. * may be null. endpoint - endpoint object, of type jsPlumb.Endpoint.
  867. * may be null. style - endpoint style, a js object. may be null. source -
  868. * element the endpoint is attached to, of type jquery object. Required.
  869. * canvas - canvas element to use. may be, and most often is, null.
  870. * connections - optional list of connections to configure the endpoint
  871. * with. isSource - boolean. indicates the endpoint can act as a source
  872. * of new connections. optional. dragOptions - if isSource is set to
  873. * true, you can supply arguments for the jquery draggable method.
  874. * optional. connectionStyle - if isSource is set to true, this is the
  875. * paint style for connections from this endpoint. optional. connector -
  876. * optional connector type to use. isTarget - boolean. indicates the
  877. * endpoint can act as a target of new connections. optional.
  878. * dropOptions - if isTarget is set to true, you can supply arguments
  879. * for the jquery droppable method. optional. reattach - optional
  880. * boolean that determines whether or not the connections reattach after
  881. * they have been dragged off an endpoint and left floating. defaults to
  882. * false: connections dropped in this way will just be deleted.
  883. */
  884. var Endpoint = function(params) {
  885. params = params || {};
  886. var self = this;
  887. var id = new String('_jsplumb_e_' + (new Date()).getTime());
  888. this.getId = function() { return id; };
  889. self.anchor = params.anchor ? jsPlumb.makeAnchor(params.anchor) : jsPlumb.makeAnchor("TopCenter");
  890. var _endpoint = params.endpoint || new jsPlumb.Endpoints.Dot();
  891. if (_endpoint.constuctor == String) _endpoint = new jsPlumb.Endpoints[_endpoint]();
  892. self.endpoint = _endpoint;
  893. var _style = params.style || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle;
  894. this.connectorStyle = params.connectorStyle;
  895. this.connectorOverlays = params.connectorOverlays;
  896. this.connector = params.connector;
  897. this.isSource = params.isSource || false;
  898. this.isTarget = params.isTarget || false;
  899. var _element = params.source, _uuid = params.uuid;
  900. var floatingEndpoint = null, inPlaceCopy = null;
  901. if (_uuid) endpointsByUUID[_uuid] = self;
  902. this.container = params.container || _currentInstance.Defaults.Container || jsPlumb.Defaults.Container;
  903. var _elementId = _getAttribute(_element, "id");
  904. var _maxConnections = params.maxConnections || 1; // maximum number of connections this endpoint can be the source of.
  905. this.canvas = params.canvas || _newCanvas(jsPlumb.endpointClass, this.container, params.uuid);
  906. this.connections = params.connections || [];
  907. this.scope = params.scope || DEFAULT_SCOPE;
  908. this.timestamp = null;
  909. var _reattach = params.reattach || false;
  910. this.dragAllowedWhenFull = params.dragAllowedWhenFull || true;
  911. this.computeAnchor = function(params) {
  912. return self.anchor.compute(params);
  913. };
  914. this.addConnection = function(connection) {
  915. self.connections.push(connection);
  916. };
  917. this.detach = function(connection, ignoreTarget) {
  918. var idx = _findIndex(self.connections, connection);
  919. if (idx >= 0) {
  920. self.connections.splice(idx, 1);
  921. // this avoids a circular loop
  922. if (!ignoreTarget) {
  923. var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0];
  924. t.detach(connection, true);
  925. }
  926. }
  927. _removeElement(connection.canvas, connection.container);
  928. _removeFromList(connectionsByScope, connection.scope, connection);
  929. fireDetachEvent(connection);
  930. };
  931. /**
  932. * detaches all connections this Endpoint has
  933. */
  934. this.detachAll = function() {
  935. while (self.connections.length > 0) {
  936. self.detach(self.connections[0]);
  937. }
  938. };
  939. /**
  940. * removes any connections from this Endpoint that are connected to
  941. * the given target endpoint.
  942. *
  943. * @param targetEndpoint
  944. */
  945. this.detachFrom = function(targetEndpoint) {
  946. var c = [];
  947. for ( var i = 0; i < self.connections.length; i++) {
  948. if (self.connections[i].endpoints[1] == targetEndpoint
  949. || self.connections[i].endpoints[0] == targetEndpoint) {
  950. c.push(self.connections[i]);
  951. }
  952. }
  953. for ( var i = 0; i < c.length; i++) {
  954. self.detach(c[i]);
  955. }
  956. };
  957. /**
  958. * detach this Endpoint from the Connection, but leave the Connection alive. used when dragging.
  959. */
  960. this.detachFromConnection = function(connection) {
  961. var idx = _findIndex(self.connections, connection);
  962. if (idx >= 0) {
  963. self.connections.splice(idx, 1);
  964. }
  965. };
  966. /**
  967. * returns the DOM element this Endpoint is attached to.
  968. */
  969. this.getElement = function() {
  970. return _element;
  971. };
  972. /**
  973. * returns the UUID for this Endpoint, if there is one.
  974. */
  975. this.getUuid = function() {
  976. return _uuid;
  977. };
  978. /**
  979. * private but must be exposed.
  980. */
  981. this.makeInPlaceCopy = function() {
  982. var e = new Endpoint( { anchor : self.anchor, source : _element, style : _style, endpoint : _endpoint });
  983. return e;
  984. };
  985. /**
  986. * returns whether or not this endpoint is connected to the given
  987. * endpoint.
  988. *
  989. * @param endpoint
  990. * Endpoint to test.
  991. * @since 1.1.1
  992. *
  993. * todo: needs testing. will this work if the endpoint passed in is
  994. * the source?
  995. */
  996. this.isConnectedTo = function(endpoint) {
  997. var found = false;
  998. if (endpoint) {
  999. for ( var i = 0; i < self.connections.length; i++) {
  1000. if (self.connections[i].endpoints[1] == endpoint) {
  1001. found = true;
  1002. break;
  1003. }
  1004. }
  1005. }
  1006. return found;
  1007. };
  1008. /**
  1009. * private but needs to be exposed.
  1010. *
  1011. * @returns {Boolean}
  1012. */
  1013. this.isFloating = function() {
  1014. return floatingEndpoint != null;
  1015. };
  1016. /**
  1017. * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can.
  1018. */
  1019. var connectorSelector = function() {
  1020. return (self.connections.length < _maxConnections) ? null : self.connections[0];
  1021. };
  1022. /**
  1023. * @returns whether or not the Endpoint can accept any more
  1024. * Connections.
  1025. */
  1026. this.isFull = function() {
  1027. return _maxConnections < 1 ? false : (self.connections.length >= _maxConnections);
  1028. };
  1029. /**
  1030. * sets whether or not connnctions can be dragged from this Endpoint
  1031. * once it is full. you would use this in a UI in which you're going
  1032. * to provide some other way of breaking connections, if you need to
  1033. * break them at all. this property is by default true; use it in
  1034. * conjunction with the 'reattach' option on a connect call.
  1035. */
  1036. this.setDragAllowedWhenFull = function(allowed) {
  1037. self.dragAllowedWhenFull = allowed;
  1038. };
  1039. /**
  1040. * a deep equals check. everything must match, including the anchor,
  1041. * styles, everything. TODO: finish Endpoint.equals
  1042. */
  1043. this.equals = function(endpoint) {
  1044. return this.anchor.equals(endpoint.anchor);
  1045. };
  1046. /**
  1047. * paints the Endpoint, recalculating offset and anchor positions if
  1048. * necessary.
  1049. */
  1050. this.paint = function(params) {
  1051. params = params || {};
  1052. var timestamp = params.timestamp;
  1053. if (!timestamp || self.timestamp !== timestamp) {
  1054. var ap = params.anchorPoint, canvas = params.canvas, connectorPaintStyle = params.connectorPaintStyle;
  1055. if (ap == null) {
  1056. // do we always want to force a repaint here? i dont
  1057. // think so!
  1058. var xy = params.offset || offsets[_elementId];
  1059. var wh = params.dimensions || sizes[_elementId];
  1060. if (xy == null || wh == null) {
  1061. _updateOffset( { elId : _elementId, timestamp : timestamp });
  1062. xy = offsets[_elementId];
  1063. wh = sizes[_elementId];
  1064. }
  1065. var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp };
  1066. if (self.anchor.isDynamic) {
  1067. if (self.connections.length > 0) {
  1068. var c = self.connections[0];
  1069. var oIdx = c.endpoints[0] == self ? 1 : 0;
  1070. var oId = oIdx == 0 ? c.sourceId : c.targetId;
  1071. var oOffset = offsets[oId], oWH = sizes[oId];
  1072. anchorParams.txy = [ oOffset.left, oOffset.top ];
  1073. anchorParams.twh = oWH;
  1074. anchorParams.tElement = c.endpoints[oIdx];
  1075. }
  1076. }
  1077. ap = self.anchor.compute(anchorParams);
  1078. }
  1079. _endpoint.paint(ap, self.anchor.getOrientation(), canvas || self.canvas, _style, connectorPaintStyle || _style);
  1080. self.timestamp = timestamp;
  1081. }
  1082. };
  1083. /**
  1084. * @deprecated
  1085. */
  1086. this.removeConnection = this.detach; // backwards compatibility
  1087. // is this a connection source? we make it draggable and have the
  1088. // drag listener
  1089. // maintain a connection with a floating endpoint.
  1090. if (params.isSource && jsPlumb.CurrentLibrary.isDragSupported(_element)) {
  1091. var n = null, id = null, jpc = null, existingJpc = false, existingJpcParams = null;
  1092. var start = function() {
  1093. jpc = connectorSelector();
  1094. if (self.isFull() && !self.dragAllowedWhenFull) return false;
  1095. _updateOffset( { elId : _elementId });
  1096. inPlaceCopy = self.makeInPlaceCopy();
  1097. inPlaceCopy.paint();
  1098. n = document.createElement("div");
  1099. var nE = _getElementObject(n);
  1100. _appendElement(n, self.container); //
  1101. // create and assign an id, and initialize the offset.
  1102. var id = _getId(nE);
  1103. _updateOffset( { elId : id });
  1104. // store the id of the dragging div and the source element. the drop function will pick these up.
  1105. _setAttribute(_getElementObject(self.canvas), "dragId", id);
  1106. _setAttribute(_getElementObject(self.canvas), "elId", _elementId);
  1107. // create a floating anchor
  1108. var floatingAnchor = new FloatingAnchor( { reference : self.anchor, referenceCanvas : self.canvas });
  1109. floatingEndpoint = new Endpoint({ style : { fillStyle : 'rgba(0,0,0,0)' }, endpoint : _endpoint, anchor : floatingAnchor, source : nE });
  1110. if (jpc == null) {
  1111. self.anchor.locked = true;
  1112. // create a connection. one end is this endpoint, the
  1113. // other is a floating endpoint.
  1114. jpc = new Connection( {
  1115. sourceEndpoint : self,
  1116. targetEndpoint : floatingEndpoint,
  1117. source : _getElementObject(_element),
  1118. target : _getElementObject(n),
  1119. anchors : [ self.anchor, floatingAnchor ],
  1120. paintStyle : params.connectorStyle, // this can be null. Connection will use the default.
  1121. connector : params.connector, // this can also be null. Connection will use the default.
  1122. overlays : self.connectorOverlays // new in 1.2.4.
  1123. });
  1124. } else {
  1125. existingJpc = true;
  1126. var anchorIdx = jpc.sourceId == _elementId ? 0 : 1;
  1127. jpc.floatingAnchorIndex = anchorIdx;
  1128. // detach from the connection while dragging is occurring.
  1129. self.detachFromConnection(jpc);
  1130. if (anchorIdx == 0) {
  1131. existingJpcParams = [ jpc.source, jpc.sourceId ];
  1132. jpc.source = _getElementObject(n);
  1133. jpc.sourceId = id;
  1134. } else {
  1135. existingJpcParams = [ jpc.target, jpc.targetId ];
  1136. jpc.target = _getElementObject(n);
  1137. jpc.targetId = id;
  1138. }
  1139. // lock the other endpoint; if it is dynamic it will not
  1140. // move while the drag is occurring.
  1141. jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true;
  1142. // store the original endpoint and assign the new
  1143. // floating endpoint for the drag.
  1144. jpc.suspendedEndpoint = jpc.endpoints[anchorIdx];
  1145. jpc.endpoints[anchorIdx] = floatingEndpoint;
  1146. }
  1147. // register it.
  1148. floatingConnections[id] = jpc;
  1149. // TODO unregister on stop? or will floating endpoint's
  1150. // destruction be assured.
  1151. floatingEndpoint.addConnection(jpc);
  1152. // only register for the target endpoint; we will not be
  1153. // dragging the source at any time
  1154. // before this connection is either discarded or made into a
  1155. // permanent connection.
  1156. _addToList(endpointsByElement, id, floatingEndpoint);
  1157. };
  1158. var dragOptions = params.dragOptions || {};
  1159. var defaultOpts = jsPlumb.extend( {}, jsPlumb.CurrentLibrary.defaultDragOptions);
  1160. dragOptions = jsPlumb.extend(defaultOpts, dragOptions);
  1161. dragOptions.scope = dragOptions.scope || self.scope;
  1162. var startEvent = jsPlumb.CurrentLibrary.dragEvents['start'];
  1163. var stopEvent = jsPlumb.CurrentLibrary.dragEvents['stop'];
  1164. var dragEvent = jsPlumb.CurrentLibrary.dragEvents['drag'];
  1165. dragOptions[startEvent] = _wrap(dragOptions[startEvent], start);
  1166. dragOptions[dragEvent] = _wrap(dragOptions[dragEvent],
  1167. function() {
  1168. var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments);
  1169. jsPlumb.CurrentLibrary.setOffset(n, _ui);
  1170. _draw(_getElementObject(n), _ui);
  1171. });
  1172. dragOptions[stopEvent] = _wrap(dragOptions[stopEvent],
  1173. function() {
  1174. _removeFromList(endpointsByElement, id, floatingEndpoint);
  1175. _removeElements( [ n, floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted)
  1176. _removeElement(inPlaceCopy.canvas, _element);
  1177. var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
  1178. jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false;
  1179. if (jpc.endpoints[idx] == floatingEndpoint) {
  1180. // if the connection was an existing one:
  1181. if (existingJpc && jpc.suspendedEndpoint) {
  1182. // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the
  1183. // floating endpoint has been replaced.
  1184. if (idx == 0) {
  1185. jpc.source = existingJpcParams[0];
  1186. jpc.sourceId = existingJpcParams[1];
  1187. } else {
  1188. jpc.target = existingJpcParams[0];
  1189. jpc.targetId = existingJpcParams[1];
  1190. }
  1191. jpc.endpoints[idx] = jpc.suspendedEndpoint;
  1192. if (_reattach) {
  1193. jpc.floatingAnchorIndex = null;
  1194. jpc.suspendedEndpoint.addConnection(jpc);
  1195. jsPlumb.repaint(existingJpcParams[1]);
  1196. } else {
  1197. jpc.endpoints[1].detach(jpc);
  1198. jpc.endpoints[0].detach(jpc);
  1199. // TODO - delete floating endpoint?
  1200. }
  1201. } else {
  1202. // TODO this looks suspiciously kind of like an Endpoint.detach call too.
  1203. // i wonder if this one should post an event though. maybe this is good like this.
  1204. _removeElement(jpc.canvas, self.container);
  1205. self.detachFromConnection(jpc);
  1206. }
  1207. }
  1208. self.anchor.locked = false;
  1209. self.paint();
  1210. jpc.repaint();
  1211. jpc = null;
  1212. delete floatingEndpoint;
  1213. delete inPlaceCopy;
  1214. });
  1215. var i = _getElementObject(self.canvas);
  1216. jsPlumb.CurrentLibrary.initDraggable(i, dragOptions);
  1217. }
  1218. // connector target
  1219. if (params.isTarget && jsPlumb.CurrentLibrary.isDropSupported(_element)) {
  1220. var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions;
  1221. dropOptions = jsPlumb.extend( {}, dropOptions);
  1222. dropOptions.scope = dropOptions.scope || self.scope;
  1223. var originalAnchor = null;
  1224. var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'];
  1225. var overEvent = jsPlumb.CurrentLibrary.dragEvents['over'];
  1226. var outEvent = jsPlumb.CurrentLibrary.dragEvents['out'];
  1227. dropOptions[dropEvent] = _wrap(dropOptions[dropEvent],
  1228. function() {
  1229. var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments);
  1230. var id = _getAttribute(_getElementObject(draggable), "dragId");
  1231. var elId = _getAttribute(_getElementObject(draggable), "elId");
  1232. var jpc = floatingConnections[id];
  1233. var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
  1234. var oidx = idx == 0 ? 1 : 0;
  1235. if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget)) {
  1236. if (idx == 0) {
  1237. jpc.source = _element;
  1238. jpc.sourceId = _elementId;
  1239. } else {
  1240. jpc.target = _element;
  1241. jpc.targetId = _elementId;
  1242. }
  1243. // todo test that the target is not full.
  1244. // remove this jpc from the current endpoint
  1245. jpc.endpoints[idx].detachFromConnection(jpc);
  1246. if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc);
  1247. jpc.endpoints[idx] = self;
  1248. self.addConnection(jpc);
  1249. _addToList(connectionsByScope, jpc.scope, jpc);
  1250. _initDraggableIfNecessary(_element, params.draggable, {});
  1251. jsPlumb.repaint(elId);
  1252. _currentInstance.fireUpdate("jsPlumbConnection", {
  1253. source : jpc.source, target : jpc.target,
  1254. sourceId : jpc.sourceId, targetId : jpc.targetId,
  1255. sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1]
  1256. });
  1257. }
  1258. // else there must be some cleanup required? or not?
  1259. delete floatingConnections[id];
  1260. });
  1261. dropOptions[overEvent] = _wrap(dropOptions[overEvent],
  1262. function() {
  1263. var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments);
  1264. var id = _getAttribute( _getElementObject(draggable), "dragId");
  1265. var jpc = floatingConnections[id];
  1266. var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
  1267. jpc.endpoints[idx].anchor.over(self.anchor);
  1268. });
  1269. dropOptions[outEvent] = _wrap(dropOptions[outEvent],
  1270. function() {
  1271. var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments);
  1272. var id = _getAttribute(_getElementObject(draggable), "dragId");
  1273. var jpc = floatingConnections[id];
  1274. var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
  1275. jpc.endpoints[idx].anchor.out();
  1276. });
  1277. jsPlumb.CurrentLibrary.initDroppable(_getElementObject(self.canvas), dropOptions);
  1278. }
  1279. return self;
  1280. };
  1281. /*
  1282. * Property: Defaults These are the default settings for jsPlumb, that
  1283. * is what will be used if you do not supply specific pieces of
  1284. * information to the various API calls. A convenient way to implement
  1285. * your own look and feel can be to override these defaults by including
  1286. * a script somewhere after the jsPlumb include, but before you make any
  1287. * calls to jsPlumb, for instance in this example we set the PaintStyle
  1288. * to be a blue line of 27 pixels: > jsPlumb.Defaults.PaintStyle = {
  1289. * lineWidth:27, strokeStyle:'blue' }
  1290. */
  1291. this.Defaults = {
  1292. Anchor : null,
  1293. Anchors : [ null, null ],
  1294. BackgroundPaintStyle : null,
  1295. Connector : null,
  1296. Container : null,
  1297. DragOptions : {},
  1298. DropOptions : {},
  1299. Endpoint : null,
  1300. Endpoints : [ null, null ],
  1301. EndpointStyle : {
  1302. fillStyle : null
  1303. },
  1304. EndpointStyles : [ null, null ],
  1305. LabelStyle : {
  1306. fillStyle : "rgba(0,0,0,0)",
  1307. color : "black"
  1308. },
  1309. LogEnabled : true,
  1310. MaxConnections : null,
  1311. // TODO: should we have OverlayStyle too?
  1312. PaintStyle : {
  1313. lineWidth : 10,
  1314. strokeStyle : 'red'
  1315. },
  1316. Scope : "_jsPlumb_DefaultScope"
  1317. };
  1318. this.logEnabled = this.Defaults.LogEnabled;
  1319. /*
  1320. * Property: connectorClass The CSS class to set on Connection canvas
  1321. * elements. This value is a String and can have multiple classes; the
  1322. * entire String is appended as-is.
  1323. */
  1324. this.connectorClass = '_jsPlumb_connector';
  1325. /*
  1326. * Property: endpointClass The CSS class to set on Endpoint canvas
  1327. * elements. This value is a String and can have multiple classes; the
  1328. * entire String is appended as-is.
  1329. */
  1330. this.endpointClass = '_jsPlumb_endpoint';
  1331. /*
  1332. * Property: overlayClass The CSS class to set on an Overlay that is an
  1333. * HTML element. This value is a String and can have multiple classes;
  1334. * the entire String is appended as-is.
  1335. */
  1336. this.overlayClass = '_jsPlumb_overlay';
  1337. /*
  1338. * Property: Anchors Default jsPlumb Anchors. These are supplied in the
  1339. * file jsPlumb-defaults-x.x.x.js, which is merged in with the main
  1340. * jsPlumb script to form <library>.jsPlumb-all-x.x.x.js. You can
  1341. * provide your own Anchors by supplying them in a script that is loaded
  1342. * after jsPlumb, for instance: > jsPlumb.Anchors.MyAnchor = {
  1343. * ....anchor code here. see the documentation. }
  1344. */
  1345. this.Anchors = {};
  1346. /*
  1347. * Property: Connectors Default jsPlumb Connectors. These are supplied
  1348. * in the file jsPlumb-defaults-x.x.x.js, which is merged in with the
  1349. * main jsPlumb script to form <library>.jsPlumb-all-x.x.x.js. You can
  1350. * provide your own Connectors by supplying them in a script that is
  1351. * loaded after jsPlumb, for instance: > jsPlumb.Connectors.MyConnector = {
  1352. * ....connector code here. see the documentation. }
  1353. */
  1354. this.Connectors = {};
  1355. /*
  1356. * Property: Endpoints Default jsPlumb Endpoints. These are supplied in
  1357. * the file jsPlumb-defaults-x.x.x.js, which is merged in with the main
  1358. * jsPlumb script to form <library>.jsPlumb-all-x.x.x.js. You can
  1359. * provide your own Endpoints by supplying them in a script that is
  1360. * loaded after jsPlumb, for instance: > jsPlumb.Endpoints.MyEndpoint = {
  1361. * ....endpoint code here. see the documentation. }
  1362. */
  1363. this.Endpoints = {};
  1364. /*
  1365. * Property:Overlays Default jsPlumb Overlays such as Arrows and Labels.
  1366. * These are supplied in the file jsPlumb-defaults-x.x.x.js, which is
  1367. * merged in with the main jsPlumb script to form
  1368. * <library>.jsPlumb-all-x.x.x.js. You can provide your own Overlays by
  1369. * supplying them in a script that is loaded after jsPlumb, for
  1370. * instance: > jsPlumb.Overlays.MyOverlay = { ....overlay code here. see
  1371. * the documentation. }
  1372. */
  1373. this.Overlays = {};
  1374. /*
  1375. * Function: addEndpoint Adds an Endpoint to a given element.
  1376. * Parameters: target - Element to add the endpoint to. either an
  1377. * element id, or a selector representing some element. params - Object
  1378. * containing Endpoint options (more info required) Returns: The newly
  1379. * created Endpoint. See Also: <addEndpoints>
  1380. */
  1381. this.addEndpoint = function(target, params) {
  1382. params = jsPlumb.extend( {}, params);
  1383. params.endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint;
  1384. params.endpointStyle = params.endpointStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle;
  1385. var el = _getElementObject(target), id = _getAttribute(el, "id");
  1386. params.source = el;
  1387. _updateOffset({ elId : id });
  1388. var e = new Endpoint(params);
  1389. _addToList(endpointsByElement, id, e);
  1390. var myOffset = offsets[id], myWH = sizes[id];
  1391. var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e });
  1392. e.paint({ anchorLoc : anchorLoc });
  1393. return e;
  1394. };
  1395. /*
  1396. * Function: addEndpoint Adds a list of Endpoints to a given element.
  1397. * Parameters: target - element to add the endpoint to. either an
  1398. * element id, or a selector representing some element. endpoints - List
  1399. * of objects containing Endpoint options. one Endpoint is created for
  1400. * each entry in this list. Returns: List of newly created Endpoints,
  1401. * one for each entry in the 'endpoints' argument. See Also:
  1402. * <addEndpoint>
  1403. */
  1404. this.addEndpoints = function(target, endpoints) {
  1405. var results = [];
  1406. for ( var i = 0; i < endpoints.length; i++) {
  1407. results.push(_currentInstance.addEndpoint(target, endpoints[i]));
  1408. }
  1409. return results;
  1410. };
  1411. /*
  1412. * Function: animate Wrapper around supporting library's animate
  1413. * function; injects a call to jsPlumb in the 'step' function (creating
  1414. * it if necessary). This only supports the two-arg version of the
  1415. * animate call in jQuery - the one that takes an 'options' object as
  1416. * the second arg. MooTools has only one method - a two arg one. Which
  1417. * is handy. Parameters: el - Element to animate. Either an id, or a
  1418. * selector representing the element. properties - The 'properties'
  1419. * argument you want passed to the standard jQuery animate call. options -
  1420. * The 'options' argument you want passed to the standard jQuery animate
  1421. * call. Returns: void
  1422. */
  1423. this.animate = function(el, properties, options) {
  1424. var ele = _getElementObject(el), id = _getAttribute(el, "id");
  1425. options = options || {};
  1426. var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step'];
  1427. var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete'];
  1428. options[stepFunction] = _wrap(options[stepFunction], function() {
  1429. _currentInstance.repaint(id);
  1430. });
  1431. // you know, probably onComplete should repaint too. that will help
  1432. // keep up
  1433. // with fast animations.
  1434. options[completeFunction] = _wrap(options[completeFunction],
  1435. function() {
  1436. _currentInstance.repaint(id);
  1437. });
  1438. jsPlumb.CurrentLibrary.animate(ele, properties, options);
  1439. };
  1440. this.autoConnect = function(params) {
  1441. var sources = [], targets = [], _endpoint = params.endpoint || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle, _anchors = anchors || jsPlumb.Defaults.DynamicAnchors();
  1442. var _addAll = function(s, t) {
  1443. for ( var i = 0; i < s.length; i++)
  1444. t.push(s[i]);
  1445. };
  1446. var source = params.source, target = params.target, anchors = params.anchors;
  1447. if (typeof source == 'string')
  1448. sources.push(_getElementObject(source));
  1449. else
  1450. _addAll(source, sources);
  1451. if (typeof target == 'string')
  1452. targets.push(_getElementObject(target));
  1453. else
  1454. _addAll(target, targets);
  1455. var connectOptions = jsPlumb.extend(params, { source : null, target : null, anchors : null });
  1456. for ( var i = 0; i < sources.length; i++) {
  1457. for ( var j = 0; j < targets.length; j++) {
  1458. var e1 = jsPlumb.addEndpoint(sources[i], jsPlumb.extend( { anchor : jsPlumb.makeDynamicAnchor(_anchors) }, _endpoint));
  1459. var e2 = jsPlumb.addEndpoint(targets[j], jsPlumb.extend( { anchor : jsPlumb.makeDynamicAnchor(_anchors) }, _endpoint));
  1460. _currentInstance.connect(jsPlumb.extend(connectOptions, { sourceEndpoint : e1, targetEndpoint : e2 }));
  1461. }
  1462. }
  1463. };
  1464. /*
  1465. * Function: connect Establishes a connection between two elements.
  1466. * Parameters: params - Object containing setup for the connection. see
  1467. * documentation. Returns: The newly created Connection.
  1468. */
  1469. this.connect = function(params) {
  1470. var _p = jsPlumb.extend( {}, params);
  1471. // test for endpoint uuids to connect
  1472. if (params.uuids) {
  1473. _p.sourceEndpoint = _getEndpoint(params.uuids[0]);
  1474. _p.targetEndpoint = _getEndpoint(params.uuids[1]);
  1475. }
  1476. // now ensure that if we do have Endpoints already, they're not
  1477. // full.
  1478. if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) {
  1479. _log("could not add connection; source endpoint is full");
  1480. return;
  1481. }
  1482. if (_p.targetEndpoint && _p.targetEndpoint.isFull()) {
  1483. _log("could not add connection; target endpoint is full");
  1484. return;
  1485. }
  1486. var jpc = new Connection(_p);
  1487. // add to list of connections (by scope).
  1488. _addToList(connectionsByScope, jpc.scope, jpc);
  1489. // fire an event
  1490. _currentInstance.fireUpdate("jsPlumbConnection", {
  1491. source : jpc.source, target : jpc.target,
  1492. sourceId : jpc.sourceId, targetId : jpc.targetId,
  1493. sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1]
  1494. });
  1495. // force a paint
  1496. _draw(jpc.source);
  1497. return jpc;
  1498. };
  1499. /*
  1500. Function: deleteEndpoint
  1501. deletes an endpoint and removes all connections it has (which removes the connections from the other endpoints involved too)
  1502. You can call this with either a string uuid, or an Endpoint
  1503. */
  1504. this.deleteEndpoint = function(object) {
  1505. var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object;
  1506. if (endpoint) {
  1507. var uuid = endpoint.getUuid();
  1508. if (uuid) endpointsByUUID[uuid] = null;
  1509. endpoint.detachAll();
  1510. _removeElement(endpoint.canvas, endpoint.container);
  1511. // remove from endpointsbyElement
  1512. for (var e in endpointsByElement) {
  1513. var endpoints = endpointsByElement[e];
  1514. if (endpoints) {
  1515. var newEndpoints = [];
  1516. for (var i = 0; i < endpoints.length; i++)
  1517. if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]);
  1518. endpointsByElement[e] = newEndpoints;
  1519. }
  1520. }
  1521. delete endpoint;
  1522. }
  1523. };
  1524. /*
  1525. * Function: deleteEveryEndpoint
  1526. *
  1527. * Deletes every Endpoint in this instance
  1528. * of jsPlumb. Use this instead of removeEveryEndpoint now.
  1529. *
  1530. * Returns: void
  1531. */
  1532. this.deleteEveryEndpoint = function() {
  1533. for ( var id in endpointsByElement) {
  1534. var endpoints = endpointsByElement[id];
  1535. if (endpoints && endpoints.length) {
  1536. for ( var i = 0; i < endpoints.length; i++) {
  1537. _currentInstance.deleteEndpoint(endpoints[i]);
  1538. }
  1539. }
  1540. }
  1541. delete endpointsByElement;
  1542. endpointsByElement = {};
  1543. delete endpointsByUUID;
  1544. endpointsByUUID = {};
  1545. };
  1546. var fireDetachEvent = function(jpc) {
  1547. _currentInstance.fireUpdate("jsPlumbConnectionDetached", {
  1548. source : jpc.source, target : jpc.target,
  1549. sourceId : jpc.sourceId, targetId : jpc.targetId,
  1550. sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1]
  1551. });
  1552. };
  1553. /*
  1554. * Function:
  1555. * detach Removes a connection. takes either (source, target) (the old way, maintained for backwards compatibility), or a params
  1556. * object with various possible values.
  1557. *
  1558. *
  1559. * Parameters:
  1560. * source - id or element object of the first element in the connection.
  1561. * target - id or element object of the second element in the connection.
  1562. *
  1563. * OR
  1564. *
  1565. * params:
  1566. * {
  1567. *
  1568. * Returns: true if successful, false if not.
  1569. */
  1570. this.detach = function(source, target) {
  1571. if (arguments.length == 2) {
  1572. var s = _getElementObject(source), sId = _getId(s);
  1573. var t = _getElementObject(target), tId = _getId(t);
  1574. _operation(sId, function(jpc) {
  1575. if ((jpc.sourceId == sId && jpc.targetId == tId) || (jpc.targetId == sId && jpc.sourceId == tId)) {
  1576. _removeElement(jpc.canvas, jpc.container);
  1577. jpc.endpoints[0].removeConnection(jpc);
  1578. jpc.endpoints[1].removeConnection(jpc);
  1579. _removeFromList(connectionsByScope, jpc.scope, jpc);
  1580. fireDetachEvent(jpc);
  1581. }
  1582. });
  1583. }
  1584. // this is the new version of the method, taking a JS object like
  1585. // the connect method does.
  1586. else if (arguments.length == 1) {
  1587. if (arguments[0].constructor == Connection) {
  1588. arguments[0].endpoints[0].detachFrom(arguments[0].endpoints[1]);
  1589. fireDetachEvent(arguments[0]);
  1590. }
  1591. else if (arguments[0].connection) {
  1592. arguments[0].connection.endpoints[0].detachFrom(arguments[0].connection.endpoints[1]);
  1593. fireDetachEvent(arguments[0].connection);
  1594. }
  1595. else {
  1596. var _p = jsPlumb.extend( {}, source); // a backwards compatibility hack: source should be thought of as 'params' in this case.
  1597. // test for endpoint uuids to detach
  1598. if (_p.uuids) {
  1599. _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]));
  1600. } else if (_p.sourceEndpoint && _p.targetEndpoint) {
  1601. _p.sourceEndpoint.detachFrom(_p.targetEndpoint);
  1602. } else {
  1603. sourceId = _getId(_p.source);
  1604. targetId = _getId(_p.target);
  1605. _operation(sourceId, function(jpc) {
  1606. if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) {
  1607. _removeElement(jpc.canvas, jpc.container);
  1608. jpc.endpoints[0].removeConnection(jpc);
  1609. jpc.endpoints[1].removeConnection(jpc);
  1610. _removeFromList(connectionsByScope, jpc.scope, jpc);
  1611. fireDetachEvent(jpc);
  1612. }
  1613. });
  1614. }
  1615. }
  1616. }
  1617. };
  1618. /*
  1619. * Function: detachAll Removes all an element's connections. Parameters:
  1620. * el - either the id of the element, or a selector for the element.
  1621. * Returns: void
  1622. */
  1623. this.detachAllConnections = function(el) {
  1624. var id = _getAttribute(el, "id");
  1625. var endpoints = endpointsByElement[id];
  1626. if (endpoints && endpoints.length) {
  1627. for ( var i = 0; i < endpoints.length; i++) {
  1628. var c = endpoints[i].connections.length;
  1629. if (c > 0) {
  1630. for ( var j = 0; j < c; j++) {
  1631. var jpc = endpoints[i].connections[0];
  1632. _removeElement(jpc.canvas, jpc.container);
  1633. jpc.endpoints[0].removeConnection(jpc);
  1634. jpc.endpoints[1].removeConnection(jpc);
  1635. _removeFromList(connectionsByScope, jpc.scope, jpc);
  1636. fireDetachEvent(jpc);
  1637. }
  1638. }
  1639. }
  1640. }
  1641. };
  1642. /**
  1643. * @deprecated Use detachAllConnections instead. this will be removed in jsPlumb 1.3.
  1644. */
  1645. this.detachAll = this.detachAllConnections;
  1646. /*
  1647. * Function: detachEverything Remove all Connections from all elements,
  1648. * but leaves Endpoints in place. Returns: void See Also:
  1649. * <removeEveryEndpoint>
  1650. */
  1651. this.detachEveryConnection = function() {
  1652. for ( var id in endpointsByElement) {
  1653. var endpoints = endpointsByElement[id];
  1654. if (endpoints && endpoints.length) {
  1655. for ( var i = 0; i < endpoints.length; i++) {
  1656. var c = endpoints[i].connections.length;
  1657. if (c > 0) {
  1658. for ( var j = 0; j < c; j++) {
  1659. var jpc = endpoints[i].connections[0];
  1660. _removeElement(jpc.canvas, jpc.container);
  1661. jpc.endpoints[0].removeConnection(jpc);
  1662. jpc.endpoints[1].removeConnection(jpc);
  1663. fireDetachEvent(jpc);
  1664. }
  1665. }
  1666. }
  1667. }
  1668. }
  1669. delete connectionsByScope;
  1670. connectionsByScope = {};
  1671. };
  1672. /**
  1673. * @deprecated use detachEveryConnection instead. this will be removed in jsPlumb 1.3.
  1674. */
  1675. this.detachEverything = this.detachEveryConnection;
  1676. /*
  1677. * Function: draggable initialises the draggability of some element or
  1678. * elements. Parameters: el - either an element id, a list of element
  1679. * ids, or a selector. options - options to pass through to the
  1680. * underlying library Returns: void
  1681. */
  1682. this.draggable = function(el, options) {
  1683. if (typeof el == 'object' && el.length) {
  1684. for ( var i = 0; i < el.length; i++) {
  1685. var ele = _getElementObject(el[i]);
  1686. if (ele) _initDraggableIfNecessary(ele, true, options);
  1687. }
  1688. } else {
  1689. var ele = _getElementObject(el);
  1690. if (ele) _initDraggableIfNecessary(ele, true, options);
  1691. }
  1692. };
  1693. /*
  1694. * Function: extend Wraps the underlying library's extend functionality.
  1695. * Parameters: o1 - object to extend o2 - object to extend o1 with
  1696. * Returns: o1, extended with all properties from o2.
  1697. */
  1698. this.extend = function(o1, o2) {
  1699. return jsPlumb.CurrentLibrary.extend(o1, o2);
  1700. };
  1701. /*
  1702. * Function: getConnections Gets all or a subset of connections
  1703. * currently managed by this jsPlumb instance. Parameters: options - a
  1704. * JS object that holds options defining what sort of connections you're
  1705. * looking for. Valid values are: scope this may be a String or a list
  1706. * of Strings. jsPlumb will only return Connections whose scope matches
  1707. * what this option defines. If you omit it, you will be given
  1708. * Connections of every scope. source may be a string or a list of
  1709. * strings; constraints results to have only Connections whose source is
  1710. * this object. target may be a string or a list of strings; constraints
  1711. * results to have only Connections whose target is this object.
  1712. *
  1713. * The return value is a dictionary in this format:
  1714. * { 'scope1': [ {sourceId:'window1', targetId:'window2', source:<sourceElement>,
  1715. * target:<targetElement>, sourceEndpoint:<sourceEndpoint>,
  1716. * targetEndpoint:<targetEndpoint>, connection:<connection>},
  1717. * {sourceId:'window3', targetId:'window4', source:<sourceElement>,
  1718. * target:<targetElement>, sourceEndpoint:<sourceEndpoint>,
  1719. * targetEndpoint:<targetEndpoint>, connection:<connection>},
  1720. * {sourceId:'window1', targetId:'window3', source:<sourceElement>,
  1721. * target:<targetElement>, sourceEndpoint:<sourceEndpoint>,
  1722. * targetEndpoint:<targetEndpoint>, connection:<connection>} ],
  1723. * 'scope2': [ {sourceId:'window1', targetId:'window3', source:<sourceElement>,
  1724. * target:<targetElement>, sourceEndpoint:<sourceEndpoint>,
  1725. * targetEndpoint:<targetEndpoint>, connection:<connection>} ] }
  1726. *
  1727. */
  1728. this.getConnections = function(options) {
  1729. var r = {};
  1730. options = options || {};
  1731. var prepareList = function(input) {
  1732. var r = [];
  1733. if (input) {
  1734. if (typeof input == 'string')
  1735. r.push(input);
  1736. else
  1737. r = input;
  1738. }
  1739. return r;
  1740. };
  1741. var scopes = prepareList(options.scope);
  1742. var sources = prepareList(options.source);
  1743. var targets = prepareList(options.target);
  1744. var filter = function(list, value) {
  1745. return list.length > 0 ? _findIndex(list, value) != -1 : true;
  1746. };
  1747. for ( var i in connectionsByScope) {
  1748. if (filter(scopes, i)) {
  1749. r[i] = [];
  1750. for ( var j = 0; j < connectionsByScope[i].length; j++) {
  1751. var c = connectionsByScope[i][j];
  1752. if (filter(sources, c.sourceId) && filter(targets, c.targetId))
  1753. r[i].push({ sourceId : c.sourceId, targetId : c.targetId, source : c.source, target : c.target, sourceEndpoint : c.endpoints[0], targetEndpoint : c.endpoints[1], connection : c });
  1754. }
  1755. }
  1756. }
  1757. return r;
  1758. };
  1759. /*
  1760. * Function: getDefaultScope Gets the default scope for connections and
  1761. * endpoints. a scope defines a type of endpoint/connection; supplying a
  1762. * scope to an endpoint or connection allows you to support different
  1763. * types of connections in the same UI. but if you're only interested in
  1764. * one type of connection, you don't need to supply a scope. this method
  1765. * will probably be used by very few people; it's good for testing
  1766. * though.
  1767. */
  1768. this.getDefaultScope = function() {
  1769. return DEFAULT_SCOPE;
  1770. };
  1771. /*
  1772. * Function: getEndpoint gets an Endpoint by UUID Parameters: uuid - the
  1773. * UUID for the Endpoint Returns: Endpoint with the given UUID, null if
  1774. * nothing found.
  1775. */
  1776. this.getEndpoint = _getEndpoint;
  1777. /*
  1778. * gets an element's id, creating one if necessary. really only exposed
  1779. * for the lib-specific functionality to access; would be better to pass
  1780. * the current instance into the lib-specific code (even though this is
  1781. * a static call. i just don't want to expose it to the public API).
  1782. */
  1783. this.getId = _getId;
  1784. /*
  1785. * Function: hide Sets an element's connections to be hidden.
  1786. * Parameters: el - either the id of the element, or a selector for the
  1787. * element. Returns: void
  1788. */
  1789. this.hide = function(el) {
  1790. _setVisible(el, "none");
  1791. };
  1792. /*
  1793. * Function: makeAnchor Creates an anchor with the given params.
  1794. * Parameters: x - the x location of the anchor as a fraction of the
  1795. * total width. y - the y location of the anchor as a fraction of the
  1796. * total height. xOrientation - value indicating the general direction a
  1797. * connection from the anchor should go in, in the x direction.
  1798. * yOrientation - value indicating the general direction a connection
  1799. * from the anchor should go in, in the y direction. xOffset - a fixed
  1800. * offset that should be applied in the x direction that should be
  1801. * applied after the x position has been figured out. optional. defaults
  1802. * to 0. yOffset - a fixed offset that should be applied in the y
  1803. * direction that should be applied after the y position has been
  1804. * figured out. optional. defaults to 0.
  1805. * -- OR --
  1806. *
  1807. * params - {x:..., y:..., xOrientation etc }
  1808. * -- OR FROM 1.2.4 ---
  1809. *
  1810. * name - the name of some Anchor in the _currentInstance.Anchors array.
  1811. * -- OR FROM 1.2.4 ---
  1812. *
  1813. * coords - a list of coords for the anchor, like you would pass to
  1814. * jsPlumb.makeAnchor (eg [0.5,0.5,0,-1] - an anchor in the center of
  1815. * some element, oriented towards the top of the screen)
  1816. * -- OR FROM 1.2.4 ---
  1817. *
  1818. * anchor - an existing anchor. just gets passed back. it's handy
  1819. * internally to have this functionality.
  1820. *
  1821. * Returns: The newly created Anchor.
  1822. */
  1823. this.makeAnchor = function(x, y, xOrientation, yOrientation, xOffset, yOffset) {
  1824. // backwards compatibility here. we used to require an object passed
  1825. // in but that makes the call very verbose. easier to use
  1826. // by just passing in four/six values. but for backwards
  1827. // compatibility if we are given only one value we assume it's a
  1828. // call in the old form.
  1829. if (arguments.length == 0) return null;
  1830. var params = {};
  1831. if (arguments.length == 1) {
  1832. var specimen = arguments[0];
  1833. // if it appears to be an anchor already...
  1834. if (specimen.compute && specimen.getOrientation) return specimen;
  1835. // is it the name of an anchor type?
  1836. else if (typeof specimen == "string") return _currentInstance.Anchors[arguments[0]]();
  1837. // is it an array of coordinates?
  1838. else if (specimen.constructor == Array) return jsPlumb.makeAnchor.apply(this, specimen);
  1839. // last we try the backwards compatibility stuff.
  1840. else if (typeof arguments[0] == "object") jsPlumb.extend(params, x);
  1841. } else {
  1842. params = { x : x, y : y };
  1843. if (arguments.length >= 4) params.orientation = [ arguments[2], arguments[3] ];
  1844. if (arguments.length == 6) params.offsets = [ arguments[4], arguments[5] ];
  1845. }
  1846. var a = new Anchor(params);
  1847. a.clone = function() {
  1848. return new Anchor(params);
  1849. };
  1850. return a;
  1851. };
  1852. /**
  1853. * makes a list of anchors from the given list of types, eg
  1854. * ["TopCenter", "RightMiddle", "BottomCenter"]
  1855. */
  1856. this.makeAnchors = function(types) {
  1857. var r = [];
  1858. for ( var i = 0; i < types.length; i++)
  1859. r.push(_currentInstance.Anchors[types[i]]());
  1860. return r;
  1861. };
  1862. this.makeDynamicAnchor = function(anchors) {
  1863. return new DynamicAnchor(anchors);
  1864. };
  1865. /*
  1866. * Function: repaint Repaints an element and its connections. This
  1867. * method gets new sizes for the elements before painting anything.
  1868. * Parameters: el - either the id of the element or a selector
  1869. * representing the element. Returns: void See Also: <repaintEverything>
  1870. */
  1871. this.repaint = function(el) {
  1872. var _processElement = function(el) {
  1873. _draw(_getElementObject(el));
  1874. };
  1875. // support both lists...
  1876. if (typeof el == 'object') {
  1877. for ( var i = 0; i < el.length; i++)
  1878. _processElement(el[i]);
  1879. } // ...and single strings.
  1880. else
  1881. _processElement(el);
  1882. };
  1883. /*
  1884. * Function: repaintEverything Repaints all connections. Returns: void
  1885. * See Also: <repaint>
  1886. */
  1887. this.repaintEverything = function() {
  1888. var timestamp = _timestamp();
  1889. for ( var elId in endpointsByElement) {
  1890. _draw(_getElementObject(elId), null, timestamp);
  1891. }
  1892. };
  1893. /*
  1894. * Function: removeAllEndpoints
  1895. * Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes.
  1896. * Parameters: el - either an element id, or a selector for an element.
  1897. * Returns: void
  1898. * See Also: <removeEndpoint>
  1899. */
  1900. this.removeAllEndpoints = function(el) {
  1901. var elId = _getAttribute(el, "id");
  1902. var ebe = endpointsByElement[elId];
  1903. for ( var i in ebe)
  1904. _currentInstance.deleteEndpoint(ebe[i]);
  1905. endpointsByElement[elId] = [];
  1906. };
  1907. /**
  1908. * Function: removeEveryEndpoint
  1909. * Removes every Endpoint in this instance
  1910. * of jsPlumb.
  1911. *
  1912. * Returns: void
  1913. *
  1914. * See Also: <removeAllEndpoints> <removeEndpoint>
  1915. *
  1916. * @deprecated use deleteEveryEndpoint instead
  1917. */
  1918. this.removeEveryEndpoint = this.deleteEveryEndpoint;
  1919. /**
  1920. * Function: removeEndpoint Removes the given Endpoint from the given
  1921. * element. Parameters: el - either an element id, or a selector for an
  1922. * element. endpoint - Endpoint to remove. this is an Endpoint object,
  1923. * such as would have been returned from a call to addEndpoint. Returns:
  1924. * void See Also: <removeAllEndpoints> <removeEveryEndpoint>
  1925. *
  1926. * @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant).
  1927. */
  1928. this.removeEndpoint = function(el, endpoint) {
  1929. _currentInstance.deleteEndpoint(endpoint);
  1930. };
  1931. /**
  1932. * Function:reset
  1933. * removes all endpoints and connections and clears the
  1934. * listener list. to keep listeners just call jsPlumb.deleteEveryEndpoint.
  1935. */
  1936. this.reset = function() {
  1937. this.deleteEveryEndpoint();
  1938. this.clearListeners();
  1939. };
  1940. /*
  1941. * Function: setAutomaticRepaint Sets/unsets automatic repaint on window
  1942. * resize. Parameters: value - whether or not to automatically repaint
  1943. * when the window is resized. Returns: void
  1944. */
  1945. this.setAutomaticRepaint = function(value) {
  1946. automaticRepaint = value;
  1947. };
  1948. /*
  1949. * Function: setDefaultNewCanvasSize Sets the default size jsPlumb will
  1950. * use for a new canvas (we create a square canvas so one value is all
  1951. * that is required). This is a hack for IE, because ExplorerCanvas
  1952. * seems to need for a canvas to be larger than what you are going to
  1953. * draw on it at initialisation time. The default value of this is 1200
  1954. * pixels, which is quite large, but if for some reason you're drawing
  1955. * connectors that are bigger, you should adjust this value
  1956. * appropriately. Parameters: size - The default size to use. jsPlumb
  1957. * will use a square canvas so you need only supply one value. Returns:
  1958. * void
  1959. */
  1960. this.setDefaultNewCanvasSize = function(size) {
  1961. DEFAULT_NEW_CANVAS_SIZE = size;
  1962. };
  1963. /*
  1964. * Function: setDefaultScope Sets the default scope for connections and
  1965. * endpoints. a scope defines a type of endpoint/connection; supplying a
  1966. * scope to an endpoint or connection allows you to support different
  1967. * types of connections in the same UI. but if you're only interested in
  1968. * one type of connection, you don't need to supply a scope. this method
  1969. * will probably be used by very few people; it just instructs jsPlumb
  1970. * to use a different key for the default scope.
  1971. */
  1972. this.setDefaultScope = function(scope) {
  1973. DEFAULT_SCOPE = scope;
  1974. };
  1975. /*
  1976. * Function: setDraggable Sets whether or not a given element is
  1977. * draggable, regardless of what any plumb command may request.
  1978. * Parameters: el - either the id for the element, or a selector
  1979. * representing the element. Returns: void
  1980. */
  1981. this.setDraggable = _setDraggable;
  1982. /*
  1983. * Function: setDraggableByDefault Sets whether or not elements are
  1984. * draggable by default. Default for this is true. Parameters: draggable -
  1985. * value to set Returns: void
  1986. */
  1987. this.setDraggableByDefault = function(draggable) {
  1988. _draggableByDefault = draggable;
  1989. };
  1990. this.setDebugLog = function(debugLog) {
  1991. log = debugLog;
  1992. };
  1993. /*
  1994. * Function: setRepaintFunction Sets the function to fire when the
  1995. * window size has changed and a repaint was fired. Parameters: f -
  1996. * Function to execute. Returns: void
  1997. */
  1998. this.setRepaintFunction = function(f) {
  1999. repaintFunction = f;
  2000. };
  2001. /*
  2002. * Function: show Sets an element's connections to be visible.
  2003. * Parameters: el - either the id of the element, or a selector for the
  2004. * element. Returns: void
  2005. */
  2006. this.show = function(el) {
  2007. _setVisible(el, "block");
  2008. };
  2009. /*
  2010. * Function: sizeCanvas Helper to size a canvas. You would typically use
  2011. * this when writing your own Connector or Endpoint implementation.
  2012. * Parameters: x - [int] x position for the Canvas origin y - [int] y
  2013. * position for the Canvas origin w - [int] width of the canvas h -
  2014. * [int] height of the canvas Returns: void
  2015. */
  2016. this.sizeCanvas = function(canvas, x, y, w, h) {
  2017. if (canvas) {
  2018. canvas.style.height = h + "px";
  2019. canvas.height = h;
  2020. canvas.style.width = w + "px";
  2021. canvas.width = w;
  2022. canvas.style.left = x + "px";
  2023. canvas.style.top = y + "px";
  2024. }
  2025. };
  2026. /**
  2027. * gets some test hooks. nothing writable.
  2028. */
  2029. this.getTestHarness = function() {
  2030. return {
  2031. endpointsByElement : endpointsByElement,
  2032. endpointCount : function(elId) {
  2033. var e = endpointsByElement[elId];
  2034. return e ? e.length : 0;
  2035. },
  2036. connectionCount : function(scope) {
  2037. scope = scope || DEFAULT_SCOPE;
  2038. var c = connectionsByScope[scope];
  2039. return c ? c.length : 0;
  2040. },
  2041. findIndex : _findIndex,
  2042. getId : _getId
  2043. };
  2044. };
  2045. /**
  2046. * Toggles visibility of an element's connections. kept for backwards
  2047. * compatibility
  2048. */
  2049. this.toggle = _toggleVisible;
  2050. /*
  2051. * Function: toggleVisible Toggles visibility of an element's
  2052. * connections. Parameters: el - either the element's id, or a selector
  2053. * representing the element. Returns: void, but should be updated to
  2054. * return the current state
  2055. */
  2056. // TODO: update this method to return the current state.
  2057. this.toggleVisible = _toggleVisible;
  2058. /*
  2059. * Function: toggleDraggable Toggles draggability (sic) of an element's
  2060. * connections. Parameters: el - either the element's id, or a selector
  2061. * representing the element. Returns: The current draggable state.
  2062. */
  2063. this.toggleDraggable = _toggleDraggable;
  2064. /*
  2065. * Function: unload Unloads jsPlumb, deleting all storage. You should
  2066. * call this from an onunload attribute on the <body> element Returns:
  2067. * void
  2068. */
  2069. this.unload = function() {
  2070. delete endpointsByElement;
  2071. delete endpointsByUUID;
  2072. delete offsets;
  2073. delete sizes;
  2074. delete floatingConnections;
  2075. delete draggableStates;
  2076. };
  2077. /*
  2078. * Function: wrap Helper method to wrap an existing function with one of
  2079. * your own. This is used by the various implementations to wrap event
  2080. * callbacks for drag/drop etc; it allows jsPlumb to be transparent in
  2081. * its handling of these things. If a user supplies their own event
  2082. * callback, for anything, it will always be called. Parameters:
  2083. */
  2084. this.wrap = _wrap;
  2085. EventGenerator.apply(this);
  2086. this.addListener = this.bind;
  2087. };
  2088. var jsPlumb = window.jsPlumb = new jsPlumbInstance();
  2089. jsPlumb.getInstance = function(_defaults) {
  2090. var j = new jsPlumbInstance();
  2091. if (_defaults)
  2092. jsPlumb.extend(j.Defaults, _defaults);
  2093. return j;
  2094. };
  2095. })();
  2096. /*
  2097. * jsPlumb-defaults-1.2.4-RC1
  2098. *
  2099. * This script contains the default Anchors, Endpoints, Connectors and Overlays for jsPlumb. It should be used with jsPlumb 1.1.0 and above;
  2100. * prior to version 1.1.0 of jsPlumb the defaults were included inside the main script.
  2101. *
  2102. * NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together,
  2103. * in a minified file.
  2104. *
  2105. * Dual licensed under MIT and GPL2.
  2106. */
  2107. (function() {
  2108. var ie = !!!document.createElement('canvas').getContext;
  2109. /**
  2110. * Places you can anchor a connection to. These are helpers for common locations; they all just return an instance
  2111. * of Anchor that has been configured appropriately.
  2112. *
  2113. * You can write your own one of these; you
  2114. * just need to provide a 'compute' method and an 'orientation'. so you'd say something like this:
  2115. *
  2116. * jsPlumb.Anchors.MY_ANCHOR = {
  2117. * compute : function(xy, wh, txy, twh) { return some mathematics on those variables; },
  2118. * getOrientation : function() { return [ox, oy]; }
  2119. * };
  2120. *
  2121. * compute takes the [x,y] position of the top left corner of the anchored element,
  2122. * and the element's [width,height] (all in pixels), as well as the location and dimension of the element it's plumbed to,
  2123. * and returns where the anchor should be located.
  2124. *
  2125. * the 'orientation' array (returned here as [ox,oy]) indicates the general direction a connection from the anchor
  2126. * should go in, if possible. it is an [x,y] matrix where a value of 0 means no preference,
  2127. * -1 means go in a negative direction for the given axis, and 1 means go in a positive
  2128. * direction. so consider a TopCenter anchor: the orientation matrix for it is [0,-1],
  2129. * meaning connections naturally want to go upwards on screen. in a Bezier implementation, for example,
  2130. * the curve would start out going in that direction, before bending towards the target anchor.
  2131. */
  2132. var _curryAnchor = function(x,y,ox,oy) {
  2133. return function() {
  2134. return jsPlumb.makeAnchor(x,y,ox,oy);
  2135. };
  2136. };
  2137. jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1);
  2138. jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1);
  2139. jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0);
  2140. jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0);
  2141. jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0);
  2142. jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1);
  2143. jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1);
  2144. jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1);
  2145. jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1);
  2146. jsPlumb.Defaults.DynamicAnchors = function() {
  2147. return jsPlumb.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"]);
  2148. };
  2149. jsPlumb.Anchors["AutoDefault"] = function() { return jsPlumb.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors()); };
  2150. /**
  2151. * The Straight connector draws a simple straight line between the two anchor points.
  2152. */
  2153. jsPlumb.Connectors.Straight = function() {
  2154. var self = this;
  2155. var currentPoints = null;
  2156. var _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty;
  2157. /**
  2158. * Computes the new size and position of the canvas.
  2159. * @param sourceAnchor Absolute position on screen of the source object's anchor.
  2160. * @param targetAnchor Absolute position on screen of the target object's anchor.
  2161. * @param positionMatrix Indicates the relative positions of the left,top of the
  2162. * two plumbed objects. so [0,0] indicates that the source is to the left of, and
  2163. * above, the target. [1,0] means the source is to the right and above. [0,1] means
  2164. * the source is to the left and below. [1,1] means the source is to the right
  2165. * and below. this is used to figure out which direction to draw the connector in.
  2166. * @returns an array of positioning information. the first two values are
  2167. * the [left, top] absolute position the canvas should be placed on screen. the
  2168. * next two values are the [width,height] the canvas should be. after that each
  2169. * Connector can put whatever it likes into the array:it will be passed back in
  2170. * to the paint call. This particular function stores the origin and destination of
  2171. * the line it is going to draw. a more involved implementation, like a Bezier curve,
  2172. * would store the control point info in this array too.
  2173. */
  2174. this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth) {
  2175. var w = Math.abs(sourcePos[0] - targetPos[0]);
  2176. var h = Math.abs(sourcePos[1] - targetPos[1]);
  2177. var widthAdjusted = false, heightAdjusted = false;
  2178. // these are padding to ensure the whole connector line appears
  2179. var xo = 0.45 * w, yo = 0.45 * h;
  2180. // these are padding to ensure the whole connector line appears
  2181. w *= 1.9; h *=1.9;
  2182. var x = Math.min(sourcePos[0], targetPos[0]) - xo;
  2183. var y = Math.min(sourcePos[1], targetPos[1]) - yo;
  2184. // minimum size is 2 * line Width if minWidth was not given.
  2185. var calculatedMinWidth = Math.max(2 * lineWidth, minWidth);
  2186. if (w < calculatedMinWidth) {
  2187. w = calculatedMinWidth;
  2188. x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2);
  2189. xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2;
  2190. }
  2191. if (h < calculatedMinWidth) {
  2192. h = calculatedMinWidth;
  2193. y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2);
  2194. yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2;
  2195. }
  2196. _sx = sourcePos[0] < targetPos[0] ? xo : w-xo;
  2197. _sy = sourcePos[1] < targetPos[1] ? yo:h-yo;
  2198. _tx = sourcePos[0] < targetPos[0] ? w-xo : xo;
  2199. _ty = sourcePos[1] < targetPos[1] ? h-yo : yo;
  2200. currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ];
  2201. _dx = _tx - _sx, _dy = (_ty - _sy);
  2202. _m = _dy / _dx, _m2 = -1 / _m;
  2203. _b = -1 * ((_m * _sx) - _sy);
  2204. _theta = Math.atan(_m); _theta2 = Math.atan(_m2);
  2205. return currentPoints;
  2206. };
  2207. this.paint = function(dimensions, ctx)
  2208. {
  2209. ctx.beginPath();
  2210. ctx.moveTo(dimensions[4], dimensions[5]);
  2211. ctx.lineTo(dimensions[6], dimensions[7]);
  2212. ctx.stroke();
  2213. };
  2214. /**
  2215. * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from
  2216. * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much.
  2217. */
  2218. this.pointOnPath = function(location) {
  2219. var xp = _sx + (location * _dx);
  2220. var yp = _m == Infinity ? xp + _b : (_m * xp) + _b;
  2221. return [xp, yp];
  2222. };
  2223. /**
  2224. * returns the gradient of the connector at the given point - which for us is constant.
  2225. */
  2226. this.gradientAtPoint = function(location) { return _m; };
  2227. /**
  2228. * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where
  2229. * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels.
  2230. */
  2231. this.pointAlongPathFrom = function(location, distance) {
  2232. var p = self.pointOnPath(location);
  2233. var orientation = distance > 0 ? 1 : -1;
  2234. var y = Math.abs(distance * Math.sin(_theta));
  2235. if (_sy > _ty) y = y * -1;
  2236. var x = Math.abs(distance * Math.cos(_theta));
  2237. if (_sx > _tx) x = x * -1;
  2238. return [p[0] + (orientation * x), p[1] + (orientation * y)];
  2239. };
  2240. /**
  2241. * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location.
  2242. * the line is 'length' pixels long.
  2243. */
  2244. this.perpendicularToPathAt = function(location, distance, length) {
  2245. var p = self.pointAlongPathFrom(location, distance);
  2246. var m = self.gradientAtPoint(p.location);
  2247. var _theta2 = Math.atan(-1 / m);
  2248. var y = length / 2 * Math.sin(_theta2);
  2249. var x = length / 2 * Math.cos(_theta2);
  2250. return [[p[0] + x, p[1] + y], [p[0] - x, p[1] - y]];
  2251. };
  2252. this.createGradient = function(dim, ctx) {
  2253. return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]);
  2254. };
  2255. };
  2256. /**
  2257. * This Connector draws a Bezier curve with two control points.
  2258. * @param curviness How 'curvy' you want the curve to be! This is a directive for the
  2259. * placement of control points, not endpoints of the curve, so your curve does not
  2260. * actually touch the given point, but it has the tendency to lean towards it. the larger
  2261. * this value, the greater the curve is pulled from a straight line.
  2262. *
  2263. * a future implementation of this could take the control points as arguments, rather
  2264. * than fixing the curve to one basic shape.
  2265. */
  2266. jsPlumb.Connectors.Bezier = function(curviness) {
  2267. var self = this;
  2268. this.majorAnchor = curviness || 150;
  2269. this.minorAnchor = 10;
  2270. var currentPoints = null;
  2271. this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceAnchor, targetAnchor) {
  2272. // determine if the two anchors are perpendicular to each other in their orientation. we swap the control
  2273. // points around if so (code could be tightened up)
  2274. var soo = sourceAnchor.getOrientation(), too = targetAnchor.getOrientation();
  2275. var perpendicular = soo[0] != too[0] || soo[1] == too[1];
  2276. var p = [];
  2277. var ma = self.majorAnchor, mi = self.minorAnchor;
  2278. if (!perpendicular) {
  2279. if (soo[0] == 0) // X
  2280. p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi);
  2281. else p.push(point[0] - (ma * soo[0]));
  2282. if (soo[1] == 0) // Y
  2283. p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi);
  2284. else p.push(point[1] + (ma * too[1]));
  2285. }
  2286. else {
  2287. if (too[0] == 0) // X
  2288. p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi);
  2289. else p.push(point[0] + (ma * too[0]));
  2290. if (too[1] == 0) // Y
  2291. p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi);
  2292. else p.push(point[1] + (ma * soo[1]));
  2293. }
  2294. return p;
  2295. };
  2296. var _CP, _CP2, _sx, _tx, _sx, _sy, _canvasX, _canvasY, _w, _h;
  2297. this.compute = function(sourcePos, targetPos, sourceAnchor, targetAnchor, lineWidth, minWidth)
  2298. {
  2299. lineWidth = lineWidth || 0;
  2300. _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth;
  2301. _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth;
  2302. _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2);
  2303. _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2);
  2304. _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2);
  2305. _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2);
  2306. _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2);
  2307. _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2);
  2308. _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceAnchor, targetAnchor);
  2309. _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, targetAnchor, sourceAnchor);
  2310. var minx1 = Math.min(_sx,_tx); var minx2 = Math.min(_CP[0], _CP2[0]); var minx = Math.min(minx1,minx2);
  2311. var maxx1 = Math.max(_sx,_tx); var maxx2 = Math.max(_CP[0], _CP2[0]); var maxx = Math.max(maxx1,maxx2);
  2312. if (maxx > _w) _w = maxx;
  2313. if (minx < 0) {
  2314. _canvasX += minx; var ox = Math.abs(minx);
  2315. _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox;
  2316. }
  2317. var miny1 = Math.min(_sy,_ty); var miny2 = Math.min(_CP[1], _CP2[1]); var miny = Math.min(miny1,miny2);
  2318. var maxy1 = Math.max(_sy,_ty); var maxy2 = Math.max(_CP[1], _CP2[1]); var maxy = Math.max(maxy1,maxy2);
  2319. if (maxy > _h) _h = maxy;
  2320. if (miny < 0) {
  2321. _canvasY += miny; var oy = Math.abs(miny);
  2322. _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy;
  2323. }
  2324. if (minWidth && _w < minWidth) {
  2325. var posAdjust = (minWidth - _w) / 2;
  2326. _w = minWidth;
  2327. _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust;
  2328. }
  2329. if (minWidth && _h < minWidth) {
  2330. var posAdjust = (minWidth - _h) / 2;
  2331. _h = minWidth;
  2332. _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust;
  2333. }
  2334. currentPoints = [_canvasX, _canvasY, _w, _h, _sx, _sy, _tx, _ty, _CP[0], _CP[1], _CP2[0], _CP2[1] ];
  2335. return currentPoints;
  2336. };
  2337. this.paint = function(d, ctx) {
  2338. ctx.beginPath();
  2339. ctx.moveTo(d[4],d[5]);
  2340. ctx.bezierCurveTo(d[8],d[9],d[10],d[11],d[6],d[7]);
  2341. ctx.stroke();
  2342. };
  2343. var _makeCurve = function() {
  2344. return [
  2345. { x:_sx, y:_sy },
  2346. { x:_CP[0], y:_CP[1] },
  2347. { x:_CP2[0], y:_CP2[1] },
  2348. { x:_tx, y:_ty }
  2349. ];
  2350. };
  2351. /**
  2352. * returns the distance the given point is from the curve. not enabled for 1.2.3. didnt make the cut. next time.
  2353. *
  2354. this.distanceFrom = function(point) {
  2355. var curve = [ {x:currentPoints[4], y:currentPoints[5]},
  2356. {x:currentPoints[8], y:currentPoints[9]},
  2357. {x:currentPoints[10], y:currentPoints[11]},
  2358. {x:currentPoints[6], y:currentPoints[7]}];
  2359. return (jsBezier.distanceFromCurve(point, curve));
  2360. };*/
  2361. /**
  2362. * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from
  2363. * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much.
  2364. */
  2365. this.pointOnPath = function(location) {
  2366. return jsBezier.pointOnCurve(_makeCurve(), location);
  2367. };
  2368. /**
  2369. * returns the gradient of the connector at the given point.
  2370. */
  2371. this.gradientAtPoint = function(location) {
  2372. return jsBezier.gradientAtPoint(_makeCurve(), location);
  2373. };
  2374. /**
  2375. * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult.
  2376. * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous
  2377. * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see.
  2378. * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller
  2379. * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to
  2380. * calculate the step as a function of distance/distance between endpoints.
  2381. */
  2382. this.pointAlongPathFrom = function(location, distance) {
  2383. return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance);
  2384. };
  2385. /**
  2386. * calculates a line that is perpendicular to, and centered on, the path at 'distance' pixels from the given location.
  2387. * the line is 'length' pixels long.
  2388. */
  2389. this.perpendicularToPathAt = function(location, distance, length) {
  2390. return jsBezier.perpendicularToCurveAt(_makeCurve(), location, distance, length);
  2391. };
  2392. this.createGradient = function(dim, ctx, swap) {
  2393. return (swap) ? ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]);
  2394. };
  2395. };
  2396. /**
  2397. * Types of endpoint UIs. we supply four - a circle of default radius 10px, a rectangle of
  2398. * default size 20x20, an image (with no default), and a Triangle, of default size 15.
  2399. * you can supply others of these if you want to - see the documentation for a howto.
  2400. */
  2401. /**
  2402. * a round endpoint, with default radius 10 pixels.
  2403. */
  2404. jsPlumb.Endpoints.Dot = function(params) {
  2405. params = params || { radius:10 };
  2406. var self = this;
  2407. this.radius = params.radius;
  2408. var defaultOffset = 0.5 * this.radius;
  2409. var defaultInnerRadius = this.radius / 3;
  2410. var parseValue = function(value) {
  2411. try {
  2412. return parseInt(value);
  2413. }
  2414. catch(e) {
  2415. if (value.substring(value.length - 1) == '%')
  2416. return parseInt(value.substring(0, value - 1));
  2417. }
  2418. }
  2419. var calculateAdjustments = function(gradient) {
  2420. var offsetAdjustment = defaultOffset;
  2421. var innerRadius = defaultInnerRadius;
  2422. if (gradient.offset) offsetAdjustment = parseValue(gradient.offset);
  2423. if(gradient.innerRadius) innerRadius = parseValue(gradient.innerRadius);
  2424. return [offsetAdjustment, innerRadius];
  2425. };
  2426. this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {
  2427. var radius = endpointStyle.radius || self.radius;
  2428. var x = anchorPoint[0] - radius;
  2429. var y = anchorPoint[1] - radius;
  2430. jsPlumb.sizeCanvas(canvas, x, y, radius * 2, radius * 2);
  2431. var ctx = canvas.getContext('2d');
  2432. var style = jsPlumb.extend({}, endpointStyle);
  2433. if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle;
  2434. jsPlumb.extend(ctx, style);
  2435. if (endpointStyle.gradient && !ie) {
  2436. var adjustments = calculateAdjustments(endpointStyle.gradient);
  2437. var yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0];
  2438. var xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0];
  2439. var g = ctx.createRadialGradient(radius, radius, radius, radius + xAdjust, radius + yAdjust, adjustments[1]);
  2440. for (var i = 0; i < endpointStyle.gradient.stops.length; i++)
  2441. g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]);
  2442. ctx.fillStyle = g;
  2443. }
  2444. ctx.beginPath();
  2445. ctx.arc(radius, radius, radius, 0, Math.PI*2, true);
  2446. ctx.closePath();
  2447. ctx.fill();
  2448. };
  2449. };
  2450. /**
  2451. * A Rectangular endpoint, with default size 20x20.
  2452. */
  2453. jsPlumb.Endpoints.Rectangle = function(params) {
  2454. params = params || { width:20, height:20 };
  2455. var self = this;
  2456. this.width = params.width;
  2457. this.height = params.height;
  2458. this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {
  2459. var width = endpointStyle.width || self.width;
  2460. var height = endpointStyle.height || self.height;
  2461. var x = anchorPoint[0] - (width/2);
  2462. var y = anchorPoint[1] - (height/2);
  2463. jsPlumb.sizeCanvas(canvas, x, y, width, height);
  2464. var ctx = canvas.getContext('2d');
  2465. var style = jsPlumb.extend({}, endpointStyle);
  2466. if (style.fillStyle == null) style.fillStyle = connectorPaintStyle.strokeStyle;
  2467. jsPlumb.extend(ctx, style);
  2468. var ie = (/MSIE/.test(navigator.userAgent) && !window.opera);
  2469. if (endpointStyle.gradient && !ie) {
  2470. // first figure out which direction to run the gradient in (it depends on the orientation of the anchors)
  2471. var y1 = orientation[1] == 1 ? height : orientation[1] == 0 ? height / 2 : 0;
  2472. var y2 = orientation[1] == -1 ? height : orientation[1] == 0 ? height / 2 : 0;
  2473. var x1 = orientation[0] == 1 ? width : orientation[0] == 0 ? width / 2 : 0;
  2474. var x2 = orientation[0] == -1 ? width : orientation[0] == 0 ? height / 2 : 0;
  2475. var g = ctx.createLinearGradient(x1,y1,x2,y2);
  2476. for (var i = 0; i < endpointStyle.gradient.stops.length; i++)
  2477. g.addColorStop(endpointStyle.gradient.stops[i][0], endpointStyle.gradient.stops[i][1]);
  2478. ctx.fillStyle = g;
  2479. }
  2480. ctx.beginPath();
  2481. ctx.rect(0, 0, width, height);
  2482. ctx.closePath();
  2483. ctx.fill();
  2484. };
  2485. };
  2486. jsPlumb.Endpoints.Triangle = function(params) {
  2487. params = params || { width:15, height:15 };
  2488. var self = this;
  2489. this.width = params.width;
  2490. this.height = params.height;
  2491. this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle)
  2492. {
  2493. var width = endpointStyle.width || self.width;
  2494. var height = endpointStyle.height || self.height;
  2495. var x = anchorPoint[0] - width/2;
  2496. var y = anchorPoint[1] - height/2;
  2497. jsPlumb.sizeCanvas(canvas, x, y, width, height);
  2498. var ctx = canvas.getContext('2d');
  2499. var offsetX = 0, offsetY = 0, angle = 0;
  2500. if( orientation[0] == 1 )
  2501. {
  2502. offsetX = width;
  2503. offsetY = height;
  2504. angle = 180;
  2505. }
  2506. if( orientation[1] == -1 )
  2507. {
  2508. offsetX = width;
  2509. angle = 90;
  2510. }
  2511. if( orientation[1] == 1 )
  2512. {
  2513. offsetY = height;
  2514. angle = -90;
  2515. }
  2516. ctx.fillStyle = endpointStyle.fillStyle;
  2517. ctx.translate(offsetX, offsetY);
  2518. ctx.rotate(angle * Math.PI/180);
  2519. ctx.beginPath();
  2520. ctx.moveTo(0, 0);
  2521. ctx.lineTo(width/2, height/2);
  2522. ctx.lineTo(0, height);
  2523. ctx.closePath();
  2524. ctx.fill();
  2525. };
  2526. };
  2527. /**
  2528. * Image endpoint - draws an image as the endpoint. You must provide a 'url' or, since 1.2.4, a 'src' property in the params object..
  2529. */
  2530. jsPlumb.Endpoints.Image = function(params) {
  2531. var self = this;
  2532. this.img = new Image();
  2533. var ready = false;
  2534. this.img.onload = function() {
  2535. self.ready = true;
  2536. };
  2537. this.img.src = params.src || params.url;
  2538. var actuallyPaint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {
  2539. var width = self.img.width || endpointStyle.width;
  2540. var height = self.img.height || endpointStyle.height;
  2541. var x = anchorPoint[0] - (width/2);
  2542. var y = anchorPoint[1] - (height/2);
  2543. jsPlumb.sizeCanvas(canvas, x, y, width, height);
  2544. var ctx = canvas.getContext('2d');
  2545. ctx.drawImage(self.img,0,0);
  2546. };
  2547. this.paint = function(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle) {
  2548. if (self.ready) {
  2549. actuallyPaint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle)
  2550. }
  2551. else
  2552. window.setTimeout(function() {
  2553. self.paint(anchorPoint, orientation, canvas, endpointStyle, connectorPaintStyle);
  2554. }, 200);
  2555. };
  2556. };
  2557. /**
  2558. * An arrow overlay. you can provide:
  2559. *
  2560. * length - distance in pixels from head to tail baseline. default 20.
  2561. * width - width in pixels of the tail baseline. default 20.
  2562. * fillStyle - style to use when filling the arrow. defaults to "black".
  2563. * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked.
  2564. * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null.
  2565. * foldback - distance (as a decimal from 0 to 1 inclusive) along the length of the arrow marking the point the tail points should fold back to. defaults to 0.623.
  2566. * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5.
  2567. */
  2568. jsPlumb.Overlays.Arrow = function(params) {
  2569. params = params || {};
  2570. var self = this;
  2571. var length = params.length || 20;
  2572. var width = params.width || 20;
  2573. var fillStyle = params.fillStyle || "black";
  2574. var strokeStyle = params.strokeStyle;
  2575. var lineWidth = params.lineWidth || 1;
  2576. this.loc = params.location || 0.5;
  2577. // how far along the arrow the lines folding back in come to. default is 62.3%.
  2578. var foldback = params.foldback || 0.623;
  2579. var _getFoldBackPoint = function(connector, loc) {
  2580. if (foldback == 0.5) return connector.pointOnPath(loc);
  2581. else {
  2582. var adj = 0.5 - foldback; // we calculate relative to the center
  2583. return connector.pointAlongPathFrom(loc, length * adj);
  2584. }
  2585. };
  2586. this.computeMaxSize = function() { return width * 1.5; }
  2587. this.draw = function(connector, ctx) {
  2588. // this is the arrow head position
  2589. var hxy = connector.pointAlongPathFrom(self.loc, length / 2);
  2590. // this is the center of the tail
  2591. var txy = connector.pointAlongPathFrom(self.loc, -length / 2), tx = txy[0], ty = txy[1];
  2592. // this is the tail vector
  2593. var tail = connector.perpendicularToPathAt(self.loc, -length / 2, width);
  2594. // this is the point the tail goes in to
  2595. var cxy = _getFoldBackPoint(connector, self.loc);
  2596. ctx.lineWidth = lineWidth;
  2597. ctx.beginPath();
  2598. ctx.moveTo(hxy[0], hxy[1]);
  2599. ctx.lineTo(tail[0][0], tail[0][1]);
  2600. ctx.lineTo(cxy[0], cxy[1]);
  2601. ctx.lineTo(tail[1][0], tail[1][1]);
  2602. ctx.lineTo(hxy[0], hxy[1]);
  2603. ctx.closePath();
  2604. if (strokeStyle) {
  2605. ctx.strokeStyle = strokeStyle;
  2606. ctx.stroke();
  2607. }
  2608. ctx.fillStyle = fillStyle;
  2609. ctx.fill();
  2610. }
  2611. };
  2612. /**
  2613. * a basic arrow. this is in fact just one instance of the more generic case in which the tail folds back on itself to some
  2614. * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does
  2615. * a 'call' to Arrow with foldback set appropriately. See Arrow for params.
  2616. */
  2617. jsPlumb.Overlays.PlainArrow = function(params) {
  2618. params = params || {};
  2619. var p = jsPlumb.extend(params, {foldback:1});
  2620. jsPlumb.Overlays.Arrow.call(this, p);
  2621. };
  2622. /**
  2623. * a diamond. like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just
  2624. * happens that in this case, that point is greater than the length of the the arrow. See Arrow for params.
  2625. *
  2626. * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the
  2627. * center is actually 1/4 of the way along for this guy. but we don't have any knowledge of pixels at this point, so we're kind of
  2628. * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value
  2629. * would be -l/4 in this case - move along one quarter of the total length.
  2630. */
  2631. jsPlumb.Overlays.Diamond = function(params) {
  2632. params = params || {};
  2633. var l = params.length || 40;
  2634. var p = jsPlumb.extend(params, {length:l/2, foldback:2});
  2635. jsPlumb.Overlays.Arrow.call(this, p);
  2636. };
  2637. /**
  2638. * A Label overlay. Params you can provide:
  2639. *
  2640. * labelStyle - js object containing style instructions for the label. defaults to jsPlumb.Defaults.LabelStyle.
  2641. * label - the label to paint. may be a string or a function that returns a string. nothing will be painted if your label is null or your
  2642. * label function returns null. empty strings _will_ be painted.
  2643. * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5.
  2644. * borderWidth - width of a border to paint. defaults to zero.
  2645. * borderStyle - strokeStyle to use when painting the border, if necessary.
  2646. */
  2647. jsPlumb.Overlays.Label = function(params) {
  2648. this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle;
  2649. this.label = params.label;
  2650. var self = this;
  2651. var labelWidth = null, labelHeight = null, labelText = null, labelPadding = null;
  2652. this.location = params.location || 0.5;
  2653. this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it!
  2654. var _textDimensions = function(ctx) {
  2655. if (self.cachedDimensions) return self.cachedDimensions; // return cached copy if we can. if we add a setLabel function remember to clear the cache.
  2656. labelText = typeof self.label == 'function' ? self.label(self) : self.label;
  2657. var d = {};
  2658. if (labelText) {
  2659. var lines = labelText.split("\n");
  2660. ctx.save();
  2661. if (self.labelStyle.font) ctx.font = self.labelStyle.font;
  2662. var t = _widestLine(lines, ctx);
  2663. // a fake text height measurement: use the width of upper case M
  2664. var h = ctx.measureText("M").width;
  2665. labelPadding = self.labelStyle.padding || 0.25;
  2666. labelWidth = t + (2 * t * labelPadding);
  2667. labelHeight = (lines.length * h) + (2 * h * labelPadding);
  2668. var textHeight = lines.length * h;
  2669. ctx.restore();
  2670. d = {width:labelWidth, height:labelHeight, lines:lines, oneLine:h, padding:labelPadding, textHeight:textHeight};
  2671. }
  2672. if (typeof self.label != 'function') self.cachedDimensions = d; // cache it if we can.
  2673. return d;
  2674. };
  2675. this.computeMaxSize = function(connector, ctx) {
  2676. var td = _textDimensions(ctx);
  2677. return td.width ? Math.max(td.width, td.height) * 1.5 : 0;
  2678. };
  2679. var _widestLine = function(lines, ctx) {
  2680. var max = 0;
  2681. for (var i = 0; i < lines.length; i++) {
  2682. var t = ctx.measureText(lines[i]).width;
  2683. if (t > max) max = t;
  2684. }
  2685. return max;
  2686. };
  2687. this.draw = function(connector, ctx) {
  2688. var td = _textDimensions(ctx);
  2689. if (td.width) {
  2690. var cxy = connector.pointOnPath(self.location);
  2691. if (self.labelStyle.font) ctx.font = self.labelStyle.font;
  2692. if (self.labelStyle.fillStyle)
  2693. ctx.fillStyle = self.labelStyle.fillStyle;
  2694. else
  2695. ctx.fillStyle = "rgba(0,0,0,0)";
  2696. ctx.fillRect(cxy[0] - (td.width / 2), cxy[1] - (td.height / 2) , td.width , td.height );
  2697. if (self.labelStyle.color) ctx.fillStyle = self.labelStyle.color;
  2698. ctx.textBaseline = "middle";
  2699. ctx.textAlign = "center";
  2700. for (i = 0; i < td.lines.length; i++) {
  2701. ctx.fillText(td.lines[i],cxy[0], cxy[1] - (td.textHeight / 2) + (td.oneLine/2) + (i*td.oneLine));
  2702. }
  2703. // border
  2704. if (self.labelStyle.borderWidth > 0) {
  2705. ctx.strokeStyle = self.labelStyle.borderStyle || "black";
  2706. ctx.strokeRect(cxy[0] - (td.width / 2), cxy[1] - (td.height / 2) , td.width , td.height );
  2707. }
  2708. }
  2709. };
  2710. };
  2711. /**
  2712. * an image overlay. params may contain:
  2713. *
  2714. * location : proportion along the connector to draw the image. optional.
  2715. * src : image src. required.
  2716. * events : map of event names to functions; each event listener will be bound to the img.
  2717. */
  2718. jsPlumb.Overlays.Image = function(params) {
  2719. var self = this;
  2720. this.location = params.location || 0.5;
  2721. this.img = new Image();
  2722. var imgDiv = null;
  2723. var notReadyInterval = null;
  2724. var notReadyConnector, notReadyContext;
  2725. var events = params.events || {};
  2726. var _init = function() {
  2727. if (self.ready) {
  2728. window.clearInterval(notReadyInterval);
  2729. imgDiv = document.createElement("img");
  2730. imgDiv.src = self.img.src;
  2731. imgDiv.style.position = "absolute";
  2732. imgDiv.style.display="none";
  2733. imgDiv.className = "_jsPlumb_overlay";
  2734. document.body.appendChild(imgDiv);// HMM
  2735. // attach events
  2736. for (var e in events) {
  2737. jsPlumb.CurrentLibrary.bind(imgDiv, e, events[e]);
  2738. }
  2739. if (notReadyConnector && notReadyContext) {
  2740. _draw(notReadyConnector, notReadyContext);
  2741. notReadyContext = null;
  2742. notReadyConnector = null;
  2743. }
  2744. }
  2745. };
  2746. this.img.onload = function() {
  2747. self.ready = true;
  2748. };
  2749. this.img.src = params.src || params.url;
  2750. notReadyInterval = window.setInterval(_init, 250);
  2751. this.computeMaxSize = function(connector, ctx) {
  2752. return [self.img.width, self.img.height]
  2753. };
  2754. var _draw = function(connector, ctx) {
  2755. var cxy = connector.pointOnPath(self.location);
  2756. var canvas = jsPlumb.CurrentLibrary.getElementObject(ctx.canvas);
  2757. var canvasOffset = jsPlumb.CurrentLibrary.getOffset(canvas);
  2758. var o = {left:canvasOffset.left + cxy[0] - (self.img.width/2), top:canvasOffset.top + cxy[1] - (self.img.height/2)};
  2759. jsPlumb.CurrentLibrary.setOffset(imgDiv, o);
  2760. imgDiv.style.display = "block";
  2761. };
  2762. this.draw = function(connector, ctx) {
  2763. if (self.ready)
  2764. _draw(connector, ctx);
  2765. else {
  2766. notReadyConnector = connector;
  2767. notReadyContext = ctx;
  2768. }
  2769. };
  2770. };
  2771. })();/*
  2772. * jquery.jsPlumb 1.2.4-RC1
  2773. *
  2774. * jQuery specific functionality for jsPlumb.
  2775. *
  2776. * http://morrisonpitt.com/jsPlumb/demo.html
  2777. * http://code.google.com/p/jsPlumb
  2778. *
  2779. * NOTE: for production usage you should use jsPlumb-all-x.x.x-min.js, which contains the main jsPlumb script and this script together,
  2780. * in a minified file.
  2781. *
  2782. * Dual licensed under MIT and GPL2.
  2783. *
  2784. */
  2785. (function($){
  2786. /**
  2787. * plumbs the results of the selector to some target, using the given options if supplied,
  2788. * or the defaults otherwise.
  2789. */
  2790. $.fn.plumb = function(options) {
  2791. var options = $.extend({}, options);
  2792. return this.each(function()
  2793. {
  2794. var params = $.extend({source:$(this)}, options);
  2795. jsPlumb.connect(params);
  2796. });
  2797. };
  2798. /**
  2799. * detaches the results of the selector from the given target or list of targets - 'target'
  2800. * may be a String or a List.
  2801. */
  2802. $.fn.detach = function(target) {
  2803. return this.each(function() {
  2804. if (target) {
  2805. var id = $(this).attr("id");
  2806. if (typeof target == 'string') target = [target];
  2807. for (var i = 0; i < target.length; i++)
  2808. jsPlumb.detach(id, target[i]);
  2809. }
  2810. });
  2811. };
  2812. /**
  2813. * detaches the results from the selector from all connections.
  2814. */
  2815. $.fn.detachAll = function() {
  2816. return this.each(function()
  2817. {
  2818. var id = $(this).attr("id");
  2819. jsPlumb.detachAll(id);
  2820. });
  2821. };
  2822. /**
  2823. * adds an endpoint to the elements resulting from the selector. options may be null,
  2824. * in which case jsPlumb will use the default options. see documentation.
  2825. */
  2826. $.fn.addEndpoint = function(options) {
  2827. var addedEndpoints = [];
  2828. this.each(function() {
  2829. addedEndpoints.push(jsPlumb.addEndpoint($(this).attr("id"), options));
  2830. });
  2831. return addedEndpoints[0];
  2832. };
  2833. /**
  2834. * adds a list of endpoints to the elements resulting from the selector. options may be null,
  2835. * in which case jsPlumb will use the default options. see documentation.
  2836. */
  2837. $.fn.addEndpoints = function(endpoints) {
  2838. var addedEndpoints = [];
  2839. return this.each(function() {
  2840. var e = jsPlumb.addEndpoints($(this).attr("id"), endpoints);
  2841. for (var i = 0; i < e.length; i++) addedEndpoints.push(e[i]);
  2842. });
  2843. };
  2844. /**
  2845. * remove the endpoint, if it exists, deleting its UI elements etc.
  2846. */
  2847. $.fn.removeEndpoint = function(endpoint) {
  2848. this.each(function() {
  2849. jsPlumb.removeEndpoint($(this).attr("id"), endpoint);
  2850. });
  2851. };
  2852. })(jQuery);
  2853. /*TODO: abstract this out from jQuery too! but how...because jsPlumb is not loaded yet.
  2854. $(window).bind('resize', function() {
  2855. if (resizeTimer) clearTimeout(resizeTimer);
  2856. resizeTimer = setTimeout(repaintEverything, 100);
  2857. });*/
  2858. /*
  2859. * the library agnostic functions, such as find offset, get id, get attribute, extend etc.
  2860. */
  2861. (function($) {
  2862. jsPlumb.CurrentLibrary = {
  2863. /**
  2864. * mapping of drag events for jQuery
  2865. */
  2866. dragEvents : {
  2867. 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step',
  2868. 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete'
  2869. },
  2870. bind : function(el, event, callback) {
  2871. el = jsPlumb.CurrentLibrary.getElementObject(el);
  2872. el.bind(event, callback);
  2873. },
  2874. appendElement : function(child, parent) {
  2875. jsPlumb.CurrentLibrary.getElementObject(parent).append(child);
  2876. },
  2877. /**
  2878. * wrapper around the library's 'extend' functionality (which it hopefully has.
  2879. * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you
  2880. * instead. it's not like its hard.
  2881. */
  2882. extend : function(o1, o2) {
  2883. return $.extend(o1, o2);
  2884. },
  2885. /**
  2886. * gets an "element object" from the given input. this means an object that is used by the
  2887. * underlying library on which jsPlumb is running. 'el' may already be one of these objects,
  2888. * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup
  2889. * function is used to find the element, using the given String as the element's id.
  2890. *
  2891. */
  2892. getElementObject : function(el) {
  2893. return typeof(el)=='string' ? $("#" + el) : $(el);
  2894. },
  2895. /**
  2896. * gets the offset for the element object. this should return a js object like this:
  2897. *
  2898. * { left:xxx, top: xxx }
  2899. */
  2900. getOffset : function(el) {
  2901. return el.offset();
  2902. },
  2903. /**
  2904. * gets the size for the element object, in an array : [ width, height ].
  2905. */
  2906. getSize : function(el) {
  2907. return [el.outerWidth(), el.outerHeight()];
  2908. },
  2909. /**
  2910. * gets the named attribute from the given element object.
  2911. */
  2912. getAttribute : function(el, attName) {
  2913. return el.attr(attName);
  2914. },
  2915. /**
  2916. * sets the named attribute on the given element object.
  2917. */
  2918. setAttribute : function(el, attName, attValue) {
  2919. el.attr(attName, attValue);
  2920. },
  2921. /**
  2922. * adds the given class to the element object.
  2923. */
  2924. addClass : function(el, clazz) {
  2925. el.addClass(clazz);
  2926. },
  2927. /**
  2928. * removes the given class from the element object.
  2929. */
  2930. removeClass : function(el, clazz) {
  2931. el.removeClass(clazz);
  2932. },
  2933. /**
  2934. * initialises the given element to be draggable.
  2935. */
  2936. initDraggable : function(el, options) {
  2937. // remove helper directive if present.
  2938. options.helper = null;
  2939. //TODO: if 'revert' is set on the options it causes end points to animate back to
  2940. // where they came from, if the connection is aborted. do we care? probably not.
  2941. // the todo is to decide whether we care or not.
  2942. options['scope'] = options['scope'] || jsPlumb.Defaults.Scope;
  2943. el.draggable(options);
  2944. },
  2945. /**
  2946. * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element.
  2947. */
  2948. isDragSupported : function(el, options) {
  2949. return el.draggable;
  2950. },
  2951. /**
  2952. * sets the draggable state for the given element
  2953. */
  2954. setDraggable : function(el, draggable) {
  2955. el.draggable("option", "disabled", !draggable);
  2956. },
  2957. /**
  2958. * initialises the given element to be droppable.
  2959. */
  2960. initDroppable : function(el, options) {
  2961. options['scope'] = options['scope'] || jsPlumb.Defaults.Scope;
  2962. el.droppable(options);
  2963. },
  2964. /**
  2965. * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element.
  2966. */
  2967. isDropSupported : function(el, options) {
  2968. return el.droppable;
  2969. },
  2970. /**
  2971. * animates the given element.
  2972. */
  2973. animate : function(el, properties, options) {
  2974. el.animate(properties, options);
  2975. },
  2976. /**
  2977. * takes the args passed to an event function and returns you an object that gives the
  2978. * position of the object being moved, as a js object with the same params as the result of
  2979. * getOffset, ie: { left: xxx, top: xxx }.
  2980. *
  2981. * different libraries have different signatures for their event callbacks.
  2982. * see getDragObject as well
  2983. */
  2984. getUIPosition : function(eventArgs) {
  2985. var ui = eventArgs[1];
  2986. //return ui.absolutePosition || ui.offset;
  2987. return ui.offset || ui.absolutePosition;
  2988. },
  2989. /**
  2990. * takes the args passed to an event function and returns you an object representing that which is being dragged.
  2991. */
  2992. getDragObject : function(eventArgs) {
  2993. return eventArgs[1].draggable;
  2994. },
  2995. removeElement : function(element, parent) {
  2996. jsPlumb.CurrentLibrary.getElementObject(element).remove();
  2997. },
  2998. getScrollLeft : function(el) {
  2999. return el.scrollLeft();
  3000. },
  3001. getScrollTop : function(el) {
  3002. return el.scrollTop();
  3003. },
  3004. setOffset : function(el, o) {
  3005. jsPlumb.CurrentLibrary.getElementObject(el).offset(o);
  3006. }
  3007. };
  3008. })(jQuery);
  3009. /**
  3010. * a set of bezier curve functions, used by jsPlumb, and perhaps useful for other people.
  3011. *
  3012. * functions are all in the 'jsBezier' namespace. here's the list:
  3013. *
  3014. * distanceFromCurve(point, curve)
  3015. * gradientAtPoint(curve, location)
  3016. * nearestPointOnCurve(point, curve) ??
  3017. * pointOnCurve(curve, location)
  3018. * pointAlongCurveFrom
  3019. * perpendicularToCurveAt
  3020. * quadraticPointOnCurve
  3021. */
  3022. (function() {
  3023. var MAXDEPTH = 64, EPSILON = Math.pow(2.0,-MAXDEPTH-1), DEGREE = 3, W_DEGREE = 5;
  3024. var V2Sub = function(from, p) { return {x:from.x - p.x, y:from.y - p.y }; };
  3025. var V2Dot = function(v1, v2) { return (v1.x * v2.x) + (v1.y * v2.y); };
  3026. var V2SquaredLength = function(v) { return (v.x * v.x) + (v.y * v.y); };
  3027. var V2ScaleII = function(v, s) { return {x:v.x * s, y:v.y * s }; };
  3028. /**
  3029. * Calculates the distance that the point P is from the curve V. i think ;)
  3030. * i need to verify that.
  3031. */
  3032. var _distanceFromCurve = function(P, V) {
  3033. var w, n_solutions, t;
  3034. var t_candidate = new Array(W_DEGREE);
  3035. w = _convertToBezierForm(P, V);
  3036. n_solutions = _findRoots(w, W_DEGREE, t_candidate, 0);
  3037. var dist, new_dist, p, v, i;
  3038. v = V2Sub(P, V[0]);
  3039. dist = V2SquaredLength(v);
  3040. t = 0.0;
  3041. for (i = 0; i < n_solutions; i++) {
  3042. p = Bezier(V, DEGREE, t_candidate[i],
  3043. null, null);
  3044. v = V2Sub(P, p);
  3045. new_dist = V2SquaredLength(v);
  3046. if (new_dist < dist) {
  3047. dist = new_dist;
  3048. t = t_candidate[i];
  3049. }
  3050. }
  3051. v = V2Sub(P, V[DEGREE]);
  3052. new_dist = V2SquaredLength(v);
  3053. if (new_dist < dist) {
  3054. dist = new_dist;
  3055. t = 1.0;
  3056. }
  3057. return {t:t,d:dist};
  3058. };
  3059. var _nearestPointOnCurve = function(P, V) {
  3060. var td = _distanceFromCurve(P, V);
  3061. return Bezier(V, DEGREE, td.t, null, null);
  3062. };
  3063. var _convertToBezierForm = function(P, V) {
  3064. var i, j, k, m, n, ub, lb, w, row, column;
  3065. var c = new Array(DEGREE+1), d = new Array(DEGREE);
  3066. var cdTable = [];
  3067. var z = [ [1.0, 0.6, 0.3, 0.1], [0.4, 0.6, 0.6, 0.4], [0.1, 0.3, 0.6, 1.0] ];
  3068. for (i = 0; i <= DEGREE; i++)
  3069. c[i] = V2Sub(V[i], P);
  3070. for (i = 0; i <= DEGREE - 1; i++) {
  3071. d[i] = V2Sub(V[i+1], V[i]);
  3072. d[i] = V2ScaleII(d[i], 3.0);
  3073. }
  3074. for (row = 0; row <= DEGREE - 1; row++) {
  3075. for (column = 0; column <= DEGREE; column++) {
  3076. if (!cdTable[row]) cdTable[row] = [];
  3077. cdTable[row][column] = V2Dot(d[row], c[column]);
  3078. }
  3079. }
  3080. w = [];
  3081. for (i = 0; i <= W_DEGREE; i++) {
  3082. if (!w[i]) w[i] = [];
  3083. w[i].y = 0.0;
  3084. w[i].x = parseFloat(i) / W_DEGREE;
  3085. }
  3086. n = DEGREE;
  3087. m = DEGREE-1;
  3088. for (k = 0; k <= n + m; k++) {
  3089. lb = Math.max(0, k - m);
  3090. ub = Math.min(k, n);
  3091. for (i = lb; i <= ub; i++) {
  3092. j = k - i;
  3093. w[i+j].y += cdTable[j][i] * z[j][i];
  3094. }
  3095. }
  3096. return (w);
  3097. };
  3098. var _findRoots = function(w, degree, t, depth) {
  3099. var i;
  3100. var Left = new Array(W_DEGREE+1), Right = new Array(W_DEGREE+1);
  3101. var left_count, right_count;
  3102. var left_t = new Array(W_DEGREE+1), right_t = new Array(W_DEGREE+1);
  3103. switch (CrossingCount(w, degree)) {
  3104. case 0 : {
  3105. return 0;
  3106. }
  3107. case 1 : {
  3108. if (depth >= MAXDEPTH) {
  3109. t[0] = (w[0].x + w[W_DEGREE].x) / 2.0;
  3110. return 1;
  3111. }
  3112. if (ControlPolygonFlatEnough(w, degree)) {
  3113. t[0] = ComputeXIntercept(w, degree);
  3114. return 1;
  3115. }
  3116. break;
  3117. }
  3118. }
  3119. Bezier(w, degree, 0.5, Left, Right);
  3120. left_count = FindRoots(Left, degree, left_t, depth+1);
  3121. right_count = FindRoots(Right, degree, right_t, depth+1);
  3122. for (i = 0; i < left_count; i++) t[i] = left_t[i];
  3123. for (i = 0; i < right_count; i++) t[i+left_count] = right_t[i];
  3124. return (left_count+right_count);
  3125. };
  3126. var CrossingCount = function(V, degree) {
  3127. var n_crossings = 0;
  3128. var sign, old_sign;
  3129. var SGN = function(x) { return x == 0 ? 0 : x > 0 ? 1 :-1; };
  3130. sign = old_sign = SGN(V[0].y);
  3131. for (var i = 1; i <= degree; i++) {
  3132. sign = SGN(V[i].y);
  3133. if (sign != old_sign) n_crossings++;
  3134. old_sign = sign;
  3135. }
  3136. return n_crossings;
  3137. };
  3138. var ControlPolygonFlatEnough = function(V, degree) {
  3139. var value, error;
  3140. var intercept_1, intercept_2, left_intercept, right_intercept;
  3141. var a, b, c, det, dInv, a1, b1, c1, a2, b2, c2;
  3142. a = V[0].y - V[degree].y;
  3143. b = V[degree].x - V[0].x;
  3144. c = V[0].x * V[degree].y - V[degree].x * V[0].y;
  3145. var max_distance_above = max_distance_below = 0.0;
  3146. for (var i = 1; i < degree; i++)
  3147. {
  3148. value = a * V[i].x + b * V[i].y + c;
  3149. if (value > max_distance_above)
  3150. {
  3151. max_distance_above = value;
  3152. }
  3153. else if (value < max_distance_below)
  3154. {
  3155. max_distance_below = value;
  3156. }
  3157. }
  3158. a1 = 0.0; b1 = 1.0; c1 = 0.0; a2 = a; b2 = b;
  3159. c2 = c - max_distance_above;
  3160. det = a1 * b2 - a2 * b1;
  3161. dInv = 1.0/det;
  3162. intercept_1 = (b1 * c2 - b2 * c1) * dInv;
  3163. a2 = a; b2 = b; c2 = c - max_distance_below;
  3164. det = a1 * b2 - a2 * b1;
  3165. dInv = 1.0/det;
  3166. intercept_2 = (b1 * c2 - b2 * c1) * dInv;
  3167. left_intercept = Math.min(intercept_1, intercept_2);
  3168. right_intercept = Math.max(intercept_1, intercept_2);
  3169. error = right_intercept - left_intercept;
  3170. return (error < EPSILON)? 1 : 0;
  3171. };
  3172. var ComputeXIntercept = function(V, degree) {
  3173. var XLK, YLK, XNM, YNM, XMK, YMK, det, detInv, S, T, X, Y;
  3174. XLK = 1.0 - 0.0;
  3175. YLK = 0.0 - 0.0;
  3176. XNM = V[degree].x - V[0].x;
  3177. YNM = V[degree].y - V[0].y;
  3178. XMK = V[0].x - 0.0;
  3179. YMK = V[0].y - 0.0;
  3180. det = XNM*YLK - YNM*XLK;
  3181. detInv = 1.0/det;
  3182. S = (XNM*YMK - YNM*XMK) * detInv;
  3183. X = 0.0 + XLK * S;
  3184. return X;
  3185. };
  3186. var Bezier = function(V, degree, t, Left, Right) {
  3187. var Vtemp = new Array();
  3188. for (var j =0; j <= degree; j++) {
  3189. if (!Vtemp[0]) Vtemp[0] = [];
  3190. Vtemp[0][j] = V[j];
  3191. }
  3192. for (var i = 1; i <= degree; i++) {
  3193. for (var j =0 ; j <= degree - i; j++) {
  3194. if (!Vtemp[i]) Vtemp[i] = [];
  3195. if (!Vtemp[i][j]) Vtemp[i][j] = {};
  3196. Vtemp[i][j].x = (1.0 - t) * Vtemp[i-1][j].x + t * Vtemp[i-1][j+1].x;
  3197. Vtemp[i][j].y = (1.0 - t) * Vtemp[i-1][j].y + t * Vtemp[i-1][j+1].y;
  3198. }
  3199. }
  3200. if (Left != null)
  3201. for (j = 0; j <= degree; j++) Left[j] = Vtemp[j][0];
  3202. if (Right != null)
  3203. for (j = 0; j <= degree; j++) Right[j] = Vtemp[degree-j][j];
  3204. return (Vtemp[degree][0]);
  3205. };
  3206. var _pointOnPath = function(curve, location) {
  3207. // from http://13thparallel.com/archive/bezier-curves/
  3208. function B1(t) { return t*t*t };
  3209. function B2(t) { return 3*t*t*(1-t) };
  3210. function B3(t) { return 3*t*(1-t)*(1-t) };
  3211. function B4(t) { return (1-t)*(1-t)*(1-t) };
  3212. var x = curve[0].x*B1(location) + curve[1].x * B2(location) + curve[2].x * B3(location) + curve[3].x * B4(location);
  3213. var y = curve[0].y*B1(location) + curve[1].y * B2(location) + curve[2].y * B3(location) + curve[3].y * B4(location);
  3214. return [x,y];
  3215. };
  3216. var _quadraticPointOnPath = function(curve, location) {
  3217. function B1(t) { return t*t; };
  3218. function B2(t) { return 2*t*(1-t); };
  3219. function B3(t) { return (1-t)*(1-t); };
  3220. var x = curve[0].x*B1(location) + curve[1].x*B2(location) + curve[2].x*B3(location);
  3221. var y = curve[0].y*B1(location) + curve[1].y*B2(location) + curve[2].y*B3(location);
  3222. return [x,y];
  3223. };
  3224. /**
  3225. * finds the point that is 'distance' along the path from 'location'. this method returns both the x,y location of the point and also
  3226. * its 'location' (proportion of travel along the path).
  3227. */
  3228. var _pointAlongPath = function(curve, location, distance) {
  3229. var _dist = function(p1,p2) { return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2)); };
  3230. var prev = _pointOnPath(curve, location), tally = 0, curLoc = location, direction = distance > 0 ? 1 : -1, cur = null;
  3231. while (tally < Math.abs(distance)) {
  3232. curLoc += (0.005 * direction);
  3233. cur = _pointOnPath(curve, curLoc);
  3234. tally += _dist(cur, prev);
  3235. prev = cur;
  3236. }
  3237. return {point:cur, location:curLoc};
  3238. };
  3239. var _pointAlongPathFrom = function(curve, location, distance) {
  3240. return _pointAlongPath(curve, location, distance).point;
  3241. };
  3242. /**
  3243. * returns the gradient of the connector at the given location, which is a decimal between 0 and 1 inclusive.
  3244. *
  3245. * thanks // http://bimixual.org/AnimationLibrary/beziertangents.html
  3246. */
  3247. var _gradientAtPoint = function(curve, location) {
  3248. var p1 = _pointOnPath(curve, location);
  3249. var p2 = _quadraticPointOnPath(curve, location);
  3250. var dy = p2[1] - p1[1], dx = p2[0] - p1[0];
  3251. return Math.atan(dy / dx);
  3252. };
  3253. /**
  3254. * calculates a line that is 'length' pixels long, perpendicular to, and centered on, the path at 'distance' pixels from the given location.
  3255. */
  3256. var _perpendicularToPathAt = function(curve, location, distance, length) {
  3257. var p = _pointAlongPath(curve, location, distance);
  3258. var m = _gradientAtPoint(curve, p.location);
  3259. var _theta2 = Math.atan(-1 / m);
  3260. var y = length / 2 * Math.sin(_theta2);
  3261. var x = length / 2 * Math.cos(_theta2);
  3262. return [[p.point[0] + x, p.point[1] + y], [p.point[0] - x, p.point[1] - y]];
  3263. };
  3264. var jsBezier = window.jsBezier = {
  3265. distanceFromCurve : _distanceFromCurve,
  3266. gradientAtPoint : _gradientAtPoint,
  3267. nearestPointOnCurve : _nearestPointOnCurve,
  3268. pointOnCurve : _pointOnPath,
  3269. pointAlongCurveFrom : _pointAlongPathFrom,
  3270. perpendicularToCurveAt : _perpendicularToPathAt,
  3271. quadraticPointOnCurve : _quadraticPointOnPath //TODO fold the two pointOnPath functions into one; it can detect what it was given.
  3272. };
  3273. })();