jquery.fancytree-all.js 191 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401
  1. /*!
  2. * jquery.fancytree.js
  3. * Dynamic tree view control, with support for lazy loading of branches.
  4. * https://github.com/mar10/fancytree/
  5. *
  6. * Copyright (c) 2006-2014, Martin Wendt (http://wwWendt.de)
  7. * Released under the MIT license
  8. * https://github.com/mar10/fancytree/wiki/LicenseInfo
  9. *
  10. * @version 2.2.0
  11. * @date 2014-06-28T17:15
  12. */
  13. /** Core Fancytree module.
  14. */
  15. // Start of local namespace
  16. ;(function($, window, document, undefined) {
  17. "use strict";
  18. // prevent duplicate loading
  19. if ( $.ui.fancytree && $.ui.fancytree.version ) {
  20. $.ui.fancytree.warn("Fancytree: ignored duplicate include");
  21. return;
  22. }
  23. /* *****************************************************************************
  24. * Private functions and variables
  25. */
  26. function _raiseNotImplemented(msg){
  27. msg = msg || "";
  28. $.error("Not implemented: " + msg);
  29. }
  30. function _assert(cond, msg){
  31. // TODO: see qunit.js extractStacktrace()
  32. if(!cond){
  33. msg = msg ? ": " + msg : "";
  34. $.error("Assertion failed" + msg);
  35. }
  36. }
  37. function consoleApply(method, args){
  38. var i, s,
  39. fn = window.console ? window.console[method] : null;
  40. if(fn){
  41. if(fn.apply){
  42. fn.apply(window.console, args);
  43. }else{
  44. // IE?
  45. s = "";
  46. for( i=0; i<args.length; i++){
  47. s += args[i];
  48. }
  49. fn(s);
  50. }
  51. }
  52. }
  53. /*Return true if x is a FancytreeNode.*/
  54. function _isNode(x){
  55. return !!(x.tree && x.statusNodeType !== undefined);
  56. }
  57. /** Return true if dotted version string is equal or higher than requested version.
  58. *
  59. * See http://jsfiddle.net/mar10/FjSAN/
  60. */
  61. function isVersionAtLeast(dottedVersion, major, minor, patch){
  62. var i, v, t,
  63. verParts = $.map($.trim(dottedVersion).split("."), function(e){ return parseInt(e, 10); }),
  64. testParts = $.map(Array.prototype.slice.call(arguments, 1), function(e){ return parseInt(e, 10); });
  65. for( i = 0; i < testParts.length; i++ ){
  66. v = verParts[i] || 0;
  67. t = testParts[i] || 0;
  68. if( v !== t ){
  69. return ( v > t );
  70. }
  71. }
  72. return true;
  73. }
  74. /** Return a wrapper that calls sub.methodName() and exposes
  75. * this : tree
  76. * this._local : tree.ext.EXTNAME
  77. * this._super : base.methodName()
  78. */
  79. function _makeVirtualFunction(methodName, tree, base, extension, extName){
  80. // $.ui.fancytree.debug("_makeVirtualFunction", methodName, tree, base, extension, extName);
  81. // if(rexTestSuper && !rexTestSuper.test(func)){
  82. // // extension.methodName() doesn't call _super(), so no wrapper required
  83. // return func;
  84. // }
  85. // Use an immediate function as closure
  86. var proxy = (function(){
  87. var prevFunc = tree[methodName], // org. tree method or prev. proxy
  88. baseFunc = extension[methodName], //
  89. _local = tree.ext[extName],
  90. _super = function(){
  91. return prevFunc.apply(tree, arguments);
  92. };
  93. // Return the wrapper function
  94. return function(){
  95. var prevLocal = tree._local,
  96. prevSuper = tree._super;
  97. try{
  98. tree._local = _local;
  99. tree._super = _super;
  100. return baseFunc.apply(tree, arguments);
  101. }finally{
  102. tree._local = prevLocal;
  103. tree._super = prevSuper;
  104. }
  105. };
  106. })(); // end of Immediate Function
  107. return proxy;
  108. }
  109. /**
  110. * Subclass `base` by creating proxy functions
  111. */
  112. function _subclassObject(tree, base, extension, extName){
  113. // $.ui.fancytree.debug("_subclassObject", tree, base, extension, extName);
  114. for(var attrName in extension){
  115. if(typeof extension[attrName] === "function"){
  116. if(typeof tree[attrName] === "function"){
  117. // override existing method
  118. tree[attrName] = _makeVirtualFunction(attrName, tree, base, extension, extName);
  119. }else if(attrName.charAt(0) === "_"){
  120. // Create private methods in tree.ext.EXTENSION namespace
  121. tree.ext[extName][attrName] = _makeVirtualFunction(attrName, tree, base, extension, extName);
  122. }else{
  123. $.error("Could not override tree." + attrName + ". Use prefix '_' to create tree." + extName + "._" + attrName);
  124. }
  125. }else{
  126. // Create member variables in tree.ext.EXTENSION namespace
  127. if(attrName !== "options"){
  128. tree.ext[extName][attrName] = extension[attrName];
  129. }
  130. }
  131. }
  132. }
  133. function _getResolvedPromise(context, argArray){
  134. if(context === undefined){
  135. return $.Deferred(function(){this.resolve();}).promise();
  136. }else{
  137. return $.Deferred(function(){this.resolveWith(context, argArray);}).promise();
  138. }
  139. }
  140. function _getRejectedPromise(context, argArray){
  141. if(context === undefined){
  142. return $.Deferred(function(){this.reject();}).promise();
  143. }else{
  144. return $.Deferred(function(){this.rejectWith(context, argArray);}).promise();
  145. }
  146. }
  147. function _makeResolveFunc(deferred, context){
  148. return function(){
  149. deferred.resolveWith(context);
  150. };
  151. }
  152. function _getElementDataAsDict($el){
  153. // Evaluate 'data-NAME' attributes with special treatment for 'data-json'.
  154. var d = $.extend({}, $el.data()),
  155. json = d.json;
  156. delete d.fancytree; // added to container by widget factory
  157. if( json ) {
  158. delete d.json;
  159. // <li data-json='...'> is already returned as object (http://api.jquery.com/data/#data-html5)
  160. d = $.extend(d, json);
  161. }
  162. return d;
  163. }
  164. // TODO: use currying
  165. function _makeNodeTitleMatcher(s){
  166. s = s.toLowerCase();
  167. return function(node){
  168. return node.title.toLowerCase().indexOf(s) >= 0;
  169. };
  170. }
  171. var i,
  172. FT = null, // initialized below
  173. ENTITY_MAP = {"&": "&amp;", "<": "&lt;", ">": "&gt;", "\"": "&quot;", "'": "&#39;", "/": "&#x2F;"},
  174. //boolean attributes that can be set with equivalent class names in the LI tags
  175. CLASS_ATTRS = "active expanded focus folder hideCheckbox lazy selected unselectable".split(" "),
  176. CLASS_ATTR_MAP = {},
  177. // Top-level Fancytree node attributes, that can be set by dict
  178. NODE_ATTRS = "expanded extraClasses folder hideCheckbox key lazy refKey selected title tooltip unselectable".split(" "),
  179. NODE_ATTR_MAP = {},
  180. // Attribute names that should NOT be added to node.data
  181. NONE_NODE_DATA_MAP = {"active": true, "children": true, "data": true, "focus": true};
  182. for(i=0; i<CLASS_ATTRS.length; i++){ CLASS_ATTR_MAP[CLASS_ATTRS[i]] = true; }
  183. for(i=0; i<NODE_ATTRS.length; i++){ NODE_ATTR_MAP[NODE_ATTRS[i]] = true; }
  184. /* *****************************************************************************
  185. * FancytreeNode
  186. */
  187. /**
  188. * Creates a new FancytreeNode
  189. *
  190. * @class FancytreeNode
  191. * @classdesc A FancytreeNode represents the hierarchical data model and operations.
  192. *
  193. * @param {FancytreeNode} parent
  194. * @param {NodeData} obj
  195. *
  196. * @property {Fancytree} tree The tree instance
  197. * @property {FancytreeNode} parent The parent node
  198. * @property {string} key Node id (must be unique inside the tree)
  199. * @property {string} title Display name (may contain HTML)
  200. * @property {object} data Contains all extra data that was passed on node creation
  201. * @property {FancytreeNode[] | null | undefined} children Array of child nodes.<br>
  202. * For lazy nodes, null or undefined means 'not yet loaded'. Use an empty array
  203. * to define a node that has no children.
  204. * @property {boolean} expanded Use isExpanded(), setExpanded() to access this property.
  205. * @property {string} extraClasses Addtional CSS classes, added to the node's `&lt;span>`
  206. * @property {boolean} folder Folder nodes have different default icons and click behavior.<br>
  207. * Note: Also non-folders may have children.
  208. * @property {string} statusNodeType null or type of temporarily generated system node like 'loading', or 'error'.
  209. * @property {boolean} lazy True if this node is loaded on demand, i.e. on first expansion.
  210. * @property {boolean} selected Use isSelected(), setSelected() to access this property.
  211. * @property {string} tooltip Alternative description used as hover banner
  212. */
  213. function FancytreeNode(parent, obj){
  214. var i, l, name, cl;
  215. this.parent = parent;
  216. this.tree = parent.tree;
  217. this.ul = null;
  218. this.li = null; // <li id='key' ftnode=this> tag
  219. this.statusNodeType = null; // if this is a temp. node to display the status of its parent
  220. this._isLoading = false; // if this node itself is loading
  221. this._error = null; // {message: '...'} if a load error occured
  222. this.data = {};
  223. // TODO: merge this code with node.toDict()
  224. // copy attributes from obj object
  225. for(i=0, l=NODE_ATTRS.length; i<l; i++){
  226. name = NODE_ATTRS[i];
  227. this[name] = obj[name];
  228. }
  229. // node.data += obj.data
  230. if(obj.data){
  231. $.extend(this.data, obj.data);
  232. }
  233. // copy all other attributes to this.data.NAME
  234. for(name in obj){
  235. if(!NODE_ATTR_MAP[name] && !$.isFunction(obj[name]) && !NONE_NODE_DATA_MAP[name]){
  236. // node.data.NAME = obj.NAME
  237. this.data[name] = obj[name];
  238. }
  239. }
  240. // Fix missing key
  241. if( this.key == null ){ // test for null OR undefined
  242. if( this.tree.options.defaultKey ) {
  243. this.key = this.tree.options.defaultKey(this);
  244. _assert(this.key, "defaultKey() must return a unique key");
  245. } else {
  246. this.key = "_" + (FT._nextNodeKey++);
  247. }
  248. } else {
  249. this.key = "" + this.key; // Convert to string (#217)
  250. }
  251. // Fix tree.activeNode
  252. // TODO: not elegant: we use obj.active as marker to set tree.activeNode
  253. // when loading from a dictionary.
  254. if(obj.active){
  255. _assert(this.tree.activeNode === null, "only one active node allowed");
  256. this.tree.activeNode = this;
  257. }
  258. if( obj.selected ){ // #186
  259. this.tree.lastSelectedNode = this;
  260. }
  261. // TODO: handle obj.focus = true
  262. // Create child nodes
  263. this.children = null;
  264. cl = obj.children;
  265. if(cl && cl.length){
  266. this._setChildren(cl);
  267. }
  268. // Add to key/ref map (except for root node)
  269. // if( parent ) {
  270. this.tree._callHook("treeRegisterNode", this.tree, true, this);
  271. // }
  272. }
  273. FancytreeNode.prototype = /** @lends FancytreeNode# */{
  274. /* Return the direct child FancytreeNode with a given key, index. */
  275. _findDirectChild: function(ptr){
  276. var i, l,
  277. cl = this.children;
  278. if(cl){
  279. if(typeof ptr === "string"){
  280. for(i=0, l=cl.length; i<l; i++){
  281. if(cl[i].key === ptr){
  282. return cl[i];
  283. }
  284. }
  285. }else if(typeof ptr === "number"){
  286. return this.children[ptr];
  287. }else if(ptr.parent === this){
  288. return ptr;
  289. }
  290. }
  291. return null;
  292. },
  293. // TODO: activate()
  294. // TODO: activateSilently()
  295. /* Internal helper called in recursive addChildren sequence.*/
  296. _setChildren: function(children){
  297. _assert(children && (!this.children || this.children.length === 0), "only init supported");
  298. this.children = [];
  299. for(var i=0, l=children.length; i<l; i++){
  300. this.children.push(new FancytreeNode(this, children[i]));
  301. }
  302. },
  303. /**
  304. * Append (or insert) a list of child nodes.
  305. *
  306. * @param {NodeData[]} children array of child node definitions (also single child accepted)
  307. * @param {FancytreeNode | string | Integer} [insertBefore] child node (or key or index of such).
  308. * If omitted, the new children are appended.
  309. * @returns {FancytreeNode} first child added
  310. *
  311. * @see FancytreeNode#applyPatch
  312. */
  313. addChildren: function(children, insertBefore){
  314. var i, l, pos,
  315. firstNode = null,
  316. nodeList = [];
  317. if($.isPlainObject(children) ){
  318. children = [children];
  319. }
  320. if(!this.children){
  321. this.children = [];
  322. }
  323. for(i=0, l=children.length; i<l; i++){
  324. nodeList.push(new FancytreeNode(this, children[i]));
  325. }
  326. firstNode = nodeList[0];
  327. if(insertBefore == null){
  328. this.children = this.children.concat(nodeList);
  329. }else{
  330. insertBefore = this._findDirectChild(insertBefore);
  331. pos = $.inArray(insertBefore, this.children);
  332. _assert(pos >= 0, "insertBefore must be an existing child");
  333. // insert nodeList after children[pos]
  334. this.children.splice.apply(this.children, [pos, 0].concat(nodeList));
  335. }
  336. if( !this.parent || this.parent.ul || this.tr ){
  337. // render if the parent was rendered (or this is a root node)
  338. this.render();
  339. }
  340. if( this.tree.options.selectMode === 3 ){
  341. this.fixSelection3FromEndNodes();
  342. }
  343. return firstNode;
  344. },
  345. /**
  346. * Append or prepend a node, or append a child node.
  347. *
  348. * This a convenience function that calls addChildren()
  349. *
  350. * @param {NodeData} node node definition
  351. * @param {string} [mode=child] 'before', 'after', or 'child' ('over' is a synonym for 'child')
  352. * @returns {FancytreeNode} new node
  353. */
  354. addNode: function(node, mode){
  355. if(mode === undefined || mode === "over"){
  356. mode = "child";
  357. }
  358. switch(mode){
  359. case "after":
  360. return this.getParent().addChildren(node, this.getNextSibling());
  361. case "before":
  362. return this.getParent().addChildren(node, this);
  363. case "child":
  364. case "over":
  365. return this.addChildren(node);
  366. }
  367. _assert(false, "Invalid mode: " + mode);
  368. },
  369. /**
  370. * Append new node after this.
  371. *
  372. * This a convenience function that calls addNode(node, 'after')
  373. *
  374. * @param {NodeData} node node definition
  375. * @returns {FancytreeNode} new node
  376. */
  377. appendSibling: function(node){
  378. return this.addNode(node, "after");
  379. },
  380. /**
  381. * Modify existing child nodes.
  382. *
  383. * @param {NodePatch} patch
  384. * @returns {$.Promise}
  385. * @see FancytreeNode#addChildren
  386. */
  387. applyPatch: function(patch) {
  388. // patch [key, null] means 'remove'
  389. if(patch === null){
  390. this.remove();
  391. return _getResolvedPromise(this);
  392. }
  393. // TODO: make sure that root node is not collapsed or modified
  394. // copy (most) attributes to node.ATTR or node.data.ATTR
  395. var name, promise, v,
  396. IGNORE_MAP = { children: true, expanded: true, parent: true }; // TODO: should be global
  397. for(name in patch){
  398. v = patch[name];
  399. if( !IGNORE_MAP[name] && !$.isFunction(v)){
  400. if(NODE_ATTR_MAP[name]){
  401. this[name] = v;
  402. }else{
  403. this.data[name] = v;
  404. }
  405. }
  406. }
  407. // Remove and/or create children
  408. if(patch.hasOwnProperty("children")){
  409. this.removeChildren();
  410. if(patch.children){ // only if not null and not empty list
  411. // TODO: addChildren instead?
  412. this._setChildren(patch.children);
  413. }
  414. // TODO: how can we APPEND or INSERT child nodes?
  415. }
  416. if(this.isVisible()){
  417. this.renderTitle();
  418. this.renderStatus();
  419. }
  420. // Expand collapse (final step, since this may be async)
  421. if(patch.hasOwnProperty("expanded")){
  422. promise = this.setExpanded(patch.expanded);
  423. }else{
  424. promise = _getResolvedPromise(this);
  425. }
  426. return promise;
  427. },
  428. /** Collapse all sibling nodes.
  429. * @returns {$.Promise}
  430. */
  431. collapseSiblings: function() {
  432. return this.tree._callHook("nodeCollapseSiblings", this);
  433. },
  434. /** Copy this node as sibling or child of `node`.
  435. *
  436. * @param {FancytreeNode} node source node
  437. * @param {string} mode 'before' | 'after' | 'child'
  438. * @param {Function} [map] callback function(NodeData) that could modify the new node
  439. * @returns {FancytreeNode} new
  440. */
  441. copyTo: function(node, mode, map) {
  442. return node.addNode(this.toDict(true, map), mode);
  443. },
  444. /** Count direct and indirect children.
  445. *
  446. * @param {boolean} [deep=true] pass 'false' to only count direct children
  447. * @returns {int} number of child nodes
  448. */
  449. countChildren: function(deep) {
  450. var cl = this.children, i, l, n;
  451. if( !cl ){
  452. return 0;
  453. }
  454. n = cl.length;
  455. if(deep !== false){
  456. for(i=0, l=n; i<l; i++){
  457. n += cl[i].countChildren();
  458. }
  459. }
  460. return n;
  461. },
  462. // TODO: deactivate()
  463. /** Write to browser console if debugLevel >= 2 (prepending node info)
  464. *
  465. * @param {*} msg string or object or array of such
  466. */
  467. debug: function(msg){
  468. if( this.tree.options.debugLevel >= 2 ) {
  469. Array.prototype.unshift.call(arguments, this.toString());
  470. consoleApply("debug", arguments);
  471. }
  472. },
  473. /** Deprecated.
  474. * @deprecated since 2014-02-16. Use resetLazy() instead.
  475. */
  476. discard: function(){
  477. this.warn("FancytreeNode.discard() is deprecated since 2014-02-16. Use .resetLazy() instead.");
  478. return this.resetLazy();
  479. },
  480. // TODO: expand(flag)
  481. /**Find all nodes that contain `match` in the title.
  482. *
  483. * @param {string | function(node)} match string to search for, of a function that
  484. * returns `true` if a node is matched.
  485. * @returns {FancytreeNode[]} array of nodes (may be empty)
  486. * @see FancytreeNode#findAll
  487. */
  488. findAll: function(match) {
  489. match = $.isFunction(match) ? match : _makeNodeTitleMatcher(match);
  490. var res = [];
  491. this.visit(function(n){
  492. if(match(n)){
  493. res.push(n);
  494. }
  495. });
  496. return res;
  497. },
  498. /**Find first node that contains `match` in the title (not including self).
  499. *
  500. * @param {string | function(node)} match string to search for, of a function that
  501. * returns `true` if a node is matched.
  502. * @returns {FancytreeNode} matching node or null
  503. * @example
  504. * <b>fat</b> text
  505. */
  506. findFirst: function(match) {
  507. match = $.isFunction(match) ? match : _makeNodeTitleMatcher(match);
  508. var res = null;
  509. this.visit(function(n){
  510. if(match(n)){
  511. res = n;
  512. return false;
  513. }
  514. });
  515. return res;
  516. },
  517. /* Apply selection state (internal use only) */
  518. _changeSelectStatusAttrs: function (state) {
  519. var changed = false;
  520. switch(state){
  521. case false:
  522. changed = ( this.selected || this.partsel );
  523. this.selected = false;
  524. this.partsel = false;
  525. break;
  526. case true:
  527. changed = ( !this.selected || !this.partsel );
  528. this.selected = true;
  529. this.partsel = true;
  530. break;
  531. case undefined:
  532. changed = ( this.selected || !this.partsel );
  533. this.selected = false;
  534. this.partsel = true;
  535. break;
  536. default:
  537. _assert(false, "invalid state: " + state);
  538. }
  539. // this.debug("fixSelection3AfterLoad() _changeSelectStatusAttrs()", state, changed);
  540. if( changed ){
  541. this.renderStatus();
  542. }
  543. return changed;
  544. },
  545. /**
  546. * Fix selection status, after this node was (de)selected in multi-hier mode.
  547. * This includes (de)selecting all children.
  548. */
  549. fixSelection3AfterClick: function() {
  550. var flag = this.isSelected();
  551. // this.debug("fixSelection3AfterClick()");
  552. this.visit(function(node){
  553. node._changeSelectStatusAttrs(flag);
  554. });
  555. this.fixSelection3FromEndNodes();
  556. },
  557. /**
  558. * Fix selection status for multi-hier mode.
  559. * Only end-nodes are considered to update the descendants branch and parents.
  560. * Should be called after this node has loaded new children or after
  561. * children have been modified using the API.
  562. */
  563. fixSelection3FromEndNodes: function() {
  564. // this.debug("fixSelection3FromEndNodes()");
  565. _assert(this.tree.options.selectMode === 3, "expected selectMode 3");
  566. // Visit all end nodes and adjust their parent's `selected` and `partsel`
  567. // attributes. Return selection state true, false, or undefined.
  568. function _walk(node){
  569. var i, l, child, s, state, allSelected,someSelected,
  570. children = node.children;
  571. if( children && children.length ){
  572. // check all children recursively
  573. allSelected = true;
  574. someSelected = false;
  575. for( i=0, l=children.length; i<l; i++ ){
  576. child = children[i];
  577. // the selection state of a node is not relevant; we need the end-nodes
  578. s = _walk(child);
  579. if( s !== false ) {
  580. someSelected = true;
  581. }
  582. if( s !== true ) {
  583. allSelected = false;
  584. }
  585. }
  586. state = allSelected ? true : (someSelected ? undefined : false);
  587. }else{
  588. // This is an end-node: simply report the status
  589. // state = ( node.unselectable ) ? undefined : !!node.selected;
  590. state = !!node.selected;
  591. }
  592. node._changeSelectStatusAttrs(state);
  593. return state;
  594. }
  595. _walk(this);
  596. // Update parent's state
  597. this.visitParents(function(node){
  598. var i, l, child, state,
  599. children = node.children,
  600. allSelected = true,
  601. someSelected = false;
  602. for( i=0, l=children.length; i<l; i++ ){
  603. child = children[i];
  604. // When fixing the parents, we trust the sibling status (i.e.
  605. // we don't recurse)
  606. if( child.selected || child.partsel ) {
  607. someSelected = true;
  608. }
  609. if( !child.unselectable && !child.selected ) {
  610. allSelected = false;
  611. }
  612. }
  613. state = allSelected ? true : (someSelected ? undefined : false);
  614. node._changeSelectStatusAttrs(state);
  615. });
  616. },
  617. // TODO: focus()
  618. /**
  619. * Update node data. If dict contains 'children', then also replace
  620. * the hole sub tree.
  621. * @param {NodeData} dict
  622. *
  623. * @see FancytreeNode#addChildren
  624. * @see FancytreeNode#applyPatch
  625. */
  626. fromDict: function(dict) {
  627. // copy all other attributes to this.data.xxx
  628. for(var name in dict){
  629. if(NODE_ATTR_MAP[name]){
  630. // node.NAME = dict.NAME
  631. this[name] = dict[name];
  632. }else if(name === "data"){
  633. // node.data += dict.data
  634. $.extend(this.data, dict.data);
  635. }else if(!$.isFunction(dict[name]) && !NONE_NODE_DATA_MAP[name]){
  636. // node.data.NAME = dict.NAME
  637. this.data[name] = dict[name];
  638. }
  639. }
  640. if(dict.children){
  641. // recursively set children and render
  642. this.removeChildren();
  643. this.addChildren(dict.children);
  644. }
  645. this.renderTitle();
  646. /*
  647. var children = dict.children;
  648. if(children === undefined){
  649. this.data = $.extend(this.data, dict);
  650. this.render();
  651. return;
  652. }
  653. dict = $.extend({}, dict);
  654. dict.children = undefined;
  655. this.data = $.extend(this.data, dict);
  656. this.removeChildren();
  657. this.addChild(children);
  658. */
  659. },
  660. /** Return the list of child nodes (undefined for unexpanded lazy nodes).
  661. * @returns {FancytreeNode[] | undefined}
  662. */
  663. getChildren: function() {
  664. if(this.hasChildren() === undefined){ // TODO: only required for lazy nodes?
  665. return undefined; // Lazy node: unloaded, currently loading, or load error
  666. }
  667. return this.children;
  668. },
  669. /** Return the first child node or null.
  670. * @returns {FancytreeNode | null}
  671. */
  672. getFirstChild: function() {
  673. return this.children ? this.children[0] : null;
  674. },
  675. /** Return the 0-based child index.
  676. * @returns {int}
  677. */
  678. getIndex: function() {
  679. // return this.parent.children.indexOf(this);
  680. return $.inArray(this, this.parent.children); // indexOf doesn't work in IE7
  681. },
  682. /** Return the hierarchical child index (1-based, e.g. '3.2.4').
  683. * @returns {string}
  684. */
  685. getIndexHier: function(separator) {
  686. separator = separator || ".";
  687. var res = [];
  688. $.each(this.getParentList(false, true), function(i, o){
  689. res.push(o.getIndex() + 1);
  690. });
  691. return res.join(separator);
  692. },
  693. /** Return the parent keys separated by options.keyPathSeparator, e.g. "id_1/id_17/id_32".
  694. * @param {boolean} [excludeSelf=false]
  695. * @returns {string}
  696. */
  697. getKeyPath: function(excludeSelf) {
  698. var path = [],
  699. sep = this.tree.options.keyPathSeparator;
  700. this.visitParents(function(n){
  701. if(n.parent){
  702. path.unshift(n.key);
  703. }
  704. }, !excludeSelf);
  705. return sep + path.join(sep);
  706. },
  707. /** Return the last child of this node or null.
  708. * @returns {FancytreeNode | null}
  709. */
  710. getLastChild: function() {
  711. return this.children ? this.children[this.children.length - 1] : null;
  712. },
  713. /** Return node depth. 0: System root node, 1: visible top-level node, 2: first sub-level, ... .
  714. * @returns {int}
  715. */
  716. getLevel: function() {
  717. var level = 0,
  718. dtn = this.parent;
  719. while( dtn ) {
  720. level++;
  721. dtn = dtn.parent;
  722. }
  723. return level;
  724. },
  725. /** Return the successor node (under the same parent) or null.
  726. * @returns {FancytreeNode | null}
  727. */
  728. getNextSibling: function() {
  729. // TODO: use indexOf, if available: (not in IE6)
  730. if( this.parent ){
  731. var i, l,
  732. ac = this.parent.children;
  733. for(i=0, l=ac.length-1; i<l; i++){ // up to length-2, so next(last) = null
  734. if( ac[i] === this ){
  735. return ac[i+1];
  736. }
  737. }
  738. }
  739. return null;
  740. },
  741. /** Return the parent node (null for the system root node).
  742. * @returns {FancytreeNode | null}
  743. */
  744. getParent: function() {
  745. // TODO: return null for top-level nodes?
  746. return this.parent;
  747. },
  748. /** Return an array of all parent nodes (top-down).
  749. * @param {boolean} [includeRoot=false] Include the invisible system root node.
  750. * @param {boolean} [includeSelf=false] Include the node itself.
  751. * @returns {FancytreeNode[]}
  752. */
  753. getParentList: function(includeRoot, includeSelf) {
  754. var l = [],
  755. dtn = includeSelf ? this : this.parent;
  756. while( dtn ) {
  757. if( includeRoot || dtn.parent ){
  758. l.unshift(dtn);
  759. }
  760. dtn = dtn.parent;
  761. }
  762. return l;
  763. },
  764. /** Return the predecessor node (under the same parent) or null.
  765. * @returns {FancytreeNode | null}
  766. */
  767. getPrevSibling: function() {
  768. if( this.parent ){
  769. var i, l,
  770. ac = this.parent.children;
  771. for(i=1, l=ac.length; i<l; i++){ // start with 1, so prev(first) = null
  772. if( ac[i] === this ){
  773. return ac[i-1];
  774. }
  775. }
  776. }
  777. return null;
  778. },
  779. /** Return true if node has children. Return undefined if not sure, i.e. the node is lazy and not yet loaded).
  780. * @returns {boolean | undefined}
  781. */
  782. hasChildren: function() {
  783. if(this.lazy){
  784. if(this.children == null ){
  785. // null or undefined: Not yet loaded
  786. return undefined;
  787. }else if(this.children.length === 0){
  788. // Loaded, but response was empty
  789. return false;
  790. }else if(this.children.length === 1 && this.children[0].isStatusNode() ){
  791. // Currently loading or load error
  792. return undefined;
  793. }
  794. return true;
  795. }
  796. return !!this.children;
  797. },
  798. /** Return true if node has keyboard focus.
  799. * @returns {boolean}
  800. */
  801. hasFocus: function() {
  802. return (this.tree.hasFocus() && this.tree.focusNode === this);
  803. },
  804. /** Write to browser console if debugLevel >= 1 (prepending node info)
  805. *
  806. * @param {*} msg string or object or array of such
  807. */
  808. info: function(msg){
  809. if( this.tree.options.debugLevel >= 1 ) {
  810. Array.prototype.unshift.call(arguments, this.toString());
  811. consoleApply("info", arguments);
  812. }
  813. },
  814. /** Return true if node is active (see also FancytreeNode#isSelected).
  815. * @returns {boolean}
  816. */
  817. isActive: function() {
  818. return (this.tree.activeNode === this);
  819. },
  820. /** Return true if node is a direct child of otherNode.
  821. * @param {FancytreeNode} otherNode
  822. * @returns {boolean}
  823. */
  824. isChildOf: function(otherNode) {
  825. return (this.parent && this.parent === otherNode);
  826. },
  827. /** Return true, if node is a direct or indirect sub node of otherNode.
  828. * @param {FancytreeNode} otherNode
  829. * @returns {boolean}
  830. */
  831. isDescendantOf: function(otherNode) {
  832. if(!otherNode || otherNode.tree !== this.tree){
  833. return false;
  834. }
  835. var p = this.parent;
  836. while( p ) {
  837. if( p === otherNode ){
  838. return true;
  839. }
  840. p = p.parent;
  841. }
  842. return false;
  843. },
  844. /** Return true if node is expanded.
  845. * @returns {boolean}
  846. */
  847. isExpanded: function() {
  848. return !!this.expanded;
  849. },
  850. /** Return true if node is the first node of its parent's children.
  851. * @returns {boolean}
  852. */
  853. isFirstSibling: function() {
  854. var p = this.parent;
  855. return !p || p.children[0] === this;
  856. },
  857. /** Return true if node is a folder, i.e. has the node.folder attribute set.
  858. * @returns {boolean}
  859. */
  860. isFolder: function() {
  861. return !!this.folder;
  862. },
  863. /** Return true if node is the last node of its parent's children.
  864. * @returns {boolean}
  865. */
  866. isLastSibling: function() {
  867. var p = this.parent;
  868. return !p || p.children[p.children.length-1] === this;
  869. },
  870. /** Return true if node is lazy (even if data was already loaded)
  871. * @returns {boolean}
  872. */
  873. isLazy: function() {
  874. return !!this.lazy;
  875. },
  876. /** Return true if node is lazy and loaded. For non-lazy nodes always return true.
  877. * @returns {boolean}
  878. */
  879. isLoaded: function() {
  880. return !this.lazy || this.hasChildren() !== undefined; // Also checks if the only child is a status node
  881. },
  882. /** Return true if children are currently beeing loaded, i.e. a Ajax request is pending.
  883. * @returns {boolean}
  884. */
  885. isLoading: function() {
  886. return !!this._isLoading;
  887. },
  888. /** Return true if this is the (invisible) system root node.
  889. * @returns {boolean}
  890. */
  891. isRoot: function() {
  892. return (this.tree.rootNode === this);
  893. },
  894. /** Return true if node is selected, i.e. has a checkmark set (see also FancytreeNode#isActive).
  895. * @returns {boolean}
  896. */
  897. isSelected: function() {
  898. return !!this.selected;
  899. },
  900. /** Return true if this node is a temporarily generated system node like
  901. * 'loading', or 'error' (node.statusNodeType contains the type).
  902. * @returns {boolean}
  903. */
  904. isStatusNode: function() {
  905. return !!this.statusNodeType;
  906. },
  907. /** Return true if node is lazy and not yet loaded. For non-lazy nodes always return false.
  908. * @returns {boolean}
  909. */
  910. isUndefined: function() {
  911. return this.hasChildren() === undefined; // also checks if the only child is a status node
  912. },
  913. /** Return true if all parent nodes are expanded. Note: this does not check
  914. * whether the node is scrolled into the visible part of the screen.
  915. * @returns {boolean}
  916. */
  917. isVisible: function() {
  918. var i, l,
  919. parents = this.getParentList(false, false);
  920. for(i=0, l=parents.length; i<l; i++){
  921. if( ! parents[i].expanded ){ return false; }
  922. }
  923. return true;
  924. },
  925. /** Deprecated.
  926. * @deprecated since 2014-02-16: use load() instead.
  927. */
  928. lazyLoad: function(discard) {
  929. this.warn("FancytreeNode.lazyLoad() is deprecated since 2014-02-16. Use .load() instead.");
  930. return this.load(discard);
  931. },
  932. /**
  933. * Load all children of a lazy node.
  934. * @param {boolean} [forceReload=false] Pass true to discard any existing nodes before.
  935. * @returns {$.Promise}
  936. */
  937. load: function(forceReload) {
  938. var res, source,
  939. that = this;
  940. _assert( this.isLazy(), "load() requires a lazy node" );
  941. _assert( forceReload || this.isUndefined(), "Pass forceReload=true to re-load a lazy node" );
  942. if( this.isLoaded() ){
  943. this.resetLazy(); // also collapses
  944. }
  945. // This method is also called by setExpanded() and loadKeyPath(), so we
  946. // have to avoid recursion.
  947. source = this.tree._triggerNodeEvent("lazyLoad", this);
  948. if( source === false ) { // #69
  949. return _getResolvedPromise(this);
  950. }
  951. _assert(typeof source !== "boolean", "lazyLoad event must return source in data.result");
  952. res = this.tree._callHook("nodeLoadChildren", this, source);
  953. if( this.expanded ) {
  954. res.always(function(){
  955. that.render();
  956. });
  957. }
  958. return res;
  959. },
  960. /** Expand all parents and optionally scroll into visible area as neccessary.
  961. * Promise is resolved, when lazy loading and animations are done.
  962. * @param {object} [opts] passed to `setExpanded()`.
  963. * Defaults to {noAnimation: false, noEvents: false, scrollIntoView: true}
  964. * @returns {$.Promise}
  965. */
  966. makeVisible: function(opts) {
  967. var i,
  968. that = this,
  969. deferreds = [],
  970. dfd = new $.Deferred(),
  971. parents = this.getParentList(false, false),
  972. len = parents.length,
  973. effects = !(opts && opts.noAnimation === true),
  974. scroll = !(opts && opts.scrollIntoView === false);
  975. // Expand bottom-up, so only the top node is animated
  976. for(i = len - 1; i >= 0; i--){
  977. // that.debug("pushexpand" + parents[i]);
  978. deferreds.push(parents[i].setExpanded(true, opts));
  979. }
  980. $.when.apply($, deferreds).done(function(){
  981. // All expands have finished
  982. // that.debug("expand DONE", scroll);
  983. if( scroll ){
  984. that.scrollIntoView(effects).done(function(){
  985. // that.debug("scroll DONE");
  986. dfd.resolve();
  987. });
  988. } else {
  989. dfd.resolve();
  990. }
  991. });
  992. return dfd.promise();
  993. },
  994. /** Move this node to targetNode.
  995. * @param {FancytreeNode} targetNode
  996. * @param {string} mode <pre>
  997. * 'child': append this node as last child of targetNode.
  998. * This is the default. To be compatble with the D'n'd
  999. * hitMode, we also accept 'over'.
  1000. * 'before': add this node as sibling before targetNode.
  1001. * 'after': add this node as sibling after targetNode.</pre>
  1002. * @param {function} [map] optional callback(FancytreeNode) to allow modifcations
  1003. */
  1004. moveTo: function(targetNode, mode, map) {
  1005. if(mode === undefined || mode === "over"){
  1006. mode = "child";
  1007. }
  1008. var pos,
  1009. prevParent = this.parent,
  1010. targetParent = (mode === "child") ? targetNode : targetNode.parent;
  1011. if(this === targetNode){
  1012. return;
  1013. }else if( !this.parent ){
  1014. throw "Cannot move system root";
  1015. }else if( targetParent.isDescendantOf(this) ){
  1016. throw "Cannot move a node to its own descendant";
  1017. }
  1018. // Unlink this node from current parent
  1019. if( this.parent.children.length === 1 ) {
  1020. this.parent.children = this.parent.lazy ? [] : null;
  1021. this.parent.expanded = false;
  1022. } else {
  1023. pos = $.inArray(this, this.parent.children);
  1024. _assert(pos >= 0);
  1025. this.parent.children.splice(pos, 1);
  1026. }
  1027. // Remove from source DOM parent
  1028. // if(this.parent.ul){
  1029. // this.parent.ul.removeChild(this.li);
  1030. // }
  1031. // Insert this node to target parent's child list
  1032. this.parent = targetParent;
  1033. if( targetParent.hasChildren() ) {
  1034. switch(mode) {
  1035. case "child":
  1036. // Append to existing target children
  1037. targetParent.children.push(this);
  1038. break;
  1039. case "before":
  1040. // Insert this node before target node
  1041. pos = $.inArray(targetNode, targetParent.children);
  1042. _assert(pos >= 0);
  1043. targetParent.children.splice(pos, 0, this);
  1044. break;
  1045. case "after":
  1046. // Insert this node after target node
  1047. pos = $.inArray(targetNode, targetParent.children);
  1048. _assert(pos >= 0);
  1049. targetParent.children.splice(pos+1, 0, this);
  1050. break;
  1051. default:
  1052. throw "Invalid mode " + mode;
  1053. }
  1054. } else {
  1055. targetParent.children = [ this ];
  1056. }
  1057. // Parent has no <ul> tag yet:
  1058. // if( !targetParent.ul ) {
  1059. // // This is the parent's first child: create UL tag
  1060. // // (Hidden, because it will be
  1061. // targetParent.ul = document.createElement("ul");
  1062. // targetParent.ul.style.display = "none";
  1063. // targetParent.li.appendChild(targetParent.ul);
  1064. // }
  1065. // // Issue 319: Add to target DOM parent (only if node was already rendered(expanded))
  1066. // if(this.li){
  1067. // targetParent.ul.appendChild(this.li);
  1068. // }^
  1069. // Let caller modify the nodes
  1070. if( map ){
  1071. targetNode.visit(map, true);
  1072. }
  1073. // Handle cross-tree moves
  1074. if( this.tree !== targetNode.tree ) {
  1075. // Fix node.tree for all source nodes
  1076. // _assert(false, "Cross-tree move is not yet implemented.");
  1077. this.warn("Cross-tree moveTo is experimantal!");
  1078. this.visit(function(n){
  1079. // TODO: fix selection state and activation, ...
  1080. n.tree = targetNode.tree;
  1081. }, true);
  1082. }
  1083. // A collaposed node won't re-render children, so we have to remove it manually
  1084. // if( !targetParent.expanded ){
  1085. // prevParent.ul.removeChild(this.li);
  1086. // }
  1087. // Update HTML markup
  1088. if( !prevParent.isDescendantOf(targetParent)) {
  1089. prevParent.render();
  1090. }
  1091. if( !targetParent.isDescendantOf(prevParent) && targetParent !== prevParent) {
  1092. targetParent.render();
  1093. }
  1094. // TODO: fix selection state
  1095. // TODO: fix active state
  1096. /*
  1097. var tree = this.tree;
  1098. var opts = tree.options;
  1099. var pers = tree.persistence;
  1100. // Always expand, if it's below minExpandLevel
  1101. // tree.logDebug ("%s._addChildNode(%o), l=%o", this, ftnode, ftnode.getLevel());
  1102. if ( opts.minExpandLevel >= ftnode.getLevel() ) {
  1103. // tree.logDebug ("Force expand for %o", ftnode);
  1104. this.bExpanded = true;
  1105. }
  1106. // In multi-hier mode, update the parents selection state
  1107. // DT issue #82: only if not initializing, because the children may not exist yet
  1108. // if( !ftnode.data.isStatusNode() && opts.selectMode==3 && !isInitializing )
  1109. // ftnode._fixSelectionState();
  1110. // In multi-hier mode, update the parents selection state
  1111. if( ftnode.bSelected && opts.selectMode==3 ) {
  1112. var p = this;
  1113. while( p ) {
  1114. if( !p.hasSubSel )
  1115. p._setSubSel(true);
  1116. p = p.parent;
  1117. }
  1118. }
  1119. // render this node and the new child
  1120. if ( tree.bEnableUpdate )
  1121. this.render();
  1122. return ftnode;
  1123. */
  1124. },
  1125. /** Set focus relative to this node and optionally activate.
  1126. *
  1127. * @param {number} where The keyCode that would normally trigger this move,
  1128. * e.g. `$.ui.keyCode.LEFT` would collapse the node if it
  1129. * is expanded or move to the parent oterwise.
  1130. * @param {boolean} [activate=true]
  1131. * @returns {$.Promise}
  1132. */
  1133. navigate: function(where, activate) {
  1134. var i, parents,
  1135. handled = true,
  1136. KC = $.ui.keyCode,
  1137. sib = null;
  1138. // Navigate to node
  1139. function _goto(n){
  1140. if( n ){
  1141. try { n.makeVisible(); } catch(e) {} // #272
  1142. // Node may still be hidden by a filter
  1143. if( ! $(n.span).is(":visible") ) {
  1144. n.debug("Navigate: skipping hidden node");
  1145. n.navigate(where, activate);
  1146. return;
  1147. }
  1148. return activate === false ? n.setFocus() : n.setActive();
  1149. }
  1150. }
  1151. switch( where ) {
  1152. case KC.BACKSPACE:
  1153. if( this.parent && this.parent.parent ) {
  1154. _goto(this.parent);
  1155. }
  1156. break;
  1157. case KC.LEFT:
  1158. if( this.expanded ) {
  1159. this.setExpanded(false);
  1160. _goto(this);
  1161. } else if( this.parent && this.parent.parent ) {
  1162. _goto(this.parent);
  1163. }
  1164. break;
  1165. case KC.RIGHT:
  1166. if( !this.expanded && (this.children || this.lazy) ) {
  1167. this.setExpanded();
  1168. _goto(this);
  1169. } else if( this.children && this.children.length ) {
  1170. _goto(this.children[0]);
  1171. }
  1172. break;
  1173. case KC.UP:
  1174. sib = this.getPrevSibling();
  1175. while( sib && sib.expanded && sib.children && sib.children.length ){
  1176. sib = sib.children[sib.children.length - 1];
  1177. }
  1178. if( !sib && this.parent && this.parent.parent ){
  1179. sib = this.parent;
  1180. }
  1181. _goto(sib);
  1182. break;
  1183. case KC.DOWN:
  1184. if( this.expanded && this.children && this.children.length ) {
  1185. sib = this.children[0];
  1186. } else {
  1187. parents = this.getParentList(false, true);
  1188. for(i=parents.length-1; i>=0; i--) {
  1189. sib = parents[i].getNextSibling();
  1190. if( sib ){ break; }
  1191. }
  1192. }
  1193. _goto(sib);
  1194. break;
  1195. default:
  1196. handled = false;
  1197. }
  1198. },
  1199. /**
  1200. * Remove this node (not allowed for system root).
  1201. */
  1202. remove: function() {
  1203. return this.parent.removeChild(this);
  1204. },
  1205. /**
  1206. * Remove childNode from list of direct children.
  1207. * @param {FancytreeNode} childNode
  1208. */
  1209. removeChild: function(childNode) {
  1210. return this.tree._callHook("nodeRemoveChild", this, childNode);
  1211. },
  1212. /**
  1213. * Remove all child nodes and descendents. This converts the node into a leaf.<br>
  1214. * If this was a lazy node, it is still considered 'loaded'; call node.resetLazy()
  1215. * in order to trigger lazyLoad on next expand.
  1216. */
  1217. removeChildren: function() {
  1218. return this.tree._callHook("nodeRemoveChildren", this);
  1219. },
  1220. /**
  1221. * This method renders and updates all HTML markup that is required
  1222. * to display this node in its current state.<br>
  1223. * Note:
  1224. * <ul>
  1225. * <li>It should only be neccessary to call this method after the node object
  1226. * was modified by direct access to its properties, because the common
  1227. * API methods (node.setTitle(), moveTo(), addChildren(), remove(), ...)
  1228. * already handle this.
  1229. * <li> {@link FancytreeNode#renderTitle} and {@link FancytreeNode#renderStatus}
  1230. * are implied. If changes are more local, calling only renderTitle() or
  1231. * renderStatus() may be sufficient and faster.
  1232. * <li>If a node was created/removed, node.render() must be called <i>on the parent</i>.
  1233. * </ul>
  1234. *
  1235. * @param {boolean} [force=false] re-render, even if html markup was already created
  1236. * @param {boolean} [deep=false] also render all descendants, even if parent is collapsed
  1237. */
  1238. render: function(force, deep) {
  1239. return this.tree._callHook("nodeRender", this, force, deep);
  1240. },
  1241. /** Create HTML markup for the node's outer <span> (expander, checkbox, icon, and title).
  1242. * @see Fancytree_Hooks#nodeRenderTitle
  1243. */
  1244. renderTitle: function() {
  1245. return this.tree._callHook("nodeRenderTitle", this);
  1246. },
  1247. /** Update element's CSS classes according to node state.
  1248. * @see Fancytree_Hooks#nodeRenderStatus
  1249. */
  1250. renderStatus: function() {
  1251. return this.tree._callHook("nodeRenderStatus", this);
  1252. },
  1253. /**
  1254. * Remove all children, collapse, and set the lazy-flag, so that the lazyLoad
  1255. * event is triggered on next expand.
  1256. */
  1257. resetLazy: function() {
  1258. this.removeChildren();
  1259. this.expanded = false;
  1260. this.lazy = true;
  1261. this.children = undefined;
  1262. this.renderStatus();
  1263. },
  1264. /** Schedule activity for delayed execution (cancel any pending request).
  1265. * scheduleAction('cancel') will only cancel a pending request (if any).
  1266. * @param {string} mode
  1267. * @param {number} ms
  1268. */
  1269. scheduleAction: function(mode, ms) {
  1270. if( this.tree.timer ) {
  1271. clearTimeout(this.tree.timer);
  1272. // this.tree.debug("clearTimeout(%o)", this.tree.timer);
  1273. }
  1274. this.tree.timer = null;
  1275. var self = this; // required for closures
  1276. switch (mode) {
  1277. case "cancel":
  1278. // Simply made sure that timer was cleared
  1279. break;
  1280. case "expand":
  1281. this.tree.timer = setTimeout(function(){
  1282. self.tree.debug("setTimeout: trigger expand");
  1283. self.setExpanded(true);
  1284. }, ms);
  1285. break;
  1286. case "activate":
  1287. this.tree.timer = setTimeout(function(){
  1288. self.tree.debug("setTimeout: trigger activate");
  1289. self.setActive(true);
  1290. }, ms);
  1291. break;
  1292. default:
  1293. throw "Invalid mode " + mode;
  1294. }
  1295. // this.tree.debug("setTimeout(%s, %s): %s", mode, ms, this.tree.timer);
  1296. },
  1297. /**
  1298. *
  1299. * @param {boolean | PlainObject} [effects=false] animation options.
  1300. * @param {object} [options=null] {topNode: null, effects: ..., parent: ...} this node will remain visible in
  1301. * any case, even if `this` is outside the scroll pane.
  1302. * @returns {$.Promise}
  1303. */
  1304. scrollIntoView: function(effects, options) {
  1305. if( options !== undefined && _isNode(options) ) {
  1306. this.warn("scrollIntoView() with 'topNode' option is deprecated since 2014-05-08. Use 'options.topNode' instead.");
  1307. options = {topNode: options};
  1308. }
  1309. // this.$scrollParent = (this.options.scrollParent === "auto") ? $ul.scrollParent() : $(this.options.scrollParent);
  1310. // this.$scrollParent = this.$scrollParent.length ? this.$scrollParent || this.$container;
  1311. var topNodeY, nodeY, horzScrollbarHeight, containerOffsetTop,
  1312. opts = $.extend({
  1313. effects: (effects === true) ? {duration: 200, queue: false} : effects,
  1314. scrollOfs: this.tree.options.scrollOfs,
  1315. scrollParent: this.tree.options.scrollParent || this.tree.$container,
  1316. topNode: null
  1317. }, options),
  1318. dfd = new $.Deferred(),
  1319. that = this,
  1320. nodeHeight = $(this.span).height(),
  1321. $container = $(opts.scrollParent),
  1322. topOfs = opts.scrollOfs.top || 0,
  1323. bottomOfs = opts.scrollOfs.bottom || 0,
  1324. containerHeight = $container.height(),// - topOfs - bottomOfs,
  1325. scrollTop = $container.scrollTop(),
  1326. $animateTarget = $container,
  1327. isParentWindow = $container[0] === window,
  1328. topNode = opts.topNode || null,
  1329. newScrollTop = null;
  1330. // this.debug("scrollIntoView(), scrollTop=", scrollTop, opts.scrollOfs);
  1331. _assert($(this.span).is(":visible"), "scrollIntoView node is invisible"); // otherwise we cannot calc offsets
  1332. if( isParentWindow ) {
  1333. nodeY = $(this.span).offset().top;
  1334. topNodeY = topNode ? $(topNode.span).offset().top : 0;
  1335. $animateTarget = $("html,body");
  1336. } else {
  1337. _assert($container[0] !== document && $container[0] !== document.body, "scrollParent should be an simple element or `window`, not document or body.");
  1338. containerOffsetTop = $container.offset().top,
  1339. nodeY = $(this.span).offset().top - containerOffsetTop + scrollTop; // relative to scroll parent
  1340. topNodeY = topNode ? $(topNode.span).offset().top - containerOffsetTop + scrollTop : 0;
  1341. horzScrollbarHeight = Math.max(0, ($container.innerHeight() - $container[0].clientHeight));
  1342. containerHeight -= horzScrollbarHeight;
  1343. }
  1344. // this.debug(" scrollIntoView(), nodeY=", nodeY, "containerHeight=", containerHeight);
  1345. if( nodeY < (scrollTop + topOfs) ){
  1346. // Node is above visible container area
  1347. newScrollTop = nodeY - topOfs;
  1348. // this.debug(" scrollIntoView(), UPPER newScrollTop=", newScrollTop);
  1349. }else if((nodeY + nodeHeight) > (scrollTop + containerHeight - bottomOfs)){
  1350. newScrollTop = nodeY + nodeHeight - containerHeight + bottomOfs;
  1351. // this.debug(" scrollIntoView(), LOWER newScrollTop=", newScrollTop);
  1352. // If a topNode was passed, make sure that it is never scrolled
  1353. // outside the upper border
  1354. if(topNode){
  1355. _assert($(topNode.span).is(":visible"));
  1356. if( topNodeY < newScrollTop ){
  1357. newScrollTop = topNodeY - topOfs;
  1358. // this.debug(" scrollIntoView(), TOP newScrollTop=", newScrollTop);
  1359. }
  1360. }
  1361. }
  1362. if(newScrollTop !== null){
  1363. // this.debug(" scrollIntoView(), SET newScrollTop=", newScrollTop);
  1364. if(opts.effects){
  1365. opts.effects.complete = function(){
  1366. dfd.resolveWith(that);
  1367. };
  1368. $animateTarget.stop(true).animate({
  1369. scrollTop: newScrollTop
  1370. }, opts.effects);
  1371. }else{
  1372. $animateTarget[0].scrollTop = newScrollTop;
  1373. dfd.resolveWith(this);
  1374. }
  1375. }else{
  1376. dfd.resolveWith(this);
  1377. }
  1378. return dfd.promise();
  1379. },
  1380. /**Activate this node.
  1381. * @param {boolean} [flag=true] pass false to deactivate
  1382. * @param {object} [opts] additional options. Defaults to {noEvents: false}
  1383. */
  1384. setActive: function(flag, opts){
  1385. return this.tree._callHook("nodeSetActive", this, flag, opts);
  1386. },
  1387. /**Expand or collapse this node. Promise is resolved, when lazy loading and animations are done.
  1388. * @param {boolean} [flag=true] pass false to collapse
  1389. * @param {object} [opts] additional options. Defaults to {noAnimation: false, noEvents: false}
  1390. * @returns {$.Promise}
  1391. */
  1392. setExpanded: function(flag, opts){
  1393. return this.tree._callHook("nodeSetExpanded", this, flag, opts);
  1394. },
  1395. /**Set keyboard focus to this node.
  1396. * @param {boolean} [flag=true] pass false to blur
  1397. * @see Fancytree#setFocus
  1398. */
  1399. setFocus: function(flag){
  1400. return this.tree._callHook("nodeSetFocus", this, flag);
  1401. },
  1402. // TODO: setLazyNodeStatus
  1403. /**Select this node, i.e. check the checkbox.
  1404. * @param {boolean} [flag=true] pass false to deselect
  1405. */
  1406. setSelected: function(flag){
  1407. return this.tree._callHook("nodeSetSelected", this, flag);
  1408. },
  1409. /**Rename this node.
  1410. * @param {string} title
  1411. */
  1412. setTitle: function(title){
  1413. this.title = title;
  1414. this.renderTitle();
  1415. },
  1416. /**Sort child list by title.
  1417. * @param {function} [cmp] custom compare function(a, b) that returns -1, 0, or 1 (defaults to sort by title).
  1418. * @param {boolean} [deep=false] pass true to sort all descendant nodes
  1419. */
  1420. sortChildren: function(cmp, deep) {
  1421. var i,l,
  1422. cl = this.children;
  1423. if( !cl ){
  1424. return;
  1425. }
  1426. cmp = cmp || function(a, b) {
  1427. var x = a.title.toLowerCase(),
  1428. y = b.title.toLowerCase();
  1429. return x === y ? 0 : x > y ? 1 : -1;
  1430. };
  1431. cl.sort(cmp);
  1432. if( deep ){
  1433. for(i=0, l=cl.length; i<l; i++){
  1434. if( cl[i].children ){
  1435. cl[i].sortChildren(cmp, "$norender$");
  1436. }
  1437. }
  1438. }
  1439. if( deep !== "$norender$" ){
  1440. this.render();
  1441. }
  1442. },
  1443. /** Convert node (or whole branch) into a plain object.
  1444. *
  1445. * The result is compatible with node.addChildren().
  1446. *
  1447. * @param {boolean} recursive
  1448. * @param {function} callback callback(dict) is called for every node, in order to allow modifications
  1449. * @returns {NodeData}
  1450. */
  1451. toDict: function(recursive, callback) {
  1452. var i, l, node,
  1453. dict = {},
  1454. self = this;
  1455. $.each(NODE_ATTRS, function(i, a){
  1456. if(self[a] || self[a] === false){
  1457. dict[a] = self[a];
  1458. }
  1459. });
  1460. if(!$.isEmptyObject(this.data)){
  1461. dict.data = $.extend({}, this.data);
  1462. if($.isEmptyObject(dict.data)){
  1463. delete dict.data;
  1464. }
  1465. }
  1466. if( callback ){
  1467. callback(dict);
  1468. }
  1469. if( recursive ) {
  1470. if(this.hasChildren()){
  1471. dict.children = [];
  1472. for(i=0, l=this.children.length; i<l; i++ ){
  1473. node = this.children[i];
  1474. if( !node.isStatusNode() ){
  1475. dict.children.push(node.toDict(true, callback));
  1476. }
  1477. }
  1478. }else{
  1479. // dict.children = null;
  1480. }
  1481. }
  1482. return dict;
  1483. },
  1484. /** Flip expanded status. */
  1485. toggleExpanded: function(){
  1486. return this.tree._callHook("nodeToggleExpanded", this);
  1487. },
  1488. /** Flip selection status. */
  1489. toggleSelected: function(){
  1490. return this.tree._callHook("nodeToggleSelected", this);
  1491. },
  1492. toString: function() {
  1493. return "<FancytreeNode(#" + this.key + ", '" + this.title + "')>";
  1494. },
  1495. /** Call fn(node) for all child nodes.<br>
  1496. * Stop iteration, if fn() returns false. Skip current branch, if fn() returns "skip".<br>
  1497. * Return false if iteration was stopped.
  1498. *
  1499. * @param {function} fn the callback function.
  1500. * Return false to stop iteration, return "skip" to skip this node and children only.
  1501. * @param {boolean} [includeSelf=false]
  1502. * @returns {boolean}
  1503. */
  1504. visit: function(fn, includeSelf) {
  1505. var i, l,
  1506. res = true,
  1507. children = this.children;
  1508. if( includeSelf === true ) {
  1509. res = fn(this);
  1510. if( res === false || res === "skip" ){
  1511. return res;
  1512. }
  1513. }
  1514. if(children){
  1515. for(i=0, l=children.length; i<l; i++){
  1516. res = children[i].visit(fn, true);
  1517. if( res === false ){
  1518. break;
  1519. }
  1520. }
  1521. }
  1522. return res;
  1523. },
  1524. /** Call fn(node) for all parent nodes, bottom-up, including invisible system root.<br>
  1525. * Stop iteration, if fn() returns false.<br>
  1526. * Return false if iteration was stopped.
  1527. *
  1528. * @param {function} fn the callback function.
  1529. * Return false to stop iteration, return "skip" to skip this node and children only.
  1530. * @param {boolean} [includeSelf=false]
  1531. * @returns {boolean}
  1532. */
  1533. visitParents: function(fn, includeSelf) {
  1534. // Visit parent nodes (bottom up)
  1535. if(includeSelf && fn(this) === false){
  1536. return false;
  1537. }
  1538. var p = this.parent;
  1539. while( p ) {
  1540. if(fn(p) === false){
  1541. return false;
  1542. }
  1543. p = p.parent;
  1544. }
  1545. return true;
  1546. },
  1547. /** Write warning to browser console (prepending node info)
  1548. *
  1549. * @param {*} msg string or object or array of such
  1550. */
  1551. warn: function(msg){
  1552. Array.prototype.unshift.call(arguments, this.toString());
  1553. consoleApply("warn", arguments);
  1554. }
  1555. };
  1556. /* *****************************************************************************
  1557. * Fancytree
  1558. */
  1559. /**
  1560. * Construct a new tree object.
  1561. *
  1562. * @class Fancytree
  1563. * @classdesc The controller behind a fancytree.
  1564. * This class also contains 'hook methods': see {@link Fancytree_Hooks}.
  1565. *
  1566. * @param {Widget} widget
  1567. *
  1568. * @property {FancytreeOptions} options
  1569. * @property {FancytreeNode} rootNode
  1570. * @property {FancytreeNode} activeNode
  1571. * @property {FancytreeNode} focusNode
  1572. * @property {jQueryObject} $div
  1573. * @property {object} widget
  1574. * @property {object} ext
  1575. * @property {object} data
  1576. * @property {object} options
  1577. * @property {string} _id
  1578. * @property {string} statusClassPropName
  1579. * @property {string} ariaPropName
  1580. * @property {string} nodeContainerAttrName
  1581. * @property {string} $container
  1582. * @property {FancytreeNode} lastSelectedNode
  1583. */
  1584. function Fancytree(widget) {
  1585. this.widget = widget;
  1586. this.$div = widget.element;
  1587. this.options = widget.options;
  1588. if( this.options && $.isFunction(this.options.lazyload) ) {
  1589. if( ! $.isFunction(this.options.lazyLoad ) ) {
  1590. this.options.lazyLoad = function() {
  1591. FT.warn("The 'lazyload' event is deprecated since 2014-02-25. Use 'lazyLoad' (with uppercase L) instead.");
  1592. widget.options.lazyload.apply(this, arguments);
  1593. };
  1594. }
  1595. }
  1596. this.ext = {}; // Active extension instances
  1597. // allow to init tree.data.foo from <div data-foo=''>
  1598. this.data = _getElementDataAsDict(this.$div);
  1599. this._id = $.ui.fancytree._nextId++;
  1600. this._ns = ".fancytree-" + this._id; // append for namespaced events
  1601. this.activeNode = null;
  1602. this.focusNode = null;
  1603. this._hasFocus = null;
  1604. this.lastSelectedNode = null;
  1605. this.systemFocusElement = null;
  1606. this.statusClassPropName = "span";
  1607. this.ariaPropName = "li";
  1608. this.nodeContainerAttrName = "li";
  1609. // Remove previous markup if any
  1610. this.$div.find(">ul.fancytree-container").remove();
  1611. // Create a node without parent.
  1612. var fakeParent = { tree: this },
  1613. $ul;
  1614. this.rootNode = new FancytreeNode(fakeParent, {
  1615. title: "root",
  1616. key: "root_" + this._id,
  1617. children: null,
  1618. expanded: true
  1619. });
  1620. this.rootNode.parent = null;
  1621. // Create root markup
  1622. $ul = $("<ul>", {
  1623. "class": "ui-fancytree fancytree-container"
  1624. }).appendTo(this.$div);
  1625. this.$container = $ul;
  1626. this.rootNode.ul = $ul[0];
  1627. if(this.options.debugLevel == null){
  1628. this.options.debugLevel = FT.debugLevel;
  1629. }
  1630. // Add container to the TAB chain
  1631. // See http://www.w3.org/TR/wai-aria-practices/#focus_activedescendant
  1632. this.$container.attr("tabindex", this.options.tabbable ? "0" : "-1");
  1633. if(this.options.aria){
  1634. this.$container
  1635. .attr("role", "tree")
  1636. .attr("aria-multiselectable", true);
  1637. }
  1638. }
  1639. Fancytree.prototype = /** @lends Fancytree# */{
  1640. /* Return a context object that can be re-used for _callHook().
  1641. * @param {Fancytree | FancytreeNode | EventData} obj
  1642. * @param {Event} originalEvent
  1643. * @param {Object} extra
  1644. * @returns {EventData}
  1645. */
  1646. _makeHookContext: function(obj, originalEvent, extra) {
  1647. var ctx, tree;
  1648. if(obj.node !== undefined){
  1649. // obj is already a context object
  1650. if(originalEvent && obj.originalEvent !== originalEvent){
  1651. $.error("invalid args");
  1652. }
  1653. ctx = obj;
  1654. }else if(obj.tree){
  1655. // obj is a FancytreeNode
  1656. tree = obj.tree;
  1657. ctx = { node: obj, tree: tree, widget: tree.widget, options: tree.widget.options, originalEvent: originalEvent };
  1658. }else if(obj.widget){
  1659. // obj is a Fancytree
  1660. ctx = { node: null, tree: obj, widget: obj.widget, options: obj.widget.options, originalEvent: originalEvent };
  1661. }else{
  1662. $.error("invalid args");
  1663. }
  1664. if(extra){
  1665. $.extend(ctx, extra);
  1666. }
  1667. return ctx;
  1668. },
  1669. /* Trigger a hook function: funcName(ctx, [...]).
  1670. *
  1671. * @param {string} funcName
  1672. * @param {Fancytree|FancytreeNode|EventData} contextObject
  1673. * @param {any} [_extraArgs] optional additional arguments
  1674. * @returns {any}
  1675. */
  1676. _callHook: function(funcName, contextObject, _extraArgs) {
  1677. var ctx = this._makeHookContext(contextObject),
  1678. fn = this[funcName],
  1679. args = Array.prototype.slice.call(arguments, 2);
  1680. if(!$.isFunction(fn)){
  1681. $.error("_callHook('" + funcName + "') is not a function");
  1682. }
  1683. args.unshift(ctx);
  1684. // this.debug("_hook", funcName, ctx.node && ctx.node.toString() || ctx.tree.toString(), args);
  1685. return fn.apply(this, args);
  1686. },
  1687. /* Check if current extensions dependencies are met and throw an error if not.
  1688. *
  1689. * This method may be called inside the `treeInit` hook for custom extensions.
  1690. *
  1691. * @param {string} extension name of the required extension
  1692. * @param {boolean} [required=true] pass `false` if the extension is optional, but we want to check for order if it is present
  1693. * @param {boolean} [before] `true` if `name` must be included before this, `false` otherwise (use `null` if order doesn't matter)
  1694. * @param {string} [message] optional error message (defaults to a descriptve error message)
  1695. */
  1696. _requireExtension: function(name, required, before, message) {
  1697. before = !!before;
  1698. var thisName = this._local.name,
  1699. extList = this.options.extensions,
  1700. isBefore = $.inArray(name, extList) < $.inArray(thisName, extList),
  1701. isMissing = required && this.ext[name] == null,
  1702. badOrder = !isMissing && before != null && (before !== isBefore);
  1703. _assert(thisName && thisName !== name);
  1704. if( isMissing || badOrder ){
  1705. if( !message ){
  1706. if( isMissing || required ){
  1707. message = "'" + thisName + "' extension requires '" + name + "'";
  1708. if( badOrder ){
  1709. message += " to be registered " + (before ? "before" : "after") + " itself";
  1710. }
  1711. }else{
  1712. message = "If used together, `" + name + "` must be registered " + (before ? "before" : "after") + " `" + thisName + "`";
  1713. }
  1714. }
  1715. $.error(message);
  1716. return false;
  1717. }
  1718. return true;
  1719. },
  1720. /** Activate node with a given key and fire focus and activate events.
  1721. *
  1722. * A prevously activated node will be deactivated.
  1723. * If activeVisible option is set, all parents will be expanded as necessary.
  1724. * Pass key = false, to deactivate the current node only.
  1725. * @param {string} key
  1726. * @returns {FancytreeNode} activated node (null, if not found)
  1727. */
  1728. activateKey: function(key) {
  1729. var node = this.getNodeByKey(key);
  1730. if(node){
  1731. node.setActive();
  1732. }else if(this.activeNode){
  1733. this.activeNode.setActive(false);
  1734. }
  1735. return node;
  1736. },
  1737. /** (experimental)
  1738. *
  1739. * @param {Array} patchList array of [key, NodePatch] arrays
  1740. * @returns {$.Promise} resolved, when all patches have been applied
  1741. * @see TreePatch
  1742. */
  1743. applyPatch: function(patchList) {
  1744. var dfd, i, p2, key, patch, node,
  1745. patchCount = patchList.length,
  1746. deferredList = [];
  1747. for(i=0; i<patchCount; i++){
  1748. p2 = patchList[i];
  1749. _assert(p2.length === 2, "patchList must be an array of length-2-arrays");
  1750. key = p2[0];
  1751. patch = p2[1];
  1752. node = (key === null) ? this.rootNode : this.getNodeByKey(key);
  1753. if(node){
  1754. dfd = new $.Deferred();
  1755. deferredList.push(dfd);
  1756. node.applyPatch(patch).always(_makeResolveFunc(dfd, node));
  1757. }else{
  1758. this.warn("could not find node with key '" + key + "'");
  1759. }
  1760. }
  1761. // Return a promise that is resovled, when ALL patches were applied
  1762. return $.when.apply($, deferredList).promise();
  1763. },
  1764. /* TODO: implement in dnd extension
  1765. cancelDrag: function() {
  1766. var dd = $.ui.ddmanager.current;
  1767. if(dd){
  1768. dd.cancel();
  1769. }
  1770. },
  1771. */
  1772. /** Return the number of nodes.
  1773. * @returns {integer}
  1774. */
  1775. count: function() {
  1776. return this.rootNode.countChildren();
  1777. },
  1778. /** Write to browser console if debugLevel >= 2 (prepending tree name)
  1779. *
  1780. * @param {*} msg string or object or array of such
  1781. */
  1782. debug: function(msg){
  1783. if( this.options.debugLevel >= 2 ) {
  1784. Array.prototype.unshift.call(arguments, this.toString());
  1785. consoleApply("debug", arguments);
  1786. }
  1787. },
  1788. // TODO: disable()
  1789. // TODO: enable()
  1790. // TODO: enableUpdate()
  1791. // TODO: fromDict
  1792. /**
  1793. * Generate INPUT elements that can be submitted with html forms.
  1794. *
  1795. * In selectMode 3 only the topmost selected nodes are considered.
  1796. *
  1797. * @param {boolean | string} [selected=true]
  1798. * @param {boolean | string} [active=true]
  1799. */
  1800. generateFormElements: function(selected, active) {
  1801. // TODO: test case
  1802. var nodeList,
  1803. selectedName = (selected !== false) ? "ft_" + this._id + "[]" : selected,
  1804. activeName = (active !== false) ? "ft_" + this._id + "_active" : active,
  1805. id = "fancytree_result_" + this._id,
  1806. $result = $("#" + id);
  1807. if($result.length){
  1808. $result.empty();
  1809. }else{
  1810. $result = $("<div>", {
  1811. id: id
  1812. }).hide().insertAfter(this.$container);
  1813. }
  1814. if(selectedName){
  1815. nodeList = this.getSelectedNodes( this.options.selectMode === 3 );
  1816. $.each(nodeList, function(idx, node){
  1817. $result.append($("<input>", {
  1818. type: "checkbox",
  1819. name: selectedName,
  1820. value: node.key,
  1821. checked: true
  1822. }));
  1823. });
  1824. }
  1825. if(activeName && this.activeNode){
  1826. $result.append($("<input>", {
  1827. type: "radio",
  1828. name: activeName,
  1829. value: this.activeNode.key,
  1830. checked: true
  1831. }));
  1832. }
  1833. },
  1834. /**
  1835. * Return the currently active node or null.
  1836. * @returns {FancytreeNode}
  1837. */
  1838. getActiveNode: function() {
  1839. return this.activeNode;
  1840. },
  1841. /** Return the first top level node if any (not the invisible root node).
  1842. * @returns {FancytreeNode | null}
  1843. */
  1844. getFirstChild: function() {
  1845. return this.rootNode.getFirstChild();
  1846. },
  1847. /**
  1848. * Return node that has keyboard focus.
  1849. * @param {boolean} [ifTreeHasFocus=false] (not yet implemented)
  1850. * @returns {FancytreeNode}
  1851. */
  1852. getFocusNode: function(ifTreeHasFocus) {
  1853. // TODO: implement ifTreeHasFocus
  1854. return this.focusNode;
  1855. },
  1856. /**
  1857. * Return node with a given key or null if not found.
  1858. * @param {string} key
  1859. * @param {FancytreeNode} [searchRoot] only search below this node
  1860. * @returns {FancytreeNode | null}
  1861. */
  1862. getNodeByKey: function(key, searchRoot) {
  1863. // Search the DOM by element ID (assuming this is faster than traversing all nodes).
  1864. // $("#...") has problems, if the key contains '.', so we use getElementById()
  1865. var el, match;
  1866. if(!searchRoot){
  1867. el = document.getElementById(this.options.idPrefix + key);
  1868. if( el ){
  1869. return el.ftnode ? el.ftnode : null;
  1870. }
  1871. }
  1872. // Not found in the DOM, but still may be in an unrendered part of tree
  1873. // TODO: optimize with specialized loop
  1874. // TODO: consider keyMap?
  1875. searchRoot = searchRoot || this.rootNode;
  1876. match = null;
  1877. searchRoot.visit(function(node){
  1878. // window.console.log("getNodeByKey(" + key + "): ", node.key);
  1879. if(node.key === key) {
  1880. match = node;
  1881. return false;
  1882. }
  1883. }, true);
  1884. return match;
  1885. },
  1886. /** Return the invisible system root node.
  1887. * @returns {FancytreeNode}
  1888. */
  1889. getRootNode: function() {
  1890. return this.rootNode;
  1891. },
  1892. /**
  1893. * Return an array of selected nodes.
  1894. * @param {boolean} [stopOnParents=false] only return the topmost selected
  1895. * node (useful with selectMode 3)
  1896. * @returns {FancytreeNode[]}
  1897. */
  1898. getSelectedNodes: function(stopOnParents) {
  1899. var nodeList = [];
  1900. this.rootNode.visit(function(node){
  1901. if( node.selected ) {
  1902. nodeList.push(node);
  1903. if( stopOnParents === true ){
  1904. return "skip"; // stop processing this branch
  1905. }
  1906. }
  1907. });
  1908. return nodeList;
  1909. },
  1910. /** Return true if the tree control has keyboard focus
  1911. * @returns {boolean}
  1912. */
  1913. hasFocus: function(){
  1914. return !!this._hasFocus;
  1915. },
  1916. /** Write to browser console if debugLevel >= 1 (prepending tree name)
  1917. * @param {*} msg string or object or array of such
  1918. */
  1919. info: function(msg){
  1920. if( this.options.debugLevel >= 1 ) {
  1921. Array.prototype.unshift.call(arguments, this.toString());
  1922. consoleApply("info", arguments);
  1923. }
  1924. },
  1925. /*
  1926. TODO: isInitializing: function() {
  1927. return ( this.phase=="init" || this.phase=="postInit" );
  1928. },
  1929. TODO: isReloading: function() {
  1930. return ( this.phase=="init" || this.phase=="postInit" ) && this.options.persist && this.persistence.cookiesFound;
  1931. },
  1932. TODO: isUserEvent: function() {
  1933. return ( this.phase=="userEvent" );
  1934. },
  1935. */
  1936. /**
  1937. * Make sure that a node with a given ID is loaded, by traversing - and
  1938. * loading - its parents. This method is ment for lazy hierarchies.
  1939. * A callback is executed for every node as we go.
  1940. * @example
  1941. * tree.loadKeyPath("/_3/_23/_26/_27", function(node, status){
  1942. * if(status === "loaded") {
  1943. * console.log("loaded intermiediate node " + node);
  1944. * }else if(status === "ok") {
  1945. * node.activate();
  1946. * }
  1947. * });
  1948. *
  1949. * @param {string | string[]} keyPathList one or more key paths (e.g. '/3/2_1/7')
  1950. * @param {function} callback callback(node, status) is called for every visited node ('loading', 'loaded', 'ok', 'error')
  1951. * @returns {$.Promise}
  1952. */
  1953. loadKeyPath: function(keyPathList, callback, _rootNode) {
  1954. var deferredList, dfd, i, path, key, loadMap, node, segList,
  1955. root = _rootNode || this.rootNode,
  1956. sep = this.options.keyPathSeparator,
  1957. self = this;
  1958. if(!$.isArray(keyPathList)){
  1959. keyPathList = [keyPathList];
  1960. }
  1961. // Pass 1: handle all path segments for nodes that are already loaded
  1962. // Collect distinct top-most lazy nodes in a map
  1963. loadMap = {};
  1964. for(i=0; i<keyPathList.length; i++){
  1965. path = keyPathList[i];
  1966. // strip leading slash
  1967. if(path.charAt(0) === sep){
  1968. path = path.substr(1);
  1969. }
  1970. // traverse and strip keys, until we hit a lazy, unloaded node
  1971. segList = path.split(sep);
  1972. while(segList.length){
  1973. key = segList.shift();
  1974. // node = _findDirectChild(root, key);
  1975. node = root._findDirectChild(key);
  1976. if(!node){
  1977. this.warn("loadKeyPath: key not found: " + key + " (parent: " + root + ")");
  1978. callback.call(this, key, "error");
  1979. break;
  1980. }else if(segList.length === 0){
  1981. callback.call(this, node, "ok");
  1982. break;
  1983. }else if(!node.lazy || (node.hasChildren() !== undefined )){
  1984. callback.call(this, node, "loaded");
  1985. root = node;
  1986. }else{
  1987. callback.call(this, node, "loaded");
  1988. // segList.unshift(key);
  1989. if(loadMap[key]){
  1990. loadMap[key].push(segList.join(sep));
  1991. }else{
  1992. loadMap[key] = [segList.join(sep)];
  1993. }
  1994. break;
  1995. }
  1996. }
  1997. }
  1998. // alert("loadKeyPath: loadMap=" + JSON.stringify(loadMap));
  1999. // Now load all lazy nodes and continue itearation for remaining paths
  2000. deferredList = [];
  2001. // Avoid jshint warning 'Don't make functions within a loop.':
  2002. function __lazyload(key, node, dfd){
  2003. callback.call(self, node, "loading");
  2004. node.load().done(function(){
  2005. self.loadKeyPath.call(self, loadMap[key], callback, node).always(_makeResolveFunc(dfd, self));
  2006. }).fail(function(errMsg){
  2007. self.warn("loadKeyPath: error loading: " + key + " (parent: " + root + ")");
  2008. callback.call(self, node, "error");
  2009. dfd.reject();
  2010. });
  2011. }
  2012. for(key in loadMap){
  2013. node = root._findDirectChild(key);
  2014. // alert("loadKeyPath: lazy node(" + key + ") = " + node);
  2015. dfd = new $.Deferred();
  2016. deferredList.push(dfd);
  2017. __lazyload(key, node, dfd);
  2018. }
  2019. // Return a promise that is resovled, when ALL paths were loaded
  2020. return $.when.apply($, deferredList).promise();
  2021. },
  2022. /** Re-fire beforeActivate and activate events. */
  2023. reactivate: function(setFocus) {
  2024. var node = this.activeNode;
  2025. if( node ) {
  2026. this.activeNode = null; // Force re-activating
  2027. node.setActive();
  2028. if( setFocus ){
  2029. node.setFocus();
  2030. }
  2031. }
  2032. },
  2033. /** Reload tree from source and return a promise.
  2034. * @param [source] optional new source (defaults to initial source data)
  2035. * @returns {$.Promise}
  2036. */
  2037. reload: function(source) {
  2038. this._callHook("treeClear", this);
  2039. return this._callHook("treeLoad", this, source);
  2040. },
  2041. /**Render tree (i.e. create DOM elements for all top-level nodes).
  2042. * @param {boolean} [force=false] create DOM elemnts, even is parent is collapsed
  2043. * @param {boolean} [deep=false]
  2044. */
  2045. render: function(force, deep) {
  2046. return this.rootNode.render(force, deep);
  2047. },
  2048. // TODO: selectKey: function(key, select)
  2049. // TODO: serializeArray: function(stopOnParents)
  2050. /**
  2051. * @param {boolean} [flag=true]
  2052. */
  2053. setFocus: function(flag) {
  2054. return this._callHook("treeSetFocus", this, flag);
  2055. },
  2056. /**
  2057. * Return all nodes as nested list of {@link NodeData}.
  2058. *
  2059. * @param {boolean} [includeRoot=false] Returns the hidden system root node (and its children)
  2060. * @param {function} [callback(node)] Called for every node
  2061. * @returns {Array | object}
  2062. * @see FancytreeNode#toDict
  2063. */
  2064. toDict: function(includeRoot, callback){
  2065. var res = this.rootNode.toDict(true, callback);
  2066. return includeRoot ? res : res.children;
  2067. },
  2068. /* Implicitly called for string conversions.
  2069. * @returns {string}
  2070. */
  2071. toString: function(){
  2072. return "<Fancytree(#" + this._id + ")>";
  2073. },
  2074. /* _trigger a widget event with additional node ctx.
  2075. * @see EventData
  2076. */
  2077. _triggerNodeEvent: function(type, node, originalEvent, extra) {
  2078. // this.debug("_trigger(" + type + "): '" + ctx.node.title + "'", ctx);
  2079. var ctx = this._makeHookContext(node, originalEvent, extra),
  2080. res = this.widget._trigger(type, originalEvent, ctx);
  2081. if(res !== false && ctx.result !== undefined){
  2082. return ctx.result;
  2083. }
  2084. return res;
  2085. },
  2086. /* _trigger a widget event with additional tree data. */
  2087. _triggerTreeEvent: function(type, originalEvent) {
  2088. // this.debug("_trigger(" + type + ")", ctx);
  2089. var ctx = this._makeHookContext(this, originalEvent),
  2090. res = this.widget._trigger(type, originalEvent, ctx);
  2091. if(res !== false && ctx.result !== undefined){
  2092. return ctx.result;
  2093. }
  2094. return res;
  2095. },
  2096. /** Call fn(node) for all nodes.
  2097. *
  2098. * @param {function} fn the callback function.
  2099. * Return false to stop iteration, return "skip" to skip this node and children only.
  2100. * @returns {boolean} false, if the iterator was stopped.
  2101. */
  2102. visit: function(fn) {
  2103. return this.rootNode.visit(fn, false);
  2104. },
  2105. /** Write warning to browser console (prepending tree info)
  2106. *
  2107. * @param {*} msg string or object or array of such
  2108. */
  2109. warn: function(msg){
  2110. Array.prototype.unshift.call(arguments, this.toString());
  2111. consoleApply("warn", arguments);
  2112. }
  2113. };
  2114. /**
  2115. * These additional methods of the {@link Fancytree} class are 'hook functions'
  2116. * that can be used and overloaded by extensions.
  2117. * (See <a href="https://github.com/mar10/fancytree/wiki/TutorialExtensions">writing extensions</a>.)
  2118. * @mixin Fancytree_Hooks
  2119. */
  2120. $.extend(Fancytree.prototype,
  2121. /** @lends Fancytree_Hooks# */
  2122. {
  2123. /** Default handling for mouse click events.
  2124. *
  2125. * @param {EventData} ctx
  2126. */
  2127. nodeClick: function(ctx) {
  2128. // this.tree.logDebug("ftnode.onClick(" + event.type + "): ftnode:" + this + ", button:" + event.button + ", which: " + event.which);
  2129. var activate, expand,
  2130. event = ctx.originalEvent,
  2131. targetType = ctx.targetType,
  2132. node = ctx.node;
  2133. // TODO: use switch
  2134. // TODO: make sure clicks on embedded <input> doesn't steal focus (see table sample)
  2135. if( targetType === "expander" ) {
  2136. // Clicking the expander icon always expands/collapses
  2137. this._callHook("nodeToggleExpanded", ctx);
  2138. // this._callHook("nodeSetFocus", ctx, true); // DT issue 95
  2139. } else if( targetType === "checkbox" ) {
  2140. // Clicking the checkbox always (de)selects
  2141. this._callHook("nodeToggleSelected", ctx);
  2142. this._callHook("nodeSetFocus", ctx, true); // DT issue 95
  2143. } else {
  2144. // Honor `clickFolderMode` for
  2145. expand = false;
  2146. activate = true;
  2147. if( node.folder ) {
  2148. switch( ctx.options.clickFolderMode ) {
  2149. case 2: // expand only
  2150. expand = true;
  2151. activate = false;
  2152. break;
  2153. case 3: // expand and activate
  2154. activate = true;
  2155. expand = true; //!node.isExpanded();
  2156. break;
  2157. // else 1 or 4: just activate
  2158. }
  2159. }
  2160. if( activate ) {
  2161. this.nodeSetFocus(ctx);
  2162. this._callHook("nodeSetActive", ctx, true);
  2163. }
  2164. if( expand ) {
  2165. if(!activate){
  2166. // this._callHook("nodeSetFocus", ctx);
  2167. }
  2168. // this._callHook("nodeSetExpanded", ctx, true);
  2169. this._callHook("nodeToggleExpanded", ctx);
  2170. }
  2171. }
  2172. // Make sure that clicks stop, otherwise <a href='#'> jumps to the top
  2173. if(event.target.localName === "a" && event.target.className === "fancytree-title"){
  2174. event.preventDefault();
  2175. }
  2176. // TODO: return promise?
  2177. },
  2178. /** Collapse all other children of same parent.
  2179. *
  2180. * @param {EventData} ctx
  2181. * @param {object} callOpts
  2182. */
  2183. nodeCollapseSiblings: function(ctx, callOpts) {
  2184. // TODO: return promise?
  2185. var ac, i, l,
  2186. node = ctx.node;
  2187. if( node.parent ){
  2188. ac = node.parent.children;
  2189. for (i=0, l=ac.length; i<l; i++) {
  2190. if ( ac[i] !== node && ac[i].expanded ){
  2191. this._callHook("nodeSetExpanded", ac[i], false, callOpts);
  2192. }
  2193. }
  2194. }
  2195. },
  2196. /** Default handling for mouse douleclick events.
  2197. * @param {EventData} ctx
  2198. */
  2199. nodeDblclick: function(ctx) {
  2200. // TODO: return promise?
  2201. if( ctx.targetType === "title" && ctx.options.clickFolderMode === 4) {
  2202. // this.nodeSetFocus(ctx);
  2203. // this._callHook("nodeSetActive", ctx, true);
  2204. this._callHook("nodeToggleExpanded", ctx);
  2205. }
  2206. // TODO: prevent text selection on dblclicks
  2207. if( ctx.targetType === "title" ) {
  2208. ctx.originalEvent.preventDefault();
  2209. }
  2210. },
  2211. /** Default handling for mouse keydown events.
  2212. *
  2213. * NOTE: this may be called with node == null if tree (but no node) has focus.
  2214. * @param {EventData} ctx
  2215. */
  2216. nodeKeydown: function(ctx) {
  2217. // TODO: return promise?
  2218. var res,
  2219. event = ctx.originalEvent,
  2220. node = ctx.node,
  2221. tree = ctx.tree,
  2222. opts = ctx.options,
  2223. handled = true,
  2224. activate = !(event.ctrlKey || !opts.autoActivate ),
  2225. KC = $.ui.keyCode;
  2226. // node.debug("ftnode.nodeKeydown(" + event.type + "): ftnode:" + this + ", charCode:" + event.charCode + ", keyCode: " + event.keyCode + ", which: " + event.which);
  2227. // Set focus to first node, if no other node has the focus yet
  2228. if( !node ){
  2229. this.rootNode.getFirstChild().setFocus();
  2230. node = ctx.node = this.focusNode;
  2231. node.debug("Keydown force focus on first node");
  2232. }
  2233. switch( event.which ) {
  2234. // charCodes:
  2235. case KC.NUMPAD_ADD: //107: // '+'
  2236. case 187: // '+' @ Chrome, Safari
  2237. tree.nodeSetExpanded(ctx, true);
  2238. break;
  2239. case KC.NUMPAD_SUBTRACT: // '-'
  2240. case 189: // '-' @ Chrome, Safari
  2241. tree.nodeSetExpanded(ctx, false);
  2242. break;
  2243. case KC.SPACE:
  2244. if(opts.checkbox){
  2245. tree.nodeToggleSelected(ctx);
  2246. }else{
  2247. tree.nodeSetActive(ctx, true);
  2248. }
  2249. break;
  2250. case KC.ENTER:
  2251. tree.nodeSetActive(ctx, true);
  2252. break;
  2253. case KC.BACKSPACE:
  2254. case KC.LEFT:
  2255. case KC.RIGHT:
  2256. case KC.UP:
  2257. case KC.DOWN:
  2258. res = node.navigate(event.which, activate);
  2259. break;
  2260. default:
  2261. handled = false;
  2262. }
  2263. if(handled){
  2264. event.preventDefault();
  2265. }
  2266. },
  2267. // /** Default handling for mouse keypress events. */
  2268. // nodeKeypress: function(ctx) {
  2269. // var event = ctx.originalEvent;
  2270. // },
  2271. // /** Trigger lazyLoad event (async). */
  2272. // nodeLazyLoad: function(ctx) {
  2273. // var node = ctx.node;
  2274. // if(this._triggerNodeEvent())
  2275. // },
  2276. /** Load child nodes (async).
  2277. *
  2278. * @param {EventData} ctx
  2279. * @param {object[]|object|string|$.Promise|function} source
  2280. * @returns {$.Promise} The deferred will be resolved as soon as the (ajax)
  2281. * data was rendered.
  2282. */
  2283. nodeLoadChildren: function(ctx, source) {
  2284. var ajax, delay,
  2285. tree = ctx.tree,
  2286. node = ctx.node;
  2287. if($.isFunction(source)){
  2288. source = source();
  2289. }
  2290. // TOTHINK: move to 'ajax' extension?
  2291. if(source.url){
  2292. // `source` is an Ajax options object
  2293. ajax = $.extend({}, ctx.options.ajax, source);
  2294. if(ajax.debugDelay){
  2295. // simulate a slow server
  2296. delay = ajax.debugDelay;
  2297. if($.isArray(delay)){ // random delay range [min..max]
  2298. delay = delay[0] + Math.random() * (delay[1] - delay[0]);
  2299. }
  2300. node.debug("nodeLoadChildren waiting debug delay " + Math.round(delay) + "ms");
  2301. ajax.debugDelay = false;
  2302. source = $.Deferred(function (dfd) {
  2303. setTimeout(function () {
  2304. $.ajax(ajax)
  2305. .done(function () { dfd.resolveWith(this, arguments); })
  2306. .fail(function () { dfd.rejectWith(this, arguments); });
  2307. }, delay);
  2308. });
  2309. }else{
  2310. source = $.ajax(ajax);
  2311. }
  2312. // TODO: change 'pipe' to 'then' for jQuery 1.8
  2313. // $.pipe returns a new Promise with filtered results
  2314. source = source.pipe(function (data, textStatus, jqXHR) {
  2315. var res;
  2316. if(typeof data === "string"){
  2317. $.error("Ajax request returned a string (did you get the JSON dataType wrong?).");
  2318. }
  2319. // postProcess is similar to the standard dataFilter hook,
  2320. // but it is also called for JSONP
  2321. if( ctx.options.postProcess ){
  2322. res = tree._triggerNodeEvent("postProcess", ctx, ctx.originalEvent, {response: data, dataType: this.dataType});
  2323. data = $.isArray(res) ? res : data;
  2324. } else if (data && data.hasOwnProperty("d") && ctx.options.enableAspx ) {
  2325. // Process ASPX WebMethod JSON object inside "d" property
  2326. data = (typeof data.d === "string") ? $.parseJSON(data.d) : data.d;
  2327. }
  2328. return data;
  2329. }, function (jqXHR, textStatus, errorThrown) {
  2330. return tree._makeHookContext(node, null, {
  2331. error: jqXHR,
  2332. args: Array.prototype.slice.call(arguments),
  2333. message: errorThrown,
  2334. details: jqXHR.status + ": " + errorThrown
  2335. });
  2336. });
  2337. }
  2338. if($.isFunction(source.promise)){
  2339. // `source` is a deferred, i.e. ajax request
  2340. _assert(!node.isLoading());
  2341. // node._isLoading = true;
  2342. tree.nodeSetStatus(ctx, "loading");
  2343. source.done(function () {
  2344. tree.nodeSetStatus(ctx, "ok");
  2345. }).fail(function(error){
  2346. var ctxErr;
  2347. if (error.node && error.error && error.message) {
  2348. // error is already a context object
  2349. ctxErr = error;
  2350. } else {
  2351. ctxErr = tree._makeHookContext(node, null, {
  2352. error: error, // it can be jqXHR or any custom error
  2353. args: Array.prototype.slice.call(arguments),
  2354. message: error ? (error.message || error.toString()) : ""
  2355. });
  2356. }
  2357. tree._triggerNodeEvent("loaderror", ctxErr, null);
  2358. tree.nodeSetStatus(ctx, "error", ctxErr.message, ctxErr.details);
  2359. });
  2360. }
  2361. // $.when(source) resolves also for non-deferreds
  2362. return $.when(source).done(function(children){
  2363. var metaData;
  2364. if( $.isPlainObject(children) ){
  2365. // We got {foo: 'abc', children: [...]}
  2366. // Copy extra properties to tree.data.foo
  2367. _assert($.isArray(children.children), "source must contain (or be) an array of children");
  2368. _assert(node.isRoot(), "source may only be an object for root nodes");
  2369. metaData = children;
  2370. children = children.children;
  2371. delete metaData.children;
  2372. $.extend(tree.data, metaData);
  2373. }
  2374. _assert($.isArray(children), "expected array of children");
  2375. node._setChildren(children);
  2376. // trigger fancytreeloadchildren
  2377. // if( node.parent ) {
  2378. tree._triggerNodeEvent("loadChildren", node);
  2379. // }
  2380. // }).always(function(){
  2381. // node._isLoading = false;
  2382. });
  2383. },
  2384. /** [Not Implemented] */
  2385. nodeLoadKeyPath: function(ctx, keyPathList) {
  2386. // TODO: implement and improve
  2387. // http://code.google.com/p/dynatree/issues/detail?id=222
  2388. },
  2389. /**
  2390. * Remove a single direct child of ctx.node.
  2391. * @param {EventData} ctx
  2392. * @param {FancytreeNode} childNode dircect child of ctx.node
  2393. */
  2394. nodeRemoveChild: function(ctx, childNode) {
  2395. var idx,
  2396. node = ctx.node,
  2397. opts = ctx.options,
  2398. subCtx = $.extend({}, ctx, {node: childNode}),
  2399. children = node.children;
  2400. // FT.debug("nodeRemoveChild()", node.toString(), childNode.toString());
  2401. if( children.length === 1 ) {
  2402. _assert(childNode === children[0]);
  2403. return this.nodeRemoveChildren(ctx);
  2404. }
  2405. if( this.activeNode && (childNode === this.activeNode || this.activeNode.isDescendantOf(childNode))){
  2406. this.activeNode.setActive(false); // TODO: don't fire events
  2407. }
  2408. if( this.focusNode && (childNode === this.focusNode || this.focusNode.isDescendantOf(childNode))){
  2409. this.focusNode = null;
  2410. }
  2411. // TODO: persist must take care to clear select and expand cookies
  2412. this.nodeRemoveMarkup(subCtx);
  2413. this.nodeRemoveChildren(subCtx);
  2414. idx = $.inArray(childNode, children);
  2415. _assert(idx >= 0);
  2416. // Unlink to support GC
  2417. childNode.visit(function(n){
  2418. n.parent = null;
  2419. }, true);
  2420. this._callHook("treeRegisterNode", this, false, childNode);
  2421. if ( opts.removeNode ){
  2422. opts.removeNode.call(ctx.tree, {type: "removeNode"}, subCtx);
  2423. }
  2424. // remove from child list
  2425. children.splice(idx, 1);
  2426. },
  2427. /**Remove HTML markup for all descendents of ctx.node.
  2428. * @param {EventData} ctx
  2429. */
  2430. nodeRemoveChildMarkup: function(ctx) {
  2431. var node = ctx.node;
  2432. // FT.debug("nodeRemoveChildMarkup()", node.toString());
  2433. // TODO: Unlink attr.ftnode to support GC
  2434. if(node.ul){
  2435. if( node.isRoot() ) {
  2436. $(node.ul).empty();
  2437. } else {
  2438. $(node.ul).remove();
  2439. node.ul = null;
  2440. }
  2441. node.visit(function(n){
  2442. n.li = n.ul = null;
  2443. });
  2444. }
  2445. },
  2446. /**Remove all descendants of ctx.node.
  2447. * @param {EventData} ctx
  2448. */
  2449. nodeRemoveChildren: function(ctx) {
  2450. var subCtx,
  2451. tree = ctx.tree,
  2452. node = ctx.node,
  2453. children = node.children,
  2454. opts = ctx.options;
  2455. // FT.debug("nodeRemoveChildren()", node.toString());
  2456. if(!children){
  2457. return;
  2458. }
  2459. if( this.activeNode && this.activeNode.isDescendantOf(node)){
  2460. this.activeNode.setActive(false); // TODO: don't fire events
  2461. }
  2462. if( this.focusNode && this.focusNode.isDescendantOf(node)){
  2463. this.focusNode = null;
  2464. }
  2465. // TODO: persist must take care to clear select and expand cookies
  2466. this.nodeRemoveChildMarkup(ctx);
  2467. // Unlink children to support GC
  2468. // TODO: also delete this.children (not possible using visit())
  2469. subCtx = $.extend({}, ctx);
  2470. node.visit(function(n){
  2471. n.parent = null;
  2472. tree._callHook("treeRegisterNode", tree, false, n);
  2473. if ( opts.removeNode ){
  2474. subCtx.node = n;
  2475. opts.removeNode.call(ctx.tree, {type: "removeNode"}, subCtx);
  2476. }
  2477. });
  2478. if( node.lazy ){
  2479. // 'undefined' would be interpreted as 'not yet loaded' for lazy nodes
  2480. node.children = [];
  2481. } else{
  2482. node.children = null;
  2483. }
  2484. this.nodeRenderStatus(ctx);
  2485. },
  2486. /**Remove HTML markup for ctx.node and all its descendents.
  2487. * @param {EventData} ctx
  2488. */
  2489. nodeRemoveMarkup: function(ctx) {
  2490. var node = ctx.node;
  2491. // FT.debug("nodeRemoveMarkup()", node.toString());
  2492. // TODO: Unlink attr.ftnode to support GC
  2493. if(node.li){
  2494. $(node.li).remove();
  2495. node.li = null;
  2496. }
  2497. this.nodeRemoveChildMarkup(ctx);
  2498. },
  2499. /**
  2500. * Create `&lt;li>&lt;span>..&lt;/span> .. &lt;/li>` tags for this node.
  2501. *
  2502. * This method takes care that all HTML markup is created that is required
  2503. * to display this node in it's current state.
  2504. *
  2505. * Call this method to create new nodes, or after the strucuture
  2506. * was changed (e.g. after moving this node or adding/removing children)
  2507. * nodeRenderTitle() and nodeRenderStatus() are implied.
  2508. *
  2509. * Note: if a node was created/removed, nodeRender() must be called for the
  2510. * parent.
  2511. * <code>
  2512. * <li id='KEY' ftnode=NODE>
  2513. * <span class='fancytree-node fancytree-expanded fancytree-has-children fancytree-lastsib fancytree-exp-el fancytree-ico-e'>
  2514. * <span class="fancytree-expander"></span>
  2515. * <span class="fancytree-checkbox"></span> // only present in checkbox mode
  2516. * <span class="fancytree-icon"></span>
  2517. * <a href="#" class="fancytree-title"> Node 1 </a>
  2518. * </span>
  2519. * <ul> // only present if node has children
  2520. * <li id='KEY' ftnode=NODE> child1 ... </li>
  2521. * <li id='KEY' ftnode=NODE> child2 ... </li>
  2522. * </ul>
  2523. * </li>
  2524. * </code>
  2525. *
  2526. * @param {EventData} ctx
  2527. * @param {boolean} [force=false] re-render, even if html markup was already created
  2528. * @param {boolean} [deep=false] also render all descendants, even if parent is collapsed
  2529. * @param {boolean} [collapsed=false] force root node to be collapsed, so we can apply animated expand later
  2530. */
  2531. nodeRender: function(ctx, force, deep, collapsed, _recursive) {
  2532. /* This method must take care of all cases where the current data mode
  2533. * (i.e. node hierarchy) does not match the current markup.
  2534. *
  2535. * - node was not yet rendered:
  2536. * create markup
  2537. * - node was rendered: exit fast
  2538. * - children have been added
  2539. * - childern have been removed
  2540. */
  2541. var childLI, childNode1, childNode2, i, l, next, subCtx,
  2542. node = ctx.node,
  2543. tree = ctx.tree,
  2544. opts = ctx.options,
  2545. aria = opts.aria,
  2546. firstTime = false,
  2547. parent = node.parent,
  2548. isRootNode = !parent,
  2549. children = node.children;
  2550. // FT.debug("nodeRender(" + !!force + ", " + !!deep + ")", node.toString());
  2551. if( ! isRootNode && ! parent.ul ) {
  2552. // Calling node.collapse on a deep, unrendered node
  2553. return;
  2554. }
  2555. _assert(isRootNode || parent.ul, "parent UL must exist");
  2556. // if(node.li && (force || (node.li.parentNode !== node.parent.ul) ) ){
  2557. // if(node.li.parentNode !== node.parent.ul){
  2558. // // alert("unlink " + node + " (must be child of " + node.parent + ")");
  2559. // this.warn("unlink " + node + " (must be child of " + node.parent + ")");
  2560. // }
  2561. // // this.debug("nodeRemoveMarkup...");
  2562. // this.nodeRemoveMarkup(ctx);
  2563. // }
  2564. // Render the node
  2565. if( !isRootNode ){
  2566. // Discard markup on force-mode, or if it is not linked to parent <ul>
  2567. if(node.li && (force || (node.li.parentNode !== node.parent.ul) ) ){
  2568. if(node.li.parentNode !== node.parent.ul){
  2569. // alert("unlink " + node + " (must be child of " + node.parent + ")");
  2570. this.warn("unlink " + node + " (must be child of " + node.parent + ")");
  2571. }
  2572. // this.debug("nodeRemoveMarkup...");
  2573. this.nodeRemoveMarkup(ctx);
  2574. }
  2575. // Create <li><span /> </li>
  2576. // node.debug("render...");
  2577. if( !node.li ) {
  2578. // node.debug("render... really");
  2579. firstTime = true;
  2580. node.li = document.createElement("li");
  2581. node.li.ftnode = node;
  2582. if(aria){
  2583. // TODO: why doesn't this work:
  2584. // node.li.role = "treeitem";
  2585. // $(node.li).attr("role", "treeitem")
  2586. // .attr("aria-labelledby", "ftal_" + node.key);
  2587. }
  2588. if( node.key && opts.generateIds ){
  2589. node.li.id = opts.idPrefix + node.key;
  2590. }
  2591. node.span = document.createElement("span");
  2592. node.span.className = "fancytree-node";
  2593. if(aria){
  2594. $(node.span).attr("aria-labelledby", "ftal_" + node.key);
  2595. }
  2596. node.li.appendChild(node.span);
  2597. // Create inner HTML for the <span> (expander, checkbox, icon, and title)
  2598. this.nodeRenderTitle(ctx);
  2599. // Allow tweaking and binding, after node was created for the first time
  2600. if ( opts.createNode ){
  2601. opts.createNode.call(tree, {type: "createNode"}, ctx);
  2602. }
  2603. }else{
  2604. // this.nodeRenderTitle(ctx);
  2605. this.nodeRenderStatus(ctx);
  2606. }
  2607. // Allow tweaking after node state was rendered
  2608. if ( opts.renderNode ){
  2609. opts.renderNode.call(tree, {type: "renderNode"}, ctx);
  2610. }
  2611. }
  2612. // Visit child nodes
  2613. if( children ){
  2614. if( isRootNode || node.expanded || deep === true ) {
  2615. // Create a UL to hold the children
  2616. if( !node.ul ){
  2617. node.ul = document.createElement("ul");
  2618. if((collapsed === true && !_recursive) || !node.expanded){
  2619. // hide top UL, so we can use an animation to show it later
  2620. node.ul.style.display = "none";
  2621. }
  2622. if(aria){
  2623. $(node.ul).attr("role", "group");
  2624. }
  2625. if ( node.li ) { // issue #67
  2626. node.li.appendChild(node.ul);
  2627. } else {
  2628. node.tree.$div.append(node.ul);
  2629. }
  2630. }
  2631. // Add child markup
  2632. for(i=0, l=children.length; i<l; i++) {
  2633. subCtx = $.extend({}, ctx, {node: children[i]});
  2634. this.nodeRender(subCtx, force, deep, false, true);
  2635. }
  2636. // Remove <li> if nodes have moved to another parent
  2637. childLI = node.ul.firstChild;
  2638. while( childLI ){
  2639. childNode2 = childLI.ftnode;
  2640. if( childNode2 && childNode2.parent !== node ) {
  2641. node.debug("_fixParent: remove missing " + childNode2, childLI);
  2642. next = childLI.nextSibling;
  2643. childLI.parentNode.removeChild(childLI);
  2644. childLI = next;
  2645. }else{
  2646. childLI = childLI.nextSibling;
  2647. }
  2648. }
  2649. // Make sure, that <li> order matches node.children order.
  2650. childLI = node.ul.firstChild;
  2651. for(i=0, l=children.length-1; i<l; i++) {
  2652. childNode1 = children[i];
  2653. childNode2 = childLI.ftnode;
  2654. if( childNode1 !== childNode2 ) {
  2655. // node.debug("_fixOrder: mismatch at index " + i + ": " + childNode1 + " != " + childNode2);
  2656. node.ul.insertBefore(childNode1.li, childNode2.li);
  2657. } else {
  2658. childLI = childLI.nextSibling;
  2659. }
  2660. }
  2661. }
  2662. }else{
  2663. // No children: remove markup if any
  2664. if( node.ul ){
  2665. // alert("remove child markup for " + node);
  2666. this.warn("remove child markup for " + node);
  2667. this.nodeRemoveChildMarkup(ctx);
  2668. }
  2669. }
  2670. if( !isRootNode ){
  2671. // Update element classes according to node state
  2672. // this.nodeRenderStatus(ctx);
  2673. // Finally add the whole structure to the DOM, so the browser can render
  2674. if(firstTime){
  2675. parent.ul.appendChild(node.li);
  2676. }
  2677. }
  2678. },
  2679. /** Create HTML for the node's outer <span> (expander, checkbox, icon, and title).
  2680. *
  2681. * nodeRenderStatus() is implied.
  2682. * @param {EventData} ctx
  2683. * @param {string} [title] optinal new title
  2684. */
  2685. nodeRenderTitle: function(ctx, title) {
  2686. // set node connector images, links and text
  2687. var id, imageSrc, nodeTitle, role, tabindex, tooltip,
  2688. node = ctx.node,
  2689. tree = ctx.tree,
  2690. opts = ctx.options,
  2691. aria = opts.aria,
  2692. level = node.getLevel(),
  2693. ares = [],
  2694. icon = node.data.icon;
  2695. if(title !== undefined){
  2696. node.title = title;
  2697. }
  2698. if(!node.span){
  2699. // Silently bail out if node was not rendered yet, assuming
  2700. // node.render() will be called as the node becomes visible
  2701. return;
  2702. }
  2703. // connector (expanded, expandable or simple)
  2704. // TODO: optiimize this if clause
  2705. if( level < opts.minExpandLevel ) {
  2706. if(level > 1){
  2707. if(aria){
  2708. ares.push("<span role='button' class='fancytree-expander' id='"+ ctx.node.key + "'></span>");
  2709. }else{
  2710. ares.push("<span class='fancytree-expander' id='"+ ctx.node.key + "'></span>");
  2711. }
  2712. }
  2713. // .. else (i.e. for root level) skip expander/connector alltogether
  2714. } else {
  2715. if(aria){
  2716. ares.push("<span role='button' class='fancytree-expander' id='"+ ctx.node.key + "'></span>");
  2717. }else{
  2718. ares.push("<span class='fancytree-expander' id='"+ ctx.node.key + "'></span>");
  2719. }
  2720. }
  2721. // Checkbox mode
  2722. if( opts.checkbox && node.hideCheckbox !== true && !node.isStatusNode() ) {
  2723. if(aria){
  2724. ares.push("<span role='checkbox' class='fancytree-checkbox' id='"+ ctx.node.key + "'></span>");
  2725. }else{
  2726. ares.push("<span class='fancytree-checkbox' id='"+ ctx.node.key + "'></span>");
  2727. }
  2728. }
  2729. // folder or doctype icon
  2730. role = aria ? " role='img'" : "";
  2731. if ( icon && typeof icon === "string" ) {
  2732. imageSrc = (icon.charAt(0) === "/") ? icon : ((opts.imagePath || "") + icon);
  2733. ares.push("<img src='" + imageSrc + "' class='fancytree-icon' alt='' />");
  2734. } else if ( node.data.iconclass ) {
  2735. // TODO: review and test and document
  2736. ares.push("<span " + role + " class='fancytree-custom-icon" + " " + node.data.iconclass + "'></span>");
  2737. } else if ( icon === true || (icon !== false && opts.icons !== false) ) {
  2738. // opts.icons defines the default behavior.
  2739. // node.icon == true/false can override this
  2740. ares.push("<span " + role + " class='fancytree-icon'></span>");
  2741. }
  2742. // node title
  2743. nodeTitle = "";
  2744. // TODO: currently undocumented; may be removed?
  2745. if ( opts.renderTitle ){
  2746. nodeTitle = opts.renderTitle.call(tree, {type: "renderTitle"}, ctx) || "";
  2747. }
  2748. if(!nodeTitle){
  2749. tooltip = node.tooltip ? " title='" + FT.escapeHtml(node.tooltip) + "'" : "";
  2750. id = aria ? " id='ftal_" + node.key + "'" : "";
  2751. role = aria ? " role='treeitem'" : "";
  2752. tabindex = opts.titlesTabbable ? " tabindex='0'" : "";
  2753. nodeTitle = "<span " + role + " class='fancytree-title'" + id + tooltip + tabindex + " id='title"+ node.key + "'>" + node.title + "</span>";
  2754. }
  2755. ares.push(nodeTitle);
  2756. // Note: this will trigger focusout, if node had the focus
  2757. //$(node.span).html(ares.join("")); // it will cleanup the jQuery data currently associated with SPAN (if any), but it executes more slowly
  2758. node.span.innerHTML = ares.join("");
  2759. // Update CSS classes
  2760. this.nodeRenderStatus(ctx);
  2761. },
  2762. /** Update element classes according to node state.
  2763. * @param {EventData} ctx
  2764. */
  2765. nodeRenderStatus: function(ctx) {
  2766. // Set classes for current status
  2767. var node = ctx.node,
  2768. tree = ctx.tree,
  2769. opts = ctx.options,
  2770. // nodeContainer = node[tree.nodeContainerAttrName],
  2771. hasChildren = node.hasChildren(),
  2772. isLastSib = node.isLastSibling(),
  2773. aria = opts.aria,
  2774. // $ariaElem = aria ? $(node[tree.ariaPropName]) : null,
  2775. $ariaElem = $(node.span).find(".fancytree-title"),
  2776. cn = opts._classNames,
  2777. cnList = [],
  2778. statusElem = node[tree.statusClassPropName];
  2779. if( !statusElem ){
  2780. // if this function is called for an unrendered node, ignore it (will be updated on nect render anyway)
  2781. return;
  2782. }
  2783. // Build a list of class names that we will add to the node <span>
  2784. cnList.push(cn.node);
  2785. if( tree.activeNode === node ){
  2786. cnList.push(cn.active);
  2787. // $(">span.fancytree-title", statusElem).attr("tabindex", "0");
  2788. // tree.$container.removeAttr("tabindex");
  2789. // }else{
  2790. // $(">span.fancytree-title", statusElem).removeAttr("tabindex");
  2791. // tree.$container.attr("tabindex", "0");
  2792. }
  2793. if( tree.focusNode === node ){
  2794. cnList.push(cn.focused);
  2795. if(aria){
  2796. // $(">span.fancytree-title", statusElem).attr("tabindex", "0");
  2797. // $(">span.fancytree-title", statusElem).attr("tabindex", "-1");
  2798. // TODO: is this the right element for this attribute?
  2799. $ariaElem
  2800. .attr("aria-activedescendant", true);
  2801. // .attr("tabindex", "-1");
  2802. }
  2803. }else if(aria){
  2804. // $(">span.fancytree-title", statusElem).attr("tabindex", "-1");
  2805. $ariaElem
  2806. .removeAttr("aria-activedescendant");
  2807. // .removeAttr("tabindex");
  2808. }
  2809. if( node.expanded ){
  2810. cnList.push(cn.expanded);
  2811. if(aria){
  2812. $ariaElem.attr("aria-expanded", true);
  2813. }
  2814. }else if(aria){
  2815. $ariaElem.removeAttr("aria-expanded");
  2816. }
  2817. if( node.folder ){
  2818. cnList.push(cn.folder);
  2819. }
  2820. if( hasChildren !== false ){
  2821. cnList.push(cn.hasChildren);
  2822. }
  2823. // TODO: required?
  2824. if( isLastSib ){
  2825. cnList.push(cn.lastsib);
  2826. }
  2827. if( node.lazy && node.children == null ){
  2828. cnList.push(cn.lazy);
  2829. }
  2830. if( node.partsel ){
  2831. cnList.push(cn.partsel);
  2832. }
  2833. if( node._isLoading ){
  2834. cnList.push(cn.loading);
  2835. }
  2836. if( node._error ){
  2837. cnList.push(cn.error);
  2838. }
  2839. if( node.selected ){
  2840. cnList.push(cn.selected);
  2841. if(aria){
  2842. $ariaElem.attr("aria-selected", true);
  2843. }
  2844. }else if(aria){
  2845. $ariaElem.attr("aria-selected", false);
  2846. }
  2847. if( node.extraClasses ){
  2848. cnList.push(node.extraClasses);
  2849. }
  2850. // IE6 doesn't correctly evaluate multiple class names,
  2851. // so we create combined class names that can be used in the CSS
  2852. if( hasChildren === false ){
  2853. cnList.push(cn.combinedExpanderPrefix + "n" +
  2854. (isLastSib ? "l" : "")
  2855. );
  2856. }else{
  2857. cnList.push(cn.combinedExpanderPrefix +
  2858. (node.expanded ? "e" : "c") +
  2859. (node.lazy && node.children == null ? "d" : "") +
  2860. (isLastSib ? "l" : "")
  2861. );
  2862. }
  2863. cnList.push(cn.combinedIconPrefix +
  2864. (node.expanded ? "e" : "c") +
  2865. (node.folder ? "f" : "")
  2866. );
  2867. // node.span.className = cnList.join(" ");
  2868. statusElem.className = cnList.join(" ");
  2869. // TODO: we should not set this in the <span> tag also, if we set it here:
  2870. // Maybe most (all) of the classes should be set in LI instead of SPAN?
  2871. if(node.li){
  2872. node.li.className = isLastSib ? cn.lastsib : "";
  2873. }
  2874. },
  2875. /** Activate node.
  2876. * flag defaults to true.
  2877. * If flag is true, the node is activated (must be a synchronous operation)
  2878. * If flag is false, the node is deactivated (must be a synchronous operation)
  2879. * @param {EventData} ctx
  2880. * @param {boolean} [flag=true]
  2881. * @param {object} [opts] additional options. Defaults to {noEvents: false}
  2882. */
  2883. nodeSetActive: function(ctx, flag, callOpts) {
  2884. // Handle user click / [space] / [enter], according to clickFolderMode.
  2885. callOpts = callOpts || {};
  2886. var subCtx,
  2887. node = ctx.node,
  2888. tree = ctx.tree,
  2889. opts = ctx.options,
  2890. noEvents = (callOpts.noEvents === true),
  2891. isActive = (node === tree.activeNode);
  2892. // flag defaults to true
  2893. flag = (flag !== false);
  2894. // node.debug("nodeSetActive", flag);
  2895. if(isActive === flag){
  2896. // Nothing to do
  2897. return _getResolvedPromise(node);
  2898. }else if(flag && !noEvents && this._triggerNodeEvent("beforeActivate", node, ctx.originalEvent) === false ){
  2899. // Callback returned false
  2900. return _getRejectedPromise(node, ["rejected"]);
  2901. }
  2902. if(flag){
  2903. if(tree.activeNode){
  2904. _assert(tree.activeNode !== node, "node was active (inconsistency)");
  2905. subCtx = $.extend({}, ctx, {node: tree.activeNode});
  2906. tree.nodeSetActive(subCtx, false);
  2907. _assert(tree.activeNode === null, "deactivate was out of sync?");
  2908. }
  2909. if(opts.activeVisible){
  2910. // tree.nodeMakeVisible(ctx);
  2911. node.makeVisible({scrollIntoView: false}); // nodeSetFocus will scroll
  2912. }
  2913. tree.activeNode = node;
  2914. tree.nodeRenderStatus(ctx);
  2915. tree.nodeSetFocus(ctx);
  2916. if( !noEvents ) {
  2917. tree._triggerNodeEvent("activate", node, ctx.originalEvent);
  2918. }
  2919. }else{
  2920. _assert(tree.activeNode === node, "node was not active (inconsistency)");
  2921. tree.activeNode = null;
  2922. this.nodeRenderStatus(ctx);
  2923. if( !noEvents ) {
  2924. ctx.tree._triggerNodeEvent("deactivate", node, ctx.originalEvent);
  2925. }
  2926. }
  2927. },
  2928. /** Expand or collapse node, return Deferred.promise.
  2929. *
  2930. * @param {EventData} ctx
  2931. * @param {boolean} [flag=true]
  2932. * @param {object} [opts] additional options. Defaults to {noAnimation: false, noEvents: false}
  2933. * @returns {$.Promise} The deferred will be resolved as soon as the (lazy)
  2934. * data was retrieved, rendered, and the expand animation finshed.
  2935. */
  2936. nodeSetExpanded: function(ctx, flag, callOpts) {
  2937. callOpts = callOpts || {};
  2938. var _afterLoad, dfd, i, l, parents, prevAC,
  2939. node = ctx.node,
  2940. tree = ctx.tree,
  2941. opts = ctx.options,
  2942. noAnimation = (callOpts.noAnimation === true),
  2943. noEvents = (callOpts.noEvents === true);
  2944. // flag defaults to true
  2945. flag = (flag !== false);
  2946. // node.debug("nodeSetExpanded(" + flag + ")");
  2947. if((node.expanded && flag) || (!node.expanded && !flag)){
  2948. // Nothing to do
  2949. // node.debug("nodeSetExpanded(" + flag + "): nothing to do");
  2950. return _getResolvedPromise(node);
  2951. }else if(flag && !node.lazy && !node.hasChildren() ){
  2952. // Prevent expanding of empty nodes
  2953. // return _getRejectedPromise(node, ["empty"]);
  2954. return _getResolvedPromise(node);
  2955. }else if( !flag && node.getLevel() < opts.minExpandLevel ) {
  2956. // Prevent collapsing locked levels
  2957. return _getRejectedPromise(node, ["locked"]);
  2958. }else if ( !noEvents && this._triggerNodeEvent("beforeExpand", node, ctx.originalEvent) === false ){
  2959. // Callback returned false
  2960. return _getRejectedPromise(node, ["rejected"]);
  2961. }
  2962. // If this node inside a collpased node, no animation and scrolling is needed
  2963. if( !noAnimation && !node.isVisible() ) {
  2964. noAnimation = callOpts.noAnimation = true;
  2965. }
  2966. dfd = new $.Deferred();
  2967. // Auto-collapse mode: collapse all siblings
  2968. if( flag && !node.expanded && opts.autoCollapse ) {
  2969. parents = node.getParentList(false, true);
  2970. prevAC = opts.autoCollapse;
  2971. try{
  2972. opts.autoCollapse = false;
  2973. for(i=0, l=parents.length; i<l; i++){
  2974. // TODO: should return promise?
  2975. this._callHook("nodeCollapseSiblings", parents[i], callOpts);
  2976. }
  2977. }finally{
  2978. opts.autoCollapse = prevAC;
  2979. }
  2980. }
  2981. // Trigger expand/collapse after expanding
  2982. dfd.done(function(){
  2983. if( flag && opts.autoScroll && !noAnimation ) {
  2984. // Scroll down to last child, but keep current node visible
  2985. node.getLastChild().scrollIntoView(true, {topNode: node}).always(function(){
  2986. if( !noEvents ) {
  2987. ctx.tree._triggerNodeEvent(flag ? "expand" : "collapse", ctx);
  2988. }
  2989. });
  2990. } else {
  2991. if( !noEvents ) {
  2992. ctx.tree._triggerNodeEvent(flag ? "expand" : "collapse", ctx);
  2993. }
  2994. }
  2995. });
  2996. // vvv Code below is executed after loading finished:
  2997. _afterLoad = function(callback){
  2998. var duration, easing, isVisible, isExpanded;
  2999. node.expanded = flag;
  3000. // Create required markup, but make sure the top UL is hidden, so we
  3001. // can animate later
  3002. tree._callHook("nodeRender", ctx, false, false, true);
  3003. // If the currently active node is now hidden, deactivate it
  3004. // if( opts.activeVisible && this.activeNode && ! this.activeNode.isVisible() ) {
  3005. // this.activeNode.deactivate();
  3006. // }
  3007. // Expanding a lazy node: set 'loading...' and call callback
  3008. // if( bExpand && this.data.isLazy && this.childList === null && !this._isLoading ) {
  3009. // this._loadContent();
  3010. // return;
  3011. // }
  3012. // Hide children, if node is collapsed
  3013. if( node.ul ) {
  3014. isVisible = (node.ul.style.display !== "none");
  3015. isExpanded = !!node.expanded;
  3016. if ( isVisible === isExpanded ) {
  3017. node.warn("nodeSetExpanded: UL.style.display already set");
  3018. } else if ( !opts.fx || noAnimation ) {
  3019. node.ul.style.display = ( node.expanded || !parent ) ? "" : "none";
  3020. } else {
  3021. duration = opts.fx.duration || 200;
  3022. easing = opts.fx.easing;
  3023. // node.debug("nodeSetExpanded: animate start...");
  3024. $(node.ul).animate(opts.fx, duration, easing, function(){
  3025. // node.debug("nodeSetExpanded: animate done");
  3026. callback();
  3027. });
  3028. return;
  3029. }
  3030. }
  3031. callback();
  3032. };
  3033. // ^^^ Code above is executed after loading finshed.
  3034. // Load lazy nodes, if any. Then continue with _afterLoad()
  3035. if(flag && node.lazy && node.hasChildren() === undefined){
  3036. // node.debug("nodeSetExpanded: load start...");
  3037. node.load().done(function(){
  3038. // node.debug("nodeSetExpanded: load done");
  3039. if(dfd.notifyWith){ // requires jQuery 1.6+
  3040. dfd.notifyWith(node, ["loaded"]);
  3041. }
  3042. _afterLoad(function () { dfd.resolveWith(node); });
  3043. }).fail(function(errMsg){
  3044. _afterLoad(function () { dfd.rejectWith(node, ["load failed (" + errMsg + ")"]); });
  3045. });
  3046. /*
  3047. var source = tree._triggerNodeEvent("lazyLoad", node, ctx.originalEvent);
  3048. _assert(typeof source !== "boolean", "lazyLoad event must return source in data.result");
  3049. node.debug("nodeSetExpanded: load start...");
  3050. this._callHook("nodeLoadChildren", ctx, source).done(function(){
  3051. node.debug("nodeSetExpanded: load done");
  3052. if(dfd.notifyWith){ // requires jQuery 1.6+
  3053. dfd.notifyWith(node, ["loaded"]);
  3054. }
  3055. _afterLoad.call(tree);
  3056. }).fail(function(errMsg){
  3057. dfd.rejectWith(node, ["load failed (" + errMsg + ")"]);
  3058. });
  3059. */
  3060. }else{
  3061. _afterLoad(function () { dfd.resolveWith(node); });
  3062. }
  3063. // node.debug("nodeSetExpanded: returns");
  3064. return dfd.promise();
  3065. },
  3066. /** Focus ot blur this node.
  3067. * @param {EventData} ctx
  3068. * @param {boolean} [flag=true]
  3069. */
  3070. nodeSetFocus: function(ctx, flag) {
  3071. // ctx.node.debug("nodeSetFocus(" + flag + ")");
  3072. var ctx2,
  3073. tree = ctx.tree,
  3074. node = ctx.node;
  3075. flag = (flag !== false);
  3076. // Blur previous node if any
  3077. if(tree.focusNode){
  3078. if(tree.focusNode === node && flag){
  3079. // node.debug("nodeSetFocus(" + flag + "): nothing to do");
  3080. return;
  3081. }
  3082. ctx2 = $.extend({}, ctx, {node: tree.focusNode});
  3083. tree.focusNode = null;
  3084. this._triggerNodeEvent("blur", ctx2);
  3085. this._callHook("nodeRenderStatus", ctx2);
  3086. }
  3087. // Set focus to container and node
  3088. if(flag){
  3089. if( !this.hasFocus() ){
  3090. node.debug("nodeSetFocus: forcing container focus");
  3091. // Note: we pass _calledByNodeSetFocus=true
  3092. this._callHook("treeSetFocus", ctx, true, true);
  3093. }
  3094. // this.nodeMakeVisible(ctx);
  3095. node.makeVisible({scrollIntoView: false});
  3096. tree.focusNode = node;
  3097. // node.debug("FOCUS...");
  3098. // $(node.span).find(".fancytree-title").focus();
  3099. this._triggerNodeEvent("focus", ctx);
  3100. // if(ctx.options.autoActivate){
  3101. // tree.nodeSetActive(ctx, true);
  3102. // }
  3103. if(ctx.options.autoScroll){
  3104. node.scrollIntoView();
  3105. }
  3106. this._callHook("nodeRenderStatus", ctx);
  3107. }
  3108. },
  3109. /** (De)Select node, return new status (sync).
  3110. *
  3111. * @param {EventData} ctx
  3112. * @param {boolean} [flag=true]
  3113. */
  3114. nodeSetSelected: function(ctx, flag) {
  3115. var node = ctx.node,
  3116. tree = ctx.tree,
  3117. opts = ctx.options;
  3118. // flag defaults to true
  3119. flag = (flag !== false);
  3120. node.debug("nodeSetSelected(" + flag + ")", ctx);
  3121. if( node.unselectable){
  3122. return;
  3123. }
  3124. // TODO: !!node.expanded is nicer, but doesn't pass jshint
  3125. // https://github.com/jshint/jshint/issues/455
  3126. // if( !!node.expanded === !!flag){
  3127. if((node.selected && flag) || (!node.selected && !flag)){
  3128. return !!node.selected;
  3129. }else if ( this._triggerNodeEvent("beforeSelect", node, ctx.originalEvent) === false ){
  3130. return !!node.selected;
  3131. }
  3132. if(flag && opts.selectMode === 1){
  3133. // single selection mode
  3134. if(tree.lastSelectedNode){
  3135. tree.lastSelectedNode.setSelected(false);
  3136. }
  3137. }else if(opts.selectMode === 3){
  3138. // multi.hier selection mode
  3139. node.selected = flag;
  3140. // this._fixSelectionState(node);
  3141. node.fixSelection3AfterClick();
  3142. }
  3143. node.selected = flag;
  3144. this.nodeRenderStatus(ctx);
  3145. tree.lastSelectedNode = flag ? node : null;
  3146. tree._triggerNodeEvent("select", ctx);
  3147. },
  3148. /** Show node status (ok, loading, error) using styles and a dummy child node.
  3149. *
  3150. * @param {EventData} ctx
  3151. * @param status
  3152. * @param message
  3153. * @param details
  3154. */
  3155. nodeSetStatus: function(ctx, status, message, details) {
  3156. var node = ctx.node,
  3157. tree = ctx.tree;
  3158. // cn = ctx.options._classNames;
  3159. function _clearStatusNode() {
  3160. // Remove dedicated dummy node, if any
  3161. var firstChild = ( node.children ? node.children[0] : null );
  3162. if ( firstChild && firstChild.isStatusNode() ) {
  3163. try{
  3164. // I've seen exceptions here with loadKeyPath...
  3165. if(node.ul){
  3166. node.ul.removeChild(firstChild.li);
  3167. firstChild.li = null; // avoid leaks (DT issue 215)
  3168. }
  3169. }catch(e){}
  3170. if( node.children.length === 1 ){
  3171. node.children = [];
  3172. }else{
  3173. node.children.shift();
  3174. }
  3175. }
  3176. }
  3177. function _setStatusNode(data, type) {
  3178. // Create/modify the dedicated dummy node for 'loading...' or
  3179. // 'error!' status. (only called for direct child of the invisible
  3180. // system root)
  3181. var firstChild = ( node.children ? node.children[0] : null );
  3182. if ( firstChild && firstChild.isStatusNode() ) {
  3183. $.extend(firstChild, data);
  3184. tree._callHook("nodeRender", firstChild);
  3185. } else {
  3186. data.key = "_statusNode";
  3187. node._setChildren([data]);
  3188. node.children[0].statusNodeType = type;
  3189. tree.render();
  3190. }
  3191. return node.children[0];
  3192. }
  3193. switch( status ){
  3194. case "ok":
  3195. _clearStatusNode();
  3196. // $(node.span).removeClass(cn.loading).removeClass(cn.error);
  3197. node._isLoading = false;
  3198. node._error = null;
  3199. node.renderStatus();
  3200. break;
  3201. case "loading":
  3202. // $(node.span).removeClass(cn.error).addClass(cn.loading);
  3203. if( !node.parent ) {
  3204. _setStatusNode({
  3205. title: tree.options.strings.loading + (message ? " (" + message + ") " : ""),
  3206. tooltip: details,
  3207. extraClasses: "fancytree-statusnode-wait"
  3208. }, status);
  3209. }
  3210. node._isLoading = true;
  3211. node._error = null;
  3212. node.renderStatus();
  3213. break;
  3214. case "error":
  3215. // $(node.span).removeClass(cn.loading).addClass(cn.error);
  3216. _setStatusNode({
  3217. title: tree.options.strings.loadError + (message ? " (" + message + ") " : ""),
  3218. tooltip: details,
  3219. extraClasses: "fancytree-statusnode-error"
  3220. }, status);
  3221. node._isLoading = false;
  3222. node._error = { message: message, details: details };
  3223. node.renderStatus();
  3224. break;
  3225. default:
  3226. $.error("invalid node status " + status);
  3227. }
  3228. },
  3229. /**
  3230. *
  3231. * @param {EventData} ctx
  3232. */
  3233. nodeToggleExpanded: function(ctx) {
  3234. return this.nodeSetExpanded(ctx, !ctx.node.expanded);
  3235. },
  3236. /**
  3237. * @param {EventData} ctx
  3238. */
  3239. nodeToggleSelected: function(ctx) {
  3240. return this.nodeSetSelected(ctx, !ctx.node.selected);
  3241. },
  3242. /** Remove all nodes.
  3243. * @param {EventData} ctx
  3244. */
  3245. treeClear: function(ctx) {
  3246. var tree = ctx.tree;
  3247. tree.activeNode = null;
  3248. tree.focusNode = null;
  3249. tree.$div.find(">ul.fancytree-container").empty();
  3250. // TODO: call destructors and remove reference loops
  3251. tree.rootNode.children = null;
  3252. },
  3253. /** Widget was created (called only once, even it re-initialized).
  3254. * @param {EventData} ctx
  3255. */
  3256. treeCreate: function(ctx) {
  3257. },
  3258. /** Widget was destroyed.
  3259. * @param {EventData} ctx
  3260. */
  3261. treeDestroy: function(ctx) {
  3262. },
  3263. /** Widget was (re-)initialized.
  3264. * @param {EventData} ctx
  3265. */
  3266. treeInit: function(ctx) {
  3267. //this.debug("Fancytree.treeInit()");
  3268. this.treeLoad(ctx);
  3269. },
  3270. /** Parse Fancytree from source, as configured in the options.
  3271. * @param {EventData} ctx
  3272. * @param {object} [source] optional new source (use last data otherwise)
  3273. */
  3274. treeLoad: function(ctx, source) {
  3275. var type, $ul,
  3276. tree = ctx.tree,
  3277. $container = ctx.widget.element,
  3278. dfd,
  3279. // calling context for root node
  3280. rootCtx = $.extend({}, ctx, {node: this.rootNode});
  3281. if(tree.rootNode.children){
  3282. this.treeClear(ctx);
  3283. }
  3284. source = source || this.options.source;
  3285. if(!source){
  3286. type = $container.data("type") || "html";
  3287. switch(type){
  3288. case "html":
  3289. $ul = $container.find(">ul:first");
  3290. /*$ul.addClass("ui-fancytree-source ui-helper-hidden"); mcareplus개발시 json으로 tree구성하는것이 아니므로 제거*/
  3291. source = $.ui.fancytree.parseHtml($ul);
  3292. // allow to init tree.data.foo from <ul data-foo=''>
  3293. this.data = $.extend(this.data, _getElementDataAsDict($ul));
  3294. break;
  3295. case "json":
  3296. // $().addClass("ui-helper-hidden");
  3297. source = $.parseJSON($container.text());
  3298. if(source.children){
  3299. if(source.title){tree.title = source.title;}
  3300. source = source.children;
  3301. }
  3302. break;
  3303. default:
  3304. $.error("Invalid data-type: " + type);
  3305. }
  3306. }else if(typeof source === "string"){
  3307. // TODO: source is an element ID
  3308. _raiseNotImplemented();
  3309. }
  3310. // $container.addClass("ui-widget ui-widget-content ui-corner-all");
  3311. // Trigger fancytreeinit after nodes have been loaded
  3312. dfd = this.nodeLoadChildren(rootCtx, source).done(function(){
  3313. tree.render();
  3314. if( ctx.options.selectMode === 3 ){
  3315. tree.rootNode.fixSelection3FromEndNodes();
  3316. }
  3317. tree._triggerTreeEvent("init", true);
  3318. }).fail(function(){
  3319. tree.render();
  3320. tree._triggerTreeEvent("init", false);
  3321. });
  3322. return dfd;
  3323. },
  3324. /** Node was inserted into or removed from the tree.
  3325. * @param {EventData} ctx
  3326. * @param {boolean} add
  3327. * @param {FancytreeNode} node
  3328. */
  3329. treeRegisterNode: function(ctx, add, node) {
  3330. },
  3331. /** Widget got focus.
  3332. * @param {EventData} ctx
  3333. * @param {boolean} [flag=true]
  3334. */
  3335. treeSetFocus: function(ctx, flag, _calledByNodeSetFocus) {
  3336. flag = (flag !== false);
  3337. // this.debug("treeSetFocus(" + flag + "), _calledByNodeSetFocus: " + _calledByNodeSetFocus);
  3338. // this.debug(" focusNode: " + this.focusNode);
  3339. // this.debug(" activeNode: " + this.activeNode);
  3340. if( flag !== this.hasFocus() ){
  3341. this._hasFocus = flag;
  3342. this.$container.toggleClass("fancytree-treefocus", flag);
  3343. this._triggerTreeEvent(flag ? "focusTree" : "blurTree");
  3344. }
  3345. }
  3346. });
  3347. /* ******************************************************************************
  3348. * jQuery UI widget boilerplate
  3349. */
  3350. /**
  3351. * The plugin (derrived from <a href=" http://api.jqueryui.com/jQuery.widget/">jQuery.Widget</a>).<br>
  3352. * This constructor is not called directly. Use `$(selector).fancytree({})`
  3353. * to initialize the plugin instead.<br>
  3354. * <pre class="sh_javascript sunlight-highlight-javascript">// Access widget methods and members:
  3355. * var tree = $("#tree").fancytree("getTree");
  3356. * var node = $("#tree").fancytree("getActiveNode", "1234");
  3357. * </pre>
  3358. *
  3359. * @mixin Fancytree_Widget
  3360. */
  3361. $.widget("ui.fancytree",
  3362. /** @lends Fancytree_Widget# */
  3363. {
  3364. /**These options will be used as defaults
  3365. * @type {FancytreeOptions}
  3366. */
  3367. options:
  3368. {
  3369. activeVisible: true,
  3370. ajax: {
  3371. type: "GET",
  3372. cache: false, // false: Append random '_' argument to the request url to prevent caching.
  3373. // timeout: 0, // >0: Make sure we get an ajax error if server is unreachable
  3374. dataType: "json" // Expect json format and pass json object to callbacks.
  3375. }, //
  3376. aria: false, // TODO: default to true
  3377. autoActivate: true,
  3378. autoCollapse: false,
  3379. // autoFocus: false,
  3380. autoScroll: false,
  3381. checkbox: false,
  3382. /**defines click behavior*/
  3383. clickFolderMode: 4,
  3384. debugLevel: null, // 0..2 (null: use global setting $.ui.fancytree.debugInfo)
  3385. disabled: false, // TODO: required anymore?
  3386. enableAspx: true, // TODO: document
  3387. extensions: [],
  3388. fx: { height: "toggle", duration: 200 },
  3389. generateIds: false,
  3390. icons: true,
  3391. idPrefix: "ft_",
  3392. keyboard: true,
  3393. keyPathSeparator: "/",
  3394. minExpandLevel: 1,
  3395. scrollOfs: {top: 0, bottom: 0},
  3396. scrollParent: null,
  3397. selectMode: 2,
  3398. strings: {
  3399. loading: "Loading&#8230;",
  3400. loadError: "Load error!"
  3401. },
  3402. tabbable: true,
  3403. titlesTabbable: false,
  3404. _classNames: {
  3405. node: "fancytree-node",
  3406. folder: "fancytree-folder",
  3407. combinedExpanderPrefix: "fancytree-exp-",
  3408. combinedIconPrefix: "fancytree-ico-",
  3409. hasChildren: "fancytree-has-children",
  3410. active: "fancytree-active",
  3411. selected: "fancytree-selected",
  3412. expanded: "fancytree-expanded",
  3413. lazy: "fancytree-lazy",
  3414. focused: "fancytree-focused",
  3415. partsel: "fancytree-partsel",
  3416. lastsib: "fancytree-lastsib",
  3417. loading: "fancytree-loading",
  3418. error: "fancytree-error"
  3419. },
  3420. // events
  3421. lazyLoad: null,
  3422. postProcess: null
  3423. },
  3424. /* Set up the widget, Called on first $().fancytree() */
  3425. _create: function() {
  3426. this.tree = new Fancytree(this);
  3427. this.$source = this.source || this.element.data("type") === "json" ? this.element
  3428. : this.element.find(">ul:first");
  3429. // Subclass Fancytree instance with all enabled extensions
  3430. var extension, extName, i,
  3431. extensions = this.options.extensions,
  3432. base = this.tree;
  3433. for(i=0; i<extensions.length; i++){
  3434. extName = extensions[i];
  3435. extension = $.ui.fancytree._extensions[extName];
  3436. if(!extension){
  3437. $.error("Could not apply extension '" + extName + "' (it is not registered, did you forget to include it?)");
  3438. }
  3439. // Add extension options as tree.options.EXTENSION
  3440. // _assert(!this.tree.options[extName], "Extension name must not exist as option name: " + extName);
  3441. this.tree.options[extName] = $.extend(true, {}, extension.options, this.tree.options[extName]);
  3442. // Add a namespace tree.ext.EXTENSION, to hold instance data
  3443. _assert(this.tree.ext[extName] === undefined, "Extension name must not exist as Fancytree.ext attribute: '" + extName + "'");
  3444. // this.tree[extName] = extension;
  3445. this.tree.ext[extName] = {};
  3446. // Subclass Fancytree methods using proxies.
  3447. _subclassObject(this.tree, base, extension, extName);
  3448. // current extension becomes base for the next extension
  3449. base = extension;
  3450. }
  3451. //
  3452. this.tree._callHook("treeCreate", this.tree);
  3453. // Note: 'fancytreecreate' event is fired by widget base class
  3454. // this.tree._triggerTreeEvent("create");
  3455. },
  3456. /* Called on every $().fancytree() */
  3457. _init: function() {
  3458. this.tree._callHook("treeInit", this.tree);
  3459. // TODO: currently we call bind after treeInit, because treeInit
  3460. // might change tree.$container.
  3461. // It would be better, to move ebent binding into hooks altogether
  3462. this._bind();
  3463. },
  3464. /* Use the _setOption method to respond to changes to options */
  3465. _setOption: function(key, value) {
  3466. var callDefault = true,
  3467. rerender = false;
  3468. switch( key ) {
  3469. case "aria":
  3470. case "checkbox":
  3471. case "icons":
  3472. case "minExpandLevel":
  3473. case "tabbable":
  3474. // case "nolink":
  3475. this.tree._callHook("treeCreate", this.tree);
  3476. rerender = true;
  3477. break;
  3478. case "source":
  3479. callDefault = false;
  3480. this.tree._callHook("treeLoad", this.tree, value);
  3481. break;
  3482. }
  3483. this.tree.debug("set option " + key + "=" + value + " <" + typeof(value) + ">");
  3484. if(callDefault){
  3485. // In jQuery UI 1.8, you have to manually invoke the _setOption method from the base widget
  3486. $.Widget.prototype._setOption.apply(this, arguments);
  3487. // TODO: In jQuery UI 1.9 and above, you use the _super method instead
  3488. // this._super( "_setOption", key, value );
  3489. }
  3490. if(rerender){
  3491. this.tree.render(true, false); // force, not-deep
  3492. }
  3493. },
  3494. /** Use the destroy method to clean up any modifications your widget has made to the DOM */
  3495. destroy: function() {
  3496. this._unbind();
  3497. this.tree._callHook("treeDestroy", this.tree);
  3498. // this.element.removeClass("ui-widget ui-widget-content ui-corner-all");
  3499. this.tree.$div.find(">ul.fancytree-container").remove();
  3500. this.$source && this.$source.removeClass("ui-helper-hidden");
  3501. // In jQuery UI 1.8, you must invoke the destroy method from the base widget
  3502. $.Widget.prototype.destroy.call(this);
  3503. // TODO: delete tree and nodes to make garbage collect easier?
  3504. // TODO: In jQuery UI 1.9 and above, you would define _destroy instead of destroy and not call the base method
  3505. },
  3506. // -------------------------------------------------------------------------
  3507. /* Remove all event handlers for our namespace */
  3508. _unbind: function() {
  3509. var ns = this.tree._ns;
  3510. this.element.unbind(ns);
  3511. this.tree.$container.unbind(ns);
  3512. $(document).unbind(ns);
  3513. },
  3514. /* Add mouse and kyboard handlers to the container */
  3515. _bind: function() {
  3516. var that = this,
  3517. opts = this.options,
  3518. tree = this.tree,
  3519. ns = tree._ns
  3520. // selstartEvent = ( $.support.selectstart ? "selectstart" : "mousedown" )
  3521. ;
  3522. // Remove all previuous handlers for this tree
  3523. this._unbind();
  3524. //alert("keydown" + ns + "foc=" + tree.hasFocus() + tree.$container);
  3525. // tree.debug("bind events; container: ", tree.$container);
  3526. tree.$container.on("focusin" + ns + " focusout" + ns, function(event){
  3527. var node = FT.getNode(event),
  3528. flag = (event.type === "focusin");
  3529. // tree.debug("Tree container got event " + event.type, node, event);
  3530. // tree.treeOnFocusInOut.call(tree, event);
  3531. if(node){
  3532. // For example clicking into an <input> that is part of a node
  3533. tree._callHook("nodeSetFocus", node, flag);
  3534. }else{
  3535. tree._callHook("treeSetFocus", tree, flag);
  3536. }
  3537. }).on("selectstart" + ns, "span.fancytree-title", function(event){
  3538. // prevent mouse-drags to select text ranges
  3539. // tree.debug("<span title> got event " + event.type);
  3540. event.preventDefault();
  3541. }).on("keydown" + ns, function(event){
  3542. // TODO: also bind keyup and keypress
  3543. // tree.debug("got event " + event.type + ", hasFocus:" + tree.hasFocus());
  3544. // if(opts.disabled || opts.keyboard === false || !tree.hasFocus() ){
  3545. if(opts.disabled || opts.keyboard === false ){
  3546. return true;
  3547. }
  3548. var res,
  3549. node = tree.focusNode, // node may be null
  3550. ctx = tree._makeHookContext(node || tree, event),
  3551. prevPhase = tree.phase;
  3552. try {
  3553. tree.phase = "userEvent";
  3554. // If a 'fancytreekeydown' handler returns false, skip the default
  3555. // handling (implemented by tree.nodeKeydown()).
  3556. if(node){
  3557. res = tree._triggerNodeEvent("keydown", node, event);
  3558. }else{
  3559. res = tree._triggerTreeEvent("keydown", event);
  3560. }
  3561. if ( res === "preventNav" ){
  3562. res = true; // prevent keyboard navigation, but don't prevent default handling of embedded input controls
  3563. } else if ( res !== false ){
  3564. res = tree._callHook("nodeKeydown", ctx);
  3565. }
  3566. return res;
  3567. } finally {
  3568. tree.phase = prevPhase;
  3569. }
  3570. }).on("click" + ns + " dblclick" + ns, function(event){
  3571. if(opts.disabled){
  3572. return true;
  3573. }
  3574. var ctx,
  3575. et = FT.getEventTarget(event),
  3576. node = et.node,
  3577. tree = that.tree,
  3578. prevPhase = tree.phase;
  3579. if( !node ){
  3580. return true; // Allow bubbling of other events
  3581. }
  3582. ctx = tree._makeHookContext(node, event);
  3583. // that.tree.debug("event(" + event.type + "): node: ", node);
  3584. try {
  3585. tree.phase = "userEvent";
  3586. switch(event.type) {
  3587. case "click":
  3588. ctx.targetType = et.type;
  3589. return ( tree._triggerNodeEvent("click", ctx, event) === false ) ? false : tree._callHook("nodeClick", ctx);
  3590. case "dblclick":
  3591. ctx.targetType = et.type;
  3592. return ( tree._triggerNodeEvent("dblclick", ctx, event) === false ) ? false : tree._callHook("nodeDblclick", ctx);
  3593. }
  3594. // } catch(e) {
  3595. // // var _ = null; // DT issue 117 // TODO
  3596. // $.error(e);
  3597. } finally {
  3598. tree.phase = prevPhase;
  3599. }
  3600. });
  3601. },
  3602. /** Return the active node or null.
  3603. * @returns {FancytreeNode}
  3604. */
  3605. getActiveNode: function() {
  3606. return this.tree.activeNode;
  3607. },
  3608. /** Return the matching node or null.
  3609. * @param {string} key
  3610. * @returns {FancytreeNode}
  3611. */
  3612. getNodeByKey: function(key) {
  3613. return this.tree.getNodeByKey(key);
  3614. },
  3615. /** Return the invisible system root node.
  3616. * @returns {FancytreeNode}
  3617. */
  3618. getRootNode: function() {
  3619. return this.tree.rootNode;
  3620. },
  3621. /** Return the current tree instance.
  3622. * @returns {Fancytree}
  3623. */
  3624. getTree: function() {
  3625. return this.tree;
  3626. }
  3627. });
  3628. // $.ui.fancytree was created by the widget factory. Create a local shortcut:
  3629. FT = $.ui.fancytree;
  3630. /**
  3631. * Static members in the `$.ui.fancytree` namespace.<br>
  3632. * <br>
  3633. * <pre class="sh_javascript sunlight-highlight-javascript">// Access static members:
  3634. * var node = $.ui.fancytree.getNode(element);
  3635. * alert($.ui.fancytree.version);
  3636. * </pre>
  3637. *
  3638. * @mixin Fancytree_Static
  3639. */
  3640. $.extend($.ui.fancytree,
  3641. /** @lends Fancytree_Static# */
  3642. {
  3643. /** @type {string} */
  3644. version: "2.2.0", // Set to semver by 'grunt release'
  3645. /** @type {string} */
  3646. buildType: "production", // Set to 'production' by 'grunt build'
  3647. /** @type {int} */
  3648. debugLevel: 1, // Set to 1 by 'grunt build'
  3649. // Used by $.ui.fancytree.debug() and as default for tree.options.debugLevel
  3650. _nextId: 1,
  3651. _nextNodeKey: 1,
  3652. _extensions: {},
  3653. // focusTree: null,
  3654. /** Expose class object as $.ui.fancytree._FancytreeClass */
  3655. _FancytreeClass: Fancytree,
  3656. /** Expose class object as $.ui.fancytree._FancytreeNodeClass */
  3657. _FancytreeNodeClass: FancytreeNode,
  3658. /* Feature checks to provide backwards compatibility */
  3659. jquerySupports: {
  3660. // http://jqueryui.com/upgrade-guide/1.9/#deprecated-offset-option-merged-into-my-and-at
  3661. positionMyOfs: isVersionAtLeast($.ui.version, 1, 9)
  3662. },
  3663. /** Throw an error if condition fails (debug method).
  3664. * @param {boolean} cond
  3665. * @param {string} msg
  3666. */
  3667. assert: function(cond, msg){
  3668. return _assert(cond, msg);
  3669. },
  3670. /** Return a function that executes *fn* at most every *timeout* ms.
  3671. * @param {integer} timeout
  3672. * @param {function} fn
  3673. * @param {boolean} [invokeAsap=false]
  3674. * @param {any} [ctx]
  3675. */
  3676. debounce : function(timeout, fn, invokeAsap, ctx) {
  3677. var timer;
  3678. if(arguments.length === 3 && typeof invokeAsap !== "boolean") {
  3679. ctx = invokeAsap;
  3680. invokeAsap = false;
  3681. }
  3682. return function() {
  3683. var args = arguments;
  3684. ctx = ctx || this;
  3685. invokeAsap && !timer && fn.apply(ctx, args);
  3686. clearTimeout(timer);
  3687. timer = setTimeout(function() {
  3688. invokeAsap || fn.apply(ctx, args);
  3689. timer = null;
  3690. }, timeout);
  3691. };
  3692. },
  3693. /** Write message to console if debugLevel >= 2
  3694. * @param {string} msg
  3695. */
  3696. debug: function(msg){
  3697. /*jshint expr:true */
  3698. ($.ui.fancytree.debugLevel >= 2) && consoleApply("log", arguments);
  3699. },
  3700. /** Write error message to console.
  3701. * @param {string} msg
  3702. */
  3703. error: function(msg){
  3704. consoleApply("error", arguments);
  3705. },
  3706. /** Convert &lt;, &gt;, &amp;, &quot;, &#39;, &#x2F; to the equivalent entitites.
  3707. *
  3708. * @param {string} s
  3709. * @returns {string}
  3710. */
  3711. escapeHtml: function(s){
  3712. return ("" + s).replace(/[&<>"'\/]/g, function (s) {
  3713. return ENTITY_MAP[s];
  3714. });
  3715. },
  3716. /** Inverse of escapeHtml().
  3717. *
  3718. * @param {string} s
  3719. * @returns {string}
  3720. */
  3721. unescapeHtml: function(s){
  3722. var e = document.createElement("div");
  3723. e.innerHTML = s;
  3724. return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
  3725. },
  3726. /** Return a {node: FancytreeNode, type: TYPE} object for a mouse event.
  3727. *
  3728. * @param {Event} event Mouse event, e.g. click, ...
  3729. * @returns {string} 'title' | 'prefix' | 'expander' | 'checkbox' | 'icon' | undefined
  3730. */
  3731. getEventTargetType: function(event){
  3732. return this.getEventTarget(event).type;
  3733. },
  3734. /** Return a {node: FancytreeNode, type: TYPE} object for a mouse event.
  3735. *
  3736. * @param {Event} event Mouse event, e.g. click, ...
  3737. * @returns {object} Return a {node: FancytreeNode, type: TYPE} object
  3738. * TYPE: 'title' | 'prefix' | 'expander' | 'checkbox' | 'icon' | undefined
  3739. */
  3740. getEventTarget: function(event){
  3741. var tcn = event && event.target ? event.target.className : "",
  3742. res = {node: this.getNode(event.target), type: undefined};
  3743. // We use a fast version of $(res.node).hasClass()
  3744. // See http://jsperf.com/test-for-classname/2
  3745. if( /\bfancytree-title\b/.test(tcn) ){
  3746. res.type = "title";
  3747. }else if( /\bfancytree-expander\b/.test(tcn) ){
  3748. res.type = (res.node.hasChildren() === false ? "prefix" : "expander");
  3749. }else if( /\bfancytree-checkbox\b/.test(tcn) || /\bfancytree-radio\b/.test(tcn) ){
  3750. res.type = "checkbox";
  3751. }else if( /\bfancytree-icon\b/.test(tcn) ){
  3752. res.type = "icon";
  3753. }else if( /\bfancytree-node\b/.test(tcn) ){
  3754. // Somewhere near the title
  3755. res.type = "title";
  3756. }else if( event && event.target && $(event.target).closest(".fancytree-title").length ) {
  3757. // #228: clicking an embedded element inside a title
  3758. res.type = "title";
  3759. }
  3760. return res;
  3761. },
  3762. /** Return a FancytreeNode instance from element.
  3763. *
  3764. * @param {Element | jQueryObject | Event} el
  3765. * @returns {FancytreeNode} matching node or null
  3766. */
  3767. getNode: function(el){
  3768. if(el instanceof FancytreeNode){
  3769. return el; // el already was a FancytreeNode
  3770. }else if(el.selector !== undefined){
  3771. el = el[0]; // el was a jQuery object: use the DOM element
  3772. }else if(el.originalEvent !== undefined){
  3773. el = el.target; // el was an Event
  3774. }
  3775. while( el ) {
  3776. if(el.ftnode) {
  3777. return el.ftnode;
  3778. }
  3779. el = el.parentNode;
  3780. }
  3781. return null;
  3782. },
  3783. /* Return a Fancytree instance from element.
  3784. * TODO: this function could help to get around the data('fancytree') / data('ui-fancytree') problem
  3785. * @param {Element | jQueryObject | Event} el
  3786. * @returns {Fancytree} matching tree or null
  3787. * /
  3788. getTree: function(el){
  3789. if(el instanceof Fancytree){
  3790. return el; // el already was a Fancytree
  3791. }else if(el.selector !== undefined){
  3792. el = el[0]; // el was a jQuery object: use the DOM element
  3793. }else if(el.originalEvent !== undefined){
  3794. el = el.target; // el was an Event
  3795. }
  3796. ...
  3797. return null;
  3798. },
  3799. */
  3800. /** Write message to console if debugLevel >= 1
  3801. * @param {string} msg
  3802. */
  3803. info: function(msg){
  3804. /*jshint expr:true */
  3805. ($.ui.fancytree.debugLevel >= 1) && consoleApply("info", arguments);
  3806. },
  3807. /**
  3808. * Parse tree data from HTML <ul> markup
  3809. *
  3810. * @param {jQueryObject} $ul
  3811. * @returns {NodeData[]}
  3812. */
  3813. parseHtml: function($ul) {
  3814. // TODO: understand this:
  3815. /*jshint validthis:true */
  3816. var extraClasses, i, l, iPos, tmp, tmp2, classes, className,
  3817. $children = $ul.find(">li"),
  3818. children = [];
  3819. $children.each(function() {
  3820. var allData,
  3821. $li = $(this),
  3822. $liSpan = $li.find(">span:first", this),
  3823. $liA = $liSpan.length ? null : $li.find(">a:first"),
  3824. d = { tooltip: null, data: {} };
  3825. if( $liSpan.length ) {
  3826. d.title = $liSpan.html();
  3827. } else if( $liA && $liA.length ) {
  3828. // If a <li><a> tag is specified, use it literally and extract href/target.
  3829. d.title = $liA.html();
  3830. d.data.href = $liA.attr("href");
  3831. d.data.target = $liA.attr("target");
  3832. d.tooltip = $liA.attr("title");
  3833. } else {
  3834. // If only a <li> tag is specified, use the trimmed string up to
  3835. // the next child <ul> tag.
  3836. d.title = $li.html();
  3837. iPos = d.title.search(/<ul/i);
  3838. if( iPos >= 0 ){
  3839. d.title = d.title.substring(0, iPos);
  3840. }
  3841. }
  3842. d.title = $.trim(d.title);
  3843. // Make sure all fields exist
  3844. for(i=0, l=CLASS_ATTRS.length; i<l; i++){
  3845. d[CLASS_ATTRS[i]] = undefined;
  3846. }
  3847. // Initialize to `true`, if class is set and collect extraClasses
  3848. classes = this.className.split(" ");
  3849. extraClasses = [];
  3850. for(i=0, l=classes.length; i<l; i++){
  3851. className = classes[i];
  3852. if(CLASS_ATTR_MAP[className]){
  3853. d[className] = true;
  3854. }else{
  3855. extraClasses.push(className);
  3856. }
  3857. }
  3858. d.extraClasses = extraClasses.join(" ");
  3859. // Parse node options from ID, title and class attributes
  3860. tmp = $li.attr("title");
  3861. if( tmp ){
  3862. d.tooltip = tmp; // overrides <a title='...'>
  3863. }
  3864. tmp = $li.attr("id");
  3865. if( tmp ){
  3866. d.key = tmp;
  3867. }
  3868. // Add <li data-NAME='...'> as node.data.NAME
  3869. allData = _getElementDataAsDict($li);
  3870. if(allData && !$.isEmptyObject(allData)) {
  3871. // #56: Allow to set special node.attributes from data-...
  3872. for(i=0, l=NODE_ATTRS.length; i<l; i++){
  3873. tmp = NODE_ATTRS[i];
  3874. tmp2 = allData[tmp];
  3875. if( tmp2 != null ) {
  3876. delete allData[tmp];
  3877. d[tmp] = tmp2;
  3878. }
  3879. }
  3880. // All other data-... goes to node.data...
  3881. $.extend(d.data, allData);
  3882. }
  3883. // Recursive reading of child nodes, if LI tag contains an UL tag
  3884. $ul = $li.find(">ul:first");
  3885. if( $ul.length ) {
  3886. d.children = $.ui.fancytree.parseHtml($ul);
  3887. }else{
  3888. d.children = d.lazy ? undefined : null;
  3889. }
  3890. children.push(d);
  3891. // FT.debug("parse ", d, children);
  3892. });
  3893. return children;
  3894. },
  3895. /** Add Fancytree extension definition to the list of globally available extensions.
  3896. *
  3897. * @param {object} definition
  3898. */
  3899. registerExtension: function(definition){
  3900. _assert(definition.name != null, "extensions must have a `name` property.");
  3901. _assert(definition.version != null, "extensions must have a `version` property.");
  3902. $.ui.fancytree._extensions[definition.name] = definition;
  3903. },
  3904. /** Write warning message to console.
  3905. * @param {string} msg
  3906. */
  3907. warn: function(msg){
  3908. consoleApply("warn", arguments);
  3909. }
  3910. });
  3911. }(jQuery, window, document));
  3912. // Extending Fancytree
  3913. // ===================
  3914. //
  3915. // See also the [live demo](http://wwwendt.de/tech/fancytree/demo/sample-ext-childcounter.html) of this code.
  3916. //
  3917. // Every extension should have a comment header containing some information
  3918. // about the author, copyright and licensing. Also a pointer to the latest
  3919. // source code.
  3920. // Prefix with `/*!` so the comment is not removed by the minifier.
  3921. /*!
  3922. * jquery.fancytree.childcounter.js
  3923. *
  3924. * Add a child counter bubble to tree nodes.
  3925. * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
  3926. *
  3927. * Copyright (c) 2014, Martin Wendt (http://wwWendt.de)
  3928. *
  3929. * Released under the MIT license
  3930. * https://github.com/mar10/fancytree/wiki/LicenseInfo
  3931. *
  3932. * @version 2.2.0
  3933. * @date 2014-06-28T17:15
  3934. */
  3935. // To keep the global namespace clean, we wrap everything in a closure
  3936. ;(function($, undefined) {
  3937. // Consider to use [strict mode](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
  3938. "use strict";
  3939. // The [coding guidelines](http://contribute.jquery.org/style-guide/js/)
  3940. // require jshint compliance.
  3941. // But for this sample, we want to allow unused variables for demonstration purpose.
  3942. /*jshint unused:false */
  3943. // Adding methods
  3944. // --------------
  3945. // New member functions can be added to the `Fancytree` class.
  3946. // This function will be available for every tree instance.
  3947. //
  3948. // var tree = $("#tree").fancytree("getTree");
  3949. // tree.countSelected(false);
  3950. $.ui.fancytree._FancytreeClass.prototype.countSelected = function(topOnly){
  3951. var tree = this,
  3952. treeOptions = tree.options;
  3953. return tree.getSelectedNodes(topOnly).length;
  3954. };
  3955. // The `FancytreeNode` class can also be easily extended. This would be called
  3956. // like
  3957. //
  3958. // node.toUpper();
  3959. $.ui.fancytree._FancytreeNodeClass.prototype.toUpper = function(){
  3960. var node = this;
  3961. return node.setTitle(node.title.toUpperCase());
  3962. };
  3963. // Finally, we can extend the widget API and create functions that are called
  3964. // like so:
  3965. //
  3966. // $("#tree").fancytree("widgetMethod1", "abc");
  3967. $.ui.fancytree.prototype.widgetMethod1 = function(arg1){
  3968. var tree = this.tree;
  3969. return arg1;
  3970. };
  3971. // Register a Fancytree extension
  3972. // ------------------------------
  3973. // A full blown extension, extension is available for all trees and can be
  3974. // enabled like so (see also the [live demo](http://wwwendt.de/tech/fancytree/demo/sample-ext-childcounter.html)):
  3975. //
  3976. // <script src="../src/jquery.fancytree.js" type="text/javascript"></script>
  3977. // <script src="../src/jquery.fancytree.childcounter.js" type="text/javascript"></script>
  3978. // ...
  3979. //
  3980. // $("#tree").fancytree({
  3981. // extensions: ["childcounter"],
  3982. // childcounter: {
  3983. // hideExpanded: true
  3984. // },
  3985. // ...
  3986. // });
  3987. //
  3988. /* 'childcounter' extension */
  3989. $.ui.fancytree.registerExtension({
  3990. // Every extension must be registered by a unique name.
  3991. name: "childcounter",
  3992. // Version information should be compliant with [semver](http://semver.org)
  3993. version: "1.0.0",
  3994. // Extension specific options and their defaults.
  3995. // This options will be available as `tree.options.childcounter.hideExpanded`
  3996. options: {
  3997. deep: true,
  3998. hideZeros: true,
  3999. hideExpanded: false
  4000. },
  4001. // Attributes other than `options` (or functions) can be defined here, and
  4002. // will be added to the tree.ext.EXTNAME namespace, in this case `tree.ext.childcounter.foo`.
  4003. // They can also be accessed as `this._local.foo` from within the extension
  4004. // methods.
  4005. foo: 42,
  4006. // Local functions are prefixed with an underscore '_'.
  4007. // Callable as `this._local._appendCounter()`.
  4008. _appendCounter: function(bar){
  4009. var tree = this;
  4010. },
  4011. // **Override virtual methods for this extension.**
  4012. //
  4013. // Fancytree implements a number of 'hook methods', prefixed by 'node...' or 'tree...'.
  4014. // with a `ctx` argument (see [EventData](http://www.wwwendt.de/tech/fancytree/doc/jsdoc/global.html#EventData)
  4015. // for details) and an extended calling context:<br>
  4016. // `this` : the Fancytree instance<br>
  4017. // `this._local`: the namespace that contains extension attributes and private methods (same as this.ext.EXTNAME)<br>
  4018. // `this._super`: the virtual function that was overridden (member of previous extension or Fancytree)
  4019. //
  4020. // See also the [complete list of available hook functions](http://www.wwwendt.de/tech/fancytree/doc/jsdoc/Fancytree_Hooks.html).
  4021. /* Init */
  4022. // `treeInit` is triggered when a tree is initalized. We can set up classes or
  4023. // bind event handlers here...
  4024. treeInit: function(ctx){
  4025. var tree = this, // same as ctx.tree,
  4026. opts = ctx.options,
  4027. extOpts = ctx.options.childcounter;
  4028. // Optionally check for dependencies with other extensions
  4029. /* this._requireExtension("glyph", false, false); */
  4030. // Call the base implementation
  4031. this._super(ctx);
  4032. // Add a class to the tree container
  4033. this.$container.addClass("fancytree-ext-childcounter");
  4034. },
  4035. // Destroy this tree instance (we only call the default implementation, so
  4036. // this method could as well be omitted).
  4037. treeDestroy: function(ctx){
  4038. this._super(ctx);
  4039. },
  4040. // Overload the `renderTitle` hook, to append a counter badge
  4041. nodeRenderTitle: function(ctx, title) {
  4042. var node = ctx.node,
  4043. extOpts = ctx.options.childcounter,
  4044. count = (node.data.childCounter == null) ? node.countChildren(extOpts.deep) : +node.data.childCounter;
  4045. // Let the base implementation render the title
  4046. this._super(ctx, title);
  4047. // Append a counter badge
  4048. if( (count || ! extOpts.hideZeros) && (!node.isExpanded() || !extOpts.hideExpanded) ){
  4049. $("span.fancytree-icon", node.span).append($("<span class='fancytree-childcounter'/>").text(count));
  4050. }
  4051. },
  4052. // Overload the `setExpanded` hook, so the counters are updated
  4053. nodeSetExpanded: function(ctx, flag, opts) {
  4054. var tree = ctx.tree,
  4055. node = ctx.node;
  4056. // Let the base implementation expand/collapse the node, then redraw the title
  4057. // after the animation has finished
  4058. return this._super(ctx, flag, opts).always(function(){
  4059. tree.nodeRenderTitle(ctx);
  4060. });
  4061. }
  4062. // End of extension definition
  4063. });
  4064. // End of namespace closure
  4065. }(jQuery));
  4066. /*!
  4067. * jquery.fancytree.dnd.js
  4068. *
  4069. * Drag-and-drop support.
  4070. * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
  4071. *
  4072. * Copyright (c) 2014, Martin Wendt (http://wwWendt.de)
  4073. *
  4074. * Released under the MIT license
  4075. * https://github.com/mar10/fancytree/wiki/LicenseInfo
  4076. *
  4077. * @version 2.2.0
  4078. * @date 2014-06-28T17:15
  4079. */
  4080. ;(function($, window, document, undefined) {
  4081. "use strict";
  4082. /* *****************************************************************************
  4083. * Private functions and variables
  4084. */
  4085. var logMsg = $.ui.fancytree.debug,
  4086. didRegisterDnd = false;
  4087. /* Convert number to string and prepend +/-; return empty string for 0.*/
  4088. function offsetString(n){
  4089. return n === 0 ? "" : (( n > 0 ) ? ("+" + n) : ("" + n));
  4090. }
  4091. /* *****************************************************************************
  4092. * Drag and drop support
  4093. */
  4094. function _initDragAndDrop(tree) {
  4095. var dnd = tree.options.dnd || null;
  4096. // Register 'connectToFancytree' option with ui.draggable
  4097. if( dnd ) {
  4098. _registerDnd();
  4099. }
  4100. // Attach ui.draggable to this Fancytree instance
  4101. if(dnd && dnd.dragStart ) {
  4102. tree.widget.element.draggable($.extend({
  4103. addClasses: false,
  4104. appendTo: "body",
  4105. containment: false,
  4106. delay: 0,
  4107. distance: 4,
  4108. // TODO: merge Dynatree issue 419
  4109. revert: false,
  4110. scroll: true, // issue 244: enable scrolling (if ul.fancytree-container)
  4111. scrollSpeed: 7,
  4112. scrollSensitivity: 10,
  4113. // Delegate draggable.start, drag, and stop events to our handler
  4114. connectToFancytree: true,
  4115. // Let source tree create the helper element
  4116. helper: function(event) {
  4117. var sourceNode = $.ui.fancytree.getNode(event.target);
  4118. if(!sourceNode){ // Dynatree issue 211
  4119. // might happen, if dragging a table *header*
  4120. return "<div>ERROR?: helper requested but sourceNode not found</div>";
  4121. }
  4122. return sourceNode.tree.ext.dnd._onDragEvent("helper", sourceNode, null, event, null, null);
  4123. },
  4124. start: function(event, ui) {
  4125. var sourceNode = ui.helper.data("ftSourceNode");
  4126. return !!sourceNode; // Abort dragging if no node could be found
  4127. }
  4128. }, tree.options.dnd.draggable));
  4129. }
  4130. // Attach ui.droppable to this Fancytree instance
  4131. if(dnd && dnd.dragDrop) {
  4132. tree.widget.element.droppable($.extend({
  4133. addClasses: false,
  4134. tolerance: "intersect",
  4135. greedy: false
  4136. /*
  4137. activate: function(event, ui) {
  4138. logMsg("droppable - activate", event, ui, this);
  4139. },
  4140. create: function(event, ui) {
  4141. logMsg("droppable - create", event, ui);
  4142. },
  4143. deactivate: function(event, ui) {
  4144. logMsg("droppable - deactivate", event, ui);
  4145. },
  4146. drop: function(event, ui) {
  4147. logMsg("droppable - drop", event, ui);
  4148. },
  4149. out: function(event, ui) {
  4150. logMsg("droppable - out", event, ui);
  4151. },
  4152. over: function(event, ui) {
  4153. logMsg("droppable - over", event, ui);
  4154. }
  4155. */
  4156. }, tree.options.dnd.droppable));
  4157. }
  4158. }
  4159. //--- Extend ui.draggable event handling --------------------------------------
  4160. function _registerDnd() {
  4161. if(didRegisterDnd){
  4162. return;
  4163. }
  4164. // Register proxy-functions for draggable.start/drag/stop
  4165. $.ui.plugin.add("draggable", "connectToFancytree", {
  4166. start: function(event, ui) {
  4167. // 'draggable' was renamed to 'ui-draggable' since jQueryUI 1.10
  4168. var draggable = $(this).data("ui-draggable") || $(this).data("draggable"),
  4169. sourceNode = ui.helper.data("ftSourceNode") || null;
  4170. if(sourceNode) {
  4171. // Adjust helper offset, so cursor is slightly outside top/left corner
  4172. draggable.offset.click.top = -2;
  4173. draggable.offset.click.left = + 16;
  4174. // Trigger dragStart event
  4175. // TODO: when called as connectTo..., the return value is ignored(?)
  4176. return sourceNode.tree.ext.dnd._onDragEvent("start", sourceNode, null, event, ui, draggable);
  4177. }
  4178. },
  4179. drag: function(event, ui) {
  4180. var isHelper,
  4181. // 'draggable' was renamed to 'ui-draggable' since jQueryUI 1.10
  4182. draggable = $(this).data("ui-draggable") || $(this).data("draggable"),
  4183. sourceNode = ui.helper.data("ftSourceNode") || null,
  4184. prevTargetNode = ui.helper.data("ftTargetNode") || null,
  4185. targetNode = $.ui.fancytree.getNode(event.target);
  4186. if(event.target && !targetNode){
  4187. // We got a drag event, but the targetNode could not be found
  4188. // at the event location. This may happen,
  4189. // 1. if the mouse jumped over the drag helper,
  4190. // 2. or if a non-fancytree element is dragged
  4191. // We ignore it:
  4192. isHelper = $(event.target).closest("div.fancytree-drag-helper,#fancytree-drop-marker").length > 0;
  4193. if(isHelper){
  4194. logMsg("Drag event over helper: ignored.");
  4195. return;
  4196. }
  4197. }
  4198. ui.helper.data("ftTargetNode", targetNode);
  4199. // Leaving a tree node
  4200. if(prevTargetNode && prevTargetNode !== targetNode ) {
  4201. prevTargetNode.tree.ext.dnd._onDragEvent("leave", prevTargetNode, sourceNode, event, ui, draggable);
  4202. }
  4203. if(targetNode){
  4204. if(!targetNode.tree.options.dnd.dragDrop) {
  4205. // not enabled as drop target
  4206. } else if(targetNode === prevTargetNode) {
  4207. // Moving over same node
  4208. targetNode.tree.ext.dnd._onDragEvent("over", targetNode, sourceNode, event, ui, draggable);
  4209. }else{
  4210. // Entering this node first time
  4211. targetNode.tree.ext.dnd._onDragEvent("enter", targetNode, sourceNode, event, ui, draggable);
  4212. }
  4213. }
  4214. // else go ahead with standard event handling
  4215. },
  4216. stop: function(event, ui) {
  4217. // 'draggable' was renamed to 'ui-draggable' since jQueryUI 1.10
  4218. var draggable = $(this).data("ui-draggable") || $(this).data("draggable"),
  4219. sourceNode = ui.helper.data("ftSourceNode") || null,
  4220. targetNode = ui.helper.data("ftTargetNode") || null,
  4221. // mouseDownEvent = draggable._mouseDownEvent,
  4222. eventType = event.type,
  4223. dropped = (eventType === "mouseup" && event.which === 1);
  4224. if(!dropped){
  4225. logMsg("Drag was cancelled");
  4226. }
  4227. if(targetNode) {
  4228. if(dropped){
  4229. targetNode.tree.ext.dnd._onDragEvent("drop", targetNode, sourceNode, event, ui, draggable);
  4230. }
  4231. targetNode.tree.ext.dnd._onDragEvent("leave", targetNode, sourceNode, event, ui, draggable);
  4232. }
  4233. if(sourceNode){
  4234. sourceNode.tree.ext.dnd._onDragEvent("stop", sourceNode, null, event, ui, draggable);
  4235. }
  4236. }
  4237. });
  4238. didRegisterDnd = true;
  4239. }
  4240. /* *****************************************************************************
  4241. *
  4242. */
  4243. $.ui.fancytree.registerExtension({
  4244. name: "dnd",
  4245. version: "0.1.0",
  4246. // Default options for this extension.
  4247. options: {
  4248. // Make tree nodes draggable:
  4249. dragStart: null, // Callback(sourceNode, data), return true, to enable dnd
  4250. dragStop: null, // Callback(sourceNode, data)
  4251. // helper: null,
  4252. // Make tree nodes accept draggables
  4253. autoExpandMS: 1000, // Expand nodes after n milliseconds of hovering.
  4254. preventVoidMoves: true, // Prevent dropping nodes 'before self', etc.
  4255. preventRecursiveMoves: true, // Prevent dropping nodes on own descendants
  4256. focusOnClick: false, // Focus, although draggable cancels mousedown event (#270)
  4257. dragEnter: null, // Callback(targetNode, data)
  4258. dragOver: null, // Callback(targetNode, data)
  4259. dragDrop: null, // Callback(targetNode, data)
  4260. dragLeave: null, // Callback(targetNode, data)
  4261. //
  4262. draggable: null, // Additional options passed to jQuery draggable
  4263. droppable: null // Additional options passed to jQuery droppable
  4264. },
  4265. treeInit: function(ctx){
  4266. var tree = ctx.tree;
  4267. this._super(ctx);
  4268. // issue #270: draggable eats mousedown events
  4269. if( tree.options.dnd.dragStart ){
  4270. tree.$container.on("mousedown", function(event){
  4271. if( !tree.hasFocus() && ctx.options.dnd.focusOnClick ) {
  4272. var node = $.ui.fancytree.getNode(event);
  4273. node.debug("Re-enable focus that was prevented by jQuery UI draggable.");
  4274. // node.setFocus();
  4275. // $(node.span).closest(":tabbable").focus();
  4276. // $(event.target).trigger("focus");
  4277. // $(event.target).closest(":tabbable").trigger("focus");
  4278. $(event.target).closest(":tabbable").focus();
  4279. }
  4280. });
  4281. }
  4282. _initDragAndDrop(tree);
  4283. },
  4284. /* Override key handler in order to cancel dnd on escape.*/
  4285. nodeKeydown: function(ctx) {
  4286. var event = ctx.originalEvent;
  4287. if( event.which === $.ui.keyCode.ESCAPE) {
  4288. this._local._cancelDrag();
  4289. }
  4290. return this._super(ctx);
  4291. },
  4292. nodeClick: function(ctx) {
  4293. // if( ctx.options.dnd.dragStart ){
  4294. // ctx.tree.$container.focus();
  4295. // }
  4296. return this._super(ctx);
  4297. },
  4298. /* Display drop marker according to hitMode ('after', 'before', 'over', 'out', 'start', 'stop'). */
  4299. _setDndStatus: function(sourceNode, targetNode, helper, hitMode, accept) {
  4300. var posOpts,
  4301. markerOffsetX = 0,
  4302. markerAt = "center",
  4303. instData = this._local,
  4304. $source = sourceNode ? $(sourceNode.span) : null,
  4305. $target = $(targetNode.span);
  4306. if( !instData.$dropMarker ) {
  4307. instData.$dropMarker = $("<div id='fancytree-drop-marker'></div>")
  4308. .hide()
  4309. .css({"z-index": 1000})
  4310. .prependTo($(this.$div).parent());
  4311. // .prependTo("body");
  4312. }
  4313. // this.$dropMarker.attr("class", hitMode);
  4314. if(hitMode === "after" || hitMode === "before" || hitMode === "over"){
  4315. // $source && $source.addClass("fancytree-drag-source");
  4316. // $target.addClass("fancytree-drop-target");
  4317. switch(hitMode){
  4318. case "before":
  4319. instData
  4320. .$dropMarker.removeClass("fancytree-drop-after fancytree-drop-over")
  4321. .addClass("fancytree-drop-before");
  4322. markerAt = "top";
  4323. break;
  4324. case "after":
  4325. instData.$dropMarker.removeClass("fancytree-drop-before fancytree-drop-over")
  4326. .addClass("fancytree-drop-after");
  4327. markerAt = "bottom";
  4328. break;
  4329. default:
  4330. instData.$dropMarker.removeClass("fancytree-drop-after fancytree-drop-before")
  4331. .addClass("fancytree-drop-over");
  4332. $target.addClass("fancytree-drop-target");
  4333. markerOffsetX = 8;
  4334. }
  4335. if( $.ui.fancytree.jquerySupports.positionMyOfs ){
  4336. posOpts = {
  4337. my: "left" + offsetString(markerOffsetX) + " center",
  4338. at: "left " + markerAt,
  4339. of: $target
  4340. };
  4341. } else {
  4342. posOpts = {
  4343. my: "left center",
  4344. at: "left " + markerAt,
  4345. of: $target,
  4346. offset: "" + markerOffsetX + " 0"
  4347. };
  4348. }
  4349. instData.$dropMarker
  4350. .show()
  4351. .position(posOpts);
  4352. // helper.addClass("fancytree-drop-hover");
  4353. } else {
  4354. // $source && $source.removeClass("fancytree-drag-source");
  4355. $target.removeClass("fancytree-drop-target");
  4356. instData.$dropMarker.hide();
  4357. // helper.removeClass("fancytree-drop-hover");
  4358. }
  4359. if(hitMode === "after"){
  4360. $target.addClass("fancytree-drop-after");
  4361. } else {
  4362. $target.removeClass("fancytree-drop-after");
  4363. }
  4364. if(hitMode === "before"){
  4365. $target.addClass("fancytree-drop-before");
  4366. } else {
  4367. $target.removeClass("fancytree-drop-before");
  4368. }
  4369. if(accept === true){
  4370. if($source){
  4371. $source.addClass("fancytree-drop-accept");
  4372. }
  4373. $target.addClass("fancytree-drop-accept");
  4374. helper.addClass("fancytree-drop-accept");
  4375. }else{
  4376. if($source){
  4377. $source.removeClass("fancytree-drop-accept");
  4378. }
  4379. $target.removeClass("fancytree-drop-accept");
  4380. helper.removeClass("fancytree-drop-accept");
  4381. }
  4382. if(accept === false){
  4383. if($source){
  4384. $source.addClass("fancytree-drop-reject");
  4385. }
  4386. $target.addClass("fancytree-drop-reject");
  4387. helper.addClass("fancytree-drop-reject");
  4388. }else{
  4389. if($source){
  4390. $source.removeClass("fancytree-drop-reject");
  4391. }
  4392. $target.removeClass("fancytree-drop-reject");
  4393. helper.removeClass("fancytree-drop-reject");
  4394. }
  4395. },
  4396. /*
  4397. * Handles drag'n'drop functionality.
  4398. *
  4399. * A standard jQuery drag-and-drop process may generate these calls:
  4400. *
  4401. * draggable helper():
  4402. * _onDragEvent("helper", sourceNode, null, event, null, null);
  4403. * start:
  4404. * _onDragEvent("start", sourceNode, null, event, ui, draggable);
  4405. * drag:
  4406. * _onDragEvent("leave", prevTargetNode, sourceNode, event, ui, draggable);
  4407. * _onDragEvent("over", targetNode, sourceNode, event, ui, draggable);
  4408. * _onDragEvent("enter", targetNode, sourceNode, event, ui, draggable);
  4409. * stop:
  4410. * _onDragEvent("drop", targetNode, sourceNode, event, ui, draggable);
  4411. * _onDragEvent("leave", targetNode, sourceNode, event, ui, draggable);
  4412. * _onDragEvent("stop", sourceNode, null, event, ui, draggable);
  4413. */
  4414. _onDragEvent: function(eventName, node, otherNode, event, ui, draggable) {
  4415. if(eventName !== "over"){
  4416. logMsg("tree.ext.dnd._onDragEvent(%s, %o, %o) - %o", eventName, node, otherNode, this);
  4417. }
  4418. var $helper, nodeOfs, relPos, relPos2,
  4419. enterResponse, hitMode, r,
  4420. opts = this.options,
  4421. dnd = opts.dnd,
  4422. ctx = this._makeHookContext(node, event, {otherNode: otherNode, ui: ui, draggable: draggable}),
  4423. res = null,
  4424. $nodeTag = $(node.span);
  4425. switch (eventName) {
  4426. case "helper":
  4427. // Only event and node argument is available
  4428. $helper = $("<div class='fancytree-drag-helper'><span class='fancytree-drag-helper-img' /></div>")
  4429. .css({zIndex: 3, position: "relative"}) // so it appears above ext-wide selection bar
  4430. .append($nodeTag.find("span.fancytree-title").clone());
  4431. // DT issue 244: helper should be child of scrollParent
  4432. $("ul.fancytree-container", node.tree.$div).append($helper);
  4433. // Attach node reference to helper object
  4434. $helper.data("ftSourceNode", node);
  4435. // logMsg("helper=%o", $helper);
  4436. // logMsg("helper.sourceNode=%o", $helper.data("ftSourceNode"));
  4437. res = $helper;
  4438. break;
  4439. case "start":
  4440. if( node.isStatusNode() ) {
  4441. res = false;
  4442. } else if(dnd.dragStart) {
  4443. res = dnd.dragStart(node, ctx);
  4444. }
  4445. if(res === false) {
  4446. this.debug("tree.dragStart() cancelled");
  4447. //draggable._clear();
  4448. // NOTE: the return value seems to be ignored (drag is not canceled, when false is returned)
  4449. // TODO: call this._cancelDrag()?
  4450. ui.helper.trigger("mouseup")
  4451. .hide();
  4452. } else {
  4453. $nodeTag.addClass("fancytree-drag-source");
  4454. }
  4455. break;
  4456. case "enter":
  4457. if(dnd.preventRecursiveMoves && node.isDescendantOf(otherNode)){
  4458. r = false;
  4459. }else{
  4460. r = dnd.dragEnter ? dnd.dragEnter(node, ctx) : null;
  4461. }
  4462. if(!r){
  4463. // convert null, undefined, false to false
  4464. res = false;
  4465. }else if ( $.isArray(r) ) {
  4466. // TODO: also accept passing an object of this format directly
  4467. res = {
  4468. over: ($.inArray("over", r) >= 0),
  4469. before: ($.inArray("before", r) >= 0),
  4470. after: ($.inArray("after", r) >= 0)
  4471. };
  4472. }else{
  4473. res = {
  4474. over: ((r === true) || (r === "over")),
  4475. before: ((r === true) || (r === "before")),
  4476. after: ((r === true) || (r === "after"))
  4477. };
  4478. }
  4479. ui.helper.data("enterResponse", res);
  4480. logMsg("helper.enterResponse: %o", res);
  4481. break;
  4482. case "over":
  4483. enterResponse = ui.helper.data("enterResponse");
  4484. hitMode = null;
  4485. if(enterResponse === false){
  4486. // Don't call dragOver if onEnter returned false.
  4487. // break;
  4488. } else if(typeof enterResponse === "string") {
  4489. // Use hitMode from onEnter if provided.
  4490. hitMode = enterResponse;
  4491. } else {
  4492. // Calculate hitMode from relative cursor position.
  4493. nodeOfs = $nodeTag.offset();
  4494. relPos = { x: event.pageX - nodeOfs.left,
  4495. y: event.pageY - nodeOfs.top };
  4496. relPos2 = { x: relPos.x / $nodeTag.width(),
  4497. y: relPos.y / $nodeTag.height() };
  4498. if( enterResponse.after && relPos2.y > 0.75 ){
  4499. hitMode = "after";
  4500. } else if(!enterResponse.over && enterResponse.after && relPos2.y > 0.5 ){
  4501. hitMode = "after";
  4502. } else if(enterResponse.before && relPos2.y <= 0.25) {
  4503. hitMode = "before";
  4504. } else if(!enterResponse.over && enterResponse.before && relPos2.y <= 0.5) {
  4505. hitMode = "before";
  4506. } else if(enterResponse.over) {
  4507. hitMode = "over";
  4508. }
  4509. // Prevent no-ops like 'before source node'
  4510. // TODO: these are no-ops when moving nodes, but not in copy mode
  4511. if( dnd.preventVoidMoves ){
  4512. if(node === otherNode){
  4513. logMsg(" drop over source node prevented");
  4514. hitMode = null;
  4515. }else if(hitMode === "before" && otherNode && node === otherNode.getNextSibling()){
  4516. logMsg(" drop after source node prevented");
  4517. hitMode = null;
  4518. }else if(hitMode === "after" && otherNode && node === otherNode.getPrevSibling()){
  4519. logMsg(" drop before source node prevented");
  4520. hitMode = null;
  4521. }else if(hitMode === "over" && otherNode && otherNode.parent === node && otherNode.isLastSibling() ){
  4522. logMsg(" drop last child over own parent prevented");
  4523. hitMode = null;
  4524. }
  4525. }
  4526. // logMsg("hitMode: %s - %s - %s", hitMode, (node.parent === otherNode), node.isLastSibling());
  4527. ui.helper.data("hitMode", hitMode);
  4528. }
  4529. // Auto-expand node (only when 'over' the node, not 'before', or 'after')
  4530. if(hitMode === "over" && dnd.autoExpandMS && node.hasChildren() !== false && !node.expanded) {
  4531. node.scheduleAction("expand", dnd.autoExpandMS);
  4532. }
  4533. if(hitMode && dnd.dragOver){
  4534. // TODO: http://code.google.com/p/dynatree/source/detail?r=625
  4535. ctx.hitMode = hitMode;
  4536. res = dnd.dragOver(node, ctx);
  4537. }
  4538. // DT issue 332
  4539. // this._setDndStatus(otherNode, node, ui.helper, hitMode, res!==false);
  4540. this._local._setDndStatus(otherNode, node, ui.helper, hitMode, res!==false && hitMode !== null);
  4541. break;
  4542. case "drop":
  4543. hitMode = ui.helper.data("hitMode");
  4544. if(hitMode && dnd.dragDrop){
  4545. ctx.hitMode = hitMode;
  4546. dnd.dragDrop(node, ctx);
  4547. }
  4548. break;
  4549. case "leave":
  4550. // Cancel pending expand request
  4551. node.scheduleAction("cancel");
  4552. ui.helper.data("enterResponse", null);
  4553. ui.helper.data("hitMode", null);
  4554. this._local._setDndStatus(otherNode, node, ui.helper, "out", undefined);
  4555. if(dnd.dragLeave){
  4556. dnd.dragLeave(node, ctx);
  4557. }
  4558. break;
  4559. case "stop":
  4560. $nodeTag.removeClass("fancytree-drag-source");
  4561. if(dnd.dragStop){
  4562. dnd.dragStop(node, ctx);
  4563. }
  4564. break;
  4565. default:
  4566. $.error("Unsupported drag event: " + eventName);
  4567. }
  4568. return res;
  4569. },
  4570. _cancelDrag: function() {
  4571. var dd = $.ui.ddmanager.current;
  4572. if(dd){
  4573. dd.cancel();
  4574. }
  4575. }
  4576. });
  4577. }(jQuery, window, document));
  4578. /*!
  4579. * jquery.fancytree.edit.js
  4580. *
  4581. * Make node titles editable.
  4582. * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
  4583. *
  4584. * Copyright (c) 2014, Martin Wendt (http://wwWendt.de)
  4585. *
  4586. * Released under the MIT license
  4587. * https://github.com/mar10/fancytree/wiki/LicenseInfo
  4588. *
  4589. * @version 2.2.0
  4590. * @date 2014-06-28T17:15
  4591. */
  4592. ;(function($, window, document, undefined) {
  4593. "use strict";
  4594. /*******************************************************************************
  4595. * Private functions and variables
  4596. */
  4597. var isMac = /Mac/.test(navigator.platform),
  4598. escapeHtml = $.ui.fancytree.escapeHtml,
  4599. unescapeHtml = $.ui.fancytree.unescapeHtml;
  4600. // modifiers = {shift: "shiftKey", ctrl: "ctrlKey", alt: "altKey", meta: "metaKey"},
  4601. // specialKeys = {
  4602. // 8: "backspace", 9: "tab", 10: "return", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
  4603. // 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
  4604. // 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del",
  4605. // 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
  4606. // 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
  4607. // 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
  4608. // 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 186: ";", 191: "/",
  4609. // 220: "\\", 222: "'", 224: "meta"
  4610. // },
  4611. // shiftNums = {
  4612. // "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&",
  4613. // "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<",
  4614. // ".": ">", "/": "?", "\\": "|"
  4615. // };
  4616. // $.ui.fancytree.isKeydownEvent = function(e, code){
  4617. // var i, part, partmap, partlist = code.split("+"), len = parts.length;
  4618. // var c = String.fromCharCode(e.which).toLowerCase();
  4619. // for( i = 0; i < len; i++ ) {
  4620. // }
  4621. // alert (parts.unshift());
  4622. // alert (parts.unshift());
  4623. // alert (parts.unshift());
  4624. // };
  4625. /**
  4626. * [ext-edit] Start inline editing of current node title.
  4627. *
  4628. * @alias FancytreeNode#editStart
  4629. * @requires Fancytree
  4630. */
  4631. $.ui.fancytree._FancytreeNodeClass.prototype.editStart = function(){
  4632. var $input,
  4633. node = this,
  4634. tree = this.tree,
  4635. local = tree.ext.edit,
  4636. prevTitle = node.title,
  4637. instOpts = tree.options.edit,
  4638. $title = $(".fancytree-title", node.span),
  4639. eventData = {node: node, tree: tree, options: tree.options};
  4640. if( instOpts.beforeEdit.call(node, {type: "beforeEdit"}, eventData) === false){
  4641. return false;
  4642. }
  4643. // beforeEdit may want to modify the title before editing
  4644. prevTitle = node.title;
  4645. node.debug("editStart");
  4646. // Disable standard Fancytree mouse- and key handling
  4647. tree.widget._unbind();
  4648. // #116: ext-dnd prevents the blur event, so we have to catch outer clicks
  4649. $(document).on("mousedown.fancytree-edit", function(event){
  4650. if( ! $(event.target).hasClass("fancytree-edit-input") ){
  4651. node.editEnd(true, event);
  4652. }
  4653. });
  4654. // Replace node with <input>
  4655. $input = $("<input />", {
  4656. "class": "fancytree-edit-input",
  4657. value: unescapeHtml(prevTitle)
  4658. });
  4659. if ( instOpts.adjustWidthOfs != null ) {
  4660. $input.width($title.width() + instOpts.adjustWidthOfs);
  4661. }
  4662. if ( instOpts.inputCss != null ) {
  4663. $input.css(instOpts.inputCss);
  4664. }
  4665. eventData.input = $input;
  4666. $title.html($input);
  4667. $.ui.fancytree.assert(!local.currentNode, "recursive edit");
  4668. local.currentNode = this;
  4669. // Focus <input> and bind keyboard handler
  4670. $input
  4671. .focus()
  4672. .change(function(event){
  4673. $input.addClass("fancytree-edit-dirty");
  4674. }).keydown(function(event){
  4675. switch( event.which ) {
  4676. case $.ui.keyCode.ESCAPE:
  4677. node.editEnd(false, event);
  4678. break;
  4679. case $.ui.keyCode.ENTER:
  4680. node.editEnd(true, event);
  4681. return false; // so we don't start editmode on Mac
  4682. }
  4683. }).blur(function(event){
  4684. return node.editEnd(true, event);
  4685. });
  4686. instOpts.edit.call(node, {type: "edit"}, eventData);
  4687. };
  4688. /**
  4689. * [ext-edit] Stop inline editing.
  4690. * @param {Boolean} [applyChanges=false]
  4691. * @alias FancytreeNode#editEnd
  4692. * @requires jquery.fancytree.edit.js
  4693. */
  4694. $.ui.fancytree._FancytreeNodeClass.prototype.editEnd = function(applyChanges, _event){
  4695. var node = this,
  4696. tree = this.tree,
  4697. local = tree.ext.edit,
  4698. instOpts = tree.options.edit,
  4699. $title = $(".fancytree-title", node.span),
  4700. $input = $title.find("input.fancytree-edit-input"),
  4701. newVal = $input.val(),
  4702. dirty = $input.hasClass("fancytree-edit-dirty"),
  4703. doSave = (applyChanges || (dirty && applyChanges !== false)) && (newVal !== node.title),
  4704. eventData = {
  4705. node: node, tree: tree, options: tree.options, originalEvent: _event,
  4706. dirty: dirty,
  4707. save: doSave,
  4708. input: $input,
  4709. value: newVal
  4710. };
  4711. if( instOpts.beforeClose.call(node, {type: "beforeClose"}, eventData) === false){
  4712. return false;
  4713. }
  4714. if( doSave && instOpts.save.call(node, {type: "save"}, eventData) === false){
  4715. return false;
  4716. }
  4717. $input
  4718. .removeClass("fancytree-edit-dirty")
  4719. .unbind();
  4720. // Unbind outer-click handler
  4721. $(document).off(".fancytree-edit");
  4722. if( doSave ) {
  4723. node.setTitle( escapeHtml(newVal) );
  4724. }else{
  4725. node.renderTitle();
  4726. }
  4727. // Re-enable mouse and keyboard handling
  4728. tree.widget._bind();
  4729. local.currentNode = null;
  4730. node.setFocus();
  4731. // Set keyboard focus, even if setFocus() claims 'nothing to do'
  4732. $(tree.$container).focus();
  4733. eventData.input = null;
  4734. instOpts.close.call(node, {type: "close"}, eventData);
  4735. return true;
  4736. };
  4737. $.ui.fancytree._FancytreeNodeClass.prototype.startEdit = function(){
  4738. this.warn("FancytreeNode.startEdit() is deprecated since 2014-01-04. Use .editStart() instead.");
  4739. return this.editStart.apply(this, arguments);
  4740. };
  4741. $.ui.fancytree._FancytreeNodeClass.prototype.endEdit = function(){
  4742. this.warn("FancytreeNode.endEdit() is deprecated since 2014-01-04. Use .editEnd() instead.");
  4743. return this.editEnd.apply(this, arguments);
  4744. };
  4745. ///**
  4746. // * Create a new child or sibling node.
  4747. // *
  4748. // * @param {String} [mode] 'before', 'after', or 'child'
  4749. // * @lends FancytreeNode.prototype
  4750. // * @requires jquery.fancytree.edit.js
  4751. // */
  4752. //$.ui.fancytree._FancytreeNodeClass.prototype.editCreateNode = function(mode){
  4753. // var newNode,
  4754. // node = this,
  4755. // tree = this.tree,
  4756. // local = tree.ext.edit,
  4757. // instOpts = tree.options.edit,
  4758. // $title = $(".fancytree-title", node.span),
  4759. // $input = $title.find("input.fancytree-edit-input"),
  4760. // newVal = $input.val(),
  4761. // dirty = $input.hasClass("fancytree-edit-dirty"),
  4762. // doSave = (applyChanges || (dirty && applyChanges !== false)) && (newVal !== node.title),
  4763. // eventData = {
  4764. // node: node, tree: tree, options: tree.options, originalEvent: _event,
  4765. // dirty: dirty,
  4766. // save: doSave,
  4767. // input: $input,
  4768. // value: newVal
  4769. // };
  4770. //
  4771. // node.debug("editCreate");
  4772. //
  4773. // if( instOpts.beforeEdit.call(node, {type: "beforeCreateNode"}, eventData) === false){
  4774. // return false;
  4775. // }
  4776. // newNode = this.addNode({title: "Neuer Knoten"}, mode);
  4777. //
  4778. // newNode.editStart();
  4779. //};
  4780. /**
  4781. * [ext-edit] Check if any node in this tree in edit mode.
  4782. *
  4783. * @returns {FancytreeNode | null}
  4784. * @alias Fancytree#isEditing
  4785. * @requires jquery.fancytree.edit.js
  4786. */
  4787. $.ui.fancytree._FancytreeClass.prototype.isEditing = function(){
  4788. return this.ext.edit.currentNode;
  4789. };
  4790. /**
  4791. * [ext-edit] Check if this node is in edit mode.
  4792. * @returns {Boolean} true if node is currently beeing edited
  4793. * @alias FancytreeNode#isEditing
  4794. * @requires jquery.fancytree.edit.js
  4795. */
  4796. $.ui.fancytree._FancytreeNodeClass.prototype.isEditing = function(){
  4797. return this.tree.ext.edit.currentNode === this;
  4798. };
  4799. /*******************************************************************************
  4800. * Extension code
  4801. */
  4802. $.ui.fancytree.registerExtension({
  4803. name: "edit",
  4804. version: "0.1.0",
  4805. // Default options for this extension.
  4806. options: {
  4807. adjustWidthOfs: 4, // null: don't adjust input size to content
  4808. inputCss: {minWidth: "3em"},
  4809. triggerCancel: ["esc", "tab", "click"],
  4810. // triggerStart: ["f2", "dblclick", "shift+click", "mac+enter"],
  4811. triggerStart: ["f2", "shift+click", "mac+enter"],
  4812. beforeClose: $.noop, // Return false to prevent cancel/save (data.input is available)
  4813. beforeEdit: $.noop, // Return false to prevent edit mode
  4814. close: $.noop, // Editor was removed
  4815. edit: $.noop, // Editor was opened (available as data.input)
  4816. // keypress: $.noop, // Not yet implemented
  4817. save: $.noop // Save data.input.val() or return false to keep editor open
  4818. },
  4819. // Local attributes
  4820. currentNode: null,
  4821. treeInit: function(ctx){
  4822. this._super(ctx);
  4823. this.$container.addClass("fancytree-ext-edit");
  4824. },
  4825. nodeClick: function(ctx) {
  4826. if( $.inArray("shift+click", ctx.options.edit.triggerStart) >= 0 ){
  4827. if( ctx.originalEvent.shiftKey ){
  4828. ctx.node.editStart();
  4829. return false;
  4830. }
  4831. }
  4832. return this._super(ctx);
  4833. },
  4834. nodeDblclick: function(ctx) {
  4835. if( $.inArray("dblclick", ctx.options.edit.triggerStart) >= 0 ){
  4836. ctx.node.editStart();
  4837. return false;
  4838. }
  4839. return this._super(ctx);
  4840. },
  4841. nodeKeydown: function(ctx) {
  4842. switch( ctx.originalEvent.which ) {
  4843. case 113: // [F2]
  4844. if( $.inArray("f2", ctx.options.edit.triggerStart) >= 0 ){
  4845. ctx.node.editStart();
  4846. return false;
  4847. }
  4848. break;
  4849. case $.ui.keyCode.ENTER:
  4850. if( $.inArray("mac+enter", ctx.options.edit.triggerStart) >= 0 && isMac ){
  4851. ctx.node.editStart();
  4852. return false;
  4853. }
  4854. break;
  4855. }
  4856. return this._super(ctx);
  4857. }
  4858. });
  4859. }(jQuery, window, document));
  4860. /*!
  4861. * jquery.fancytree.filter.js
  4862. *
  4863. * Remove or highlight tree nodes, based on a filter.
  4864. * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
  4865. *
  4866. * Copyright (c) 2014, Martin Wendt (http://wwWendt.de)
  4867. *
  4868. * Released under the MIT license
  4869. * https://github.com/mar10/fancytree/wiki/LicenseInfo
  4870. *
  4871. * @version 2.2.0
  4872. * @date 2014-06-28T17:15
  4873. */
  4874. ;(function($, window, document, undefined) {
  4875. "use strict";
  4876. /*******************************************************************************
  4877. * Private functions and variables
  4878. */
  4879. function _escapeRegex(str){
  4880. /*jshint regexdash:true */
  4881. return (str + "").replace(/([.?*+\^\$\[\]\\(){}|-])/g, "\\$1");
  4882. }
  4883. $.ui.fancytree._FancytreeClass.prototype._applyFilterImpl = function(filter, branchMode, leavesOnly){
  4884. var match, re,
  4885. count = 0,
  4886. hideMode = this.options.filter.mode === "hide";
  4887. // leavesOnly = !branchMode && this.options.filter.leavesOnly;
  4888. leavesOnly = !!leavesOnly && !branchMode;
  4889. // Default to 'match title substring (not case sensitive)'
  4890. if(typeof filter === "string"){
  4891. match = _escapeRegex(filter); // make sure a '.' is treated literally
  4892. re = new RegExp(".*" + match + ".*", "i");
  4893. filter = function(node){
  4894. return !!re.exec(node.title);
  4895. };
  4896. }
  4897. this.enableFilter = true;
  4898. this.$div.addClass("fancytree-ext-filter");
  4899. if( hideMode ){
  4900. this.$div.addClass("fancytree-ext-filter-hide");
  4901. } else {
  4902. this.$div.addClass("fancytree-ext-filter-dimm");
  4903. }
  4904. // Reset current filter
  4905. this.visit(function(node){
  4906. delete node.match;
  4907. delete node.subMatch;
  4908. });
  4909. // Adjust node.hide, .match, .subMatch flags
  4910. this.visit(function(node){
  4911. if ((!leavesOnly || node.children == null) && filter(node)) {
  4912. count++;
  4913. node.match = true;
  4914. node.visitParents(function(p){
  4915. p.subMatch = true;
  4916. });
  4917. if( branchMode ) {
  4918. node.visit(function(p){
  4919. p.match = true;
  4920. });
  4921. return "skip";
  4922. }
  4923. }
  4924. });
  4925. // Redraw
  4926. this.render();
  4927. return count;
  4928. };
  4929. /**
  4930. * [ext-filter] Dimm or hide nodes.
  4931. *
  4932. * @param {function | string} filter
  4933. * @param {boolean} [leavesOnly=false]
  4934. * @returns {integer} count
  4935. * @alias Fancytree#filterNodes
  4936. * @requires jquery.fancytree.filter.js
  4937. */
  4938. $.ui.fancytree._FancytreeClass.prototype.filterNodes = function(filter, leavesOnly){
  4939. return this._applyFilterImpl(filter, false, leavesOnly);
  4940. };
  4941. $.ui.fancytree._FancytreeClass.prototype.applyFilter = function(filter){
  4942. this.warn("Fancytree.applyFilter() is deprecated since 2014-05-10. Use .filterNodes() instead.");
  4943. return this.filterNodes.apply(this, arguments);
  4944. };
  4945. /**
  4946. * [ext-filter] Dimm or hide whole branches.
  4947. *
  4948. * @param {function | string} filter
  4949. * @returns {integer} count
  4950. * @alias Fancytree#filterBranches
  4951. * @requires jquery.fancytree.filter.js
  4952. */
  4953. $.ui.fancytree._FancytreeClass.prototype.filterBranches = function(filter){
  4954. return this._applyFilterImpl(filter, true, null);
  4955. };
  4956. /**
  4957. * [ext-filter] Reset the filter.
  4958. *
  4959. * @alias Fancytree#clearFilter
  4960. * @requires jquery.fancytree.filter.js
  4961. */
  4962. $.ui.fancytree._FancytreeClass.prototype.clearFilter = function(){
  4963. this.visit(function(node){
  4964. delete node.match;
  4965. delete node.subMatch;
  4966. });
  4967. this.enableFilter = false;
  4968. this.$div.removeClass("fancytree-ext-filter fancytree-ext-filter-dimm fancytree-ext-filter-hide");
  4969. this.render();
  4970. };
  4971. /*******************************************************************************
  4972. * Extension code
  4973. */
  4974. $.ui.fancytree.registerExtension({
  4975. name: "filter",
  4976. version: "0.2.0",
  4977. // Default options for this extension.
  4978. options: {
  4979. mode: "dimm"
  4980. // leavesOnly: false
  4981. },
  4982. treeInit: function(ctx){
  4983. this._super(ctx);
  4984. },
  4985. nodeRenderStatus: function(ctx) {
  4986. // Set classes for current status
  4987. var res,
  4988. node = ctx.node,
  4989. tree = ctx.tree,
  4990. $span = $(node[tree.statusClassPropName]);
  4991. res = this._super(ctx);
  4992. // nothing to do, if node was not yet rendered
  4993. if( !$span.length || !tree.enableFilter ) {
  4994. return res;
  4995. }
  4996. $span
  4997. .toggleClass("fancytree-match", !!node.match)
  4998. .toggleClass("fancytree-submatch", !!node.subMatch)
  4999. .toggleClass("fancytree-hide", !(node.match || node.subMatch));
  5000. return res;
  5001. }
  5002. });
  5003. }(jQuery, window, document));
  5004. /*!
  5005. * jquery.fancytree.glyph.js
  5006. *
  5007. * Use glyph fonts as instead of icon sprites.
  5008. * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
  5009. *
  5010. * Copyright (c) 2014, Martin Wendt (http://wwWendt.de)
  5011. *
  5012. * Released under the MIT license
  5013. * https://github.com/mar10/fancytree/wiki/LicenseInfo
  5014. *
  5015. * @version 2.2.0
  5016. * @date 2014-06-28T17:15
  5017. */
  5018. ;(function($, window, document, undefined) {
  5019. "use strict";
  5020. /* *****************************************************************************
  5021. * Private functions and variables
  5022. */
  5023. function _getIcon(opts, type){
  5024. return opts.map[type];
  5025. }
  5026. $.ui.fancytree.registerExtension({
  5027. name: "glyph",
  5028. version: "0.1.0",
  5029. // Default options for this extension.
  5030. options: {
  5031. prefix: "icon-",
  5032. extra: null,
  5033. map: {
  5034. doc: "icon-file-alt",
  5035. docOpen: "icon-file-alt",
  5036. checkbox: "icon-check-empty",
  5037. checkboxSelected: "icon-check",
  5038. checkboxUnknown: "icon-check icon-muted",
  5039. error: "icon-exclamation-sign",
  5040. expanderClosed: "icon-caret-right",
  5041. expanderLazy: "icon-angle-right",
  5042. expanderOpen: "icon-caret-down",
  5043. folder: "icon-folder-close-alt",
  5044. folderOpen: "icon-folder-open-alt",
  5045. loading: "icon-refresh icon-spin",
  5046. noExpander: ""
  5047. },
  5048. icon: null // TODO: allow callback here
  5049. },
  5050. treeInit: function(ctx){
  5051. var tree = ctx.tree;
  5052. this._super(ctx);
  5053. tree.$container.addClass("fancytree-ext-glyph");
  5054. },
  5055. nodeRenderStatus: function(ctx) {
  5056. var icon, span,
  5057. node = ctx.node,
  5058. opts = ctx.options.glyph,
  5059. // callback = opts.icon,
  5060. map = opts.map
  5061. // prefix = opts.prefix
  5062. // $span = $(node.span)
  5063. ;
  5064. this._super(ctx);
  5065. if( node.isRoot() ){
  5066. return;
  5067. }
  5068. span = $("span.fancytree-expander", node.span).get(0);
  5069. if( span ){
  5070. if( node.isLoading() ){
  5071. icon = "loading";
  5072. }else if( node.expanded ){
  5073. icon = "expanderOpen";
  5074. }else if( node.isUndefined() ){
  5075. icon = "expanderLazy";
  5076. }else if( node.hasChildren() ){
  5077. icon = "expanderClosed";
  5078. }else{
  5079. icon = "noExpander";
  5080. }
  5081. span.className = "fancytree-expander " + map[icon];
  5082. }
  5083. span = $("span.fancytree-checkbox", node.tr || node.span).get(0);
  5084. if( span ){
  5085. icon = node.selected ? "checkboxSelected" : (node.partsel ? "checkboxUnknown" : "checkbox");
  5086. span.className = "fancytree-checkbox " + map[icon];
  5087. }
  5088. span = $("span.fancytree-icon", node.span).get(0);
  5089. if( span ){
  5090. if( node.folder ){
  5091. icon = node.expanded ? _getIcon(opts, "folderOpen") : _getIcon(opts, "folder");
  5092. }else{
  5093. icon = node.expanded ? _getIcon(opts, "docOpen") : _getIcon(opts, "doc");
  5094. }
  5095. span.className = "fancytree-icon " + icon;
  5096. }
  5097. },
  5098. nodeSetStatus: function(ctx, status, message, details) {
  5099. var span,
  5100. opts = ctx.options.glyph,
  5101. node = ctx.node;
  5102. this._super(ctx, status, message, details);
  5103. if(node.parent){
  5104. span = $("span.fancytree-expander", node.span).get(0);
  5105. }else{
  5106. span = $(".fancytree-statusnode-wait, .fancytree-statusnode-error", node[this.nodeContainerAttrName])
  5107. .find("span.fancytree-expander").get(0);
  5108. }
  5109. if( status === "loading"){
  5110. // $("span.fancytree-expander", ctx.node.span).addClass(_getIcon(opts, "loading"));
  5111. span.className = "fancytree-expander " + _getIcon(opts, "loading");
  5112. }else if( status === "error"){
  5113. span.className = "fancytree-expander " + _getIcon(opts, "error");
  5114. }
  5115. }
  5116. });
  5117. }(jQuery, window, document));
  5118. /*!
  5119. * jquery.fancytree.gridnav.js
  5120. *
  5121. * Support keyboard navigation for trees with embedded input controls.
  5122. * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
  5123. *
  5124. * Copyright (c) 2014, Martin Wendt (http://wwWendt.de)
  5125. *
  5126. * Released under the MIT license
  5127. * https://github.com/mar10/fancytree/wiki/LicenseInfo
  5128. *
  5129. * @version 2.2.0
  5130. * @date 2014-06-28T17:15
  5131. */
  5132. ;(function($, window, document, undefined) {
  5133. "use strict";
  5134. /*******************************************************************************
  5135. * Private functions and variables
  5136. */
  5137. // Allow these navigation keys even when input controls are focused
  5138. var KC = $.ui.keyCode,
  5139. // which keys are *not* handled by embedded control, but passed to tree
  5140. // navigation handler:
  5141. NAV_KEYS = {
  5142. "text": [KC.UP, KC.DOWN],
  5143. "checkbox": [KC.UP, KC.DOWN, KC.LEFT, KC.RIGHT],
  5144. "radiobutton": [KC.UP, KC.DOWN, KC.LEFT, KC.RIGHT],
  5145. "select-one": [KC.LEFT, KC.RIGHT],
  5146. "select-multiple": [KC.LEFT, KC.RIGHT]
  5147. };
  5148. /* Calculate TD column index (considering colspans).*/
  5149. function getColIdx($tr, $td) {
  5150. var colspan,
  5151. td = $td.get(0),
  5152. idx = 0;
  5153. $tr.children().each(function () {
  5154. if( this === td ) {
  5155. return false;
  5156. }
  5157. colspan = $(this).prop("colspan");
  5158. idx += colspan ? colspan : 1;
  5159. });
  5160. return idx;
  5161. }
  5162. /* Find TD at given column index (considering colspans).*/
  5163. function findTdAtColIdx($tr, colIdx) {
  5164. var colspan,
  5165. res = null,
  5166. idx = 0;
  5167. $tr.children().each(function () {
  5168. if( idx >= colIdx ) {
  5169. res = $(this);
  5170. return false;
  5171. }
  5172. colspan = $(this).prop("colspan");
  5173. idx += colspan ? colspan : 1;
  5174. });
  5175. return res;
  5176. }
  5177. /* Find adjacent cell for a given direction. Skip empty cells and consider merged cells */
  5178. function findNeighbourTd($target, keyCode){
  5179. var $tr, colIdx,
  5180. $td = $target.closest("td"),
  5181. $tdNext = null;
  5182. switch( keyCode ){
  5183. case KC.LEFT:
  5184. $tdNext = $td.prev();
  5185. break;
  5186. case KC.RIGHT:
  5187. $tdNext = $td.next();
  5188. break;
  5189. case KC.UP:
  5190. case KC.DOWN:
  5191. $tr = $td.parent();
  5192. colIdx = getColIdx($tr, $td);
  5193. while( true ) {
  5194. $tr = (keyCode === KC.UP) ? $tr.prev() : $tr.next();
  5195. if( !$tr.length ) {
  5196. break;
  5197. }
  5198. // Skip hidden rows
  5199. if( $tr.is(":hidden") ) {
  5200. continue;
  5201. }
  5202. // Find adjacent cell in the same column
  5203. $tdNext = findTdAtColIdx($tr, colIdx);
  5204. // Skip cells that don't conatain a focusable element
  5205. if( $tdNext && $tdNext.find(":input").length ) {
  5206. break;
  5207. }
  5208. }
  5209. break;
  5210. }
  5211. return $tdNext;
  5212. }
  5213. /*******************************************************************************
  5214. * Extension code
  5215. */
  5216. $.ui.fancytree.registerExtension({
  5217. name: "gridnav",
  5218. version: "0.0.1",
  5219. // Default options for this extension.
  5220. options: {
  5221. autofocusInput: false, // Focus first embedded input if node gets activated
  5222. handleCursorKeys: true // Allow UP/DOWN in inputs to move to prev/next node
  5223. },
  5224. treeInit: function(ctx){
  5225. // gridnav requires the table extension to be loaded before itself
  5226. this._requireExtension("table", true, true);
  5227. this._super(ctx);
  5228. this.$container.addClass("fancytree-ext-gridnav");
  5229. // Activate node if embedded input gets focus (due to a click)
  5230. this.$container.on("focusin", function(event){
  5231. var ctx2,
  5232. node = $.ui.fancytree.getNode(event.target);
  5233. if( node && !node.isActive() ){
  5234. // Call node.setActive(), but also pass the event
  5235. ctx2 = ctx.tree._makeHookContext(node, event);
  5236. ctx.tree._callHook("nodeSetActive", ctx2, true);
  5237. }
  5238. });
  5239. },
  5240. nodeSetActive: function(ctx, flag) {
  5241. var $outer,
  5242. opts = ctx.options.gridnav,
  5243. node = ctx.node,
  5244. event = ctx.originalEvent || {},
  5245. triggeredByInput = $(event.target).is(":input");
  5246. flag = (flag !== false);
  5247. this._super(ctx, flag);
  5248. if( flag ){
  5249. if( ctx.options.titlesTabbable ){
  5250. if( !triggeredByInput ) {
  5251. $(node.span).find("span.fancytree-title").focus();
  5252. node.setFocus();
  5253. }
  5254. // If one node is tabbable, the container no longer needs to be
  5255. ctx.tree.$container.attr("tabindex", "-1");
  5256. // ctx.tree.$container.removeAttr("tabindex");
  5257. } else if( opts.autofocusInput && !triggeredByInput ){
  5258. // Set focus to input sub input (if node was clicked, but not
  5259. // when TAB was pressed )
  5260. $outer = $(node.tr || node.span);
  5261. $outer.find(":input:enabled:first").focus();
  5262. }
  5263. }
  5264. },
  5265. nodeKeydown: function(ctx) {
  5266. var inputType, handleKeys, $td,
  5267. opts = ctx.options.gridnav,
  5268. event = ctx.originalEvent,
  5269. $target = $(event.target);
  5270. // jQuery
  5271. inputType = $target.is(":input:enabled") ? $target.prop("type") : null;
  5272. // ctx.tree.debug("ext-gridnav nodeKeydown", event, inputType);
  5273. if( inputType && opts.handleCursorKeys ){
  5274. handleKeys = NAV_KEYS[inputType];
  5275. if( handleKeys && $.inArray(event.which, handleKeys) >= 0 ){
  5276. $td = findNeighbourTd($target, event.which);
  5277. // ctx.node.debug("ignore keydown in input", event.which, handleKeys);
  5278. if( $td && $td.length ) {
  5279. $td.find(":input:enabled").focus();
  5280. // Prevent Fancytree default navigation
  5281. return false;
  5282. }
  5283. }
  5284. return true;
  5285. }
  5286. ctx.tree.debug("ext-gridnav NOT HANDLED", event, inputType);
  5287. return this._super(ctx);
  5288. }
  5289. });
  5290. }(jQuery, window, document));
  5291. /*!
  5292. * jquery.fancytree.persist.js
  5293. *
  5294. * Persist tree status in cookiesRemove or highlight tree nodes, based on a filter.
  5295. * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
  5296. *
  5297. * @depends: jquery.cookie.js
  5298. *
  5299. * Copyright (c) 2014, Martin Wendt (http://wwWendt.de)
  5300. *
  5301. * Released under the MIT license
  5302. * https://github.com/mar10/fancytree/wiki/LicenseInfo
  5303. *
  5304. * @version 2.2.0
  5305. * @date 2014-06-28T17:15
  5306. */
  5307. ;(function($, window, document, undefined) {
  5308. "use strict";
  5309. /*******************************************************************************
  5310. * Private functions and variables
  5311. */
  5312. function _assert(cond, msg){
  5313. msg = msg || "";
  5314. if(!cond){
  5315. $.error("Assertion failed " + msg);
  5316. }
  5317. }
  5318. var ACTIVE = "active",
  5319. EXPANDED = "expanded",
  5320. FOCUS = "focus",
  5321. SELECTED = "selected";
  5322. /* Recursively load lazy nodes
  5323. * @param {string} mode 'load', 'expand', false
  5324. */
  5325. function _loadLazyNodes(tree, local, keyList, mode, dfd) {
  5326. var i, key, l, node,
  5327. foundOne = false,
  5328. deferredList = [],
  5329. missingKeyList = [];
  5330. keyList = keyList || [];
  5331. dfd = dfd || $.Deferred();
  5332. for( i=0, l=keyList.length; i<l; i++ ) {
  5333. key = keyList[i];
  5334. node = tree.getNodeByKey(key);
  5335. if( node ) {
  5336. if( mode && node.isUndefined() ) {
  5337. foundOne = true;
  5338. tree.debug("_loadLazyNodes: " + node + " is lazy: loading...");
  5339. if( mode === "expand" ) {
  5340. deferredList.push(node.setExpanded());
  5341. } else {
  5342. deferredList.push(node.load());
  5343. }
  5344. } else {
  5345. tree.debug("_loadLazyNodes: " + node + " already loaded.");
  5346. node.setExpanded();
  5347. }
  5348. } else {
  5349. missingKeyList.push(key);
  5350. tree.debug("_loadLazyNodes: " + node + " was not yet found.");
  5351. }
  5352. }
  5353. $.when.apply($, deferredList).always(function(){
  5354. // All lazy-expands have finished
  5355. if( foundOne && missingKeyList.length > 0 ) {
  5356. // If we read new nodes from server, try to resolve yet-missing keys
  5357. _loadLazyNodes(tree, local, missingKeyList, mode, dfd);
  5358. } else {
  5359. if( missingKeyList.length ) {
  5360. tree.warn("_loadLazyNodes: could not load those keys: ", missingKeyList);
  5361. for( i=0, l=missingKeyList.length; i<l; i++ ) {
  5362. key = keyList[i];
  5363. local._appendKey(EXPANDED, keyList[i], false);
  5364. }
  5365. }
  5366. dfd.resolve();
  5367. }
  5368. });
  5369. return dfd;
  5370. }
  5371. /**
  5372. * [ext-persist] Remove persistence cookies of the given type(s).
  5373. * Called like
  5374. * $("#tree").fancytree("getTree").clearCookies("active expanded focus selected");
  5375. *
  5376. * @alias Fancytree#clearCookies
  5377. * @requires jquery.fancytree.persist.js
  5378. */
  5379. $.ui.fancytree._FancytreeClass.prototype.clearCookies = function(types){
  5380. var local = this.ext.persist,
  5381. prefix = local.cookiePrefix;
  5382. types = types || "active expanded focus selected";
  5383. if(types.indexOf(ACTIVE) >= 0){
  5384. local._data(prefix + ACTIVE, null);
  5385. }
  5386. if(types.indexOf(EXPANDED) >= 0){
  5387. local._data(prefix + EXPANDED, null);
  5388. }
  5389. if(types.indexOf(FOCUS) >= 0){
  5390. local._data(prefix + FOCUS, null);
  5391. }
  5392. if(types.indexOf(SELECTED) >= 0){
  5393. local._data(prefix + SELECTED, null);
  5394. }
  5395. };
  5396. /**
  5397. * [ext-persist] Return persistence information from cookies
  5398. *
  5399. * Called like
  5400. * $("#tree").fancytree("getTree").getPersistData();
  5401. *
  5402. * @alias Fancytree#getPersistData
  5403. * @requires jquery.fancytree.persist.js
  5404. */
  5405. $.ui.fancytree._FancytreeClass.prototype.getPersistData = function(){
  5406. var local = this.ext.persist,
  5407. prefix = local.cookiePrefix,
  5408. delim = local.cookieDelimiter,
  5409. res = {};
  5410. res[ACTIVE] = local._data(prefix + ACTIVE);
  5411. res[EXPANDED] = (local._data(prefix + EXPANDED) || "").split(delim);
  5412. res[SELECTED] = (local._data(prefix + SELECTED) || "").split(delim);
  5413. res[FOCUS] = local._data(prefix + FOCUS);
  5414. return res;
  5415. };
  5416. /* *****************************************************************************
  5417. * Extension code
  5418. */
  5419. $.ui.fancytree.registerExtension({
  5420. name: "persist",
  5421. version: "0.3.0",
  5422. // Default options for this extension.
  5423. options: {
  5424. cookieDelimiter: "~",
  5425. cookiePrefix: undefined, // 'fancytree-<treeId>-' by default
  5426. cookie: {
  5427. raw: false,
  5428. expires: "",
  5429. path: "",
  5430. domain: "",
  5431. secure: false
  5432. },
  5433. expandLazy: false, // true: recursively expand and load lazy nodes
  5434. overrideSource: false, // true: cookie takes precedence over `source` data attributes.
  5435. store: "auto", // 'cookie': force cookie, 'local': force localStore, 'session': force sessionStore
  5436. types: "active expanded focus selected"
  5437. },
  5438. /* Generic read/write string data to cookie, sessionStorage or localStorage. */
  5439. _data: function(key, value){
  5440. var ls = this._local.localStorage; // null, sessionStorage, or localStorage
  5441. if( value === undefined ) {
  5442. return ls ? ls.getItem(key) : $.cookie(key);
  5443. } else if ( value === null ) {
  5444. if( ls ) {
  5445. ls.removeItem(key);
  5446. } else {
  5447. $.removeCookie(key);
  5448. }
  5449. } else {
  5450. if( ls ) {
  5451. ls.setItem(key, value);
  5452. } else {
  5453. $.cookie(key, value, this.options.persist.cookie);
  5454. }
  5455. }
  5456. },
  5457. /* Append `key` to a cookie. */
  5458. _appendKey: function(type, key, flag){
  5459. key = "" + key; // #90
  5460. var local = this._local,
  5461. instOpts = this.options.persist,
  5462. delim = instOpts.cookieDelimiter,
  5463. cookieName = local.cookiePrefix + type,
  5464. data = local._data(cookieName),
  5465. keyList = data ? data.split(delim) : [],
  5466. idx = $.inArray(key, keyList);
  5467. // Remove, even if we add a key, so the key is always the last entry
  5468. if(idx >= 0){
  5469. keyList.splice(idx, 1);
  5470. }
  5471. // Append key to cookie
  5472. if(flag){
  5473. keyList.push(key);
  5474. }
  5475. local._data(cookieName, keyList.join(delim));
  5476. },
  5477. treeInit: function(ctx){
  5478. var tree = ctx.tree,
  5479. opts = ctx.options,
  5480. local = this._local,
  5481. instOpts = this.options.persist;
  5482. // For 'auto' or 'cookie' mode, the cookie plugin must be available
  5483. _assert(instOpts.store === "localStore" || $.cookie, "Missing required plugin for 'persist' extension: jquery.cookie.js");
  5484. local.cookiePrefix = instOpts.cookiePrefix || ("fancytree-" + tree._id + "-");
  5485. local.storeActive = instOpts.types.indexOf(ACTIVE) >= 0;
  5486. local.storeExpanded = instOpts.types.indexOf(EXPANDED) >= 0;
  5487. local.storeSelected = instOpts.types.indexOf(SELECTED) >= 0;
  5488. local.storeFocus = instOpts.types.indexOf(FOCUS) >= 0;
  5489. if( instOpts.store === "cookie" || !window.localStorage ) {
  5490. local.localStorage = null;
  5491. } else {
  5492. local.localStorage = (instOpts.store === "local") ? window.localStorage : window.sessionStorage;
  5493. }
  5494. // Bind init-handler to apply cookie state
  5495. tree.$div.bind("fancytreeinit", function(event){
  5496. var cookie, dfd, i, keyList, node,
  5497. prevFocus = $.cookie(local.cookiePrefix + FOCUS); // record this before node.setActive() overrides it;
  5498. tree.debug("COOKIE " + document.cookie);
  5499. cookie = local._data(local.cookiePrefix + EXPANDED);
  5500. keyList = cookie && cookie.split(instOpts.cookieDelimiter);
  5501. if( local.storeExpanded ) {
  5502. // Recursively load nested lazy nodes if expandLazy is 'expand' or 'load'
  5503. // Also remove expand-cookies for unmatched nodes
  5504. dfd = _loadLazyNodes(tree, local, keyList, instOpts.expandLazy ? "expand" : false , null);
  5505. } else {
  5506. // nothing to do
  5507. dfd = new $.Deferred().resolve();
  5508. }
  5509. dfd.done(function(){
  5510. if(local.storeSelected){
  5511. cookie = local._data(local.cookiePrefix + SELECTED);
  5512. if(cookie){
  5513. keyList = cookie.split(instOpts.cookieDelimiter);
  5514. for(i=0; i<keyList.length; i++){
  5515. node = tree.getNodeByKey(keyList[i]);
  5516. if(node){
  5517. if(node.selected === undefined || instOpts.overrideSource && (node.selected === false)){
  5518. // node.setSelected();
  5519. node.selected = true;
  5520. node.renderStatus();
  5521. }
  5522. }else{
  5523. // node is no longer member of the tree: remove from cookie also
  5524. local._appendKey(SELECTED, keyList[i], false);
  5525. }
  5526. }
  5527. }
  5528. }
  5529. if(local.storeActive){
  5530. cookie = local._data(local.cookiePrefix + ACTIVE);
  5531. if(cookie && (opts.persist.overrideSource || !tree.activeNode)){
  5532. node = tree.getNodeByKey(cookie);
  5533. if(node){
  5534. node.setActive();
  5535. }
  5536. }
  5537. }
  5538. if(local.storeFocus && prevFocus){
  5539. node = tree.getNodeByKey(prevFocus);
  5540. if(node){
  5541. node.setFocus();
  5542. }
  5543. }
  5544. });
  5545. });
  5546. // Init the tree
  5547. return this._super(ctx);
  5548. },
  5549. nodeSetActive: function(ctx, flag, opts) {
  5550. var res,
  5551. local = this._local;
  5552. flag = (flag !== false);
  5553. res = this._super(ctx, flag, opts);
  5554. if(local.storeActive){
  5555. local._data(local.cookiePrefix + ACTIVE, this.activeNode ? this.activeNode.key : null);
  5556. }
  5557. return res;
  5558. },
  5559. nodeSetExpanded: function(ctx, flag, opts) {
  5560. var res,
  5561. node = ctx.node,
  5562. local = this._local;
  5563. flag = (flag !== false);
  5564. res = this._super(ctx, flag, opts);
  5565. if(local.storeExpanded){
  5566. local._appendKey(EXPANDED, node.key, flag);
  5567. }
  5568. return res;
  5569. },
  5570. nodeSetFocus: function(ctx, flag) {
  5571. var res,
  5572. local = this._local;
  5573. flag = (flag !== false);
  5574. res = this._super(ctx, flag);
  5575. if(flag && local.storeFocus){
  5576. local._data(local.cookiePrefix + FOCUS, this.focusNode ? this.focusNode.key : null);
  5577. }
  5578. return res;
  5579. },
  5580. nodeSetSelected: function(ctx, flag) {
  5581. var res,
  5582. node = ctx.node,
  5583. local = this._local;
  5584. flag = (flag !== false);
  5585. res = this._super(ctx, flag);
  5586. if(local.storeSelected){
  5587. local._appendKey(SELECTED, node.key, flag);
  5588. }
  5589. return res;
  5590. }
  5591. });
  5592. }(jQuery, window, document));
  5593. /*!
  5594. * jquery.fancytree.table.js
  5595. *
  5596. * Render tree as table (aka 'treegrid', 'tabletree').
  5597. * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
  5598. *
  5599. * Copyright (c) 2014, Martin Wendt (http://wwWendt.de)
  5600. *
  5601. * Released under the MIT license
  5602. * https://github.com/mar10/fancytree/wiki/LicenseInfo
  5603. *
  5604. * @version 2.2.0
  5605. * @date 2014-06-28T17:15
  5606. */
  5607. ;(function($, window, document, undefined) {
  5608. "use strict";
  5609. /* *****************************************************************************
  5610. * Private functions and variables
  5611. */
  5612. function _assert(cond, msg){
  5613. msg = msg || "";
  5614. if(!cond){
  5615. $.error("Assertion failed " + msg);
  5616. }
  5617. }
  5618. function insertSiblingAfter(referenceNode, newNode) {
  5619. referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
  5620. }
  5621. /* Show/hide all rows that are structural descendants of `parent`. */
  5622. function setChildRowVisibility(parent, flag) {
  5623. parent.visit(function(node){
  5624. var tr = node.tr;
  5625. // currentFlag = node.hide ? false : flag; // fix for ext-filter
  5626. if(tr){
  5627. tr.style.display = (node.hide || !flag) ? "none" : "";
  5628. }
  5629. if(!node.expanded){
  5630. return "skip";
  5631. }
  5632. });
  5633. }
  5634. /* Find node that is rendered in previous row. */
  5635. function findPrevRowNode(node){
  5636. var i, last, prev,
  5637. parent = node.parent,
  5638. siblings = parent ? parent.children : null;
  5639. if(siblings && siblings.length > 1 && siblings[0] !== node){
  5640. // use the lowest descendant of the preceeding sibling
  5641. i = $.inArray(node, siblings);
  5642. prev = siblings[i - 1];
  5643. _assert(prev.tr);
  5644. // descend to lowest child (with a <tr> tag)
  5645. while(prev.children){
  5646. last = prev.children[prev.children.length - 1];
  5647. if(!last.tr){
  5648. break;
  5649. }
  5650. prev = last;
  5651. }
  5652. }else{
  5653. // if there is no preceding sibling, use the direct parent
  5654. prev = parent;
  5655. }
  5656. return prev;
  5657. }
  5658. $.ui.fancytree.registerExtension({
  5659. name: "table",
  5660. version: "0.2.0",
  5661. // Default options for this extension.
  5662. options: {
  5663. checkboxColumnIdx: null, // render the checkboxes into the this column index (default: nodeColumnIdx)
  5664. customStatus: false, // true: generate renderColumns events for status nodes
  5665. indentation: 16, // indent every node level by 16px
  5666. nodeColumnIdx: 0 // render node expander, icon, and title to this column (default: #0)
  5667. },
  5668. // Overide virtual methods for this extension.
  5669. // `this` : is this extension object
  5670. // `this._super`: the virtual function that was overriden (member of prev. extension or Fancytree)
  5671. treeInit: function(ctx){
  5672. var i, $row, tdRole,
  5673. tree = ctx.tree,
  5674. $table = tree.widget.element;
  5675. $table.addClass("fancytree-container fancytree-ext-table");
  5676. tree.tbody = $table.find("> tbody")[0];
  5677. tree.columnCount = $("thead >tr >th", $table).length;
  5678. $(tree.tbody).empty();
  5679. tree.rowFragment = document.createDocumentFragment();
  5680. $row = $("<tr />");
  5681. tdRole = "";
  5682. if(ctx.options.aria){
  5683. $row.attr("role", "row");
  5684. tdRole = " role='gridcell'";
  5685. }
  5686. for(i=0; i<tree.columnCount; i++) {
  5687. if(ctx.options.table.nodeColumnIdx === i){
  5688. $row.append("<td" + tdRole + "><span class='fancytree-node' /></td>");
  5689. }else{
  5690. $row.append("<td" + tdRole + " />");
  5691. }
  5692. }
  5693. tree.rowFragment.appendChild($row.get(0));
  5694. // Make sure that status classes are set on the node's <tr> elements
  5695. tree.statusClassPropName = "tr";
  5696. tree.ariaPropName = "tr";
  5697. this.nodeContainerAttrName = "tr";
  5698. this._super(ctx);
  5699. // standard Fancytree created a root UL
  5700. $(tree.rootNode.ul).remove();
  5701. tree.rootNode.ul = null;
  5702. tree.$container = $table;
  5703. // Add container to the TAB chain
  5704. this.$container.attr("tabindex", this.options.tabbable ? "0" : "-1");
  5705. if(this.options.aria){
  5706. tree.$container
  5707. .attr("role", "treegrid")
  5708. .attr("aria-readonly", true);
  5709. }
  5710. },
  5711. /* Called by nodeRender to sync node order with tag order.*/
  5712. // nodeFixOrder: function(ctx) {
  5713. // },
  5714. nodeRemoveChildMarkup: function(ctx) {
  5715. var node = ctx.node;
  5716. // DT.debug("nodeRemoveChildMarkup()", node.toString());
  5717. node.visit(function(n){
  5718. if(n.tr){
  5719. $(n.tr).remove();
  5720. n.tr = null;
  5721. }
  5722. });
  5723. },
  5724. nodeRemoveMarkup: function(ctx) {
  5725. var node = ctx.node;
  5726. // DT.debug("nodeRemoveMarkup()", node.toString());
  5727. if(node.tr){
  5728. $(node.tr).remove();
  5729. node.tr = null;
  5730. }
  5731. this.nodeRemoveChildMarkup(ctx);
  5732. },
  5733. /* Override standard render. */
  5734. nodeRender: function(ctx, force, deep, collapsed, _recursive) {
  5735. var children, firstTr, i, l, newRow, prevNode, prevTr, subCtx,
  5736. tree = ctx.tree,
  5737. node = ctx.node,
  5738. opts = ctx.options,
  5739. isRootNode = !node.parent;
  5740. if( !_recursive ){
  5741. ctx.hasCollapsedParents = node.parent && !node.parent.expanded;
  5742. }
  5743. // $.ui.fancytree.debug("*** nodeRender " + node + ", isRoot=" + isRootNode, "tr=" + node.tr, "hcp=" + ctx.hasCollapsedParents, "parent.tr=" + (node.parent && node.parent.tr));
  5744. if( !isRootNode ){
  5745. if(!node.tr){
  5746. if( ctx.hasCollapsedParents /*&& !node.parent.tr*/ ) {
  5747. // #166: we assume that the parent will be (recursively) rendered
  5748. // later anyway.
  5749. node.debug("nodeRender ignored due to unrendered parent");
  5750. return;
  5751. }
  5752. // Create new <tr> after previous row
  5753. newRow = tree.rowFragment.firstChild.cloneNode(true);
  5754. prevNode = findPrevRowNode(node);
  5755. // $.ui.fancytree.debug("*** nodeRender " + node + ": prev: " + prevNode.key);
  5756. _assert(prevNode);
  5757. if(collapsed === true && _recursive){
  5758. // hide all child rows, so we can use an animation to show it later
  5759. newRow.style.display = "none";
  5760. }else if(deep && ctx.hasCollapsedParents){
  5761. // also hide this row if deep === true but any parent is collapsed
  5762. newRow.style.display = "none";
  5763. // newRow.style.color = "red";
  5764. }
  5765. if(!prevNode.tr){
  5766. _assert(!prevNode.parent, "prev. row must have a tr, or is system root");
  5767. tree.tbody.appendChild(newRow);
  5768. }else{
  5769. insertSiblingAfter(prevNode.tr, newRow);
  5770. }
  5771. node.tr = newRow;
  5772. if( node.key && opts.generateIds ){
  5773. node.tr.id = opts.idPrefix + node.key;
  5774. }
  5775. node.tr.ftnode = node;
  5776. if(opts.aria){
  5777. // TODO: why doesn't this work:
  5778. // node.li.role = "treeitem";
  5779. $(node.tr).attr("aria-labelledby", "ftal_" + node.key);
  5780. }
  5781. node.span = $("span.fancytree-node", node.tr).get(0);
  5782. // Set icon, link, and title (normally this is only required on initial render)
  5783. this.nodeRenderTitle(ctx);
  5784. // Allow tweaking, binding, after node was created for the first time
  5785. // tree._triggerNodeEvent("createNode", ctx);
  5786. if ( opts.createNode ){
  5787. opts.createNode.call(tree, {type: "createNode"}, ctx);
  5788. }
  5789. } else {
  5790. if( force ) {
  5791. // Set icon, link, and title (normally this is only required on initial render)
  5792. this.nodeRenderTitle(ctx); // triggers renderColumns()
  5793. } else {
  5794. // Update element classes according to node state
  5795. this.nodeRenderStatus(ctx);
  5796. }
  5797. }
  5798. }
  5799. // Allow tweaking after node state was rendered
  5800. // tree._triggerNodeEvent("renderNode", ctx);
  5801. if ( opts.renderNode ){
  5802. opts.renderNode.call(tree, {type: "renderNode"}, ctx);
  5803. }
  5804. // Visit child nodes
  5805. // Add child markup
  5806. children = node.children;
  5807. if(children && (isRootNode || deep || node.expanded)){
  5808. for(i=0, l=children.length; i<l; i++) {
  5809. subCtx = $.extend({}, ctx, {node: children[i]});
  5810. subCtx.hasCollapsedParents = subCtx.hasCollapsedParents || !node.expanded;
  5811. this.nodeRender(subCtx, force, deep, collapsed, true);
  5812. }
  5813. }
  5814. // Make sure, that <tr> order matches node.children order.
  5815. if(children && !_recursive){ // we only have to do it once, for the root branch
  5816. prevTr = node.tr || null;
  5817. firstTr = tree.tbody.firstChild;
  5818. // Iterate over all descendants
  5819. node.visit(function(n){
  5820. if(n.tr){
  5821. if(!n.parent.expanded && n.tr.style.display !== "none"){
  5822. // fix after a node was dropped over a collapsed
  5823. n.tr.style.display = "none";
  5824. setChildRowVisibility(n, false);
  5825. }
  5826. if(n.tr.previousSibling !== prevTr){
  5827. node.debug("_fixOrder: mismatch at node: " + n);
  5828. var nextTr = prevTr ? prevTr.nextSibling : firstTr;
  5829. tree.tbody.insertBefore(n.tr, nextTr);
  5830. }
  5831. prevTr = n.tr;
  5832. }
  5833. });
  5834. }
  5835. // Update element classes according to node state
  5836. // if(!isRootNode){
  5837. // this.nodeRenderStatus(ctx);
  5838. // }
  5839. },
  5840. nodeRenderTitle: function(ctx, title) {
  5841. var $cb,
  5842. node = ctx.node,
  5843. opts = ctx.options;
  5844. this._super(ctx);
  5845. // Move checkbox to custom column
  5846. if(opts.checkbox && opts.table.checkboxColumnIdx != null ){
  5847. $cb = $("span.fancytree-checkbox", node.span).detach();
  5848. $(node.tr).find("td:first").html($cb);
  5849. }
  5850. // Let user code write column content
  5851. // ctx.tree._triggerNodeEvent("renderColumns", node);
  5852. // Update element classes according to node state
  5853. if( ! node.isRoot() ){
  5854. this.nodeRenderStatus(ctx);
  5855. }
  5856. if( !opts.table.customStatus && node.isStatusNode() ) {
  5857. // default rendering for status node: leave other cells empty
  5858. } else if ( opts.renderColumns ) {
  5859. opts.renderColumns.call(ctx.tree, {type: "renderColumns"}, ctx);
  5860. }
  5861. },
  5862. nodeRenderStatus: function(ctx) {
  5863. var indent,
  5864. node = ctx.node,
  5865. opts = ctx.options;
  5866. this._super(ctx);
  5867. $(node.tr).removeClass("fancytree-node");
  5868. // indent
  5869. indent = (node.getLevel() - 1) * opts.table.indentation;
  5870. $(node.span).css({marginLeft: indent + "px"});
  5871. },
  5872. /* Expand node, return Deferred.promise. */
  5873. nodeSetExpanded: function(ctx, flag, opts) {
  5874. var dfd = new $.Deferred(),
  5875. subOpts = $.extend({}, opts, {noEvents: true, noAnimation: true});
  5876. opts = opts || {};
  5877. function _afterExpand(ok) {
  5878. flag = (flag !== false);
  5879. setChildRowVisibility(ctx.node, flag);
  5880. if( ok ) {
  5881. if( flag && ctx.options.autoScroll && !opts.noAnimation && ctx.node.hasChildren() ) {
  5882. // Scroll down to last child, but keep current node visible
  5883. ctx.node.getLastChild().scrollIntoView(true, {topNode: ctx.node}).always(function(){
  5884. if( !opts.noEvents ) {
  5885. ctx.tree._triggerNodeEvent(flag ? "expand" : "collapse", ctx);
  5886. }
  5887. dfd.resolveWith(ctx.node);
  5888. });
  5889. } else {
  5890. if( !opts.noEvents ) {
  5891. ctx.tree._triggerNodeEvent(flag ? "expand" : "collapse", ctx);
  5892. }
  5893. dfd.resolveWith(ctx.node);
  5894. }
  5895. } else {
  5896. if( !opts.noEvents ) {
  5897. ctx.tree._triggerNodeEvent(flag ? "expand" : "collapse", ctx);
  5898. }
  5899. dfd.rejectWith(ctx.node);
  5900. }
  5901. }
  5902. // Call base-expand with disabled events and animation
  5903. this._super(ctx, flag, subOpts).done(function () {
  5904. _afterExpand(true);
  5905. }).fail(function () {
  5906. _afterExpand(false);
  5907. });
  5908. return dfd.promise();
  5909. },
  5910. nodeSetStatus: function(ctx, status, message, details) {
  5911. if(status === "ok"){
  5912. var node = ctx.node,
  5913. firstChild = ( node.children ? node.children[0] : null );
  5914. if ( firstChild && firstChild.isStatusNode() ) {
  5915. $(firstChild.tr).remove();
  5916. }
  5917. }
  5918. this._super(ctx, status, message, details);
  5919. },
  5920. treeClear: function(ctx) {
  5921. this.nodeRemoveChildMarkup(this._makeHookContext(this.rootNode));
  5922. return this._super(ctx);
  5923. }
  5924. /*,
  5925. treeSetFocus: function(ctx, flag) {
  5926. // alert("treeSetFocus" + ctx.tree.$container);
  5927. ctx.tree.$container.focus();
  5928. $.ui.fancytree.focusTree = ctx.tree;
  5929. }*/
  5930. });
  5931. }(jQuery, window, document));
  5932. /*!
  5933. * jquery.fancytree.themeroller.js
  5934. *
  5935. * Enable jQuery UI ThemeRoller styles.
  5936. * (Extension module for jquery.fancytree.js: https://github.com/mar10/fancytree/)
  5937. *
  5938. * @see http://jqueryui.com/themeroller/
  5939. *
  5940. * Copyright (c) 2014, Martin Wendt (http://wwWendt.de)
  5941. *
  5942. * Released under the MIT license
  5943. * https://github.com/mar10/fancytree/wiki/LicenseInfo
  5944. *
  5945. * @version 2.2.0
  5946. * @date 2014-06-28T17:15
  5947. */
  5948. ;(function($, window, document, undefined) {
  5949. "use strict";
  5950. /*******************************************************************************
  5951. * Extension code
  5952. */
  5953. $.ui.fancytree.registerExtension({
  5954. name: "themeroller",
  5955. version: "0.0.1",
  5956. // Default options for this extension.
  5957. options: {
  5958. activeClass: "ui-state-active",
  5959. foccusClass: "ui-state-focus",
  5960. hoverClass: "ui-state-hover",
  5961. selectedClass: "ui-state-highlight"
  5962. },
  5963. treeInit: function(ctx){
  5964. this._super(ctx);
  5965. var $el = ctx.widget.element;
  5966. if($el[0].nodeName === "TABLE"){
  5967. $el.addClass("ui-widget ui-corner-all");
  5968. $el.find(">thead tr").addClass("ui-widget-header");
  5969. $el.find(">tbody").addClass("ui-widget-conent");
  5970. }else{
  5971. $el.addClass("ui-widget ui-widget-content ui-corner-all");
  5972. }
  5973. $el.delegate(".fancytree-node", "mouseenter mouseleave", function(event){
  5974. var node = $.ui.fancytree.getNode(event.target),
  5975. flag = (event.type === "mouseenter");
  5976. node.debug("hover: " + flag);
  5977. $(node.span).toggleClass("ui-state-hover ui-corner-all", flag);
  5978. });
  5979. },
  5980. treeDestroy: function(ctx){
  5981. this._super(ctx);
  5982. ctx.widget.element.removeClass("ui-widget ui-widget-content ui-corner-all");
  5983. },
  5984. nodeRenderStatus: function(ctx){
  5985. var node = ctx.node,
  5986. $el = $(node.span);
  5987. this._super(ctx);
  5988. /*
  5989. .ui-state-highlight: Class to be applied to highlighted or selected elements. Applies "highlight" container styles to an element and its child text, links, and icons.
  5990. .ui-state-error: Class to be applied to error messaging container elements. Applies "error" container styles to an element and its child text, links, and icons.
  5991. .ui-state-error-text: An additional class that applies just the error text color without background. Can be used on form labels for instance. Also applies error icon color to child icons.
  5992. .ui-state-default: Class to be applied to clickable button-like elements. Applies "clickable default" container styles to an element and its child text, links, and icons.
  5993. .ui-state-hover: Class to be applied on mouseover to clickable button-like elements. Applies "clickable hover" container styles to an element and its child text, links, and icons.
  5994. .ui-state-focus: Class to be applied on keyboard focus to clickable button-like elements. Applies "clickable hover" container styles to an element and its child text, links, and icons.
  5995. .ui-state-active: Class to be applied on mousedown to clickable button-like elements. Applies "clickable active" container styles to an element and its child text, links, and icons.
  5996. */
  5997. $el.toggleClass("ui-state-active", node.isActive());
  5998. $el.toggleClass("ui-state-focus", node.hasFocus());
  5999. $el.toggleClass("ui-state-highlight", node.isSelected());
  6000. // node.debug("ext-themeroller.nodeRenderStatus: ", node.span.className);
  6001. }
  6002. });
  6003. }(jQuery, window, document));