1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668 |
- /*
- * jsPlumb
- *
- * Title:jsPlumb 1.3.7
- *
- * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
- * elements, or VML.
- *
- * This file contains the jsPlumb core code.
- *
- * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com)
- *
- * http://jsplumb.org
- * http://github.com/sporritt/jsplumb
- * http://code.google.com/p/jsplumb
- *
- * Dual licensed under the MIT and GPL2 licenses.
- */
- ;(function() {
-
- /**
- * Class:jsPlumb
- * The jsPlumb engine, registered as a static object in the window. This object contains all of the methods you will use to
- * create and maintain Connections and Endpoints.
- */
-
- var canvasAvailable = !!document.createElement('canvas').getContext,
- svgAvailable = !!window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1"),
- // TODO what is a good test for VML availability? aside from just assuming its there because nothing else is.
- vmlAvailable = !(canvasAvailable | svgAvailable);
-
- var _findWithFunction = function(a, f) {
- if (a)
- for (var i = 0; i < a.length; i++) if (f(a[i])) return i;
- return -1;
- },
- _indexOf = function(l, v) {
- return _findWithFunction(l, function(_v) { return _v == v; });
- },
- _removeWithFunction = function(a, f) {
- var idx = _findWithFunction(a, f);
- if (idx > -1) a.splice(idx, 1);
- return idx != -1;
- },
- _remove = function(l, v) {
- var idx = _indexOf(l, v);
- if (idx > -1) l.splice(idx, 1);
- return idx != -1;
- },
- // TODO support insert index
- _addWithFunction = function(list, item, hashFunction) {
- if (_findWithFunction(list, hashFunction) == -1) list.push(item);
- },
- _addToList = function(map, key, value) {
- var l = map[key];
- if (l == null) {
- l = [], map[key] = l;
- }
- l.push(value);
- return l;
- };
-
- // for those browsers that dont have it. they still don't have it! but at least they won't crash.
- if (!window.console)
- window.console = { time:function(){}, timeEnd:function(){}, group:function(){}, groupEnd:function(){}, log:function(){} };
-
- /**
- * helper method to add an item to a list, creating the list if it does
- * not yet exist.
- */
- var _connectionBeingDragged = null,
- _getAttribute = function(el, attName) { return jsPlumb.CurrentLibrary.getAttribute(_getElementObject(el), attName); },
- _setAttribute = function(el, attName, attValue) { jsPlumb.CurrentLibrary.setAttribute(_getElementObject(el), attName, attValue); },
- _addClass = function(el, clazz) { jsPlumb.CurrentLibrary.addClass(_getElementObject(el), clazz); },
- _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); },
- _removeClass = function(el, clazz) { jsPlumb.CurrentLibrary.removeClass(_getElementObject(el), clazz); },
- _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); },
- _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); },
- _getSize = function(el) { return jsPlumb.CurrentLibrary.getSize(_getElementObject(el)); },
- _logEnabled = true,
- _log = function() {
- if (_logEnabled && typeof console != "undefined") {
- try {
- var msg = arguments[arguments.length - 1];
- console.log(msg);
- }
- catch (e) {}
- }
- },
- _group = function(g) { if (_logEnabled && typeof console != "undefined") console.group(g); },
- _groupEnd = function(g) { if (_logEnabled && typeof console != "undefined") console.groupEnd(g); },
- _time = function(t) { if (_logEnabled && typeof console != "undefined") console.time(t); },
- _timeEnd = function(t) { if (_logEnabled && typeof console != "undefined") console.timeEnd(t); };
-
- /**
- * EventGenerator
- * Superclass for objects that generate events - jsPlumb extends this, as does jsPlumbUIComponent, which all the UI elements extend.
- */
- EventGenerator = function() {
- var _listeners = {}, self = this;
-
- // this is a list of events that should re-throw any errors that occur during their dispatch. as of 1.3.0 this is private to
- // jsPlumb, but it seems feasible that people might want to manipulate this list. the thinking is that we don't want event
- // listeners to bring down jsPlumb - or do we. i can't make up my mind about this, but i know i want to hear about it if the "ready"
- // event fails, because then my page has most likely not initialised. so i have this halfway-house solution. it will be interesting
- // to hear what other people think.
- var eventsToDieOn = [ "ready" ];
-
- /*
- * Binds a listener to an event.
- *
- * Parameters:
- * event - name of the event to bind to.
- * listener - function to execute.
- */
- this.bind = function(event, listener) {
- _addToList(_listeners, event, listener);
- };
- /*
- * Fires an update for the given event.
- *
- * Parameters:
- * event - event to fire
- * value - value to pass to the event listener(s).
- * originalEvent - the original event from the browser
- */
- this.fire = function(event, value, originalEvent) {
- if (_listeners[event]) {
- for ( var i = 0; i < _listeners[event].length; i++) {
- // doing it this way rather than catching and then possibly re-throwing means that an error propagated by this
- // method will have the whole call stack available in the debugger.
- //if (_findIndex(eventsToDieOn, event) != -1)
- if (_findWithFunction(eventsToDieOn, function(e) { return e === event}) != -1)
- _listeners[event][i](value, originalEvent);
- else {
- // for events we don't want to die on, catch and log.
- try {
- _listeners[event][i](value, originalEvent);
- } catch (e) {
- _log("jsPlumb: fire failed for event " + event + " : " + e);
- }
- }
- }
- }
- };
- /*
- * Clears either all listeners, or listeners for some specific event.
- *
- * Parameters:
- * event - optional. constrains the clear to just listeners for this event.
- */
- this.clearListeners = function(event) {
- if (event)
- delete _listeners[event];
- else {
- delete _listeners;
- _listeners = {};
- }
- };
-
- this.getListener = function(forEvent) {
- return _listeners[forEvent];
- };
- },
-
- /**
- * creates a timestamp, using milliseconds since 1970, but as a string.
- */
- _timestamp = function() { return "" + (new Date()).getTime(); },
-
- /*
- * Class:jsPlumbUIComponent
- * Abstract superclass for UI components Endpoint and Connection. Provides the abstraction of paintStyle/hoverPaintStyle,
- * and also extends EventGenerator to provide the bind and fire methods.
- */
- jsPlumbUIComponent = function(params) {
- var self = this, a = arguments, _hover = false, parameters = params.parameters || {}, idPrefix = self.idPrefix,
- id = idPrefix + (new Date()).getTime();
- self._jsPlumb = params["_jsPlumb"];
- self.getId = function() { return id; };
- self.tooltip = params.tooltip;
- self.hoverClass = params.hoverClass;
-
- // all components can generate events
- EventGenerator.apply(this);
- // all components get this clone function.
- // TODO issue 116 showed a problem with this - it seems 'a' that is in
- // the clone function's scope is shared by all invocations of it, the classic
- // JS closure problem. for now, jsPlumb does a version of this inline where
- // it used to call clone. but it would be nice to find some time to look
- // further at this.
- this.clone = function() {
- var o = new Object();
- self.constructor.apply(o, a);
- return o;
- };
-
- this.getParameter = function(name) { return parameters[name]; },
- this.getParameters = function() { return parameters; },
- this.setParameter = function(name, value) { parameters[name] = value; },
- this.setParameters = function(p) { parameters = p; },
- this.overlayPlacements = [],
- this.paintStyle = null,
- this.hoverPaintStyle = null;
-
- // user can supply a beforeDetach callback, which will be executed before a detach
- // is performed; returning false prevents the detach.
- var beforeDetach = params.beforeDetach;
- this.isDetachAllowed = function(connection) {
- var r = self._jsPlumb.checkCondition("beforeDetach", connection );
- if (beforeDetach) {
- try {
- r = beforeDetach(connection);
- }
- catch (e) { _log("jsPlumb: beforeDetach callback failed", e); }
- }
- return r;
- };
-
- // user can supply a beforeDrop callback, which will be executed before a dropped
- // connection is confirmed. user can return false to reject connection.
- var beforeDrop = params.beforeDrop;
- this.isDropAllowed = function(sourceId, targetId, scope) {
- var r = self._jsPlumb.checkCondition("beforeDrop", { sourceId:sourceId, targetId:targetId, scope:scope });
- if (beforeDrop) {
- try {
- r = beforeDrop({ sourceId:sourceId, targetId:targetId, scope:scope });
- }
- catch (e) { _log("jsPlumb: beforeDrop callback failed", e); }
- }
- return r;
- };
-
- // helper method to update the hover style whenever it, or paintStyle, changes.
- // we use paintStyle as the foundation and merge hoverPaintStyle over the
- // top.
- var _updateHoverStyle = function() {
- if (self.paintStyle && self.hoverPaintStyle) {
- var mergedHoverStyle = {};
- jsPlumb.extend(mergedHoverStyle, self.paintStyle);
- jsPlumb.extend(mergedHoverStyle, self.hoverPaintStyle);
- delete self["hoverPaintStyle"];
- // we want the fillStyle of paintStyle to override a gradient, if possible.
- if (mergedHoverStyle.gradient && self.paintStyle.fillStyle)
- delete mergedHoverStyle["gradient"];
- self.hoverPaintStyle = mergedHoverStyle;
- }
- };
-
- /*
- * Sets the paint style and then repaints the element.
- *
- * Parameters:
- * style - Style to use.
- */
- this.setPaintStyle = function(style, doNotRepaint) {
- self.paintStyle = style;
- self.paintStyleInUse = self.paintStyle;
- _updateHoverStyle();
- if (!doNotRepaint) self.repaint();
- };
-
- /*
- * Sets the paint style to use when the mouse is hovering over the element. This is null by default.
- * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace
- * it. This is because people will most likely want to change just one thing when hovering, say the
- * color for example, but leave the rest of the appearance the same.
- *
- * Parameters:
- * style - Style to use when the mouse is hovering.
- * doNotRepaint - if true, the component will not be repainted. useful when setting things up initially.
- */
- this.setHoverPaintStyle = function(style, doNotRepaint) {
- self.hoverPaintStyle = style;
- _updateHoverStyle();
- if (!doNotRepaint) self.repaint();
- };
-
- /*
- * sets/unsets the hover state of this element.
- *
- * Parameters:
- * hover - hover state boolean
- * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops.
- */
- this.setHover = function(hover, ignoreAttachedElements, timestamp) {
- // while dragging, we ignore these events. this keeps the UI from flashing and
- // swishing and whatevering.
- if (!self._jsPlumb.currentlyDragging && !self._jsPlumb.isHoverSuspended()) {
-
- _hover = hover;
- if (self.hoverClass != null && self.canvas != null) {
- if (hover)
- jpcl.addClass(self.canvas, self.hoverClass);
- else
- jpcl.removeClass(self.canvas, self.hoverClass);
- }
- if (self.hoverPaintStyle != null) {
- self.paintStyleInUse = hover ? self.hoverPaintStyle : self.paintStyle;
- timestamp = timestamp || _timestamp();
- self.repaint({timestamp:timestamp, recalc:false});
- }
- // get the list of other affected elements, if supported by this component.
- // for a connection, its the endpoints. for an endpoint, its the connections! surprise.
- if (self.getAttachedElements && !ignoreAttachedElements)
- _updateAttachedElements(hover, _timestamp(), self);
- }
- };
-
- this.isHover = function() { return _hover; };
- var jpcl = jsPlumb.CurrentLibrary,
- events = [ "click", "dblclick", "mouseenter", "mouseout", "mousemove", "mousedown", "mouseup", "contextmenu" ],
- eventFilters = { "mouseout":"mouseexit" },
- bindOne = function(o, c, evt) {
- var filteredEvent = eventFilters[evt] || evt;
- jpcl.bind(o, evt, function(ee) {
- c.fire(filteredEvent, c, ee);
- });
- },
- unbindOne = function(o, evt) {
- var filteredEvent = eventFilters[evt] || evt;
- jpcl.unbind(o, evt);
- };
-
- this.attachListeners = function(o, c) {
- for (var i = 0; i < events.length; i++) {
- bindOne(o, c, events[i]);
- }
- };
-
- var _updateAttachedElements = function(state, timestamp, sourceElement) {
- var affectedElements = self.getAttachedElements(); // implemented in subclasses
- if (affectedElements) {
- for (var i = 0; i < affectedElements.length; i++) {
- if (!sourceElement || sourceElement != affectedElements[i])
- affectedElements[i].setHover(state, true, timestamp); // tell the attached elements not to inform their own attached elements.
- }
- }
- };
-
- this.reattachListenersForElement = function(o) {
- if (arguments.length > 1) {
- for (var i = 0; i < events.length; i++)
- unbindOne(o, events[i]);
- for (var i = 1; i < arguments.length; i++)
- self.attachListeners(o, arguments[i]);
- }
- };
- },
- overlayCapableJsPlumbUIComponent = function(params) {
- jsPlumbUIComponent.apply(this, arguments);
- var self = this;
- /*
- * Property: overlays
- * List of Overlays for this component.
- */
- this.overlays = [];
- var processOverlay = function(o) {
- var _newOverlay = null;
- if (o.constructor == Array) { // this is for the shorthand ["Arrow", { width:50 }] syntax
- // there's also a three arg version:
- // ["Arrow", { width:50 }, {location:0.7}]
- // which merges the 3rd arg into the 2nd.
- var type = o[0],
- // make a copy of the object so as not to mess up anyone else's reference...
- p = jsPlumb.extend({component:self, _jsPlumb:self._jsPlumb}, o[1]);
- if (o.length == 3) jsPlumb.extend(p, o[2]);
- _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][type](p);
- if (p.events) {
- for (var evt in p.events) {
- _newOverlay.bind(evt, p.events[evt]);
- }
- }
- } else if (o.constructor == String) {
- _newOverlay = new jsPlumb.Overlays[self._jsPlumb.getRenderMode()][o]({component:self, _jsPlumb:self._jsPlumb});
- } else {
- _newOverlay = o;
- }
-
- self.overlays.push(_newOverlay);
- },
- calculateOverlaysToAdd = function(params) {
- var defaultKeys = self.defaultOverlayKeys || [],
- o = params.overlays,
- checkKey = function(k) {
- return self._jsPlumb.Defaults[k] || jsPlumb.Defaults[k] || [];
- };
-
- if (!o) o = [];
- for (var i = 0; i < defaultKeys.length; i++)
- o.unshift.apply(o, checkKey(defaultKeys[i]));
-
- return o;
- }
- var _overlays = calculateOverlaysToAdd(params);//params.overlays || self._jsPlumb.Defaults.Overlays;
- if (_overlays) {
- for (var i = 0; i < _overlays.length; i++) {
- processOverlay(_overlays[i]);
- }
- }
- // overlay finder helper method
- var _getOverlayIndex = function(id) {
- var idx = -1;
- for (var i = 0; i < self.overlays.length; i++) {
- if (id === self.overlays[i].id) {
- idx = i;
- break;
- }
- }
- return idx;
- };
-
- /*
- * Function: addOverlay
- * Adds an Overlay to the Connection.
- *
- * Parameters:
- * overlay - Overlay to add.
- */
- this.addOverlay = function(overlay) {
- processOverlay(overlay);
- self.repaint();
- };
-
- /*
- * Function: getOverlay
- * Gets an overlay, by ID. Note: by ID. You would pass an 'id' parameter
- * in to the Overlay's constructor arguments, and then use that to retrieve
- * it via this method.
- */
- this.getOverlay = function(id) {
- var idx = _getOverlayIndex(id);
- return idx >= 0 ? self.overlays[idx] : null;
- };
- /*
- * Function:getOverlays
- * Gets all the overlays for this component.
- */
- this.getOverlays = function() {
- return self.overlays;
- };
-
- /*
- * Function: hideOverlay
- * Hides the overlay specified by the given id.
- */
- this.hideOverlay = function(id) {
- var o = self.getOverlay(id);
- if (o) o.hide();
- };
- this.hideOverlays = function() {
- for (var i = 0; i < self.overlays.length; i++)
- self.overlays[i].hide();
- };
-
- /*
- * Function: showOverlay
- * Shows the overlay specified by the given id.
- */
- this.showOverlay = function(id) {
- var o = self.getOverlay(id);
- if (o) o.show();
- };
- this.showOverlays = function() {
- for (var i = 0; i < self.overlays.length; i++)
- self.overlays[i].show();
- };
-
- /**
- * Function: removeAllOverlays
- * Removes all overlays from the Connection, and then repaints.
- */
- this.removeAllOverlays = function() {
- self.overlays.splice(0, self.overlays.length);
- self.repaint();
- };
-
- /**
- * Function:removeOverlay
- * Removes an overlay by ID. Note: by ID. this is a string you set in the overlay spec.
- * Parameters:
- * overlayId - id of the overlay to remove.
- */
- this.removeOverlay = function(overlayId) {
- var idx = _getOverlayIndex(overlayId);
- if (idx != -1) {
- var o = self.overlays[idx];
- o.cleanup();
- self.overlays.splice(idx, 1);
- }
- };
-
- /**
- * Function:removeOverlays
- * Removes a set of overlays by ID. Note: by ID. this is a string you set in the overlay spec.
- * Parameters:
- * overlayIds - this function takes an arbitrary number of arguments, each of which is a single overlay id.
- */
- this.removeOverlays = function() {
- for (var i = 0; i < arguments.length; i++)
- self.removeOverlay(arguments[i]);
- };
- // this is a shortcut helper method to let people add a label as
- // overlay.
- var _internalLabelOverlayId = "__label",
- _makeLabelOverlay = function(params) {
- var _params = {
- cssClass:params.cssClass,
- labelStyle : this.labelStyle,
- id:_internalLabelOverlayId,
- component:self,
- _jsPlumb:self._jsPlumb
- },
- mergedParams = jsPlumb.extend(_params, params);
- return new jsPlumb.Overlays[self._jsPlumb.getRenderMode()].Label( mergedParams );
- };
- if (params.label) {
- var loc = params.labelLocation || self.defaultLabelLocation || 0.5,
- labelStyle = params.labelStyle || self._jsPlumb.Defaults.LabelStyle || jsPlumb.Defaults.LabelStyle;
- this.overlays.push(_makeLabelOverlay({
- label:params.label,
- location:loc,
- labelStyle:labelStyle
- }));
- }
- /*
- * Function: setLabel
- * Sets the Connection's label.
- *
- * Parameters:
- * l - label to set. May be a String, a Function that returns a String, or a params object containing { "label", "labelStyle", "location", "cssClass" }
- */
- this.setLabel = function(l) {
- var lo = self.getOverlay(_internalLabelOverlayId);
- if (!lo) {
- var params = l.constructor == String || l.constructor == Function ? { label:l } : l;
- lo = _makeLabelOverlay(params);
- this.overlays.push(lo);
- }
- else {
- if (l.constructor == String || l.constructor == Function) lo.setLabel(l);
- else {
- if (l.label) lo.setLabel(l.label);
- if (l.location) lo.setLocation(l.location);
- }
- }
-
- self.repaint();
- };
- /*
- Function:getLabel
- Returns the label text for this component (or a function if you are labelling with a function).
- This does not return the overlay itself; this is a convenience method which is a pair with
- setLabel; together they allow you to add and access a Label Overlay without having to create the
- Overlay object itself. For access to the underlying label overlay that jsPlumb has created,
- use getLabelOverlay.
- */
- this.getLabel = function() {
- var lo = self.getOverlay(_internalLabelOverlayId);
- return lo != null ? lo.getLabel() : null;
- };
- /*
- Function:getLabelOverlay
- Returns the underlying internal label overlay, which will exist if you specified a label on
- a connect or addEndpoint call, or have called setLabel at any stage.
- */
- this.getLabelOverlay = function() {
- return self.getOverlay(_internalLabelOverlayId);
- }
- },
-
- _bindListeners = function(obj, _self, _hoverFunction) {
- obj.bind("click", function(ep, e) { _self.fire("click", _self, e); });
- obj.bind("dblclick", function(ep, e) { _self.fire("dblclick", _self, e); });
- obj.bind("contextmenu", function(ep, e) { _self.fire("contextmenu", _self, e); });
- obj.bind("mouseenter", function(ep, e) {
- if (!_self.isHover()) {
- _hoverFunction(true);
- _self.fire("mouseenter", _self, e);
- }
- });
- obj.bind("mouseexit", function(ep, e) {
- if (_self.isHover()) {
- _hoverFunction(false);
- _self.fire("mouseexit", _self, e);
- }
- });
- };
-
- var _jsPlumbInstanceIndex = 0,
- getInstanceIndex = function() {
- var i = _jsPlumbInstanceIndex + 1;
- _jsPlumbInstanceIndex++;
- return i;
- };
- var jsPlumbInstance = function(_defaults) {
-
- /*
- * Property: Defaults
- *
- * These are the default settings for jsPlumb. They are what will be used if you do not supply specific pieces of information
- * to the various API calls. A convenient way to implement your own look and feel can be to override these defaults
- * by including a script somewhere after the jsPlumb include, but before you make any calls to jsPlumb.
- *
- * Properties:
- * - *Anchor* The default anchor to use for all connections (both source and target). Default is "BottomCenter".
- * - *Anchors* The default anchors to use ([source, target]) for all connections. Defaults are ["BottomCenter", "BottomCenter"].
- * - *ConnectionsDetachable* Whether or not connections are detachable by default (using the mouse). Defults to true.
- * - *ConnectionOverlays* The default overlay definitions for Connections. Defaults to an empty list.
- * - *Connector* The default connector definition to use for all connections. Default is "Bezier".
- * - *Container* Optional selector or element id that instructs jsPlumb to append elements it creates to a specific element.
- * - *DragOptions* The default drag options to pass in to connect, makeTarget and addEndpoint calls. Default is empty.
- * - *DropOptions* The default drop options to pass in to connect, makeTarget and addEndpoint calls. Default is empty.
- * - *Endpoint* The default endpoint definition to use for all connections (both source and target). Default is "Dot".
- * - *EndpointOverlays* The default overlay definitions for Endpoints. Defaults to an empty list.
- * - *Endpoints* The default endpoint definitions ([ source, target ]) to use for all connections. Defaults are ["Dot", "Dot"].
- * - *EndpointStyle* The default style definition to use for all endpoints. Default is fillStyle:"#456".
- * - *EndpointStyles* The default style definitions ([ source, target ]) to use for all endpoints. Defaults are empty.
- * - *EndpointHoverStyle* The default hover style definition to use for all endpoints. Default is null.
- * - *EndpointHoverStyles* The default hover style definitions ([ source, target ]) to use for all endpoints. Defaults are null.
- * - *HoverPaintStyle* The default hover style definition to use for all connections. Defaults are null.
- * - *LabelStyle* The default style to use for label overlays on connections.
- * - *LogEnabled* Whether or not the jsPlumb log is enabled. defaults to false.
- * - *Overlays* The default overlay definitions (for both Connections and Endpoint). Defaults to an empty list.
- * - *MaxConnections* The default maximum number of connections for an Endpoint. Defaults to 1.
- * - *PaintStyle* The default paint style for a connection. Default is line width of 8 pixels, with color "#456".
- * - *RenderMode* What mode to use to paint with. If you're on IE<9, you don't really get to choose this. You'll just get VML. Otherwise, the jsPlumb default is to use SVG.
- * - *Scope* The default "scope" to use for connections. Scope lets you assign connections to different categories.
- */
- this.Defaults = {
- Anchor : "BottomCenter",
- Anchors : [ null, null ],
- ConnectionsDetachable : true,
- ConnectionOverlays : [ ],
- Connector : "Bezier",
- Container : null,
- DragOptions : { },
- DropOptions : { },
- Endpoint : "Dot",
- EndpointOverlays : [ ],
- Endpoints : [ null, null ],
- EndpointStyle : { fillStyle : "#456" },
- EndpointStyles : [ null, null ],
- EndpointHoverStyle : null,
- EndpointHoverStyles : [ null, null ],
- HoverPaintStyle : null,
- LabelStyle : { color : "black" },
- LogEnabled : false,
- Overlays : [ ],
- MaxConnections : 1,
- PaintStyle : { lineWidth : 8, strokeStyle : "#456" },
- //Reattach:false,
- RenderMode : "svg",
- Scope : "jsPlumb_DefaultScope"
- };
- if (_defaults) jsPlumb.extend(this.Defaults, _defaults);
-
- this.logEnabled = this.Defaults.LogEnabled;
- EventGenerator.apply(this);
- var _currentInstance = this,
- _instanceIndex = getInstanceIndex(),
- _bb = _currentInstance.bind,
- _initialDefaults = {};
- for (var i in this.Defaults)
- _initialDefaults[i] = this.Defaults[i];
- this.bind = function(event, fn) {
- if ("ready" === event && initialized) fn();
- else _bb.apply(_currentInstance,[event, fn]);
- };
- /*
- Function: importDefaults
- Imports all the given defaults into this instance of jsPlumb.
- */
- _currentInstance.importDefaults = function(d) {
- for (var i in d) {
- _currentInstance.Defaults[i] = d[i];
- }
- };
- /*
- Function:restoreDefaults
- Restores the default settings to "factory" values.
- */
- _currentInstance.restoreDefaults = function() {
- _currentInstance.Defaults = jsPlumb.extend({}, _initialDefaults);
- };
- var log = null,
- repaintFunction = function() {
- jsPlumb.repaintEverything();
- },
- automaticRepaint = true,
- repaintEverything = function() {
- if (automaticRepaint)
- repaintFunction();
- },
- resizeTimer = null,
- initialized = false,
- connectionsByScope = {},
- /**
- * map of element id -> endpoint lists. an element can have an arbitrary
- * number of endpoints on it, and not all of them have to be connected
- * to anything.
- */
- endpointsByElement = {},
- endpointsByUUID = {},
- offsets = {},
- offsetTimestamps = {},
- floatingConnections = {},
- draggableStates = {},
- canvasList = [],
- sizes = [],
- //listeners = {}, // a map: keys are event types, values are lists of listeners.
- DEFAULT_SCOPE = this.Defaults.Scope,
- renderMode = null, // will be set in init()
- /**
- * helper method to add an item to a list, creating the list if it does
- * not yet exist.
- */
- _addToList = function(map, key, value) {
- var l = map[key];
- if (l == null) {
- l = [];
- map[key] = l;
- }
- l.push(value);
- return l;
- },
- /**
- * appends an element to some other element, which is calculated as follows:
- *
- * 1. if _currentInstance.Defaults.Container exists, use that element.
- * 2. if the 'parent' parameter exists, use that.
- * 3. otherwise just use the document body.
- *
- */
- _appendElement = function(el, parent) {
- if (_currentInstance.Defaults.Container)
- jsPlumb.CurrentLibrary.appendElement(el, _currentInstance.Defaults.Container);
- else if (!parent)
- document.body.appendChild(el);
- else
- jsPlumb.CurrentLibrary.appendElement(el, parent);
- },
- _curIdStamp = 1,
- _idstamp = function() { return "" + _curIdStamp++; },
-
- /**
- * YUI, for some reason, put the result of a Y.all call into an object that contains
- * a '_nodes' array, instead of handing back an array-like object like the other
- * libraries do.
- */
- _convertYUICollection = function(c) {
- return c._nodes ? c._nodes : c;
- },
- _suspendDrawing = false,
- /*
- sets whether or not to suspend drawing. you should use this if you need to connect a whole load of things in one go.
- it will save you a lot of time.
- */
- _setSuspendDrawing = function(val, repaintAfterwards) {
- _suspendDrawing = val;
- if (repaintAfterwards) _currentInstance.repaintEverything();
- },
- /**
- * Draws an endpoint and its connections. this is the main entry point into drawing connections as well
- * as endpoints, since jsPlumb is endpoint-centric under the hood.
- *
- * @param element element to draw (of type library specific element object)
- * @param ui UI object from current library's event system. optional.
- * @param timestamp timestamp for this paint cycle. used to speed things up a little by cutting down the amount of offset calculations we do.
- */
- _draw = function(element, ui, timestamp) {
- if (!_suspendDrawing) {
- var id = _getAttribute(element, "id"),
- repaintEls = _currentInstance.dragManager.getElementsForDraggable(id);
- if (timestamp == null) timestamp = _timestamp();
- _currentInstance.anchorManager.redraw(id, ui, timestamp);
- if (repaintEls) {
- for (var i in repaintEls) {
- _currentInstance.anchorManager.redraw(repaintEls[i].id, ui, timestamp, repaintEls[i].offset);
- }
- }
- }
- },
- /**
- * executes the given function against the given element if the first
- * argument is an object, or the list of elements, if the first argument
- * is a list. the function passed in takes (element, elementId) as
- * arguments.
- */
- _elementProxy = function(element, fn) {
- var retVal = null;
- if (element.constructor == Array) {
- retVal = [];
- for ( var i = 0; i < element.length; i++) {
- var el = _getElementObject(element[i]), id = _getAttribute(el, "id");
- retVal.push(fn(el, id)); // append return values to what we will return
- }
- } else {
- var el = _getElementObject(element), id = _getAttribute(el, "id");
- retVal = fn(el, id);
- }
- return retVal;
- },
- /**
- * gets an Endpoint by uuid.
- */
- _getEndpoint = function(uuid) { return endpointsByUUID[uuid]; },
- /**
- * inits a draggable if it's not already initialised.
- */
- _initDraggableIfNecessary = function(element, isDraggable, dragOptions) {
- var draggable = isDraggable == null ? false : isDraggable,
- jpcl = jsPlumb.CurrentLibrary;
- if (draggable) {
- if (jpcl.isDragSupported(element) && !jpcl.isAlreadyDraggable(element)) {
- var options = dragOptions || _currentInstance.Defaults.DragOptions || jsPlumb.Defaults.DragOptions;
- options = jsPlumb.extend( {}, options); // make a copy.
- var dragEvent = jpcl.dragEvents["drag"],
- stopEvent = jpcl.dragEvents["stop"],
- startEvent = jpcl.dragEvents["start"];
- options[dragEvent] = _wrap(options[dragEvent], function() {
- var ui = jpcl.getUIPosition(arguments);
- _draw(element, ui);
- _addClass(element, "jsPlumb_dragged");
- });
- options[stopEvent] = _wrap(options[stopEvent], function() {
- var ui = jpcl.getUIPosition(arguments);
- _draw(element, ui);
- _removeClass(element, "jsPlumb_dragged");
- });
- draggableStates[_getId(element)] = true;
- var draggable = draggableStates[_getId(element)];
- options.disabled = draggable == null ? false : !draggable;
- jpcl.initDraggable(element, options, false);
- _currentInstance.dragManager.register(element);
- }
- }
- },
-
- /*
- * prepares a final params object that can be passed to _newConnection, taking into account defaults, events, etc.
- */
- _prepareConnectionParams = function(params, referenceParams) {
- var _p = jsPlumb.extend( {}, params);
- if (referenceParams) jsPlumb.extend(_p, referenceParams);
-
- // hotwire endpoints passed as source or target to sourceEndpoint/targetEndpoint, respectively.
- if (_p.source && _p.source.endpoint) _p.sourceEndpoint = _p.source;
- if (_p.source && _p.target.endpoint) _p.targetEndpoint = _p.target;
-
- // test for endpoint uuids to connect
- if (params.uuids) {
- _p.sourceEndpoint = _getEndpoint(params.uuids[0]);
- _p.targetEndpoint = _getEndpoint(params.uuids[1]);
- }
- // now ensure that if we do have Endpoints already, they're not full.
- // source:
- if (_p.sourceEndpoint && _p.sourceEndpoint.isFull()) {
- _log(_currentInstance, "could not add connection; source endpoint is full");
- return;
- }
- // target:
- if (_p.targetEndpoint && _p.targetEndpoint.isFull()) {
- _log(_currentInstance, "could not add connection; target endpoint is full");
- return;
- }
-
- // copy in any connectorOverlays that were specified on the source endpoint.
- // it doesnt copy target endpoint overlays. i'm not sure if we want it to or not.
- if (_p.sourceEndpoint && _p.sourceEndpoint.connectorOverlays) {
- _p.overlays = _p.overlays || [];
- for (var i = 0; i < _p.sourceEndpoint.connectorOverlays.length; i++) {
- _p.overlays.push(_p.sourceEndpoint.connectorOverlays[i]);
- }
- }
-
- // tooltip. params.tooltip takes precedence, then sourceEndpoint.connectorTooltip.
- _p.tooltip = params.tooltip;
- if (!_p.tooltip && _p.sourceEndpoint && _p.sourceEndpoint.connectorTooltip)
- _p.tooltip = _p.sourceEndpoint.connectorTooltip;
-
- // if there's a target specified (which of course there should be), and there is no
- // target endpoint specified, and 'newConnection' was not set to true, then we check to
- // see if a prior call to makeTarget has provided us with the specs for the target endpoint, and
- // we use those if so. additionally, if the makeTarget call was specified with 'uniqueEndpoint' set
- // to true, then if that target endpoint has already been created, we re-use it.
- if (_p.target && !_p.target.endpoint && !_p.targetEndpoint && !_p.newConnection) {
- var tid = _getId(_p.target),
- tep =_targetEndpointDefinitions[tid],
- existingUniqueEndpoint = _targetEndpoints[tid];
- if (tep) {
-
- var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.target, tep);
- if (_targetEndpointsUnique[tid]) _targetEndpoints[tid] = newEndpoint;
- _p.targetEndpoint = newEndpoint;
- }
- }
- // same thing, but for source.
- if (_p.source && !_p.source.endpoint && !_p.sourceEndpoint && !_p.newConnection) {
- var tid = _getId(_p.source),
- tep = _sourceEndpointDefinitions[tid],
- existingUniqueEndpoint = _sourceEndpoints[tid];
- if (tep) {
-
- var newEndpoint = existingUniqueEndpoint != null ? existingUniqueEndpoint : _currentInstance.addEndpoint(_p.source, tep);
- if (_sourceEndpointsUnique[tid]) _sourceEndpoints[tid] = newEndpoint;
- _p.sourceEndpoint = newEndpoint;
- }
- }
-
- return _p;
- },
-
- _newConnection = function(params) {
- var connectionFunc = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(),
- endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint,
- parent = jsPlumb.CurrentLibrary.getParent;
-
- if (params.container)
- params["parent"] = params.container;
- else {
- if (params.sourceEndpoint)
- params["parent"] = params.sourceEndpoint.parent;
- else if (params.source.constructor == endpointFunc)
- params["parent"] = params.source.parent;
- else params["parent"] = parent(params.source);
- }
-
- params["_jsPlumb"] = _currentInstance;
- var con = new connectionFunc(params);
- con.id = "con_" + _idstamp();
- _eventFireProxy("click", "click", con);
- _eventFireProxy("dblclick", "dblclick", con);
- _eventFireProxy("contextmenu", "contextmenu", con);
- return con;
- },
-
- /**
- * adds the connection to the backing model, fires an event if necessary and then redraws
- */
- _finaliseConnection = function(jpc, params, originalEvent) {
- params = params || {};
- // add to list of connections (by scope).
- if (!jpc.suspendedEndpoint)
- _addToList(connectionsByScope, jpc.scope, jpc);
- // fire an event
- if (!params.doNotFireConnectionEvent && params.fireEvent !== false) {
- _currentInstance.fire("jsPlumbConnection", {
- connection:jpc,
- source : jpc.source, target : jpc.target,
- sourceId : jpc.sourceId, targetId : jpc.targetId,
- sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1]
- }, originalEvent);
- }
- // always inform the anchor manager
- // except that if jpc has a suspended endpoint it's not true to say the
- // connection is new; it has just (possibly) moved. the question is whether
- // to make that call here or in the anchor manager. i think perhaps here.
- _currentInstance.anchorManager.newConnection(jpc);
- // force a paint
- _draw(jpc.source);
- },
-
- _eventFireProxy = function(event, proxyEvent, obj) {
- obj.bind(event, function(originalObject, originalEvent) {
- _currentInstance.fire(proxyEvent, obj, originalEvent);
- });
- },
-
- /**
- * for the given endpoint params, returns an appropriate parent element for the UI elements that will be added.
- * this function is used by _newEndpoint (directly below), and also in the makeSource function in jsPlumb.
- *
- * the logic is to first look for a "container" member of params, and pass that back if found. otherwise we
- * handoff to the 'getParent' function in the current library.
- */
- _getParentFromParams = function(params) {
- if (params.container)
- return params.container;
- else {
- var tag = jsPlumb.CurrentLibrary.getTagName(params.source),
- p = jsPlumb.CurrentLibrary.getParent(params.source);
- if (tag && tag.toLowerCase() === "td")
- return jsPlumb.CurrentLibrary.getParent(p);
- else return p;
- }
- },
-
- /**
- factory method to prepare a new endpoint. this should always be used instead of creating Endpoints
- manually, since this method attaches event listeners and an id.
- */
- _newEndpoint = function(params) {
- var endpointFunc = _currentInstance.Defaults.EndpointType || Endpoint;
- params.parent = _getParentFromParams(params);
- params["_jsPlumb"] = _currentInstance;
- var ep = new endpointFunc(params);
- ep.id = "ep_" + _idstamp();
- _eventFireProxy("click", "endpointClick", ep);
- _eventFireProxy("dblclick", "endpointDblClick", ep);
- _eventFireProxy("contextmenu", "contextmenu", ep);
- return ep;
- },
-
- /**
- * performs the given function operation on all the connections found
- * for the given element id; this means we find all the endpoints for
- * the given element, and then for each endpoint find the connectors
- * connected to it. then we pass each connection in to the given
- * function.
- */
- _operation = function(elId, func, endpointFunc) {
- var endpoints = endpointsByElement[elId];
- if (endpoints && endpoints.length) {
- for ( var i = 0; i < endpoints.length; i++) {
- for ( var j = 0; j < endpoints[i].connections.length; j++) {
- var retVal = func(endpoints[i].connections[j]);
- // if the function passed in returns true, we exit.
- // most functions return false.
- if (retVal) return;
- }
- if (endpointFunc) endpointFunc(endpoints[i]);
- }
- }
- },
- /**
- * perform an operation on all elements.
- */
- _operationOnAll = function(func) {
- for ( var elId in endpointsByElement) {
- _operation(elId, func);
- }
- },
-
- /**
- * helper to remove an element from the DOM.
- */
- _removeElement = function(element, parent) {
- if (element != null && element.parentNode != null) {
- element.parentNode.removeChild(element);
- }
- },
- /**
- * helper to remove a list of elements from the DOM.
- */
- _removeElements = function(elements, parent) {
- for ( var i = 0; i < elements.length; i++)
- _removeElement(elements[i], parent);
- },
- /**
- * Sets whether or not the given element(s) should be draggable,
- * regardless of what a particular plumb command may request.
- *
- * @param element
- * May be a string, a element objects, or a list of
- * strings/elements.
- * @param draggable
- * Whether or not the given element(s) should be draggable.
- */
- _setDraggable = function(element, draggable) {
- return _elementProxy(element, function(el, id) {
- draggableStates[id] = draggable;
- if (jsPlumb.CurrentLibrary.isDragSupported(el)) {
- jsPlumb.CurrentLibrary.setDraggable(el, draggable);
- }
- });
- },
- /**
- * private method to do the business of hiding/showing.
- *
- * @param el
- * either Id of the element in question or a library specific
- * object for the element.
- * @param state
- * String specifying a value for the css 'display' property
- * ('block' or 'none').
- */
- _setVisible = function(el, state, alsoChangeEndpoints) {
- state = state === "block";
- var endpointFunc = null;
- if (alsoChangeEndpoints) {
- if (state) endpointFunc = function(ep) {
- ep.setVisible(true, true, true);
- };
- else endpointFunc = function(ep) {
- ep.setVisible(false, true, true);
- };
- }
- var id = _getAttribute(el, "id");
- _operation(id, function(jpc) {
- if (state && alsoChangeEndpoints) {
- // this test is necessary because this functionality is new, and i wanted to maintain backwards compatibility.
- // this block will only set a connection to be visible if the other endpoint in the connection is also visible.
- var oidx = jpc.sourceId === id ? 1 : 0;
- if (jpc.endpoints[oidx].isVisible()) jpc.setVisible(true);
- }
- else // the default behaviour for show, and what always happens for hide, is to just set the visibility without getting clever.
- jpc.setVisible(state);
- }, endpointFunc);
- },
- /**
- * toggles the draggable state of the given element(s).
- *
- * @param el
- * either an id, or an element object, or a list of
- * ids/element objects.
- */
- _toggleDraggable = function(el) {
- return _elementProxy(el, function(el, elId) {
- var state = draggableStates[elId] == null ? false : draggableStates[elId];
- state = !state;
- draggableStates[elId] = state;
- jsPlumb.CurrentLibrary.setDraggable(el, state);
- return state;
- });
- },
- /**
- * private method to do the business of toggling hiding/showing.
- *
- * @param elId
- * Id of the element in question
- */
- _toggleVisible = function(elId, changeEndpoints) {
- var endpointFunc = null;
- if (changeEndpoints) {
- endpointFunc = function(ep) {
- var state = ep.isVisible();
- ep.setVisible(!state);
- };
- }
- _operation(elId, function(jpc) {
- var state = jpc.isVisible();
- jpc.setVisible(!state);
- }, endpointFunc);
- // todo this should call _elementProxy, and pass in the
- // _operation(elId, f) call as a function. cos _toggleDraggable does
- // that.
- },
- /**
- * updates the offset and size for a given element, and stores the
- * values. if 'offset' is not null we use that (it would have been
- * passed in from a drag call) because it's faster; but if it is null,
- * or if 'recalc' is true in order to force a recalculation, we get the current values.
- */
- _updateOffset = function(params) {
- var timestamp = params.timestamp, recalc = params.recalc, offset = params.offset, elId = params.elId;
- if (!recalc) {
- if (timestamp && timestamp === offsetTimestamps[elId])
- return offsets[elId];
- }
- if (recalc || !offset) { // if forced repaint or no offset
- // available, we recalculate.
- // get the current size and offset, and store them
- var s = _getElementObject(elId);
- if (s != null) {
- sizes[elId] = _getSize(s);
- offsets[elId] = _getOffset(s);
- offsetTimestamps[elId] = timestamp;
- }
- } else {
- offsets[elId] = offset;
- if (sizes[elId] == null) {
- var s = _getElementObject(elId);
- if (s != null)
- sizes[elId] = _getSize(s);
- }
- }
-
- if(offsets[elId] && !offsets[elId].right) {
- offsets[elId].right = offsets[elId].left + sizes[elId][0];
- offsets[elId].bottom = offsets[elId].top + sizes[elId][1];
- offsets[elId].width = sizes[elId][0];
- offsets[elId].height = sizes[elId][1];
- offsets[elId].centerx = offsets[elId].left + (offsets[elId].width / 2);
- offsets[elId].centery = offsets[elId].top + (offsets[elId].height / 2);
- }
- return offsets[elId];
- },
- // TODO comparison performance
- _getCachedData = function(elId) {
- var o = offsets[elId];
- if (!o) o = _updateOffset({elId:elId});
- return {o:o, s:sizes[elId]};
- },
- /**
- * gets an id for the given element, creating and setting one if
- * necessary. the id is of the form
- *
- * jsPlumb_<instance index>_<index in instance>
- *
- * where "index in instance" is a monotonically increasing integer that starts at 0,
- * for each instance. this method is used not only to assign ids to elements that do not
- * have them but also to connections and endpoints.
- */
- _getId = function(element, uuid) {
- var ele = _getElementObject(element);
- var id = _getAttribute(ele, "id");
- if (!id || id == "undefined") {
- // check if fixed uuid parameter is given
- if (arguments.length == 2 && arguments[1] != undefined)
- id = uuid;
- else
- id = "jsPlumb_" + _instanceIndex + "_" + _idstamp();
- _setAttribute(ele, "id", id);
- }
- return id;
- },
- /**
- * wraps one function with another, creating a placeholder for the
- * wrapped function if it was null. this is used to wrap the various
- * drag/drop event functions - to allow jsPlumb to be notified of
- * important lifecycle events without imposing itself on the user's
- * drag/drop functionality. TODO: determine whether or not we should
- * support an error handler concept, if one of the functions fails.
- *
- * @param wrappedFunction original function to wrap; may be null.
- * @param newFunction function to wrap the original with.
- * @param returnOnThisValue Optional. Indicates that the wrappedFunction should
- * not be executed if the newFunction returns a value matching 'returnOnThisValue'.
- * note that this is a simple comparison and only works for primitives right now.
- */
- _wrap = function(wrappedFunction, newFunction, returnOnThisValue) {
- wrappedFunction = wrappedFunction || function() { };
- newFunction = newFunction || function() { };
- return function() {
- var r = null;
- try {
- r = newFunction.apply(this, arguments);
- } catch (e) {
- _log(_currentInstance, "jsPlumb function failed : " + e);
- }
- if (returnOnThisValue == null || (r !== returnOnThisValue)) {
- try {
- wrappedFunction.apply(this, arguments);
- } catch (e) {
- _log(_currentInstance, "wrapped function failed : " + e);
- }
- }
- return r;
- };
- };
- /*
- * Property: connectorClass
- * The CSS class to set on Connection elements. This value is a String and can have multiple classes; the entire String is appended as-is.
- */
- this.connectorClass = "_jsPlumb_connector";
- /*
- * Property: endpointClass
- * The CSS class to set on Endpoint elements. This value is a String and can have multiple classes; the entire String is appended as-is.
- */
- this.endpointClass = "_jsPlumb_endpoint";
- /*
- * Property: overlayClass
- * The CSS class to set on an Overlay that is an HTML element. This value is a String and can have multiple classes; the entire String is appended as-is.
- */
- this.overlayClass = "_jsPlumb_overlay";
-
- this.Anchors = {};
-
- this.Connectors = {
- "canvas":{},
- "svg":{},
- "vml":{}
- };
- this.Endpoints = {
- "canvas":{},
- "svg":{},
- "vml":{}
- };
- this.Overlays = {
- "canvas":{},
- "svg":{},
- "vml":{}
- };
-
- // ************************ PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS *****************************************
- /*
- * Function: bind
- * Bind to an event on jsPlumb.
- *
- * Parameters:
- * event - the event to bind. Available events on jsPlumb are:
- * - *jsPlumbConnection* : notification that a new Connection was established. jsPlumb passes the new Connection to the callback.
- * - *jsPlumbConnectionDetached* : notification that a Connection was detached. jsPlumb passes the detached Connection to the callback.
- * - *click* : notification that a Connection was clicked. jsPlumb passes the Connection that was clicked to the callback.
- * - *dblclick* : notification that a Connection was double clicked. jsPlumb passes the Connection that was double clicked to the callback.
- * - *endpointClick* : notification that an Endpoint was clicked. jsPlumb passes the Endpoint that was clicked to the callback.
- * - *endpointDblClick* : notification that an Endpoint was double clicked. jsPlumb passes the Endpoint that was double clicked to the callback.
- *
- * callback - function to callback. This function will be passed the Connection/Endpoint that caused the event, and also the original event.
- */
-
- /*
- * Function: clearListeners
- * Clears either all listeners, or listeners for some specific event.
- *
- * Parameters:
- * event - optional. constrains the clear to just listeners for this event.
- */
-
- // *************** END OF PLACEHOLDER DOC ENTRIES FOR NATURAL DOCS ***********************************************************
-
- // --------------------------- jsPLumbInstance public API ---------------------------------------------------------
- /*
- Function: addClass
-
- Helper method to abstract out differences in setting css classes on the different renderer types.
- */
- this.addClass = function(el, clazz) {
- return jsPlumb.CurrentLibrary.addClass(el, clazz);
- };
-
- /*
- Function: removeClass
-
- Helper method to abstract out differences in setting css classes on the different renderer types.
- */
- this.removeClass = function(el, clazz) {
- return jsPlumb.CurrentLibrary.removeClass(el, clazz);
- };
-
- /*
- Function: hasClass
-
- Helper method to abstract out differences in testing for css classes on the different renderer types.
- */
- this.hasClass = function(el, clazz) {
- return jsPlumb.CurrentLibrary.hasClass(el, clazz);
- };
-
- /*
- Function: addEndpoint
-
- Adds an <Endpoint> to a given element or elements.
-
- Parameters:
-
- el - Element to add the endpoint to. Either an element id, a selector representing some element(s), or an array of either of these.
- params - Object containing Endpoint constructor arguments. For more information, see <Endpoint>.
- referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some
- shared parameters that you wanted to reuse when you added Endpoints to a number of elements. The allowed values in
- this object are anything that 'params' can contain. See <Endpoint>.
-
- Returns:
- The newly created <Endpoint>, if el referred to a single element. Otherwise, an array of newly created <Endpoint>s.
-
- See Also:
- <addEndpoints>
- */
- this.addEndpoint = function(el, params, referenceParams) {
- referenceParams = referenceParams || {};
- var p = jsPlumb.extend({}, referenceParams);
- jsPlumb.extend(p, params);
- p.endpoint = p.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint;
- p.paintStyle = p.paintStyle || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle;
- // YUI wrapper
- el = _convertYUICollection(el);
-
- var results = [], inputs = el.length && el.constructor != String ? el : [ el ];
-
- for (var i = 0; i < inputs.length; i++) {
- var _el = _getElementObject(inputs[i]), id = _getId(_el);
- p.source = _el;
- _updateOffset({ elId : id });
- var e = _newEndpoint(p);
- if (p.parentAnchor) e.parentAnchor = p.parentAnchor;
- _addToList(endpointsByElement, id, e);
- var myOffset = offsets[id], myWH = sizes[id];
- var anchorLoc = e.anchor.compute( { xy : [ myOffset.left, myOffset.top ], wh : myWH, element : e });
- e.paint({ anchorLoc : anchorLoc });
- results.push(e);
- _currentInstance.dragManager.endpointAdded(_el);
- }
-
- return results.length == 1 ? results[0] : results;
- };
-
- /*
- Function: addEndpoints
- Adds a list of <Endpoint>s to a given element or elements.
-
- Parameters:
- target - element to add the Endpoint to. Either an element id, a selector representing some element(s), or an array of either of these.
- endpoints - List of objects containing Endpoint constructor arguments. one Endpoint is created for each entry in this list. See <Endpoint>'s constructor documentation.
- referenceParams - Object containing more Endpoint constructor arguments; it will be merged with params by jsPlumb. You would use this if you had some shared parameters that you wanted to reuse when you added Endpoints to a number of elements.
- Returns:
- List of newly created <Endpoint>s, one for each entry in the 'endpoints' argument.
-
- See Also:
- <addEndpoint>
- */
- this.addEndpoints = function(el, endpoints, referenceParams) {
- var results = [];
- for ( var i = 0; i < endpoints.length; i++) {
- var e = _currentInstance.addEndpoint(el, endpoints[i], referenceParams);
- if (e.constructor == Array)
- Array.prototype.push.apply(results, e);
- else results.push(e);
- }
- return results;
- };
- /*
- Function: animate
- This is a wrapper around the supporting library's animate function; it injects a call to jsPlumb in the 'step' function (creating
- the 'step' function if necessary). This only supports the two-arg version of the animate call in jQuery, the one that takes an 'options' object as
- the second arg. MooTools has only one method, a two arg one. Which is handy. YUI has a one-arg method, so jsPlumb merges 'properties' and 'options' together for YUI.
-
- Parameters:
- el - Element to animate. Either an id, or a selector representing the element.
- properties - The 'properties' argument you want passed to the library's animate call.
- options - The 'options' argument you want passed to the library's animate call.
-
- Returns:
- void
- */
- this.animate = function(el, properties, options) {
- var ele = _getElementObject(el), id = _getAttribute(el, "id");
- options = options || {};
- var stepFunction = jsPlumb.CurrentLibrary.dragEvents['step'];
- var completeFunction = jsPlumb.CurrentLibrary.dragEvents['complete'];
- options[stepFunction] = _wrap(options[stepFunction], function() {
- _currentInstance.repaint(id);
- });
- // onComplete repaints, just to make sure everything looks good at the end of the animation.
- options[completeFunction] = _wrap(options[completeFunction],
- function() {
- _currentInstance.repaint(id);
- });
- jsPlumb.CurrentLibrary.animate(ele, properties, options);
- };
-
- /**
- * checks for a listener for the given condition, executing it if found, passing in the given value.
- * condition listeners would have been attached using "bind" (which is, you could argue, now overloaded, since
- * firing click events etc is a bit different to what this does). i thought about adding a "bindCondition"
- * or something, but decided against it, for the sake of simplicity. jsPlumb will never fire one of these
- * condition events anyway.
- */
- this.checkCondition = function(conditionName, value) {
- var l = _currentInstance.getListener(conditionName);
- var r = true;
- if (l && l.length > 0) {
- try {
- for (var i = 0 ; i < l.length; i++) {
- r = r && l[i](value);
- }
- }
- catch (e) {
- _log(_currentInstance, "cannot check condition [" + conditionName + "]" + e);
- }
- }
- return r;
- };
- /*
- Function: connect
- Establishes a <Connection> between two elements (or <Endpoint>s, which are themselves registered to elements).
-
- Parameters:
- params - Object containing constructor arguments for the Connection. See <Connection>'s constructor documentation.
- referenceParams - Optional object containing more constructor arguments for the Connection. Typically you would pass in data that a lot of
- Connections are sharing here, such as connector style etc, and then use the main params for data specific to this Connection.
-
- Returns:
- The newly created <Connection>.
- */
- this.connect = function(params, referenceParams) {
- // prepare a final set of parameters to create connection with
- var _p = _prepareConnectionParams(params, referenceParams);
- // TODO probably a nicer return value if the connection was not made. _prepareConnectionParams
- // will return null (and log something) if either endpoint was full. what would be nicer is to
- // create a dedicated 'error' object.
- if (_p) {
- // a connect call will delete its created endpoints on detach, unless otherwise specified.
- // this is because the endpoints belong to this connection only, and are no use to
- // anyone else, so they hang around like a bad smell.
- if (_p.deleteEndpointsOnDetach == null)
- _p.deleteEndpointsOnDetach = true;
- // create the connection. it is not yet registered
- var jpc = _newConnection(_p);
- // now add it the model, fire an event, and redraw
- _finaliseConnection(jpc, _p);
- return jpc;
- }
- };
-
- /*
- Function: deleteEndpoint
- Deletes an Endpoint and removes all Connections it has (which removes the Connections from the other Endpoints involved too)
-
- Parameters:
- object - either an <Endpoint> object (such as from an addEndpoint call), or a String UUID.
-
- Returns:
- void
- */
- this.deleteEndpoint = function(object) {
- var endpoint = (typeof object == "string") ? endpointsByUUID[object] : object;
- if (endpoint) {
- var uuid = endpoint.getUuid();
- if (uuid) endpointsByUUID[uuid] = null;
- endpoint.detachAll();
- _removeElements(endpoint.endpoint.getDisplayElements());
- _currentInstance.anchorManager.deleteEndpoint(endpoint);
- for (var e in endpointsByElement) {
- var endpoints = endpointsByElement[e];
- if (endpoints) {
- var newEndpoints = [];
- for (var i = 0; i < endpoints.length; i++)
- if (endpoints[i] != endpoint) newEndpoints.push(endpoints[i]);
-
- endpointsByElement[e] = newEndpoints;
- }
- }
- _currentInstance.dragManager.endpointDeleted(endpoint);
- }
- };
-
- /*
- Function: deleteEveryEndpoint
- Deletes every <Endpoint>, and their associated <Connection>s, in this instance of jsPlumb. Does not unregister any event listeners (this is the only difference
- between this method and jsPlumb.reset).
-
- Returns:
- void
- */
- this.deleteEveryEndpoint = function() {
- for ( var id in endpointsByElement) {
- var endpoints = endpointsByElement[id];
- if (endpoints && endpoints.length) {
- for ( var i = 0; i < endpoints.length; i++) {
- _currentInstance.deleteEndpoint(endpoints[i]);
- }
- }
- }
- delete endpointsByElement;
- endpointsByElement = {};
- delete endpointsByUUID;
- endpointsByUUID = {};
- };
- var fireDetachEvent = function(jpc, doFireEvent, originalEvent) {
- // may have been given a connection, or in special cases, an object
- var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(),
- argIsConnection = jpc.constructor == connType,
- params = argIsConnection ? {
- connection:jpc,
- source : jpc.source, target : jpc.target,
- sourceId : jpc.sourceId, targetId : jpc.targetId,
- sourceEndpoint : jpc.endpoints[0], targetEndpoint : jpc.endpoints[1]
- } : jpc;
- if (doFireEvent) _currentInstance.fire("jsPlumbConnectionDetached", params, originalEvent);
- _currentInstance.anchorManager.connectionDetached(params);
- },
- /**
- fires an event to indicate an existing connection is being dragged.
- */
- fireConnectionDraggingEvent = function(jpc) {
- _currentInstance.fire("connectionDrag", jpc);
- },
- fireConnectionDragStopEvent = function(jpc) {
- _currentInstance.fire("connectionDragStop", jpc);
- };
- /*
- Function: detach
- Detaches and then removes a <Connection>. From 1.3.5 this method has been altered to remove support for
- specifying Connections by various parameters; you can now pass in a Connection as the first argument and
- an optional parameters object as a second argument. If you need the functionality this method provided
- before 1.3.5 then you should use the getConnections method to get the list of Connections to detach, and
- then iterate through them, calling this for each one.
-
- Parameters:
- connection - the <Connection> to detach
- params - optional parameters to the detach call. valid values here are
- fireEvent : defaults to false; indicates you want jsPlumb to fire a connection
- detached event. The thinking behind this is that if you made a programmatic
- call to detach an event, you probably don't need the callback.
- forceDetach : defaults to false. allows you to override any beforeDetach listeners that may be registered.
- Returns:
- true if successful, false if not.
- */
- this.detach = function() {
- if (arguments.length == 0) return;
- var connType = _currentInstance.Defaults.ConnectionType || _currentInstance.getDefaultConnectionType(),
- firstArgIsConnection = arguments[0].constructor == connType,
- params = arguments.length == 2 ? firstArgIsConnection ? (arguments[1] || {}) : arguments[0] : arguments[0],
- fireEvent = (params.fireEvent !== false),
- forceDetach = params.forceDetach,
- connection = firstArgIsConnection ? arguments[0] : params.connection;
- if (connection) {
- if (forceDetach || (connection.isDetachAllowed(connection)
- && connection.endpoints[0].isDetachAllowed(connection)
- && connection.endpoints[1].isDetachAllowed(connection))) {
- if (forceDetach || _currentInstance.checkCondition("beforeDetach", connection))
- connection.endpoints[0].detach(connection, false, true, fireEvent); // TODO check this param iscorrect for endpoint's detach method
- }
- }
- else {
- var _p = jsPlumb.extend( {}, params); // a backwards compatibility hack: source should be thought of as 'params' in this case.
- // test for endpoint uuids to detach
- if (_p.uuids) {
- _getEndpoint(_p.uuids[0]).detachFrom(_getEndpoint(_p.uuids[1]), fireEvent);
- } else if (_p.sourceEndpoint && _p.targetEndpoint) {
- _p.sourceEndpoint.detachFrom(_p.targetEndpoint);
- } else {
- var sourceId = _getId(_p.source),
- targetId = _getId(_p.target);
- _operation(sourceId, function(jpc) {
- if ((jpc.sourceId == sourceId && jpc.targetId == targetId) || (jpc.targetId == sourceId && jpc.sourceId == targetId)) {
- if (_currentInstance.checkCondition("beforeDetach", jpc)) {
- jpc.endpoints[0].detach(jpc, false, true, fireEvent);
- }
- }
- });
- }
- }
- };
- /*
- Function: detachAllConnections
- Removes all an element's Connections.
-
- Parameters:
- el - either the id of the element, or a selector for the element.
- params - optional parameters. alowed values:
- fireEvent : defaults to true, whether or not to fire the detach event.
-
- Returns:
- void
- */
- this.detachAllConnections = function(el, params) {
- params = params || {};
- el = _getElementObject(el);
- var id = _getAttribute(el, "id"),
- endpoints = endpointsByElement[id];
- if (endpoints && endpoints.length) {
- for ( var i = 0; i < endpoints.length; i++) {
- endpoints[i].detachAll(params.fireEvent);
- }
- }
- };
- /*
- Function: detachEveryConnection
- Remove all Connections from all elements, but leaves Endpoints in place.
- Parameters:
- params - optional params object containing:
- fireEvent : whether or not to fire detach events. defaults to true.
-
- Returns:
- void
-
- See Also:
- <removeEveryEndpoint>
- */
- this.detachEveryConnection = function(params) {
- params = params || {};
- for ( var id in endpointsByElement) {
- var endpoints = endpointsByElement[id];
- if (endpoints && endpoints.length) {
- for ( var i = 0; i < endpoints.length; i++) {
- endpoints[i].detachAll(params.fireEvent);
- }
- }
- }
- delete connectionsByScope;
- connectionsByScope = {};
- };
- /*
- Function: draggable
- Initialises the draggability of some element or elements. You should use this instead of y
- our library's draggable method so that jsPlumb can setup the appropriate callbacks. Your
- underlying library's drag method is always called from this method.
-
- Parameters:
- el - either an element id, a list of element ids, or a selector.
- options - options to pass through to the underlying library
-
- Returns:
- void
- */
- // TODO it would be nice if this supported a selector string, instead of an id.
- this.draggable = function(el, options) {
- if (typeof el == 'object' && el.length) {
- for ( var i = 0; i < el.length; i++) {
- var ele = _getElementObject(el[i]);
- if (ele) _initDraggableIfNecessary(ele, true, options);
- }
- }
- else if (el._nodes) { // TODO this is YUI specific; really the logic should be forced
- // into the library adapters (for jquery and mootools aswell)
- for ( var i = 0; i < el._nodes.length; i++) {
- var ele = _getElementObject(el._nodes[i]);
- if (ele) _initDraggableIfNecessary(ele, true, options);
- }
- }
- else {
- var ele = _getElementObject(el);
- if (ele) _initDraggableIfNecessary(ele, true, options);
- }
- };
- /*
- Function: extend
- Wraps the underlying library's extend functionality.
-
- Parameters:
- o1 - object to extend
- o2 - object to extend o1 with
-
- Returns:
- o1, extended with all properties from o2.
- */
- this.extend = function(o1, o2) {
- return jsPlumb.CurrentLibrary.extend(o1, o2);
- };
-
- /*
- * Function: getDefaultEndpointType
- * Returns the default Endpoint type. Used when someone wants to subclass Endpoint and have jsPlumb return instances of their subclass.
- * you would make a call like this in your class's constructor:
- * jsPlumb.getDefaultEndpointType().apply(this, arguments);
- *
- * Returns:
- * the default Endpoint function used by jsPlumb.
- */
- this.getDefaultEndpointType = function() {
- return Endpoint;
- };
-
- /*
- * Function: getDefaultConnectionType
- * Returns the default Connection type. Used when someone wants to subclass Connection and have jsPlumb return instances of their subclass.
- * you would make a call like this in your class's constructor:
- * jsPlumb.getDefaultConnectionType().apply(this, arguments);
- *
- * Returns:
- * the default Connection function used by jsPlumb.
- */
- this.getDefaultConnectionType = function() {
- return Connection;
- };
- // helpers for select/selectEndpoints
- var _setOperation = function(list, func, args, selector) {
- for (var i = 0; i < list.length; i++) {
- list[i][func].apply(list[i], args);
- }
- return selector(list);
- },
- _getOperation = function(list, func, args) {
- var out = [];
- for (var i = 0; i < list.length; i++) {
- out.push([ list[i][func].apply(list[i], args), list[i] ]);
- }
- return out;
- },
- setter = function(list, func, selector) {
- return function() {
- return _setOperation(list, func, arguments, selector);
- };
- },
- getter = function(list, func) {
- return function() {
- return _getOperation(list, func, arguments);
- };
- };
- /*
- * Function: getConnections
- * Gets all or a subset of connections currently managed by this jsPlumb instance. If only one scope is passed in to this method,
- * the result will be a list of connections having that scope (passing in no scope at all will result in jsPlumb assuming you want the
- * default scope). If multiple scopes are passed in, the return value will be a map of { scope -> [ connection... ] }.
- *
- * Parameters
- * scope - if the only argument to getConnections is a string, jsPlumb will treat that string as a scope filter, and return a list
- * of connections that are in the given scope. use '*' for all scopes.
- * options - if the argument is a JS object, you can specify a finer-grained filter:
- *
- * - *scope* may be a string specifying a single scope, or an array of strings, specifying multiple scopes.
- * - *source* either a string representing an element id, or a selector. constrains the result to connections having this source.
- * - *target* either a string representing an element id, or a selector. constrains the result to connections having this target.
- * flat - return results in a flat array (don't return an object whose keys are scopes and whose values are lists per scope).
- *
- */
- this.getConnections = function(options, flat) {
- if (!options) {
- options = {};
- } else if (options.constructor == String) {
- options = { "scope": options };
- }
- var prepareList = function(input) {
- var r = [];
- if (input) {
- if (typeof input == 'string') {
- if (input === "*") return input;
- r.push(input);
- }
- else
- r = input;
- }
- return r;
- },
- scope = options.scope || _currentInstance.getDefaultScope(),
- scopes = prepareList(scope),
- sources = prepareList(options.source),
- targets = prepareList(options.target),
- filter = function(list, value) {
- if (list === "*") return true;
- return list.length > 0 ? _indexOf(list, value) != -1 : true;
- },
- results = (!flat && scopes.length > 1) ? {} : [],
- _addOne = function(scope, obj) {
- if (!flat && scopes.length > 1) {
- var ss = results[scope];
- if (ss == null) {
- ss = []; results[scope] = ss;
- }
- ss.push(obj);
- } else results.push(obj);
- };
- for ( var i in connectionsByScope) {
- if (filter(scopes, i)) {
- for ( var j = 0; j < connectionsByScope[i].length; j++) {
- var c = connectionsByScope[i][j];
- if (filter(sources, c.sourceId) && filter(targets, c.targetId))
- _addOne(i, c);
- }
- }
- }
- return results;
- };
-
- var _makeConnectionSelectHandler = function(list) {
- //var
- return {
- // setters
- setHover:setter(list, "setHover", _makeConnectionSelectHandler),
- removeAllOverlays:setter(list, "removeAllOverlays", _makeConnectionSelectHandler),
- setLabel:setter(list, "setLabel", _makeConnectionSelectHandler),
- addOverlay:setter(list, "addOverlay", _makeConnectionSelectHandler),
- removeOverlay:setter(list, "removeOverlay", _makeConnectionSelectHandler),
- removeOverlays:setter(list, "removeOverlays", _makeConnectionSelectHandler),
- showOverlay:setter(list, "showOverlay", _makeConnectionSelectHandler),
- hideOverlay:setter(list, "hideOverlay", _makeConnectionSelectHandler),
- showOverlays:setter(list, "showOverlays", _makeConnectionSelectHandler),
- hideOverlays:setter(list, "hideOverlays", _makeConnectionSelectHandler),
- setPaintStyle:setter(list, "setPaintStyle", _makeConnectionSelectHandler),
- setHoverPaintStyle:setter(list, "setHoverPaintStyle", _makeConnectionSelectHandler),
- setDetachable:setter(list, "setDetachable", _makeConnectionSelectHandler),
- setConnector:setter(list, "setConnector", _makeConnectionSelectHandler),
- setParameter:setter(list, "setParameter", _makeConnectionSelectHandler),
- setParameters:setter(list, "setParameters", _makeConnectionSelectHandler),
-
- detach:function() {
- for (var i = 0; i < list.length; i++)
- _currentInstance.detach(list[i]);
- },
- // getters
- getLabel:getter(list, "getLabel"),
- getOverlay:getter(list, "getOverlay"),
- isHover:getter(list, "isHover"),
- isDetachable:getter(list, "isDetachable"),
- getParameter:getter(list, "getParameter"),
- getParameters:getter(list, "getParameters"),
- // util
- length:list.length,
- each:function(f) {
- for (var i = 0; i < list.length; i++) {
- f(list[i]);
- }
- return _makeConnectionSelectHandler(list);
- },
- get:function(idx) {
- return list[idx];
- }
- };
- };
-
- this.select = function(params) {
- params = params || {};
- params.scope = params.scope || "*";
- var c = _currentInstance.getConnections(params, true);
- return _makeConnectionSelectHandler(c);
- };
- /*
- * Function: getAllConnections
- * Gets all connections, as a map of { scope -> [ connection... ] }.
- */
- this.getAllConnections = function() {
- return connectionsByScope;
- };
- /*
- * Function: getDefaultScope
- * Gets the default scope for connections and endpoints. a scope defines a type of endpoint/connection; supplying a
- * scope to an endpoint or connection allows you to support different
- * types of connections in the same UI. but if you're only interested in
- * one type of connection, you don't need to supply a scope. this method
- * will probably be used by very few people; it's good for testing
- * though.
- */
- this.getDefaultScope = function() {
- return DEFAULT_SCOPE;
- };
- /*
- Function: getEndpoint
- Gets an Endpoint by UUID
-
- Parameters:
- uuid - the UUID for the Endpoint
-
- Returns:
- Endpoint with the given UUID, null if nothing found.
- */
- this.getEndpoint = _getEndpoint;
-
- /**
- * Function:getEndpoints
- * Gets the list of Endpoints for a given selector, or element id.
- * @param el
- * @return
- */
- this.getEndpoints = function(el) {
- return endpointsByElement[_getId(el)];
- };
- /*
- * Gets an element's id, creating one if necessary. really only exposed
- * for the lib-specific functionality to access; would be better to pass
- * the current instance into the lib-specific code (even though this is
- * a static call. i just don't want to expose it to the public API).
- */
- this.getId = _getId;
- this.getOffset = function(id) {
- var o = offsets[id];
- return _updateOffset({elId:id});
- };
-
- this.getSelector = function(spec) {
- return jsPlumb.CurrentLibrary.getSelector(spec);
- };
-
- this.getSize = function(id) {
- var s = sizes[id];
- if (!s) _updateOffset({elId:id});
- return sizes[id];
- };
-
- this.appendElement = _appendElement;
-
- var _hoverSuspended = false;
- this.isHoverSuspended = function() { return _hoverSuspended; };
- this.setHoverSuspended = function(s) { _hoverSuspended = s; };
- /*
- Function: hide
- Sets an element's connections to be hidden.
-
- Parameters:
- el - either the id of the element, or a selector for the element.
- changeEndpoints - whether not to also hide endpoints on the element. by default this is false.
-
- Returns:
- void
- */
- this.hide = function(el, changeEndpoints) {
- _setVisible(el, "none", changeEndpoints);
- };
-
- // exposed for other objects to use to get a unique id.
- this.idstamp = _idstamp;
-
- /**
- * callback from the current library to tell us to prepare ourselves (attach
- * mouse listeners etc; can't do that until the library has provided a bind method)
- * @return
- */
- this.init = function() {
- if (!initialized) {
- _currentInstance.setRenderMode(_currentInstance.Defaults.RenderMode); // calling the method forces the capability logic to be run.
-
- var bindOne = function(event) {
- jsPlumb.CurrentLibrary.bind(document, event, function(e) {
- if (!_currentInstance.currentlyDragging && renderMode == jsPlumb.CANVAS) {
- // try connections first
- for (var scope in connectionsByScope) {
- var c = connectionsByScope[scope];
- for (var i = 0; i < c.length; i++) {
- var t = c[i].connector[event](e);
- if (t) return;
- }
- }
- for (var el in endpointsByElement) {
- var ee = endpointsByElement[el];
- for (var i = 0; i < ee.length; i++) {
- if (ee[i].endpoint[event](e)) return;
- }
- }
- }
- });
- };
- bindOne("click");bindOne("dblclick");bindOne("mousemove");bindOne("mousedown");bindOne("mouseup");bindOne("contextmenu");
-
- initialized = true;
- _currentInstance.fire("ready");
- }
- };
-
- this.log = log;
- this.jsPlumbUIComponent = jsPlumbUIComponent;
- this.EventGenerator = EventGenerator;
- /*
- * Creates an anchor with the given params.
- *
- *
- * Returns: The newly created Anchor.
- */
- this.makeAnchor = function() {
- if (arguments.length == 0) return null;
- var specimen = arguments[0], elementId = arguments[1], jsPlumbInstance = arguments[2], newAnchor = null;
- if (!jsPlumbInstance)
- throw "NO JSPLUMB SET";
- // if it appears to be an anchor already...
- if (specimen.compute && specimen.getOrientation) return specimen; //TODO hazy here about whether it should be added or is already added somehow.
- // is it the name of an anchor type?
- else if (typeof specimen == "string") {
- newAnchor = jsPlumb.Anchors[arguments[0]]({elementId:elementId, jsPlumbInstance:_currentInstance});
- }
- // is it an array? it will be one of:
- // an array of [name, params] - this defines a single anchor
- // an array of arrays - this defines some dynamic anchors
- // an array of numbers - this defines a single anchor.
- else if (specimen.constructor == Array) {
- if (specimen[0].constructor == Array || specimen[0].constructor == String) {
- if (specimen.length == 2 && specimen[0].constructor == String && specimen[1].constructor == Object) {
- var pp = jsPlumb.extend({elementId:elementId, jsPlumbInstance:_currentInstance}, specimen[1]);
- newAnchor = jsPlumb.Anchors[specimen[0]](pp);
- }
- else
- newAnchor = new DynamicAnchor(specimen, null, elementId);
- }
- else {
- var anchorParams = {
- x:specimen[0], y:specimen[1],
- orientation : (specimen.length >= 4) ? [ specimen[2], specimen[3] ] : [0,0],
- offsets : (specimen.length == 6) ? [ specimen[4], specimen[5] ] : [ 0, 0 ],
- elementId:elementId
- };
- newAnchor = new Anchor(anchorParams);
- newAnchor.clone = function() { return new Anchor(anchorParams); };
- }
- }
-
- if (!newAnchor.id) newAnchor.id = "anchor_" + _idstamp();
- return newAnchor;
- };
- /**
- * makes a list of anchors from the given list of types or coords, eg
- * ["TopCenter", "RightMiddle", "BottomCenter", [0, 1, -1, -1] ]
- */
- this.makeAnchors = function(types, elementId, jsPlumbInstance) {
- var r = [];
- for ( var i = 0; i < types.length; i++) {
- if (typeof types[i] == "string")
- r.push(jsPlumb.Anchors[types[i]]({elementId:elementId, jsPlumbInstance:jsPlumbInstance}));
- else if (types[i].constructor == Array)
- r.push(_currentInstance.makeAnchor(types[i], elementId, jsPlumbInstance));
- }
- return r;
- };
- /**
- * Makes a dynamic anchor from the given list of anchors (which may be in shorthand notation as strings or dimension arrays, or Anchor
- * objects themselves) and the given, optional, anchorSelector function (jsPlumb uses a default if this is not provided; most people will
- * not need to provide this - i think).
- */
- this.makeDynamicAnchor = function(anchors, anchorSelector) {
- return new DynamicAnchor(anchors, anchorSelector);
- };
-
- /**
- * Function: makeTarget
- * Makes some DOM element a Connection target, allowing you to drag connections to it
- * without having to register any Endpoints on it first. When a Connection is established,
- * the endpoint spec that was passed in to this method is used to create a suitable
- * Endpoint (the default will be used if you do not provide one).
- *
- * Parameters:
- * el - string id or element selector for the element to make a target.
- * params - JS object containing parameters:
- * endpoint optional. specification of an endpoint to create when a connection is created.
- * scope optional. scope for the drop zone.
- * dropOptions optional. same stuff as you would pass to dropOptions of an Endpoint definition.
- * deleteEndpointsOnDetach optional, defaults to true. whether or not to delete
- * any Endpoints created by a connection to this target if
- * the connection is subsequently detached. this will not
- * remove Endpoints that have had more Connections attached
- * to them after they were created.
- *
- *
- */
- var _targetEndpointDefinitions = {},
- _targetEndpoints = {},
- _targetEndpointsUnique = {},
- _setEndpointPaintStylesAndAnchor = function(ep, epIndex) {
- ep.paintStyle = ep.paintStyle ||
- _currentInstance.Defaults.EndpointStyles[epIndex] ||
- _currentInstance.Defaults.EndpointStyle ||
- jsPlumb.Defaults.EndpointStyles[epIndex] ||
- jsPlumb.Defaults.EndpointStyle;
- ep.hoverPaintStyle = ep.hoverPaintStyle ||
- _currentInstance.Defaults.EndpointHoverStyles[epIndex] ||
- _currentInstance.Defaults.EndpointHoverStyle ||
- jsPlumb.Defaults.EndpointHoverStyles[epIndex] ||
- jsPlumb.Defaults.EndpointHoverStyle;
- ep.anchor = ep.anchor ||
- _currentInstance.Defaults.Anchors[epIndex] ||
- _currentInstance.Defaults.Anchor ||
- jsPlumb.Defaults.Anchors[epIndex] ||
- jsPlumb.Defaults.Anchor;
-
- ep.endpoint = ep.endpoint ||
- _currentInstance.Defaults.Endpoints[epIndex] ||
- _currentInstance.Defaults.Endpoint ||
- jsPlumb.Defaults.Endpoints[epIndex] ||
- jsPlumb.Defaults.Endpoint;
- };
- this.makeTarget = function(el, params, referenceParams) {
-
- var p = jsPlumb.extend({_jsPlumb:_currentInstance}, referenceParams);
- jsPlumb.extend(p, params);
- _setEndpointPaintStylesAndAnchor(p, 1);
- var jpcl = jsPlumb.CurrentLibrary,
- targetScope = p.scope || _currentInstance.Defaults.Scope,
- deleteEndpointsOnDetach = !(p.deleteEndpointsOnDetach === false),
- _doOne = function(_el) {
-
- // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these,
- // and use the endpoint definition if found.
- var elid = _getId(_el);
- _targetEndpointDefinitions[elid] = p;
- _targetEndpointsUnique[elid] = p.uniqueEndpoint,
- proxyComponent = new jsPlumbUIComponent(p);
-
- var dropOptions = jsPlumb.extend({}, p.dropOptions || {}),
- _drop = function() {
- var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments);
- _currentInstance.currentlyDragging = false;
- var draggable = _getElementObject(jpcl.getDragObject(arguments)),
- id = _getAttribute(draggable, "dragId"),
- // restore the original scope if necessary (issue 57)
- scope = _getAttribute(draggable, "originalScope"),
- jpc = floatingConnections[id],
- source = jpc.endpoints[0],
- _endpoint = p.endpoint ? jsPlumb.extend({}, p.endpoint) : {};
- // unlock the source anchor to allow it to refresh its position if necessary
- source.anchor.locked = false;
-
- if (scope) jpcl.setDragScope(draggable, scope);
-
- // check if drop is allowed here.
- //var _continue = jpc.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope);
- var _continue = proxyComponent.isDropAllowed(jpc.sourceId, _getId(_el), jpc.scope);
-
- // regardless of whether the connection is ok, reconfigure the existing connection to
- // point at the current info. we need this to be correct for the detach event that will follow.
- // clear the source endpoint from the list to detach. we will detach this connection at this
- // point, but we want to keep the source endpoint. the target is a floating endpoint and should
- // be removed. TODO need to figure out whether this code can result in endpoints kicking around
- // when they shouldnt be. like is this a full detach of a connection? can it be?
- if (jpc.endpointsToDeleteOnDetach) {
- if (source === jpc.endpointsToDeleteOnDetach[0])
- jpc.endpointsToDeleteOnDetach[0] = null;
- else if (source === jpc.endpointsToDeleteOnDetach[1])
- jpc.endpointsToDeleteOnDetach[1] = null;
- }
- // reinstate any suspended endpoint; this just puts the connection back into
- // a state in which it will report sensible values if someone asks it about
- // its target. we're going to throw this connection away shortly so it doesnt matter
- // if we manipulate it a bit.
- if (jpc.suspendedEndpoint) {
- jpc.targetId = jpc.suspendedEndpoint.elementId;
- jpc.target = jpcl.getElementObject(jpc.suspendedEndpoint.elementId);
- jpc.endpoints[1] = jpc.suspendedEndpoint;
- }
-
- if (_continue) {
-
- // detach this connection from the source.
- source.detach(jpc, false, true, false);//source.endpointWillMoveAfterConnection);
-
- // make a new Endpoint for the target
- //var newEndpoint = _currentInstance.addEndpoint(_el, _endpoint);
-
- var newEndpoint = _targetEndpoints[elid] || _currentInstance.addEndpoint(_el, p);
- if (p.uniqueEndpoint) _targetEndpoints[elid] = newEndpoint; // may of course just store what it just pulled out. that's ok.
-
- // if the anchor has a 'positionFinder' set, then delegate to that function to find
- // out where to locate the anchor.
- if (newEndpoint.anchor.positionFinder != null) {
- var dropPosition = jpcl.getUIPosition(arguments),
- elPosition = jpcl.getOffset(_el),
- elSize = jpcl.getSize(_el),
- ap = newEndpoint.anchor.positionFinder(dropPosition, elPosition, elSize, newEndpoint.anchor.constructorParams);
- newEndpoint.anchor.x = ap[0];
- newEndpoint.anchor.y = ap[1];
- // now figure an orientation for it..kind of hard to know what to do actually. probably the best thing i can do is to
- // support specifying an orientation in the anchor's spec. if one is not supplied then i will make the orientation
- // be what will cause the most natural link to the source: it will be pointing at the source, but it needs to be
- // specified in one axis only, and so how to make that choice? i think i will use whichever axis is the one in which
- // the target is furthest away from the source.
- }
- var c = _currentInstance.connect({
- source:source,
- target:newEndpoint,
- scope:scope,
- previousConnection:jpc,
- container:jpc.parent,
- deleteEndpointsOnDetach:deleteEndpointsOnDetach,
- // 'endpointWillMoveAfterConnection' is set by the makeSource function, and it indicates that the
- // given endpoint will actually transfer from the element it is currently attached to to some other
- // element after a connection has been established. in that case, we do not want to fire the
- // connection event, since it will have the wrong data in it; makeSource will do it for us.
- // this is controlled by the 'parent' parameter on a makeSource call.
- doNotFireConnectionEvent:source.endpointWillMoveAfterConnection
- });
- if (deleteEndpointsOnDetach)
- c.endpointsToDeleteOnDetach = [ source, newEndpoint ];
- c.repaint();
- }
- // if not allowed to drop...
- else {
- // TODO this code is identical (pretty much) to what happens when a connection
- // dragged from a normal endpoint is in this situation. refactor.
- // is this an existing connection, and will we reattach?
- if (jpc.suspendedEndpoint) {
- if (source.isReattach) {
- jpc.setHover(false);
- jpc.floatingAnchorIndex = null;
- jpc.suspendedEndpoint.addConnection(jpc);
- _currentInstance.repaint(source.elementId);
- }
- else
- source.detach(jpc, false, true, true, originalEvent); // otherwise, detach the connection and tell everyone about it.
- }
-
- }
- };
-
- var dropEvent = jpcl.dragEvents['drop'];
- dropOptions["scope"] = dropOptions["scope"] || targetScope;
- dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], _drop);
-
- jpcl.initDroppable(_el, dropOptions, true);
- };
-
- el = _convertYUICollection(el);
-
- var inputs = el.length && el.constructor != String ? el : [ el ];
-
- for (var i = 0; i < inputs.length; i++) {
- _doOne(_getElementObject(inputs[i]));
- }
- };
-
- /**
- * helper method to make a list of elements drop targets.
- * @param els
- * @param params
- * @param referenceParams
- * @return
- */
- this.makeTargets = function(els, params, referenceParams) {
- for ( var i = 0; i < els.length; i++) {
- _currentInstance.makeTarget(els[i], params, referenceParams);
- }
- };
-
- /**
- * Function: makeSource
- * Makes some DOM element a Connection source, allowing you to drag connections from it
- * without having to register any Endpoints on it first. When a Connection is established,
- * the endpoint spec that was passed in to this method is used to create a suitable
- * Endpoint (the default will be used if you do not provide one).
- *
- * Parameters:
- * el - string id or element selector for the element to make a source.
- * params - JS object containing parameters:
- * endpoint optional. specification of an endpoint to create when a connection is created.
- * parent optional. the element to add Endpoints to when a Connection is established. if you omit this,
- * Endpoints will be added to 'el'.
- * scope optional. scope for the connections dragged from this element.
- * dragOptions optional. same stuff as you would pass to dragOptions of an Endpoint definition.
- * deleteEndpointsOnDetach optional, defaults to false. whether or not to delete
- * any Endpoints created by a connection from this source if
- * the connection is subsequently detached. this will not
- * remove Endpoints that have had more Connections attached
- * to them after they were created.
- *
- *
- */
- var _sourceEndpointDefinitions = {},
- _sourceEndpoints = {},
- _sourceEndpointsUnique = {};
- this.makeSource = function(el, params, referenceParams) {
- var p = jsPlumb.extend({}, referenceParams);
- jsPlumb.extend(p, params);
- _setEndpointPaintStylesAndAnchor(p, 0);
- var jpcl = jsPlumb.CurrentLibrary,
- _doOne = function(_el) {
- // get the element's id and store the endpoint definition for it. jsPlumb.connect calls will look for one of these,
- // and use the endpoint definition if found.
- var elid = _getId(_el),
- parent = p.parent,
- idToRegisterAgainst = parent != null ? _currentInstance.getId(jpcl.getElementObject(parent)) : elid;
-
- _sourceEndpointDefinitions[idToRegisterAgainst] = p;
- _sourceEndpointsUnique[idToRegisterAgainst] = p.uniqueEndpoint;
- var stopEvent = jpcl.dragEvents["stop"],
- dragEvent = jpcl.dragEvents["drag"],
- dragOptions = jsPlumb.extend({ }, p.dragOptions || {}),
- existingDrag = dragOptions.drag,
- existingStop = dragOptions.stop,
- ep = null,
- endpointAddedButNoDragYet = false;
- // set scope if its not set in dragOptions but was passed in in params
- dragOptions["scope"] = dragOptions["scope"] || p.scope;
- dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], function() {
- if (existingDrag) existingDrag.apply(this, arguments);
- endpointAddedButNoDragYet = false;
- });
-
- dragOptions[stopEvent] = function() {
- if (existingStop) existingStop.apply(this, arguments);
- //_currentlyDown = false;
- _currentInstance.currentlyDragging = false;
-
- if (ep.connections.length == 0)
- _currentInstance.deleteEndpoint(ep);
- else {
-
- jpcl.unbind(ep.canvas, "mousedown");
-
- // reset the anchor to the anchor that was initially provided. the one we were using to drag
- // the connection was just a placeholder that was located at the place the user pressed the
- // mouse button to initiate the drag.
- var anchorDef = p.anchor || _currentInstance.Defaults.Anchor,
- oldAnchor = ep.anchor;
- ep.anchor = _currentInstance.makeAnchor(anchorDef, elid, _currentInstance);
-
-
- if (p.parent) {
- var parent = jpcl.getElementObject(p.parent);
- if (parent) {
- var currentId = ep.elementId;
- ep.setElement(parent);
- ep.endpointWillMoveAfterConnection = false;
- _currentInstance.anchorManager.rehomeEndpoint(currentId, parent);
- ep.connections[0].previousConnection = null;
- _currentInstance.anchorManager.connectionDetached({
- sourceId:ep.connections[0].sourceId,
- targetId:ep.connections[0].targetId,
- connection:ep.connections[0]
- });
- _finaliseConnection(ep.connections[0]);
- }
- }
-
- ep.repaint();
- _currentInstance.repaint(ep.elementId);
- _currentInstance.repaint(ep.connections[0].targetId);
- }
- };
- // when the user presses the mouse, add an Endpoint
- var mouseDownListener = function(e) {
- // make sure we have the latest offset for this div
- var myOffsetInfo = _updateOffset({elId:elid});
- var x = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width,
- y = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height,
- parentX = x,
- parentY = y;
-
-
- // if there is a parent, the endpoint will actually be added to it now, rather than the div
- // that was the source. in that case, we have to adjust the anchor position so it refers to
- // the parent.
- if (p.parent) {
- var pEl = jsPlumb.CurrentLibrary.getElementObject(p.parent),
- pId = _getId(pEl);
- myOffsetInfo = _updateOffset({elId:pId});
- parentX = ((e.pageX || e.page.x) - myOffsetInfo.left) / myOffsetInfo.width,
- parentY = ((e.pageY || e.page.y) - myOffsetInfo.top) / myOffsetInfo.height;
- }
-
- // we need to override the anchor in here, and force 'isSource', but we don't want to mess with
- // the params passed in, because after a connection is established we're going to reset the endpoint
- // to have the anchor we were given.
- var tempEndpointParams = {};
- jsPlumb.extend(tempEndpointParams, p);
- tempEndpointParams.isSource = true;
- tempEndpointParams.anchor = [x,y,0,0];
- tempEndpointParams.parentAnchor = [ parentX, parentY, 0, 0 ];
- tempEndpointParams.dragOptions = dragOptions;
- // if a parent was given we need to turn that into a "container" argument. this is, by default,
- // the parent of the element we will move to, so parent of p.parent in this case. however, if
- // the user has specified a 'container' on the endpoint definition or on
- // the defaults, we should use that.
- if (p.parent) {
- var potentialParent = tempEndpointParams.container || _currentInstance.Defaults.Container;
- if (potentialParent)
- tempEndpointParams.container = potentialParent;
- else
- tempEndpointParams.container = jsPlumb.CurrentLibrary.getParent(p.parent);
- }
-
- ep = _currentInstance.addEndpoint(elid, tempEndpointParams);
- endpointAddedButNoDragYet = true;
- // we set this to prevent connections from firing attach events before this function has had a chance
- // to move the endpoint.
- ep.endpointWillMoveAfterConnection = p.parent != null;
- ep.endpointWillMoveTo = p.parent ? jpcl.getElementObject(p.parent) : null;
- var _delTempEndpoint = function() {
- // this mouseup event is fired only if no dragging occurred, by jquery and yui, but for mootools
- // it is fired even if dragging has occurred, in which case we would blow away a perfectly
- // legitimate endpoint, were it not for this check. the flag is set after adding an
- // endpoint and cleared in a drag listener we set in the dragOptions above.
- if(endpointAddedButNoDragYet) {
- _currentInstance.deleteEndpoint(ep);
- }
- };
- _currentInstance.registerListener(ep.canvas, "mouseup", _delTempEndpoint);
- _currentInstance.registerListener(_el, "mouseup", _delTempEndpoint);
-
- // and then trigger its mousedown event, which will kick off a drag, which will start dragging
- // a new connection from this endpoint.
- jpcl.trigger(ep.canvas, "mousedown", e);
- };
-
- // register this on jsPlumb so that it can be cleared by a reset.
- _currentInstance.registerListener(_el, "mousedown", mouseDownListener);
- };
-
- el = _convertYUICollection(el);
-
- var inputs = el.length && el.constructor != String ? el : [ el ];
-
- for (var i = 0; i < inputs.length; i++) {
- _doOne(_getElementObject(inputs[i]));
- }
- };
-
- /**
- * helper method to make a list of elements connection sources.
- * @param els
- * @param params
- * @param referenceParams
- * @return
- */
- this.makeSources = function(els, params, referenceParams) {
- for ( var i = 0; i < els.length; i++) {
- _currentInstance.makeSource(els[i], params, referenceParams);
- }
- };
-
- /*
- Function: ready
- Helper method to bind a function to jsPlumb's ready event.
- */
- this.ready = function(fn) {
- _currentInstance.bind("ready", fn);
- },
- /*
- Function: repaint
- Repaints an element and its connections. This method gets new sizes for the elements before painting anything.
-
- Parameters:
- el - either the id of the element or a selector representing the element.
-
- Returns:
- void
-
- See Also:
- <repaintEverything>
- */
- this.repaint = function(el) {
- var _processElement = function(el) { _draw(_getElementObject(el)); };
- // support both lists...
- if (typeof el == 'object')
- for ( var i = 0; i < el.length; i++) _processElement(el[i]);
- else // ...and single strings.
- _processElement(el);
- };
- /*
- Function: repaintEverything
- Repaints all connections.
-
- Returns:
- void
-
- See Also:
- <repaint>
- */
- this.repaintEverything = function() {
- var timestamp = _timestamp();
- for ( var elId in endpointsByElement) {
- _draw(_getElementObject(elId), null, timestamp);
- }
- };
- /*
- Function: removeAllEndpoints
- Removes all Endpoints associated with a given element. Also removes all Connections associated with each Endpoint it removes.
-
- Parameters:
- el - either an element id, or a selector for an element.
-
- Returns:
- void
-
- See Also:
- <removeEndpoint>
- */
- this.removeAllEndpoints = function(el) {
- var elId = _getAttribute(el, "id"),
- ebe = endpointsByElement[elId];
- if (ebe) {
- for ( var i = 0; i < ebe.length; i++)
- _currentInstance.deleteEndpoint(ebe[i]);
- }
- endpointsByElement[elId] = [];
- };
- /*
- Removes every Endpoint in this instance of jsPlumb.
- @deprecated use deleteEveryEndpoint instead
- */
- this.removeEveryEndpoint = this.deleteEveryEndpoint;
-
- /*
- Removes the given Endpoint from the given element.
- @deprecated Use jsPlumb.deleteEndpoint instead (and note you dont need to supply the element. it's irrelevant).
- */
- this.removeEndpoint = function(el, endpoint) {
- _currentInstance.deleteEndpoint(endpoint);
- };
- var _registeredListeners = {},
- _unbindRegisteredListeners = function() {
- for (var i in _registeredListeners) {
- for (var j = 0; j < _registeredListeners[i].length; j++) {
- var info = _registeredListeners[i][j];
- jsPlumb.CurrentLibrary.unbind(info.el, info.event, info.listener);
- }
- }
- _registeredListeners = {};
- };
- // internal register listener method. gives us a hook to clean things up
- // with if the user calls jsPlumb.reset.
- this.registerListener = function(el, type, listener) {
- jsPlumb.CurrentLibrary.bind(el, type, listener);
- _addToList(_registeredListeners, type, {el:el, event:type, listener:listener});
- };
- /*
- Function:reset
- Removes all endpoints and connections and clears the listener list. To keep listeners call jsPlumb.deleteEveryEndpoint instead of this.
- */
- this.reset = function() {
- _currentInstance.deleteEveryEndpoint();
- _currentInstance.clearListeners();
- _targetEndpointDefinitions = {};
- _targetEndpoints = {};
- _targetEndpointsUnique = {};
- _sourceEndpointDefinitions = {};
- _sourceEndpoints = {};
- _sourceEndpointsUnique = {};
- _unbindRegisteredListeners();
- _currentInstance.anchorManager.reset();
- _currentInstance.dragManager.reset();
- };
- /*
- Function: setAutomaticRepaint
- Sets/unsets automatic repaint on window resize.
-
- Parameters:
- value - whether or not to automatically repaint when the window is resized.
-
- Returns: void
- *
- this.setAutomaticRepaint = function(value) {
- automaticRepaint = value;
- };*/
- /*
- * Function: setDefaultScope
- * Sets the default scope for Connections and Endpoints. A scope defines a type of Endpoint/Connection; supplying a
- * scope to an Endpoint or Connection allows you to support different
- * types of Connections in the same UI. If you're only interested in
- * one type of Connection, you don't need to supply a scope. This method
- * will probably be used by very few people; it just instructs jsPlumb
- * to use a different key for the default scope.
- *
- * Parameters:
- * scope - scope to set as default.
- */
- this.setDefaultScope = function(scope) {
- DEFAULT_SCOPE = scope;
- };
- /*
- * Function: setDraggable
- * Sets whether or not a given element is
- * draggable, regardless of what any jsPlumb command may request.
- *
- * Parameters:
- * el - either the id for the element, or a selector representing the element.
- *
- * Returns:
- * void
- */
- this.setDraggable = _setDraggable;
- /*
- * Function: setId
- * Changes the id of some element, adjusting all connections and endpoints
- *
- * Parameters:
- * el - a selector, a DOM element, or a string.
- * newId - string.
- */
- this.setId = function(el, newId, doNotSetAttribute) {
-
- var id = el.constructor == String ? el : _currentInstance.getId(el),
- sConns = _currentInstance.getConnections({source:id, scope:'*'}, true),
- tConns = _currentInstance.getConnections({target:id, scope:'*'}, true);
- newId = "" + newId;
-
- if (!doNotSetAttribute) {
- el = jsPlumb.CurrentLibrary.getElementObject(id);
- jsPlumb.CurrentLibrary.setAttribute(el, "id", newId);
- }
-
- el = jsPlumb.CurrentLibrary.getElementObject(newId);
-
- endpointsByElement[newId] = endpointsByElement[id] || [];
- for (var i = 0; i < endpointsByElement[newId].length; i++) {
- endpointsByElement[newId][i].elementId = newId;
- endpointsByElement[newId][i].element = el;
- endpointsByElement[newId][i].anchor.elementId = newId;
- }
- delete endpointsByElement[id];
- _currentInstance.anchorManager.changeId(id, newId);
- var _conns = function(list, epIdx, type) {
- for (var i = 0; i < list.length; i++) {
- list[i].endpoints[epIdx].elementId = newId;
- list[i].endpoints[epIdx].element = el;
- list[i][type + "Id"] = newId;
- list[i][type] = el;
- }
- };
- _conns(sConns, 0, "source");
- _conns(tConns, 1, "target");
- };
- /*
- * Function: setIdChanged
- * Notify jsPlumb that the element with oldId has had its id changed to newId.
- */
- this.setIdChanged = function(oldId, newId) {
- _currentInstance.setId(oldId, newId, true);
- };
- this.setDebugLog = function(debugLog) {
- log = debugLog;
- };
- /*
- * Function: setRepaintFunction
- * Sets the function to fire when the window size has changed and a repaint was fired.
- *
- * Parameters:
- * f - Function to execute.
- *
- * Returns: void
- */
- this.setRepaintFunction = function(f) {
- repaintFunction = f;
- };
- /*
- * Function: setSuspendDrawing
- * Suspends drawing operations. This can be used when you have a lot of connections to make or endpoints to register;
- * it will save you a lot of time.
- */
- this.setSuspendDrawing = _setSuspendDrawing;
-
- /*
- * Constant for use with the setRenderMode method
- */
- this.CANVAS = "canvas";
-
- /*
- * Constant for use with the setRenderMode method
- */
- this.SVG = "svg";
-
- this.VML = "vml";
-
- /*
- * Function: setRenderMode
- * Sets render mode: jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML. jsPlumb will fall back to VML if it determines that
- * what you asked for is not supported (and that VML is). If you asked for VML but the browser does
- * not support it, jsPlumb uses SVG.
- *
- * Returns:
- * the render mode that jsPlumb set, which of course may be different from that requested.
- */
- this.setRenderMode = function(mode) {
- if (mode)
- mode = mode.toLowerCase();
- else
- return;
- if (mode !== jsPlumb.CANVAS && mode !== jsPlumb.SVG && mode !== jsPlumb.VML) throw new Error("render mode must be one of jsPlumb.CANVAS, jsPlumb.SVG or jsPlumb.VML");
- // now test we actually have the capability to do this.
- if (mode === jsPlumb.CANVAS && canvasAvailable)
- renderMode = jsPlumb.CANVAS;
- else if (mode === jsPlumb.SVG && svgAvailable)
- renderMode = jsPlumb.SVG;
- else if (vmlAvailable)
- renderMode = jsPlumb.VML;
-
- return renderMode;
- };
-
- this.getRenderMode = function() { return renderMode; };
- /*
- * Function: show
- * Sets an element's connections to be visible.
- *
- * Parameters:
- * el - either the id of the element, or a selector for the element.
- * changeEndpoints - whether or not to also change the visible state of the endpoints on the element. this also has a bearing on
- * other connections on those endpoints: if their other endpoint is also visible, the connections are made visible.
- *
- * Returns:
- * void
- */
- this.show = function(el, changeEndpoints) {
- _setVisible(el, "block", changeEndpoints);
- };
- /*
- * Function: sizeCanvas
- * Helper to size a canvas. You would typically use
- * this when writing your own Connector or Endpoint implementation.
- *
- * Parameters:
- * x - [int] x position for the Canvas origin
- * y - [int] y position for the Canvas origin
- * w - [int] width of the canvas
- * h - [int] height of the canvas
- *
- * Returns:
- * void
- */
- this.sizeCanvas = function(canvas, x, y, w, h) {
- if (canvas) {
- canvas.style.height = h + "px";
- canvas.height = h;
- canvas.style.width = w + "px";
- canvas.width = w;
- canvas.style.left = x + "px";
- canvas.style.top = y + "px";
- }
- };
- /**
- * gets some test hooks. nothing writable.
- */
- this.getTestHarness = function() {
- return {
- endpointsByElement : endpointsByElement,
- endpointCount : function(elId) {
- var e = endpointsByElement[elId];
- return e ? e.length : 0;
- },
- connectionCount : function(scope) {
- scope = scope || DEFAULT_SCOPE;
- var c = connectionsByScope[scope];
- return c ? c.length : 0;
- },
- //findIndex : _findIndex,
- getId : _getId,
- makeAnchor:self.makeAnchor,
- makeDynamicAnchor:self.makeDynamicAnchor
- };
- };
- /**
- * Toggles visibility of an element's connections. kept for backwards
- * compatibility
- */
- this.toggle = _toggleVisible;
- /*
- * Function: toggleVisible
- * Toggles visibility of an element's Connections.
- *
- * Parameters:
- * el - either the element's id, or a selector representing the element.
- * changeEndpoints - whether or not to also toggle the endpoints on the element.
- *
- * Returns:
- * void, but should be updated to return the current state
- */
- // TODO: update this method to return the current state.
- this.toggleVisible = _toggleVisible;
- /*
- * Function: toggleDraggable
- * Toggles draggability (sic?) of an element's Connections.
- *
- * Parameters:
- * el - either the element's id, or a selector representing the element.
- *
- * Returns:
- * The current draggable state.
- */
- this.toggleDraggable = _toggleDraggable;
- /*
- * Function: unload
- * Unloads jsPlumb, deleting all storage. You should call this from an onunload attribute on the <body> element.
- *
- * Returns:
- * void
- */
- this.unload = function() {
- // this used to do something, but it turns out that what it did was nothing.
- // now it exists only for backwards compatibility.
- };
- /*
- * Helper method to wrap an existing function with one of
- * your own. This is used by the various implementations to wrap event
- * callbacks for drag/drop etc; it allows jsPlumb to be transparent in
- * its handling of these things. If a user supplies their own event
- * callback, for anything, it will always be called.
- */
- this.wrap = _wrap;
- this.addListener = this.bind;
-
- var adjustForParentOffsetAndScroll = function(xy, el) {
- var offsetParent = null, result = xy;
- if (el.tagName.toLowerCase() === "svg" && el.parentNode) {
- offsetParent = el.parentNode;
- }
- else if (el.offsetParent) {
- offsetParent = el.offsetParent;
- }
- if (offsetParent != null) {
- var po = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : _getOffset(offsetParent),
- so = offsetParent.tagName.toLowerCase() === "body" ? {left:0,top:0} : {left:offsetParent.scrollLeft, top:offsetParent.scrollTop};
- // i thought it might be cool to do this:
- // lastReturnValue[0] = lastReturnValue[0] - offsetParent.offsetLeft + offsetParent.scrollLeft;
- // lastReturnValue[1] = lastReturnValue[1] - offsetParent.offsetTop + offsetParent.scrollTop;
- // but i think it ignores margins. my reasoning was that it's quicker to not hand off to some underlying
- // library.
-
- result[0] = xy[0] - po.left + so.left;
- result[1] = xy[1] - po.top + so.top;
- }
-
- return result;
-
- };
- /**
- * Anchors model a position on some element at which an Endpoint may be located. They began as a first class citizen of jsPlumb, ie. a user
- * was required to create these themselves, but over time this has been replaced by the concept of referring to them either by name (eg. "TopMiddle"),
- * or by an array describing their coordinates (eg. [ 0, 0.5, 0, -1 ], which is the same as "TopMiddle"). jsPlumb now handles all of the
- * creation of Anchors without user intervention.
- */
- var Anchor = function(params) {
- var self = this;
- this.x = params.x || 0;
- this.y = params.y || 0;
- this.elementId = params.elementId;
- var orientation = params.orientation || [ 0, 0 ];
- var lastTimestamp = null, lastReturnValue = null;
- this.offsets = params.offsets || [ 0, 0 ];
- self.timestamp = null;
- this.compute = function(params) {
- var xy = params.xy, wh = params.wh, element = params.element, timestamp = params.timestamp;
-
- if (timestamp && timestamp === self.timestamp)
- return lastReturnValue;
-
- lastReturnValue = [ xy[0] + (self.x * wh[0]) + self.offsets[0], xy[1] + (self.y * wh[1]) + self.offsets[1] ];
-
- // adjust loc if there is an offsetParent
- lastReturnValue = adjustForParentOffsetAndScroll(lastReturnValue, element.canvas);
-
- self.timestamp = timestamp;
- return lastReturnValue;
- };
- this.getOrientation = function(_endpoint) { return orientation; };
- this.equals = function(anchor) {
- if (!anchor) return false;
- var ao = anchor.getOrientation();
- var o = this.getOrientation();
- return this.x == anchor.x && this.y == anchor.y
- && this.offsets[0] == anchor.offsets[0]
- && this.offsets[1] == anchor.offsets[1]
- && o[0] == ao[0] && o[1] == ao[1];
- };
- this.getCurrentLocation = function() { return lastReturnValue; };
- };
- /**
- * An Anchor that floats. its orientation is computed dynamically from
- * its position relative to the anchor it is floating relative to. It is used when creating
- * a connection through drag and drop.
- *
- * TODO FloatingAnchor could totally be refactored to extend Anchor just slightly.
- */
- var FloatingAnchor = function(params) {
- // this is the anchor that this floating anchor is referenced to for
- // purposes of calculating the orientation.
- var ref = params.reference,
- // the canvas this refers to.
- refCanvas = params.referenceCanvas,
- size = _getSize(_getElementObject(refCanvas)),
- // these are used to store the current relative position of our
- // anchor wrt the reference anchor. they only indicate
- // direction, so have a value of 1 or -1 (or, very rarely, 0). these
- // values are written by the compute method, and read
- // by the getOrientation method.
- xDir = 0, yDir = 0,
- // temporary member used to store an orientation when the floating
- // anchor is hovering over another anchor.
- orientation = null,
- _lastResult = null;
- // set these to 0 each; they are used by certain types of connectors in the loopback case,
- // when the connector is trying to clear the element it is on. but for floating anchor it's not
- // very important.
- this.x = 0; this.y = 0;
- this.isFloating = true;
- this.compute = function(params) {
- var xy = params.xy, element = params.element,
- 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.
-
- // adjust loc if there is an offsetParent
- result = adjustForParentOffsetAndScroll(result, element.canvas);
-
- _lastResult = result;
- return result;
- };
- this.getOrientation = function(_endpoint) {
- if (orientation) return orientation;
- else {
- var o = ref.getOrientation(_endpoint);
- // here we take into account the orientation of the other
- // anchor: if it declares zero for some direction, we declare zero too. this might not be the most awesome. perhaps we can come
- // up with a better way. it's just so that the line we draw looks like it makes sense. maybe this wont make sense.
- return [ Math.abs(o[0]) * xDir * -1,
- Math.abs(o[1]) * yDir * -1 ];
- }
- };
- /**
- * notification the endpoint associated with this anchor is hovering
- * over another anchor; we want to assume that anchor's orientation
- * for the duration of the hover.
- */
- this.over = function(anchor) {
- orientation = anchor.getOrientation();
- };
- /**
- * notification the endpoint associated with this anchor is no
- * longer hovering over another anchor; we should resume calculating
- * orientation as we normally do.
- */
- this.out = function() { orientation = null; };
- this.getCurrentLocation = function() { return _lastResult; };
- };
- /*
- * A DynamicAnchor is an Anchor that contains a list of other Anchors, which it cycles
- * through at compute time to find the one that is located closest to
- * the center of the target element, and returns that Anchor's compute
- * method result. this causes endpoints to follow each other with
- * respect to the orientation of their target elements, which is a useful
- * feature for some applications.
- *
- */
- var DynamicAnchor = function(anchors, anchorSelector, elementId) {
- this.isSelective = true;
- this.isDynamic = true;
- var _anchors = [], self = this,
- _convert = function(anchor) {
- return anchor.constructor == Anchor ? anchor: _currentInstance.makeAnchor(anchor, elementId, _currentInstance);
- };
- for (var i = 0; i < anchors.length; i++)
- _anchors[i] = _convert(anchors[i]);
- this.addAnchor = function(anchor) { _anchors.push(_convert(anchor)); };
- this.getAnchors = function() { return _anchors; };
- this.locked = false;
- var _curAnchor = _anchors.length > 0 ? _anchors[0] : null,
- _curIndex = _anchors.length > 0 ? 0 : -1,
- self = this,
-
- // helper method to calculate the distance between the centers of the two elements.
- _distance = function(anchor, cx, cy, xy, wh) {
- var ax = xy[0] + (anchor.x * wh[0]), ay = xy[1] + (anchor.y * wh[1]);
- return Math.sqrt(Math.pow(cx - ax, 2) + Math.pow(cy - ay, 2));
- },
-
- // default method uses distance between element centers. you can provide your own method in the dynamic anchor
- // constructor (and also to jsPlumb.makeDynamicAnchor). the arguments to it are four arrays:
- // xy - xy loc of the anchor's element
- // wh - anchor's element's dimensions
- // txy - xy loc of the element of the other anchor in the connection
- // twh - dimensions of the element of the other anchor in the connection.
- // anchors - the list of selectable anchors
- _anchorSelector = anchorSelector || function(xy, wh, txy, twh, anchors) {
- var cx = txy[0] + (twh[0] / 2), cy = txy[1] + (twh[1] / 2);
- var minIdx = -1, minDist = Infinity;
- for ( var i = 0; i < anchors.length; i++) {
- var d = _distance(anchors[i], cx, cy, xy, wh);
- if (d < minDist) {
- minIdx = i + 0;
- minDist = d;
- }
- }
- return anchors[minIdx];
- };
-
- this.compute = function(params) {
- var xy = params.xy, wh = params.wh, timestamp = params.timestamp, txy = params.txy, twh = params.twh;
- // if anchor is locked or an opposite element was not given, we
- // maintain our state. anchor will be locked
- // if it is the source of a drag and drop.
- if (self.locked || txy == null || twh == null)
- return _curAnchor.compute(params);
- else
- params.timestamp = null; // otherwise clear this, i think. we want the anchor to compute.
-
- _curAnchor = _anchorSelector(xy, wh, txy, twh, _anchors);
- self.x = _curAnchor.x;
- self.y = _curAnchor.y;
-
- return _curAnchor.compute(params);
- };
- this.getCurrentLocation = function() {
- return _curAnchor != null ? _curAnchor.getCurrentLocation() : null;
- };
- this.getOrientation = function(_endpoint) { return _curAnchor != null ? _curAnchor.getOrientation(_endpoint) : [ 0, 0 ]; };
- this.over = function(anchor) { if (_curAnchor != null) _curAnchor.over(anchor); };
- this.out = function() { if (_curAnchor != null) _curAnchor.out(); };
- };
-
- /*
- manages anchors for all elements.
- */
- // "continuous" anchors: anchors that pick their location based on how many connections the given element has.
- // this requires looking at a lot more elements than normal - anything that has had a Continuous anchor applied has
- // to be recalculated. so this manager is used as a reference point. the first time, with a new timestamp, that
- // a continuous anchor is asked to compute, it calls this guy. or maybe, even, this guy gets called somewhere else
- // and compute only ever returns pre-computed values. either way, this is the central point, and we want it to
- // be called as few times as possible.
- var continuousAnchors = {},
- continuousAnchorLocations = {},
- continuousAnchorOrientations = {},
- Orientation = { HORIZONTAL : "horizontal", VERTICAL : "vertical", DIAGONAL : "diagonal", IDENTITY:"identity" },
-
- // TODO this functions uses a crude method of determining orientation between two elements.
- // 'diagonal' should be chosen when the angle of the line between the two centers is around
- // one of 45, 135, 225 and 315 degrees. maybe +- 15 degrees.
- calculateOrientation = function(sourceId, targetId, sd, td) {
- if (sourceId === targetId) return {
- orientation:Orientation.IDENTITY,
- a:["top", "top"]
- };
- var theta = Math.atan2((td.centery - sd.centery) , (td.centerx - sd.centerx)),
- theta2 = Math.atan2((sd.centery - td.centery) , (sd.centerx - td.centerx)),
- h = ((sd.left <= td.left && sd.right >= td.left) || (sd.left <= td.right && sd.right >= td.right) ||
- (sd.left <= td.left && sd.right >= td.right) || (td.left <= sd.left && td.right >= sd.right)),
- v = ((sd.top <= td.top && sd.bottom >= td.top) || (sd.top <= td.bottom && sd.bottom >= td.bottom) ||
- (sd.top <= td.top && sd.bottom >= td.bottom) || (td.top <= sd.top && td.bottom >= sd.bottom));
- if (! (h || v)) {
- var a = null, rls = false, rrs = false, sortValue = null;
- if (td.left > sd.left && td.top > sd.top)
- a = ["right", "top"];
- else if (td.left > sd.left && sd.top > td.top)
- a = [ "top", "left"];
- else if (td.left < sd.left && td.top < sd.top)
- a = [ "top", "right"];
- else if (td.left < sd.left && td.top > sd.top)
- a = ["left", "top" ];
- return { orientation:Orientation.DIAGONAL, a:a, theta:theta, theta2:theta2 };
- }
- else if (h) return {
- orientation:Orientation.HORIZONTAL,
- a:sd.top < td.top ? ["bottom", "top"] : ["top", "bottom"],
- theta:theta, theta2:theta2
- }
- else return {
- orientation:Orientation.VERTICAL,
- a:sd.left < td.left ? ["right", "left"] : ["left", "right"],
- theta:theta, theta2:theta2
- }
- },
- placeAnchorsOnLine = function(desc, elementDimensions, elementPosition,
- connections, horizontal, otherMultiplier, reverse) {
- var a = [], step = elementDimensions[horizontal ? 0 : 1] / (connections.length + 1);
- for (var i = 0; i < connections.length; i++) {
- var val = (i + 1) * step, other = otherMultiplier * elementDimensions[horizontal ? 1 : 0];
- if (reverse)
- val = elementDimensions[horizontal ? 0 : 1] - val;
- var dx = (horizontal ? val : other), x = elementPosition[0] + dx, xp = dx / elementDimensions[0],
- dy = (horizontal ? other : val), y = elementPosition[1] + dy, yp = dy / elementDimensions[1];
- a.push([ x, y, xp, yp, connections[i][1], connections[i][2] ]);
- }
- return a;
- },
- standardEdgeSort = function(a, b) { return a[0] > b[0] ? 1 : -1 },
- currySort = function(reverseAngles) {
- return function(a,b) {
- var r = true;
- if (reverseAngles) {
- if (a[0][0] < b[0][0])
- r = true;
- else
- r = a[0][1] > b[0][1];
- }
- else {
- if (a[0][0] > b[0][0])
- r= true;
- else
- r =a[0][1] > b[0][1];
- }
- return r === false ? -1 : 1;
- };
- },
- leftSort = function(a,b) {
- // first get adjusted values
- var p1 = a[0][0] < 0 ? -Math.PI - a[0][0] : Math.PI - a[0][0],
- p2 = b[0][0] < 0 ? -Math.PI - b[0][0] : Math.PI - b[0][0];
- if (p1 > p2) return 1;
- else return a[0][1] > b[0][1] ? 1 : -1;
- },
- edgeSortFunctions = {
- "top":standardEdgeSort,
- "right":currySort(true),
- "bottom":currySort(true),
- "left":leftSort
- },
- _sortHelper = function(_array, _fn) {
- return _array.sort(_fn);
- },
- placeAnchors = function(elementId, _anchorLists) {
- var sS = sizes[elementId], sO = offsets[elementId],
- placeSomeAnchors = function(desc, elementDimensions, elementPosition, unsortedConnections, isHorizontal, otherMultiplier, orientation) {
- if (unsortedConnections.length > 0) {
- var sc = _sortHelper(unsortedConnections, edgeSortFunctions[desc]), // puts them in order based on the target element's pos on screen
- reverse = desc === "right" || desc === "top",
- anchors = placeAnchorsOnLine(desc, elementDimensions,
- elementPosition, sc,
- isHorizontal, otherMultiplier, reverse );
- // takes a computed anchor position and adjusts it for parent offset and scroll, then stores it.
- var _setAnchorLocation = function(endpoint, anchorPos) {
- var a = adjustForParentOffsetAndScroll([anchorPos[0], anchorPos[1]], endpoint.canvas);
- continuousAnchorLocations[endpoint.id] = [ a[0], a[1], anchorPos[2], anchorPos[3] ];
- continuousAnchorOrientations[endpoint.id] = orientation;
- };
- for (var i = 0; i < anchors.length; i++) {
- var c = anchors[i][4], weAreSource = c.endpoints[0].elementId === elementId, weAreTarget = c.endpoints[1].elementId === elementId;
- if (weAreSource)
- _setAnchorLocation(c.endpoints[0], anchors[i]);
- else if (weAreTarget)
- _setAnchorLocation(c.endpoints[1], anchors[i]);
- }
- }
- };
- placeSomeAnchors("bottom", sS, [sO.left,sO.top], _anchorLists.bottom, true, 1, [0,1]);
- placeSomeAnchors("top", sS, [sO.left,sO.top], _anchorLists.top, true, 0, [0,-1]);
- placeSomeAnchors("left", sS, [sO.left,sO.top], _anchorLists.left, false, 0, [-1,0]);
- placeSomeAnchors("right", sS, [sO.left,sO.top], _anchorLists.right, false, 1, [1,0]);
- },
- AnchorManager = function() {
- var _amEndpoints = {},
- connectionsByElementId = {},
- self = this,
- anchorLists = {};
- this.reset = function() {
- _amEndpoints = {};
- connectionsByElementId = {};
- anchorLists = {};
- };
- this.newConnection = function(conn) {
- var sourceId = conn.sourceId, targetId = conn.targetId,
- ep = conn.endpoints,
- doRegisterTarget = true,
- registerConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) {
- if ((sourceId == targetId) && otherAnchor.isContinuous){
- // remove the target endpoint's canvas. we dont need it.
- jsPlumb.CurrentLibrary.removeElement(ep[1].canvas);
- doRegisterTarget = false;
- }
- _addToList(connectionsByElementId, elId, [c, otherEndpoint, otherAnchor.constructor == DynamicAnchor]);
- };
- registerConnection(0, ep[0], ep[0].anchor, targetId, conn);
- if (doRegisterTarget)
- registerConnection(1, ep[1], ep[1].anchor, sourceId, conn);
- };
- this.connectionDetached = function(connInfo) {
- var connection = connInfo.connection || connInfo;
- var sourceId = connection.sourceId,
- targetId = connection.targetId,
- ep = connection.endpoints,
- removeConnection = function(otherIndex, otherEndpoint, otherAnchor, elId, c) {
- if (otherAnchor.constructor == FloatingAnchor) {
- // no-op
- }
- else {
- _removeWithFunction(connectionsByElementId[elId], function(_c) {
- return _c[0].id == c.id;
- });
- }
- };
-
- removeConnection(1, ep[1], ep[1].anchor, sourceId, connection);
- removeConnection(0, ep[0], ep[0].anchor, targetId, connection);
- // remove from anchorLists
- var sEl = connection.sourceId,
- tEl = connection.targetId,
- sE = connection.endpoints[0].id,
- tE = connection.endpoints[1].id,
- _remove = function(list, eId) {
- if (list) { // transient anchors dont get entries in this list.
- var f = function(e) { return e[4] == eId; };
- _removeWithFunction(list["top"], f);
- _removeWithFunction(list["left"], f);
- _removeWithFunction(list["bottom"], f);
- _removeWithFunction(list["right"], f);
- }
- };
-
- _remove(anchorLists[sEl], sE);
- _remove(anchorLists[tEl], tE);
- self.redraw(sEl);
- self.redraw(tEl);
- };
- this.add = function(endpoint, elementId) {
- _addToList(_amEndpoints, elementId, endpoint);
- };
- this.changeId = function(oldId, newId) {
- connectionsByElementId[newId] = connectionsByElementId[oldId];
- _amEndpoints[newId] = _amEndpoints[oldId];
- delete connectionsByElementId[oldId];
- delete _amEndpoints[oldId];
- };
- this.getConnectionsFor = function(elementId) {
- return connectionsByElementId[elementId] || [];
- };
- this.getEndpointsFor = function(elementId) {
- return _amEndpoints[elementId] || [];
- };
- this.deleteEndpoint = function(endpoint) {
- _removeWithFunction(_amEndpoints[endpoint.elementId], function(e) {
- return e.id == endpoint.id;
- });
- };
- this.clearFor = function(elementId) {
- delete _amEndpoints[elementId];
- _amEndpoints[elementId] = [];
- };
- // updates the given anchor list by either updating an existing anchor's info, or adding it. this function
- // also removes the anchor from its previous list, if the edge it is on has changed.
- // all connections found along the way (those that are connected to one of the faces this function
- // operates on) are added to the connsToPaint list, as are their endpoints. in this way we know to repaint
- // them wthout having to calculate anything else about them.
- var _updateAnchorList = function(lists, theta, order, conn, aBoolean, otherElId, idx, reverse, edgeId, elId, connsToPaint, endpointsToPaint) {
- // first try to find the exact match, but keep track of the first index of a matching element id along the way.s
- var exactIdx = -1,
- firstMatchingElIdx = -1,
- endpoint = conn.endpoints[idx],
- endpointId = endpoint.id,
- oIdx = [1,0][idx],
- values = [ [ theta, order ], conn, aBoolean, otherElId, endpointId ],
- listToAddTo = lists[edgeId],
- listToRemoveFrom = endpoint._continuousAnchorEdge ? lists[endpoint._continuousAnchorEdge] : null;
- if (listToRemoveFrom) {
- var rIdx = _findWithFunction(listToRemoveFrom, function(e) { return e[4] == endpointId });
- if (rIdx != -1) {
- listToRemoveFrom.splice(rIdx, 1);
- // get all connections from this list
- for (var i = 0; i < listToRemoveFrom.length; i++) {
- _addWithFunction(connsToPaint, listToRemoveFrom[i][1], function(c) { return c.id == listToRemoveFrom[i][1].id });
- _addWithFunction(endpointsToPaint, listToRemoveFrom[i][1].endpoints[idx], function(e) { return e.id == listToRemoveFrom[i][1].endpoints[idx].id });
- }
- }
- }
- for (var i = 0; i < listToAddTo.length; i++) {
- if (idx == 1 && listToAddTo[i][3] === otherElId && firstMatchingElIdx == -1)
- firstMatchingElIdx = i;
- _addWithFunction(connsToPaint, listToAddTo[i][1], function(c) { return c.id == listToAddTo[i][1].id });
- _addWithFunction(endpointsToPaint, listToAddTo[i][1].endpoints[idx], function(e) { return e.id == listToAddTo[i][1].endpoints[idx].id });
- }
- if (exactIdx != -1) {
- listToAddTo[exactIdx] = values;
- }
- else {
- var insertIdx = reverse ? firstMatchingElIdx != -1 ? firstMatchingElIdx : 0 : listToAddTo.length; // of course we will get this from having looked through the array shortly.
- listToAddTo.splice(insertIdx, 0, values);
- }
- // store this for next time.
- endpoint._continuousAnchorEdge = edgeId;
- };
- this.redraw = function(elementId, ui, timestamp, offsetToUI) {
- // get all the endpoints for this element
- var ep = _amEndpoints[elementId] || [],
- endpointConnections = connectionsByElementId[elementId] || [],
- connectionsToPaint = [],
- endpointsToPaint = [],
- anchorsToUpdate = [];
-
- timestamp = timestamp || _timestamp();
- // offsetToUI are values that would have been calculated in the dragManager when registering
- // an endpoint for an element that had a parent (somewhere in the hierarchy) that had been
- // registered as draggable.
- offsetToUI = offsetToUI || {left:0, top:0};
- if (ui) {
- ui = {
- left:ui.left + offsetToUI.left,
- top:ui.top + offsetToUI.top
- }
- }
-
- _updateOffset( { elId : elementId, offset : ui, recalc : false, timestamp : timestamp });
- // valid for one paint cycle.
- var myOffset = offsets[elementId],
- myWH = sizes[elementId],
- orientationCache = {};
-
- // actually, first we should compute the orientation of this element to all other elements to which
- // this element is connected with a continuous anchor (whether both ends of the connection have
- // a continuous anchor or just one)
- //for (var i = 0; i < continuousAnchorConnections.length; i++) {
- for (var i = 0; i < endpointConnections.length; i++) {
- var conn = endpointConnections[i][0],
- sourceId = conn.sourceId,
- targetId = conn.targetId,
- sourceContinuous = conn.endpoints[0].anchor.isContinuous,
- targetContinuous = conn.endpoints[1].anchor.isContinuous;
- if (sourceContinuous || targetContinuous) {
- var oKey = sourceId + "_" + targetId,
- oKey2 = targetId + "_" + sourceId,
- o = orientationCache[oKey],
- oIdx = conn.sourceId == elementId ? 1 : 0;
- if (sourceContinuous && !anchorLists[sourceId]) anchorLists[sourceId] = { top:[], right:[], bottom:[], left:[] };
- if (targetContinuous && !anchorLists[targetId]) anchorLists[targetId] = { top:[], right:[], bottom:[], left:[] };
- if (elementId != targetId) _updateOffset( { elId : targetId, timestamp : timestamp });
- if (elementId != sourceId) _updateOffset( { elId : sourceId, timestamp : timestamp });
- var td = _getCachedData(targetId),
- sd = _getCachedData(sourceId);
- if (targetId == sourceId && (sourceContinuous || targetContinuous)) {
- // here we may want to improve this by somehow determining the face we'd like
- // to put the connector on. ideally, when drawing, the face should be calculated
- // by determining which face is closest to the point at which the mouse button
- // was released. for now, we're putting it on the top face.
- _updateAnchorList(anchorLists[sourceId], -Math.PI / 2, 0, conn, false, targetId, 0, false, "top", sourceId, connectionsToPaint, endpointsToPaint)
- }
- else {
- if (!o) {
- o = calculateOrientation(sourceId, targetId, sd.o, td.o);
- orientationCache[oKey] = o;
- // this would be a performance enhancement, but the computed angles need to be clamped to
- //the (-PI/2 -> PI/2) range in order for the sorting to work properly.
- /* orientationCache[oKey2] = {
- orientation:o.orientation,
- a:[o.a[1], o.a[0]],
- theta:o.theta + Math.PI,
- theta2:o.theta2 + Math.PI
- };*/
- }
- if (sourceContinuous) _updateAnchorList(anchorLists[sourceId], o.theta, 0, conn, false, targetId, 0, false, o.a[0], sourceId, connectionsToPaint, endpointsToPaint);
- if (targetContinuous) _updateAnchorList(anchorLists[targetId], o.theta2, -1, conn, true, sourceId, 1, true, o.a[1], targetId, connectionsToPaint, endpointsToPaint);
- }
- if (sourceContinuous) _addWithFunction(anchorsToUpdate, sourceId, function(a) { return a === sourceId; });
- if (targetContinuous) _addWithFunction(anchorsToUpdate, targetId, function(a) { return a === targetId; });
- _addWithFunction(connectionsToPaint, conn, function(c) { return c.id == conn.id; });
- if ((sourceContinuous && oIdx == 0) || (targetContinuous && oIdx == 1))
- _addWithFunction(endpointsToPaint, conn.endpoints[oIdx], function(e) { return e.id == conn.endpoints[oIdx].id; });
- }
- }
- // now place all the continuous anchors we need to;
- for (var i = 0; i < anchorsToUpdate.length; i++) {
- placeAnchors(anchorsToUpdate[i], anchorLists[anchorsToUpdate[i]]);
- }
-
- // now that continuous anchors have been placed, paint all the endpoints for this element
- // TODO performance: add the endpoint ids to a temp array, and then when iterating in the next
- // loop, check that we didn't just paint that endpoint. we can probably shave off a few more milliseconds this way.
- for (var i = 0; i < ep.length; i++) {
- ep[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH });
- }
- // ... and any other endpoints we came across as a result of the continuous anchors.
- for (var i = 0; i < endpointsToPaint.length; i++) {
- endpointsToPaint[i].paint( { timestamp : timestamp, offset : myOffset, dimensions : myWH });
- }
- // paint all the standard and "dynamic connections", which are connections whose other anchor is
- // static and therefore does need to be recomputed; we make sure that happens only one time.
- // TODO we could have compiled a list of these in the first pass through connections; might save some time.
- for (var i = 0; i < endpointConnections.length; i++) {
- var otherEndpoint = endpointConnections[i][1];
- if (otherEndpoint.anchor.constructor == DynamicAnchor) {
- otherEndpoint.paint({ elementWithPrecedence:elementId });
- _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; });
- // all the connections for the other endpoint now need to be repainted
- for (var k = 0; k < otherEndpoint.connections.length; k++) {
- if (otherEndpoint.connections[k] !== endpointConnections[i][0])
- _addWithFunction(connectionsToPaint, otherEndpoint.connections[k], function(c) { return c.id == otherEndpoint.connections[k].id; });
- }
- } else if (otherEndpoint.anchor.constructor == Anchor) {
- _addWithFunction(connectionsToPaint, endpointConnections[i][0], function(c) { return c.id == endpointConnections[i][0].id; });
- }
- }
- // paint current floating connection for this element, if there is one.
- var fc = floatingConnections[elementId];
- if (fc)
- fc.paint({timestamp:timestamp, recalc:false, elId:elementId});
-
- // paint all the connections
- for (var i = 0; i < connectionsToPaint.length; i++) {
- connectionsToPaint[i].paint({elId:elementId, timestamp:timestamp, recalc:false});
- }
- };
- this.rehomeEndpoint = function(currentId, element) {
- var eps = _amEndpoints[currentId] || [], //,
- elementId = _currentInstance.getId(element);
- for (var i = 0; i < eps.length; i++) {
- self.add(eps[i], elementId);
- }
- eps.splice(0, eps.length);
- };
- };
- _currentInstance.anchorManager = new AnchorManager();
- _currentInstance.continuousAnchorFactory = {
- get:function(params) {
- var existing = continuousAnchors[params.elementId];
- if (!existing) {
- existing = {
- type:"Continuous",
- compute : function(params) {
- return continuousAnchorLocations[params.element.id] || [0,0];
- },
- getCurrentLocation : function(endpoint) {
- return continuousAnchorLocations[endpoint.id] || [0,0];
- },
- getOrientation : function(endpoint) {
- return continuousAnchorOrientations[endpoint.id] || [0,0];
- },
- isDynamic : true,
- isContinuous : true
- };
- continuousAnchors[params.elementId] = existing;
- }
- return existing;
- }
- };
- /**
- Manages dragging for some instance of jsPlumb.
- */
- var DragManager = function() {
-
- var _draggables = {}, _dlist = [], _delements = {}, _elementsWithEndpoints = {};
- /**
- register some element as draggable. right now the drag init stuff is done elsewhere, and it is
- possible that will continue to be the case.
- */
- this.register = function(el) {
- el = jsPlumb.CurrentLibrary.getElementObject(el);
- var id = _currentInstance.getId(el),
- domEl = jsPlumb.CurrentLibrary.getDOMElement(el);
- if (!_draggables[id]) {
- _draggables[id] = el;
- _dlist.push(el);
- _delements[id] = {};
- }
-
- // look for child elements that have endpoints and register them against this draggable.
- var _oneLevel = function(p) {
- var pEl = jsPlumb.CurrentLibrary.getElementObject(p),
- pOff = jsPlumb.CurrentLibrary.getOffset(pEl);
- for (var i = 0; i < p.childNodes.length; i++) {
- if (p.childNodes[i].nodeType != 3) {
- var cEl = jsPlumb.CurrentLibrary.getElementObject(p.childNodes[i]),
- cid = _currentInstance.getId(cEl);
- if (_elementsWithEndpoints[cid] && _elementsWithEndpoints[cid] > 0) {
- var cOff = jsPlumb.CurrentLibrary.getOffset(cEl);
- _delements[id][cid] = {
- id:cid,
- offset:{
- left:cOff.left - pOff.left,
- top:cOff.top - pOff.top
- }
- };
- }
- }
- }
- };
- _oneLevel(domEl);
- };
- /**
- notification that an endpoint was added to the given el. we go up from that el's parent
- node, looking for a parent that has been registered as a draggable. if we find one, we add this
- el to that parent's list of elements to update on drag (if it is not there already)
- */
- this.endpointAdded = function(el) {
- var jpcl = jsPlumb.CurrentLibrary, b = document.body, id = _currentInstance.getId(el), c = jpcl.getDOMElement(el),
- p = c.parentNode, done = p == b;
- _elementsWithEndpoints[id] = _elementsWithEndpoints[id] ? _elementsWithEndpoints[id] + 1 : 1;
- while (p != b) {
- var pid = _currentInstance.getId(p);
- if (_draggables[pid]) {
- var idx = -1, pEl = jpcl.getElementObject(p), pLoc = jsPlumb.CurrentLibrary.getOffset(pEl);
-
- if (_delements[pid][id] == null) {
- var cLoc = jsPlumb.CurrentLibrary.getOffset(el);
- _delements[pid][id] = {
- id:id,
- offset:{
- left:cLoc.left - pLoc.left,
- top:cLoc.top - pLoc.top
- }
- };
- }
- break;
- }
- p = p.parentNode;
- }
- };
- this.endpointDeleted = function(endpoint) {
- if (_elementsWithEndpoints[endpoint.elementId]) {
- _elementsWithEndpoints[endpoint.elementId]--;
- if (_elementsWithEndpoints[endpoint.elementId] <= 0) {
- for (var i in _delements) {
- delete _delements[i][endpoint.elementId];
- }
- }
- }
- };
- this.getElementsForDraggable = function(id) {
- return _delements[id];
- };
- this.reset = function() {
- _draggables = {};
- _dlist = [];
- _delements = {};
- _elementsWithEndpoints = {};
- };
-
- };
- _currentInstance.dragManager = new DragManager();
-
- /*
- * Class: Connection
- * The connecting line between two Endpoints.
- */
- /*
- * Function: Connection
- * Connection constructor.
- *
- * Parameters:
- * source - either an element id, a selector for an element, or an Endpoint.
- * target - either an element id, a selector for an element, or an Endpoint
- * scope - scope descriptor for this connection. optional.
- * container - optional id or selector instructing jsPlumb where to attach all the elements it creates for this connection. you should read the documentation for a full discussion of this.
- * endpoint - Optional. Endpoint definition to use for both ends of the connection.
- * endpoints - Optional. Array of two Endpoint definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters.
- * endpointStyle - Optional. Endpoint style definition to use for both ends of the Connection.
- * endpointStyles - Optional. Array of two Endpoint style definitions, one for each end of the Connection. This and 'endpoint' are mutually exclusive parameters.
- * paintStyle - Parameters defining the appearance of the Connection. Optional; jsPlumb will use the defaults if you supply nothing here.
- * hoverPaintStyle - Parameters defining the appearance of the Connection when the mouse is hovering over it. Optional; jsPlumb will use the defaults if you supply nothing here (note that the default hoverPaintStyle is null).
- * overlays - Optional array of Overlay definitions to appear on this Connection.
- * drawEndpoints - if false, instructs jsPlumb to not draw the endpoints for this Connection. Be careful with this: it only really works when you tell jsPlumb to attach elements to the document body. Read the documentation for a full discussion of this.
- */
- var Connection = function(params) {
- var self = this, visible = true;
- self.idPrefix = "_jsplumb_c_";
- self.defaultLabelLocation = 0.5;
- self.defaultOverlayKeys = ["Overlays", "ConnectionOverlays"];
- this.parent = params.parent;
- overlayCapableJsPlumbUIComponent.apply(this, arguments);
- // ************** get the source and target and register the connection. *******************
-
- /**
- Function:isVisible
- Returns whether or not the Connection is currently visible.
- */
- this.isVisible = function() { return visible; };
- /**
- Function: setVisible
- Sets whether or not the Connection should be visible.
- Parameters:
- visible - boolean indicating desired visible state.
- */
- this.setVisible = function(v) {
- visible = v;
- self[v ? "showOverlays" : "hideOverlays"]();
- if (self.connector && self.connector.canvas) self.connector.canvas.style.display = v ? "block" : "none";
- };
-
- /**
- Property: source
- The source element for this Connection.
- */
- this.source = _getElementObject(params.source);
- /**
- Property:target
- The target element for this Connection.
- */
- this.target = _getElementObject(params.target);
- // sourceEndpoint and targetEndpoint override source/target, if they are present. but
- // source is not overridden if the Endpoint has declared it is not the final target of a connection;
- // instead we use the source that the Endpoint declares will be the final source element.
- if (params.sourceEndpoint) {
- this.source = params.sourceEndpoint.endpointWillMoveTo || params.sourceEndpoint.getElement();
- }
- if (params.targetEndpoint) this.target = params.targetEndpoint.getElement();
-
- // if a new connection is the result of moving some existing connection, params.previousConnection
- // will have that Connection in it. listeners for the jsPlumbConnection event can look for that
- // member and take action if they need to.
- self.previousConnection = params.previousConnection;
-
- var _cost = params.cost;
- self.getCost = function() { return _cost; };
- self.setCost = function(c) { _cost = c; };
-
- var _bidirectional = params.bidirectional === false ? false : true;
- self.isBidirectional = function() { return _bidirectional; };
-
- /*
- * Property: sourceId
- * Id of the source element in the connection.
- */
- this.sourceId = _getAttribute(this.source, "id");
- /*
- * Property: targetId
- * Id of the target element in the connection.
- */
- this.targetId = _getAttribute(this.target, "id");
-
- /**
- * implementation of abstract method in EventGenerator
- * @return list of attached elements. in our case, a list of Endpoints.
- */
- this.getAttachedElements = function() {
- return self.endpoints;
- };
-
- /*
- * Property: scope
- * Optional scope descriptor for the connection.
- */
- this.scope = params.scope; // scope may have been passed in to the connect call. if it wasn't, we will pull it from the source endpoint, after having initialised the endpoints.
- /*
- * Property: endpoints
- * Array of [source, target] Endpoint objects.
- */
- this.endpoints = [];
- this.endpointStyles = [];
- // wrapped the main function to return null if no input given. this lets us cascade defaults properly.
- var _makeAnchor = function(anchorParams, elementId) {
- if (anchorParams)
- return _currentInstance.makeAnchor(anchorParams, elementId, _currentInstance);
- },
- prepareEndpoint = function(existing, index, params, element, elementId, connectorPaintStyle, connectorHoverPaintStyle) {
- if (existing) {
- self.endpoints[index] = existing;
- existing.addConnection(self);
- } else {
- if (!params.endpoints) params.endpoints = [ null, null ];
- var ep = params.endpoints[index]
- || params.endpoint
- || _currentInstance.Defaults.Endpoints[index]
- || jsPlumb.Defaults.Endpoints[index]
- || _currentInstance.Defaults.Endpoint
- || jsPlumb.Defaults.Endpoint;
- if (!params.endpointStyles) params.endpointStyles = [ null, null ];
- if (!params.endpointHoverStyles) params.endpointHoverStyles = [ null, null ];
- var es = params.endpointStyles[index] || params.endpointStyle || _currentInstance.Defaults.EndpointStyles[index] || jsPlumb.Defaults.EndpointStyles[index] || _currentInstance.Defaults.EndpointStyle || jsPlumb.Defaults.EndpointStyle;
- // Endpoints derive their fillStyle from the connector's strokeStyle, if no fillStyle was specified.
- if (es.fillStyle == null && connectorPaintStyle != null)
- es.fillStyle = connectorPaintStyle.strokeStyle;
-
- // TODO: decide if the endpoint should derive the connection's outline width and color. currently it does:
- //*
- if (es.outlineColor == null && connectorPaintStyle != null)
- es.outlineColor = connectorPaintStyle.outlineColor;
- if (es.outlineWidth == null && connectorPaintStyle != null)
- es.outlineWidth = connectorPaintStyle.outlineWidth;
- //*/
-
- var ehs = params.endpointHoverStyles[index] || params.endpointHoverStyle || _currentInstance.Defaults.EndpointHoverStyles[index] || jsPlumb.Defaults.EndpointHoverStyles[index] || _currentInstance.Defaults.EndpointHoverStyle || jsPlumb.Defaults.EndpointHoverStyle;
- // endpoint hover fill style is derived from connector's hover stroke style. TODO: do we want to do this by default? for sure?
- if (connectorHoverPaintStyle != null) {
- if (ehs == null) ehs = {};
- if (ehs.fillStyle == null) {
- ehs.fillStyle = connectorHoverPaintStyle.strokeStyle;
- }
- }
- var a = params.anchors ? params.anchors[index] :
- params.anchor ? params.anchor :
- _makeAnchor(_currentInstance.Defaults.Anchors[index], elementId) ||
- _makeAnchor(jsPlumb.Defaults.Anchors[index], elementId) ||
- _makeAnchor(_currentInstance.Defaults.Anchor, elementId) ||
- _makeAnchor(jsPlumb.Defaults.Anchor, elementId),
- u = params.uuids ? params.uuids[index] : null,
- e = _newEndpoint({
- paintStyle : es,
- hoverPaintStyle:ehs,
- endpoint : ep,
- connections : [ self ],
- uuid : u,
- anchor : a,
- source : element,
- scope : params.scope,
- container:params.container,
- reattach:params.reattach,
- detachable:params.detachable
- });
- self.endpoints[index] = e;
-
-
- if (params.drawEndpoints === false) e.setVisible(false, true, true);
-
- return e;
- }
- };
- var eS = prepareEndpoint(params.sourceEndpoint,
- 0,
- params,
- self.source,
- self.sourceId,
- params.paintStyle,
- params.hoverPaintStyle);
- if (eS) _addToList(endpointsByElement, this.sourceId, eS);
-
- // if there were no endpoints supplied and the source element is the target element, we will reuse the source
- // endpoint that was just created.
- var existingTargetEndpoint = ((self.sourceId == self.targetId) && params.targetEndpoint == null) ? eS : params.targetEndpoint,
- eT = prepareEndpoint(existingTargetEndpoint,
- 1,
- params,
- self.target,
- self.targetId,
- params.paintStyle,
- params.hoverPaintStyle);
- if (eT) _addToList(endpointsByElement, this.targetId, eT);
- // if scope not set, set it to be the scope for the source endpoint.
- if (!this.scope) this.scope = this.endpoints[0].scope;
-
- // if delete endpoints on detach, keep a record of just exactly which endpoints they are.
- if (params.deleteEndpointsOnDetach)
- self.endpointsToDeleteOnDetach = [eS, eT];
- var _detachable = _currentInstance.Defaults.ConnectionsDetachable;
- if (params.detachable === false) _detachable = false;
- if(self.endpoints[0].connectionsDetachable === false) _detachable = false;
- if(self.endpoints[1].connectionsDetachable === false) _detachable = false;
-
- // inherit connectin cost if it was set on source endpoint
- if (_cost == null) _cost = self.endpoints[0].getConnectionCost();
- // inherit bidirectional flag if set no source endpoint
- if (params.bidirectional == null) _bidirectional = self.endpoints[0].areConnectionsBidirectional();
-
- /*
- Function: isDetachable
- Returns whether or not this connection can be detached from its target/source endpoint. by default this
- is false; use it in conjunction with the 'reattach' parameter.
- */
- this.isDetachable = function() {
- return _detachable === true;
- };
- /*
- Function: setDetachable
- Sets whether or not this connection is detachable.
- */
- this.setDetachable = function(detachable) {
- _detachable = detachable === true;
- };
-
- // merge all the parameters objects into the connection. parameters set
- // on the connection take precedence; then target endpoint params, then
- // finally source endpoint params.
- // TODO jsPlumb.extend could be made to take more than two args, and it would
- // apply the second through nth args in order.
- var _p = jsPlumb.extend({}, this.endpoints[0].getParameters());
- jsPlumb.extend(_p, this.endpoints[1].getParameters());
- jsPlumb.extend(_p, self.getParameters());
- self.setParameters(_p);
-
- // override setHover to pass it down to the underlying connector
- var _sh = self.setHover;
- self.setHover = function() {
- self.connector.setHover.apply(self.connector, arguments);
- _sh.apply(self, arguments);
- };
-
- var _internalHover = function(state) {
- if (_connectionBeingDragged == null) {
- self.setHover(state, false);
- }
- };
- /*
- * Function: setConnector
- * Sets the Connection's connector (eg "Bezier", "Flowchart", etc). You pass a Connector definition into this method - the same
- * thing that you would set as the 'connector' property on a jsPlumb.connect call.
- *
- * Parameters:
- * connector - Connector definition
- */
- this.setConnector = function(connector, doNotRepaint) {
- if (self.connector != null) _removeElements(self.connector.getDisplayElements(), self.parent);
- var connectorArgs = {
- _jsPlumb:self._jsPlumb,
- parent:params.parent,
- cssClass:params.cssClass,
- container:params.container,
- tooltip:self.tooltip
- };
- if (connector.constructor == String)
- this.connector = new jsPlumb.Connectors[renderMode][connector](connectorArgs); // lets you use a string as shorthand.
- else if (connector.constructor == Array)
- this.connector = new jsPlumb.Connectors[renderMode][connector[0]](jsPlumb.extend(connector[1], connectorArgs));
- self.canvas = self.connector.canvas;
- // binds mouse listeners to the current connector.
- _bindListeners(self.connector, self, _internalHover);
- if (!doNotRepaint) self.repaint();
- };
- /*
- * Property: connector
- * The underlying Connector for this Connection (eg. a Bezier connector, straight line connector, flowchart connector etc)
- */
-
- self.setConnector(this.endpoints[0].connector ||
- this.endpoints[1].connector ||
- params.connector ||
- _currentInstance.Defaults.Connector ||
- jsPlumb.Defaults.Connector, true);
-
-
- this.setPaintStyle(this.endpoints[0].connectorStyle ||
- this.endpoints[1].connectorStyle ||
- params.paintStyle ||
- _currentInstance.Defaults.PaintStyle ||
- jsPlumb.Defaults.PaintStyle, true);
-
- this.setHoverPaintStyle(this.endpoints[0].connectorHoverStyle ||
- this.endpoints[1].connectorHoverStyle ||
- params.hoverPaintStyle ||
- _currentInstance.Defaults.HoverPaintStyle ||
- jsPlumb.Defaults.HoverPaintStyle, true);
-
- this.paintStyleInUse = this.paintStyle;
-
-
- this.moveParent = function(newParent) {
- var jpcl = jsPlumb.CurrentLibrary, curParent = jpcl.getParent(self.connector.canvas);
- jpcl.removeElement(self.connector.canvas, curParent);
- jpcl.appendElement(self.connector.canvas, newParent);
- if (self.connector.bgCanvas) {
- jpcl.removeElement(self.connector.bgCanvas, curParent);
- jpcl.appendElement(self.connector.bgCanvas, newParent);
- }
- // this only applies for DOMOverlays
- for (var i = 0; i < self.overlays.length; i++) {
- if (self.overlays[i].isAppendedAtTopLevel) {
- jpcl.removeElement(self.overlays[i].canvas, curParent);
- jpcl.appendElement(self.overlays[i].canvas, newParent);
- if (self.overlays[i].reattachListeners)
- self.overlays[i].reattachListeners(self.connector);
- }
- }
- if (self.connector.reattachListeners) // this is for SVG/VML; change an element's parent and you have to reinit its listeners.
- self.connector.reattachListeners(); // the Canvas implementation doesn't have to care about this
- };
-
- // ***************************** PLACEHOLDERS FOR NATURAL DOCS *************************************************
- /*
- * Function: bind
- * Bind to an event on the Connection.
- *
- * Parameters:
- * event - the event to bind. Available events on a Connection are:
- * - *click* : notification that a Connection was clicked.
- * - *dblclick* : notification that a Connection was double clicked.
- * - *mouseenter* : notification that the mouse is over a Connection.
- * - *mouseexit* : notification that the mouse exited a Connection.
- *
- * callback - function to callback. This function will be passed the Connection that caused the event, and also the original event.
- */
-
- /*
- * Function: setPaintStyle
- * Sets the Connection's paint style and then repaints the Connection.
- *
- * Parameters:
- * style - Style to use.
- */
-
- /*
- * Function: setHoverPaintStyle
- * Sets the paint style to use when the mouse is hovering over the Connection. This is null by default.
- * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace
- * it. This is because people will most likely want to change just one thing when hovering, say the
- * color for example, but leave the rest of the appearance the same.
- *
- * Parameters:
- * style - Style to use when the mouse is hovering.
- * doNotRepaint - if true, the Connection will not be repainted. useful when setting things up initially.
- */
-
- /*
- * Function: setHover
- * Sets/unsets the hover state of this Connection.
- *
- * Parameters:
- * hover - hover state boolean
- * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops.
- */
-
- // ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS *************************************************
- _updateOffset( { elId : this.sourceId });
- _updateOffset( { elId : this.targetId });
-
- // paint the endpoints
- var myOffset = offsets[this.sourceId], myWH = sizes[this.sourceId],
- otherOffset = offsets[this.targetId],
- otherWH = sizes[this.targetId],
- initialTimestamp = _timestamp(),
- anchorLoc = this.endpoints[0].anchor.compute( {
- xy : [ myOffset.left, myOffset.top ], wh : myWH, element : this.endpoints[0],
- elementId:this.endpoints[0].elementId,
- txy : [ otherOffset.left, otherOffset.top ], twh : otherWH, tElement : this.endpoints[1],
- timestamp:initialTimestamp
- });
- this.endpoints[0].paint( { anchorLoc : anchorLoc, timestamp:initialTimestamp });
- anchorLoc = this.endpoints[1].anchor.compute( {
- xy : [ otherOffset.left, otherOffset.top ], wh : otherWH, element : this.endpoints[1],
- elementId:this.endpoints[1].elementId,
- txy : [ myOffset.left, myOffset.top ], twh : myWH, tElement : this.endpoints[0],
- timestamp:initialTimestamp
- });
- this.endpoints[1].paint({ anchorLoc : anchorLoc, timestamp:initialTimestamp });
-
- /*
- * Paints the Connection. Not exposed for public usage.
- *
- * Parameters:
- * elId - Id of the element that is in motion.
- * ui - current library's event system ui object (present if we came from a drag to get here).
- * recalc - whether or not to recalculate all anchors etc before painting.
- * timestamp - timestamp of this paint. If the Connection was last painted with the same timestamp, it does not paint again.
- */
- this.paint = function(params) {
- params = params || {};
- var elId = params.elId, ui = params.ui, recalc = params.recalc, timestamp = params.timestamp,
- // if the moving object is not the source we must transpose the two references.
- swap = false,
- tId = swap ? this.sourceId : this.targetId, sId = swap ? this.targetId : this.sourceId,
- tIdx = swap ? 0 : 1, sIdx = swap ? 1 : 0;
- var sourceInfo = _updateOffset( { elId : elId, offset : ui, recalc : recalc, timestamp : timestamp }),
- targetInfo = _updateOffset( { elId : tId, timestamp : timestamp }); // update the target if this is a forced repaint. otherwise, only the source has been moved.
-
- var sE = this.endpoints[sIdx], tE = this.endpoints[tIdx],
- sAnchorP = sE.anchor.getCurrentLocation(sE),
- tAnchorP = tE.anchor.getCurrentLocation(tE);
- /* paint overlays*/
- var maxSize = 0;
- for ( var i = 0; i < self.overlays.length; i++) {
- var o = self.overlays[i];
- if (o.isVisible()) maxSize = Math.max(maxSize, o.computeMaxSize(self.connector));
- }
- var dim = this.connector.compute(sAnchorP, tAnchorP,
- this.endpoints[sIdx], this.endpoints[tIdx],
- this.endpoints[sIdx].anchor, this.endpoints[tIdx].anchor,
- self.paintStyleInUse.lineWidth, maxSize,
- sourceInfo,
- targetInfo);
-
- self.connector.paint(dim, self.paintStyleInUse);
- /* paint overlays*/
- for ( var i = 0; i < self.overlays.length; i++) {
- var o = self.overlays[i];
- if (o.isVisible) self.overlayPlacements[i] = o.draw(self.connector, self.paintStyleInUse, dim);
- }
- };
- /*
- * Function: repaint
- * Repaints the Connection.
- */
- this.repaint = function(params) {
- params = params || {};
- var recalc = !(params.recalc === false);
- this.paint({ elId : this.sourceId, recalc : recalc, timestamp:params.timestamp });
- };
-
- };
-
- // ENDPOINT HELPER FUNCTIONS
- var _makeConnectionDragHandler = function(placeholder) {
- var stopped = false;
- return {
- drag : function() {
- if (stopped) {
- stopped = false;
- return true;
- }
- var _ui = jsPlumb.CurrentLibrary.getUIPosition(arguments),
- el = placeholder.element;
- if (el) {
- jsPlumb.CurrentLibrary.setOffset(el, _ui);
- _draw(_getElementObject(el), _ui);
- }
- },
- stopDrag : function() {
- stopped = true;
- }
- };
- };
-
- var _makeFloatingEndpoint = function(paintStyle, referenceAnchor, endpoint, referenceCanvas, sourceElement) {
- var floatingAnchor = new FloatingAnchor( { reference : referenceAnchor, referenceCanvas : referenceCanvas });
- //setting the scope here should not be the way to fix that mootools issue. it should be fixed by not
- // adding the floating endpoint as a droppable. that makes more sense anyway!
-
- return _newEndpoint({ paintStyle : paintStyle, endpoint : endpoint, anchor : floatingAnchor, source : sourceElement, scope:"__floating" });
- };
-
- /**
- * creates a placeholder div for dragging purposes, adds it to the DOM, and pre-computes its offset. then returns
- * both the element id and a selector for the element.
- */
- var _makeDraggablePlaceholder = function(placeholder, parent) {
- var n = document.createElement("div");
- n.style.position = "absolute";
- var placeholderDragElement = _getElementObject(n);
- _appendElement(n, parent);
- var id = _getId(placeholderDragElement);
- _updateOffset( { elId : id });
- // create and assign an id, and initialize the offset.
- placeholder.id = id;
- placeholder.element = placeholderDragElement;
- };
- /*
- * Class: Endpoint
- *
- * Models an endpoint. Can have 1 to 'maxConnections' Connections emanating from it (set maxConnections to -1
- * to allow unlimited). Typically, if you use 'jsPlumb.connect' to programmatically connect two elements, you won't
- * actually deal with the underlying Endpoint objects. But if you wish to support drag and drop Connections, one of the ways you
- * do so is by creating and registering Endpoints using 'jsPlumb.addEndpoint', and marking these Endpoints as 'source' and/or
- * 'target' Endpoints for Connections.
- *
- *
- */
- /*
- * Function: Endpoint
- *
- * Endpoint constructor.
- *
- * Parameters:
- * anchor - definition of the Anchor for the endpoint. You can include one or more Anchor definitions here; if you include more than one, jsPlumb creates a 'dynamic' Anchor, ie. an Anchor which changes position relative to the other elements in a Connection. Each Anchor definition can be either a string nominating one of the basic Anchors provided by jsPlumb (eg. "TopCenter"), or a four element array that designates the Anchor's location and orientation (eg, and this is equivalent to TopCenter, [ 0.5, 0, 0, -1 ]). To provide more than one Anchor definition just put them all in an array. You can mix string definitions with array definitions.
- * endpoint - optional Endpoint definition. This takes the form of either a string nominating one of the basic Endpoints provided by jsPlumb (eg. "Rectangle"), or an array containing [name,params] for those cases where you don't wish to use the default values, eg. [ "Rectangle", { width:5, height:10 } ].
- * enabled - optional, defaults to true. Indicates whether or not the Endpoint should be enabled for mouse events (drag/drop).
- * paintStyle - endpoint style, a js object. may be null.
- * hoverPaintStyle - style to use when the mouse is hovering over the Endpoint. A js object. may be null; defaults to null.
- * source - element the Endpoint is attached to, of type String (an element id) or element selector. Required.
- * canvas - canvas element to use. may be, and most often is, null.
- * container - optional id or selector instructing jsPlumb where to attach the element it creates for this endpoint. you should read the documentation for a full discussion of this.
- * connections - optional list of Connections to configure the Endpoint with.
- * isSource - boolean. indicates the endpoint can act as a source of new connections. Optional; defaults to false.
- * maxConnections - integer; defaults to 1. a value of -1 means no upper limit.
- * dragOptions - if isSource is set to true, you can supply arguments for the underlying library's drag method. Optional; defaults to null.
- * connectorStyle - if isSource is set to true, this is the paint style for Connections from this Endpoint. Optional; defaults to null.
- * connectorHoverStyle - if isSource is set to true, this is the hover paint style for Connections from this Endpoint. Optional; defaults to null.
- * connector - optional Connector type to use. Like 'endpoint', this may be either a single string nominating a known Connector type (eg. "Bezier", "Straight"), or an array containing [name, params], eg. [ "Bezier", { curviness:160 } ].
- * connectorOverlays - optional array of Overlay definitions that will be applied to any Connection from this Endpoint.
- * isTarget - boolean. indicates the endpoint can act as a target of new connections. Optional; defaults to false.
- * dropOptions - if isTarget is set to true, you can supply arguments for the underlying library's drop method with this parameter. Optional; defaults to null.
- * reattach - optional boolean that determines whether or not the Connections reattach after they have been dragged off an Endpoint and left floating. defaults to false: Connections dropped in this way will just be deleted.
- */
- var Endpoint = function(params) {
- var self = this;
- self.idPrefix = "_jsplumb_e_";
- self.defaultLabelLocation = [ 0.5, 0.5 ];
- self.defaultOverlayKeys = ["Overlays", "EndpointOverlays"];
- this.parent = params.parent;
- overlayCapableJsPlumbUIComponent.apply(this, arguments);
- params = params || {};
- // ***************************** PLACEHOLDERS FOR NATURAL DOCS *************************************************
- /*
- * Function: bind
- * Bind to an event on the Endpoint.
- *
- * Parameters:
- * event - the event to bind. Available events on an Endpoint are:
- * - *click* : notification that a Endpoint was clicked.
- * - *dblclick* : notification that a Endpoint was double clicked.
- * - *mouseenter* : notification that the mouse is over a Endpoint.
- * - *mouseexit* : notification that the mouse exited a Endpoint.
- *
- * callback - function to callback. This function will be passed the Endpoint that caused the event, and also the original event.
- */
-
- /*
- * Function: setPaintStyle
- * Sets the Endpoint's paint style and then repaints the Endpoint.
- *
- * Parameters:
- * style - Style to use.
- */
-
- /*
- * Function: setHoverPaintStyle
- * Sets the paint style to use when the mouse is hovering over the Endpoint. This is null by default.
- * The hover paint style is applied as extensions to the paintStyle; it does not entirely replace
- * it. This is because people will most likely want to change just one thing when hovering, say the
- * color for example, but leave the rest of the appearance the same.
- *
- * Parameters:
- * style - Style to use when the mouse is hovering.
- * doNotRepaint - if true, the Endpoint will not be repainted. useful when setting things up initially.
- */
-
- /*
- * Function: setHover
- * Sets/unsets the hover state of this Endpoint.
- *
- * Parameters:
- * hover - hover state boolean
- * ignoreAttachedElements - if true, does not notify any attached elements of the change in hover state. used mostly to avoid infinite loops.
- */
-
- // ***************************** END OF PLACEHOLDERS FOR NATURAL DOCS *************************************************
-
- var visible = true, __enabled = !(params.enabled === false);
- /*
- Function: isVisible
- Returns whether or not the Endpoint is currently visible.
- */
- this.isVisible = function() { return visible; };
- /*
- Function: setVisible
- Sets whether or not the Endpoint is currently visible.
- Parameters:
- visible - whether or not the Endpoint should be visible.
- doNotChangeConnections - Instructs jsPlumb to not pass the visible state on to any attached Connections. defaults to false.
- doNotNotifyOtherEndpoint - Instructs jsPlumb to not pass the visible state on to Endpoints at the other end of any attached Connections. defaults to false.
- */
- this.setVisible = function(v, doNotChangeConnections, doNotNotifyOtherEndpoint) {
- visible = v;
- if (self.canvas) self.canvas.style.display = v ? "block" : "none";
- self[v ? "showOverlays" : "hideOverlays"]();
- if (!doNotChangeConnections) {
- for (var i = 0; i < self.connections.length; i++) {
- self.connections[i].setVisible(v);
- if (!doNotNotifyOtherEndpoint) {
- var oIdx = self === self.connections[i].endpoints[0] ? 1 : 0;
- // only change the other endpoint if this is its only connection.
- if (self.connections[i].endpoints[oIdx].connections.length == 1) self.connections[i].endpoints[oIdx].setVisible(v, true, true);
- }
- }
- }
- };
- /*
- Function: isEnabled
- Returns whether or not the Endpoint is enabled for drag/drop connections.
- */
- this.isEnabled = function() { return __enabled; };
- /*
- Function: setEnabled
- Sets whether or not the Endpoint is enabled for drag/drop connections.
- */
- this.setEnabled = function(e) { __enabled = e; };
- var _element = params.source, _uuid = params.uuid, floatingEndpoint = null, inPlaceCopy = null;
- if (_uuid) endpointsByUUID[_uuid] = self;
- var _elementId = _getAttribute(_element, "id");
- this.elementId = _elementId;
- this.element = _element;
-
- var _connectionCost = params.connectionCost;
- this.getConnectionCost = function() { return _connectionCost; };
- this.setConnectionCost = function(c) {
- _connectionCost = c;
- };
-
- var _connectionsBidirectional = params.connectionsBidirectional === false ? false : true;
- this.areConnectionsBidirectional = function() { return _connectionsBidirectional; };
- this.setConnectionsBidirectional = function(b) { _connectionsBidirectional = b; };
-
- self.anchor = params.anchor ? _currentInstance.makeAnchor(params.anchor, _elementId, _currentInstance) : params.anchors ? _currentInstance.makeAnchor(params.anchors, _elementId, _currentInstance) : _currentInstance.makeAnchor("TopCenter", _elementId, _currentInstance);
-
- // ANCHOR MANAGER
- if (!params._transient) // in place copies, for example, are transient. they will never need to be retrieved during a paint cycle, because they dont move, and then they are deleted.
- _currentInstance.anchorManager.add(self, _elementId);
-
- var _endpoint = params.endpoint || _currentInstance.Defaults.Endpoint || jsPlumb.Defaults.Endpoint || "Dot",
- endpointArgs = {
- _jsPlumb:self._jsPlumb,
- parent:params.parent,
- container:params.container,
- tooltip:params.tooltip,
- connectorTooltip:params.connectorTooltip,
- endpoint:self
- };
- if (_endpoint.constructor == String)
- _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint](endpointArgs);
- else if (_endpoint.constructor == Array) {
- endpointArgs = jsPlumb.extend(_endpoint[1], endpointArgs);
- _endpoint = new jsPlumb.Endpoints[renderMode][_endpoint[0]](endpointArgs);
- }
- else {
- _endpoint = _endpoint.clone();
- }
- // assign a clone function using a copy of endpointArgs. this is used when a drag starts: the endpoint that was dragged is cloned,
- // and the clone is left in its place while the original one goes off on a magical journey.
- // the copy is to get around a closure problem, in which endpointArgs ends up getting shared by
- // the whole world.
- var argsForClone = jsPlumb.extend({}, endpointArgs);
- _endpoint.clone = function() {
- var o = new Object();
- _endpoint.constructor.apply(o, [argsForClone]);
- return o;
- };
-
- self.endpoint = _endpoint;
- self.type = self.endpoint.type;
- // override setHover to pass it down to the underlying endpoint
- var _sh = self.setHover;
- self.setHover = function() {
- self.endpoint.setHover.apply(self.endpoint, arguments);
- _sh.apply(self, arguments);
- };
- // endpoint delegates to first connection for hover, if there is one.
- var internalHover = function(state) {
- if (self.connections.length > 0)
- self.connections[0].setHover(state, false);
- else
- self.setHover(state);
- };
-
- // bind listeners from endpoint to self, with the internal hover function defined above.
- _bindListeners(self.endpoint, self, internalHover);
-
- this.setPaintStyle(params.paintStyle ||
- params.style ||
- _currentInstance.Defaults.EndpointStyle ||
- jsPlumb.Defaults.EndpointStyle, true);
- this.setHoverPaintStyle(params.hoverPaintStyle ||
- _currentInstance.Defaults.EndpointHoverStyle ||
- jsPlumb.Defaults.EndpointHoverStyle, true);
- this.paintStyleInUse = this.paintStyle;
- this.connectorStyle = params.connectorStyle;
- this.connectorHoverStyle = params.connectorHoverStyle;
- this.connectorOverlays = params.connectorOverlays;
- this.connector = params.connector;
- this.connectorTooltip = params.connectorTooltip;
- this.isSource = params.isSource || false;
- this.isTarget = params.isTarget || false;
-
- var _maxConnections = params.maxConnections || _currentInstance.Defaults.MaxConnections; // maximum number of connections this endpoint can be the source of.
-
- this.getAttachedElements = function() {
- return self.connections;
- };
-
- /*
- * Property: canvas
- * The Endpoint's Canvas.
- */
- this.canvas = this.endpoint.canvas;
- /*
- * Property: connections
- * List of Connections this Endpoint is attached to.
- */
- this.connections = params.connections || [];
- /*
- * Property: scope
- * Scope descriptor for this Endpoint.
- */
- this.scope = params.scope || DEFAULT_SCOPE;
- this.timestamp = null;
- self.isReattach = params.reattach || false;
- self.connectionsDetachable = _currentInstance.Defaults.ConnectionsDetachable;
- if (params.connectionsDetachable === false || params.detachable === false)
- self.connectionsDetachable = false;
- var dragAllowedWhenFull = params.dragAllowedWhenFull || true;
- this.computeAnchor = function(params) {
- return self.anchor.compute(params);
- };
- /*
- * Function: addConnection
- * Adds a Connection to this Endpoint.
- *
- * Parameters:
- * connection - the Connection to add.
- */
- this.addConnection = function(connection) {
- self.connections.push(connection);
- };
- /*
- * Function: detach
- * Detaches the given Connection from this Endpoint.
- *
- * Parameters:
- * connection - the Connection to detach.
- * ignoreTarget - optional; tells the Endpoint to not notify the Connection target that the Connection was detached. The default behaviour is to notify the target.
- */
- this.detach = function(connection, ignoreTarget, forceDetach, fireEvent, originalEvent) {
- var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id}),
- actuallyDetached = false;
- fireEvent = (fireEvent !== false);
- if (idx >= 0) {
- // 1. does the connection have a before detach (note this also checks jsPlumb's bound
- // detach handlers; but then Endpoint's check will, too, hmm.)
- if (forceDetach || connection._forceDetach || connection.isDetachable() || connection.isDetachAllowed(connection)) {
- // get the target endpoint
- var t = connection.endpoints[0] == self ? connection.endpoints[1] : connection.endpoints[0];
- // it would be nice to check with both endpoints that it is ok to detach. but
- // for this we'll have to get a bit fancier: right now if you use the same beforeDetach
- // interceptor for two endpoints (which is kind of common, because it's part of the
- // endpoint definition), then it gets fired twice. so in fact we need to loop through
- // each beforeDetach and see if it returns false, at which point we exit. but if it
- // returns true, we have to check the next one. however we need to track which ones
- // have already been run, and not run them again.
- if (forceDetach || connection._forceDetach || (self.isDetachAllowed(connection) /*&& t.isDetachAllowed(connection)*/)) {
-
- self.connections.splice(idx, 1);
-
- // this avoids a circular loop
- if (!ignoreTarget) {
-
- t.detach(connection, true, forceDetach);
- // check connection to see if we want to delete the endpoints associated with it.
- // we only detach those that have just this connection; this scenario is most
- // likely if we got to this bit of code because it is set by the methods that
- // create their own endpoints, like .connect or .makeTarget. the user is
- // not likely to have interacted with those endpoints.
- if (connection.endpointsToDeleteOnDetach){
- for (var i = 0; i < connection.endpointsToDeleteOnDetach.length; i++) {
- var cde = connection.endpointsToDeleteOnDetach[i];
- if (cde && cde.connections.length == 0)
- _currentInstance.deleteEndpoint(cde);
- }
- }
- }
- _removeElements(connection.connector.getDisplayElements(), connection.parent);
- _removeWithFunction(connectionsByScope[connection.scope], function(c) {
- return c.id == connection.id;
- });
- actuallyDetached = true;
- var doFireEvent = (!ignoreTarget && fireEvent)
- fireDetachEvent(connection, doFireEvent, originalEvent);
- }
- }
- }
- return actuallyDetached;
- };
- /*
- * Function: detachAll
- * Detaches all Connections this Endpoint has.
- *
- * Parameters:
- * fireEvent - whether or not to fire the detach event. defaults to false.
- */
- this.detachAll = function(fireEvent, originalEvent) {
- while (self.connections.length > 0) {
- self.detach(self.connections[0], false, true, fireEvent, originalEvent);
- }
- };
- /*
- * Function: detachFrom
- * Removes any connections from this Endpoint that are connected to the given target endpoint.
- *
- * Parameters:
- * targetEndpoint - Endpoint from which to detach all Connections from this Endpoint.
- * fireEvent - whether or not to fire the detach event. defaults to false.
- */
- this.detachFrom = function(targetEndpoint, fireEvent, originalEvent) {
- var c = [];
- for ( var i = 0; i < self.connections.length; i++) {
- if (self.connections[i].endpoints[1] == targetEndpoint
- || self.connections[i].endpoints[0] == targetEndpoint) {
- c.push(self.connections[i]);
- }
- }
- for ( var i = 0; i < c.length; i++) {
- if (self.detach(c[i], false, true, fireEvent, originalEvent))
- c[i].setHover(false, false);
- }
- };
- /*
- * Function: detachFromConnection
- * Detach this Endpoint from the Connection, but leave the Connection alive. Used when dragging.
- *
- * Parameters:
- * connection - Connection to detach from.
- */
- this.detachFromConnection = function(connection) {
- var idx = _findWithFunction(self.connections, function(c) { return c.id == connection.id});
- if (idx >= 0) {
- self.connections.splice(idx, 1);
- }
- };
- /*
- * Function: getElement
- * Returns the DOM element this Endpoint is attached to.
- */
- this.getElement = function() {
- return _element;
- };
-
- /*
- * Function: setElement
- * Sets the DOM element this Endpoint is attached to.
- */
- this.setElement = function(el) {
- // TODO possibly have this object take charge of moving the UI components into the appropriate
- // parent. this is used only by makeSource right now, and that function takes care of
- // moving the UI bits and pieces. however it would s
- var parentId = _getId(el);
- // remove the endpoint from the list for the current endpoint's element
- _removeWithFunction(endpointsByElement[_elementId], function(e) {
- return e.id == self.id;
- });
- _element = _getElementObject(el);
- _elementId = _getId(_element);
- self.elementId = _elementId;
- // need to get the new parent now
- var newParentElement = _getParentFromParams({source:parentId}),
- curParent = jpcl.getParent(self.canvas);
- jpcl.removeElement(self.canvas, curParent);
- jpcl.appendElement(self.canvas, newParentElement);
-
- // now move connection(s)...i would expect there to be only one but we will iterate.
- for (var i = 0; i < self.connections.length; i++) {
- self.connections[i].moveParent(newParentElement);
- self.connections[i].sourceId = _elementId;
- self.connections[i].source = _element;
- }
- _addToList(endpointsByElement, parentId, self);
- //_currentInstance.repaint(parentId);
-
- };
- /*
- * Function: getUuid
- * Returns the UUID for this Endpoint, if there is one. Otherwise returns null.
- */
- this.getUuid = function() {
- return _uuid;
- };
- /**
- * private but must be exposed.
- */
- this.makeInPlaceCopy = function() {
- return _newEndpoint( {
- anchor : self.anchor,
- source : _element,
- paintStyle : this.paintStyle,
- endpoint : _endpoint,
- _transient:true,
- scope:self.scope
- });
- };
- /*
- * Function: isConnectedTo
- * Returns whether or not this endpoint is connected to the given Endpoint.
- *
- * Parameters:
- * endpoint - Endpoint to test.
- */
- this.isConnectedTo = function(endpoint) {
- var found = false;
- if (endpoint) {
- for ( var i = 0; i < self.connections.length; i++) {
- if (self.connections[i].endpoints[1] == endpoint) {
- found = true;
- break;
- }
- }
- }
- return found;
- };
- /**
- * private but needs to be exposed.
- */
- this.isFloating = function() {
- return floatingEndpoint != null;
- };
-
- /**
- * returns a connection from the pool; used when dragging starts. just gets the head of the array if it can.
- */
- this.connectorSelector = function() {
- var candidate = self.connections[0];
- if (self.isTarget && candidate) return candidate;
- else {
- return (self.connections.length < _maxConnections) || _maxConnections == -1 ? null : candidate;
- }
- };
- /*
- * Function: isFull
- * Returns whether or not the Endpoint can accept any more Connections.
- */
- this.isFull = function() {
- return !(self.isFloating() || _maxConnections < 1 || self.connections.length < _maxConnections);
- };
- /*
- * Function: setDragAllowedWhenFull
- * Sets whether or not connections can be dragged from this Endpoint once it is full. You would use this in a UI in
- * which you're going to provide some other way of breaking connections, if you need to break them at all. This property
- * is by default true; use it in conjunction with the 'reattach' option on a connect call.
- *
- * Parameters:
- * allowed - whether drag is allowed or not when the Endpoint is full.
- */
- this.setDragAllowedWhenFull = function(allowed) {
- dragAllowedWhenFull = allowed;
- };
- /*
- * Function: setStyle
- * Sets the paint style of the Endpoint. This is a JS object of the same form you supply to a jsPlumb.addEndpoint or jsPlumb.connect call.
- * TODO move setStyle into EventGenerator, remove it from here. is Connection's method currently setPaintStyle ? wire that one up to
- * setStyle and deprecate it if so.
- *
- * Parameters:
- * style - Style object to set, for example {fillStyle:"blue"}.
- *
- * @deprecated use setPaintStyle instead.
- */
- this.setStyle = self.setPaintStyle;
- /**
- * a deep equals check. everything must match, including the anchor,
- * styles, everything. TODO: finish Endpoint.equals
- */
- this.equals = function(endpoint) {
- return this.anchor.equals(endpoint.anchor);
- };
-
- // a helper function that tries to find a connection to the given element, and returns it if so. if elementWithPrecedence is null,
- // or no connection to it is found, we return the first connection in our list.
- var findConnectionToUseForDynamicAnchor = function(elementWithPrecedence) {
- var idx = 0;
- if (elementWithPrecedence != null) {
- for (var i = 0; i < self.connections.length; i++) {
- if (self.connections[i].sourceId == elementWithPrecedence || self.connections[i].targetId == elementWithPrecedence) {
- idx = i;
- break;
- }
- }
- }
-
- return self.connections[idx];
- };
- /*
- * Function: paint
- * Paints the Endpoint, recalculating offset and anchor positions if necessary. This does NOT paint
- * any of the Endpoint's connections.
- *
- * Parameters:
- * timestamp - optional timestamp advising the Endpoint of the current paint time; if it has painted already once for this timestamp, it will not paint again.
- * canvas - optional Canvas to paint on. Only used internally by jsPlumb in certain obscure situations.
- * connectorPaintStyle - paint style of the Connector attached to this Endpoint. Used to get a fillStyle if nothing else was supplied.
- */
- this.paint = function(params) {
- params = params || {};
- var timestamp = params.timestamp,
- recalc = !(params.recalc === false);
- if (!timestamp || self.timestamp !== timestamp) {
- _updateOffset({ elId:_elementId, timestamp:timestamp, recalc:recalc });
- var xy = params.offset || offsets[_elementId];
- if(xy) {
- var ap = params.anchorPoint,connectorPaintStyle = params.connectorPaintStyle;
- if (ap == null) {
- var wh = params.dimensions || sizes[_elementId];
- if (xy == null || wh == null) {
- _updateOffset( { elId : _elementId, timestamp : timestamp });
- xy = offsets[_elementId];
- wh = sizes[_elementId];
- }
- var anchorParams = { xy : [ xy.left, xy.top ], wh : wh, element : self, timestamp : timestamp };
- if (recalc && self.anchor.isDynamic && self.connections.length > 0) {
- var c = findConnectionToUseForDynamicAnchor(params.elementWithPrecedence),
- oIdx = c.endpoints[0] == self ? 1 : 0,
- oId = oIdx == 0 ? c.sourceId : c.targetId,
- oOffset = offsets[oId], oWH = sizes[oId];
- anchorParams.txy = [ oOffset.left, oOffset.top ];
- anchorParams.twh = oWH;
- anchorParams.tElement = c.endpoints[oIdx];
- }
- ap = self.anchor.compute(anchorParams);
- }
-
- var d = _endpoint.compute(ap, self.anchor.getOrientation(_endpoint), self.paintStyleInUse, connectorPaintStyle || self.paintStyleInUse);
- _endpoint.paint(d, self.paintStyleInUse, self.anchor);
- self.timestamp = timestamp;
- /* paint overlays*/
- for ( var i = 0; i < self.overlays.length; i++) {
- var o = self.overlays[i];
- if (o.isVisible) self.overlayPlacements[i] = o.draw(self.endpoint, self.paintStyleInUse, d);
- }
- }
- }
- };
- this.repaint = this.paint;
- /**
- * @deprecated
- */
- this.removeConnection = this.detach; // backwards compatibility
- // is this a connection source? we make it draggable and have the
- // drag listener maintain a connection with a floating endpoint.
- if (jsPlumb.CurrentLibrary.isDragSupported(_element)) {
- var placeholderInfo = { id:null, element:null },
- jpc = null,
- existingJpc = false,
- existingJpcParams = null,
- _dragHandler = _makeConnectionDragHandler(placeholderInfo);
- var start = function() {
- // drag might have started on an endpoint that is not actually a source, but which has
- // one or more connections.
- jpc = self.connectorSelector();
- var _continue = true;
- // if not enabled, return
- if (!self.isEnabled()) _continue = false;
- // if no connection and we're not a source, return.
- if (jpc == null && !params.isSource) _continue = false;
- // otherwise if we're full and not allowed to drag, also return false.
- if (params.isSource && self.isFull() && !dragAllowedWhenFull) _continue = false;
- // if the connection was setup as not detachable or one of its endpoints
- // was setup as connectionsDetachable = false, or Defaults.ConnectionsDetachable
- // is set to false...
- if (jpc != null && !jpc.isDetachable()) _continue = false;
- if (_continue === false) {
- // this is for mootools and yui. returning false from this causes jquery to stop drag.
- // the events are wrapped in both mootools and yui anyway, but i don't think returning
- // false from the start callback would stop a drag.
- if (jsPlumb.CurrentLibrary.stopDrag) jsPlumb.CurrentLibrary.stopDrag();
- _dragHandler.stopDrag();
- return false;
- }
- // if we're not full but there was a connection, make it null. we'll create a new one.
- if (jpc && !self.isFull() && params.isSource) jpc = null;
- _updateOffset( { elId : _elementId });
- inPlaceCopy = self.makeInPlaceCopy();
- inPlaceCopy.paint();
-
- _makeDraggablePlaceholder(placeholderInfo, self.parent);
-
- // set the offset of this div to be where 'inPlaceCopy' is, to start with.
- // TODO merge this code with the code in both Anchor and FloatingAnchor, because it
- // does the same stuff.
- var ipcoel = _getElementObject(inPlaceCopy.canvas),
- ipco = jsPlumb.CurrentLibrary.getOffset(ipcoel),
- po = adjustForParentOffsetAndScroll([ipco.left, ipco.top], inPlaceCopy.canvas);
- jsPlumb.CurrentLibrary.setOffset(placeholderInfo.element, {left:po[0], top:po[1]});
-
- // when using makeSource and a parent, we first draw the source anchor on the source element, then
- // move it to the parent. note that this happens after drawing the placeholder for the
- // first time.
- if (self.parentAnchor) self.anchor = _currentInstance.makeAnchor(self.parentAnchor, self.elementId, _currentInstance);
- // store the id of the dragging div and the source element. the drop function will pick these up.
- _setAttribute(_getElementObject(self.canvas), "dragId", placeholderInfo.id);
- _setAttribute(_getElementObject(self.canvas), "elId", _elementId);
- // create a floating anchor
- floatingEndpoint = _makeFloatingEndpoint(self.paintStyle, self.anchor, _endpoint, self.canvas, placeholderInfo.element);
-
- if (jpc == null) {
- self.anchor.locked = true;
- self.setHover(false, false);
- // TODO the hover call above does not reset any target endpoint's hover
- // states.
- // create a connection. one end is this endpoint, the other is a floating endpoint.
- jpc = _newConnection({
- sourceEndpoint : self,
- targetEndpoint : floatingEndpoint,
- source : self.endpointWillMoveTo || _getElementObject(_element), // for makeSource with parent option. ensure source element is represented correctly.
- target : placeholderInfo.element,
- anchors : [ self.anchor, floatingEndpoint.anchor ],
- paintStyle : params.connectorStyle, // this can be null. Connection will use the default.
- hoverPaintStyle:params.connectorHoverStyle,
- connector : params.connector, // this can also be null. Connection will use the default.
- overlays : params.connectorOverlays
- });
- } else {
- existingJpc = true;
- jpc.connector.setHover(false, false);
- // if existing connection, allow to be dropped back on the source endpoint (issue 51).
- _initDropTarget(_getElementObject(inPlaceCopy.canvas), false, true);
- // new anchor idx
- var anchorIdx = jpc.endpoints[0].id == self.id ? 0 : 1;
- jpc.floatingAnchorIndex = anchorIdx; // save our anchor index as the connection's floating index.
- self.detachFromConnection(jpc); // detach from the connection while dragging is occurring.
-
- // store the original scope (issue 57)
- var c = _getElementObject(self.canvas),
- dragScope = jsPlumb.CurrentLibrary.getDragScope(c);
- _setAttribute(c, "originalScope", dragScope);
- // now we want to get this endpoint's DROP scope, and set it for now: we can only be dropped on drop zones
- // that have our drop scope (issue 57).
- var dropScope = jsPlumb.CurrentLibrary.getDropScope(c);
- jsPlumb.CurrentLibrary.setDragScope(c, dropScope);
-
- // now we replace ourselves with the temporary div we created above:
- if (anchorIdx == 0) {
- existingJpcParams = [ jpc.source, jpc.sourceId, i, dragScope ];
- jpc.source = placeholderInfo.element;
- jpc.sourceId = placeholderInfo.id;
- } else {
- existingJpcParams = [ jpc.target, jpc.targetId, i, dragScope ];
- jpc.target = placeholderInfo.element;
- jpc.targetId = placeholderInfo.id;
- }
- // lock the other endpoint; if it is dynamic it will not move while the drag is occurring.
- jpc.endpoints[anchorIdx == 0 ? 1 : 0].anchor.locked = true;
- // store the original endpoint and assign the new floating endpoint for the drag.
- jpc.suspendedEndpoint = jpc.endpoints[anchorIdx];
- jpc.suspendedEndpoint.setHover(false);
- jpc.endpoints[anchorIdx] = floatingEndpoint;
- // fire an event that informs that a connection is being dragged
- fireConnectionDraggingEvent(jpc);
- }
- // register it and register connection on it.
- floatingConnections[placeholderInfo.id] = jpc;
- floatingEndpoint.addConnection(jpc);
- // only register for the target endpoint; we will not be dragging the source at any time
- // before this connection is either discarded or made into a permanent connection.
- _addToList(endpointsByElement, placeholderInfo.id, floatingEndpoint);
- // tell jsplumb about it
- _currentInstance.currentlyDragging = true;
- };
- var jpcl = jsPlumb.CurrentLibrary,
- dragOptions = params.dragOptions || {},
- defaultOpts = jsPlumb.extend( {}, jpcl.defaultDragOptions),
- startEvent = jpcl.dragEvents["start"],
- stopEvent = jpcl.dragEvents["stop"],
- dragEvent = jpcl.dragEvents["drag"];
-
- dragOptions = jsPlumb.extend(defaultOpts, dragOptions);
- dragOptions.scope = dragOptions.scope || self.scope;
- dragOptions[startEvent] = _wrap(dragOptions[startEvent], start);
- // extracted drag handler function so can be used by makeSource
- dragOptions[dragEvent] = _wrap(dragOptions[dragEvent], _dragHandler.drag);
- dragOptions[stopEvent] = _wrap(dragOptions[stopEvent],
- function() {
- _currentInstance.currentlyDragging = false;
- _removeWithFunction(endpointsByElement[placeholderInfo.id], function(e) {
- return e.id == floatingEndpoint.id;
- });
- _removeElements( [ placeholderInfo.element[0], floatingEndpoint.canvas ], _element); // TODO: clean up the connection canvas (if the user aborted)
- _removeElement(inPlaceCopy.canvas, _element);
- _currentInstance.anchorManager.clearFor(placeholderInfo.id);
- var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
- jpc.endpoints[idx == 0 ? 1 : 0].anchor.locked = false;
- if (jpc.endpoints[idx] == floatingEndpoint) {
- // if the connection was an existing one:
- if (existingJpc && jpc.suspendedEndpoint) {
- // fix for issue35, thanks Sylvain Gizard: when firing the detach event make sure the
- // floating endpoint has been replaced.
- if (idx == 0) {
- jpc.source = existingJpcParams[0];
- jpc.sourceId = existingJpcParams[1];
- } else {
- jpc.target = existingJpcParams[0];
- jpc.targetId = existingJpcParams[1];
- }
-
- // restore the original scope (issue 57)
- jsPlumb.CurrentLibrary.setDragScope(existingJpcParams[2], existingJpcParams[3]);
- jpc.endpoints[idx] = jpc.suspendedEndpoint;
- if (self.isReattach || jpc._forceDetach || !jpc.endpoints[idx == 0 ? 1 : 0].detach(jpc)) {
- jpc.setHover(false);
- jpc.floatingAnchorIndex = null;
- jpc.suspendedEndpoint.addConnection(jpc);
- _currentInstance.repaint(existingJpcParams[1]);
- }
- jpc._forceDetach = null;
- } else {
- // TODO this looks suspiciously kind of like an Endpoint.detach call too.
- // i wonder if this one should post an event though. maybe this is good like this.
- _removeElements(jpc.connector.getDisplayElements(), self.parent);
- self.detachFromConnection(jpc);
- }
- }
- self.anchor.locked = false;
- self.paint({recalc:false});
- jpc.setHover(false, false);
- fireConnectionDragStopEvent(jpc);
- jpc = null;
- inPlaceCopy = null;
- delete endpointsByElement[floatingEndpoint.elementId];
- floatingEndpoint.anchor = null;
- floatingEndpoint = null;
- _currentInstance.currentlyDragging = false;
- });
-
- var i = _getElementObject(self.canvas);
- jsPlumb.CurrentLibrary.initDraggable(i, dragOptions, true);
- }
- // pulled this out into a function so we can reuse it for the inPlaceCopy canvas; you can now drop detached connections
- // back onto the endpoint you detached it from.
- var _initDropTarget = function(canvas, forceInit, isTransient, endpoint) {
- if ((params.isTarget || forceInit) && jsPlumb.CurrentLibrary.isDropSupported(_element)) {
- var dropOptions = params.dropOptions || _currentInstance.Defaults.DropOptions || jsPlumb.Defaults.DropOptions;
- dropOptions = jsPlumb.extend( {}, dropOptions);
- dropOptions.scope = dropOptions.scope || self.scope;
- var dropEvent = jsPlumb.CurrentLibrary.dragEvents['drop'],
- overEvent = jsPlumb.CurrentLibrary.dragEvents['over'],
- outEvent = jsPlumb.CurrentLibrary.dragEvents['out'],
- drop = function() {
- var originalEvent = jsPlumb.CurrentLibrary.getDropEvent(arguments);
- var draggable = _getElementObject(jsPlumb.CurrentLibrary.getDragObject(arguments)),
- id = _getAttribute(draggable, "dragId"),
- elId = _getAttribute(draggable, "elId"),
- scope = _getAttribute(draggable, "originalScope"),
- jpc = floatingConnections[id];
- if (jpc != null) {
- var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex, oidx = idx == 0 ? 1 : 0;
-
- // restore the original scope if necessary (issue 57)
- if (scope) jsPlumb.CurrentLibrary.setDragScope(draggable, scope);
-
- var endpointEnabled = endpoint != null ? endpoint.isEnabled() : true;
- if (!self.isFull() && !(idx == 0 && !self.isSource) && !(idx == 1 && !self.isTarget) && endpointEnabled) {
-
- var _doContinue = true;
- // the second check here is for the case that the user is dropping it back
- // where it came from.
- if (jpc.suspendedEndpoint && jpc.suspendedEndpoint.id != self.id) {
- if (idx == 0) {
- jpc.source = jpc.suspendedEndpoint.element;
- jpc.sourceId = jpc.suspendedEndpoint.elementId;
- } else {
- jpc.target = jpc.suspendedEndpoint.element;
- jpc.targetId = jpc.suspendedEndpoint.elementId;
- }
- if (!jpc.isDetachAllowed(jpc) || !jpc.endpoints[idx].isDetachAllowed(jpc) || !jpc.suspendedEndpoint.isDetachAllowed(jpc) || !_currentInstance.checkCondition("beforeDetach", jpc))
- _doContinue = false;
- }
-
- // these have to be set before testing for beforeDrop.
- if (idx == 0) {
- jpc.source = self.element;
- jpc.sourceId = self.elementId;
- } else {
- jpc.target = self.element;
- jpc.targetId = self.elementId;
- }
-
-
- // now check beforeDrop. this will be available only on Endpoints that are setup to
- // have a beforeDrop condition (although, secretly, under the hood all Endpoints and
- // the Connection have them, because they are on jsPlumbUIComponent. shhh!), because
- // it only makes sense to have it on a target endpoint.
- _doContinue = _doContinue && self.isDropAllowed(jpc.sourceId, jpc.targetId, jpc.scope);
-
- if (_doContinue) {
- // remove this jpc from the current endpoint
- jpc.endpoints[idx].detachFromConnection(jpc);
- if (jpc.suspendedEndpoint) jpc.suspendedEndpoint.detachFromConnection(jpc);
- jpc.endpoints[idx] = self;
- self.addConnection(jpc);
- if (!jpc.suspendedEndpoint) {
- //_addToList(connectionsByScope, jpc.scope, jpc);
- _initDraggableIfNecessary(self.element, params.draggable, {});
- }
- else {
- var suspendedElement = jpc.suspendedEndpoint.getElement(), suspendedElementId = jpc.suspendedEndpoint.elementId;
- // fire a detach event
- fireDetachEvent({
- source : idx == 0 ? suspendedElement : jpc.source,
- target : idx == 1 ? suspendedElement : jpc.target,
- sourceId : idx == 0 ? suspendedElementId : jpc.sourceId,
- targetId : idx == 1 ? suspendedElementId : jpc.targetId,
- sourceEndpoint : idx == 0 ? jpc.suspendedEndpoint : jpc.endpoints[0],
- targetEndpoint : idx == 1 ? jpc.suspendedEndpoint : jpc.endpoints[1],
- connection : jpc
- }, true, originalEvent);
- }
- // finalise will inform the anchor manager and also add to
- // connectionsByScope if necessary.
- _finaliseConnection(jpc, null, originalEvent);
- }
- else {
- // otherwise just put it back on the endpoint it was on before the drag.
- if (jpc.suspendedEndpoint) {
- // self.detachFrom(jpc);
- jpc.endpoints[idx] = jpc.suspendedEndpoint;
- jpc.setHover(false);
- jpc._forceDetach = true;
- if (idx == 0) {
- jpc.source = jpc.suspendedEndpoint.element;
- jpc.sourceId = jpc.suspendedEndpoint.elementId;
- } else {
- jpc.target = jpc.suspendedEndpoint.element;
- jpc.targetId = jpc.suspendedEndpoint.elementId;;
- }
- jpc.suspendedEndpoint.addConnection(jpc);
- jpc.endpoints[0].repaint();
- jpc.repaint();
- _currentInstance.repaint(jpc.source.elementId);
- jpc._forceDetach = false;
- }
- }
- jpc.floatingAnchorIndex = null;
- }
- _currentInstance.currentlyDragging = false;
- delete floatingConnections[id];
- }
- };
-
- dropOptions[dropEvent] = _wrap(dropOptions[dropEvent], drop);
- dropOptions[overEvent] = _wrap(dropOptions[overEvent], function() {
- if (self.isTarget) {
- var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments),
- id = _getAttribute( _getElementObject(draggable), "dragId"),
- jpc = floatingConnections[id];
- if (jpc != null) {
- var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
- jpc.endpoints[idx].anchor.over(self.anchor);
- }
- }
- });
- dropOptions[outEvent] = _wrap(dropOptions[outEvent], function() {
- if (self.isTarget) {
- var draggable = jsPlumb.CurrentLibrary.getDragObject(arguments),
- id = _getAttribute( _getElementObject(draggable), "dragId"),
- jpc = floatingConnections[id];
- if (jpc != null) {
- var idx = jpc.floatingAnchorIndex == null ? 1 : jpc.floatingAnchorIndex;
- jpc.endpoints[idx].anchor.out();
- }
- }
- });
- jsPlumb.CurrentLibrary.initDroppable(canvas, dropOptions, true, isTransient);
- }
- };
-
- // initialise the endpoint's canvas as a drop target. this will be ignored if the endpoint is not a target or drag is not supported.
- _initDropTarget(_getElementObject(self.canvas), true, !(params._transient || self.anchor.isFloating), self);
- return self;
- };
- };
- var jsPlumb = window.jsPlumb = new jsPlumbInstance();
- jsPlumb.getInstance = function(_defaults) {
- var j = new jsPlumbInstance(_defaults);
- j.init();
- return j;
- };
- jsPlumb.util = {
- convertStyle : function(s, ignoreAlpha) {
- // TODO: jsPlumb should support a separate 'opacity' style member.
- if ("transparent" === s) return s;
- var o = s,
- pad = function(n) { return n.length == 1 ? "0" + n : n; },
- hex = function(k) { return pad(Number(k).toString(16)); },
- pattern = /(rgb[a]?\()(.*)(\))/;
- if (s.match(pattern)) {
- var parts = s.match(pattern)[2].split(",");
- o = "#" + hex(parts[0]) + hex(parts[1]) + hex(parts[2]);
- if (!ignoreAlpha && parts.length == 4)
- o = o + hex(parts[3]);
- }
- return o;
- },
- gradient : function(p1, p2) {
- p1 = p1.constructor == Array ? p1 : [p1.x, p1.y];
- p2 = p2.constructor == Array ? p2 : [p2.x, p2.y];
- return (p2[1] - p1[1]) / (p2[0] - p1[0]);
- },
- normal : function(p1, p2) {
- return -1 / jsPlumb.util.gradient(p1,p2);
- },
- segment : function(p1, p2) {
- p1 = p1.constructor == Array ? p1 : [p1.x, p1.y];
- p2 = p2.constructor == Array ? p2 : [p2.x, p2.y];
- if (p2[0] > p1[0]) {
- return (p2[1] > p1[1]) ? 2 : 1;
- }
- else {
- return (p2[1] > p1[1]) ? 3 : 4;
- }
- },
- intersects : function(r1, r2) {
- var x1 = r1.x, x2 = r1.x + r1.w, y1 = r1.y, y2 = r1.y + r1.h,
- a1 = r2.x, a2 = r2.x + r2.w, b1 = r2.y, b2 = r2.y + r2.h;
-
- return ( (x1 < a1 && a1 < x2) && (y1 < b1 && b1 < y2) ) ||
- ( (x1 < a2 && a2 < x2) && (y1 < b1 && b1 < y2) ) ||
- ( (x1 < a1 && a1 < x2) && (y1 < b2 && b2 < y2) ) ||
- ( (x1 < a2 && a1 < x2) && (y1 < b2 && b2 < y2) ) ||
-
- ( (a1 < x1 && x1 < a2) && (b1 < y1 && y1 < b2) ) ||
- ( (a1 < x2 && x2 < a2) && (b1 < y1 && y1 < b2) ) ||
- ( (a1 < x1 && x1 < a2) && (b1 < y2 && y2 < b2) ) ||
- ( (a1 < x2 && x1 < a2) && (b1 < y2 && y2 < b2) );
- },
- segmentMultipliers : [null, [1, -1], [1, 1], [-1, 1], [-1, -1] ],
- inverseSegmentMultipliers : [null, [-1, -1], [-1, 1], [1, 1], [1, -1] ],
- pointOnLine : function(fromPoint, toPoint, distance) {
- var m = jsPlumb.util.gradient(fromPoint, toPoint),
- s = jsPlumb.util.segment(fromPoint, toPoint),
- segmentMultiplier = distance > 0 ? jsPlumb.util.segmentMultipliers[s] : jsPlumb.util.inverseSegmentMultipliers[s],
- theta = Math.atan(m),
- y = Math.abs(distance * Math.sin(theta)) * segmentMultiplier[1],
- x = Math.abs(distance * Math.cos(theta)) * segmentMultiplier[0];
- return { x:fromPoint.x + x, y:fromPoint.y + y };
- },
- /**
- * calculates a perpendicular to the line fromPoint->toPoint, that passes through toPoint and is 'length' long.
- * @param fromPoint
- * @param toPoint
- * @param length
- */
- perpendicularLineTo : function(fromPoint, toPoint, length) {
- var m = jsPlumb.util.gradient(fromPoint, toPoint),
- theta2 = Math.atan(-1 / m),
- y = length / 2 * Math.sin(theta2),
- x = length / 2 * Math.cos(theta2);
- return [{x:toPoint.x + x, y:toPoint.y + y}, {x:toPoint.x - x, y:toPoint.y - y}];
- }
- };
-
- var _curryAnchor = function(x, y, ox, oy, type, fnInit) {
- return function(params) {
- params = params || {};
- //var a = jsPlumb.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance);
- var a = params.jsPlumbInstance.makeAnchor([ x, y, ox, oy, 0, 0 ], params.elementId, params.jsPlumbInstance);
- a.type = type;
- if (fnInit) fnInit(a, params);
- return a;
- };
- };
- jsPlumb.Anchors["TopCenter"] = _curryAnchor(0.5, 0, 0,-1, "TopCenter");
- jsPlumb.Anchors["BottomCenter"] = _curryAnchor(0.5, 1, 0, 1, "BottomCenter");
- jsPlumb.Anchors["LeftMiddle"] = _curryAnchor(0, 0.5, -1, 0, "LeftMiddle");
- jsPlumb.Anchors["RightMiddle"] = _curryAnchor(1, 0.5, 1, 0, "RightMiddle");
- jsPlumb.Anchors["Center"] = _curryAnchor(0.5, 0.5, 0, 0, "Center");
- jsPlumb.Anchors["TopRight"] = _curryAnchor(1, 0, 0,-1, "TopRight");
- jsPlumb.Anchors["BottomRight"] = _curryAnchor(1, 1, 0, 1, "BottomRight");
- jsPlumb.Anchors["TopLeft"] = _curryAnchor(0, 0, 0, -1, "TopLeft");
- jsPlumb.Anchors["BottomLeft"] = _curryAnchor(0, 1, 0, 1, "BottomLeft");
-
- // TODO test that this does not break with the current instance idea
- jsPlumb.Defaults.DynamicAnchors = function(params) {
- return params.jsPlumbInstance.makeAnchors(["TopCenter", "RightMiddle", "BottomCenter", "LeftMiddle"], params.elementId, params.jsPlumbInstance);
- };
- jsPlumb.Anchors["AutoDefault"] = function(params) {
- var a = params.jsPlumbInstance.makeDynamicAnchor(jsPlumb.Defaults.DynamicAnchors(params));
- a.type = "AutoDefault";
- return a;
- };
-
- jsPlumb.Anchors["Assign"] = _curryAnchor(0,0,0,0,"Assign", function(anchor, params) {
- // find what to use as the "position finder". the user may have supplied a String which represents
- // the id of a position finder in jsPlumb.AnchorPositionFinders, or the user may have supplied the
- // position finder as a function. we find out what to use and then set it on the anchor.
- var pf = params.position || "Fixed";
- anchor.positionFinder = pf.constructor == String ? params.jsPlumbInstance.AnchorPositionFinders[pf] : pf;
- // always set the constructor params; the position finder might need them later (the Grid one does,
- // for example)
- anchor.constructorParams = params;
- });
- // Continuous anchor is just curried through to the 'get' method of the continuous anchor
- // factory.
- jsPlumb.Anchors["Continuous"] = function(params) {
- return params.jsPlumbInstance.continuousAnchorFactory.get(params);
- };
- // these are the default anchor positions finders, which are used by the makeTarget function. supply
- // a position finder argument to that function allows you to specify where the resulting anchor will
- // be located
- jsPlumb.AnchorPositionFinders = {
- "Fixed": function(dp, ep, es, params) {
- return [ (dp.left - ep.left) / es[0], (dp.top - ep.top) / es[1] ];
- },
- "Grid":function(dp, ep, es, params) {
- var dx = dp.left - ep.left, dy = dp.top - ep.top,
- gx = es[0] / (params.grid[0]), gy = es[1] / (params.grid[1]),
- mx = Math.floor(dx / gx), my = Math.floor(dy / gy);
- return [ ((mx * gx) + (gx / 2)) / es[0], ((my * gy) + (gy / 2)) / es[1] ];
- }
- };
- })();
- /*
- * jsPlumb
- *
- * Title:jsPlumb 1.3.7
- *
- * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
- * elements, or VML.
- *
- * This file contains the default Connectors, Endpoint and Overlay definitions.
- *
- * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org)
- *
- * http://jsplumb.org
- * http://github.com/sporritt/jsplumb
- * http://code.google.com/p/jsplumb
- *
- * Dual licensed under the MIT and GPL2 licenses.
- */
- (function() {
-
- /**
- *
- * Helper class to consume unused mouse events by components that are DOM elements and
- * are used by all of the different rendering modes.
- *
- */
- jsPlumb.DOMElementComponent = function(params) {
- jsPlumb.jsPlumbUIComponent.apply(this, arguments);
- // when render mode is canvas, these functions may be called by the canvas mouse handler.
- // this component is safe to pipe this stuff to /dev/null.
- this.mousemove =
- this.dblclick =
- this.click =
- this.mousedown =
- this.mouseup = function(e) { };
- };
-
- /**
- * Class: Connectors.Straight
- * The Straight connector draws a simple straight line between the two anchor points. It does not have any constructor parameters.
- */
- jsPlumb.Connectors.Straight = function() {
- this.type = "Straight";
- var self = this,
- currentPoints = null,
- _m, _m2, _b, _dx, _dy, _theta, _theta2, _sx, _sy, _tx, _ty, _segment, _length;
- /**
- * Computes the new size and position of the canvas.
- */
- this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) {
- var w = Math.abs(sourcePos[0] - targetPos[0]),
- h = Math.abs(sourcePos[1] - targetPos[1]),
- // these are padding to ensure the whole connector line appears
- xo = 0.45 * w, yo = 0.45 * h;
- // these are padding to ensure the whole connector line appears
- w *= 1.9; h *=1.9;
-
- var x = Math.min(sourcePos[0], targetPos[0]) - xo;
- var y = Math.min(sourcePos[1], targetPos[1]) - yo;
-
- // minimum size is 2 * line Width if minWidth was not given.
- var calculatedMinWidth = Math.max(2 * lineWidth, minWidth);
-
- if (w < calculatedMinWidth) {
- w = calculatedMinWidth;
- x = sourcePos[0] + ((targetPos[0] - sourcePos[0]) / 2) - (calculatedMinWidth / 2);
- xo = (w - Math.abs(sourcePos[0]-targetPos[0])) / 2;
- }
- if (h < calculatedMinWidth) {
- h = calculatedMinWidth;
- y = sourcePos[1] + ((targetPos[1] - sourcePos[1]) / 2) - (calculatedMinWidth / 2);
- yo = (h - Math.abs(sourcePos[1]-targetPos[1])) / 2;
- }
-
- _sx = sourcePos[0] < targetPos[0] ? xo : w-xo;
- _sy = sourcePos[1] < targetPos[1] ? yo:h-yo;
- _tx = sourcePos[0] < targetPos[0] ? w-xo : xo;
- _ty = sourcePos[1] < targetPos[1] ? h-yo : yo;
- currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty ];
- _dx = _tx - _sx, _dy = _ty - _sy;
- //_m = _dy / _dx, _m2 = -1 / _m;
- _m = jsPlumb.util.gradient({x:_sx, y:_sy}, {x:_tx, y:_ty}), _m2 = -1 / _m;
- _b = -1 * ((_m * _sx) - _sy);
- _theta = Math.atan(_m); _theta2 = Math.atan(_m2);
- //_segment = jsPlumb.util.segment({x:_sx, y:_sy}, {x:_tx, y:_ty});
- _length = Math.sqrt((_dx * _dx) + (_dy * _dy));
-
- return currentPoints;
- };
-
-
- /**
- * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from
- * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much.
- */
- this.pointOnPath = function(location) {
- if (location == 0)
- return { x:_sx, y:_sy };
- else if (location == 1)
- return { x:_tx, y:_ty };
- else
- return jsPlumb.util.pointOnLine({x:_sx, y:_sy}, {x:_tx, y:_ty}, location * _length);
- };
-
- /**
- * returns the gradient of the connector at the given point - which for us is constant.
- */
- this.gradientAtPoint = function(location) {
- return _m;
- };
-
- /**
- * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where
- * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels.
- * this hands off to jsPlumb.util to do the maths, supplying two points and the distance.
- */
- this.pointAlongPathFrom = function(location, distance) {
- var p = self.pointOnPath(location),
- farAwayPoint = location == 1 ? {
- x:_sx + ((_tx - _sx) * 10),
- y:_sy + ((_sy - _ty) * 10)
- } : {x:_tx, y:_ty };
- return jsPlumb.util.pointOnLine(p, farAwayPoint, distance);
- };
- };
-
-
- /**
- * Class:Connectors.Bezier
- * This Connector draws a Bezier curve with two control points. You can provide a 'curviness' value which gets applied to jsPlumb's
- * internal voodoo machine and ends up generating locations for the two control points. See the constructor documentation below.
- */
- /**
- * Function:Constructor
- *
- * Parameters:
- * curviness - How 'curvy' you want the curve to be! This is a directive for the placement of control points, not endpoints of the curve, so your curve does not
- * actually touch the given point, but it has the tendency to lean towards it. The larger this value, the greater the curve is pulled from a straight line.
- * Optional; defaults to 150.
- * stub - optional value for a distance to travel from the connector's endpoint before beginning the Bezier curve. defaults to 0.
- *
- */
- jsPlumb.Connectors.Bezier = function(params) {
- var self = this;
- params = params || {};
- this.majorAnchor = params.curviness || 150;
- this.minorAnchor = 10;
- var currentPoints = null;
- this.type = "Bezier";
-
- this._findControlPoint = function(point, sourceAnchorPosition, targetAnchorPosition, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor) {
- // determine if the two anchors are perpendicular to each other in their orientation. we swap the control
- // points around if so (code could be tightened up)
- var soo = sourceAnchor.getOrientation(sourceEndpoint),
- too = targetAnchor.getOrientation(targetEndpoint),
- perpendicular = soo[0] != too[0] || soo[1] == too[1],
- p = [],
- ma = self.majorAnchor, mi = self.minorAnchor;
-
- if (!perpendicular) {
- if (soo[0] == 0) // X
- p.push(sourceAnchorPosition[0] < targetAnchorPosition[0] ? point[0] + mi : point[0] - mi);
- else p.push(point[0] - (ma * soo[0]));
-
- if (soo[1] == 0) // Y
- p.push(sourceAnchorPosition[1] < targetAnchorPosition[1] ? point[1] + mi : point[1] - mi);
- else p.push(point[1] + (ma * too[1]));
- }
- else {
- if (too[0] == 0) // X
- p.push(targetAnchorPosition[0] < sourceAnchorPosition[0] ? point[0] + mi : point[0] - mi);
- else p.push(point[0] + (ma * too[0]));
-
- if (too[1] == 0) // Y
- p.push(targetAnchorPosition[1] < sourceAnchorPosition[1] ? point[1] + mi : point[1] - mi);
- else p.push(point[1] + (ma * soo[1]));
- }
- return p;
- };
- var _CP, _CP2, _sx, _tx, _ty, _sx, _sy, _canvasX, _canvasY, _w, _h, _sStubX, _sStubY, _tStubX, _tStubY;
- this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) {
- lineWidth = lineWidth || 0;
- _w = Math.abs(sourcePos[0] - targetPos[0]) + lineWidth;
- _h = Math.abs(sourcePos[1] - targetPos[1]) + lineWidth;
- _canvasX = Math.min(sourcePos[0], targetPos[0])-(lineWidth/2);
- _canvasY = Math.min(sourcePos[1], targetPos[1])-(lineWidth/2);
- _sx = sourcePos[0] < targetPos[0] ? _w - (lineWidth/2): (lineWidth/2);
- _sy = sourcePos[1] < targetPos[1] ? _h - (lineWidth/2) : (lineWidth/2);
- _tx = sourcePos[0] < targetPos[0] ? (lineWidth/2) : _w - (lineWidth/2);
- _ty = sourcePos[1] < targetPos[1] ? (lineWidth/2) : _h - (lineWidth/2);
-
- _CP = self._findControlPoint([_sx,_sy], sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor);
- _CP2 = self._findControlPoint([_tx,_ty], targetPos, sourcePos, sourceEndpoint, targetEndpoint, targetAnchor, sourceAnchor);
- var minx1 = Math.min(_sx,_tx), minx2 = Math.min(_CP[0], _CP2[0]), minx = Math.min(minx1,minx2),
- maxx1 = Math.max(_sx,_tx), maxx2 = Math.max(_CP[0], _CP2[0]), maxx = Math.max(maxx1,maxx2);
-
- if (maxx > _w) _w = maxx;
- if (minx < 0) {
- _canvasX += minx; var ox = Math.abs(minx);
- _w += ox; _CP[0] += ox; _sx += ox; _tx +=ox; _CP2[0] += ox;
- }
- var miny1 = Math.min(_sy,_ty), miny2 = Math.min(_CP[1], _CP2[1]), miny = Math.min(miny1,miny2),
- maxy1 = Math.max(_sy,_ty), maxy2 = Math.max(_CP[1], _CP2[1]), maxy = Math.max(maxy1,maxy2);
-
- if (maxy > _h) _h = maxy;
- if (miny < 0) {
- _canvasY += miny; var oy = Math.abs(miny);
- _h += oy; _CP[1] += oy; _sy += oy; _ty +=oy; _CP2[1] += oy;
- }
-
- if (minWidth && _w < minWidth) {
- var posAdjust = (minWidth - _w) / 2;
- _w = minWidth;
- _canvasX -= posAdjust; _sx = _sx + posAdjust ; _tx = _tx + posAdjust; _CP[0] = _CP[0] + posAdjust; _CP2[0] = _CP2[0] + posAdjust;
- }
-
- if (minWidth && _h < minWidth) {
- var posAdjust = (minWidth - _h) / 2;
- _h = minWidth;
- _canvasY -= posAdjust; _sy = _sy + posAdjust ; _ty = _ty + posAdjust; _CP[1] = _CP[1] + posAdjust; _CP2[1] = _CP2[1] + posAdjust;
- }
- currentPoints = [_canvasX, _canvasY, _w, _h,
- _sx, _sy, _tx, _ty,
- _CP[0], _CP[1], _CP2[0], _CP2[1] ];
-
- return currentPoints;
- };
-
- var _makeCurve = function() {
- return [
- { x:_sx, y:_sy },
- { x:_CP[0], y:_CP[1] },
- { x:_CP2[0], y:_CP2[1] },
- { x:_tx, y:_ty }
- ];
- };
-
- /**
- * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from
- * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much.
- */
- this.pointOnPath = function(location) {
- return jsBezier.pointOnCurve(_makeCurve(), location);
- };
-
- /**
- * returns the gradient of the connector at the given point.
- */
- this.gradientAtPoint = function(location) {
- return jsBezier.gradientAtPoint(_makeCurve(), location);
- };
-
- /**
- * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult.
- * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous
- * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see.
- * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller
- * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to
- * calculate the step as a function of distance/distance between endpoints.
- */
- this.pointAlongPathFrom = function(location, distance) {
- return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance);
- };
- };
-
-
- /**
- * Class: Connectors.Flowchart
- * Provides 'flowchart' connectors, consisting of vertical and horizontal line segments.
- */
- /**
- * Function: Constructor
- *
- * Parameters:
- * stub - minimum length for the stub at each end of the connector. defaults to 30 pixels.
- */
- jsPlumb.Connectors.Flowchart = function(params) {
- this.type = "Flowchart";
- params = params || {};
- var self = this,
- minStubLength = params.stub || params.minStubLength /* bwds compat. */ || 30,
- segments = [],
- totalLength = 0,
- segmentProportions = [],
- segmentProportionalLengths = [],
- points = [],
- swapX, swapY,
- maxX = 0, maxY = 0,
- /**
- * recalculates the points at which the segments begin and end, proportional to the total length travelled
- * by all the segments that constitute the connector. we use this to help with pointOnPath calculations.
- */
- updateSegmentProportions = function(startX, startY, endX, endY) {
- var curLoc = 0;
- for (var i = 0; i < segments.length; i++) {
- segmentProportionalLengths[i] = segments[i][5] / totalLength;
- segmentProportions[i] = [curLoc, (curLoc += (segments[i][5] / totalLength)) ];
- }
- },
- appendSegmentsToPoints = function() {
- points.push(segments.length);
- for (var i = 0; i < segments.length; i++) {
- points.push(segments[i][0]);
- points.push(segments[i][1]);
- }
- },
- /**
- * helper method to add a segment.
- */
- addSegment = function(x, y, sx, sy, tx, ty) {
- var lx = segments.length == 0 ? sx : segments[segments.length - 1][0],
- ly = segments.length == 0 ? sy : segments[segments.length - 1][1],
- m = x == lx ? Infinity : 0,
- l = Math.abs(x == lx ? y - ly : x - lx);
- segments.push([x, y, lx, ly, m, l]);
- totalLength += l;
-
- maxX = Math.max(maxX, x);
- maxY = Math.max(maxY, y);
- },
- /**
- * returns [segment, proportion of travel in segment, segment index] for the segment
- * that contains the point which is 'location' distance along the entire path, where
- * 'location' is a decimal between 0 and 1 inclusive. in this connector type, paths
- * are made up of a list of segments, each of which contributes some fraction to
- * the total length.
- */
- findSegmentForLocation = function(location) {
- var idx = segmentProportions.length - 1, inSegmentProportion = 1;
- for (var i = 0; i < segmentProportions.length; i++) {
- if (segmentProportions[i][1] >= location) {
- idx = i;
- inSegmentProportion = (location - segmentProportions[i][0]) / segmentProportionalLengths[i];
- break;
- }
- }
- return { segment:segments[idx], proportion:inSegmentProportion, index:idx };
- };
-
- this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint,
- sourceAnchor, targetAnchor, lineWidth, minWidth, sourceInfo, targetInfo) {
-
- segments = [];
- totalLength = 0;
- segmentProportionalLengths = [];
- maxX = maxY = 0;
-
- swapX = targetPos[0] < sourcePos[0];
- swapY = targetPos[1] < sourcePos[1];
-
- var lw = lineWidth || 1,
- offx = (lw / 2) + (minStubLength * 2),
- offy = (lw / 2) + (minStubLength * 2),
- so = sourceAnchor.orientation || sourceAnchor.getOrientation(sourceEndpoint),
- to = targetAnchor.orientation || targetAnchor.getOrientation(targetEndpoint),
- x = swapX ? targetPos[0] : sourcePos[0],
- y = swapY ? targetPos[1] : sourcePos[1],
- w = Math.abs(targetPos[0] - sourcePos[0]) + 2*offx,
- h = Math.abs(targetPos[1] - sourcePos[1]) + 2*offy;
- // if either anchor does not have an orientation set, we derive one from their relative
- // positions. we fix the axis to be the one in which the two elements are further apart, and
- // point each anchor at the other element. this is also used when dragging a new connection.
- if (so[0] == 0 && so[1] == 0 || to[0] == 0 && to[1] == 0) {
- var index = w > h ? 0 : 1, oIndex = [1,0][index];
- so = []; to = [];
- so[index] = sourcePos[index] > targetPos[index] ? -1 : 1;
- to[index] = sourcePos[index] > targetPos[index] ? 1 : -1;
- so[oIndex] = 0;
- to[oIndex] = 0;
- }
- if (w < minWidth) {
- offx += (minWidth - w) / 2;
- w = minWidth;
- }
- if (h < minWidth) {
- offy += (minWidth - h) / 2;
- h = minWidth;
- }
- var sx = swapX ? w-offx : offx,
- sy = swapY ? h-offy : offy,
- tx = swapX ? offx : w-offx ,
- ty = swapY ? offy : h-offy,
- startStubX = sx + (so[0] * minStubLength),
- startStubY = sy + (so[1] * minStubLength),
- endStubX = tx + (to[0] * minStubLength),
- endStubY = ty + (to[1] * minStubLength),
- isXGreaterThanStubTimes2 = Math.abs(sx - tx) > 2 * minStubLength,
- isYGreaterThanStubTimes2 = Math.abs(sy - ty) > 2 * minStubLength,
- midx = startStubX + ((endStubX - startStubX) / 2),
- midy = startStubY + ((endStubY - startStubY) / 2),
- oProduct = ((so[0] * to[0]) + (so[1] * to[1])),
- opposite = oProduct == -1,
- perpendicular = oProduct == 0,
- orthogonal = oProduct == 1;
-
- x -= offx; y -= offy;
- points = [x, y, w, h, sx, sy, tx, ty];
- var extraPoints = [];
-
- addSegment(startStubX, startStubY, sx, sy, tx, ty);
-
- var sourceAxis = so[0] == 0 ? "y" : "x",
- anchorOrientation = opposite ? "opposite" : orthogonal ? "orthogonal" : "perpendicular",
- segment = jsPlumb.util.segment([sx, sy], [tx, ty]),
- flipSourceSegments = so[sourceAxis == "x" ? 0 : 1] == -1,
- flipSegments = {
- "x":[null, 4, 3, 2, 1],
- "y":[null, 2, 1, 4, 3]
- }
-
- if (flipSourceSegments)
- segment = flipSegments[sourceAxis][segment];
- var findClearedLine = function(start, mult, anchorPos, dimension) {
- return start + (mult * (( 1 - anchorPos) * dimension) + minStubLength);
- //mx = so[0] == 0 ? startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength : startStubX,
- },
- lineCalculators = {
- oppositex : function() {
- if (sourceEndpoint.elementId == targetEndpoint.elementId) {
- var _y = startStubY + ((1 - sourceAnchor.y) * sourceInfo.height) + minStubLength;
- return [ [ startStubX, _y ], [ endStubX, _y ]];
- }
- else if (isXGreaterThanStubTimes2 && (segment == 1 || segment == 2)) {
- return [[ midx, sy ], [ midx, ty ]];
- }
- else {
- return [[ startStubX, midy ], [endStubX, midy ]];
- }
- },
- orthogonalx : function() {
- if (segment == 1 || segment == 2) {
- return [ [ endStubX, startStubY ]];
- }
- else {
- return [ [ startStubX, endStubY ]];
- }
- },
- perpendicularx : function() {
- var my = (ty + sy) / 2;
- if ((segment == 1 && to[1] == 1) || (segment == 2 && to[1] == -1)) {
- if (Math.abs(tx - sx) > minStubLength)
- return [ [endStubX, startStubY ]];
- else
- return [ [startStubX, startStubY ], [ startStubX, my ], [ endStubX, my ]];
- }
- else if ((segment == 3 && to[1] == -1) || (segment == 4 && to[1] == 1)) {
- return [ [ startStubX, my ], [ endStubX, my ]];
- }
- else if ((segment == 3 && to[1] == 1) || (segment == 4 && to[1] == -1)) {
- return [ [ startStubX, endStubY ]];
- }
- else if ((segment == 1 && to[1] == -1) || (segment == 2 && to[1] == 1)) {
- if (Math.abs(tx - sx) > minStubLength)
- return [ [ midx, startStubY ], [ midx, endStubY ]];
- else
- return [ [ startStubX, endStubY ]];
- }
- },
- oppositey : function() {
- if (sourceEndpoint.elementId == targetEndpoint.elementId) {
- var _x = startStubX + ((1 - sourceAnchor.x) * sourceInfo.width) + minStubLength;
- return [ [ _x, startStubY ], [ _x, endStubY ]];
- }
- else if (isYGreaterThanStubTimes2 && (segment == 2 || segment == 3)) {
- return [[ sx, midy ], [ tx, midy ]];
- }
- else {
- return [[ midx, startStubY ], [midx, endStubY ]];
- }
- },
- orthogonaly : function() {
- if (segment == 2 || segment == 3) {
- return [ [ startStubX, endStubY ]];
- }
- else {
- return [ [ endStubX, startStubY ]];
- }
- },
- perpendiculary : function() {
- var mx = (tx + sx) / 2;
- if ((segment == 2 && to[0] == -1) || (segment == 3 && to[0] == 1)) {
- if (Math.abs(tx - sx) > minStubLength)
- return [ [startStubX, endStubY ]];
- else
- return [ [startStubX, midy ], [ endStubX, midy ]];
- }
- else if ((segment == 1 && to[0] == -1) || (segment == 4 && to[0] == 1)) {
- var mx = (tx + sx) / 2;
- return [ [ mx, startStubY ], [ mx, endStubY ]];
- }
- else if ((segment == 1 && to[0] == 1) || (segment == 4 && to[0] == -1)) {
- return [ [ endStubX, startStubY ]];
- }
- else if ((segment == 2 && to[0] == 1) || (segment == 3 && to[0] == -1)) {
- if (Math.abs(ty - sy) > minStubLength)
- return [ [ startStubX, midy ], [ endStubX, midy ]];
- else
- return [ [ endStubX, startStubY ]];
- }
- }
- };
- var p = lineCalculators[anchorOrientation + sourceAxis]();
- if (p) {
- for (var i = 0; i < p.length; i++) {
- addSegment(p[i][0], p[i][1], sx, sy, tx, ty);
- }
- }
-
- addSegment(endStubX, endStubY, sx, sy, tx, ty);
- addSegment(tx, ty, sx, sy, tx, ty);
-
- appendSegmentsToPoints();
- updateSegmentProportions(sx, sy, tx, ty);
-
- // adjust the max values of the canvas if we have a value that is larger than what we previously set.
- //
- if (maxY > points[3]) points[3] = maxY + (lineWidth * 2);
- if (maxX > points[2]) points[2] = maxX + (lineWidth * 2);
-
- return points;
- };
-
- /**
- * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from
- * 0 to 1 inclusive. for this connector we must first figure out which segment the given point lies in, and then compute the x,y position
- * from our knowledge of the segment's start and end points.
- */
- this.pointOnPath = function(location) {
- return self.pointAlongPathFrom(location, 0);
- };
-
- /**
- * returns the gradient of the connector at the given point; the gradient will be either 0 or Infinity, depending on the direction of the
- * segment the point falls in. segment gradients are calculated in the compute method.
- */
- this.gradientAtPoint = function(location) {
- return segments[findSegmentForLocation(location)["index"]][4];
- };
-
- /**
- * returns the point on the connector's path that is 'distance' along the length of the path from 'location', where
- * 'location' is a decimal from 0 to 1 inclusive, and 'distance' is a number of pixels. when you consider this concept from the point of view
- * of this connector, it starts to become clear that there's a problem with the overlay paint code: given that this connector makes several
- * 90 degree turns, it's entirely possible that an arrow overlay could be forced to paint itself around a corner, which would look stupid. this is
- * because jsPlumb uses this method (and pointOnPath) so determine the locations of the various points that go to make up an overlay. a better
- * solution would probably be to just use pointOnPath along with gradientAtPoint, and draw the overlay so that its axis ran along
- * a tangent to the connector. for straight line connectors this would obviously mean the overlay was painted directly on the connector, since a
- * tangent to a straight line is the line itself, which is what we want; for this connector, and for beziers, the results would probably be better. an additional
- * advantage is, of course, that there's less computation involved doing it that way.
- */
- this.pointAlongPathFrom = function(location, distance) {
- var s = findSegmentForLocation(location), seg = s.segment, p = s.proportion, sl = segments[s.index][5], m = segments[s.index][4];
- var e = {
- x : m == Infinity ? seg[2] : seg[2] > seg[0] ? seg[0] + ((1 - p) * sl) - distance : seg[2] + (p * sl) + distance,
- y : m == 0 ? seg[3] : seg[3] > seg[1] ? seg[1] + ((1 - p) * sl) - distance : seg[3] + (p * sl) + distance,
- segmentInfo : s
- };
-
- return e;
- };
- };
- // ********************************* END OF CONNECTOR TYPES *******************************************************************
-
- // ********************************* ENDPOINT TYPES *******************************************************************
-
- /**
- * Class: Endpoints.Dot
- * A round endpoint, with default radius 10 pixels.
- */
-
- /**
- * Function: Constructor
- *
- * Parameters:
- *
- * radius - radius of the endpoint. defaults to 10 pixels.
- */
- jsPlumb.Endpoints.Dot = function(params) {
- this.type = "Dot";
- var self = this;
- params = params || {};
- this.radius = params.radius || 10;
- this.defaultOffset = 0.5 * this.radius;
- this.defaultInnerRadius = this.radius / 3;
-
- this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
- var r = endpointStyle.radius || self.radius,
- x = anchorPoint[0] - r,
- y = anchorPoint[1] - r;
- return [ x, y, r * 2, r * 2, r ];
- };
- };
-
- /**
- * Class: Endpoints.Rectangle
- * A Rectangular Endpoint, with default size 20x20.
- */
- /**
- * Function: Constructor
- *
- * Parameters:
- *
- * width - width of the endpoint. defaults to 20 pixels.
- * height - height of the endpoint. defaults to 20 pixels.
- */
- jsPlumb.Endpoints.Rectangle = function(params) {
- this.type = "Rectangle";
- var self = this;
- params = params || {};
- this.width = params.width || 20;
- this.height = params.height || 20;
-
- this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
- var width = endpointStyle.width || self.width,
- height = endpointStyle.height || self.height,
- x = anchorPoint[0] - (width/2),
- y = anchorPoint[1] - (height/2);
- return [ x, y, width, height];
- };
- };
-
- var DOMElementEndpoint = function(params) {
- jsPlumb.DOMElementComponent.apply(this, arguments);
- var self = this;
- var displayElements = [ ];
- this.getDisplayElements = function() {
- return displayElements;
- };
-
- this.appendDisplayElement = function(el) {
- displayElements.push(el);
- };
- };
- /**
- * Class: Endpoints.Image
- * Draws an image as the Endpoint.
- */
- /**
- * Function: Constructor
- *
- * Parameters:
- *
- * src - location of the image to use.
- */
- jsPlumb.Endpoints.Image = function(params) {
-
- this.type = "Image";
- DOMElementEndpoint.apply(this, arguments);
-
- var self = this,
- initialized = false,
- widthToUse = params.width,
- heightToUse = params.height,
- _onload = null,
- _endpoint = params.endpoint;
-
- this.img = new Image();
- self.ready = false;
- this.img.onload = function() {
- self.ready = true;
- widthToUse = widthToUse || self.img.width;
- heightToUse = heightToUse || self.img.height;
- if (_onload) {
- _onload(self);
- }
- };
- _endpoint.setImage = function(img, onload) {
- var s = img.constructor == String ? img : img.src;
- _onload = onload;
- self.img.src = img;
- };
- _endpoint.setImage(params.src || params.url, params.onload);
- this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
- self.anchorPoint = anchorPoint;
- if (self.ready) return [anchorPoint[0] - widthToUse / 2, anchorPoint[1] - heightToUse / 2,
- widthToUse, heightToUse];
- else return [0,0,0,0];
- };
-
- self.canvas = document.createElement("img"), initialized = false;
- self.canvas.style["margin"] = 0;
- self.canvas.style["padding"] = 0;
- self.canvas.style["outline"] = 0;
- self.canvas.style["position"] = "absolute";
- var clazz = params.cssClass ? " " + params.cssClass : "";
- self.canvas.className = jsPlumb.endpointClass + clazz;
- if (widthToUse) self.canvas.setAttribute("width", widthToUse);
- if (heightToUse) self.canvas.setAttribute("height", heightToUse);
- jsPlumb.appendElement(self.canvas, params.parent);
- self.attachListeners(self.canvas, self);
-
- var actuallyPaint = function(d, style, anchor) {
- if (!initialized) {
- self.canvas.setAttribute("src", self.img.src);
- initialized = true;
- }
- var x = self.anchorPoint[0] - (widthToUse / 2),
- y = self.anchorPoint[1] - (heightToUse / 2);
- jsPlumb.sizeCanvas(self.canvas, x, y, widthToUse, heightToUse);
- };
-
- this.paint = function(d, style, anchor) {
- if (self.ready) {
- actuallyPaint(d, style, anchor);
- }
- else {
- window.setTimeout(function() {
- self.paint(d, style, anchor);
- }, 200);
- }
- };
- };
-
- /**
- * Class: Endpoints.Blank
- * An Endpoint that paints nothing (visible) on the screen. Supports cssClass and hoverClass parameters like all Endpoints.
- */
- jsPlumb.Endpoints.Blank = function(params) {
- var self = this;
- this.type = "Blank";
- DOMElementEndpoint.apply(this, arguments);
- this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
- return [anchorPoint[0], anchorPoint[1],10,0];
- };
-
- self.canvas = document.createElement("div");
- self.canvas.style.display = "block";
- self.canvas.style.width = "1px";
- self.canvas.style.height = "1px";
- self.canvas.style.background = "transparent";
- self.canvas.style.position = "absolute";
- self.canvas.className = self._jsPlumb.endpointClass;
- jsPlumb.appendElement(self.canvas, params.parent);
-
- this.paint = function(d, style, anchor) {
- jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]);
- };
- };
-
- /**
- * Class: Endpoints.Triangle
- * A triangular Endpoint.
- */
- /**
- * Function: Constructor
- *
- * Parameters:
- *
- * width - width of the triangle's base. defaults to 55 pixels.
- * height - height of the triangle from base to apex. defaults to 55 pixels.
- */
- jsPlumb.Endpoints.Triangle = function(params) {
- this.type = "Triangle";
- params = params || { };
- params.width = params.width || 55;
- params.height = params.height || 55;
- this.width = params.width;
- this.height = params.height;
- this.compute = function(anchorPoint, orientation, endpointStyle, connectorPaintStyle) {
- var width = endpointStyle.width || self.width,
- height = endpointStyle.height || self.height,
- x = anchorPoint[0] - (width/2),
- y = anchorPoint[1] - (height/2);
- return [ x, y, width, height ];
- };
- };
- // ********************************* END OF ENDPOINT TYPES *******************************************************************
-
- // ********************************* OVERLAY DEFINITIONS ***********************************************************************
- var AbstractOverlay = function(params) {
- var visible = true, self = this;
- this.isAppendedAtTopLevel = true;
- this.component = params.component;
- this.loc = params.location == null ? 0.5 : params.location;
- this.endpointLoc = params.endpointLocation == null ? [ 0.5, 0.5] : params.endpointLocation;
- this.setVisible = function(val) {
- visible = val;
- self.component.repaint();
- };
- this.isVisible = function() { return visible; };
- this.hide = function() { self.setVisible(false); };
- this.show = function() { self.setVisible(true); };
-
- this.incrementLocation = function(amount) {
- self.loc += amount;
- self.component.repaint();
- };
- this.setLocation = function(l) {
- self.loc = l;
- self.component.repaint();
- };
- this.getLocation = function() {
- return self.loc;
- };
- };
-
-
- /**
- * Class: Overlays.Arrow
- *
- * An arrow overlay, defined by four points: the head, the two sides of the tail, and a 'foldback' point at some distance along the length
- * of the arrow that lines from each tail point converge into. The foldback point is defined using a decimal that indicates some fraction
- * of the length of the arrow and has a default value of 0.623. A foldback point value of 1 would mean that the arrow had a straight line
- * across the tail.
- */
- /**
- * Function: Constructor
- *
- * Parameters:
- *
- * length - distance in pixels from head to tail baseline. default 20.
- * width - width in pixels of the tail baseline. default 20.
- * fillStyle - style to use when filling the arrow. defaults to "black".
- * strokeStyle - style to use when stroking the arrow. defaults to null, which means the arrow is not stroked.
- * lineWidth - line width to use when stroking the arrow. defaults to 1, but only used if strokeStyle is not null.
- * 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.
- * location - distance (as a decimal from 0 to 1 inclusive) marking where the arrow should sit on the connector. defaults to 0.5.
- * direction - indicates the direction the arrow points in. valid values are -1 and 1; 1 is default.
- */
- jsPlumb.Overlays.Arrow = function(params) {
- this.type = "Arrow";
- AbstractOverlay.apply(this, arguments);
- this.isAppendedAtTopLevel = false;
- params = params || {};
- var self = this;
-
- this.length = params.length || 20;
- this.width = params.width || 20;
- this.id = params.id;
- var direction = (params.direction || 1) < 0 ? -1 : 1,
- paintStyle = params.paintStyle || { lineWidth:1 },
- // how far along the arrow the lines folding back in come to. default is 62.3%.
- foldback = params.foldback || 0.623;
-
- this.computeMaxSize = function() { return self.width * 1.5; };
-
- this.cleanup = function() { }; // nothing to clean up for Arrows
-
- this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) {
- var hxy, mid, txy, tail, cxy;
- if (connector.pointAlongPathFrom) {
- if (self.loc == 1) {
- hxy = connector.pointOnPath(self.loc);
- mid = connector.pointAlongPathFrom(self.loc, -1);
- txy = jsPlumb.util.pointOnLine(hxy, mid, self.length);
- }
- else if (self.loc == 0) {
- txy = connector.pointOnPath(self.loc);
- mid = connector.pointAlongPathFrom(self.loc, 1);
- hxy = jsPlumb.util.pointOnLine(txy, mid, self.length);
- }
- else {
- hxy = connector.pointAlongPathFrom(self.loc, direction * self.length / 2),
- mid = connector.pointOnPath(self.loc),
- txy = jsPlumb.util.pointOnLine(hxy, mid, self.length);
- }
- tail = jsPlumb.util.perpendicularLineTo(hxy, txy, self.width);
- cxy = jsPlumb.util.pointOnLine(hxy, txy, foldback * self.length);
- var minx = Math.min(hxy.x, tail[0].x, tail[1].x),
- maxx = Math.max(hxy.x, tail[0].x, tail[1].x),
- miny = Math.min(hxy.y, tail[0].y, tail[1].y),
- maxy = Math.max(hxy.y, tail[0].y, tail[1].y);
-
- var d = { hxy:hxy, tail:tail, cxy:cxy },
- strokeStyle = paintStyle.strokeStyle || currentConnectionPaintStyle.strokeStyle,
- fillStyle = paintStyle.fillStyle || currentConnectionPaintStyle.strokeStyle,
- lineWidth = paintStyle.lineWidth || currentConnectionPaintStyle.lineWidth;
-
- self.paint(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions);
-
- return [ minx, maxx, miny, maxy];
- }
- else return [0,0,0,0];
- };
- };
-
- /**
- * Class: Overlays.PlainArrow
- *
- * 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
- * point along the length of the arrow: in this case, that foldback point is the full length of the arrow. so it just does
- * a 'call' to Arrow with foldback set appropriately.
- */
- /**
- * Function: Constructor
- * See <Overlays.Arrow> for allowed parameters for this overlay.
- */
- jsPlumb.Overlays.PlainArrow = function(params) {
- params = params || {};
- var p = jsPlumb.extend(params, {foldback:1});
- jsPlumb.Overlays.Arrow.call(this, p);
- this.type = "PlainArrow";
- };
-
- /**
- * Class: Overlays.Diamond
- *
- * A diamond. Like PlainArrow, this is a concrete case of the more generic case of the tail points converging on some point...it just
- * happens that in this case, that point is greater than the length of the the arrow.
- *
- * this could probably do with some help with positioning...due to the way it reuses the Arrow paint code, what Arrow thinks is the
- * 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
- * stuck when it comes to helping out the Arrow class. possibly we could pass in a 'transpose' parameter or something. the value
- * would be -l/4 in this case - move along one quarter of the total length.
- */
- /**
- * Function: Constructor
- * See <Overlays.Arrow> for allowed parameters for this overlay.
- */
- jsPlumb.Overlays.Diamond = function(params) {
- params = params || {};
- var l = params.length || 40,
- p = jsPlumb.extend(params, {length:l/2, foldback:2});
- jsPlumb.Overlays.Arrow.call(this, p);
- this.type = "Diamond";
- };
-
-
-
- /**
- * Class: Overlays.Label
- * A Label overlay. For all different renderer types (SVG/Canvas/VML), jsPlumb draws a Label overlay as a styled DIV. Version 1.3.0 of jsPlumb
- * introduced the ability to set css classes on the label; this is now the preferred way for you to style a label. The 'labelStyle' parameter
- * is still supported in 1.3.0 but its usage is deprecated. Under the hood, jsPlumb just turns that object into a bunch of CSS directive that it
- * puts on the Label's 'style' attribute, so the end result is the same.
- */
- /**
- * Function: Constructor
- *
- * Parameters:
- * cssClass - optional css class string to append to css class. This string is appended "as-is", so you can of course have multiple classes
- * defined. This parameter is preferred to using labelStyle, borderWidth and borderStyle.
- * 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
- * label function returns null. empty strings _will_ be painted.
- * location - distance (as a decimal from 0 to 1 inclusive) marking where the label should sit on the connector. defaults to 0.5.
- *
- */
- jsPlumb.Overlays.Label = function(params) {
- this.type = "Label";
- jsPlumb.DOMElementComponent.apply(this, arguments);
- AbstractOverlay.apply(this, arguments);
- this.labelStyle = params.labelStyle || jsPlumb.Defaults.LabelStyle;
- this.id = params.id;
- this.cachedDimensions = null; // setting on 'this' rather than using closures uses a lot less memory. just don't monkey with it!
- var label = params.label || "",
- self = this,
- initialised = false,
- div = document.createElement("div"),
- labelText = null;
- div.style["position"] = "absolute";
-
- var clazz = params["_jsPlumb"].overlayClass + " " +
- (self.labelStyle.cssClass ? self.labelStyle.cssClass :
- params.cssClass ? params.cssClass : "");
-
- div.className = clazz;
-
- jsPlumb.appendElement(div, params.component.parent);
- jsPlumb.getId(div);
- self.attachListeners(div, self);
- self.canvas = div;
-
- //override setVisible
- var osv = self.setVisible;
- self.setVisible = function(state) {
- osv(state); // call superclass
- div.style.display = state ? "block" : "none";
- };
-
- this.getElement = function() {
- return div;
- };
-
- this.cleanup = function() {
- if (div != null) jsPlumb.CurrentLibrary.removeElement(div);
- };
-
- /*
- * Function: setLabel
- * sets the label's, um, label. you would think i'd call this function
- * 'setText', but you can pass either a Function or a String to this, so
- * it makes more sense as 'setLabel'.
- */
- this.setLabel = function(l) {
- label = l;
- labelText = null;
- self.component.repaint();
- };
-
- this.getLabel = function() {
- return label;
- };
-
- this.paint = function(component, d, componentDimensions) {
- if (!initialised) {
- component.appendDisplayElement(div);
- self.attachListeners(div, component);
- initialised = true;
- }
- div.style.left = (componentDimensions[0] + d.minx) + "px";
- div.style.top = (componentDimensions[1] + d.miny) + "px";
- };
-
- this.getTextDimensions = function() {
- if (typeof label == "function") {
- var lt = label(self);
- div.innerHTML = lt.replace(/\r\n/g, "<br/>");
- }
- else {
- if (labelText == null) {
- labelText = label;
- div.innerHTML = labelText.replace(/\r\n/g, "<br/>");
- }
- }
- var de = jsPlumb.CurrentLibrary.getElementObject(div),
- s = jsPlumb.CurrentLibrary.getSize(de);
- return {width:s[0], height:s[1]};
- };
-
- this.computeMaxSize = function(connector) {
- var td = self.getTextDimensions(connector);
- return td.width ? Math.max(td.width, td.height) * 1.5 : 0;
- };
-
- this.draw = function(component, currentConnectionPaintStyle, componentDimensions) {
- var td = self.getTextDimensions(component);
- if (td.width != null) {
- var cxy = {x:0,y:0};
- if (component.pointOnPath)
- cxy = component.pointOnPath(self.loc); // a connection
- else {
- var locToUse = self.loc.constructor == Array ? self.loc : self.endpointLoc;
- cxy = { x:locToUse[0] * componentDimensions[2],
- y:locToUse[1] * componentDimensions[3] };
- }
-
- minx = cxy.x - (td.width / 2),
- miny = cxy.y - (td.height / 2);
-
- self.paint(component, {
- minx:minx,
- miny:miny,
- td:td,
- cxy:cxy
- }, componentDimensions);
-
- return [minx, minx+td.width, miny, miny+td.height];
- }
- else return [0,0,0,0];
- };
-
- this.reattachListeners = function(connector) {
- if (div) {
- self.reattachListenersForElement(div, self, connector);
- }
- };
- };
- // this is really just a test overlay, so its undocumented and doesnt take any parameters. but i was loth to delete it.
- jsPlumb.Overlays.GuideLines = function() {
- var self = this;
- self.length = 50;
- self.lineWidth = 5;
- this.type = "GuideLines";
- AbstractOverlay.apply(this, arguments);
- jsPlumb.jsPlumbUIComponent.apply(this, arguments);
- this.draw = function(connector, currentConnectionPaintStyle, connectorDimensions) {
- var head = connector.pointAlongPathFrom(self.loc, self.length / 2),
- mid = connector.pointOnPath(self.loc),
- tail = jsPlumb.util.pointOnLine(head, mid, self.length),
- tailLine = jsPlumb.util.perpendicularLineTo(head, tail, 40),
- headLine = jsPlumb.util.perpendicularLineTo(tail, head, 20);
- self.paint(connector, [head, tail, tailLine, headLine], self.lineWidth, "red", null, connectorDimensions);
- return [Math.min(head.x, tail.x), Math.min(head.y, tail.y), Math.max(head.x, tail.x), Math.max(head.y,tail.y)];
- };
- this.computeMaxSize = function() { return 50; };
- this.cleanup = function() { }; // nothing to clean up for GuideLines
- };
- // ********************************* END OF OVERLAY DEFINITIONS ***********************************************************************
-
- // ********************************* OVERLAY CANVAS RENDERERS***********************************************************************
-
- // ********************************* END OF OVERLAY CANVAS RENDERERS ***********************************************************************
- })();/*
- * jsPlumb
- *
- * Title:jsPlumb 1.3.7
- *
- * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
- * elements, or VML.
- *
- * This file contains the state machine connectors.
- *
- * Thanks to Brainstorm Mobile Solutions for supporting the development of these.
- *
- * Copyright (c) 2010 - 2012 Simon Porritt (simon.porritt@gmail.com)
- *
- * http://jsplumb.org
- * http://github.com/sporritt/jsplumb
- * http://code.google.com/p/jsplumb
- *
- * Dual licensed under the MIT and GPL2 licenses.
- */
- ;(function() {
- var Line = function(x1, y1, x2, y2) {
- this.m = (y2 - y1) / (x2 - x1);
- this.b = -1 * ((this.m * x1) - y1);
-
- this.rectIntersect = function(x,y,w,h) {
- var results = [];
-
- // try top face
- // the equation of the top face is y = (0 * x) + b; y = b.
- var xInt = (y - this.b) / this.m;
- // test that the X value is in the line's range.
- if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]);
-
- // try right face
- var yInt = (this.m * (x + w)) + this.b;
- if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]);
-
- // bottom face
- var xInt = ((y + h) - this.b) / this.m;
- // test that the X value is in the line's range.
- if (xInt >= x && xInt <= (x + w)) results.push([ xInt, (this.m * xInt) + this.b ]);
-
- // try left face
- var yInt = (this.m * x) + this.b;
- if (yInt >= y && yInt <= (y + h)) results.push([ (yInt - this.b) / this.m, yInt ]);
- if (results.length == 2) {
- var midx = (results[0][0] + results[1][0]) / 2, midy = (results[0][1] + results[1][1]) / 2;
- results.push([ midx,midy ]);
- // now calculate the segment inside the rectangle where the midpoint lies.
- var xseg = midx <= x + (w / 2) ? -1 : 1,
- yseg = midy <= y + (h / 2) ? -1 : 1;
- results.push([xseg, yseg]);
- return results;
- }
-
- return null;
- };
- },
- _segment = function(x1, y1, x2, y2) {
- if (x1 <= x2 && y2 <= y1) return 1;
- else if (x1 <= x2 && y1 <= y2) return 2;
- else if (x2 <= x1 && y2 >= y1) return 3;
- return 4;
- },
-
- // the control point we will use depends on the faces to which each end of the connection is assigned, specifically whether or not the
- // two faces are parallel or perpendicular. if they are parallel then the control point lies on the midpoint of the axis in which they
- // are parellel and varies only in the other axis; this variation is proportional to the distance that the anchor points lie from the
- // center of that face. if the two faces are perpendicular then the control point is at some distance from both the midpoints; the amount and
- // direction are dependent on the orientation of the two elements. 'seg', passed in to this method, tells you which segment the target element
- // lies in with respect to the source: 1 is top right, 2 is bottom right, 3 is bottom left, 4 is top left.
- //
- // sourcePos and targetPos are arrays of info about where on the source and target each anchor is located. their contents are:
- //
- // 0 - absolute x
- // 1 - absolute y
- // 2 - proportional x in element (0 is left edge, 1 is right edge)
- // 3 - proportional y in element (0 is top edge, 1 is bottom edge)
- //
- _findControlPoint = function(midx, midy, segment, sourceEdge, targetEdge, dx, dy, distance, proximityLimit) {
- // TODO (maybe)
- // - if anchor pos is 0.5, make the control point take into account the relative position of the elements.
- if (distance <= proximityLimit) return [midx, midy];
- if (segment == 1) {
- if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
- else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
- else return [ midx + (-1 * dx) , midy + (-1 * dy) ];
- }
- else if (segment == 2) {
- if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
- else if (sourceEdge[2] >= 1 && targetEdge[2] <= 0) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
- else return [ midx + (1 * dx) , midy + (-1 * dy) ];
- }
- else if (segment == 3) {
- if (sourceEdge[3] >= 1 && targetEdge[3] <= 0) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
- else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
- else return [ midx + (-1 * dx) , midy + (-1 * dy) ];
- }
- else if (segment == 4) {
- if (sourceEdge[3] <= 0 && targetEdge[3] >= 1) return [ midx + (sourceEdge[2] < 0.5 ? -1 * dx : dx), midy ];
- else if (sourceEdge[2] <= 0 && targetEdge[2] >= 1) return [ midx, midy + (sourceEdge[3] < 0.5 ? -1 * dy : dy) ];
- else return [ midx + (1 * dx) , midy + (-1 * dy) ];
- }
- };
- /*
- Function: StateMachine constructor
-
- Allowed parameters:
- curviness - measure of how "curvy" the connectors will be. this is translated as the distance that the
- Bezier curve's control point is from the midpoint of the straight line connecting the two
- endpoints, and does not mean that the connector is this wide. The Bezier curve never reaches
- its control points; they act as gravitational masses. defaults to 10.
- margin - distance from element to start and end connectors, in pixels. defaults to 5.
- proximityLimit - sets the distance beneath which the elements are consider too close together to bother with fancy
- curves. by default this is 80 pixels.
- loopbackRadius - the radius of a loopback connector. optional; defaults to 25.
- */
- jsPlumb.Connectors.StateMachine = function(params) {
- var self = this,
- currentPoints = null,
- _sx, _sy, _tx, _ty, _controlPoint = [],
- curviness = params.curviness || 10,
- margin = params.margin || 5,
- proximityLimit = params.proximityLimit || 80,
- clockwise = params.orientation && params.orientation == "clockwise",
- loopbackRadius = params.loopbackRadius || 25,
- isLoopback = false;
- this.type = "StateMachine";
- params = params || {};
-
- this.compute = function(sourcePos, targetPos, sourceEndpoint, targetEndpoint, sourceAnchor, targetAnchor, lineWidth, minWidth) {
- var w = Math.abs(sourcePos[0] - targetPos[0]),
- h = Math.abs(sourcePos[1] - targetPos[1]),
- // these are padding to ensure the whole connector line appears
- xo = 0.45 * w, yo = 0.45 * h;
- // these are padding to ensure the whole connector line appears
- w *= 1.9; h *= 1.9;
- //ensure at least one pixel width
- lineWidth = lineWidth || 1;
- var x = Math.min(sourcePos[0], targetPos[0]) - xo,
- y = Math.min(sourcePos[1], targetPos[1]) - yo;
-
- if (sourceEndpoint.elementId != targetEndpoint.elementId) {
-
- isLoopback = false;
-
- _sx = sourcePos[0] < targetPos[0] ? xo : w-xo;
- _sy = sourcePos[1] < targetPos[1] ? yo:h-yo;
- _tx = sourcePos[0] < targetPos[0] ? w-xo : xo;
- _ty = sourcePos[1] < targetPos[1] ? h-yo : yo;
-
- // now adjust for the margin
- if (sourcePos[2] == 0) _sx -= margin;
- if (sourcePos[2] == 1) _sx += margin;
- if (sourcePos[3] == 0) _sy -= margin;
- if (sourcePos[3] == 1) _sy += margin;
- if (targetPos[2] == 0) _tx -= margin;
- if (targetPos[2] == 1) _tx += margin;
- if (targetPos[3] == 0) _ty -= margin;
- if (targetPos[3] == 1) _ty += margin;
- //
- // these connectors are quadratic bezier curves, having a single control point. if both anchors
- // are located at 0.5 on their respective faces, the control point is set to the midpoint and you
- // get a straight line. this is also the case if the two anchors are within 'proximityLimit', since
- // it seems to make good aesthetic sense to do that. outside of that, the control point is positioned
- // at 'curviness' pixels away along the normal to the straight line connecting the two anchors.
- //
- // there may be two improvements to this. firstly, we might actually support the notion of avoiding nodes
- // in the UI, or at least making a good effort at doing so. if a connection would pass underneath some node,
- // for example, we might increase the distance the control point is away from the midpoint in a bid to
- // steer it around that node. this will work within limits, but i think those limits would also be the likely
- // limits for, once again, aesthetic good sense in the layout of a chart using these connectors.
- //
- // the second possible change is actually two possible changes: firstly, it is possible we should gradually
- // decrease the 'curviness' as the distance between the anchors decreases; start tailing it off to 0 at some
- // point (which should be configurable). secondly, we might slightly increase the 'curviness' for connectors
- // with respect to how far their anchor is from the center of its respective face. this could either look cool,
- // or stupid, and may indeed work only in a way that is so subtle as to have been a waste of time.
- //
- var _midx = (_sx + _tx) / 2, _midy = (_sy + _ty) / 2,
- m2 = (-1 * _midx) / _midy, theta2 = Math.atan(m2),
- dy = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.sin(theta2)),
- dx = (m2 == Infinity || m2 == -Infinity) ? 0 : Math.abs(curviness / 2 * Math.cos(theta2)),
- segment = _segment(_sx, _sy, _tx, _ty),
- distance = Math.sqrt(Math.pow(_tx - _sx, 2) + Math.pow(_ty - _sy, 2));
-
- // calculate the control point. this code will be where we'll put in a rudimentary element avoidance scheme; it
- // will work by extending the control point to force the curve to be, um, curvier.
- _controlPoint = _findControlPoint(_midx,
- _midy,
- segment,
- sourcePos,
- targetPos,
- curviness, curviness,
- distance,
- proximityLimit);
-
- var requiredWidth = Math.max(Math.abs(_controlPoint[0] - _sx) * 3, Math.abs(_controlPoint[0] - _tx) * 3, Math.abs(_tx-_sx), 2 * lineWidth, minWidth),
- requiredHeight = Math.max(Math.abs(_controlPoint[1] - _sy) * 3, Math.abs(_controlPoint[1] - _ty) * 3, Math.abs(_ty-_sy), 2 * lineWidth, minWidth);
- if (w < requiredWidth) {
- var dw = requiredWidth - w;
- x -= (dw / 2);
- _sx += (dw / 2);
- _tx += (dw / 2);
- w = requiredWidth;
- _controlPoint[0] += (dw / 2);
- }
-
- if (h < requiredHeight) {
- var dh = requiredHeight - h;
- y -= (dh / 2);
- _sy += (dh / 2);
- _ty += (dh / 2);
- h = requiredHeight;
- _controlPoint[1] += (dh / 2);
- }
- currentPoints = [ x, y, w, h, _sx, _sy, _tx, _ty, _controlPoint[0], _controlPoint[1] ];
- }
- else {
- isLoopback = true;
- // a loopback connector. draw an arc from one anchor to the other.
- // i guess we'll do this the same way as the others. just the control point will be a fair distance away.
- var x1 = sourcePos[0], x2 = sourcePos[0], y1 = sourcePos[1] - margin, y2 = sourcePos[1] - margin,
- cx = x1, cy = y1 - loopbackRadius;
-
- // canvas sizing stuff, to ensure the whole painted area is visible.
- w = ((2 * lineWidth) + (4 * loopbackRadius)), h = ((2 * lineWidth) + (4 * loopbackRadius));
- x = cx - loopbackRadius - lineWidth - loopbackRadius, y = cy - loopbackRadius - lineWidth - loopbackRadius;
- currentPoints = [ x, y, w, h, cx-x, cy-y, loopbackRadius, clockwise, x1-x, y1-y, x2-x, y2-y];
- }
-
- return currentPoints;
- };
-
- var _makeCurve = function() {
- return [
- { x:_tx, y:_ty },
- { x:_controlPoint[0], y:_controlPoint[1] },
- { x:_controlPoint[0] + 1, y:_controlPoint[1] + 1},
- { x:_sx, y:_sy }
- ];
- };
-
- /**
- * returns the point on the connector's path that is 'location' along the length of the path, where 'location' is a decimal from
- * 0 to 1 inclusive. for the straight line connector this is simple maths. for Bezier, not so much.
- */
- this.pointOnPath = function(location) {
- if (isLoopback) {
- if (location > 0 && location < 1) location = 1- location;
-
- // current points are [ x, y, width, height, center x, center y, radius, clockwise, startx, starty, endx, endy ]
- // so the path length is the circumference of the circle
- //var len = 2 * Math.PI * currentPoints[6],
- // map 'location' to an angle. 0 is PI/2 when the connector is on the top face; if we
- // support other faces it will have to be calculated for each one. 1 is also PI/2.
- // 0.5 is -PI/2.
- var startAngle = (location * 2 * Math.PI) + (Math.PI / 2),
- startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)),
- startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle));
- return {x:startX, y:startY};
-
- }
- else return jsBezier.pointOnCurve(_makeCurve(), location);
- };
-
- /**
- * returns the gradient of the connector at the given point.
- */
- this.gradientAtPoint = function(location) {
- if (isLoopback)
- return Math.atan(location * 2 * Math.PI);
- else
- return jsBezier.gradientAtPoint(_makeCurve(), location);
- };
-
- /**
- * for Bezier curves this method is a little tricky, cos calculating path distance algebraically is notoriously difficult.
- * this method is iterative, jumping forward .05% of the path at a time and summing the distance between this point and the previous
- * one, until the sum reaches 'distance'. the method may turn out to be computationally expensive; we'll see.
- * another drawback of this method is that if the connector gets quite long, .05% of the length of it is not necessarily smaller
- * than the desired distance, in which case the loop returns immediately and the arrow is mis-shapen. so a better strategy might be to
- * calculate the step as a function of distance/distance between endpoints.
- */
- this.pointAlongPathFrom = function(location, distance) {
- if (isLoopback) {
- if (location > 0 && location < 1) location = 1- location;
- var circumference = 2 * Math.PI * currentPoints[6],
- arcSpan = distance / circumference * 2 * Math.PI,
- startAngle = (location * 2 * Math.PI) - arcSpan + (Math.PI / 2),
-
- startX = currentPoints[4] + (currentPoints[6] * Math.cos(startAngle)),
- startY = currentPoints[5] + (currentPoints[6] * Math.sin(startAngle));
- return {x:startX, y:startY};
- }
- return jsBezier.pointAlongCurveFrom(_makeCurve(), location, distance);
- };
-
- };
-
- /*
- * Canvas state machine renderer.
- */
- jsPlumb.Connectors.canvas.StateMachine = function(params) {
- params = params || {};
- var self = this, drawGuideline = params.drawGuideline || true, avoidSelector = params.avoidSelector;
- jsPlumb.Connectors.StateMachine.apply(this, arguments);
- jsPlumb.CanvasConnector.apply(this, arguments);
-
-
- this._paint = function(dimensions) {
-
- if (dimensions.length == 10) {
- self.ctx.beginPath();
- self.ctx.moveTo(dimensions[4], dimensions[5]);
- self.ctx.quadraticCurveTo(dimensions[8], dimensions[9], dimensions[6], dimensions[7]);
- self.ctx.stroke();
-
- /*/ draw the guideline
- if (drawGuideline) {
- self.ctx.save();
- self.ctx.beginPath();
- self.ctx.strokeStyle = "silver";
- self.ctx.lineWidth = 1;
- self.ctx.moveTo(dimensions[4], dimensions[5]);
- self.ctx.lineTo(dimensions[6], dimensions[7]);
- self.ctx.stroke();
- self.ctx.restore();
- }
- //*/
- }
- else {
- // a loopback connector
- self.ctx.save();
- self.ctx.beginPath();
- var startAngle = 0, // Starting point on circle
- endAngle = 2 * Math.PI, // End point on circle
- clockwise = dimensions[7]; // clockwise or anticlockwise
- self.ctx.arc(dimensions[4],dimensions[5],dimensions[6],0, endAngle, clockwise);
- self.ctx.stroke();
- self.ctx.closePath();
- self.ctx.restore();
- }
- };
-
- this.createGradient = function(dim, ctx) {
- return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]);
- };
- };
-
- /*
- * SVG State Machine renderer
- */
- jsPlumb.Connectors.svg.StateMachine = function() {
- var self = this;
- jsPlumb.Connectors.StateMachine.apply(this, arguments);
- jsPlumb.SvgConnector.apply(this, arguments);
- this.getPath = function(d) {
-
- if (d.length == 10)
- return "M " + d[4] + " " + d[5] + " C " + d[8] + " " + d[9] + " " + d[8] + " " + d[9] + " " + d[6] + " " + d[7];
- else {
- // loopback
- return "M" + (d[8] + 4) + " " + d[9] + " A " + d[6] + " " + d[6] + " 0 1,0 " + (d[8]-4) + " " + d[9];
- }
- };
- };
-
- /*
- * VML state machine renderer
- */
- jsPlumb.Connectors.vml.StateMachine = function() {
- jsPlumb.Connectors.StateMachine.apply(this, arguments);
- jsPlumb.VmlConnector.apply(this, arguments);
- var _conv = jsPlumb.vml.convertValue;
- this.getPath = function(d) {
- if (d.length == 10) {
- return "m" + _conv(d[4]) + "," + _conv(d[5]) +
- " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e";
- }
- else {
- // loopback
- var left = _conv(d[8] - d[6]),
- top = _conv(d[9] - (2 * d[6])),
- right = left + _conv(2 * d[6]),
- bottom = top + _conv(2 * d[6]),
- posString = left + "," + top + "," + right + "," + bottom;
-
- var o = "ar " + posString + "," + _conv(d[8]) + ","
- + _conv(d[9]) + "," + _conv(d[8]) + "," + _conv(d[9]) + " e";
-
- return o;
- }
- };
- };
- })();
- /*
- // now for a rudimentary avoidance scheme. TODO: how to set this in a cross-library way?
- // if (avoidSelector) {
- // var testLine = new Line(sourcePos[0] + _sx,sourcePos[1] + _sy,sourcePos[0] + _tx,sourcePos[1] + _ty);
- // var sel = jsPlumb.getSelector(avoidSelector);
- // for (var i = 0; i < sel.length; i++) {
- // var id = jsPlumb.getId(sel[i]);
- // if (id != sourceEndpoint.elementId && id != targetEndpoint.elementId) {
- // o = jsPlumb.getOffset(id), s = jsPlumb.getSize(id);
- //
- // if (o && s) {
- // var collision = testLine.rectIntersect(o.left,o.top,s[0],s[1]);
- // if (collision) {
- // set the control point to be a certain distance from the midpoint of the two points that
- // the line crosses on the rectangle.
- // TODO where will this 75 number come from?
- // _controlX = collision[2][0] + (75 * collision[3][0]);
- // / _controlY = collision[2][1] + (75 * collision[3][1]);
- // }
- // }
- // }
- // }
- //}
- *//*
- * jsPlumb
- *
- * Title:jsPlumb 1.3.7
- *
- * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
- * elements, or VML.
- *
- * This file contains the VML renderers.
- *
- * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org)
- *
- * http://jsplumb.org
- * http://github.com/sporritt/jsplumb
- * http://code.google.com/p/jsplumb
- *
- * Dual licensed under the MIT and GPL2 licenses.
- */
- ;(function() {
-
- // http://ajaxian.com/archives/the-vml-changes-in-ie-8
- // http://www.nczonline.net/blog/2010/01/19/internet-explorer-8-document-and-browser-modes/
- // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/
-
- var vmlAttributeMap = {
- "stroke-linejoin":"joinstyle",
- "joinstyle":"joinstyle",
- "endcap":"endcap",
- "miterlimit":"miterlimit"
- };
-
- if (document.createStyleSheet) {
-
- // this is the style rule for IE7/6: it uses a CSS class, tidy.
- document.createStyleSheet().addRule(".jsplumb_vml", "behavior:url(#default#VML);position:absolute;");
-
- // these are for VML in IE8. you have to explicitly call out which elements
- // you're going to expect to support VML!
- //
- document.createStyleSheet().addRule("jsplumb\\:textbox", "behavior:url(#default#VML);position:absolute;");
- document.createStyleSheet().addRule("jsplumb\\:oval", "behavior:url(#default#VML);position:absolute;");
- document.createStyleSheet().addRule("jsplumb\\:rect", "behavior:url(#default#VML);position:absolute;");
- document.createStyleSheet().addRule("jsplumb\\:stroke", "behavior:url(#default#VML);position:absolute;");
- document.createStyleSheet().addRule("jsplumb\\:shape", "behavior:url(#default#VML);position:absolute;");
- document.createStyleSheet().addRule("jsplumb\\:group", "behavior:url(#default#VML);position:absolute;");
-
- // in this page it is also mentioned that IE requires the extra arg to the namespace
- // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/
- // but someone commented saying they didn't need it, and it seems jsPlumb doesnt need it either.
- // var iev = document.documentMode;
- //if (!iev || iev < 8)
- document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml");
- //else
- // document.namespaces.add("jsplumb", "urn:schemas-microsoft-com:vml", "#default#VML");
- }
-
- jsPlumb.vml = {};
-
- var scale = 1000,
- _groupMap = {},
- _getGroup = function(container, connectorClass) {
- var id = jsPlumb.getId(container),
- g = _groupMap[id];
- if(!g) {
- g = _node("group", [0,0,scale, scale], {"class":connectorClass});
- //g.style.position=absolute;
- //g["coordsize"] = "1000,1000";
- g.style.backgroundColor="red";
- _groupMap[id] = g;
- jsPlumb.appendElement(g, container); // todo if this gets reinstated, remember to use the current jsplumb instance.
- //document.body.appendChild(g);
- }
- return g;
- },
- _atts = function(o, atts) {
- for (var i in atts) {
- // IE8 fix: setattribute does not work after an element has been added to the dom!
- // http://www.louisremi.com/2009/03/30/changes-in-vml-for-ie8-or-what-feature-can-the-ie-dev-team-break-for-you-today/
- //o.setAttribute(i, atts[i]);
-
- o[i] = atts[i];
- }
- },
- _node = function(name, d, atts) {
- atts = atts || {};
- var o = document.createElement("jsplumb:" + name);
- o.className = (atts["class"] ? atts["class"] + " " : "") + "jsplumb_vml";
- _pos(o, d);
- _atts(o, atts);
- return o;
- },
- _pos = function(o,d) {
- o.style.left = d[0] + "px";
- o.style.top = d[1] + "px";
- o.style.width= d[2] + "px";
- o.style.height= d[3] + "px";
- o.style.position = "absolute";
- },
- _conv = jsPlumb.vml.convertValue = function(v) {
- return Math.floor(v * scale);
- },
- // tests if the given style is "transparent" and then sets the appropriate opacity node to 0 if so,
- // or 1 if not. TODO in the future, support variable opacity.
- _maybeSetOpacity = function(styleToWrite, styleToCheck, type, component) {
- if ("transparent" === styleToCheck)
- component.setOpacity(type, "0.0");
- else
- component.setOpacity(type, "1.0");
- },
- _applyStyles = function(node, style, component) {
- var styleToWrite = {};
- if (style.strokeStyle) {
- styleToWrite["stroked"] = "true";
- var strokeColor = jsPlumb.util.convertStyle(style.strokeStyle, true);
- styleToWrite["strokecolor"] = strokeColor;
- _maybeSetOpacity(styleToWrite, strokeColor, "stroke", component);
- styleToWrite["strokeweight"] = style.lineWidth + "px";
- }
- else styleToWrite["stroked"] = "false";
-
- if (style.fillStyle) {
- styleToWrite["filled"] = "true";
- var fillColor = jsPlumb.util.convertStyle(style.fillStyle, true);
- styleToWrite["fillcolor"] = fillColor;
- _maybeSetOpacity(styleToWrite, fillColor, "fill", component);
- }
- else styleToWrite["filled"] = "false";
-
- if(style["dashstyle"]) {
- if (component.strokeNode == null) {
- component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:style["dashstyle"] });
- node.appendChild(component.strokeNode);
- }
- else
- component.strokeNode.dashstyle = style["dashstyle"];
- }
- else if (style["stroke-dasharray"] && style["lineWidth"]) {
- var sep = style["stroke-dasharray"].indexOf(",") == -1 ? " " : ",",
- parts = style["stroke-dasharray"].split(sep),
- styleToUse = "";
- for(var i = 0; i < parts.length; i++) {
- styleToUse += (Math.floor(parts[i] / style.lineWidth) + sep);
- }
- if (component.strokeNode == null) {
- component.strokeNode = _node("stroke", [0,0,0,0], { dashstyle:styleToUse });
- node.appendChild(component.strokeNode);
- }
- else
- component.strokeNode.dashstyle = styleToUse;
- }
-
- _atts(node, styleToWrite);
- },
- /*
- * Base class for Vml endpoints and connectors. Extends jsPlumbUIComponent.
- */
- VmlComponent = function() {
- var self = this;
- jsPlumb.jsPlumbUIComponent.apply(this, arguments);
- this.opacityNodes = {
- "stroke":null,
- "fill":null
- };
- this.initOpacityNodes = function(vml) {
- self.opacityNodes["stroke"] = _node("stroke", [0,0,1,1], {opacity:"0.0"});
- self.opacityNodes["fill"] = _node("fill", [0,0,1,1], {opacity:"0.0"});
- vml.appendChild(self.opacityNodes["stroke"]);
- vml.appendChild(self.opacityNodes["fill"]);
- };
- this.setOpacity = function(type, value) {
- var node = self.opacityNodes[type];
- if (node) node["opacity"] = "" + value;
- };
- var displayElements = [ ];
- this.getDisplayElements = function() {
- return displayElements;
- };
-
- this.appendDisplayElement = function(el, doNotAppendToCanvas) {
- if (!doNotAppendToCanvas) self.canvas.parentNode.appendChild(el);
- displayElements.push(el);
- };
- },
- /*
- * Base class for Vml connectors. extends VmlComponent.
- */
- VmlConnector = jsPlumb.VmlConnector = function(params) {
- var self = this;
- self.strokeNode = null;
- self.canvas = null;
- VmlComponent.apply(this, arguments);
- var clazz = self._jsPlumb.connectorClass + (params.cssClass ? (" " + params.cssClass) : "");
- this.paint = function(d, style, anchor) {
- if (style != null) {
- var path = self.getPath(d), p = { "path":path };
- //*
- if (style.outlineColor) {
- var outlineWidth = style.outlineWidth || 1,
- outlineStrokeWidth = style.lineWidth + (2 * outlineWidth),
- outlineStyle = {
- strokeStyle : jsPlumb.util.convertStyle(style.outlineColor),
- lineWidth : outlineStrokeWidth
- };
- for (var aa in vmlAttributeMap) outlineStyle[aa] = style[aa];
-
- if (self.bgCanvas == null) {
- p["class"] = clazz;
- p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale);
- self.bgCanvas = _node("shape", d, p);
- params["_jsPlumb"].appendElement(self.bgCanvas, params.parent);
- _pos(self.bgCanvas, d);
- self.appendDisplayElement(self.bgCanvas, true);
- }
- else {
- p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale);
- _pos(self.bgCanvas, d);
- _atts(self.bgCanvas, p);
- }
-
- _applyStyles(self.bgCanvas, outlineStyle, self);
- }
- //*/
-
- if (self.canvas == null) {
- p["class"] = clazz;
- p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale);
- if (self.tooltip) p["label"] = self.tooltip;
- self.canvas = _node("shape", d, p);
-
- //var group = _getGroup(params.parent); // test of append everything to a group
- //group.appendChild(self.canvas); // sort of works but not exactly;
- params["_jsPlumb"].appendElement(self.canvas, params.parent); //before introduction of groups
- self.appendDisplayElement(self.canvas, true);
-
- self.attachListeners(self.canvas, self);
-
- self.initOpacityNodes(self.canvas, ["stroke"]);
- }
- else {
- p["coordsize"] = (d[2] * scale) + "," + (d[3] * scale);
- _pos(self.canvas, d);
- _atts(self.canvas, p);
- }
-
- _applyStyles(self.canvas, style, self);
- }
- };
-
- //self.appendDisplayElement(self.canvas);
-
- this.reattachListeners = function() {
- if (self.canvas) self.reattachListenersForElement(self.canvas, self);
- };
- },
- /*
- *
- * Base class for Vml Endpoints. extends VmlComponent.
- *
- */
- VmlEndpoint = function(params) {
- VmlComponent.apply(this, arguments);
- var vml = null, self = this, opacityStrokeNode = null, opacityFillNode = null;
- self.canvas = document.createElement("div");
- self.canvas.style["position"] = "absolute";
- //var group = _getGroup(params.parent);
- //group.appendChild(self.canvas);
- params["_jsPlumb"].appendElement(self.canvas, params.parent);
- if (self.tooltip) self.canvas.setAttribute("label", self.tooltip);
-
- this.paint = function(d, style, anchor) {
- var p = { };
-
- jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]);
- if (vml == null) {
- p["class"] = jsPlumb.endpointClass;
- vml = self.getVml([0,0, d[2], d[3]], p, anchor);
- self.canvas.appendChild(vml);
- self.attachListeners(vml, self);
- self.appendDisplayElement(vml, true);
- self.appendDisplayElement(self.canvas, true);
-
- self.initOpacityNodes(vml, ["fill"]);
- }
- else {
- //p["coordsize"] = "1,1";//(d[2] * scale) + "," + (d[3] * scale); again, unsure.
- _pos(vml, [0,0, d[2], d[3]]);
- _atts(vml, p);
- }
-
- _applyStyles(vml, style, self);
- };
-
- this.reattachListeners = function() {
- if (vml) self.reattachListenersForElement(vml, self);
- };
- };
-
- jsPlumb.Connectors.vml.Bezier = function() {
- jsPlumb.Connectors.Bezier.apply(this, arguments);
- VmlConnector.apply(this, arguments);
- this.getPath = function(d) {
- return "m" + _conv(d[4]) + "," + _conv(d[5]) +
- " c" + _conv(d[8]) + "," + _conv(d[9]) + "," + _conv(d[10]) + "," + _conv(d[11]) + "," + _conv(d[6]) + "," + _conv(d[7]) + " e";
- };
- };
-
- jsPlumb.Connectors.vml.Straight = function() {
- jsPlumb.Connectors.Straight.apply(this, arguments);
- VmlConnector.apply(this, arguments);
- this.getPath = function(d) {
- return "m" + _conv(d[4]) + "," + _conv(d[5]) + " l" + _conv(d[6]) + "," + _conv(d[7]) + " e";
- };
- };
-
- jsPlumb.Connectors.vml.Flowchart = function() {
- jsPlumb.Connectors.Flowchart.apply(this, arguments);
- VmlConnector.apply(this, arguments);
- this.getPath = function(dimensions) {
- var p = "m " + _conv(dimensions[4]) + "," + _conv(dimensions[5]) + " l";
- // loop through extra points
- for (var i = 0; i < dimensions[8]; i++) {
- p = p + " " + _conv(dimensions[9 + (i*2)]) + "," + _conv(dimensions[10 + (i*2)]);
- }
- // finally draw a line to the end
- p = p + " " + _conv(dimensions[6]) + "," + _conv(dimensions[7]) + " e";
- return p;
- };
- };
-
- jsPlumb.Endpoints.vml.Dot = function() {
- jsPlumb.Endpoints.Dot.apply(this, arguments);
- VmlEndpoint.apply(this, arguments);
- this.getVml = function(d, atts, anchor) { return _node("oval", d, atts); };
- };
-
- jsPlumb.Endpoints.vml.Rectangle = function() {
- jsPlumb.Endpoints.Rectangle.apply(this, arguments);
- VmlEndpoint.apply(this, arguments);
- this.getVml = function(d, atts, anchor) { return _node("rect", d, atts); };
- };
-
- /*
- * VML Image Endpoint is the same as the default image endpoint.
- */
- jsPlumb.Endpoints.vml.Image = jsPlumb.Endpoints.Image;
-
- /**
- * placeholder for Blank endpoint in vml renderer.
- */
- jsPlumb.Endpoints.vml.Blank = jsPlumb.Endpoints.Blank;
-
- /**
- * VML Label renderer. uses the default label renderer (which adds an element to the DOM)
- */
- jsPlumb.Overlays.vml.Label = jsPlumb.Overlays.Label;
-
- var AbstractVmlArrowOverlay = function(superclass, originalArgs) {
- superclass.apply(this, originalArgs);
- VmlComponent.apply(this, arguments);
- var self = this, path = null;
- self.canvas = null;
- var getPath = function(d, connectorDimensions) {
- return "m " + _conv(d.hxy.x) + "," + _conv(d.hxy.y) +
- " l " + _conv(d.tail[0].x) + "," + _conv(d.tail[0].y) +
- " " + _conv(d.cxy.x) + "," + _conv(d.cxy.y) +
- " " + _conv(d.tail[1].x) + "," + _conv(d.tail[1].y) +
- " x e";
- };
- this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle, connectorDimensions) {
- var p = {};
- if (strokeStyle) {
- p["stroked"] = "true";
- p["strokecolor"] = jsPlumb.util.convertStyle(strokeStyle, true);
- }
- if (lineWidth) p["strokeweight"] = lineWidth + "px";
- if (fillStyle) {
- p["filled"] = "true";
- p["fillcolor"] = fillStyle;
- }
- var xmin = Math.min(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x),
- ymin = Math.min(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y),
- xmax = Math.max(d.hxy.x, d.tail[0].x, d.tail[1].x, d.cxy.x),
- ymax = Math.max(d.hxy.y, d.tail[0].y, d.tail[1].y, d.cxy.y),
- w = Math.abs(xmax - xmin),
- h = Math.abs(ymax - ymin),
- dim = [xmin, ymin, w, h];
-
- // for VML, we create overlays using shapes that have the same dimensions and
- // coordsize as their connector - overlays calculate themselves relative to the
- // connector (it's how it's been done since the original canvas implementation, because
- // for canvas that makes sense).
- p["path"] = getPath(d, connectorDimensions);
- p["coordsize"] = (connectorDimensions[2] * scale) + "," + (connectorDimensions[3] * scale);
-
- dim[0] = connectorDimensions[0];
- dim[1] = connectorDimensions[1];
- dim[2] = connectorDimensions[2];
- dim[3] = connectorDimensions[3];
-
- if (self.canvas == null) {
- //p["class"] = jsPlumb.overlayClass; // TODO currentInstance?
- self.canvas = _node("shape", dim, p);
- connector.appendDisplayElement(self.canvas);
- self.attachListeners(self.canvas, connector);
- }
- else {
- _pos(self.canvas, dim);
- _atts(self.canvas, p);
- }
- };
-
- this.reattachListeners = function() {
- if (self.canvas) self.reattachListenersForElement(self.canvas, self);
- };
- };
-
- jsPlumb.Overlays.vml.Arrow = function() {
- AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]);
- };
-
- jsPlumb.Overlays.vml.PlainArrow = function() {
- AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]);
- };
-
- jsPlumb.Overlays.vml.Diamond = function() {
- AbstractVmlArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]);
- };
- })();/*
- * jsPlumb
- *
- * Title:jsPlumb 1.3.7
- *
- * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
- * elements, or VML.
- *
- * This file contains the SVG renderers.
- *
- * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org)
- *
- * http://jsplumb.org
- * http://github.com/sporritt/jsplumb
- * http://code.google.com/p/jsplumb
- *
- * Dual licensed under the MIT and GPL2 licenses.
- */
- /**
- * SVG support for jsPlumb.
- *
- * things to investigate:
- *
- * gradients: https://developer.mozilla.org/en/svg_in_html_introduction
- * css:http://tutorials.jenkov.com/svg/svg-and-css.html
- * text on a path: http://www.w3.org/TR/SVG/text.html#TextOnAPath
- * pointer events: https://developer.mozilla.org/en/css/pointer-events
- *
- * IE9 hover jquery: http://forum.jquery.com/topic/1-6-2-broke-svg-hover-events
- *
- */
- ;(function() {
-
- var svgAttributeMap = {
- "joinstyle":"stroke-linejoin",
- "stroke-linejoin":"stroke-linejoin",
- "stroke-dashoffset":"stroke-dashoffset",
- "stroke-linecap":"stroke-linecap"
- },
- STROKE_DASHARRAY = "stroke-dasharray",
- DASHSTYLE = "dashstyle",
- LINEAR_GRADIENT = "linearGradient",
- RADIAL_GRADIENT = "radialGradient",
- FILL = "fill",
- STOP = "stop",
- STROKE = "stroke",
- STROKE_WIDTH = "stroke-width",
- STYLE = "style",
- NONE = "none",
- JSPLUMB_GRADIENT = "jsplumb_gradient_",
- LINE_WIDTH = "lineWidth",
- ns = {
- svg:"http://www.w3.org/2000/svg",
- xhtml:"http://www.w3.org/1999/xhtml"
- },
- _attr = function(node, attributes) {
- for (var i in attributes)
- node.setAttribute(i, "" + attributes[i]);
- },
- _node = function(name, attributes) {
- var n = document.createElementNS(ns.svg, name);
- attributes = attributes || {};
- attributes["version"] = "1.1";
- attributes["xmlns"] = ns.xhtml;
- _attr(n, attributes);
- return n;
- },
- _pos = function(d) { return "position:absolute;left:" + d[0] + "px;top:" + d[1] + "px"; },
- _clearGradient = function(parent) {
- for (var i = 0; i < parent.childNodes.length; i++) {
- if (parent.childNodes[i].tagName == LINEAR_GRADIENT || parent.childNodes[i].tagName == RADIAL_GRADIENT)
- parent.removeChild(parent.childNodes[i]);
- }
- },
- _updateGradient = function(parent, node, style, dimensions, uiComponent) {
- var id = JSPLUMB_GRADIENT + uiComponent._jsPlumb.idstamp();
- // first clear out any existing gradient
- _clearGradient(parent);
- // this checks for an 'offset' property in the gradient, and in the absence of it, assumes
- // we want a linear gradient. if it's there, we create a radial gradient.
- // it is possible that a more explicit means of defining the gradient type would be
- // better. relying on 'offset' means that we can never have a radial gradient that uses
- // some default offset, for instance.
- if (!style.gradient.offset) {
- var g = _node(LINEAR_GRADIENT, {id:id});
- parent.appendChild(g);
- }
- else {
- var g = _node(RADIAL_GRADIENT, {
- id:id
- });
- parent.appendChild(g);
- }
-
- // the svg radial gradient seems to treat stops in the reverse
- // order to how canvas does it. so we want to keep all the maths the same, but
- // iterate the actual style declarations in reverse order, if the x indexes are not in order.
- for (var i = 0; i < style.gradient.stops.length; i++) {
- // Straight Connectors and Bezier connectors act slightly differently; this code is a bit of a kludge. but next version of
- // jsplumb will be replacing both Straight and Bezier to be generic instances of 'Connector', which has a list of segments.
- // so, not too concerned about leaving this in for now.
- var styleToUse = i;
- if (dimensions.length == 8)
- styleToUse = dimensions[4] < dimensions[6] ? i: style.gradient.stops.length - 1 - i;
- else
- styleToUse = dimensions[4] < dimensions[6] ? style.gradient.stops.length - 1 - i : i;
- var stopColor = jsPlumb.util.convertStyle(style.gradient.stops[styleToUse][1], true);
- var s = _node(STOP, {"offset":Math.floor(style.gradient.stops[i][0] * 100) + "%", "stop-color":stopColor});
- g.appendChild(s);
- }
- var applyGradientTo = style.strokeStyle ? STROKE : FILL;
- node.setAttribute(STYLE, applyGradientTo + ":url(#" + id + ")");
- },
- _applyStyles = function(parent, node, style, dimensions, uiComponent) {
-
- if (style.gradient) {
- _updateGradient(parent, node, style, dimensions, uiComponent);
- }
- else {
- // make sure we clear any existing gradient
- _clearGradient(parent);
- node.setAttribute(STYLE, "");
- }
-
- node.setAttribute(FILL, style.fillStyle ? jsPlumb.util.convertStyle(style.fillStyle, true) : NONE);
- node.setAttribute(STROKE, style.strokeStyle ? jsPlumb.util.convertStyle(style.strokeStyle, true) : NONE);
- if (style.lineWidth) {
- node.setAttribute(STROKE_WIDTH, style.lineWidth);
- }
-
- // in SVG there is a stroke-dasharray attribute we can set, and its syntax looks like
- // the syntax in VML but is actually kind of nasty: values are given in the pixel
- // coordinate space, whereas in VML they are multiples of the width of the stroked
- // line, which makes a lot more sense. for that reason, jsPlumb is supporting both
- // the native svg 'stroke-dasharray' attribute, and also the 'dashstyle' concept from
- // VML, which will be the preferred method. the code below this converts a dashstyle
- // attribute given in terms of stroke width into a pixel representation, by using the
- // stroke's lineWidth.
- if (style[DASHSTYLE] && style[LINE_WIDTH] && !style[STROKE_DASHARRAY]) {
- var sep = style[DASHSTYLE].indexOf(",") == -1 ? " " : ",",
- parts = style[DASHSTYLE].split(sep),
- styleToUse = "";
- parts.forEach(function(p) {
- styleToUse += (Math.floor(p * style.lineWidth) + sep);
- });
- node.setAttribute(STROKE_DASHARRAY, styleToUse);
- }
- else if(style[STROKE_DASHARRAY]) {
- node.setAttribute(STROKE_DASHARRAY, style[STROKE_DASHARRAY]);
- }
-
- // extra attributes such as join type, dash offset.
- for (var i in svgAttributeMap) {
- if (style[i]) {
- node.setAttribute(svgAttributeMap[i], style[i]);
- }
- }
- },
- _decodeFont = function(f) {
- var r = /([0-9].)(p[xt])\s(.*)/;
- var bits = f.match(r);
- return {size:bits[1] + bits[2], font:bits[3]};
- },
- _classManip = function(el, add, clazz) {
- var classesToAddOrRemove = clazz.split(" "),
- className = el.className,
- curClasses = className.baseVal.split(" ");
-
- for (var i = 0; i < classesToAddOrRemove.length; i++) {
- if (add) {
- if (curClasses.indexOf(classesToAddOrRemove[i]) == -1)
- curClasses.push(classesToAddOrRemove[i]);
- }
- else {
- var idx = curClasses.indexOf(classesToAddOrRemove[i]);
- if (idx != -1)
- curClasses.splice(idx, 1);
- }
- }
-
- el.className.baseVal = curClasses.join(" ");
- },
- _addClass = function(el, clazz) {
- _classManip(el, true, clazz);
- },
- _removeClass = function(el, clazz) {
- _classManip(el, false, clazz);
- };
-
- /**
- utility methods for other objects to use.
- */
- jsPlumb.util.svg = {
- addClass:_addClass,
- removeClass:_removeClass
- };
-
- /*
- * Base class for SVG components.
- */
- //var SvgComponent = function(cssClass, originalArgs, pointerEventsSpec) {
- var SvgComponent = function(params) {
- var self = this,
- pointerEventsSpec = params.pointerEventsSpec || "all";
- jsPlumb.jsPlumbUIComponent.apply(this, params.originalArgs);
- self.canvas = null, self.path = null, self.svg = null;
-
- var clazz = params.cssClass + " " + (params.originalArgs[0].cssClass || ""),
- svgParams = {
- "style":"",
- "width":0,
- "height":0,
- "pointer-events":pointerEventsSpec,
- "position":"absolute"
- };
- if (self.tooltip) svgParams["title"] = self.tooltip;
- self.svg = _node("svg", svgParams);
- if (params.useDivWrapper) {
- self.canvas = document.createElement("div");
- self.canvas.style["position"] = "absolute";
- jsPlumb.sizeCanvas(self.canvas,0,0,1,1);
- self.canvas.className = clazz;
- if (self.tooltip) self.canvas.setAttribute("title", self.tooltip);
- }
- else {
- _attr(self.svg, { "class":clazz });
- self.canvas = self.svg;
- }
-
- params._jsPlumb.appendElement(self.canvas, params.originalArgs[0]["parent"]);
- if (params.useDivWrapper) self.canvas.appendChild(self.svg);
-
- // TODO this displayElement stuff is common between all components, across all
- // renderers. would be best moved to jsPlumbUIComponent.
- var displayElements = [ self.canvas ];
- this.getDisplayElements = function() {
- return displayElements;
- };
-
- this.appendDisplayElement = function(el) {
- displayElements.push(el);
- };
-
- this.paint = function(d, style, anchor) {
- if (style != null) {
- var x = d[0], y = d[1];
- if (params.useDivWrapper) {
- jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]);
- x = 0, y = 0;
- }
- _attr(self.svg, {
- "style":_pos([x, y, d[2], d[3]]),
- "width": d[2],
- "height": d[3]
- });
- self._paint.apply(this, arguments);
- }
- };
- };
-
- /*
- * Base class for SVG connectors.
- */
- var SvgConnector = jsPlumb.SvgConnector = function(params) {
- var self = this;
- SvgComponent.apply(this, [ {
- cssClass:params["_jsPlumb"].connectorClass,
- originalArgs:arguments,
- pointerEventsSpec:"none",
- tooltip:params.tooltip,
- _jsPlumb:params["_jsPlumb"]
- } ]);
- this._paint = function(d, style) {
- var p = self.getPath(d), a = { "d":p }, outlineStyle = null;
- a["pointer-events"] = "all";
-
- // outline style. actually means drawing an svg object underneath the main one.
- if (style.outlineColor) {
- var outlineWidth = style.outlineWidth || 1,
- outlineStrokeWidth = style.lineWidth + (2 * outlineWidth),
- outlineStyle = jsPlumb.CurrentLibrary.extend({}, style);
- outlineStyle.strokeStyle = jsPlumb.util.convertStyle(style.outlineColor);
- outlineStyle.lineWidth = outlineStrokeWidth;
-
- if (self.bgPath == null) {
- self.bgPath = _node("path", a);
- self.svg.appendChild(self.bgPath);
- self.attachListeners(self.bgPath, self);
- }
- else {
- _attr(self.bgPath, a);
- }
-
- _applyStyles(self.svg, self.bgPath, outlineStyle, d, self);
- }
-
-
- // test - see below
- // a["clip-path"]= "url(#testClip)";
-
- if (self.path == null) {
- self.path = _node("path", a);
- self.svg.appendChild(self.path);
- self.attachListeners(self.path, self);
-
- /*
- this is a test of a clip path. i'm looking into using one of these to animate a jsplumb connection.
- you could do this by walking along the line, stepping along a little at a time, and setting the clip
- path to extend as far as that point.
-
- self.clip = _node("clipPath", {id:"testClip", clipPathUnits:"objectBoundingBox"});
- self.svg.appendChild(self.clip);
- self.clip.appendChild(_node("rect", {
- x:"0",y:"0",width:"0.5",height:"1"
- }));
- */
- }
- else {
- _attr(self.path, a);
- }
-
- _applyStyles(self.svg, self.path, style, d, self);
- };
-
- this.reattachListeners = function() {
- if (self.bgPath) self.reattachListenersForElement(self.bgPath, self);
- if (self.path) self.reattachListenersForElement(self.path, self);
- };
-
- };
- /*
- * SVG Bezier Connector
- */
- jsPlumb.Connectors.svg.Bezier = function(params) {
- jsPlumb.Connectors.Bezier.apply(this, arguments);
- SvgConnector.apply(this, arguments);
- this.getPath = function(d) {
- var _p = "M " + d[4] + " " + d[5];
- _p += (" C " + d[8] + " " + d[9] + " " + d[10] + " " + d[11] + " " + d[6] + " " + d[7]);
- return _p;
- };
- };
-
- /*
- * SVG straight line Connector
- */
- jsPlumb.Connectors.svg.Straight = function(params) {
- jsPlumb.Connectors.Straight.apply(this, arguments);
- SvgConnector.apply(this, arguments);
- this.getPath = function(d) { return "M " + d[4] + " " + d[5] + " L " + d[6] + " " + d[7]; };
- };
-
- jsPlumb.Connectors.svg.Flowchart = function() {
- var self = this;
- jsPlumb.Connectors.Flowchart.apply(this, arguments);
- SvgConnector.apply(this, arguments);
- this.getPath = function(dimensions) {
- var p = "M " + dimensions[4] + "," + dimensions[5];
- // loop through extra points
- for (var i = 0; i < dimensions[8]; i++) {
- p = p + " L " + dimensions[9 + (i*2)] + " " + dimensions[10 + (i*2)];
- }
- // finally draw a line to the end
- p = p + " " + dimensions[6] + "," + dimensions[7];
- return p;
- };
- };
-
- /*
- * Base class for SVG endpoints.
- */
- var SvgEndpoint = function(params) {
- var self = this;
- SvgComponent.apply(this, [ {
- cssClass:params["_jsPlumb"].endpointClass,
- originalArgs:arguments,
- pointerEventsSpec:"all",
- useDivWrapper:true,
- _jsPlumb:params["_jsPlumb"]
- } ]);
- this._paint = function(d, style) {
- var s = jsPlumb.extend({}, style);
- if (s.outlineColor) {
- s.strokeWidth = s.outlineWidth;
- s.strokeStyle = jsPlumb.util.convertStyle(s.outlineColor, true);
- }
-
- if (self.node == null) {
- self.node = self.makeNode(d, s);
- self.svg.appendChild(self.node);
- self.attachListeners(self.node, self);
- }
- _applyStyles(self.svg, self.node, s, d, self);
- _pos(self.node, d);
- };
-
- this.reattachListeners = function() {
- if (self.node) self.reattachListenersForElement(self.node, self);
- };
- };
-
- /*
- * SVG Dot Endpoint
- */
- jsPlumb.Endpoints.svg.Dot = function() {
- jsPlumb.Endpoints.Dot.apply(this, arguments);
- SvgEndpoint.apply(this, arguments);
- this.makeNode = function(d, style) {
- return _node("circle", {
- "cx" : d[2] / 2,
- "cy" : d[3] / 2,
- "r" : d[2] / 3
- });
- };
- };
-
- /*
- * SVG Rectangle Endpoint
- */
- jsPlumb.Endpoints.svg.Rectangle = function() {
- jsPlumb.Endpoints.Rectangle.apply(this, arguments);
- SvgEndpoint.apply(this, arguments);
- this.makeNode = function(d, style) {
- return _node("rect", {
- "width":d[2],
- "height":d[3]
- });
- };
- };
-
- /*
- * SVG Image Endpoint is the default image endpoint.
- */
- jsPlumb.Endpoints.svg.Image = jsPlumb.Endpoints.Image;
- /*
- * Blank endpoint in svg renderer is the default Blank endpoint.
- */
- jsPlumb.Endpoints.svg.Blank = jsPlumb.Endpoints.Blank;
- /*
- * Label endpoint in svg renderer is the default Label endpoint.
- */
- jsPlumb.Overlays.svg.Label = jsPlumb.Overlays.Label;
-
- var AbstractSvgArrowOverlay = function(superclass, originalArgs) {
- superclass.apply(this, originalArgs);
- jsPlumb.jsPlumbUIComponent.apply(this, originalArgs);
- this.isAppendedAtTopLevel = false;
- var self = this, path =null;
- this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) {
- if (path == null) {
- path = _node("path");
- connector.svg.appendChild(path);
- self.attachListeners(path, connector);
- self.attachListeners(path, self);
- }
- var clazz = originalArgs && (originalArgs.length == 1) ? (originalArgs[0].cssClass || "") : "";
-
- _attr(path, {
- "d" : makePath(d),
- "class" : clazz,
- stroke : strokeStyle ? strokeStyle : null,
- fill : fillStyle ? fillStyle : null
- });
- };
- var makePath = function(d) {
- return "M" + d.hxy.x + "," + d.hxy.y +
- " L" + d.tail[0].x + "," + d.tail[0].y +
- " L" + d.cxy.x + "," + d.cxy.y +
- " L" + d.tail[1].x + "," + d.tail[1].y +
- " L" + d.hxy.x + "," + d.hxy.y;
- };
- this.reattachListeners = function() {
- if (path) self.reattachListenersForElement(path, self);
- };
- };
-
- jsPlumb.Overlays.svg.Arrow = function() {
- AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]);
- };
-
- jsPlumb.Overlays.svg.PlainArrow = function() {
- AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]);
- };
-
- jsPlumb.Overlays.svg.Diamond = function() {
- AbstractSvgArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]);
- };
- // a test
- jsPlumb.Overlays.svg.GuideLines = function() {
- var path = null, self = this, path2 = null, p1_1, p1_2;
- jsPlumb.Overlays.GuideLines.apply(this, arguments);
- this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) {
- if (path == null) {
- path = _node("path");
- connector.svg.appendChild(path);
- self.attachListeners(path, connector);
- self.attachListeners(path, self);
- p1_1 = _node("path");
- connector.svg.appendChild(p1_1);
- self.attachListeners(p1_1, connector);
- self.attachListeners(p1_1, self);
- p1_2 = _node("path");
- connector.svg.appendChild(p1_2);
- self.attachListeners(p1_2, connector);
- self.attachListeners(p1_2, self);
- }
- _attr(path, {
- "d" : makePath(d[0], d[1]),
- stroke : "red",
- fill : null
- });
- _attr(p1_1, {
- "d" : makePath(d[2][0], d[2][1]),
- stroke : "blue",
- fill : null
- });
- _attr(p1_2, {
- "d" : makePath(d[3][0], d[3][1]),
- stroke : "green",
- fill : null
- });
- };
- var makePath = function(d1, d2) {
- return "M " + d1.x + "," + d1.y +
- " L" + d2.x + "," + d2.y;
- };
- };
- })();/*
- * jsPlumb
- *
- * Title:jsPlumb 1.3.7
- *
- * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
- * elements, or VML.
- *
- * This file contains the HTML5 canvas renderers.
- *
- * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org)
- *
- * http://jsplumb.org
- * http://github.com/sporritt/jsplumb
- * http://code.google.com/p/jsplumb
- *
- * Dual licensed under the MIT and GPL2 licenses.
- */
- ;(function() {
-
- // ********************************* CANVAS RENDERERS FOR CONNECTORS AND ENDPOINTS *******************************************************************
-
- // TODO refactor to renderer common script. put a ref to jsPlumb.sizeCanvas in there too.
- var _connectionBeingDragged = null,
- _hasClass = function(el, clazz) { return jsPlumb.CurrentLibrary.hasClass(_getElementObject(el), clazz); },
- _getElementObject = function(el) { return jsPlumb.CurrentLibrary.getElementObject(el); },
- _getOffset = function(el) { return jsPlumb.CurrentLibrary.getOffset(_getElementObject(el)); },
- _pageXY = function(el) { return jsPlumb.CurrentLibrary.getPageXY(el); },
- _clientXY = function(el) { return jsPlumb.CurrentLibrary.getClientXY(el); };
-
- /*
- * Class:CanvasMouseAdapter
- * Provides support for mouse events on canvases.
- */
- var CanvasMouseAdapter = function() {
- var self = this;
- self.overlayPlacements = [];
- jsPlumb.jsPlumbUIComponent.apply(this, arguments);
- jsPlumb.EventGenerator.apply(this, arguments);
- /**
- * returns whether or not the given event is ojver a painted area of the canvas.
- */
- this._over = function(e) {
- var o = _getOffset(_getElementObject(self.canvas)),
- pageXY = _pageXY(e),
- x = pageXY[0] - o.left, y = pageXY[1] - o.top;
- if (x > 0 && y > 0 && x < self.canvas.width && y < self.canvas.height) {
- // first check overlays
- for ( var i = 0; i < self.overlayPlacements.length; i++) {
- var p = self.overlayPlacements[i];
- if (p && (p[0] <= x && p[1] >= x && p[2] <= y && p[3] >= y))
- return true;
- }
-
- // then the canvas
- var d = self.canvas.getContext("2d").getImageData(parseInt(x), parseInt(y), 1, 1);
- return d.data[0] != 0 || d.data[1] != 0 || d.data[2] != 0 || d.data[3] != 0;
- }
- return false;
- };
-
- var _mouseover = false, _mouseDown = false, _posWhenMouseDown = null, _mouseWasDown = false,
- _nullSafeHasClass = function(el, clazz) {
- return el != null && _hasClass(el, clazz);
- };
- this.mousemove = function(e) {
- var pageXY = _pageXY(e), clientXY = _clientXY(e),
- ee = document.elementFromPoint(clientXY[0], clientXY[1]),
- eventSourceWasOverlay = _nullSafeHasClass(ee, "_jsPlumb_overlay");
- var _continue = _connectionBeingDragged == null && (_nullSafeHasClass(ee, "_jsPlumb_endpoint") || _nullSafeHasClass(ee, "_jsPlumb_connector"));
- if (!_mouseover && _continue && self._over(e)) {
- _mouseover = true;
- self.fire("mouseenter", self, e);
- return true;
- }
- // TODO here there is a remote chance that the overlay the mouse moved onto
- // is actually not an overlay for the current component. a more thorough check would
- // be to ensure the overlay belonged to the current component.
- else if (_mouseover && (!self._over(e) || !_continue) && !eventSourceWasOverlay) {
- _mouseover = false;
- self.fire("mouseexit", self, e);
- }
- self.fire("mousemove", self, e);
- };
-
- this.click = function(e) {
- if (_mouseover && self._over(e) && !_mouseWasDown)
- self.fire("click", self, e);
- _mouseWasDown = false;
- };
-
- this.dblclick = function(e) {
- if (_mouseover && self._over(e) && !_mouseWasDown)
- self.fire("dblclick", self, e);
- _mouseWasDown = false;
- };
-
- this.mousedown = function(e) {
- if(self._over(e) && !_mouseDown) {
- _mouseDown = true;
- _posWhenMouseDown = _getOffset(_getElementObject(self.canvas));
- self.fire("mousedown", self, e);
- }
- };
-
- this.mouseup = function(e) {
- _mouseDown = false;
- self.fire("mouseup", self, e);
- };
- this.contextmenu = function(e) {
- if (_mouseover && self._over(e) && !_mouseWasDown)
- self.fire("contextmenu", self, e);
- _mouseWasDown = false;
- };
- };
-
- var _newCanvas = function(params) {
- var canvas = document.createElement("canvas");
- params["_jsPlumb"].appendElement(canvas, params.parent);
- canvas.style.position = "absolute";
- if (params["class"]) canvas.className = params["class"];
- // set an id. if no id on the element and if uuid was supplied it
- // will be used, otherwise we'll create one.
- params["_jsPlumb"].getId(canvas, params.uuid);
- if (params.tooltip) canvas.setAttribute("title", params.tooltip);
- return canvas;
- };
- var CanvasComponent = function(params) {
- CanvasMouseAdapter.apply(this, arguments);
- var displayElements = [ ];
- this.getDisplayElements = function() { return displayElements; };
- this.appendDisplayElement = function(el) { displayElements.push(el); };
- }
-
- /**
- * Class:CanvasConnector
- * Superclass for Canvas Connector renderers.
- */
- var CanvasConnector = jsPlumb.CanvasConnector = function(params) {
-
- CanvasComponent.apply(this, arguments);
-
- var _paintOneStyle = function(dim, aStyle) {
- self.ctx.save();
- jsPlumb.extend(self.ctx, aStyle);
- if (aStyle.gradient) {
- var g = self.createGradient(dim, self.ctx);
- for ( var i = 0; i < aStyle.gradient.stops.length; i++)
- g.addColorStop(aStyle.gradient.stops[i][0], aStyle.gradient.stops[i][1]);
- self.ctx.strokeStyle = g;
- }
- self._paint(dim);
- self.ctx.restore();
- };
- var self = this,
- clazz = self._jsPlumb.connectorClass + " " + (params.cssClass || "");
- self.canvas = _newCanvas({
- "class":clazz,
- _jsPlumb:self._jsPlumb,
- parent:params.parent,
- tooltip:params.tooltip
- });
- self.ctx = self.canvas.getContext("2d");
-
- self.appendDisplayElement(self.canvas);
-
- self.paint = function(dim, style) {
- if (style != null) {
- jsPlumb.sizeCanvas(self.canvas, dim[0], dim[1], dim[2], dim[3]);
- if (style.outlineColor != null) {
- var outlineWidth = style.outlineWidth || 1,
- outlineStrokeWidth = style.lineWidth + (2 * outlineWidth),
- outlineStyle = {
- strokeStyle:style.outlineColor,
- lineWidth:outlineStrokeWidth
- };
- _paintOneStyle(dim, outlineStyle);
- }
- _paintOneStyle(dim, style);
- }
- };
- };
-
- /**
- * Class:CanvasEndpoint
- * Superclass for Canvas Endpoint renderers.
- */
- var CanvasEndpoint = function(params) {
- var self = this;
- CanvasComponent.apply(this, arguments);
- var clazz = self._jsPlumb.endpointClass + " " + (params.cssClass || ""),
- canvasParams = {
- "class":clazz,
- _jsPlumb:self._jsPlumb,
- parent:params.parent,
- tooltip:self.tooltip
- };
- self.canvas = _newCanvas(canvasParams);
- self.ctx = self.canvas.getContext("2d");
- self.appendDisplayElement(self.canvas);
-
- this.paint = function(d, style, anchor) {
- jsPlumb.sizeCanvas(self.canvas, d[0], d[1], d[2], d[3]);
- if (style.outlineColor != null) {
- var outlineWidth = style.outlineWidth || 1,
- outlineStrokeWidth = style.lineWidth + (2 * outlineWidth);
- var outlineStyle = {
- strokeStyle:style.outlineColor,
- lineWidth:outlineStrokeWidth
- };
- }
-
- self._paint.apply(this, arguments);
- };
- };
-
- jsPlumb.Endpoints.canvas.Dot = function(params) {
- jsPlumb.Endpoints.Dot.apply(this, arguments);
- CanvasEndpoint.apply(this, arguments);
- var self = this,
- parseValue = function(value) {
- try { return parseInt(value); }
- catch(e) {
- if (value.substring(value.length - 1) == '%')
- return parseInt(value.substring(0, value - 1));
- }
- },
- calculateAdjustments = function(gradient) {
- var offsetAdjustment = self.defaultOffset, innerRadius = self.defaultInnerRadius;
- gradient.offset && (offsetAdjustment = parseValue(gradient.offset));
- gradient.innerRadius && (innerRadius = parseValue(gradient.innerRadius));
- return [offsetAdjustment, innerRadius];
- };
- this._paint = function(d, style, anchor) {
- if (style != null) {
- var ctx = self.canvas.getContext('2d'), orientation = anchor.getOrientation(self);
- jsPlumb.extend(ctx, style);
- if (style.gradient) {
- var adjustments = calculateAdjustments(style.gradient),
- yAdjust = orientation[1] == 1 ? adjustments[0] * -1 : adjustments[0],
- xAdjust = orientation[0] == 1 ? adjustments[0] * -1: adjustments[0],
- g = ctx.createRadialGradient(d[4], d[4], d[4], d[4] + xAdjust, d[4] + yAdjust, adjustments[1]);
- for (var i = 0; i < style.gradient.stops.length; i++)
- g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]);
- ctx.fillStyle = g;
- }
- ctx.beginPath();
- ctx.arc(d[4], d[4], d[4], 0, Math.PI*2, true);
- ctx.closePath();
- if (style.fillStyle || style.gradient) ctx.fill();
- if (style.strokeStyle) ctx.stroke();
- }
- };
- };
-
- jsPlumb.Endpoints.canvas.Rectangle = function(params) {
-
- var self = this;
- jsPlumb.Endpoints.Rectangle.apply(this, arguments);
- CanvasEndpoint.apply(this, arguments);
-
- this._paint = function(d, style, anchor) {
-
- var ctx = self.canvas.getContext("2d"), orientation = anchor.getOrientation(self);
- jsPlumb.extend(ctx, style);
-
- /* canvas gradient */
- if (style.gradient) {
- // first figure out which direction to run the gradient in (it depends on the orientation of the anchors)
- var y1 = orientation[1] == 1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0;
- var y2 = orientation[1] == -1 ? d[3] : orientation[1] == 0 ? d[3] / 2 : 0;
- var x1 = orientation[0] == 1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0;
- var x2 = orientation[0] == -1 ? d[2] : orientation[0] == 0 ? d[2] / 2 : 0;
- var g = ctx.createLinearGradient(x1,y1,x2,y2);
- for (var i = 0; i < style.gradient.stops.length; i++)
- g.addColorStop(style.gradient.stops[i][0], style.gradient.stops[i][1]);
- ctx.fillStyle = g;
- }
-
- ctx.beginPath();
- ctx.rect(0, 0, d[2], d[3]);
- ctx.closePath();
- if (style.fillStyle || style.gradient) ctx.fill();
- if (style.strokeStyle) ctx.stroke();
- };
- };
-
- jsPlumb.Endpoints.canvas.Triangle = function(params) {
-
- var self = this;
- jsPlumb.Endpoints.Triangle.apply(this, arguments);
- CanvasEndpoint.apply(this, arguments);
-
- this._paint = function(d, style, anchor)
- {
- var width = d[2], height = d[3], x = d[0], y = d[1],
- ctx = self.canvas.getContext('2d'),
- offsetX = 0, offsetY = 0, angle = 0,
- orientation = anchor.getOrientation(self);
-
- if( orientation[0] == 1 ) {
- offsetX = width;
- offsetY = height;
- angle = 180;
- }
- if( orientation[1] == -1 ) {
- offsetX = width;
- angle = 90;
- }
- if( orientation[1] == 1 ) {
- offsetY = height;
- angle = -90;
- }
-
- ctx.fillStyle = style.fillStyle;
-
- ctx.translate(offsetX, offsetY);
- ctx.rotate(angle * Math.PI/180);
- ctx.beginPath();
- ctx.moveTo(0, 0);
- ctx.lineTo(width/2, height/2);
- ctx.lineTo(0, height);
- ctx.closePath();
- if (style.fillStyle || style.gradient) ctx.fill();
- if (style.strokeStyle) ctx.stroke();
- };
- };
-
- /*
- * Canvas Image Endpoint: uses the default version, which creates an <img> tag.
- */
- jsPlumb.Endpoints.canvas.Image = jsPlumb.Endpoints.Image;
-
- /*
- * Blank endpoint in all renderers is just the default Blank endpoint.
- */
- jsPlumb.Endpoints.canvas.Blank = jsPlumb.Endpoints.Blank;
-
- /*
- * Canvas Bezier Connector. Draws a Bezier curve onto a Canvas element.
- */
- jsPlumb.Connectors.canvas.Bezier = function() {
- var self = this;
- jsPlumb.Connectors.Bezier.apply(this, arguments);
- CanvasConnector.apply(this, arguments);
- this._paint = function(dimensions) {
- self.ctx.beginPath();
- self.ctx.moveTo(dimensions[4], dimensions[5]);
- self.ctx.bezierCurveTo(dimensions[8], dimensions[9], dimensions[10], dimensions[11], dimensions[6], dimensions[7]);
- self.ctx.stroke();
- };
-
- // TODO i doubt this handles the case that source and target are swapped.
- this.createGradient = function(dim, ctx, swap) {
- return /*(swap) ? self.ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]) : */self.ctx.createLinearGradient(dim[6], dim[7], dim[4], dim[5]);
- };
- };
-
- /*
- * Canvas straight line Connector. Draws a straight line onto a Canvas element.
- */
- jsPlumb.Connectors.canvas.Straight = function() {
- var self = this;
- jsPlumb.Connectors.Straight.apply(this, arguments);
- CanvasConnector.apply(this, arguments);
- this._paint = function(dimensions) {
- self.ctx.beginPath();
- self.ctx.moveTo(dimensions[4], dimensions[5]);
- self.ctx.lineTo(dimensions[6], dimensions[7]);
- self.ctx.stroke();
- };
-
- // TODO this does not handle the case that src and target are swapped.
- this.createGradient = function(dim, ctx) {
- return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]);
- };
- };
-
- jsPlumb.Connectors.canvas.Flowchart = function() {
- var self = this;
- jsPlumb.Connectors.Flowchart.apply(this, arguments);
- CanvasConnector.apply(this, arguments);
- this._paint = function(dimensions) {
- self.ctx.beginPath();
- self.ctx.moveTo(dimensions[4], dimensions[5]);
- // loop through extra points
- for (var i = 0; i < dimensions[8]; i++) {
- self.ctx.lineTo(dimensions[9 + (i*2)], dimensions[10 + (i*2)]);
- }
- // finally draw a line to the end
- self.ctx.lineTo(dimensions[6], dimensions[7]);
- self.ctx.stroke();
- };
-
- this.createGradient = function(dim, ctx) {
- return ctx.createLinearGradient(dim[4], dim[5], dim[6], dim[7]);
- };
- };
-
- // ********************************* END OF CANVAS RENDERERS *******************************************************************
-
- jsPlumb.Overlays.canvas.Label = jsPlumb.Overlays.Label;
-
- /**
- * a placeholder right now, really just exists to mirror the fact that there are SVG and VML versions of this.
- */
- var CanvasOverlay = function() {
- jsPlumb.jsPlumbUIComponent.apply(this, arguments);
- };
-
- var AbstractCanvasArrowOverlay = function(superclass, originalArgs) {
- superclass.apply(this, originalArgs);
- CanvasOverlay.apply(this, arguments);
- this.paint = function(connector, d, lineWidth, strokeStyle, fillStyle) {
- var ctx = connector.ctx;
-
- ctx.lineWidth = lineWidth;
- ctx.beginPath();
- ctx.moveTo(d.hxy.x, d.hxy.y);
- ctx.lineTo(d.tail[0].x, d.tail[0].y);
- ctx.lineTo(d.cxy.x, d.cxy.y);
- ctx.lineTo(d.tail[1].x, d.tail[1].y);
- ctx.lineTo(d.hxy.x, d.hxy.y);
- ctx.closePath();
-
- if (strokeStyle) {
- ctx.strokeStyle = strokeStyle;
- ctx.stroke();
- }
- if (fillStyle) {
- ctx.fillStyle = fillStyle;
- ctx.fill();
- }
- };
- };
-
- jsPlumb.Overlays.canvas.Arrow = function() {
- AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Arrow, arguments]);
- };
-
- jsPlumb.Overlays.canvas.PlainArrow = function() {
- AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.PlainArrow, arguments]);
- };
-
- jsPlumb.Overlays.canvas.Diamond = function() {
- AbstractCanvasArrowOverlay.apply(this, [jsPlumb.Overlays.Diamond, arguments]);
- };
- })();/*
- * jsPlumb
- *
- * Title:jsPlumb 1.3.7
- *
- * Provides a way to visually connect elements on an HTML page, using either SVG, Canvas
- * elements, or VML.
- *
- * This file contains the jQuery adapter.
- *
- * Copyright (c) 2010 - 2012 Simon Porritt (http://jsplumb.org)
- *
- * http://jsplumb.org
- * http://github.com/sporritt/jsplumb
- * http://code.google.com/p/jsplumb
- *
- * Dual licensed under the MIT and GPL2 licenses.
- */
- /*
- * the library specific functions, such as find offset, get id, get attribute, extend etc.
- * the full list is:
- *
- * addClass adds a class to the given element
- * animate calls the underlying library's animate functionality
- * appendElement appends a child element to a parent element.
- * bind binds some event to an element
- * dragEvents a dictionary of event names
- * extend extend some js object with another. probably not overly necessary; jsPlumb could just do this internally.
- * getAttribute gets some attribute from an element
- * getDragObject gets the object that is being dragged, by extracting it from the arguments passed to a drag callback
- * getDragScope gets the drag scope for a given element.
- * getDropScope gets the drop scope for a given element.
- * getElementObject turns an id or dom element into an element object of the underlying library's type.
- * getOffset gets an element's offset
- * getPageXY gets the page event's xy location.
- * getParent gets the parent of some element.
- * getScrollLeft gets an element's scroll left. TODO: is this actually used? will it be?
- * getScrollTop gets an element's scroll top. TODO: is this actually used? will it be?
- * getSize gets an element's size.
- * getUIPosition gets the position of some element that is currently being dragged, by extracting it from the arguments passed to a drag callback.
- * hasClass returns whether or not the given element has the given class.
- * initDraggable initializes an element to be draggable
- * initDroppable initializes an element to be droppable
- * isDragSupported returns whether or not drag is supported for some element.
- * isDropSupported returns whether or not drop is supported for some element.
- * removeClass removes a class from a given element.
- * removeElement removes some element completely from the DOM.
- * setAttribute sets an attribute on some element.
- * setDraggable sets whether or not some element should be draggable.
- * setDragScope sets the drag scope for a given element.
- * setOffset sets the offset of some element.
- * trigger triggers some event on an element.
- * unbind unbinds some listener from some element.
- */
- (function($) {
-
- //var getBoundingClientRectSupported = "getBoundingClientRect" in document.documentElement;
- jsPlumb.CurrentLibrary = {
-
- /**
- * adds the given class to the element object.
- */
- addClass : function(el, clazz) {
- el = jsPlumb.CurrentLibrary.getElementObject(el);
- try {
- if (el[0].className.constructor == SVGAnimatedString) {
- jsPlumb.util.svg.addClass(el[0], clazz);
- }
- }
- catch (e) {
- // SVGAnimatedString not supported; no problem.
- }
- el.addClass(clazz);
- },
-
- /**
- * animates the given element.
- */
- animate : function(el, properties, options) {
- el.animate(properties, options);
- },
-
- /**
- * appends the given child to the given parent.
- */
- appendElement : function(child, parent) {
- jsPlumb.CurrentLibrary.getElementObject(parent).append(child);
- },
-
- /**
- * event binding wrapper. it just so happens that jQuery uses 'bind' also. yui3, for example,
- * uses 'on'.
- */
- bind : function(el, event, callback) {
- el = jsPlumb.CurrentLibrary.getElementObject(el);
- el.bind(event, callback);
- },
-
- /**
- * mapping of drag events for jQuery
- */
- dragEvents : {
- 'start':'start', 'stop':'stop', 'drag':'drag', 'step':'step',
- 'over':'over', 'out':'out', 'drop':'drop', 'complete':'complete'
- },
-
- /**
- * wrapper around the library's 'extend' functionality (which it hopefully has.
- * otherwise you'll have to do it yourself). perhaps jsPlumb could do this for you
- * instead. it's not like its hard.
- */
- extend : function(o1, o2) {
- return $.extend(o1, o2);
- },
-
- /**
- * gets the named attribute from the given element object.
- */
- getAttribute : function(el, attName) {
- return el.attr(attName);
- },
-
- getClientXY : function(eventObject) {
- return [eventObject.clientX, eventObject.clientY];
- },
-
- getDocumentElement : function() { return document; },
-
- /**
- * takes the args passed to an event function and returns you an object representing that which is being dragged.
- */
- getDragObject : function(eventArgs) {
- return eventArgs[1].draggable;
- },
-
- getDragScope : function(el) {
- return el.draggable("option", "scope");
- },
- getDropEvent : function(args) {
- return args[0];
- },
-
- getDropScope : function(el) {
- return el.droppable("option", "scope");
- },
- /**
- * gets a DOM element from the given input, which might be a string (in which case we just do document.getElementById),
- * a selector (in which case we return el[0]), or a DOM element already (we assume this if it's not either of the other
- * two cases). this is the opposite of getElementObject below.
- */
- getDOMElement : function(el) {
- if (typeof(el) == "string") return document.getElementById(el);
- else if (el.context) return el[0];
- else return el;
- },
-
- /**
- * gets an "element object" from the given input. this means an object that is used by the
- * underlying library on which jsPlumb is running. 'el' may already be one of these objects,
- * in which case it is returned as-is. otherwise, 'el' is a String, the library's lookup
- * function is used to find the element, using the given String as the element's id.
- *
- */
- getElementObject : function(el) {
- return typeof(el) == "string" ? $("#" + el) : $(el);
- },
-
- /**
- * gets the offset for the element object. this should return a js object like this:
- *
- * { left:xxx, top: xxx }
- */
- getOffset : function(el) {
- return el.offset();
- },
-
- getPageXY : function(eventObject) {
- return [eventObject.pageX, eventObject.pageY];
- },
-
- getParent : function(el) {
- return jsPlumb.CurrentLibrary.getElementObject(el).parent();
- },
-
- getScrollLeft : function(el) {
- return el.scrollLeft();
- },
-
- getScrollTop : function(el) {
- return el.scrollTop();
- },
-
- getSelector : function(spec) {
- return $(spec);
- },
-
- /**
- * gets the size for the element object, in an array : [ width, height ].
- */
- getSize : function(el) {
- return [el.outerWidth(), el.outerHeight()];
- },
- getTagName : function(el) {
- var e = jsPlumb.CurrentLibrary.getElementObject(el);
- return e.length > 0 ? e[0].tagName : null;
- },
-
- /**
- * takes the args passed to an event function and returns you an object that gives the
- * position of the object being moved, as a js object with the same params as the result of
- * getOffset, ie: { left: xxx, top: xxx }.
- *
- * different libraries have different signatures for their event callbacks.
- * see getDragObject as well
- */
- getUIPosition : function(eventArgs) {
-
- // this code is a workaround for the case that the element being dragged has a margin set on it. jquery UI passes
- // in the wrong offset if the element has a margin (it doesn't take the margin into account). the getBoundingClientRect
- // method, which is in pretty much all browsers now, reports the right numbers. but it introduces a noticeable lag, which
- // i don't like.
-
- /*if ( getBoundingClientRectSupported ) {
- var r = eventArgs[1].helper[0].getBoundingClientRect();
- return { left : r.left, top: r.top };
- } else {*/
- if (eventArgs.length == 1) {
- ret = { left: eventArgs[0].pageX, top:eventArgs[0].pageY };
- }
- else {
- var ui = eventArgs[1], _offset = ui.offset;
- ret = _offset || ui.absolutePosition;
- }
- return ret;
- },
-
- hasClass : function(el, clazz) {
- return el.hasClass(clazz);
- },
-
- /**
- * initialises the given element to be draggable.
- */
- initDraggable : function(el, options) {
- options = options || {};
- // remove helper directive if present.
- options.helper = null;
- options['scope'] = options['scope'] || jsPlumb.Defaults.Scope;
- el.draggable(options);
- },
-
- /**
- * initialises the given element to be droppable.
- */
- initDroppable : function(el, options) {
- options['scope'] = options['scope'] || jsPlumb.Defaults.Scope;
- el.droppable(options);
- },
-
- isAlreadyDraggable : function(el) {
- el = jsPlumb.CurrentLibrary.getElementObject(el);
- return el.hasClass("ui-draggable");
- },
-
- /**
- * returns whether or not drag is supported (by the library, not whether or not it is disabled) for the given element.
- */
- isDragSupported : function(el, options) {
- return el.draggable;
- },
-
- /**
- * returns whether or not drop is supported (by the library, not whether or not it is disabled) for the given element.
- */
- isDropSupported : function(el, options) {
- return el.droppable;
- },
-
- /**
- * removes the given class from the element object.
- */
- removeClass : function(el, clazz) {
- el = jsPlumb.CurrentLibrary.getElementObject(el);
- try {
- if (el[0].className.constructor == SVGAnimatedString) {
- jsPlumb.util.svg.removeClass(el[0], clazz);
- }
- }
- catch (e) {
- // SVGAnimatedString not supported; no problem.
- }
- el.removeClass(clazz);
- },
-
- removeElement : function(element, parent) {
- jsPlumb.CurrentLibrary.getElementObject(element).remove();
- },
-
- /**
- * sets the named attribute on the given element object.
- */
- setAttribute : function(el, attName, attValue) {
- el.attr(attName, attValue);
- },
-
- /**
- * sets the draggable state for the given element
- */
- setDraggable : function(el, draggable) {
- el.draggable("option", "disabled", !draggable);
- },
-
- /**
- * sets the drag scope. probably time for a setDragOption method (roll this and the one above together)
- * @param el
- * @param scope
- */
- setDragScope : function(el, scope) {
- el.draggable("option", "scope", scope);
- },
-
- setOffset : function(el, o) {
- jsPlumb.CurrentLibrary.getElementObject(el).offset(o);
- },
-
- /**
- * note that jquery ignores the name of the event you wanted to trigger, and figures it out for itself.
- * the other libraries do not. yui, in fact, cannot even pass an original event. we have to pull out stuff
- * from the originalEvent to put in an options object for YUI.
- * @param el
- * @param event
- * @param originalEvent
- */
- trigger : function(el, event, originalEvent) {
- //originalEvent.stopPropagation();
- //jsPlumb.CurrentLibrary.getElementObject(el).trigger(originalEvent);
- var h = jQuery._data(jsPlumb.CurrentLibrary.getElementObject(el)[0], "handle");
- h(originalEvent);
- //originalEvent.stopPropagation();
- },
-
- /**
- * event unbinding wrapper. it just so happens that jQuery uses 'unbind' also. yui3, for example,
- * uses..something else.
- */
- unbind : function(el, event, callback) {
- el = jsPlumb.CurrentLibrary.getElementObject(el);
- el.unbind(event, callback);
- }
- };
-
- $(document).ready(jsPlumb.init);
-
- })(jQuery);
- (function(){if("undefined"==typeof Math.sgn)Math.sgn=function(a){return 0==a?0:0<a?1:-1};var l={subtract:function(a,b){return{x:a.x-b.x,y:a.y-b.y}},dotProduct:function(a,b){return a.x*b.x+a.y*b.y},square:function(a){return Math.sqrt(a.x*a.x+a.y*a.y)},scale:function(a,b){return{x:a.x*b,y:a.y*b}}},w=Math.pow(2,-65),u=function(a,b){for(var f=[],d=b.length-1,h=2*d-1,g=[],c=[],k=[],i=[],m=[[1,0.6,0.3,0.1],[0.4,0.6,0.6,0.4],[0.1,0.3,0.6,1]],e=0;e<=d;e++)g[e]=l.subtract(b[e],a);for(e=0;e<=d-1;e++)c[e]=l.subtract(b[e+
- 1],b[e]),c[e]=l.scale(c[e],3);for(e=0;e<=d-1;e++)for(var n=0;n<=d;n++)k[e]||(k[e]=[]),k[e][n]=l.dotProduct(c[e],g[n]);for(e=0;e<=h;e++)i[e]||(i[e]=[]),i[e].y=0,i[e].x=parseFloat(e)/h;h=d-1;for(g=0;g<=d+h;g++){e=Math.max(0,g-h);for(c=Math.min(g,d);e<=c;e++)j=g-e,i[e+j].y+=k[j][e]*m[j][e]}d=b.length-1;i=q(i,2*d-1,f,0);h=l.subtract(a,b[0]);k=l.square(h);for(e=m=0;e<i;e++)h=l.subtract(a,r(b,d,f[e],null,null)),h=l.square(h),h<k&&(k=h,m=f[e]);h=l.subtract(a,b[d]);h=l.square(h);h<k&&(k=h,m=1);return{location:m,
- distance:k}},q=function(a,b,f,d){var h=[],g=[],c=[],k=[],i=0,m,e;e=Math.sgn(a[0].y);for(var n=1;n<=b;n++)m=Math.sgn(a[n].y),m!=e&&i++,e=m;switch(i){case 0:return 0;case 1:if(64<=d)return f[0]=(a[0].x+a[b].x)/2,1;var o,i=a[0].y-a[b].y;m=a[b].x-a[0].x;e=a[0].x*a[b].y-a[b].x*a[0].y;n=max_distance_below=0;for(o=1;o<b;o++){var l=i*a[o].x+m*a[o].y+e;l>n?n=l:l<max_distance_below&&(max_distance_below=l)}o=m;n=(1*(e-n)-0*o)*(1/(0*o-1*i));o=m;i=(1*(e-max_distance_below)-0*o)*(1/(0*o-1*i));m=Math.min(n,i);if(Math.max(n,
- i)-m<w)return c=a[b].x-a[0].x,k=a[b].y-a[0].y,f[0]=0+1*(c*(a[0].y-0)-k*(a[0].x-0))*(1/(0*c-1*k)),1}r(a,b,0.5,h,g);a=q(h,b,c,d+1);b=q(g,b,k,d+1);for(d=0;d<a;d++)f[d]=c[d];for(d=0;d<b;d++)f[d+a]=k[d];return a+b},r=function(a,b,f,d,h){for(var g=[[]],c=0;c<=b;c++)g[0][c]=a[c];for(a=1;a<=b;a++)for(c=0;c<=b-a;c++)g[a]||(g[a]=[]),g[a][c]||(g[a][c]={}),g[a][c].x=(1-f)*g[a-1][c].x+f*g[a-1][c+1].x,g[a][c].y=(1-f)*g[a-1][c].y+f*g[a-1][c+1].y;if(null!=d)for(c=0;c<=b;c++)d[c]=g[c][0];if(null!=h)for(c=0;c<=b;c++)h[c]=
- g[b-c][c];return g[b][0]},v={},x=function(a){var b=v[a];if(!b){var b=[],f=function(a){return function(){return a}},d=function(){return function(a){return a}},h=function(){return function(a){return 1-a}},g=function(a){return function(b){for(var c=1,d=0;d<a.length;d++)c*=a[d](b);return c}};b.push(new function(){return function(b){return Math.pow(b,a)}});for(var c=1;c<a;c++){for(var k=[new f(a)],i=0;i<a-c;i++)k.push(new d);for(i=0;i<c;i++)k.push(new h);b.push(new g(k))}b.push(new function(){return function(b){return Math.pow(1-
- b,a)}});v[a]=b}return b},p=function(a,b){for(var f=x(a.length-1),d=0,h=0,g=0;g<a.length;g++)d+=a[g].x*f[g](b),h+=a[g].y*f[g](b);return{x:d,y:h}},s=function(a,b,f){for(var d=p(a,b),h=0,g=0<f?1:-1,c=null;h<Math.abs(f);)b+=0.005*g,c=p(a,b),h+=Math.sqrt(Math.pow(c.x-d.x,2)+Math.pow(c.y-d.y,2)),d=c;return{point:c,location:b}},t=function(a,b){var f=p(a,b),d=p(a.slice(0,a.length-1),b),h=d.y-f.y,f=d.x-f.x;return 0==h?Infinity:Math.atan(h/f)};window.jsBezier={distanceFromCurve:u,gradientAtPoint:t,gradientAtPointAlongCurveFrom:function(a,
- b,f){b=s(a,b,f);if(1<b.location)b.location=1;if(0>b.location)b.location=0;return t(a,b.location)},nearestPointOnCurve:function(a,b){var f=u(a,b);return{point:r(b,b.length-1,f.location,null,null),location:f.location}},pointOnCurve:p,pointAlongCurveFrom:function(a,b,f){return s(a,b,f).point},perpendicularToCurveAt:function(a,b,f,d){b=s(a,b,null==d?0:d);a=t(a,b.location);d=Math.atan(-1/a);a=f/2*Math.sin(d);f=f/2*Math.cos(d);return[{x:b.point.x+f,y:b.point.y+a},{x:b.point.x-f,y:b.point.y-a}]}}})();
|