mootools.jsPlumb-1.2.4-all.js 130 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603
  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. * mootools.jsPlumb 1.2.4-RC1
  2773. *
  2774. * MooTools 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 mootools.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. * overrides the FX class to inject 'step' functionality, which MooTools does not
  2788. * offer, and which makes me sad. they don't seem keen to add it, either, despite
  2789. * the fact that it could be useful:
  2790. *
  2791. * https://mootools.lighthouseapp.com/projects/2706/tickets/668
  2792. *
  2793. */
  2794. var jsPlumbMorph = new Class({
  2795. Extends:Fx.Morph,
  2796. onStep : null,
  2797. initialize : function(el, options) {
  2798. this.parent(el, options);
  2799. if (options['onStep']) {
  2800. this.onStep = options['onStep'];
  2801. }
  2802. },
  2803. step : function() {
  2804. this.parent();
  2805. if (this.onStep) {
  2806. try { this.onStep(); }
  2807. catch(e) { }
  2808. }
  2809. }
  2810. });
  2811. var _droppables = {};
  2812. var _droppableOptions = {};
  2813. var _draggablesByScope = {};
  2814. var _draggablesById = {};
  2815. /*
  2816. *
  2817. */
  2818. var _executeDroppableOption = function(el, dr, event) {
  2819. if (dr) {
  2820. var id = dr.get("id");
  2821. if (id) {
  2822. var options = _droppableOptions[id];
  2823. if (options) {
  2824. if (options[event]) {
  2825. options[event](el, dr);
  2826. }
  2827. }
  2828. }
  2829. }
  2830. };
  2831. var _checkHover = function(el, entering) {
  2832. if (el) {
  2833. var id = el.get("id");
  2834. if (id) {
  2835. var options = _droppableOptions[id];
  2836. if (options) {
  2837. if (options['hoverClass']) {
  2838. if (entering) el.addClass(options['hoverClass']);
  2839. else el.removeClass(options['hoverClass']);
  2840. }
  2841. }
  2842. }
  2843. }
  2844. };
  2845. /**
  2846. * adds the given value to the given list, with the given scope. creates the scoped list
  2847. * if necessary.
  2848. * used by initDraggable and initDroppable.
  2849. */
  2850. var _add = function(list, scope, value) {
  2851. var l = list[scope];
  2852. if (!l) {
  2853. l = [];
  2854. list[scope] = l;
  2855. }
  2856. l.push(value);
  2857. };
  2858. jsPlumb.CurrentLibrary = {
  2859. dragEvents : {
  2860. 'start':'onStart', 'stop':'onComplete', 'drag':'onDrag', 'step':'onStep',
  2861. 'over':'onEnter', 'out':'onLeave','drop':'onDrop', 'complete':'onComplete'
  2862. },
  2863. appendElement : function(child, parent) {
  2864. jsPlumb.CurrentLibrary.getElementObject(parent).grab(child);
  2865. },
  2866. bind : function(el, event, callback) {
  2867. el = jsPlumb.CurrentLibrary.getElementObject(el);
  2868. el.addEvent(event, callback);
  2869. },
  2870. /*
  2871. * wrapper around the library's 'extend' functionality (which it hopefully has.
  2872. * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you
  2873. * instead. it's not like its hard.
  2874. */
  2875. extend : function(o1, o2) {
  2876. return $extend(o1, o2);
  2877. },
  2878. /*
  2879. * gets an "element object" from the given input. this means an object that is used by the
  2880. * underlying library on which jsPlumb is running. 'el' may already be one of these objects,
  2881. * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup
  2882. * function is used to find the element, using the given String as the element's id.
  2883. */
  2884. getElementObject : function(el) {
  2885. return $(el);
  2886. },
  2887. /*
  2888. gets the offset for the element object. this should return a js object like this:
  2889. { left:xxx, top: xxx}
  2890. */
  2891. getOffset : function(el) {
  2892. var p = el.getPosition();
  2893. return { left:p.x, top:p.y };
  2894. },
  2895. setOffset : function(el, o) {
  2896. jsPlumb.CurrentLibrary.getElementObject(el).setPosition({x:o.left, y:o.top});
  2897. },
  2898. getSize : function(el) {
  2899. var s = el.getSize();
  2900. return [s.x, s.y];
  2901. },
  2902. /**
  2903. * gets the named attribute from the given element object.
  2904. */
  2905. getAttribute : function(el, attName) {
  2906. return el.get(attName);
  2907. },
  2908. /**
  2909. * sets the named attribute on the given element object.
  2910. */
  2911. setAttribute : function(el, attName, attValue) {
  2912. el.set(attName, attValue);
  2913. },
  2914. /**
  2915. * adds the given class to the element object.
  2916. */
  2917. addClass : function(el, clazz) {
  2918. el.addClass(clazz);
  2919. },
  2920. /**
  2921. * removes the given class from the element object.
  2922. */
  2923. removeClass : function(el, clazz) {
  2924. el.removeClass(clazz);
  2925. },
  2926. initDraggable : function(el, options) {
  2927. var id = jsPlumb.getId(el);
  2928. var drag = _draggablesById[id];
  2929. if (!drag) {
  2930. var originalZIndex = 0, originalCursor = null;
  2931. var dragZIndex = jsPlumb.Defaults.DragOptions.zIndex || 2000;
  2932. options['onStart'] = jsPlumb.wrap(options['onStart'], function()
  2933. {
  2934. originalZIndex = this.element.getStyle('z-index');
  2935. this.element.setStyle('z-index', dragZIndex);
  2936. if (jsPlumb.Defaults.DragOptions.cursor) {
  2937. originalCursor = this.element.getStyle('cursor');
  2938. this.element.setStyle('cursor', jsPlumb.Defaults.DragOptions.cursor);
  2939. }
  2940. });
  2941. options['onComplete'] = jsPlumb.wrap(options['onComplete'], function()
  2942. {
  2943. this.element.setStyle('z-index', originalZIndex);
  2944. if (originalCursor) {
  2945. this.element.setStyle('cursor', originalCursor);
  2946. }
  2947. });
  2948. // DROPPABLES:
  2949. var scope = options['scope'] || jsPlumb.Defaults.Scope;
  2950. var filterFunc = function(entry) {
  2951. return entry.get("id") != el.get("id");
  2952. };
  2953. var droppables = _droppables[scope] ? _droppables[scope].filter(filterFunc) : [];
  2954. options['droppables'] = droppables;
  2955. options['onLeave'] = jsPlumb.wrap(options['onLeave'], function(el, dr) {
  2956. if (dr) {
  2957. _checkHover(dr, false);
  2958. _executeDroppableOption(el, dr, 'onLeave');
  2959. }
  2960. });
  2961. options['onEnter'] = jsPlumb.wrap(options['onEnter'], function(el, dr) {
  2962. if (dr) {
  2963. _checkHover(dr, true);
  2964. _executeDroppableOption(el, dr, 'onEnter');
  2965. }
  2966. });
  2967. options['onDrop'] = function(el, dr) {
  2968. if (dr) {
  2969. _checkHover(dr, false);
  2970. _executeDroppableOption(el, dr, 'onDrop');
  2971. }
  2972. };
  2973. drag = new Drag.Move(el, options);
  2974. _add(_draggablesByScope, scope, drag);
  2975. _add(_draggablesById, el.get("id"), drag);
  2976. // test for disabled.
  2977. if (options.disabled) drag.detach();
  2978. }
  2979. return drag;
  2980. },
  2981. isDragSupported : function(el, options) {
  2982. return typeof Drag != 'undefined' ;
  2983. },
  2984. setDraggable : function(el, draggable) {
  2985. var draggables = _draggablesById[el.get("id")];
  2986. if (draggables) {
  2987. draggables.each(function(d) {
  2988. if (draggable) d.attach(); else d.detach();
  2989. });
  2990. }
  2991. },
  2992. initDroppable : function(el, options) {
  2993. var scope = options['scope'] || jsPlumb.Defaults.Scope;
  2994. _add(_droppables, scope, el);
  2995. var id = jsPlumb.getId(el);
  2996. _droppableOptions[id] = options;
  2997. var filterFunc = function(entry) {
  2998. return entry.element != el;
  2999. };
  3000. var draggables = _draggablesByScope[scope] ? _draggablesByScope[scope].filter(filterFunc) : [];
  3001. for (var i = 0; i < draggables.length; i++) {
  3002. draggables[i].droppables.push(el);
  3003. }
  3004. },
  3005. /*
  3006. * you need Drag.Move imported to make drop work.
  3007. */
  3008. isDropSupported : function(el, options) {
  3009. if (typeof Drag != undefined)
  3010. return typeof Drag.Move != undefined;
  3011. return false;
  3012. },
  3013. animate : function(el, properties, options) {
  3014. var m = new jsPlumbMorph(el, options);
  3015. m.start(properties);
  3016. },
  3017. /*
  3018. * takes the args passed to an event function and returns you an object that gives the
  3019. * position of the object being moved, as a js object with the same params as the result of
  3020. * getOffset, ie: { left: xxx, top: xxx }.
  3021. */
  3022. getUIPosition : function(eventArgs) {
  3023. var ui = eventArgs[0];
  3024. return { left: ui.offsetLeft, top: ui.offsetTop };
  3025. },
  3026. getDragObject : function(eventArgs) {
  3027. return eventArgs[0];
  3028. },
  3029. removeElement : function(element, parent) {
  3030. jsPlumb.CurrentLibrary.getElementObject(element).dispose(); // ??
  3031. },
  3032. getScrollLeft : function(el) {
  3033. return null;
  3034. },
  3035. getScrollTop : function(el) {
  3036. return null;
  3037. }
  3038. };
  3039. })();
  3040. /**
  3041. * a set of bezier curve functions, used by jsPlumb, and perhaps useful for other people.
  3042. *
  3043. * functions are all in the 'jsBezier' namespace. here's the list:
  3044. *
  3045. * distanceFromCurve(point, curve)
  3046. * gradientAtPoint(curve, location)
  3047. * nearestPointOnCurve(point, curve) ??
  3048. * pointOnCurve(curve, location)
  3049. * pointAlongCurveFrom
  3050. * perpendicularToCurveAt
  3051. * quadraticPointOnCurve
  3052. */
  3053. (function() {
  3054. var MAXDEPTH = 64, EPSILON = Math.pow(2.0,-MAXDEPTH-1), DEGREE = 3, W_DEGREE = 5;
  3055. var V2Sub = function(from, p) { return {x:from.x - p.x, y:from.y - p.y }; };
  3056. var V2Dot = function(v1, v2) { return (v1.x * v2.x) + (v1.y * v2.y); };
  3057. var V2SquaredLength = function(v) { return (v.x * v.x) + (v.y * v.y); };
  3058. var V2ScaleII = function(v, s) { return {x:v.x * s, y:v.y * s }; };
  3059. /**
  3060. * Calculates the distance that the point P is from the curve V. i think ;)
  3061. * i need to verify that.
  3062. */
  3063. var _distanceFromCurve = function(P, V) {
  3064. var w, n_solutions, t;
  3065. var t_candidate = new Array(W_DEGREE);
  3066. w = _convertToBezierForm(P, V);
  3067. n_solutions = _findRoots(w, W_DEGREE, t_candidate, 0);
  3068. var dist, new_dist, p, v, i;
  3069. v = V2Sub(P, V[0]);
  3070. dist = V2SquaredLength(v);
  3071. t = 0.0;
  3072. for (i = 0; i < n_solutions; i++) {
  3073. p = Bezier(V, DEGREE, t_candidate[i],
  3074. null, null);
  3075. v = V2Sub(P, p);
  3076. new_dist = V2SquaredLength(v);
  3077. if (new_dist < dist) {
  3078. dist = new_dist;
  3079. t = t_candidate[i];
  3080. }
  3081. }
  3082. v = V2Sub(P, V[DEGREE]);
  3083. new_dist = V2SquaredLength(v);
  3084. if (new_dist < dist) {
  3085. dist = new_dist;
  3086. t = 1.0;
  3087. }
  3088. return {t:t,d:dist};
  3089. };
  3090. var _nearestPointOnCurve = function(P, V) {
  3091. var td = _distanceFromCurve(P, V);
  3092. return Bezier(V, DEGREE, td.t, null, null);
  3093. };
  3094. var _convertToBezierForm = function(P, V) {
  3095. var i, j, k, m, n, ub, lb, w, row, column;
  3096. var c = new Array(DEGREE+1), d = new Array(DEGREE);
  3097. var cdTable = [];
  3098. 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] ];
  3099. for (i = 0; i <= DEGREE; i++)
  3100. c[i] = V2Sub(V[i], P);
  3101. for (i = 0; i <= DEGREE - 1; i++) {
  3102. d[i] = V2Sub(V[i+1], V[i]);
  3103. d[i] = V2ScaleII(d[i], 3.0);
  3104. }
  3105. for (row = 0; row <= DEGREE - 1; row++) {
  3106. for (column = 0; column <= DEGREE; column++) {
  3107. if (!cdTable[row]) cdTable[row] = [];
  3108. cdTable[row][column] = V2Dot(d[row], c[column]);
  3109. }
  3110. }
  3111. w = [];
  3112. for (i = 0; i <= W_DEGREE; i++) {
  3113. if (!w[i]) w[i] = [];
  3114. w[i].y = 0.0;
  3115. w[i].x = parseFloat(i) / W_DEGREE;
  3116. }
  3117. n = DEGREE;
  3118. m = DEGREE-1;
  3119. for (k = 0; k <= n + m; k++) {
  3120. lb = Math.max(0, k - m);
  3121. ub = Math.min(k, n);
  3122. for (i = lb; i <= ub; i++) {
  3123. j = k - i;
  3124. w[i+j].y += cdTable[j][i] * z[j][i];
  3125. }
  3126. }
  3127. return (w);
  3128. };
  3129. var _findRoots = function(w, degree, t, depth) {
  3130. var i;
  3131. var Left = new Array(W_DEGREE+1), Right = new Array(W_DEGREE+1);
  3132. var left_count, right_count;
  3133. var left_t = new Array(W_DEGREE+1), right_t = new Array(W_DEGREE+1);
  3134. switch (CrossingCount(w, degree)) {
  3135. case 0 : {
  3136. return 0;
  3137. }
  3138. case 1 : {
  3139. if (depth >= MAXDEPTH) {
  3140. t[0] = (w[0].x + w[W_DEGREE].x) / 2.0;
  3141. return 1;
  3142. }
  3143. if (ControlPolygonFlatEnough(w, degree)) {
  3144. t[0] = ComputeXIntercept(w, degree);
  3145. return 1;
  3146. }
  3147. break;
  3148. }
  3149. }
  3150. Bezier(w, degree, 0.5, Left, Right);
  3151. left_count = FindRoots(Left, degree, left_t, depth+1);
  3152. right_count = FindRoots(Right, degree, right_t, depth+1);
  3153. for (i = 0; i < left_count; i++) t[i] = left_t[i];
  3154. for (i = 0; i < right_count; i++) t[i+left_count] = right_t[i];
  3155. return (left_count+right_count);
  3156. };
  3157. var CrossingCount = function(V, degree) {
  3158. var n_crossings = 0;
  3159. var sign, old_sign;
  3160. var SGN = function(x) { return x == 0 ? 0 : x > 0 ? 1 :-1; };
  3161. sign = old_sign = SGN(V[0].y);
  3162. for (var i = 1; i <= degree; i++) {
  3163. sign = SGN(V[i].y);
  3164. if (sign != old_sign) n_crossings++;
  3165. old_sign = sign;
  3166. }
  3167. return n_crossings;
  3168. };
  3169. var ControlPolygonFlatEnough = function(V, degree) {
  3170. var value, error;
  3171. var intercept_1, intercept_2, left_intercept, right_intercept;
  3172. var a, b, c, det, dInv, a1, b1, c1, a2, b2, c2;
  3173. a = V[0].y - V[degree].y;
  3174. b = V[degree].x - V[0].x;
  3175. c = V[0].x * V[degree].y - V[degree].x * V[0].y;
  3176. var max_distance_above = max_distance_below = 0.0;
  3177. for (var i = 1; i < degree; i++)
  3178. {
  3179. value = a * V[i].x + b * V[i].y + c;
  3180. if (value > max_distance_above)
  3181. {
  3182. max_distance_above = value;
  3183. }
  3184. else if (value < max_distance_below)
  3185. {
  3186. max_distance_below = value;
  3187. }
  3188. }
  3189. a1 = 0.0; b1 = 1.0; c1 = 0.0; a2 = a; b2 = b;
  3190. c2 = c - max_distance_above;
  3191. det = a1 * b2 - a2 * b1;
  3192. dInv = 1.0/det;
  3193. intercept_1 = (b1 * c2 - b2 * c1) * dInv;
  3194. a2 = a; b2 = b; c2 = c - max_distance_below;
  3195. det = a1 * b2 - a2 * b1;
  3196. dInv = 1.0/det;
  3197. intercept_2 = (b1 * c2 - b2 * c1) * dInv;
  3198. left_intercept = Math.min(intercept_1, intercept_2);
  3199. right_intercept = Math.max(intercept_1, intercept_2);
  3200. error = right_intercept - left_intercept;
  3201. return (error < EPSILON)? 1 : 0;
  3202. };
  3203. var ComputeXIntercept = function(V, degree) {
  3204. var XLK, YLK, XNM, YNM, XMK, YMK, det, detInv, S, T, X, Y;
  3205. XLK = 1.0 - 0.0;
  3206. YLK = 0.0 - 0.0;
  3207. XNM = V[degree].x - V[0].x;
  3208. YNM = V[degree].y - V[0].y;
  3209. XMK = V[0].x - 0.0;
  3210. YMK = V[0].y - 0.0;
  3211. det = XNM*YLK - YNM*XLK;
  3212. detInv = 1.0/det;
  3213. S = (XNM*YMK - YNM*XMK) * detInv;
  3214. X = 0.0 + XLK * S;
  3215. return X;
  3216. };
  3217. var Bezier = function(V, degree, t, Left, Right) {
  3218. var Vtemp = new Array();
  3219. for (var j =0; j <= degree; j++) {
  3220. if (!Vtemp[0]) Vtemp[0] = [];
  3221. Vtemp[0][j] = V[j];
  3222. }
  3223. for (var i = 1; i <= degree; i++) {
  3224. for (var j =0 ; j <= degree - i; j++) {
  3225. if (!Vtemp[i]) Vtemp[i] = [];
  3226. if (!Vtemp[i][j]) Vtemp[i][j] = {};
  3227. Vtemp[i][j].x = (1.0 - t) * Vtemp[i-1][j].x + t * Vtemp[i-1][j+1].x;
  3228. Vtemp[i][j].y = (1.0 - t) * Vtemp[i-1][j].y + t * Vtemp[i-1][j+1].y;
  3229. }
  3230. }
  3231. if (Left != null)
  3232. for (j = 0; j <= degree; j++) Left[j] = Vtemp[j][0];
  3233. if (Right != null)
  3234. for (j = 0; j <= degree; j++) Right[j] = Vtemp[degree-j][j];
  3235. return (Vtemp[degree][0]);
  3236. };
  3237. var _pointOnPath = function(curve, location) {
  3238. // from http://13thparallel.com/archive/bezier-curves/
  3239. function B1(t) { return t*t*t };
  3240. function B2(t) { return 3*t*t*(1-t) };
  3241. function B3(t) { return 3*t*(1-t)*(1-t) };
  3242. function B4(t) { return (1-t)*(1-t)*(1-t) };
  3243. var x = curve[0].x*B1(location) + curve[1].x * B2(location) + curve[2].x * B3(location) + curve[3].x * B4(location);
  3244. var y = curve[0].y*B1(location) + curve[1].y * B2(location) + curve[2].y * B3(location) + curve[3].y * B4(location);
  3245. return [x,y];
  3246. };
  3247. var _quadraticPointOnPath = function(curve, location) {
  3248. function B1(t) { return t*t; };
  3249. function B2(t) { return 2*t*(1-t); };
  3250. function B3(t) { return (1-t)*(1-t); };
  3251. var x = curve[0].x*B1(location) + curve[1].x*B2(location) + curve[2].x*B3(location);
  3252. var y = curve[0].y*B1(location) + curve[1].y*B2(location) + curve[2].y*B3(location);
  3253. return [x,y];
  3254. };
  3255. /**
  3256. * finds the point that is 'distance' along the path from 'location'. this method returns both the x,y location of the point and also
  3257. * its 'location' (proportion of travel along the path).
  3258. */
  3259. var _pointAlongPath = function(curve, location, distance) {
  3260. var _dist = function(p1,p2) { return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2)); };
  3261. var prev = _pointOnPath(curve, location), tally = 0, curLoc = location, direction = distance > 0 ? 1 : -1, cur = null;
  3262. while (tally < Math.abs(distance)) {
  3263. curLoc += (0.005 * direction);
  3264. cur = _pointOnPath(curve, curLoc);
  3265. tally += _dist(cur, prev);
  3266. prev = cur;
  3267. }
  3268. return {point:cur, location:curLoc};
  3269. };
  3270. var _pointAlongPathFrom = function(curve, location, distance) {
  3271. return _pointAlongPath(curve, location, distance).point;
  3272. };
  3273. /**
  3274. * returns the gradient of the connector at the given location, which is a decimal between 0 and 1 inclusive.
  3275. *
  3276. * thanks // http://bimixual.org/AnimationLibrary/beziertangents.html
  3277. */
  3278. var _gradientAtPoint = function(curve, location) {
  3279. var p1 = _pointOnPath(curve, location);
  3280. var p2 = _quadraticPointOnPath(curve, location);
  3281. var dy = p2[1] - p1[1], dx = p2[0] - p1[0];
  3282. return Math.atan(dy / dx);
  3283. };
  3284. /**
  3285. * calculates a line that is 'length' pixels long, perpendicular to, and centered on, the path at 'distance' pixels from the given location.
  3286. */
  3287. var _perpendicularToPathAt = function(curve, location, distance, length) {
  3288. var p = _pointAlongPath(curve, location, distance);
  3289. var m = _gradientAtPoint(curve, p.location);
  3290. var _theta2 = Math.atan(-1 / m);
  3291. var y = length / 2 * Math.sin(_theta2);
  3292. var x = length / 2 * Math.cos(_theta2);
  3293. return [[p.point[0] + x, p.point[1] + y], [p.point[0] - x, p.point[1] - y]];
  3294. };
  3295. var jsBezier = window.jsBezier = {
  3296. distanceFromCurve : _distanceFromCurve,
  3297. gradientAtPoint : _gradientAtPoint,
  3298. nearestPointOnCurve : _nearestPointOnCurve,
  3299. pointOnCurve : _pointOnPath,
  3300. pointAlongCurveFrom : _pointAlongPathFrom,
  3301. perpendicularToCurveAt : _perpendicularToPathAt,
  3302. quadraticPointOnCurve : _quadraticPointOnPath //TODO fold the two pointOnPath functions into one; it can detect what it was given.
  3303. };
  3304. })();