jstree.js 204KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026
  1. /*globals jQuery, define, exports, require, window, document */
  2. (function (factory) {
  3. "use strict";
  4. if (typeof define === 'function' && define.amd) {
  5. define(['jquery'], factory);
  6. }
  7. else if(typeof exports === 'object') {
  8. factory(require('jquery'));
  9. }
  10. else {
  11. factory(jQuery);
  12. }
  13. }(function ($, undefined) {
  14. "use strict";
  15. /*!
  16. * jsTree 3.0.0
  17. * http://jstree.com/
  18. *
  19. * Copyright (c) 2013 Ivan Bozhanov (http://vakata.com)
  20. *
  21. * Licensed same as jquery - under the terms of the MIT License
  22. * http://www.opensource.org/licenses/mit-license.php
  23. */
  24. /*!
  25. * if using jslint please allow for the jQuery global and use following options:
  26. * jslint: browser: true, ass: true, bitwise: true, continue: true, nomen: true, plusplus: true, regexp: true, unparam: true, todo: true, white: true
  27. */
  28. // prevent another load? maybe there is a better way?
  29. if($.jstree) {
  30. return;
  31. }
  32. /**
  33. * ### jsTree core functionality
  34. */
  35. // internal variables
  36. var instance_counter = 0,
  37. ccp_node = false,
  38. ccp_mode = false,
  39. ccp_inst = false,
  40. themes_loaded = [],
  41. src = $('script:last').attr('src'),
  42. _d = document, _node = _d.createElement('LI'), _temp1, _temp2;
  43. _node.setAttribute('role', 'treeitem');
  44. _temp1 = _d.createElement('I');
  45. _temp1.className = 'jstree-icon jstree-ocl';
  46. _node.appendChild(_temp1);
  47. _temp1 = _d.createElement('A');
  48. _temp1.className = 'jstree-anchor';
  49. _temp1.setAttribute('href','#');
  50. _temp2 = _d.createElement('I');
  51. _temp2.className = 'jstree-icon jstree-themeicon';
  52. _temp1.appendChild(_temp2);
  53. _node.appendChild(_temp1);
  54. _temp1 = _temp2 = null;
  55. /**
  56. * holds all jstree related functions and variables, including the actual class and methods to create, access and manipulate instances.
  57. * @name $.jstree
  58. */
  59. $.jstree = {
  60. /**
  61. * specifies the jstree version in use
  62. * @name $.jstree.version
  63. */
  64. version : '3.0.0',
  65. /**
  66. * holds all the default options used when creating new instances
  67. * @name $.jstree.defaults
  68. */
  69. defaults : {
  70. /**
  71. * configure which plugins will be active on an instance. Should be an array of strings, where each element is a plugin name. The default is `[]`
  72. * @name $.jstree.defaults.plugins
  73. */
  74. plugins : []
  75. },
  76. /**
  77. * stores all loaded jstree plugins (used internally)
  78. * @name $.jstree.plugins
  79. */
  80. plugins : {},
  81. path : src && src.indexOf('/') !== -1 ? src.replace(/\/[^\/]+$/,'') : '',
  82. idregex : /[\\:&'".,=\- \/]/g
  83. };
  84. /**
  85. * creates a jstree instance
  86. * @name $.jstree.create(el [, options])
  87. * @param {DOMElement|jQuery|String} el the element to create the instance on, can be jQuery extended or a selector
  88. * @param {Object} options options for this instance (extends `$.jstree.defaults`)
  89. * @return {jsTree} the new instance
  90. */
  91. $.jstree.create = function (el, options) {
  92. var tmp = new $.jstree.core(++instance_counter),
  93. opt = options;
  94. options = $.extend(true, {}, $.jstree.defaults, options);
  95. if(opt && opt.plugins) {
  96. options.plugins = opt.plugins;
  97. }
  98. $.each(options.plugins, function (i, k) {
  99. if(i !== 'core') {
  100. tmp = tmp.plugin(k, options[k]);
  101. }
  102. });
  103. tmp.init(el, options);
  104. return tmp;
  105. };
  106. /**
  107. * the jstree class constructor, used only internally
  108. * @private
  109. * @name $.jstree.core(id)
  110. * @param {Number} id this instance's index
  111. */
  112. $.jstree.core = function (id) {
  113. this._id = id;
  114. this._cnt = 0;
  115. this._data = {
  116. core : {
  117. themes : {
  118. name : false,
  119. dots : false,
  120. icons : false
  121. },
  122. selected : [],
  123. last_error : {}
  124. }
  125. };
  126. };
  127. /**
  128. * get a reference to an existing instance
  129. *
  130. * __Examples__
  131. *
  132. * // provided a container with an ID of "tree", and a nested node with an ID of "branch"
  133. * // all of there will return the same instance
  134. * $.jstree.reference('tree');
  135. * $.jstree.reference('#tree');
  136. * $.jstree.reference($('#tree'));
  137. * $.jstree.reference(document.getElementByID('tree'));
  138. * $.jstree.reference('branch');
  139. * $.jstree.reference('#branch');
  140. * $.jstree.reference($('#branch'));
  141. * $.jstree.reference(document.getElementByID('branch'));
  142. *
  143. * @name $.jstree.reference(needle)
  144. * @param {DOMElement|jQuery|String} needle
  145. * @return {jsTree|null} the instance or `null` if not found
  146. */
  147. $.jstree.reference = function (needle) {
  148. var tmp = null,
  149. obj = null;
  150. if(needle && needle.id) { needle = needle.id; }
  151. if(!obj || !obj.length) {
  152. try { obj = $(needle); } catch (ignore) { }
  153. }
  154. if(!obj || !obj.length) {
  155. try { obj = $('#' + needle.replace($.jstree.idregex,'\\$&')); } catch (ignore) { }
  156. }
  157. if(obj && obj.length && (obj = obj.closest('.jstree')).length && (obj = obj.data('jstree'))) {
  158. tmp = obj;
  159. }
  160. else {
  161. $('.jstree').each(function () {
  162. var inst = $(this).data('jstree');
  163. if(inst && inst._model.data[needle]) {
  164. tmp = inst;
  165. return false;
  166. }
  167. });
  168. }
  169. return tmp;
  170. };
  171. /**
  172. * Create an instance, get an instance or invoke a command on a instance.
  173. *
  174. * If there is no instance associated with the current node a new one is created and `arg` is used to extend `$.jstree.defaults` for this new instance. There would be no return value (chaining is not broken).
  175. *
  176. * If there is an existing instance and `arg` is a string the command specified by `arg` is executed on the instance, with any additional arguments passed to the function. If the function returns a value it will be returned (chaining could break depending on function).
  177. *
  178. * If there is an existing instance and `arg` is not a string the instance itself is returned (similar to `$.jstree.reference`).
  179. *
  180. * In any other case - nothing is returned and chaining is not broken.
  181. *
  182. * __Examples__
  183. *
  184. * $('#tree1').jstree(); // creates an instance
  185. * $('#tree2').jstree({ plugins : [] }); // create an instance with some options
  186. * $('#tree1').jstree('open_node', '#branch_1'); // call a method on an existing instance, passing additional arguments
  187. * $('#tree2').jstree(); // get an existing instance (or create an instance)
  188. * $('#tree2').jstree(true); // get an existing instance (will not create new instance)
  189. * $('#branch_1').jstree().select_node('#branch_1'); // get an instance (using a nested element and call a method)
  190. *
  191. * @name $().jstree([arg])
  192. * @param {String|Object} arg
  193. * @return {Mixed}
  194. */
  195. $.fn.jstree = function (arg) {
  196. // check for string argument
  197. var is_method = (typeof arg === 'string'),
  198. args = Array.prototype.slice.call(arguments, 1),
  199. result = null;
  200. this.each(function () {
  201. // get the instance (if there is one) and method (if it exists)
  202. var instance = $.jstree.reference(this),
  203. method = is_method && instance ? instance[arg] : null;
  204. // if calling a method, and method is available - execute on the instance
  205. result = is_method && method ?
  206. method.apply(instance, args) :
  207. null;
  208. // if there is no instance and no method is being called - create one
  209. if(!instance && !is_method && (arg === undefined || $.isPlainObject(arg))) {
  210. $(this).data('jstree', new $.jstree.create(this, arg));
  211. }
  212. // if there is an instance and no method is called - return the instance
  213. if( (instance && !is_method) || arg === true ) {
  214. result = instance || false;
  215. }
  216. // if there was a method call which returned a result - break and return the value
  217. if(result !== null && result !== undefined) {
  218. return false;
  219. }
  220. });
  221. // if there was a method call with a valid return value - return that, otherwise continue the chain
  222. return result !== null && result !== undefined ?
  223. result : this;
  224. };
  225. /**
  226. * used to find elements containing an instance
  227. *
  228. * __Examples__
  229. *
  230. * $('div:jstree').each(function () {
  231. * $(this).jstree('destroy');
  232. * });
  233. *
  234. * @name $(':jstree')
  235. * @return {jQuery}
  236. */
  237. $.expr[':'].jstree = $.expr.createPseudo(function(search) {
  238. return function(a) {
  239. return $(a).hasClass('jstree') &&
  240. $(a).data('jstree') !== undefined;
  241. };
  242. });
  243. /**
  244. * stores all defaults for the core
  245. * @name $.jstree.defaults.core
  246. */
  247. $.jstree.defaults.core = {
  248. /**
  249. * data configuration
  250. *
  251. * If left as `false` the HTML inside the jstree container element is used to populate the tree (that should be an unordered list with list items).
  252. *
  253. * You can also pass in a HTML string or a JSON array here.
  254. *
  255. * It is possible to pass in a standard jQuery-like AJAX config and jstree will automatically determine if the response is JSON or HTML and use that to populate the tree.
  256. * In addition to the standard jQuery ajax options here you can suppy functions for `data` and `url`, the functions will be run in the current instance's scope and a param will be passed indicating which node is being loaded, the return value of those functions will be used.
  257. *
  258. * The last option is to specify a function, that function will receive the node being loaded as argument and a second param which is a function which should be called with the result.
  259. *
  260. * __Examples__
  261. *
  262. * // AJAX
  263. * $('#tree').jstree({
  264. * 'core' : {
  265. * 'data' : {
  266. * 'url' : '/get/children/',
  267. * 'data' : function (node) {
  268. * return { 'id' : node.id };
  269. * }
  270. * }
  271. * });
  272. *
  273. * // direct data
  274. * $('#tree').jstree({
  275. * 'core' : {
  276. * 'data' : [
  277. * 'Simple root node',
  278. * {
  279. * 'id' : 'node_2',
  280. * 'text' : 'Root node with options',
  281. * 'state' : { 'opened' : true, 'selected' : true },
  282. * 'children' : [ { 'text' : 'Child 1' }, 'Child 2']
  283. * }
  284. * ]
  285. * });
  286. *
  287. * // function
  288. * $('#tree').jstree({
  289. * 'core' : {
  290. * 'data' : function (obj, callback) {
  291. * callback.call(this, ['Root 1', 'Root 2']);
  292. * }
  293. * });
  294. *
  295. * @name $.jstree.defaults.core.data
  296. */
  297. data : false,
  298. /**
  299. * configure the various strings used throughout the tree
  300. *
  301. * You can use an object where the key is the string you need to replace and the value is your replacement.
  302. * Another option is to specify a function which will be called with an argument of the needed string and should return the replacement.
  303. * If left as `false` no replacement is made.
  304. *
  305. * __Examples__
  306. *
  307. * $('#tree').jstree({
  308. * 'core' : {
  309. * 'strings' : {
  310. * 'Loading...' : 'Please wait ...'
  311. * }
  312. * }
  313. * });
  314. *
  315. * @name $.jstree.defaults.core.strings
  316. */
  317. strings : false,
  318. /**
  319. * determines what happens when a user tries to modify the structure of the tree
  320. * If left as `false` all operations like create, rename, delete, move or copy are prevented.
  321. * You can set this to `true` to allow all interactions or use a function to have better control.
  322. *
  323. * __Examples__
  324. *
  325. * $('#tree').jstree({
  326. * 'core' : {
  327. * 'check_callback' : function (operation, node, node_parent, node_position, more) {
  328. * // operation can be 'create_node', 'rename_node', 'delete_node', 'move_node' or 'copy_node'
  329. * // in case of 'rename_node' node_position is filled with the new node name
  330. * return operation === 'rename_node' ? true : false;
  331. * }
  332. * }
  333. * });
  334. *
  335. * @name $.jstree.defaults.core.check_callback
  336. */
  337. check_callback : false,
  338. /**
  339. * a callback called with a single object parameter in the instance's scope when something goes wrong (operation prevented, ajax failed, etc)
  340. * @name $.jstree.defaults.core.error
  341. */
  342. error : $.noop,
  343. /**
  344. * the open / close animation duration in milliseconds - set this to `false` to disable the animation (default is `200`)
  345. * @name $.jstree.defaults.core.animation
  346. */
  347. animation : 200,
  348. /**
  349. * a boolean indicating if multiple nodes can be selected
  350. * @name $.jstree.defaults.core.multiple
  351. */
  352. multiple : true,
  353. /**
  354. * theme configuration object
  355. * @name $.jstree.defaults.core.themes
  356. */
  357. themes : {
  358. /**
  359. * the name of the theme to use (if left as `false` the default theme is used)
  360. * @name $.jstree.defaults.core.themes.name
  361. */
  362. name : false,
  363. /**
  364. * the URL of the theme's CSS file, leave this as `false` if you have manually included the theme CSS (recommended). You can set this to `true` too which will try to autoload the theme.
  365. * @name $.jstree.defaults.core.themes.url
  366. */
  367. url : false,
  368. /**
  369. * the location of all jstree themes - only used if `url` is set to `true`
  370. * @name $.jstree.defaults.core.themes.dir
  371. */
  372. dir : false,
  373. /**
  374. * a boolean indicating if connecting dots are shown
  375. * @name $.jstree.defaults.core.themes.dots
  376. */
  377. dots : true,
  378. /**
  379. * a boolean indicating if node icons are shown
  380. * @name $.jstree.defaults.core.themes.icons
  381. */
  382. icons : true,
  383. /**
  384. * a boolean indicating if the tree background is striped
  385. * @name $.jstree.defaults.core.themes.stripes
  386. */
  387. stripes : false,
  388. /**
  389. * a string (or boolean `false`) specifying the theme variant to use (if the theme supports variants)
  390. * @name $.jstree.defaults.core.themes.variant
  391. */
  392. variant : false,
  393. /**
  394. * a boolean specifying if a reponsive version of the theme should kick in on smaller screens (if the theme supports it). Defaults to `true`.
  395. * @name $.jstree.defaults.core.themes.responsive
  396. */
  397. responsive : true
  398. },
  399. /**
  400. * if left as `true` all parents of all selected nodes will be opened once the tree loads (so that all selected nodes are visible to the user)
  401. * @name $.jstree.defaults.core.expand_selected_onload
  402. */
  403. expand_selected_onload : true
  404. };
  405. $.jstree.core.prototype = {
  406. /**
  407. * used to decorate an instance with a plugin. Used internally.
  408. * @private
  409. * @name plugin(deco [, opts])
  410. * @param {String} deco the plugin to decorate with
  411. * @param {Object} opts options for the plugin
  412. * @return {jsTree}
  413. */
  414. plugin : function (deco, opts) {
  415. var Child = $.jstree.plugins[deco];
  416. if(Child) {
  417. this._data[deco] = {};
  418. Child.prototype = this;
  419. return new Child(opts, this);
  420. }
  421. return this;
  422. },
  423. /**
  424. * used to decorate an instance with a plugin. Used internally.
  425. * @private
  426. * @name init(el, optons)
  427. * @param {DOMElement|jQuery|String} el the element we are transforming
  428. * @param {Object} options options for this instance
  429. * @trigger init.jstree, loading.jstree, loaded.jstree, ready.jstree, changed.jstree
  430. */
  431. init : function (el, options) {
  432. this._model = {
  433. data : {
  434. '#' : {
  435. id : '#',
  436. parent : null,
  437. parents : [],
  438. children : [],
  439. children_d : [],
  440. state : { loaded : false }
  441. }
  442. },
  443. changed : [],
  444. force_full_redraw : false,
  445. redraw_timeout : false,
  446. default_state : {
  447. loaded : true,
  448. opened : false,
  449. selected : false,
  450. disabled : false
  451. }
  452. };
  453. this.element = $(el).addClass('jstree jstree-' + this._id);
  454. this.settings = options;
  455. this.element.bind("destroyed", $.proxy(this.teardown, this));
  456. this._data.core.ready = false;
  457. this._data.core.loaded = false;
  458. this._data.core.rtl = (this.element.css("direction") === "rtl");
  459. this.element[this._data.core.rtl ? 'addClass' : 'removeClass']("jstree-rtl");
  460. this.element.attr('role','tree');
  461. this.bind();
  462. /**
  463. * triggered after all events are bound
  464. * @event
  465. * @name init.jstree
  466. */
  467. this.trigger("init");
  468. this._data.core.original_container_html = this.element.find(" > ul > li").clone(true);
  469. this._data.core.original_container_html
  470. .find("li").addBack()
  471. .contents().filter(function() {
  472. return this.nodeType === 3 && (!this.nodeValue || /^\s+$/.test(this.nodeValue));
  473. })
  474. .remove();
  475. this.element.html("<"+"ul class='jstree-container-ul'><"+"li class='jstree-initial-node jstree-loading jstree-leaf jstree-last'><i class='jstree-icon jstree-ocl'></i><"+"a class='jstree-anchor' href='#'><i class='jstree-icon jstree-themeicon-hidden'></i>" + this.get_string("Loading ...") + "</a></li></ul>");
  476. this._data.core.li_height = this.get_container_ul().children("li:eq(0)").height() || 18;
  477. /**
  478. * triggered after the loading text is shown and before loading starts
  479. * @event
  480. * @name loading.jstree
  481. */
  482. this.trigger("loading");
  483. this.load_node('#');
  484. },
  485. /**
  486. * destroy an instance
  487. * @name destroy()
  488. */
  489. destroy : function () {
  490. this.element.unbind("destroyed", this.teardown);
  491. this.teardown();
  492. },
  493. /**
  494. * part of the destroying of an instance. Used internally.
  495. * @private
  496. * @name teardown()
  497. */
  498. teardown : function () {
  499. this.unbind();
  500. this.element
  501. .removeClass('jstree')
  502. .removeData('jstree')
  503. .find("[class^='jstree']")
  504. .addBack()
  505. .attr("class", function () { return this.className.replace(/jstree[^ ]*|$/ig,''); });
  506. this.element = null;
  507. },
  508. /**
  509. * bind all events. Used internally.
  510. * @private
  511. * @name bind()
  512. */
  513. bind : function () {
  514. this.element
  515. .on("dblclick.jstree", function () {
  516. if(document.selection && document.selection.empty) {
  517. document.selection.empty();
  518. }
  519. else {
  520. if(window.getSelection) {
  521. var sel = window.getSelection();
  522. try {
  523. sel.removeAllRanges();
  524. sel.collapse();
  525. } catch (ignore) { }
  526. }
  527. }
  528. })
  529. .on("click.jstree", ".jstree-ocl", $.proxy(function (e) {
  530. this.toggle_node(e.target);
  531. }, this))
  532. .on("click.jstree", ".jstree-anchor", $.proxy(function (e) {
  533. e.preventDefault();
  534. $(e.currentTarget).focus();
  535. this.activate_node(e.currentTarget, e);
  536. }, this))
  537. .on('keydown.jstree', '.jstree-anchor', $.proxy(function (e) {
  538. if(e.target.tagName === "INPUT") { return true; }
  539. var o = null;
  540. switch(e.which) {
  541. case 13:
  542. case 32:
  543. e.type = "click";
  544. $(e.currentTarget).trigger(e);
  545. break;
  546. case 37:
  547. e.preventDefault();
  548. if(this.is_open(e.currentTarget)) {
  549. this.close_node(e.currentTarget);
  550. }
  551. else {
  552. o = this.get_prev_dom(e.currentTarget);
  553. if(o && o.length) { o.children('.jstree-anchor').focus(); }
  554. }
  555. break;
  556. case 38:
  557. e.preventDefault();
  558. o = this.get_prev_dom(e.currentTarget);
  559. if(o && o.length) { o.children('.jstree-anchor').focus(); }
  560. break;
  561. case 39:
  562. e.preventDefault();
  563. if(this.is_closed(e.currentTarget)) {
  564. this.open_node(e.currentTarget, function (o) { this.get_node(o, true).children('.jstree-anchor').focus(); });
  565. }
  566. else {
  567. o = this.get_next_dom(e.currentTarget);
  568. if(o && o.length) { o.children('.jstree-anchor').focus(); }
  569. }
  570. break;
  571. case 40:
  572. e.preventDefault();
  573. o = this.get_next_dom(e.currentTarget);
  574. if(o && o.length) { o.children('.jstree-anchor').focus(); }
  575. break;
  576. // delete
  577. case 46:
  578. e.preventDefault();
  579. o = this.get_node(e.currentTarget);
  580. if(o && o.id && o.id !== '#') {
  581. o = this.is_selected(o) ? this.get_selected() : o;
  582. // this.delete_node(o);
  583. }
  584. break;
  585. // f2
  586. case 113:
  587. e.preventDefault();
  588. o = this.get_node(e.currentTarget);
  589. /*!
  590. if(o && o.id && o.id !== '#') {
  591. // this.edit(o);
  592. }
  593. */
  594. break;
  595. default:
  596. // console.log(e.which);
  597. break;
  598. }
  599. }, this))
  600. .on("load_node.jstree", $.proxy(function (e, data) {
  601. if(data.status) {
  602. if(data.node.id === '#' && !this._data.core.loaded) {
  603. this._data.core.loaded = true;
  604. /**
  605. * triggered after the root node is loaded for the first time
  606. * @event
  607. * @name loaded.jstree
  608. */
  609. this.trigger("loaded");
  610. }
  611. if(!this._data.core.ready && !this.get_container_ul().find('.jstree-loading:eq(0)').length) {
  612. this._data.core.ready = true;
  613. if(this._data.core.selected.length) {
  614. if(this.settings.core.expand_selected_onload) {
  615. var tmp = [], i, j;
  616. for(i = 0, j = this._data.core.selected.length; i < j; i++) {
  617. tmp = tmp.concat(this._model.data[this._data.core.selected[i]].parents);
  618. }
  619. tmp = $.vakata.array_unique(tmp);
  620. for(i = 0, j = tmp.length; i < j; i++) {
  621. this.open_node(tmp[i], false, 0);
  622. }
  623. }
  624. this.trigger('changed', { 'action' : 'ready', 'selected' : this._data.core.selected });
  625. }
  626. /**
  627. * triggered after all nodes are finished loading
  628. * @event
  629. * @name ready.jstree
  630. */
  631. setTimeout($.proxy(function () { this.trigger("ready"); }, this), 0);
  632. }
  633. }
  634. }, this))
  635. // THEME RELATED
  636. .on("init.jstree", $.proxy(function () {
  637. var s = this.settings.core.themes;
  638. this._data.core.themes.dots = s.dots;
  639. this._data.core.themes.stripes = s.stripes;
  640. this._data.core.themes.icons = s.icons;
  641. this.set_theme(s.name || "default", s.url);
  642. this.set_theme_variant(s.variant);
  643. }, this))
  644. .on("loading.jstree", $.proxy(function () {
  645. this[ this._data.core.themes.dots ? "show_dots" : "hide_dots" ]();
  646. this[ this._data.core.themes.icons ? "show_icons" : "hide_icons" ]();
  647. this[ this._data.core.themes.stripes ? "show_stripes" : "hide_stripes" ]();
  648. }, this))
  649. .on('focus.jstree', '.jstree-anchor', $.proxy(function (e) {
  650. this.element.find('.jstree-hovered').not(e.currentTarget).mouseleave();
  651. $(e.currentTarget).mouseenter();
  652. }, this))
  653. .on('mouseenter.jstree', '.jstree-anchor', $.proxy(function (e) {
  654. this.hover_node(e.currentTarget);
  655. }, this))
  656. .on('mouseleave.jstree', '.jstree-anchor', $.proxy(function (e) {
  657. this.dehover_node(e.currentTarget);
  658. }, this));
  659. },
  660. /**
  661. * part of the destroying of an instance. Used internally.
  662. * @private
  663. * @name unbind()
  664. */
  665. unbind : function () {
  666. this.element.off('.jstree');
  667. $(document).off('.jstree-' + this._id);
  668. },
  669. /**
  670. * trigger an event. Used internally.
  671. * @private
  672. * @name trigger(ev [, data])
  673. * @param {String} ev the name of the event to trigger
  674. * @param {Object} data additional data to pass with the event
  675. */
  676. trigger : function (ev, data) {
  677. if(!data) {
  678. data = {};
  679. }
  680. data.instance = this;
  681. this.element.triggerHandler(ev.replace('.jstree','') + '.jstree', data);
  682. },
  683. /**
  684. * returns the jQuery extended instance container
  685. * @name get_container()
  686. * @return {jQuery}
  687. */
  688. get_container : function () {
  689. return this.element;
  690. },
  691. /**
  692. * returns the jQuery extended main UL node inside the instance container. Used internally.
  693. * @private
  694. * @name get_container_ul()
  695. * @return {jQuery}
  696. */
  697. get_container_ul : function () {
  698. return this.element.children("ul:eq(0)");
  699. },
  700. /**
  701. * gets string replacements (localization). Used internally.
  702. * @private
  703. * @name get_string(key)
  704. * @param {String} key
  705. * @return {String}
  706. */
  707. get_string : function (key) {
  708. var a = this.settings.core.strings;
  709. if($.isFunction(a)) { return a.call(this, key); }
  710. if(a && a[key]) { return a[key]; }
  711. return key;
  712. },
  713. /**
  714. * gets the first child of a DOM node. Used internally.
  715. * @private
  716. * @name _firstChild(dom)
  717. * @param {DOMElement} dom
  718. * @return {DOMElement}
  719. */
  720. _firstChild : function (dom) {
  721. dom = dom ? dom.firstChild : null;
  722. while(dom !== null && dom.nodeType !== 1) {
  723. dom = dom.nextSibling;
  724. }
  725. return dom;
  726. },
  727. /**
  728. * gets the next sibling of a DOM node. Used internally.
  729. * @private
  730. * @name _nextSibling(dom)
  731. * @param {DOMElement} dom
  732. * @return {DOMElement}
  733. */
  734. _nextSibling : function (dom) {
  735. dom = dom ? dom.nextSibling : null;
  736. while(dom !== null && dom.nodeType !== 1) {
  737. dom = dom.nextSibling;
  738. }
  739. return dom;
  740. },
  741. /**
  742. * gets the previous sibling of a DOM node. Used internally.
  743. * @private
  744. * @name _previousSibling(dom)
  745. * @param {DOMElement} dom
  746. * @return {DOMElement}
  747. */
  748. _previousSibling : function (dom) {
  749. dom = dom ? dom.previousSibling : null;
  750. while(dom !== null && dom.nodeType !== 1) {
  751. dom = dom.previousSibling;
  752. }
  753. return dom;
  754. },
  755. /**
  756. * get the JSON representation of a node (or the actual jQuery extended DOM node) by using any input (child DOM element, ID string, selector, etc)
  757. * @name get_node(obj [, as_dom])
  758. * @param {mixed} obj
  759. * @param {Boolean} as_dom
  760. * @return {Object|jQuery}
  761. */
  762. get_node : function (obj, as_dom) {
  763. if(obj && obj.id) {
  764. obj = obj.id;
  765. }
  766. var dom;
  767. try {
  768. if(this._model.data[obj]) {
  769. obj = this._model.data[obj];
  770. }
  771. else if(((dom = $(obj, this.element)).length || (dom = $('#' + obj.replace($.jstree.idregex,'\\$&'), this.element)).length) && this._model.data[dom.closest('li').attr('id')]) {
  772. obj = this._model.data[dom.closest('li').attr('id')];
  773. }
  774. else if((dom = $(obj, this.element)).length && dom.hasClass('jstree')) {
  775. obj = this._model.data['#'];
  776. }
  777. else {
  778. return false;
  779. }
  780. if(as_dom) {
  781. obj = obj.id === '#' ? this.element : $('#' + obj.id.replace($.jstree.idregex,'\\$&'), this.element);
  782. }
  783. return obj;
  784. } catch (ex) { return false; }
  785. },
  786. /**
  787. * get the path to a node, either consisting of node texts, or of node IDs, optionally glued together (otherwise an array)
  788. * @name get_path(obj [, glue, ids])
  789. * @param {mixed} obj the node
  790. * @param {String} glue if you want the path as a string - pass the glue here (for example '/'), if a falsy value is supplied here, an array is returned
  791. * @param {Boolean} ids if set to true build the path using ID, otherwise node text is used
  792. * @return {mixed}
  793. */
  794. get_path : function (obj, glue, ids) {
  795. obj = obj.parents ? obj : this.get_node(obj);
  796. if(!obj || obj.id === '#' || !obj.parents) {
  797. return false;
  798. }
  799. var i, j, p = [];
  800. p.push(ids ? obj.id : obj.text);
  801. for(i = 0, j = obj.parents.length; i < j; i++) {
  802. p.push(ids ? obj.parents[i] : this.get_text(obj.parents[i]));
  803. }
  804. p = p.reverse().slice(1);
  805. return glue ? p.join(glue) : p;
  806. },
  807. /**
  808. * get the next visible node that is below the `obj` node. If `strict` is set to `true` only sibling nodes are returned.
  809. * @name get_next_dom(obj [, strict])
  810. * @param {mixed} obj
  811. * @param {Boolean} strict
  812. * @return {jQuery}
  813. */
  814. get_next_dom : function (obj, strict) {
  815. var tmp;
  816. obj = this.get_node(obj, true);
  817. if(obj[0] === this.element[0]) {
  818. tmp = this._firstChild(this.get_container_ul()[0]);
  819. return tmp ? $(tmp) : false;
  820. }
  821. if(!obj || !obj.length) {
  822. return false;
  823. }
  824. if(strict) {
  825. tmp = this._nextSibling(obj[0]);
  826. return tmp ? $(tmp) : false;
  827. }
  828. if(obj.hasClass("jstree-open")) {
  829. tmp = this._firstChild(obj.children('ul')[0]);
  830. return tmp ? $(tmp) : false;
  831. }
  832. if((tmp = this._nextSibling(obj[0])) !== null) {
  833. return $(tmp);
  834. }
  835. return obj.parentsUntil(".jstree","li").next("li").eq(0);
  836. },
  837. /**
  838. * get the previous visible node that is above the `obj` node. If `strict` is set to `true` only sibling nodes are returned.
  839. * @name get_prev_dom(obj [, strict])
  840. * @param {mixed} obj
  841. * @param {Boolean} strict
  842. * @return {jQuery}
  843. */
  844. get_prev_dom : function (obj, strict) {
  845. var tmp;
  846. obj = this.get_node(obj, true);
  847. if(obj[0] === this.element[0]) {
  848. tmp = this.get_container_ul()[0].lastChild;
  849. return tmp ? $(tmp) : false;
  850. }
  851. if(!obj || !obj.length) {
  852. return false;
  853. }
  854. if(strict) {
  855. tmp = this._previousSibling(obj[0]);
  856. return tmp ? $(tmp) : false;
  857. }
  858. if((tmp = this._previousSibling(obj[0])) !== null) {
  859. obj = $(tmp);
  860. while(obj.hasClass("jstree-open")) {
  861. obj = obj.children("ul:eq(0)").children("li:last");
  862. }
  863. return obj;
  864. }
  865. tmp = obj[0].parentNode.parentNode;
  866. return tmp && tmp.tagName === 'LI' ? $(tmp) : false;
  867. },
  868. /**
  869. * get the parent ID of a node
  870. * @name get_parent(obj)
  871. * @param {mixed} obj
  872. * @return {String}
  873. */
  874. get_parent : function (obj) {
  875. obj = this.get_node(obj);
  876. if(!obj || obj.id === '#') {
  877. return false;
  878. }
  879. return obj.parent;
  880. },
  881. /**
  882. * get a jQuery collection of all the children of a node (node must be rendered)
  883. * @name get_children_dom(obj)
  884. * @param {mixed} obj
  885. * @return {jQuery}
  886. */
  887. get_children_dom : function (obj) {
  888. obj = this.get_node(obj, true);
  889. if(obj[0] === this.element[0]) {
  890. return this.get_container_ul().children("li");
  891. }
  892. if(!obj || !obj.length) {
  893. return false;
  894. }
  895. return obj.children("ul").children("li");
  896. },
  897. /**
  898. * checks if a node has children
  899. * @name is_parent(obj)
  900. * @param {mixed} obj
  901. * @return {Boolean}
  902. */
  903. is_parent : function (obj) {
  904. obj = this.get_node(obj);
  905. return obj && (obj.state.loaded === false || obj.children.length > 0);
  906. },
  907. /**
  908. * checks if a node is loaded (its children are available)
  909. * @name is_loaded(obj)
  910. * @param {mixed} obj
  911. * @return {Boolean}
  912. */
  913. is_loaded : function (obj) {
  914. obj = this.get_node(obj);
  915. return obj && obj.state.loaded;
  916. },
  917. /**
  918. * check if a node is currently loading (fetching children)
  919. * @name is_loading(obj)
  920. * @param {mixed} obj
  921. * @return {Boolean}
  922. */
  923. is_loading : function (obj) {
  924. obj = this.get_node(obj);
  925. return obj && obj.state && obj.state.loading;
  926. },
  927. /**
  928. * check if a node is opened
  929. * @name is_open(obj)
  930. * @param {mixed} obj
  931. * @return {Boolean}
  932. */
  933. is_open : function (obj) {
  934. obj = this.get_node(obj);
  935. return obj && obj.state.opened;
  936. },
  937. /**
  938. * check if a node is in a closed state
  939. * @name is_closed(obj)
  940. * @param {mixed} obj
  941. * @return {Boolean}
  942. */
  943. is_closed : function (obj) {
  944. obj = this.get_node(obj);
  945. return obj && this.is_parent(obj) && !obj.state.opened;
  946. },
  947. /**
  948. * check if a node has no children
  949. * @name is_leaf(obj)
  950. * @param {mixed} obj
  951. * @return {Boolean}
  952. */
  953. is_leaf : function (obj) {
  954. return !this.is_parent(obj);
  955. },
  956. /**
  957. * loads a node (fetches its children using the `core.data` setting). Multiple nodes can be passed to by using an array.
  958. * @name load_node(obj [, callback])
  959. * @param {mixed} obj
  960. * @param {function} callback a function to be executed once loading is conplete, the function is executed in the instance's scope and receives two arguments - the node and a boolean status
  961. * @return {Boolean}
  962. * @trigger load_node.jstree
  963. */
  964. load_node : function (obj, callback) {
  965. var t1, t2, k, l, i, j, c;
  966. if($.isArray(obj)) {
  967. obj = obj.slice();
  968. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  969. this.load_node(obj[t1], callback);
  970. }
  971. return true;
  972. }
  973. obj = this.get_node(obj);
  974. if(!obj) {
  975. if(callback) { callback.call(this, obj, false); }
  976. return false;
  977. }
  978. if(obj.state.loaded) {
  979. obj.state.loaded = false;
  980. for(k = 0, l = obj.children_d.length; k < l; k++) {
  981. for(i = 0, j = obj.parents.length; i < j; i++) {
  982. this._model.data[obj.parents[i]].children_d = $.vakata.array_remove_item(this._model.data[obj.parents[i]].children_d, obj.children_d[k]);
  983. }
  984. if(this._model.data[obj.children_d[k]].state.selected) {
  985. c = true;
  986. this._data.core.selected = $.vakata.array_remove_item(this._data.core.selected, obj.children_d[k]);
  987. }
  988. delete this._model.data[obj.children_d[k]];
  989. }
  990. obj.children = [];
  991. obj.children_d = [];
  992. if(c) {
  993. this.trigger('changed', { 'action' : 'load_node', 'node' : obj, 'selected' : this._data.core.selected });
  994. }
  995. }
  996. obj.state.loading = true;
  997. this.get_node(obj, true).addClass("jstree-loading");
  998. this._load_node(obj, $.proxy(function (status) {
  999. obj.state.loading = false;
  1000. obj.state.loaded = status;
  1001. var dom = this.get_node(obj, true);
  1002. if(obj.state.loaded && !obj.children.length && dom && dom.length && !dom.hasClass('jstree-leaf')) {
  1003. dom.removeClass('jstree-closed jstree-open').addClass('jstree-leaf');
  1004. }
  1005. dom.removeClass("jstree-loading");
  1006. /**
  1007. * triggered after a node is loaded
  1008. * @event
  1009. * @name load_node.jstree
  1010. * @param {Object} node the node that was loading
  1011. * @param {Boolean} status was the node loaded successfully
  1012. */
  1013. this.trigger('load_node', { "node" : obj, "status" : status });
  1014. if(callback) {
  1015. callback.call(this, obj, status);
  1016. }
  1017. }, this));
  1018. return true;
  1019. },
  1020. /**
  1021. * load an array of nodes (will also load unavailable nodes as soon as the appear in the structure). Used internally.
  1022. * @private
  1023. * @name _load_nodes(nodes [, callback])
  1024. * @param {array} nodes
  1025. * @param {function} callback a function to be executed once loading is complete, the function is executed in the instance's scope and receives one argument - the array passed to _load_nodes
  1026. */
  1027. _load_nodes : function (nodes, callback, is_callback) {
  1028. var r = true,
  1029. c = function () { this._load_nodes(nodes, callback, true); },
  1030. m = this._model.data, i, j;
  1031. for(i = 0, j = nodes.length; i < j; i++) {
  1032. if(m[nodes[i]] && (!m[nodes[i]].state.loaded || !is_callback)) {
  1033. if(!this.is_loading(nodes[i])) {
  1034. this.load_node(nodes[i], c);
  1035. }
  1036. r = false;
  1037. }
  1038. }
  1039. if(r) {
  1040. if(!callback.done) {
  1041. callback.call(this, nodes);
  1042. callback.done = true;
  1043. }
  1044. }
  1045. },
  1046. /**
  1047. * handles the actual loading of a node. Used only internally.
  1048. * @private
  1049. * @name _load_node(obj [, callback])
  1050. * @param {mixed} obj
  1051. * @param {function} callback a function to be executed once loading is complete, the function is executed in the instance's scope and receives one argument - a boolean status
  1052. * @return {Boolean}
  1053. */
  1054. _load_node : function (obj, callback) {
  1055. var s = this.settings.core.data, t;
  1056. // use original HTML
  1057. if(!s) {
  1058. return callback.call(this, obj.id === '#' ? this._append_html_data(obj, this._data.core.original_container_html.clone(true)) : false);
  1059. }
  1060. if($.isFunction(s)) {
  1061. return s.call(this, obj, $.proxy(function (d) {
  1062. return d === false ? callback.call(this, false) : callback.call(this, this[typeof d === 'string' ? '_append_html_data' : '_append_json_data'](obj, typeof d === 'string' ? $(d) : d));
  1063. }, this));
  1064. }
  1065. if(typeof s === 'object') {
  1066. if(s.url) {
  1067. s = $.extend(true, {}, s);
  1068. if($.isFunction(s.url)) {
  1069. s.url = s.url.call(this, obj);
  1070. }
  1071. if($.isFunction(s.data)) {
  1072. s.data = s.data.call(this, obj);
  1073. }
  1074. return $.ajax(s)
  1075. .done($.proxy(function (d,t,x) {
  1076. var type = x.getResponseHeader('Content-Type');
  1077. if(type.indexOf('json') !== -1 || typeof d === "object") {
  1078. return callback.call(this, this._append_json_data(obj, d));
  1079. }
  1080. if(type.indexOf('html') !== -1 || typeof d === "string") {
  1081. return callback.call(this, this._append_html_data(obj, $(d)));
  1082. }
  1083. this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'core', 'id' : 'core_04', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id, 'xhr' : x }) };
  1084. return callback.call(this, false);
  1085. }, this))
  1086. .fail($.proxy(function (f) {
  1087. callback.call(this, false);
  1088. this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'core', 'id' : 'core_04', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id, 'xhr' : f }) };
  1089. this.settings.core.error.call(this, this._data.core.last_error);
  1090. }, this));
  1091. }
  1092. t = ($.isArray(s) || $.isPlainObject(s)) ? JSON.parse(JSON.stringify(s)) : s;
  1093. if(obj.id !== "#") { this._data.core.last_error = { 'error' : 'nodata', 'plugin' : 'core', 'id' : 'core_05', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id }) }; }
  1094. return callback.call(this, (obj.id === "#" ? this._append_json_data(obj, t) : false) );
  1095. }
  1096. if(typeof s === 'string') {
  1097. if(obj.id !== "#") { this._data.core.last_error = { 'error' : 'nodata', 'plugin' : 'core', 'id' : 'core_06', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id }) }; }
  1098. return callback.call(this, (obj.id === "#" ? this._append_html_data(obj, $(s)) : false) );
  1099. }
  1100. return callback.call(this, false);
  1101. },
  1102. /**
  1103. * adds a node to the list of nodes to redraw. Used only internally.
  1104. * @private
  1105. * @name _node_changed(obj [, callback])
  1106. * @param {mixed} obj
  1107. */
  1108. _node_changed : function (obj) {
  1109. obj = this.get_node(obj);
  1110. if(obj) {
  1111. this._model.changed.push(obj.id);
  1112. }
  1113. },
  1114. /**
  1115. * appends HTML content to the tree. Used internally.
  1116. * @private
  1117. * @name _append_html_data(obj, data)
  1118. * @param {mixed} obj the node to append to
  1119. * @param {String} data the HTML string to parse and append
  1120. * @return {Boolean}
  1121. * @trigger model.jstree, changed.jstree
  1122. */
  1123. _append_html_data : function (dom, data) {
  1124. dom = this.get_node(dom);
  1125. dom.children = [];
  1126. dom.children_d = [];
  1127. var dat = data.is('ul') ? data.children() : data,
  1128. par = dom.id,
  1129. chd = [],
  1130. dpc = [],
  1131. m = this._model.data,
  1132. p = m[par],
  1133. s = this._data.core.selected.length,
  1134. tmp, i, j;
  1135. dat.each($.proxy(function (i, v) {
  1136. tmp = this._parse_model_from_html($(v), par, p.parents.concat());
  1137. if(tmp) {
  1138. chd.push(tmp);
  1139. dpc.push(tmp);
  1140. if(m[tmp].children_d.length) {
  1141. dpc = dpc.concat(m[tmp].children_d);
  1142. }
  1143. }
  1144. }, this));
  1145. p.children = chd;
  1146. p.children_d = dpc;
  1147. for(i = 0, j = p.parents.length; i < j; i++) {
  1148. m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc);
  1149. }
  1150. /**
  1151. * triggered when new data is inserted to the tree model
  1152. * @event
  1153. * @name model.jstree
  1154. * @param {Array} nodes an array of node IDs
  1155. * @param {String} parent the parent ID of the nodes
  1156. */
  1157. this.trigger('model', { "nodes" : dpc, 'parent' : par });
  1158. if(par !== '#') {
  1159. this._node_changed(par);
  1160. this.redraw();
  1161. }
  1162. else {
  1163. this.get_container_ul().children('.jstree-initial-node').remove();
  1164. this.redraw(true);
  1165. }
  1166. if(this._data.core.selected.length !== s) {
  1167. this.trigger('changed', { 'action' : 'model', 'selected' : this._data.core.selected });
  1168. }
  1169. return true;
  1170. },
  1171. /**
  1172. * appends JSON content to the tree. Used internally.
  1173. * @private
  1174. * @name _append_json_data(obj, data)
  1175. * @param {mixed} obj the node to append to
  1176. * @param {String} data the JSON object to parse and append
  1177. * @return {Boolean}
  1178. */
  1179. _append_json_data : function (dom, data) {
  1180. dom = this.get_node(dom);
  1181. dom.children = [];
  1182. dom.children_d = [];
  1183. var dat = data,
  1184. par = dom.id,
  1185. chd = [],
  1186. dpc = [],
  1187. m = this._model.data,
  1188. p = m[par],
  1189. s = this._data.core.selected.length,
  1190. tmp, i, j;
  1191. // *%$@!!!
  1192. if(dat.d) {
  1193. dat = dat.d;
  1194. if(typeof dat === "string") {
  1195. dat = JSON.parse(dat);
  1196. }
  1197. }
  1198. if(!$.isArray(dat)) { dat = [dat]; }
  1199. if(dat.length && dat[0].id !== undefined && dat[0].parent !== undefined) {
  1200. // Flat JSON support (for easy import from DB):
  1201. // 1) convert to object (foreach)
  1202. for(i = 0, j = dat.length; i < j; i++) {
  1203. if(!dat[i].children) {
  1204. dat[i].children = [];
  1205. }
  1206. m[dat[i].id.toString()] = dat[i];
  1207. }
  1208. // 2) populate children (foreach)
  1209. for(i = 0, j = dat.length; i < j; i++) {
  1210. m[dat[i].parent.toString()].children.push(dat[i].id.toString());
  1211. // populate parent.children_d
  1212. p.children_d.push(dat[i].id.toString());
  1213. }
  1214. // 3) normalize && populate parents and children_d with recursion
  1215. for(i = 0, j = p.children.length; i < j; i++) {
  1216. tmp = this._parse_model_from_flat_json(m[p.children[i]], par, p.parents.concat());
  1217. dpc.push(tmp);
  1218. if(m[tmp].children_d.length) {
  1219. dpc = dpc.concat(m[tmp].children_d);
  1220. }
  1221. }
  1222. // ?) three_state selection - p.state.selected && t - (if three_state foreach(dat => ch) -> foreach(parents) if(parent.selected) child.selected = true;
  1223. }
  1224. else {
  1225. for(i = 0, j = dat.length; i < j; i++) {
  1226. tmp = this._parse_model_from_json(dat[i], par, p.parents.concat());
  1227. if(tmp) {
  1228. chd.push(tmp);
  1229. dpc.push(tmp);
  1230. if(m[tmp].children_d.length) {
  1231. dpc = dpc.concat(m[tmp].children_d);
  1232. }
  1233. }
  1234. }
  1235. p.children = chd;
  1236. p.children_d = dpc;
  1237. for(i = 0, j = p.parents.length; i < j; i++) {
  1238. m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc);
  1239. }
  1240. }
  1241. this.trigger('model', { "nodes" : dpc, 'parent' : par });
  1242. if(par !== '#') {
  1243. this._node_changed(par);
  1244. this.redraw();
  1245. }
  1246. else {
  1247. // this.get_container_ul().children('.jstree-initial-node').remove();
  1248. this.redraw(true);
  1249. }
  1250. if(this._data.core.selected.length !== s) {
  1251. this.trigger('changed', { 'action' : 'model', 'selected' : this._data.core.selected });
  1252. }
  1253. return true;
  1254. },
  1255. /**
  1256. * parses a node from a jQuery object and appends them to the in memory tree model. Used internally.
  1257. * @private
  1258. * @name _parse_model_from_html(d [, p, ps])
  1259. * @param {jQuery} d the jQuery object to parse
  1260. * @param {String} p the parent ID
  1261. * @param {Array} ps list of all parents
  1262. * @return {String} the ID of the object added to the model
  1263. */
  1264. _parse_model_from_html : function (d, p, ps) {
  1265. if(!ps) { ps = []; }
  1266. else { ps = [].concat(ps); }
  1267. if(p) { ps.unshift(p); }
  1268. var c, e, m = this._model.data,
  1269. data = {
  1270. id : false,
  1271. text : false,
  1272. icon : true,
  1273. parent : p,
  1274. parents : ps,
  1275. children : [],
  1276. children_d : [],
  1277. data : null,
  1278. state : { },
  1279. li_attr : { id : false },
  1280. a_attr : { href : '#' },
  1281. original : false
  1282. }, i, tmp, tid;
  1283. for(i in this._model.default_state) {
  1284. if(this._model.default_state.hasOwnProperty(i)) {
  1285. data.state[i] = this._model.default_state[i];
  1286. }
  1287. }
  1288. tmp = $.vakata.attributes(d, true);
  1289. $.each(tmp, function (i, v) {
  1290. v = $.trim(v);
  1291. if(!v.length) { return true; }
  1292. data.li_attr[i] = v;
  1293. if(i === 'id') {
  1294. data.id = v.toString();
  1295. }
  1296. });
  1297. tmp = d.children('a').eq(0);
  1298. if(tmp.length) {
  1299. tmp = $.vakata.attributes(tmp, true);
  1300. $.each(tmp, function (i, v) {
  1301. v = $.trim(v);
  1302. if(v.length) {
  1303. data.a_attr[i] = v;
  1304. }
  1305. });
  1306. }
  1307. tmp = d.children("a:eq(0)").length ? d.children("a:eq(0)").clone() : d.clone();
  1308. tmp.children("ins, i, ul").remove();
  1309. tmp = tmp.html();
  1310. tmp = $('<div />').html(tmp);
  1311. data.text = tmp.html();
  1312. tmp = d.data();
  1313. data.data = tmp ? $.extend(true, {}, tmp) : null;
  1314. data.state.opened = d.hasClass('jstree-open');
  1315. data.state.selected = d.children('a').hasClass('jstree-clicked');
  1316. data.state.disabled = d.children('a').hasClass('jstree-disabled');
  1317. if(data.data && data.data.jstree) {
  1318. for(i in data.data.jstree) {
  1319. if(data.data.jstree.hasOwnProperty(i)) {
  1320. data.state[i] = data.data.jstree[i];
  1321. }
  1322. }
  1323. }
  1324. tmp = d.children("a").children(".jstree-themeicon");
  1325. if(tmp.length) {
  1326. data.icon = tmp.hasClass('jstree-themeicon-hidden') ? false : tmp.attr('rel');
  1327. }
  1328. if(data.state.icon) {
  1329. data.icon = data.state.icon;
  1330. }
  1331. tmp = d.children("ul").children("li");
  1332. do {
  1333. tid = 'j' + this._id + '_' + (++this._cnt);
  1334. } while(m[tid]);
  1335. data.id = data.li_attr.id ? data.li_attr.id.toString() : tid;
  1336. if(tmp.length) {
  1337. tmp.each($.proxy(function (i, v) {
  1338. c = this._parse_model_from_html($(v), data.id, ps);
  1339. e = this._model.data[c];
  1340. data.children.push(c);
  1341. if(e.children_d.length) {
  1342. data.children_d = data.children_d.concat(e.children_d);
  1343. }
  1344. }, this));
  1345. data.children_d = data.children_d.concat(data.children);
  1346. }
  1347. else {
  1348. if(d.hasClass('jstree-closed')) {
  1349. data.state.loaded = false;
  1350. }
  1351. }
  1352. if(data.li_attr['class']) {
  1353. data.li_attr['class'] = data.li_attr['class'].replace('jstree-closed','').replace('jstree-open','');
  1354. }
  1355. if(data.a_attr['class']) {
  1356. data.a_attr['class'] = data.a_attr['class'].replace('jstree-clicked','').replace('jstree-disabled','');
  1357. }
  1358. m[data.id] = data;
  1359. if(data.state.selected) {
  1360. this._data.core.selected.push(data.id);
  1361. }
  1362. return data.id;
  1363. },
  1364. /**
  1365. * parses a node from a JSON object (used when dealing with flat data, which has no nesting of children, but has id and parent properties) and appends it to the in memory tree model. Used internally.
  1366. * @private
  1367. * @name _parse_model_from_flat_json(d [, p, ps])
  1368. * @param {Object} d the JSON object to parse
  1369. * @param {String} p the parent ID
  1370. * @param {Array} ps list of all parents
  1371. * @return {String} the ID of the object added to the model
  1372. */
  1373. _parse_model_from_flat_json : function (d, p, ps) {
  1374. if(!ps) { ps = []; }
  1375. else { ps = ps.concat(); }
  1376. if(p) { ps.unshift(p); }
  1377. var tid = d.id.toString(),
  1378. m = this._model.data,
  1379. df = this._model.default_state,
  1380. i, j, c, e,
  1381. tmp = {
  1382. id : tid,
  1383. text : d.text || '',
  1384. icon : d.icon !== undefined ? d.icon : true,
  1385. parent : p,
  1386. parents : ps,
  1387. children : d.children || [],
  1388. children_d : d.children_d || [],
  1389. data : d.data,
  1390. state : { },
  1391. li_attr : { id : false },
  1392. a_attr : { href : '#' },
  1393. original : false
  1394. };
  1395. for(i in df) {
  1396. if(df.hasOwnProperty(i)) {
  1397. tmp.state[i] = df[i];
  1398. }
  1399. }
  1400. if(d && d.data && d.data.jstree && d.data.jstree.icon) {
  1401. tmp.icon = d.data.jstree.icon;
  1402. }
  1403. if(d && d.data) {
  1404. tmp.data = d.data;
  1405. if(d.data.jstree) {
  1406. for(i in d.data.jstree) {
  1407. if(d.data.jstree.hasOwnProperty(i)) {
  1408. tmp.state[i] = d.data.jstree[i];
  1409. }
  1410. }
  1411. }
  1412. }
  1413. if(d && typeof d.state === 'object') {
  1414. for (i in d.state) {
  1415. if(d.state.hasOwnProperty(i)) {
  1416. tmp.state[i] = d.state[i];
  1417. }
  1418. }
  1419. }
  1420. if(d && typeof d.li_attr === 'object') {
  1421. for (i in d.li_attr) {
  1422. if(d.li_attr.hasOwnProperty(i)) {
  1423. tmp.li_attr[i] = d.li_attr[i];
  1424. }
  1425. }
  1426. }
  1427. if(!tmp.li_attr.id) {
  1428. tmp.li_attr.id = tid;
  1429. }
  1430. if(d && typeof d.a_attr === 'object') {
  1431. for (i in d.a_attr) {
  1432. if(d.a_attr.hasOwnProperty(i)) {
  1433. tmp.a_attr[i] = d.a_attr[i];
  1434. }
  1435. }
  1436. }
  1437. if(d && d.children && d.children === true) {
  1438. tmp.state.loaded = false;
  1439. tmp.children = [];
  1440. tmp.children_d = [];
  1441. }
  1442. m[tmp.id] = tmp;
  1443. for(i = 0, j = tmp.children.length; i < j; i++) {
  1444. c = this._parse_model_from_flat_json(m[tmp.children[i]], tmp.id, ps);
  1445. e = m[c];
  1446. tmp.children_d.push(c);
  1447. if(e.children_d.length) {
  1448. tmp.children_d = tmp.children_d.concat(e.children_d);
  1449. }
  1450. }
  1451. delete d.data;
  1452. delete d.children;
  1453. m[tmp.id].original = d;
  1454. if(tmp.state.selected) {
  1455. this._data.core.selected.push(tmp.id);
  1456. }
  1457. return tmp.id;
  1458. },
  1459. /**
  1460. * parses a node from a JSON object and appends it to the in memory tree model. Used internally.
  1461. * @private
  1462. * @name _parse_model_from_json(d [, p, ps])
  1463. * @param {Object} d the JSON object to parse
  1464. * @param {String} p the parent ID
  1465. * @param {Array} ps list of all parents
  1466. * @return {String} the ID of the object added to the model
  1467. */
  1468. _parse_model_from_json : function (d, p, ps) {
  1469. if(!ps) { ps = []; }
  1470. else { ps = ps.concat(); }
  1471. if(p) { ps.unshift(p); }
  1472. var tid = false, i, j, c, e, m = this._model.data, df = this._model.default_state, tmp;
  1473. do {
  1474. tid = 'j' + this._id + '_' + (++this._cnt);
  1475. } while(m[tid]);
  1476. tmp = {
  1477. id : false,
  1478. text : typeof d === 'string' ? d : '',
  1479. icon : typeof d === 'object' && d.icon !== undefined ? d.icon : true,
  1480. parent : p,
  1481. parents : ps,
  1482. children : [],
  1483. children_d : [],
  1484. data : null,
  1485. state : { },
  1486. li_attr : { id : false },
  1487. a_attr : { href : '#' },
  1488. original : false
  1489. };
  1490. for(i in df) {
  1491. if(df.hasOwnProperty(i)) {
  1492. tmp.state[i] = df[i];
  1493. }
  1494. }
  1495. if(d && d.id) { tmp.id = d.id.toString(); }
  1496. if(d && d.text) { tmp.text = d.text; }
  1497. if(d && d.data && d.data.jstree && d.data.jstree.icon) {
  1498. tmp.icon = d.data.jstree.icon;
  1499. }
  1500. if(d && d.data) {
  1501. tmp.data = d.data;
  1502. if(d.data.jstree) {
  1503. for(i in d.data.jstree) {
  1504. if(d.data.jstree.hasOwnProperty(i)) {
  1505. tmp.state[i] = d.data.jstree[i];
  1506. }
  1507. }
  1508. }
  1509. }
  1510. if(d && typeof d.state === 'object') {
  1511. for (i in d.state) {
  1512. if(d.state.hasOwnProperty(i)) {
  1513. tmp.state[i] = d.state[i];
  1514. }
  1515. }
  1516. }
  1517. if(d && typeof d.li_attr === 'object') {
  1518. for (i in d.li_attr) {
  1519. if(d.li_attr.hasOwnProperty(i)) {
  1520. tmp.li_attr[i] = d.li_attr[i];
  1521. }
  1522. }
  1523. }
  1524. if(tmp.li_attr.id && !tmp.id) {
  1525. tmp.id = tmp.li_attr.id.toString();
  1526. }
  1527. if(!tmp.id) {
  1528. tmp.id = tid;
  1529. }
  1530. if(!tmp.li_attr.id) {
  1531. tmp.li_attr.id = tmp.id;
  1532. }
  1533. if(d && typeof d.a_attr === 'object') {
  1534. for (i in d.a_attr) {
  1535. if(d.a_attr.hasOwnProperty(i)) {
  1536. tmp.a_attr[i] = d.a_attr[i];
  1537. }
  1538. }
  1539. }
  1540. if(d && d.children && d.children.length) {
  1541. for(i = 0, j = d.children.length; i < j; i++) {
  1542. c = this._parse_model_from_json(d.children[i], tmp.id, ps);
  1543. e = m[c];
  1544. tmp.children.push(c);
  1545. if(e.children_d.length) {
  1546. tmp.children_d = tmp.children_d.concat(e.children_d);
  1547. }
  1548. }
  1549. tmp.children_d = tmp.children_d.concat(tmp.children);
  1550. }
  1551. if(d && d.children && d.children === true) {
  1552. tmp.state.loaded = false;
  1553. tmp.children = [];
  1554. tmp.children_d = [];
  1555. }
  1556. delete d.data;
  1557. delete d.children;
  1558. tmp.original = d;
  1559. m[tmp.id] = tmp;
  1560. if(tmp.state.selected) {
  1561. this._data.core.selected.push(tmp.id);
  1562. }
  1563. return tmp.id;
  1564. },
  1565. /**
  1566. * redraws all nodes that need to be redrawn. Used internally.
  1567. * @private
  1568. * @name _redraw()
  1569. * @trigger redraw.jstree
  1570. */
  1571. _redraw : function () {
  1572. var nodes = this._model.force_full_redraw ? this._model.data['#'].children.concat([]) : this._model.changed.concat([]),
  1573. f = document.createElement('UL'), tmp, i, j;
  1574. for(i = 0, j = nodes.length; i < j; i++) {
  1575. tmp = this.redraw_node(nodes[i], true, this._model.force_full_redraw);
  1576. if(tmp && this._model.force_full_redraw) {
  1577. f.appendChild(tmp);
  1578. }
  1579. }
  1580. if(this._model.force_full_redraw) {
  1581. f.className = this.get_container_ul()[0].className;
  1582. this.element.empty().append(f);
  1583. //this.get_container_ul()[0].appendChild(f);
  1584. }
  1585. this._model.force_full_redraw = false;
  1586. this._model.changed = [];
  1587. /**
  1588. * triggered after nodes are redrawn
  1589. * @event
  1590. * @name redraw.jstree
  1591. * @param {array} nodes the redrawn nodes
  1592. */
  1593. this.trigger('redraw', { "nodes" : nodes });
  1594. },
  1595. /**
  1596. * redraws all nodes that need to be redrawn or optionally - the whole tree
  1597. * @name redraw([full])
  1598. * @param {Boolean} full if set to `true` all nodes are redrawn.
  1599. */
  1600. redraw : function (full) {
  1601. if(full) {
  1602. this._model.force_full_redraw = true;
  1603. }
  1604. //if(this._model.redraw_timeout) {
  1605. // clearTimeout(this._model.redraw_timeout);
  1606. //}
  1607. //this._model.redraw_timeout = setTimeout($.proxy(this._redraw, this),0);
  1608. this._redraw();
  1609. },
  1610. /**
  1611. * redraws a single node. Used internally.
  1612. * @private
  1613. * @name redraw_node(node, deep, is_callback)
  1614. * @param {mixed} node the node to redraw
  1615. * @param {Boolean} deep should child nodes be redrawn too
  1616. * @param {Boolean} is_callback is this a recursion call
  1617. */
  1618. redraw_node : function (node, deep, is_callback) {
  1619. var obj = this.get_node(node),
  1620. par = false,
  1621. ind = false,
  1622. old = false,
  1623. i = false,
  1624. j = false,
  1625. k = false,
  1626. c = '',
  1627. d = document,
  1628. m = this._model.data,
  1629. f = false,
  1630. s = false;
  1631. if(!obj) { return false; }
  1632. if(obj.id === '#') { return this.redraw(true); }
  1633. deep = deep || obj.children.length === 0;
  1634. node = !document.querySelector ? document.getElementById(obj.id) : this.element[0].querySelector('#' + ("0123456789".indexOf(obj.id[0]) !== -1 ? '\\3' + obj.id[0] + ' ' + obj.id.substr(1).replace($.jstree.idregex,'\\$&') : obj.id.replace($.jstree.idregex,'\\$&')) ); //, this.element);
  1635. if(!node) {
  1636. deep = true;
  1637. //node = d.createElement('LI');
  1638. if(!is_callback) {
  1639. par = obj.parent !== '#' ? $('#' + obj.parent.replace($.jstree.idregex,'\\$&'), this.element)[0] : null;
  1640. if(par !== null && (!par || !m[obj.parent].state.opened)) {
  1641. return false;
  1642. }
  1643. ind = $.inArray(obj.id, par === null ? m['#'].children : m[obj.parent].children);
  1644. }
  1645. }
  1646. else {
  1647. node = $(node);
  1648. if(!is_callback) {
  1649. par = node.parent().parent()[0];
  1650. if(par === this.element[0]) {
  1651. par = null;
  1652. }
  1653. ind = node.index();
  1654. }
  1655. // m[obj.id].data = node.data(); // use only node's data, no need to touch jquery storage
  1656. if(!deep && obj.children.length && !node.children('ul').length) {
  1657. deep = true;
  1658. }
  1659. if(!deep) {
  1660. old = node.children('UL')[0];
  1661. }
  1662. s = node.attr('aria-selected');
  1663. f = node.children('.jstree-anchor')[0] === document.activeElement;
  1664. node.remove();
  1665. //node = d.createElement('LI');
  1666. //node = node[0];
  1667. }
  1668. node = _node.cloneNode(true);
  1669. // node is DOM, deep is boolean
  1670. c = 'jstree-node ';
  1671. for(i in obj.li_attr) {
  1672. if(obj.li_attr.hasOwnProperty(i)) {
  1673. if(i === 'id') { continue; }
  1674. if(i !== 'class') {
  1675. node.setAttribute(i, obj.li_attr[i]);
  1676. }
  1677. else {
  1678. c += obj.li_attr[i];
  1679. }
  1680. }
  1681. }
  1682. if(s && s !== "false") {
  1683. node.setAttribute('aria-selected', true);
  1684. }
  1685. if(obj.state.loaded && !obj.children.length) {
  1686. c += ' jstree-leaf';
  1687. }
  1688. else {
  1689. c += obj.state.opened && obj.state.loaded ? ' jstree-open' : ' jstree-closed';
  1690. node.setAttribute('aria-expanded', (obj.state.opened && obj.state.loaded) );
  1691. }
  1692. if(obj.parent !== null && m[obj.parent].children[m[obj.parent].children.length - 1] === obj.id) {
  1693. c += ' jstree-last';
  1694. }
  1695. node.id = obj.id;
  1696. node.className = c;
  1697. c = ( obj.state.selected ? ' jstree-clicked' : '') + ( obj.state.disabled ? ' jstree-disabled' : '');
  1698. for(j in obj.a_attr) {
  1699. if(obj.a_attr.hasOwnProperty(j)) {
  1700. if(j === 'href' && obj.a_attr[j] === '#') { continue; }
  1701. if(j !== 'class') {
  1702. node.childNodes[1].setAttribute(j, obj.a_attr[j]);
  1703. }
  1704. else {
  1705. c += ' ' + obj.a_attr[j];
  1706. }
  1707. }
  1708. }
  1709. if(c.length) {
  1710. node.childNodes[1].className = 'jstree-anchor ' + c;
  1711. }
  1712. if((obj.icon && obj.icon !== true) || obj.icon === false) {
  1713. if(obj.icon === false) {
  1714. node.childNodes[1].childNodes[0].className += ' jstree-themeicon-hidden';
  1715. }
  1716. else if(obj.icon.indexOf('/') === -1 && obj.icon.indexOf('.') === -1) {
  1717. node.childNodes[1].childNodes[0].className += ' ' + obj.icon + ' jstree-themeicon-custom';
  1718. }
  1719. else {
  1720. node.childNodes[1].childNodes[0].style.backgroundImage = 'url('+obj.icon+')';
  1721. node.childNodes[1].childNodes[0].style.backgroundPosition = 'center center';
  1722. node.childNodes[1].childNodes[0].style.backgroundSize = 'auto';
  1723. node.childNodes[1].childNodes[0].className += ' jstree-themeicon-custom';
  1724. }
  1725. }
  1726. //node.childNodes[1].appendChild(d.createTextNode(obj.text));
  1727. node.childNodes[1].innerHTML += obj.text;
  1728. // if(obj.data) { $.data(node, obj.data); } // always work with node's data, no need to touch jquery store
  1729. if(deep && obj.children.length && obj.state.opened && obj.state.loaded) {
  1730. k = d.createElement('UL');
  1731. k.setAttribute('role', 'group');
  1732. k.className = 'jstree-children';
  1733. for(i = 0, j = obj.children.length; i < j; i++) {
  1734. k.appendChild(this.redraw_node(obj.children[i], deep, true));
  1735. }
  1736. node.appendChild(k);
  1737. }
  1738. if(old) {
  1739. node.appendChild(old);
  1740. }
  1741. if(!is_callback) {
  1742. // append back using par / ind
  1743. if(!par) {
  1744. par = this.element[0];
  1745. }
  1746. if(!par.getElementsByTagName('UL').length) {
  1747. i = d.createElement('UL');
  1748. i.setAttribute('role', 'group');
  1749. i.className = 'jstree-children';
  1750. par.appendChild(i);
  1751. par = i;
  1752. }
  1753. else {
  1754. par = par.getElementsByTagName('UL')[0];
  1755. }
  1756. if(ind < par.childNodes.length) {
  1757. par.insertBefore(node, par.childNodes[ind]);
  1758. }
  1759. else {
  1760. par.appendChild(node);
  1761. }
  1762. if(f) {
  1763. node.childNodes[1].focus();
  1764. }
  1765. }
  1766. if(obj.state.opened && !obj.state.loaded) {
  1767. obj.state.opened = false;
  1768. setTimeout($.proxy(function () {
  1769. this.open_node(obj.id, false, 0);
  1770. }, this), 0);
  1771. }
  1772. return node;
  1773. },
  1774. /**
  1775. * opens a node, revaling its children. If the node is not loaded it will be loaded and opened once ready.
  1776. * @name open_node(obj [, callback, animation])
  1777. * @param {mixed} obj the node to open
  1778. * @param {Function} callback a function to execute once the node is opened
  1779. * @param {Number} animation the animation duration in milliseconds when opening the node (overrides the `core.animation` setting). Use `false` for no animation.
  1780. * @trigger open_node.jstree, after_open.jstree, before_open.jstree
  1781. */
  1782. open_node : function (obj, callback, animation) {
  1783. var t1, t2, d, t;
  1784. if($.isArray(obj)) {
  1785. obj = obj.slice();
  1786. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  1787. this.open_node(obj[t1], callback, animation);
  1788. }
  1789. return true;
  1790. }
  1791. obj = this.get_node(obj);
  1792. if(!obj || obj.id === '#') {
  1793. return false;
  1794. }
  1795. animation = animation === undefined ? this.settings.core.animation : animation;
  1796. if(!this.is_closed(obj)) {
  1797. if(callback) {
  1798. callback.call(this, obj, false);
  1799. }
  1800. return false;
  1801. }
  1802. if(!this.is_loaded(obj)) {
  1803. if(this.is_loading(obj)) {
  1804. return setTimeout($.proxy(function () {
  1805. this.open_node(obj, callback, animation);
  1806. }, this), 500);
  1807. }
  1808. this.load_node(obj, function (o, ok) {
  1809. return ok ? this.open_node(o, callback, animation) : (callback ? callback.call(this, o, false) : false);
  1810. });
  1811. }
  1812. else {
  1813. d = this.get_node(obj, true);
  1814. t = this;
  1815. if(d.length) {
  1816. if(obj.children.length && !this._firstChild(d.children('ul')[0])) {
  1817. obj.state.opened = true;
  1818. this.redraw_node(obj, true);
  1819. d = this.get_node(obj, true);
  1820. }
  1821. if(!animation) {
  1822. this.trigger('before_open', { "node" : obj });
  1823. d[0].className = d[0].className.replace('jstree-closed', 'jstree-open');
  1824. d[0].setAttribute("aria-expanded", true);
  1825. }
  1826. else {
  1827. this.trigger('before_open', { "node" : obj });
  1828. d
  1829. .children("ul").css("display","none").end()
  1830. .removeClass("jstree-closed").addClass("jstree-open").attr("aria-expanded", true)
  1831. .children("ul").stop(true, true)
  1832. .slideDown(animation, function () {
  1833. this.style.display = "";
  1834. t.trigger("after_open", { "node" : obj });
  1835. });
  1836. }
  1837. }
  1838. obj.state.opened = true;
  1839. if(callback) {
  1840. callback.call(this, obj, true);
  1841. }
  1842. if(!d.length) {
  1843. /**
  1844. * triggered when a node is about to be opened (if the node is supposed to be in the DOM, it will be, but it won't be visible yet)
  1845. * @event
  1846. * @name before_open.jstree
  1847. * @param {Object} node the opened node
  1848. */
  1849. this.trigger('before_open', { "node" : obj });
  1850. }
  1851. /**
  1852. * triggered when a node is opened (if there is an animation it will not be completed yet)
  1853. * @event
  1854. * @name open_node.jstree
  1855. * @param {Object} node the opened node
  1856. */
  1857. this.trigger('open_node', { "node" : obj });
  1858. if(!animation || !d.length) {
  1859. /**
  1860. * triggered when a node is opened and the animation is complete
  1861. * @event
  1862. * @name after_open.jstree
  1863. * @param {Object} node the opened node
  1864. */
  1865. this.trigger("after_open", { "node" : obj });
  1866. }
  1867. }
  1868. },
  1869. /**
  1870. * opens every parent of a node (node should be loaded)
  1871. * @name _open_to(obj)
  1872. * @param {mixed} obj the node to reveal
  1873. * @private
  1874. */
  1875. _open_to : function (obj) {
  1876. obj = this.get_node(obj);
  1877. if(!obj || obj.id === '#') {
  1878. return false;
  1879. }
  1880. var i, j, p = obj.parents;
  1881. for(i = 0, j = p.length; i < j; i+=1) {
  1882. if(i !== '#') {
  1883. this.open_node(p[i], false, 0);
  1884. }
  1885. }
  1886. return $('#' + obj.id.replace($.jstree.idregex,'\\$&'), this.element);
  1887. },
  1888. /**
  1889. * closes a node, hiding its children
  1890. * @name close_node(obj [, animation])
  1891. * @param {mixed} obj the node to close
  1892. * @param {Number} animation the animation duration in milliseconds when closing the node (overrides the `core.animation` setting). Use `false` for no animation.
  1893. * @trigger close_node.jstree, after_close.jstree
  1894. */
  1895. close_node : function (obj, animation) {
  1896. var t1, t2, t, d;
  1897. if($.isArray(obj)) {
  1898. obj = obj.slice();
  1899. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  1900. this.close_node(obj[t1], animation);
  1901. }
  1902. return true;
  1903. }
  1904. obj = this.get_node(obj);
  1905. if(!obj || obj.id === '#') {
  1906. return false;
  1907. }
  1908. if(this.is_closed(obj)) {
  1909. return false;
  1910. }
  1911. animation = animation === undefined ? this.settings.core.animation : animation;
  1912. t = this;
  1913. d = this.get_node(obj, true);
  1914. if(d.length) {
  1915. if(!animation) {
  1916. d[0].className = d[0].className.replace('jstree-open', 'jstree-closed');
  1917. d.attr("aria-expanded", false).children('ul').remove();
  1918. }
  1919. else {
  1920. d
  1921. .children("ul").attr("style","display:block !important").end()
  1922. .removeClass("jstree-open").addClass("jstree-closed").attr("aria-expanded", false)
  1923. .children("ul").stop(true, true).slideUp(animation, function () {
  1924. this.style.display = "";
  1925. d.children('ul').remove();
  1926. t.trigger("after_close", { "node" : obj });
  1927. });
  1928. }
  1929. }
  1930. obj.state.opened = false;
  1931. /**
  1932. * triggered when a node is closed (if there is an animation it will not be complete yet)
  1933. * @event
  1934. * @name close_node.jstree
  1935. * @param {Object} node the closed node
  1936. */
  1937. this.trigger('close_node',{ "node" : obj });
  1938. if(!animation || !d.length) {
  1939. /**
  1940. * triggered when a node is closed and the animation is complete
  1941. * @event
  1942. * @name after_close.jstree
  1943. * @param {Object} node the closed node
  1944. */
  1945. this.trigger("after_close", { "node" : obj });
  1946. }
  1947. },
  1948. /**
  1949. * toggles a node - closing it if it is open, opening it if it is closed
  1950. * @name toggle_node(obj)
  1951. * @param {mixed} obj the node to toggle
  1952. */
  1953. toggle_node : function (obj) {
  1954. var t1, t2;
  1955. if($.isArray(obj)) {
  1956. obj = obj.slice();
  1957. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  1958. this.toggle_node(obj[t1]);
  1959. }
  1960. return true;
  1961. }
  1962. if(this.is_closed(obj)) {
  1963. return this.open_node(obj);
  1964. }
  1965. if(this.is_open(obj)) {
  1966. return this.close_node(obj);
  1967. }
  1968. },
  1969. /**
  1970. * opens all nodes within a node (or the tree), revaling their children. If the node is not loaded it will be loaded and opened once ready.
  1971. * @name open_all([obj, animation, original_obj])
  1972. * @param {mixed} obj the node to open recursively, omit to open all nodes in the tree
  1973. * @param {Number} animation the animation duration in milliseconds when opening the nodes, the default is no animation
  1974. * @param {jQuery} reference to the node that started the process (internal use)
  1975. * @trigger open_all.jstree
  1976. */
  1977. open_all : function (obj, animation, original_obj) {
  1978. if(!obj) { obj = '#'; }
  1979. obj = this.get_node(obj);
  1980. if(!obj) { return false; }
  1981. var dom = obj.id === '#' ? this.get_container_ul() : this.get_node(obj, true), i, j, _this;
  1982. if(!dom.length) {
  1983. for(i = 0, j = obj.children_d.length; i < j; i++) {
  1984. if(this.is_closed(this._model.data[obj.children_d[i]])) {
  1985. this._model.data[obj.children_d[i]].state.opened = true;
  1986. }
  1987. }
  1988. return this.trigger('open_all', { "node" : obj });
  1989. }
  1990. original_obj = original_obj || dom;
  1991. _this = this;
  1992. dom = this.is_closed(obj) ? dom.find('li.jstree-closed').addBack() : dom.find('li.jstree-closed');
  1993. dom.each(function () {
  1994. _this.open_node(
  1995. this,
  1996. function(node, status) { if(status && this.is_parent(node)) { this.open_all(node, animation, original_obj); } },
  1997. animation || 0
  1998. );
  1999. });
  2000. if(original_obj.find('li.jstree-closed').length === 0) {
  2001. /**
  2002. * triggered when an `open_all` call completes
  2003. * @event
  2004. * @name open_all.jstree
  2005. * @param {Object} node the opened node
  2006. */
  2007. this.trigger('open_all', { "node" : this.get_node(original_obj) });
  2008. }
  2009. },
  2010. /**
  2011. * closes all nodes within a node (or the tree), revaling their children
  2012. * @name close_all([obj, animation])
  2013. * @param {mixed} obj the node to close recursively, omit to close all nodes in the tree
  2014. * @param {Number} animation the animation duration in milliseconds when closing the nodes, the default is no animation
  2015. * @trigger close_all.jstree
  2016. */
  2017. close_all : function (obj, animation) {
  2018. if(!obj) { obj = '#'; }
  2019. obj = this.get_node(obj);
  2020. if(!obj) { return false; }
  2021. var dom = obj.id === '#' ? this.get_container_ul() : this.get_node(obj, true),
  2022. _this = this, i, j;
  2023. if(!dom.length) {
  2024. for(i = 0, j = obj.children_d.length; i < j; i++) {
  2025. this._model.data[obj.children_d[i]].state.opened = false;
  2026. }
  2027. return this.trigger('close_all', { "node" : obj });
  2028. }
  2029. dom = this.is_open(obj) ? dom.find('li.jstree-open').addBack() : dom.find('li.jstree-open');
  2030. $(dom.get().reverse()).each(function () { _this.close_node(this, animation || 0); });
  2031. /**
  2032. * triggered when an `close_all` call completes
  2033. * @event
  2034. * @name close_all.jstree
  2035. * @param {Object} node the closed node
  2036. */
  2037. this.trigger('close_all', { "node" : obj });
  2038. },
  2039. /**
  2040. * checks if a node is disabled (not selectable)
  2041. * @name is_disabled(obj)
  2042. * @param {mixed} obj
  2043. * @return {Boolean}
  2044. */
  2045. is_disabled : function (obj) {
  2046. obj = this.get_node(obj);
  2047. return obj && obj.state && obj.state.disabled;
  2048. },
  2049. /**
  2050. * enables a node - so that it can be selected
  2051. * @name enable_node(obj)
  2052. * @param {mixed} obj the node to enable
  2053. * @trigger enable_node.jstree
  2054. */
  2055. enable_node : function (obj) {
  2056. var t1, t2;
  2057. if($.isArray(obj)) {
  2058. obj = obj.slice();
  2059. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  2060. this.enable_node(obj[t1]);
  2061. }
  2062. return true;
  2063. }
  2064. obj = this.get_node(obj);
  2065. if(!obj || obj.id === '#') {
  2066. return false;
  2067. }
  2068. obj.state.disabled = false;
  2069. this.get_node(obj,true).children('.jstree-anchor').removeClass('jstree-disabled');
  2070. /**
  2071. * triggered when an node is enabled
  2072. * @event
  2073. * @name enable_node.jstree
  2074. * @param {Object} node the enabled node
  2075. */
  2076. this.trigger('enable_node', { 'node' : obj });
  2077. },
  2078. /**
  2079. * disables a node - so that it can not be selected
  2080. * @name disable_node(obj)
  2081. * @param {mixed} obj the node to disable
  2082. * @trigger disable_node.jstree
  2083. */
  2084. disable_node : function (obj) {
  2085. var t1, t2;
  2086. if($.isArray(obj)) {
  2087. obj = obj.slice();
  2088. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  2089. this.disable_node(obj[t1]);
  2090. }
  2091. return true;
  2092. }
  2093. obj = this.get_node(obj);
  2094. if(!obj || obj.id === '#') {
  2095. return false;
  2096. }
  2097. obj.state.disabled = true;
  2098. this.get_node(obj,true).children('.jstree-anchor').addClass('jstree-disabled');
  2099. /**
  2100. * triggered when an node is disabled
  2101. * @event
  2102. * @name disable_node.jstree
  2103. * @param {Object} node the disabled node
  2104. */
  2105. this.trigger('disable_node', { 'node' : obj });
  2106. },
  2107. /**
  2108. * called when a node is selected by the user. Used internally.
  2109. * @private
  2110. * @name activate_node(obj, e)
  2111. * @param {mixed} obj the node
  2112. * @param {Object} e the related event
  2113. * @trigger activate_node.jstree
  2114. */
  2115. activate_node : function (obj, e) {
  2116. if(this.is_disabled(obj)) {
  2117. return false;
  2118. }
  2119. // ensure last_clicked is still in the DOM, make it fresh (maybe it was moved?) and make sure it is still selected, if not - make last_clicked the last selected node
  2120. this._data.core.last_clicked = this._data.core.last_clicked && this._data.core.last_clicked.id !== undefined ? this.get_node(this._data.core.last_clicked.id) : null;
  2121. if(this._data.core.last_clicked && !this._data.core.last_clicked.state.selected) { this._data.core.last_clicked = null; }
  2122. if(!this._data.core.last_clicked && this._data.core.selected.length) { this._data.core.last_clicked = this.get_node(this._data.core.selected[this._data.core.selected.length - 1]); }
  2123. if(!this.settings.core.multiple || (!e.metaKey && !e.ctrlKey && !e.shiftKey) || (e.shiftKey && (!this._data.core.last_clicked || !this.get_parent(obj) || this.get_parent(obj) !== this._data.core.last_clicked.parent ) )) {
  2124. if(!this.settings.core.multiple && (e.metaKey || e.ctrlKey || e.shiftKey) && this.is_selected(obj)) {
  2125. this.deselect_node(obj, false, false, e);
  2126. }
  2127. else {
  2128. this.deselect_all(true);
  2129. this.select_node(obj, false, false, e);
  2130. this._data.core.last_clicked = this.get_node(obj);
  2131. }
  2132. }
  2133. else {
  2134. if(e.shiftKey) {
  2135. var o = this.get_node(obj).id,
  2136. l = this._data.core.last_clicked.id,
  2137. p = this.get_node(this._data.core.last_clicked.parent).children,
  2138. c = false,
  2139. i, j;
  2140. for(i = 0, j = p.length; i < j; i += 1) {
  2141. // separate IFs work whem o and l are the same
  2142. if(p[i] === o) {
  2143. c = !c;
  2144. }
  2145. if(p[i] === l) {
  2146. c = !c;
  2147. }
  2148. if(c || p[i] === o || p[i] === l) {
  2149. this.select_node(p[i], false, false, e);
  2150. }
  2151. else {
  2152. this.deselect_node(p[i], false, false, e);
  2153. }
  2154. }
  2155. }
  2156. else {
  2157. if(!this.is_selected(obj)) {
  2158. this.select_node(obj, false, false, e);
  2159. }
  2160. else {
  2161. this.deselect_node(obj, false, false, e);
  2162. }
  2163. }
  2164. }
  2165. /**
  2166. * triggered when an node is clicked or intercated with by the user
  2167. * @event
  2168. * @name activate_node.jstree
  2169. * @param {Object} node
  2170. */
  2171. this.trigger('activate_node', { 'node' : this.get_node(obj) });
  2172. },
  2173. /**
  2174. * applies the hover state on a node, called when a node is hovered by the user. Used internally.
  2175. * @private
  2176. * @name hover_node(obj)
  2177. * @param {mixed} obj
  2178. * @trigger hover_node.jstree
  2179. */
  2180. hover_node : function (obj) {
  2181. obj = this.get_node(obj, true);
  2182. if(!obj || !obj.length || obj.children('.jstree-hovered').length) {
  2183. return false;
  2184. }
  2185. var o = this.element.find('.jstree-hovered'), t = this.element;
  2186. if(o && o.length) { this.dehover_node(o); }
  2187. obj.children('.jstree-anchor').addClass('jstree-hovered');
  2188. /**
  2189. * triggered when an node is hovered
  2190. * @event
  2191. * @name hover_node.jstree
  2192. * @param {Object} node
  2193. */
  2194. this.trigger('hover_node', { 'node' : this.get_node(obj) });
  2195. setTimeout(function () { t.attr('aria-activedescendant', obj[0].id); obj.attr('aria-selected', true); }, 0);
  2196. },
  2197. /**
  2198. * removes the hover state from a nodecalled when a node is no longer hovered by the user. Used internally.
  2199. * @private
  2200. * @name dehover_node(obj)
  2201. * @param {mixed} obj
  2202. * @trigger dehover_node.jstree
  2203. */
  2204. dehover_node : function (obj) {
  2205. obj = this.get_node(obj, true);
  2206. if(!obj || !obj.length || !obj.children('.jstree-hovered').length) {
  2207. return false;
  2208. }
  2209. obj.attr('aria-selected', false).children('.jstree-anchor').removeClass('jstree-hovered');
  2210. /**
  2211. * triggered when an node is no longer hovered
  2212. * @event
  2213. * @name dehover_node.jstree
  2214. * @param {Object} node
  2215. */
  2216. this.trigger('dehover_node', { 'node' : this.get_node(obj) });
  2217. },
  2218. /**
  2219. * select a node
  2220. * @name select_node(obj [, supress_event, prevent_open])
  2221. * @param {mixed} obj an array can be used to select multiple nodes
  2222. * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
  2223. * @param {Boolean} prevent_open if set to `true` parents of the selected node won't be opened
  2224. * @trigger select_node.jstree, changed.jstree
  2225. */
  2226. select_node : function (obj, supress_event, prevent_open, e) {
  2227. var dom, t1, t2, th;
  2228. if($.isArray(obj)) {
  2229. obj = obj.slice();
  2230. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  2231. this.select_node(obj[t1], supress_event, prevent_open, e);
  2232. }
  2233. return true;
  2234. }
  2235. obj = this.get_node(obj);
  2236. if(!obj || obj.id === '#') {
  2237. return false;
  2238. }
  2239. dom = this.get_node(obj, true);
  2240. if(!obj.state.selected) {
  2241. obj.state.selected = true;
  2242. this._data.core.selected.push(obj.id);
  2243. if(!prevent_open) {
  2244. dom = this._open_to(obj);
  2245. }
  2246. if(dom && dom.length) {
  2247. dom.children('.jstree-anchor').addClass('jstree-clicked');
  2248. }
  2249. /**
  2250. * triggered when an node is selected
  2251. * @event
  2252. * @name select_node.jstree
  2253. * @param {Object} node
  2254. * @param {Array} selected the current selection
  2255. * @param {Object} event the event (if any) that triggered this select_node
  2256. */
  2257. this.trigger('select_node', { 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
  2258. if(!supress_event) {
  2259. /**
  2260. * triggered when selection changes
  2261. * @event
  2262. * @name changed.jstree
  2263. * @param {Object} node
  2264. * @param {Object} action the action that caused the selection to change
  2265. * @param {Array} selected the current selection
  2266. * @param {Object} event the event (if any) that triggered this changed event
  2267. */
  2268. this.trigger('changed', { 'action' : 'select_node', 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
  2269. }
  2270. }
  2271. },
  2272. /**
  2273. * deselect a node
  2274. * @name deselect_node(obj [, supress_event])
  2275. * @param {mixed} obj an array can be used to deselect multiple nodes
  2276. * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
  2277. * @trigger deselect_node.jstree, changed.jstree
  2278. */
  2279. deselect_node : function (obj, supress_event, e) {
  2280. var t1, t2, dom;
  2281. if($.isArray(obj)) {
  2282. obj = obj.slice();
  2283. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  2284. this.deselect_node(obj[t1], supress_event, e);
  2285. }
  2286. return true;
  2287. }
  2288. obj = this.get_node(obj);
  2289. if(!obj || obj.id === '#') {
  2290. return false;
  2291. }
  2292. dom = this.get_node(obj, true);
  2293. if(obj.state.selected) {
  2294. obj.state.selected = false;
  2295. this._data.core.selected = $.vakata.array_remove_item(this._data.core.selected, obj.id);
  2296. if(dom.length) {
  2297. dom.children('.jstree-anchor').removeClass('jstree-clicked');
  2298. }
  2299. /**
  2300. * triggered when an node is deselected
  2301. * @event
  2302. * @name deselect_node.jstree
  2303. * @param {Object} node
  2304. * @param {Array} selected the current selection
  2305. * @param {Object} event the event (if any) that triggered this deselect_node
  2306. */
  2307. this.trigger('deselect_node', { 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
  2308. if(!supress_event) {
  2309. this.trigger('changed', { 'action' : 'deselect_node', 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
  2310. }
  2311. }
  2312. },
  2313. /**
  2314. * select all nodes in the tree
  2315. * @name select_all([supress_event])
  2316. * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
  2317. * @trigger select_all.jstree, changed.jstree
  2318. */
  2319. select_all : function (supress_event) {
  2320. var tmp = this._data.core.selected.concat([]), i, j;
  2321. this._data.core.selected = this._model.data['#'].children_d.concat();
  2322. for(i = 0, j = this._data.core.selected.length; i < j; i++) {
  2323. if(this._model.data[this._data.core.selected[i]]) {
  2324. this._model.data[this._data.core.selected[i]].state.selected = true;
  2325. }
  2326. }
  2327. this.redraw(true);
  2328. /**
  2329. * triggered when all nodes are selected
  2330. * @event
  2331. * @name select_all.jstree
  2332. * @param {Array} selected the current selection
  2333. */
  2334. this.trigger('select_all', { 'selected' : this._data.core.selected });
  2335. if(!supress_event) {
  2336. this.trigger('changed', { 'action' : 'select_all', 'selected' : this._data.core.selected, 'old_selection' : tmp });
  2337. }
  2338. },
  2339. /**
  2340. * deselect all selected nodes
  2341. * @name deselect_all([supress_event])
  2342. * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
  2343. * @trigger deselect_all.jstree, changed.jstree
  2344. */
  2345. deselect_all : function (supress_event) {
  2346. var tmp = this._data.core.selected.concat([]), i, j;
  2347. for(i = 0, j = this._data.core.selected.length; i < j; i++) {
  2348. if(this._model.data[this._data.core.selected[i]]) {
  2349. this._model.data[this._data.core.selected[i]].state.selected = false;
  2350. }
  2351. }
  2352. this._data.core.selected = [];
  2353. this.element.find('.jstree-clicked').removeClass('jstree-clicked');
  2354. /**
  2355. * triggered when all nodes are deselected
  2356. * @event
  2357. * @name deselect_all.jstree
  2358. * @param {Object} node the previous selection
  2359. * @param {Array} selected the current selection
  2360. */
  2361. this.trigger('deselect_all', { 'selected' : this._data.core.selected, 'node' : tmp });
  2362. if(!supress_event) {
  2363. this.trigger('changed', { 'action' : 'deselect_all', 'selected' : this._data.core.selected, 'old_selection' : tmp });
  2364. }
  2365. },
  2366. /**
  2367. * checks if a node is selected
  2368. * @name is_selected(obj)
  2369. * @param {mixed} obj
  2370. * @return {Boolean}
  2371. */
  2372. is_selected : function (obj) {
  2373. obj = this.get_node(obj);
  2374. if(!obj || obj.id === '#') {
  2375. return false;
  2376. }
  2377. return obj.state.selected;
  2378. },
  2379. /**
  2380. * get an array of all selected nodes
  2381. * @name get_selected([full])
  2382. * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
  2383. * @return {Array}
  2384. */
  2385. get_selected : function (full) {
  2386. return full ? $.map(this._data.core.selected, $.proxy(function (i) { return this.get_node(i); }, this)) : this._data.core.selected;
  2387. },
  2388. /**
  2389. * get an array of all top level selected nodes (ignoring children of selected nodes)
  2390. * @name get_top_selected([full])
  2391. * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
  2392. * @return {Array}
  2393. */
  2394. get_top_selected : function (full) {
  2395. var tmp = this.get_selected(true),
  2396. obj = {}, i, j, k, l;
  2397. for(i = 0, j = tmp.length; i < j; i++) {
  2398. obj[tmp[i].id] = tmp[i];
  2399. }
  2400. for(i = 0, j = tmp.length; i < j; i++) {
  2401. for(k = 0, l = tmp[i].children_d.length; k < l; k++) {
  2402. if(obj[tmp[i].children_d[k]]) {
  2403. delete obj[tmp[i].children_d[k]];
  2404. }
  2405. }
  2406. }
  2407. tmp = [];
  2408. for(i in obj) {
  2409. if(obj.hasOwnProperty(i)) {
  2410. tmp.push(i);
  2411. }
  2412. }
  2413. return full ? $.map(tmp, $.proxy(function (i) { return this.get_node(i); }, this)) : tmp;
  2414. },
  2415. /**
  2416. * get an array of all bottom level selected nodes (ignoring selected parents)
  2417. * @name get_top_selected([full])
  2418. * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
  2419. * @return {Array}
  2420. */
  2421. get_bottom_selected : function (full) {
  2422. var tmp = this.get_selected(true),
  2423. obj = [], i, j;
  2424. for(i = 0, j = tmp.length; i < j; i++) {
  2425. if(!tmp[i].children.length) {
  2426. obj.push(tmp[i].id);
  2427. }
  2428. }
  2429. return full ? $.map(obj, $.proxy(function (i) { return this.get_node(i); }, this)) : obj;
  2430. },
  2431. /**
  2432. * gets the current state of the tree so that it can be restored later with `set_state(state)`. Used internally.
  2433. * @name get_state()
  2434. * @private
  2435. * @return {Object}
  2436. */
  2437. get_state : function () {
  2438. var state = {
  2439. 'core' : {
  2440. 'open' : [],
  2441. 'scroll' : {
  2442. 'left' : this.element.scrollLeft(),
  2443. 'top' : this.element.scrollTop()
  2444. },
  2445. /*!
  2446. 'themes' : {
  2447. 'name' : this.get_theme(),
  2448. 'icons' : this._data.core.themes.icons,
  2449. 'dots' : this._data.core.themes.dots
  2450. },
  2451. */
  2452. 'selected' : []
  2453. }
  2454. }, i;
  2455. for(i in this._model.data) {
  2456. if(this._model.data.hasOwnProperty(i)) {
  2457. if(i !== '#') {
  2458. if(this._model.data[i].state.opened) {
  2459. state.core.open.push(i);
  2460. }
  2461. if(this._model.data[i].state.selected) {
  2462. state.core.selected.push(i);
  2463. }
  2464. }
  2465. }
  2466. }
  2467. return state;
  2468. },
  2469. /**
  2470. * sets the state of the tree. Used internally.
  2471. * @name set_state(state [, callback])
  2472. * @private
  2473. * @param {Object} state the state to restore
  2474. * @param {Function} callback an optional function to execute once the state is restored.
  2475. * @trigger set_state.jstree
  2476. */
  2477. set_state : function (state, callback) {
  2478. if(state) {
  2479. if(state.core) {
  2480. var res, n, t, _this;
  2481. if(state.core.open) {
  2482. if(!$.isArray(state.core.open)) {
  2483. delete state.core.open;
  2484. this.set_state(state, callback);
  2485. return false;
  2486. }
  2487. res = true;
  2488. n = false;
  2489. t = this;
  2490. $.each(state.core.open.concat([]), function (i, v) {
  2491. n = t.get_node(v);
  2492. if(n) {
  2493. if(t.is_loaded(v)) {
  2494. if(t.is_closed(v)) {
  2495. t.open_node(v, false, 0);
  2496. }
  2497. if(state && state.core && state.core.open) {
  2498. $.vakata.array_remove_item(state.core.open, v);
  2499. }
  2500. }
  2501. else {
  2502. if(!t.is_loading(v)) {
  2503. t.open_node(v, $.proxy(function (o, s) {
  2504. if(!s && state && state.core && state.core.open) {
  2505. $.vakata.array_remove_item(state.core.open, o.id);
  2506. }
  2507. this.set_state(state, callback);
  2508. }, t), 0);
  2509. }
  2510. // there will be some async activity - so wait for it
  2511. res = false;
  2512. }
  2513. }
  2514. });
  2515. if(res) {
  2516. delete state.core.open;
  2517. this.set_state(state, callback);
  2518. }
  2519. return false;
  2520. }
  2521. if(state.core.scroll) {
  2522. if(state.core.scroll && state.core.scroll.left !== undefined) {
  2523. this.element.scrollLeft(state.core.scroll.left);
  2524. }
  2525. if(state.core.scroll && state.core.scroll.top !== undefined) {
  2526. this.element.scrollTop(state.core.scroll.top);
  2527. }
  2528. delete state.core.scroll;
  2529. this.set_state(state, callback);
  2530. return false;
  2531. }
  2532. /*!
  2533. if(state.core.themes) {
  2534. if(state.core.themes.name) {
  2535. this.set_theme(state.core.themes.name);
  2536. }
  2537. if(typeof state.core.themes.dots !== 'undefined') {
  2538. this[ state.core.themes.dots ? "show_dots" : "hide_dots" ]();
  2539. }
  2540. if(typeof state.core.themes.icons !== 'undefined') {
  2541. this[ state.core.themes.icons ? "show_icons" : "hide_icons" ]();
  2542. }
  2543. delete state.core.themes;
  2544. delete state.core.open;
  2545. this.set_state(state, callback);
  2546. return false;
  2547. }
  2548. */
  2549. if(state.core.selected) {
  2550. _this = this;
  2551. this.deselect_all();
  2552. $.each(state.core.selected, function (i, v) {
  2553. _this.select_node(v);
  2554. });
  2555. delete state.core.selected;
  2556. this.set_state(state, callback);
  2557. return false;
  2558. }
  2559. if($.isEmptyObject(state.core)) {
  2560. delete state.core;
  2561. this.set_state(state, callback);
  2562. return false;
  2563. }
  2564. }
  2565. if($.isEmptyObject(state)) {
  2566. state = null;
  2567. if(callback) { callback.call(this); }
  2568. /**
  2569. * triggered when a `set_state` call completes
  2570. * @event
  2571. * @name set_state.jstree
  2572. */
  2573. this.trigger('set_state');
  2574. return false;
  2575. }
  2576. return true;
  2577. }
  2578. return false;
  2579. },
  2580. /**
  2581. * refreshes the tree - all nodes are reloaded with calls to `load_node`.
  2582. * @name refresh()
  2583. * @param {Boolean} skip_loading an option to skip showing the loading indicator
  2584. * @trigger refresh.jstree
  2585. */
  2586. refresh : function (skip_loading) {
  2587. this._data.core.state = this.get_state();
  2588. this._cnt = 0;
  2589. this._model.data = {
  2590. '#' : {
  2591. id : '#',
  2592. parent : null,
  2593. parents : [],
  2594. children : [],
  2595. children_d : [],
  2596. state : { loaded : false }
  2597. }
  2598. };
  2599. var c = this.get_container_ul()[0].className;
  2600. if(!skip_loading) {
  2601. this.element.html("<"+"ul class='jstree-container-ul'><"+"li class='jstree-initial-node jstree-loading jstree-leaf jstree-last'><i class='jstree-icon jstree-ocl'></i><"+"a class='jstree-anchor' href='#'><i class='jstree-icon jstree-themeicon-hidden'></i>" + this.get_string("Loading ...") + "</a></li></ul>");
  2602. }
  2603. this.load_node('#', function (o, s) {
  2604. if(s) {
  2605. this.get_container_ul()[0].className = c;
  2606. this.set_state($.extend(true, {}, this._data.core.state), function () {
  2607. /**
  2608. * triggered when a `refresh` call completes
  2609. * @event
  2610. * @name refresh.jstree
  2611. */
  2612. this.trigger('refresh');
  2613. });
  2614. }
  2615. this._data.core.state = null;
  2616. });
  2617. },
  2618. /**
  2619. * refreshes a node in the tree (reload its children) all opened nodes inside that node are reloaded with calls to `load_node`.
  2620. * @name refresh_name(obj)
  2621. * @param {Boolean} skip_loading an option to skip showing the loading indicator
  2622. * @trigger refresh.jstree
  2623. */
  2624. refresh_node : function (obj) {
  2625. obj = this.get_node(obj);
  2626. if(!obj || obj.id === '#') { return false; }
  2627. var opened = [], s = this._data.core.selected.concat([]);
  2628. if(obj.state.opened === true) { opened.push(obj.id); }
  2629. this.get_node(obj, true).find('.jstree-open').each(function() { opened.push(this.id); });
  2630. this._load_nodes(opened, $.proxy(function (nodes) {
  2631. this.open_node(nodes, false, 0);
  2632. this.select_node(this._data.core.selected);
  2633. /**
  2634. * triggered when a node is refreshed
  2635. * @event
  2636. * @name move_node.jstree
  2637. * @param {Object} node - the refreshed node
  2638. * @param {Array} nodes - an array of the IDs of the nodes that were reloaded
  2639. */
  2640. this.trigger('refresh_node', { 'node' : obj, 'nodes' : nodes });
  2641. }, this));
  2642. },
  2643. /**
  2644. * set (change) the ID of a node
  2645. * @name set_id(obj, id)
  2646. * @param {mixed} obj the node
  2647. * @param {String} id the new ID
  2648. * @return {Boolean}
  2649. */
  2650. set_id : function (obj, id) {
  2651. obj = this.get_node(obj);
  2652. if(!obj || obj.id === '#') { return false; }
  2653. var i, j, m = this._model.data;
  2654. id = id.toString();
  2655. // update parents (replace current ID with new one in children and children_d)
  2656. m[obj.parent].children[$.inArray(obj.id, m[obj.parent].children)] = id;
  2657. for(i = 0, j = obj.parents.length; i < j; i++) {
  2658. m[obj.parents[i]].children_d[$.inArray(obj.id, m[obj.parents[i]].children_d)] = id;
  2659. }
  2660. // update children (replace current ID with new one in parent and parents)
  2661. for(i = 0, j = obj.children.length; i < j; i++) {
  2662. m[obj.children[i]].parent = id;
  2663. }
  2664. for(i = 0, j = obj.children_d.length; i < j; i++) {
  2665. m[obj.children_d[i]].parents[$.inArray(obj.id, m[obj.children_d[i]].parents)] = id;
  2666. }
  2667. i = $.inArray(obj.id, this._data.core.selected);
  2668. if(i !== -1) { this._data.core.selected[i] = id; }
  2669. // update model and obj itself (obj.id, this._model.data[KEY])
  2670. i = this.get_node(obj.id, true);
  2671. if(i) {
  2672. i.attr('id', id);
  2673. }
  2674. delete m[obj.id];
  2675. obj.id = id;
  2676. m[id] = obj;
  2677. return true;
  2678. },
  2679. /**
  2680. * get the text value of a node
  2681. * @name get_text(obj)
  2682. * @param {mixed} obj the node
  2683. * @return {String}
  2684. */
  2685. get_text : function (obj) {
  2686. obj = this.get_node(obj);
  2687. return (!obj || obj.id === '#') ? false : obj.text;
  2688. },
  2689. /**
  2690. * set the text value of a node. Used internally, please use `rename_node(obj, val)`.
  2691. * @private
  2692. * @name set_text(obj, val)
  2693. * @param {mixed} obj the node, you can pass an array to set the text on multiple nodes
  2694. * @param {String} val the new text value
  2695. * @return {Boolean}
  2696. * @trigger set_text.jstree
  2697. */
  2698. set_text : function (obj, val) {
  2699. var t1, t2, dom, tmp;
  2700. if($.isArray(obj)) {
  2701. obj = obj.slice();
  2702. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  2703. this.set_text(obj[t1], val);
  2704. }
  2705. return true;
  2706. }
  2707. obj = this.get_node(obj);
  2708. if(!obj || obj.id === '#') { return false; }
  2709. obj.text = val;
  2710. dom = this.get_node(obj, true);
  2711. if(dom.length) {
  2712. dom = dom.children(".jstree-anchor:eq(0)");
  2713. tmp = dom.children("I").clone();
  2714. dom.html(val).prepend(tmp);
  2715. /**
  2716. * triggered when a node text value is changed
  2717. * @event
  2718. * @name set_text.jstree
  2719. * @param {Object} obj
  2720. * @param {String} text the new value
  2721. */
  2722. this.trigger('set_text',{ "obj" : obj, "text" : val });
  2723. }
  2724. return true;
  2725. },
  2726. /**
  2727. * gets a JSON representation of a node (or the whole tree)
  2728. * @name get_json([obj, options])
  2729. * @param {mixed} obj
  2730. * @param {Object} options
  2731. * @param {Boolean} options.no_state do not return state information
  2732. * @param {Boolean} options.no_id do not return ID
  2733. * @param {Boolean} options.no_children do not include children
  2734. * @param {Boolean} options.no_data do not include node data
  2735. * @param {Boolean} options.flat return flat JSON instead of nested
  2736. * @return {Object}
  2737. */
  2738. get_json : function (obj, options, flat) {
  2739. obj = this.get_node(obj || '#');
  2740. if(!obj) { return false; }
  2741. if(options && options.flat && !flat) { flat = []; }
  2742. var tmp = {
  2743. 'id' : obj.id,
  2744. 'text' : obj.text,
  2745. 'icon' : this.get_icon(obj),
  2746. 'li_attr' : obj.li_attr,
  2747. 'a_attr' : obj.a_attr,
  2748. 'state' : {},
  2749. 'data' : options && options.no_data ? false : obj.data
  2750. //( this.get_node(obj, true).length ? this.get_node(obj, true).data() : obj.data ),
  2751. }, i, j;
  2752. if(options && options.flat) {
  2753. tmp.parent = obj.parent;
  2754. }
  2755. else {
  2756. tmp.children = [];
  2757. }
  2758. if(!options || !options.no_state) {
  2759. for(i in obj.state) {
  2760. if(obj.state.hasOwnProperty(i)) {
  2761. tmp.state[i] = obj.state[i];
  2762. }
  2763. }
  2764. }
  2765. if(options && options.no_id) {
  2766. delete tmp.id;
  2767. if(tmp.li_attr && tmp.li_attr.id) {
  2768. delete tmp.li_attr.id;
  2769. }
  2770. }
  2771. if(options && options.flat && obj.id !== '#') {
  2772. flat.push(tmp);
  2773. }
  2774. if(!options || !options.no_children) {
  2775. for(i = 0, j = obj.children.length; i < j; i++) {
  2776. if(options && options.flat) {
  2777. this.get_json(obj.children[i], options, flat);
  2778. }
  2779. else {
  2780. tmp.children.push(this.get_json(obj.children[i], options));
  2781. }
  2782. }
  2783. }
  2784. return options && options.flat ? flat : (obj.id === '#' ? tmp.children : tmp);
  2785. },
  2786. /**
  2787. * create a new node (do not confuse with load_node)
  2788. * @name create_node([obj, node, pos, callback, is_loaded])
  2789. * @param {mixed} par the parent node (to create a root node use either "#" (string) or `null`)
  2790. * @param {mixed} node the data for the new node (a valid JSON object, or a simple string with the name)
  2791. * @param {mixed} pos the index at which to insert the node, "first" and "last" are also supported, default is "last"
  2792. * @param {Function} callback a function to be called once the node is created
  2793. * @param {Boolean} is_loaded internal argument indicating if the parent node was succesfully loaded
  2794. * @return {String} the ID of the newly create node
  2795. * @trigger model.jstree, create_node.jstree
  2796. */
  2797. create_node : function (par, node, pos, callback, is_loaded) {
  2798. if(par === null) { par = "#"; }
  2799. par = this.get_node(par);
  2800. if(!par) { return false; }
  2801. pos = pos === undefined ? "last" : pos;
  2802. if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
  2803. return this.load_node(par, function () { this.create_node(par, node, pos, callback, true); });
  2804. }
  2805. if(!node) { node = { "text" : this.get_string('New node') }; }
  2806. if(node.text === undefined) { node.text = this.get_string('New node'); }
  2807. var tmp, dpc, i, j;
  2808. if(par.id === '#') {
  2809. if(pos === "before") { pos = "first"; }
  2810. if(pos === "after") { pos = "last"; }
  2811. }
  2812. switch(pos) {
  2813. case "before":
  2814. tmp = this.get_node(par.parent);
  2815. pos = $.inArray(par.id, tmp.children);
  2816. par = tmp;
  2817. break;
  2818. case "after" :
  2819. tmp = this.get_node(par.parent);
  2820. pos = $.inArray(par.id, tmp.children) + 1;
  2821. par = tmp;
  2822. break;
  2823. case "inside":
  2824. case "first":
  2825. pos = 0;
  2826. break;
  2827. case "last":
  2828. pos = par.children.length;
  2829. break;
  2830. default:
  2831. if(!pos) { pos = 0; }
  2832. break;
  2833. }
  2834. if(pos > par.children.length) { pos = par.children.length; }
  2835. if(!node.id) { node.id = true; }
  2836. if(!this.check("create_node", node, par, pos)) {
  2837. this.settings.core.error.call(this, this._data.core.last_error);
  2838. return false;
  2839. }
  2840. if(node.id === true) { delete node.id; }
  2841. node = this._parse_model_from_json(node, par.id, par.parents.concat());
  2842. if(!node) { return false; }
  2843. tmp = this.get_node(node);
  2844. dpc = [];
  2845. dpc.push(node);
  2846. dpc = dpc.concat(tmp.children_d);
  2847. this.trigger('model', { "nodes" : dpc, "parent" : par.id });
  2848. par.children_d = par.children_d.concat(dpc);
  2849. for(i = 0, j = par.parents.length; i < j; i++) {
  2850. this._model.data[par.parents[i]].children_d = this._model.data[par.parents[i]].children_d.concat(dpc);
  2851. }
  2852. node = tmp;
  2853. tmp = [];
  2854. for(i = 0, j = par.children.length; i < j; i++) {
  2855. tmp[i >= pos ? i+1 : i] = par.children[i];
  2856. }
  2857. tmp[pos] = node.id;
  2858. par.children = tmp;
  2859. this.redraw_node(par, true);
  2860. if(callback) { callback.call(this, this.get_node(node)); }
  2861. /**
  2862. * triggered when a node is created
  2863. * @event
  2864. * @name create_node.jstree
  2865. * @param {Object} node
  2866. * @param {String} parent the parent's ID
  2867. * @param {Number} position the position of the new node among the parent's children
  2868. */
  2869. this.trigger('create_node', { "node" : this.get_node(node), "parent" : par.id, "position" : pos });
  2870. return node.id;
  2871. },
  2872. /**
  2873. * set the text value of a node
  2874. * @name rename_node(obj, val)
  2875. * @param {mixed} obj the node, you can pass an array to rename multiple nodes to the same name
  2876. * @param {String} val the new text value
  2877. * @return {Boolean}
  2878. * @trigger rename_node.jstree
  2879. */
  2880. rename_node : function (obj, val) {
  2881. var t1, t2, old;
  2882. if($.isArray(obj)) {
  2883. obj = obj.slice();
  2884. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  2885. this.rename_node(obj[t1], val);
  2886. }
  2887. return true;
  2888. }
  2889. obj = this.get_node(obj);
  2890. if(!obj || obj.id === '#') { return false; }
  2891. old = obj.text;
  2892. if(!this.check("rename_node", obj, this.get_parent(obj), val)) {
  2893. this.settings.core.error.call(this, this._data.core.last_error);
  2894. return false;
  2895. }
  2896. this.set_text(obj, val); // .apply(this, Array.prototype.slice.call(arguments))
  2897. /**
  2898. * triggered when a node is renamed
  2899. * @event
  2900. * @name rename_node.jstree
  2901. * @param {Object} node
  2902. * @param {String} text the new value
  2903. * @param {String} old the old value
  2904. */
  2905. this.trigger('rename_node', { "node" : obj, "text" : val, "old" : old });
  2906. return true;
  2907. },
  2908. /**
  2909. * remove a node
  2910. * @name delete_node(obj)
  2911. * @param {mixed} obj the node, you can pass an array to delete multiple nodes
  2912. * @return {Boolean}
  2913. * @trigger delete_node.jstree, changed.jstree
  2914. */
  2915. delete_node : function (obj) {
  2916. var t1, t2, par, pos, tmp, i, j, k, l, c;
  2917. if($.isArray(obj)) {
  2918. obj = obj.slice();
  2919. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  2920. this.delete_node(obj[t1]);
  2921. }
  2922. return true;
  2923. }
  2924. obj = this.get_node(obj);
  2925. if(!obj || obj.id === '#') { return false; }
  2926. par = this.get_node(obj.parent);
  2927. pos = $.inArray(obj.id, par.children);
  2928. c = false;
  2929. if(!this.check("delete_node", obj, par, pos)) {
  2930. this.settings.core.error.call(this, this._data.core.last_error);
  2931. return false;
  2932. }
  2933. if(pos !== -1) {
  2934. par.children = $.vakata.array_remove(par.children, pos);
  2935. }
  2936. tmp = obj.children_d.concat([]);
  2937. tmp.push(obj.id);
  2938. for(k = 0, l = tmp.length; k < l; k++) {
  2939. for(i = 0, j = obj.parents.length; i < j; i++) {
  2940. pos = $.inArray(tmp[k], this._model.data[obj.parents[i]].children_d);
  2941. if(pos !== -1) {
  2942. this._model.data[obj.parents[i]].children_d = $.vakata.array_remove(this._model.data[obj.parents[i]].children_d, pos);
  2943. }
  2944. }
  2945. if(this._model.data[tmp[k]].state.selected) {
  2946. c = true;
  2947. pos = $.inArray(tmp[k], this._data.core.selected);
  2948. if(pos !== -1) {
  2949. this._data.core.selected = $.vakata.array_remove(this._data.core.selected, pos);
  2950. }
  2951. }
  2952. }
  2953. /**
  2954. * triggered when a node is deleted
  2955. * @event
  2956. * @name delete_node.jstree
  2957. * @param {Object} node
  2958. * @param {String} parent the parent's ID
  2959. */
  2960. this.trigger('delete_node', { "node" : obj, "parent" : par.id });
  2961. if(c) {
  2962. this.trigger('changed', { 'action' : 'delete_node', 'node' : obj, 'selected' : this._data.core.selected, 'parent' : par.id });
  2963. }
  2964. for(k = 0, l = tmp.length; k < l; k++) {
  2965. delete this._model.data[tmp[k]];
  2966. }
  2967. this.redraw_node(par, true);
  2968. return true;
  2969. },
  2970. /**
  2971. * check if an operation is premitted on the tree. Used internally.
  2972. * @private
  2973. * @name check(chk, obj, par, pos)
  2974. * @param {String} chk the operation to check, can be "create_node", "rename_node", "delete_node", "copy_node" or "move_node"
  2975. * @param {mixed} obj the node
  2976. * @param {mixed} par the parent
  2977. * @param {mixed} pos the position to insert at, or if "rename_node" - the new name
  2978. * @param {mixed} more some various additional information, for example if a "move_node" operations is triggered by DND this will be the hovered node
  2979. * @return {Boolean}
  2980. */
  2981. check : function (chk, obj, par, pos, more) {
  2982. obj = obj && obj.id ? obj : this.get_node(obj);
  2983. par = par && par.id ? par : this.get_node(par);
  2984. var tmp = chk.match(/^move_node|copy_node|create_node$/i) ? par : obj,
  2985. chc = this.settings.core.check_callback;
  2986. if(chk === "move_node" || chk === "copy_node") {
  2987. if((!more || !more.is_multi) && (obj.id === par.id || $.inArray(obj.id, par.children) === pos || $.inArray(par.id, obj.children_d) !== -1)) {
  2988. this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_01', 'reason' : 'Moving parent inside child', 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
  2989. return false;
  2990. }
  2991. }
  2992. if(tmp && tmp.data) { tmp = tmp.data; }
  2993. if(tmp && tmp.functions && (tmp.functions[chk] === false || tmp.functions[chk] === true)) {
  2994. if(tmp.functions[chk] === false) {
  2995. this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_02', 'reason' : 'Node data prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
  2996. }
  2997. return tmp.functions[chk];
  2998. }
  2999. if(chc === false || ($.isFunction(chc) && chc.call(this, chk, obj, par, pos, more) === false) || (chc && chc[chk] === false)) {
  3000. this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_03', 'reason' : 'User config for core.check_callback prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
  3001. return false;
  3002. }
  3003. return true;
  3004. },
  3005. /**
  3006. * get the last error
  3007. * @name last_error()
  3008. * @return {Object}
  3009. */
  3010. last_error : function () {
  3011. return this._data.core.last_error;
  3012. },
  3013. /**
  3014. * move a node to a new parent
  3015. * @name move_node(obj, par [, pos, callback, is_loaded])
  3016. * @param {mixed} obj the node to move, pass an array to move multiple nodes
  3017. * @param {mixed} par the new parent
  3018. * @param {mixed} pos the position to insert at (besides integer values, "first" and "last" are supported, as well as "before" and "after"), defaults to integer `0`
  3019. * @param {function} callback a function to call once the move is completed, receives 3 arguments - the node, the new parent and the position
  3020. * @param {Boolean} internal parameter indicating if the parent node has been loaded
  3021. * @trigger move_node.jstree
  3022. */
  3023. move_node : function (obj, par, pos, callback, is_loaded) {
  3024. var t1, t2, old_par, new_par, old_ins, is_multi, dpc, tmp, i, j, k, l, p;
  3025. if($.isArray(obj)) {
  3026. obj = obj.reverse().slice();
  3027. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  3028. this.move_node(obj[t1], par, pos, callback, is_loaded);
  3029. }
  3030. return true;
  3031. }
  3032. obj = obj && obj.id ? obj : this.get_node(obj);
  3033. par = this.get_node(par);
  3034. pos = pos === undefined ? 0 : pos;
  3035. if(!par || !obj || obj.id === '#') { return false; }
  3036. if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
  3037. return this.load_node(par, function () { this.move_node(obj, par, pos, callback, true); });
  3038. }
  3039. old_par = (obj.parent || '#').toString();
  3040. new_par = (!pos.toString().match(/^(before|after)$/) || par.id === '#') ? par : this.get_node(par.parent);
  3041. old_ins = obj.instance ? obj.instance : (this._model.data[obj.id] ? this : $.jstree.reference(obj.id));
  3042. is_multi = !old_ins || !old_ins._id || (this._id !== old_ins._id);
  3043. if(is_multi) {
  3044. if(this.copy_node(obj, par, pos, callback, is_loaded)) {
  3045. if(old_ins) { old_ins.delete_node(obj); }
  3046. return true;
  3047. }
  3048. return false;
  3049. }
  3050. //var m = this._model.data;
  3051. if(new_par.id === '#') {
  3052. if(pos === "before") { pos = "first"; }
  3053. if(pos === "after") { pos = "last"; }
  3054. }
  3055. switch(pos) {
  3056. case "before":
  3057. pos = $.inArray(par.id, new_par.children);
  3058. break;
  3059. case "after" :
  3060. pos = $.inArray(par.id, new_par.children) + 1;
  3061. break;
  3062. case "inside":
  3063. case "first":
  3064. pos = 0;
  3065. break;
  3066. case "last":
  3067. pos = new_par.children.length;
  3068. break;
  3069. default:
  3070. if(!pos) { pos = 0; }
  3071. break;
  3072. }
  3073. if(pos > new_par.children.length) { pos = new_par.children.length; }
  3074. if(!this.check("move_node", obj, new_par, pos, { 'core' : true, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id) })) {
  3075. this.settings.core.error.call(this, this._data.core.last_error);
  3076. return false;
  3077. }
  3078. if(obj.parent === new_par.id) {
  3079. dpc = new_par.children.concat();
  3080. tmp = $.inArray(obj.id, dpc);
  3081. if(tmp !== -1) {
  3082. dpc = $.vakata.array_remove(dpc, tmp);
  3083. if(pos > tmp) { pos--; }
  3084. }
  3085. tmp = [];
  3086. for(i = 0, j = dpc.length; i < j; i++) {
  3087. tmp[i >= pos ? i+1 : i] = dpc[i];
  3088. }
  3089. tmp[pos] = obj.id;
  3090. new_par.children = tmp;
  3091. this._node_changed(new_par.id);
  3092. this.redraw(new_par.id === '#');
  3093. }
  3094. else {
  3095. // clean old parent and up
  3096. tmp = obj.children_d.concat();
  3097. tmp.push(obj.id);
  3098. for(i = 0, j = obj.parents.length; i < j; i++) {
  3099. dpc = [];
  3100. p = old_ins._model.data[obj.parents[i]].children_d;
  3101. for(k = 0, l = p.length; k < l; k++) {
  3102. if($.inArray(p[k], tmp) === -1) {
  3103. dpc.push(p[k]);
  3104. }
  3105. }
  3106. old_ins._model.data[obj.parents[i]].children_d = dpc;
  3107. }
  3108. old_ins._model.data[old_par].children = $.vakata.array_remove_item(old_ins._model.data[old_par].children, obj.id);
  3109. // insert into new parent and up
  3110. for(i = 0, j = new_par.parents.length; i < j; i++) {
  3111. this._model.data[new_par.parents[i]].children_d = this._model.data[new_par.parents[i]].children_d.concat(tmp);
  3112. }
  3113. dpc = [];
  3114. for(i = 0, j = new_par.children.length; i < j; i++) {
  3115. dpc[i >= pos ? i+1 : i] = new_par.children[i];
  3116. }
  3117. dpc[pos] = obj.id;
  3118. new_par.children = dpc;
  3119. new_par.children_d.push(obj.id);
  3120. new_par.children_d = new_par.children_d.concat(obj.children_d);
  3121. // update object
  3122. obj.parent = new_par.id;
  3123. tmp = new_par.parents.concat();
  3124. tmp.unshift(new_par.id);
  3125. p = obj.parents.length;
  3126. obj.parents = tmp;
  3127. // update object children
  3128. tmp = tmp.concat();
  3129. for(i = 0, j = obj.children_d.length; i < j; i++) {
  3130. this._model.data[obj.children_d[i]].parents = this._model.data[obj.children_d[i]].parents.slice(0,p*-1);
  3131. Array.prototype.push.apply(this._model.data[obj.children_d[i]].parents, tmp);
  3132. }
  3133. this._node_changed(old_par);
  3134. this._node_changed(new_par.id);
  3135. this.redraw(old_par === '#' || new_par.id === '#');
  3136. }
  3137. if(callback) { callback.call(this, obj, new_par, pos); }
  3138. /**
  3139. * triggered when a node is moved
  3140. * @event
  3141. * @name move_node.jstree
  3142. * @param {Object} node
  3143. * @param {String} parent the parent's ID
  3144. * @param {Number} position the position of the node among the parent's children
  3145. * @param {String} old_parent the old parent of the node
  3146. * @param {Boolean} is_multi do the node and new parent belong to different instances
  3147. * @param {jsTree} old_instance the instance the node came from
  3148. * @param {jsTree} new_instance the instance of the new parent
  3149. */
  3150. this.trigger('move_node', { "node" : obj, "parent" : new_par.id, "position" : pos, "old_parent" : old_par, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id), 'old_instance' : old_ins, 'new_instance' : this });
  3151. return true;
  3152. },
  3153. /**
  3154. * copy a node to a new parent
  3155. * @name copy_node(obj, par [, pos, callback, is_loaded])
  3156. * @param {mixed} obj the node to copy, pass an array to copy multiple nodes
  3157. * @param {mixed} par the new parent
  3158. * @param {mixed} pos the position to insert at (besides integer values, "first" and "last" are supported, as well as "before" and "after"), defaults to integer `0`
  3159. * @param {function} callback a function to call once the move is completed, receives 3 arguments - the node, the new parent and the position
  3160. * @param {Boolean} internal parameter indicating if the parent node has been loaded
  3161. * @trigger model.jstree copy_node.jstree
  3162. */
  3163. copy_node : function (obj, par, pos, callback, is_loaded) {
  3164. var t1, t2, dpc, tmp, i, j, node, old_par, new_par, old_ins, is_multi;
  3165. if($.isArray(obj)) {
  3166. obj = obj.reverse().slice();
  3167. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  3168. this.copy_node(obj[t1], par, pos, callback, is_loaded);
  3169. }
  3170. return true;
  3171. }
  3172. obj = obj && obj.id ? obj : this.get_node(obj);
  3173. par = this.get_node(par);
  3174. pos = pos === undefined ? 0 : pos;
  3175. if(!par || !obj || obj.id === '#') { return false; }
  3176. if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
  3177. return this.load_node(par, function () { this.copy_node(obj, par, pos, callback, true); });
  3178. }
  3179. old_par = (obj.parent || '#').toString();
  3180. new_par = (!pos.toString().match(/^(before|after)$/) || par.id === '#') ? par : this.get_node(par.parent);
  3181. old_ins = obj.instance ? obj.instance : (this._model.data[obj.id] ? this : $.jstree.reference(obj.id));
  3182. is_multi = !old_ins || !old_ins._id || (this._id !== old_ins._id);
  3183. if(new_par.id === '#') {
  3184. if(pos === "before") { pos = "first"; }
  3185. if(pos === "after") { pos = "last"; }
  3186. }
  3187. switch(pos) {
  3188. case "before":
  3189. pos = $.inArray(par.id, new_par.children);
  3190. break;
  3191. case "after" :
  3192. pos = $.inArray(par.id, new_par.children) + 1;
  3193. break;
  3194. case "inside":
  3195. case "first":
  3196. pos = 0;
  3197. break;
  3198. case "last":
  3199. pos = new_par.children.length;
  3200. break;
  3201. default:
  3202. if(!pos) { pos = 0; }
  3203. break;
  3204. }
  3205. if(pos > new_par.children.length) { pos = new_par.children.length; }
  3206. if(!this.check("copy_node", obj, new_par, pos, { 'core' : true, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id) })) {
  3207. this.settings.core.error.call(this, this._data.core.last_error);
  3208. return false;
  3209. }
  3210. node = old_ins ? old_ins.get_json(obj, { no_id : true, no_data : true, no_state : true }) : obj;
  3211. if(!node) { return false; }
  3212. if(node.id === true) { delete node.id; }
  3213. node = this._parse_model_from_json(node, new_par.id, new_par.parents.concat());
  3214. if(!node) { return false; }
  3215. tmp = this.get_node(node);
  3216. if(obj && obj.state && obj.state.loaded === false) { tmp.state.loaded = false; }
  3217. dpc = [];
  3218. dpc.push(node);
  3219. dpc = dpc.concat(tmp.children_d);
  3220. this.trigger('model', { "nodes" : dpc, "parent" : new_par.id });
  3221. // insert into new parent and up
  3222. for(i = 0, j = new_par.parents.length; i < j; i++) {
  3223. this._model.data[new_par.parents[i]].children_d = this._model.data[new_par.parents[i]].children_d.concat(dpc);
  3224. }
  3225. dpc = [];
  3226. for(i = 0, j = new_par.children.length; i < j; i++) {
  3227. dpc[i >= pos ? i+1 : i] = new_par.children[i];
  3228. }
  3229. dpc[pos] = tmp.id;
  3230. new_par.children = dpc;
  3231. new_par.children_d.push(tmp.id);
  3232. new_par.children_d = new_par.children_d.concat(tmp.children_d);
  3233. this._node_changed(new_par.id);
  3234. this.redraw(new_par.id === '#');
  3235. if(callback) { callback.call(this, tmp, new_par, pos); }
  3236. /**
  3237. * triggered when a node is copied
  3238. * @event
  3239. * @name copy_node.jstree
  3240. * @param {Object} node the copied node
  3241. * @param {Object} original the original node
  3242. * @param {String} parent the parent's ID
  3243. * @param {Number} position the position of the node among the parent's children
  3244. * @param {String} old_parent the old parent of the node
  3245. * @param {Boolean} is_multi do the node and new parent belong to different instances
  3246. * @param {jsTree} old_instance the instance the node came from
  3247. * @param {jsTree} new_instance the instance of the new parent
  3248. */
  3249. this.trigger('copy_node', { "node" : tmp, "original" : obj, "parent" : new_par.id, "position" : pos, "old_parent" : old_par, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id), 'old_instance' : old_ins, 'new_instance' : this });
  3250. return tmp.id;
  3251. },
  3252. /**
  3253. * cut a node (a later call to `paste(obj)` would move the node)
  3254. * @name cut(obj)
  3255. * @param {mixed} obj multiple objects can be passed using an array
  3256. * @trigger cut.jstree
  3257. */
  3258. cut : function (obj) {
  3259. if(!obj) { obj = this._data.core.selected.concat(); }
  3260. if(!$.isArray(obj)) { obj = [obj]; }
  3261. if(!obj.length) { return false; }
  3262. var tmp = [], o, t1, t2;
  3263. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  3264. o = this.get_node(obj[t1]);
  3265. if(o && o.id && o.id !== '#') { tmp.push(o); }
  3266. }
  3267. if(!tmp.length) { return false; }
  3268. ccp_node = tmp;
  3269. ccp_inst = this;
  3270. ccp_mode = 'move_node';
  3271. /**
  3272. * triggered when nodes are added to the buffer for moving
  3273. * @event
  3274. * @name cut.jstree
  3275. * @param {Array} node
  3276. */
  3277. this.trigger('cut', { "node" : obj });
  3278. },
  3279. /**
  3280. * copy a node (a later call to `paste(obj)` would copy the node)
  3281. * @name copy(obj)
  3282. * @param {mixed} obj multiple objects can be passed using an array
  3283. * @trigger copy.jstre
  3284. */
  3285. copy : function (obj) {
  3286. if(!obj) { obj = this._data.core.selected.concat(); }
  3287. if(!$.isArray(obj)) { obj = [obj]; }
  3288. if(!obj.length) { return false; }
  3289. var tmp = [], o, t1, t2;
  3290. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  3291. o = this.get_node(obj[t1]);
  3292. if(o && o.id && o.id !== '#') { tmp.push(o); }
  3293. }
  3294. if(!tmp.length) { return false; }
  3295. ccp_node = tmp;
  3296. ccp_inst = this;
  3297. ccp_mode = 'copy_node';
  3298. /**
  3299. * triggered when nodes are added to the buffer for copying
  3300. * @event
  3301. * @name copy.jstree
  3302. * @param {Array} node
  3303. */
  3304. this.trigger('copy', { "node" : obj });
  3305. },
  3306. /**
  3307. * get the current buffer (any nodes that are waiting for a paste operation)
  3308. * @name get_buffer()
  3309. * @return {Object} an object consisting of `mode` ("copy_node" or "move_node"), `node` (an array of objects) and `inst` (the instance)
  3310. */
  3311. get_buffer : function () {
  3312. return { 'mode' : ccp_mode, 'node' : ccp_node, 'inst' : ccp_inst };
  3313. },
  3314. /**
  3315. * check if there is something in the buffer to paste
  3316. * @name can_paste()
  3317. * @return {Boolean}
  3318. */
  3319. can_paste : function () {
  3320. return ccp_mode !== false && ccp_node !== false; // && ccp_inst._model.data[ccp_node];
  3321. },
  3322. /**
  3323. * copy or move the previously cut or copied nodes to a new parent
  3324. * @name paste(obj [, pos])
  3325. * @param {mixed} obj the new parent
  3326. * @param {mixed} pos the position to insert at (besides integer, "first" and "last" are supported), defaults to integer `0`
  3327. * @trigger paste.jstree
  3328. */
  3329. paste : function (obj, pos) {
  3330. obj = this.get_node(obj);
  3331. if(!obj || !ccp_mode || !ccp_mode.match(/^(copy_node|move_node)$/) || !ccp_node) { return false; }
  3332. if(this[ccp_mode](ccp_node, obj, pos)) {
  3333. /**
  3334. * triggered when paste is invoked
  3335. * @event
  3336. * @name paste.jstree
  3337. * @param {String} parent the ID of the receiving node
  3338. * @param {Array} node the nodes in the buffer
  3339. * @param {String} mode the performed operation - "copy_node" or "move_node"
  3340. */
  3341. this.trigger('paste', { "parent" : obj.id, "node" : ccp_node, "mode" : ccp_mode });
  3342. }
  3343. ccp_node = false;
  3344. ccp_mode = false;
  3345. ccp_inst = false;
  3346. },
  3347. /**
  3348. * put a node in edit mode (input field to rename the node)
  3349. * @name edit(obj [, default_text])
  3350. * @param {mixed} obj
  3351. * @param {String} default_text the text to populate the input with (if omitted the node text value is used)
  3352. */
  3353. edit : function (obj, default_text) {
  3354. obj = this._open_to(obj);
  3355. if(!obj || !obj.length) { return false; }
  3356. var rtl = this._data.core.rtl,
  3357. w = this.element.width(),
  3358. a = obj.children('.jstree-anchor'),
  3359. s = $('<span>'),
  3360. /*!
  3361. oi = obj.children("i:visible"),
  3362. ai = a.children("i:visible"),
  3363. w1 = oi.width() * oi.length,
  3364. w2 = ai.width() * ai.length,
  3365. */
  3366. t = typeof default_text === 'string' ? default_text : this.get_text(obj),
  3367. h1 = $("<"+"div />", { css : { "position" : "absolute", "top" : "-200px", "left" : (rtl ? "0px" : "-1000px"), "visibility" : "hidden" } }).appendTo("body"),
  3368. h2 = $("<"+"input />", {
  3369. "value" : t,
  3370. "class" : "jstree-rename-input",
  3371. // "size" : t.length,
  3372. "css" : {
  3373. "padding" : "0",
  3374. "border" : "1px solid silver",
  3375. "box-sizing" : "border-box",
  3376. "display" : "inline-block",
  3377. "height" : (this._data.core.li_height) + "px",
  3378. "lineHeight" : (this._data.core.li_height) + "px",
  3379. "width" : "150px" // will be set a bit further down
  3380. },
  3381. "blur" : $.proxy(function () {
  3382. var i = s.children(".jstree-rename-input"),
  3383. v = i.val();
  3384. if(v === "") { v = t; }
  3385. h1.remove();
  3386. s.replaceWith(a);
  3387. s.remove();
  3388. this.set_text(obj, t);
  3389. if(this.rename_node(obj, v) === false) {
  3390. this.set_text(obj, t); // move this up? and fix #483
  3391. }
  3392. }, this),
  3393. "keydown" : function (event) {
  3394. var key = event.which;
  3395. if(key === 27) {
  3396. this.value = t;
  3397. }
  3398. if(key === 27 || key === 13 || key === 37 || key === 38 || key === 39 || key === 40 || key === 32) {
  3399. event.stopImmediatePropagation();
  3400. }
  3401. if(key === 27 || key === 13) {
  3402. event.preventDefault();
  3403. this.blur();
  3404. }
  3405. },
  3406. "click" : function (e) { e.stopImmediatePropagation(); },
  3407. "mousedown" : function (e) { e.stopImmediatePropagation(); },
  3408. "keyup" : function (event) {
  3409. h2.width(Math.min(h1.text("pW" + this.value).width(),w));
  3410. },
  3411. "keypress" : function(event) {
  3412. if(event.which === 13) { return false; }
  3413. }
  3414. }),
  3415. fn = {
  3416. fontFamily : a.css('fontFamily') || '',
  3417. fontSize : a.css('fontSize') || '',
  3418. fontWeight : a.css('fontWeight') || '',
  3419. fontStyle : a.css('fontStyle') || '',
  3420. fontStretch : a.css('fontStretch') || '',
  3421. fontVariant : a.css('fontVariant') || '',
  3422. letterSpacing : a.css('letterSpacing') || '',
  3423. wordSpacing : a.css('wordSpacing') || ''
  3424. };
  3425. this.set_text(obj, "");
  3426. s.attr('class', a.attr('class')).append(a.contents().clone()).append(h2);
  3427. a.replaceWith(s);
  3428. h1.css(fn);
  3429. h2.css(fn).width(Math.min(h1.text("pW" + h2[0].value).width(),w))[0].select();
  3430. },
  3431. /**
  3432. * changes the theme
  3433. * @name set_theme(theme_name [, theme_url])
  3434. * @param {String} theme_name the name of the new theme to apply
  3435. * @param {mixed} theme_url the location of the CSS file for this theme. Omit or set to `false` if you manually included the file. Set to `true` to autoload from the `core.themes.dir` directory.
  3436. * @trigger set_theme.jstree
  3437. */
  3438. set_theme : function (theme_name, theme_url) {
  3439. if(!theme_name) { return false; }
  3440. if(theme_url === true) {
  3441. var dir = this.settings.core.themes.dir;
  3442. if(!dir) { dir = $.jstree.path + '/themes'; }
  3443. theme_url = dir + '/' + theme_name + '/style.css';
  3444. }
  3445. if(theme_url && $.inArray(theme_url, themes_loaded) === -1) {
  3446. $('head').append('<'+'link rel="stylesheet" href="' + theme_url + '" type="text/css" />');
  3447. themes_loaded.push(theme_url);
  3448. }
  3449. if(this._data.core.themes.name) {
  3450. this.element.removeClass('jstree-' + this._data.core.themes.name);
  3451. }
  3452. this._data.core.themes.name = theme_name;
  3453. this.element.addClass('jstree-' + theme_name);
  3454. this.element[this.settings.core.themes.responsive ? 'addClass' : 'removeClass' ]('jstree-' + theme_name + '-responsive');
  3455. /**
  3456. * triggered when a theme is set
  3457. * @event
  3458. * @name set_theme.jstree
  3459. * @param {String} theme the new theme
  3460. */
  3461. this.trigger('set_theme', { 'theme' : theme_name });
  3462. },
  3463. /**
  3464. * gets the name of the currently applied theme name
  3465. * @name get_theme()
  3466. * @return {String}
  3467. */
  3468. get_theme : function () { return this._data.core.themes.name; },
  3469. /**
  3470. * changes the theme variant (if the theme has variants)
  3471. * @name set_theme_variant(variant_name)
  3472. * @param {String|Boolean} variant_name the variant to apply (if `false` is used the current variant is removed)
  3473. */
  3474. set_theme_variant : function (variant_name) {
  3475. if(this._data.core.themes.variant) {
  3476. this.element.removeClass('jstree-' + this._data.core.themes.name + '-' + this._data.core.themes.variant);
  3477. }
  3478. this._data.core.themes.variant = variant_name;
  3479. if(variant_name) {
  3480. this.element.addClass('jstree-' + this._data.core.themes.name + '-' + this._data.core.themes.variant);
  3481. }
  3482. },
  3483. /**
  3484. * gets the name of the currently applied theme variant
  3485. * @name get_theme()
  3486. * @return {String}
  3487. */
  3488. get_theme_variant : function () { return this._data.core.themes.variant; },
  3489. /**
  3490. * shows a striped background on the container (if the theme supports it)
  3491. * @name show_stripes()
  3492. */
  3493. show_stripes : function () { this._data.core.themes.stripes = true; this.get_container_ul().addClass("jstree-striped"); },
  3494. /**
  3495. * hides the striped background on the container
  3496. * @name hide_stripes()
  3497. */
  3498. hide_stripes : function () { this._data.core.themes.stripes = false; this.get_container_ul().removeClass("jstree-striped"); },
  3499. /**
  3500. * toggles the striped background on the container
  3501. * @name toggle_stripes()
  3502. */
  3503. toggle_stripes : function () { if(this._data.core.themes.stripes) { this.hide_stripes(); } else { this.show_stripes(); } },
  3504. /**
  3505. * shows the connecting dots (if the theme supports it)
  3506. * @name show_dots()
  3507. */
  3508. show_dots : function () { this._data.core.themes.dots = true; this.get_container_ul().removeClass("jstree-no-dots"); },
  3509. /**
  3510. * hides the connecting dots
  3511. * @name hide_dots()
  3512. */
  3513. hide_dots : function () { this._data.core.themes.dots = false; this.get_container_ul().addClass("jstree-no-dots"); },
  3514. /**
  3515. * toggles the connecting dots
  3516. * @name toggle_dots()
  3517. */
  3518. toggle_dots : function () { if(this._data.core.themes.dots) { this.hide_dots(); } else { this.show_dots(); } },
  3519. /**
  3520. * show the node icons
  3521. * @name show_icons()
  3522. */
  3523. show_icons : function () { this._data.core.themes.icons = true; this.get_container_ul().removeClass("jstree-no-icons"); },
  3524. /**
  3525. * hide the node icons
  3526. * @name hide_icons()
  3527. */
  3528. hide_icons : function () { this._data.core.themes.icons = false; this.get_container_ul().addClass("jstree-no-icons"); },
  3529. /**
  3530. * toggle the node icons
  3531. * @name toggle_icons()
  3532. */
  3533. toggle_icons : function () { if(this._data.core.themes.icons) { this.hide_icons(); } else { this.show_icons(); } },
  3534. /**
  3535. * set the node icon for a node
  3536. * @name set_icon(obj, icon)
  3537. * @param {mixed} obj
  3538. * @param {String} icon the new icon - can be a path to an icon or a className, if using an image that is in the current directory use a `./` prefix, otherwise it will be detected as a class
  3539. */
  3540. set_icon : function (obj, icon) {
  3541. var t1, t2, dom, old;
  3542. if($.isArray(obj)) {
  3543. obj = obj.slice();
  3544. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  3545. this.set_icon(obj[t1], icon);
  3546. }
  3547. return true;
  3548. }
  3549. obj = this.get_node(obj);
  3550. if(!obj || obj.id === '#') { return false; }
  3551. old = obj.icon;
  3552. obj.icon = icon;
  3553. dom = this.get_node(obj, true).children(".jstree-anchor").children(".jstree-themeicon");
  3554. if(icon === false) {
  3555. this.hide_icon(obj);
  3556. }
  3557. else if(icon === true) {
  3558. dom.removeClass('jstree-themeicon-custom ' + old).css("background","").removeAttr("rel");
  3559. }
  3560. else if(icon.indexOf("/") === -1 && icon.indexOf(".") === -1) {
  3561. dom.removeClass(old).css("background","");
  3562. dom.addClass(icon + ' jstree-themeicon-custom').attr("rel",icon);
  3563. }
  3564. else {
  3565. dom.removeClass(old).css("background","");
  3566. dom.addClass('jstree-themeicon-custom').css("background", "url('" + icon + "') center center no-repeat").attr("rel",icon);
  3567. }
  3568. return true;
  3569. },
  3570. /**
  3571. * get the node icon for a node
  3572. * @name get_icon(obj)
  3573. * @param {mixed} obj
  3574. * @return {String}
  3575. */
  3576. get_icon : function (obj) {
  3577. obj = this.get_node(obj);
  3578. return (!obj || obj.id === '#') ? false : obj.icon;
  3579. },
  3580. /**
  3581. * hide the icon on an individual node
  3582. * @name hide_icon(obj)
  3583. * @param {mixed} obj
  3584. */
  3585. hide_icon : function (obj) {
  3586. var t1, t2;
  3587. if($.isArray(obj)) {
  3588. obj = obj.slice();
  3589. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  3590. this.hide_icon(obj[t1]);
  3591. }
  3592. return true;
  3593. }
  3594. obj = this.get_node(obj);
  3595. if(!obj || obj === '#') { return false; }
  3596. obj.icon = false;
  3597. this.get_node(obj, true).children("a").children(".jstree-themeicon").addClass('jstree-themeicon-hidden');
  3598. return true;
  3599. },
  3600. /**
  3601. * show the icon on an individual node
  3602. * @name show_icon(obj)
  3603. * @param {mixed} obj
  3604. */
  3605. show_icon : function (obj) {
  3606. var t1, t2, dom;
  3607. if($.isArray(obj)) {
  3608. obj = obj.slice();
  3609. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  3610. this.show_icon(obj[t1]);
  3611. }
  3612. return true;
  3613. }
  3614. obj = this.get_node(obj);
  3615. if(!obj || obj === '#') { return false; }
  3616. dom = this.get_node(obj, true);
  3617. obj.icon = dom.length ? dom.children("a").children(".jstree-themeicon").attr('rel') : true;
  3618. if(!obj.icon) { obj.icon = true; }
  3619. dom.children("a").children(".jstree-themeicon").removeClass('jstree-themeicon-hidden');
  3620. return true;
  3621. }
  3622. };
  3623. // helpers
  3624. $.vakata = {};
  3625. // collect attributes
  3626. $.vakata.attributes = function(node, with_values) {
  3627. node = $(node)[0];
  3628. var attr = with_values ? {} : [];
  3629. if(node && node.attributes) {
  3630. $.each(node.attributes, function (i, v) {
  3631. if($.inArray(v.nodeName.toLowerCase(),['style','contenteditable','hasfocus','tabindex']) !== -1) { return; }
  3632. if(v.nodeValue !== null && $.trim(v.nodeValue) !== '') {
  3633. if(with_values) { attr[v.nodeName] = v.nodeValue; }
  3634. else { attr.push(v.nodeName); }
  3635. }
  3636. });
  3637. }
  3638. return attr;
  3639. };
  3640. $.vakata.array_unique = function(array) {
  3641. var a = [], i, j, l;
  3642. for(i = 0, l = array.length; i < l; i++) {
  3643. for(j = 0; j <= i; j++) {
  3644. if(array[i] === array[j]) {
  3645. break;
  3646. }
  3647. }
  3648. if(j === i) { a.push(array[i]); }
  3649. }
  3650. return a;
  3651. };
  3652. // remove item from array
  3653. $.vakata.array_remove = function(array, from, to) {
  3654. var rest = array.slice((to || from) + 1 || array.length);
  3655. array.length = from < 0 ? array.length + from : from;
  3656. array.push.apply(array, rest);
  3657. return array;
  3658. };
  3659. // remove item from array
  3660. $.vakata.array_remove_item = function(array, item) {
  3661. var tmp = $.inArray(item, array);
  3662. return tmp !== -1 ? $.vakata.array_remove(array, tmp) : array;
  3663. };
  3664. // browser sniffing
  3665. (function () {
  3666. var browser = {},
  3667. b_match = function(ua) {
  3668. ua = ua.toLowerCase();
  3669. var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
  3670. /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
  3671. /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
  3672. /(msie) ([\w.]+)/.exec( ua ) ||
  3673. (ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua )) ||
  3674. [];
  3675. return {
  3676. browser: match[1] || "",
  3677. version: match[2] || "0"
  3678. };
  3679. },
  3680. matched = b_match(window.navigator.userAgent);
  3681. if(matched.browser) {
  3682. browser[ matched.browser ] = true;
  3683. browser.version = matched.version;
  3684. }
  3685. if(browser.chrome) {
  3686. browser.webkit = true;
  3687. }
  3688. else if(browser.webkit) {
  3689. browser.safari = true;
  3690. }
  3691. $.vakata.browser = browser;
  3692. }());
  3693. if($.vakata.browser.msie && $.vakata.browser.version < 8) {
  3694. $.jstree.defaults.core.animation = 0;
  3695. }
  3696. /**
  3697. * ### Checkbox plugin
  3698. *
  3699. * This plugin renders checkbox icons in front of each node, making multiple selection much easier.
  3700. * It also supports tri-state behavior, meaning that if a node has a few of its children checked it will be rendered as undetermined, and state will be propagated up.
  3701. */
  3702. var _i = document.createElement('I');
  3703. _i.className = 'jstree-icon jstree-checkbox';
  3704. /**
  3705. * stores all defaults for the checkbox plugin
  3706. * @name $.jstree.defaults.checkbox
  3707. * @plugin checkbox
  3708. */
  3709. $.jstree.defaults.checkbox = {
  3710. /**
  3711. * a boolean indicating if checkboxes should be visible (can be changed at a later time using `show_checkboxes()` and `hide_checkboxes`). Defaults to `true`.
  3712. * @name $.jstree.defaults.checkbox.visible
  3713. * @plugin checkbox
  3714. */
  3715. visible : true,
  3716. /**
  3717. * a boolean indicating if checkboxes should cascade down and have an undetermined state. Defaults to `true`.
  3718. * @name $.jstree.defaults.checkbox.three_state
  3719. * @plugin checkbox
  3720. */
  3721. three_state : true,
  3722. /**
  3723. * a boolean indicating if clicking anywhere on the node should act as clicking on the checkbox. Defaults to `true`.
  3724. * @name $.jstree.defaults.checkbox.whole_node
  3725. * @plugin checkbox
  3726. */
  3727. whole_node : true,
  3728. /**
  3729. * a boolean indicating if the selected style of a node should be kept, or removed. Defaults to `true`.
  3730. * @name $.jstree.defaults.checkbox.keep_selected_style
  3731. * @plugin checkbox
  3732. */
  3733. keep_selected_style : true
  3734. };
  3735. $.jstree.plugins.checkbox = function (options, parent) {
  3736. this.bind = function () {
  3737. parent.bind.call(this);
  3738. this._data.checkbox.uto = false;
  3739. this.element
  3740. .on("init.jstree", $.proxy(function () {
  3741. this._data.checkbox.visible = this.settings.checkbox.visible;
  3742. if(!this.settings.checkbox.keep_selected_style) {
  3743. this.element.addClass('jstree-checkbox-no-clicked');
  3744. }
  3745. }, this))
  3746. .on("loading.jstree", $.proxy(function () {
  3747. this[ this._data.checkbox.visible ? 'show_checkboxes' : 'hide_checkboxes' ]();
  3748. }, this));
  3749. if(this.settings.checkbox.three_state) {
  3750. this.element
  3751. .on('changed.jstree move_node.jstree copy_node.jstree redraw.jstree open_node.jstree', $.proxy(function () {
  3752. if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); }
  3753. this._data.checkbox.uto = setTimeout($.proxy(this._undetermined, this), 50);
  3754. }, this))
  3755. .on('model.jstree', $.proxy(function (e, data) {
  3756. var m = this._model.data,
  3757. p = m[data.parent],
  3758. dpc = data.nodes,
  3759. chd = [],
  3760. c, i, j, k, l, tmp;
  3761. // apply down
  3762. if(p.state.selected) {
  3763. for(i = 0, j = dpc.length; i < j; i++) {
  3764. m[dpc[i]].state.selected = true;
  3765. }
  3766. this._data.core.selected = this._data.core.selected.concat(dpc);
  3767. }
  3768. else {
  3769. for(i = 0, j = dpc.length; i < j; i++) {
  3770. if(m[dpc[i]].state.selected) {
  3771. for(k = 0, l = m[dpc[i]].children_d.length; k < l; k++) {
  3772. m[m[dpc[i]].children_d[k]].state.selected = true;
  3773. }
  3774. this._data.core.selected = this._data.core.selected.concat(m[dpc[i]].children_d);
  3775. }
  3776. }
  3777. }
  3778. // apply up
  3779. for(i = 0, j = p.children_d.length; i < j; i++) {
  3780. if(!m[p.children_d[i]].children.length) {
  3781. chd.push(m[p.children_d[i]].parent);
  3782. }
  3783. }
  3784. chd = $.vakata.array_unique(chd);
  3785. for(k = 0, l = chd.length; k < l; k++) {
  3786. p = m[chd[k]];
  3787. while(p && p.id !== '#') {
  3788. c = 0;
  3789. for(i = 0, j = p.children.length; i < j; i++) {
  3790. c += m[p.children[i]].state.selected;
  3791. }
  3792. if(c === j) {
  3793. p.state.selected = true;
  3794. this._data.core.selected.push(p.id);
  3795. tmp = this.get_node(p, true);
  3796. if(tmp && tmp.length) {
  3797. tmp.children('.jstree-anchor').addClass('jstree-clicked');
  3798. }
  3799. }
  3800. else {
  3801. break;
  3802. }
  3803. p = this.get_node(p.parent);
  3804. }
  3805. }
  3806. this._data.core.selected = $.vakata.array_unique(this._data.core.selected);
  3807. }, this))
  3808. .on('select_node.jstree', $.proxy(function (e, data) {
  3809. var obj = data.node,
  3810. m = this._model.data,
  3811. par = this.get_node(obj.parent),
  3812. dom = this.get_node(obj, true),
  3813. i, j, c, tmp;
  3814. this._data.core.selected = $.vakata.array_unique(this._data.core.selected.concat(obj.children_d));
  3815. for(i = 0, j = obj.children_d.length; i < j; i++) {
  3816. tmp = m[obj.children_d[i]];
  3817. tmp.state.selected = true;
  3818. if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
  3819. tmp.original.state.undetermined = false;
  3820. }
  3821. }
  3822. while(par && par.id !== '#') {
  3823. c = 0;
  3824. for(i = 0, j = par.children.length; i < j; i++) {
  3825. c += m[par.children[i]].state.selected;
  3826. }
  3827. if(c === j) {
  3828. par.state.selected = true;
  3829. this._data.core.selected.push(par.id);
  3830. tmp = this.get_node(par, true);
  3831. if(tmp && tmp.length) {
  3832. tmp.children('.jstree-anchor').addClass('jstree-clicked');
  3833. }
  3834. }
  3835. else {
  3836. break;
  3837. }
  3838. par = this.get_node(par.parent);
  3839. }
  3840. if(dom.length) {
  3841. dom.find('.jstree-anchor').addClass('jstree-clicked');
  3842. }
  3843. }, this))
  3844. .on('deselect_all.jstree', $.proxy(function (e, data) {
  3845. var obj = this.get_node('#'),
  3846. m = this._model.data,
  3847. i, j, tmp;
  3848. for(i = 0, j = obj.children_d.length; i < j; i++) {
  3849. tmp = m[obj.children_d[i]];
  3850. if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
  3851. tmp.original.state.undetermined = false;
  3852. }
  3853. }
  3854. }, this))
  3855. .on('deselect_node.jstree', $.proxy(function (e, data) {
  3856. var obj = data.node,
  3857. dom = this.get_node(obj, true),
  3858. i, j, tmp;
  3859. if(obj && obj.original && obj.original.state && obj.original.state.undetermined) {
  3860. obj.original.state.undetermined = false;
  3861. }
  3862. for(i = 0, j = obj.children_d.length; i < j; i++) {
  3863. tmp = this._model.data[obj.children_d[i]];
  3864. tmp.state.selected = false;
  3865. if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
  3866. tmp.original.state.undetermined = false;
  3867. }
  3868. }
  3869. for(i = 0, j = obj.parents.length; i < j; i++) {
  3870. tmp = this._model.data[obj.parents[i]];
  3871. tmp.state.selected = false;
  3872. if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
  3873. tmp.original.state.undetermined = false;
  3874. }
  3875. tmp = this.get_node(obj.parents[i], true);
  3876. if(tmp && tmp.length) {
  3877. tmp.children('.jstree-anchor').removeClass('jstree-clicked');
  3878. }
  3879. }
  3880. tmp = [];
  3881. for(i = 0, j = this._data.core.selected.length; i < j; i++) {
  3882. if($.inArray(this._data.core.selected[i], obj.children_d) === -1 && $.inArray(this._data.core.selected[i], obj.parents) === -1) {
  3883. tmp.push(this._data.core.selected[i]);
  3884. }
  3885. }
  3886. this._data.core.selected = $.vakata.array_unique(tmp);
  3887. if(dom.length) {
  3888. dom.find('.jstree-anchor').removeClass('jstree-clicked');
  3889. }
  3890. }, this))
  3891. .on('delete_node.jstree', $.proxy(function (e, data) {
  3892. var p = this.get_node(data.parent),
  3893. m = this._model.data,
  3894. i, j, c, tmp;
  3895. while(p && p.id !== '#') {
  3896. c = 0;
  3897. for(i = 0, j = p.children.length; i < j; i++) {
  3898. c += m[p.children[i]].state.selected;
  3899. }
  3900. if(c === j) {
  3901. p.state.selected = true;
  3902. this._data.core.selected.push(p.id);
  3903. tmp = this.get_node(p, true);
  3904. if(tmp && tmp.length) {
  3905. tmp.children('.jstree-anchor').addClass('jstree-clicked');
  3906. }
  3907. }
  3908. else {
  3909. break;
  3910. }
  3911. p = this.get_node(p.parent);
  3912. }
  3913. }, this))
  3914. .on('move_node.jstree', $.proxy(function (e, data) {
  3915. var is_multi = data.is_multi,
  3916. old_par = data.old_parent,
  3917. new_par = this.get_node(data.parent),
  3918. m = this._model.data,
  3919. p, c, i, j, tmp;
  3920. if(!is_multi) {
  3921. p = this.get_node(old_par);
  3922. while(p && p.id !== '#') {
  3923. c = 0;
  3924. for(i = 0, j = p.children.length; i < j; i++) {
  3925. c += m[p.children[i]].state.selected;
  3926. }
  3927. if(c === j) {
  3928. p.state.selected = true;
  3929. this._data.core.selected.push(p.id);
  3930. tmp = this.get_node(p, true);
  3931. if(tmp && tmp.length) {
  3932. tmp.children('.jstree-anchor').addClass('jstree-clicked');
  3933. }
  3934. }
  3935. else {
  3936. break;
  3937. }
  3938. p = this.get_node(p.parent);
  3939. }
  3940. }
  3941. p = new_par;
  3942. while(p && p.id !== '#') {
  3943. c = 0;
  3944. for(i = 0, j = p.children.length; i < j; i++) {
  3945. c += m[p.children[i]].state.selected;
  3946. }
  3947. if(c === j) {
  3948. if(!p.state.selected) {
  3949. p.state.selected = true;
  3950. this._data.core.selected.push(p.id);
  3951. tmp = this.get_node(p, true);
  3952. if(tmp && tmp.length) {
  3953. tmp.children('.jstree-anchor').addClass('jstree-clicked');
  3954. }
  3955. }
  3956. }
  3957. else {
  3958. if(p.state.selected) {
  3959. p.state.selected = false;
  3960. this._data.core.selected = $.vakata.array_remove_item(this._data.core.selected, p.id);
  3961. tmp = this.get_node(p, true);
  3962. if(tmp && tmp.length) {
  3963. tmp.children('.jstree-anchor').removeClass('jstree-clicked');
  3964. }
  3965. }
  3966. else {
  3967. break;
  3968. }
  3969. }
  3970. p = this.get_node(p.parent);
  3971. }
  3972. }, this));
  3973. }
  3974. };
  3975. /**
  3976. * set the undetermined state where and if necessary. Used internally.
  3977. * @private
  3978. * @name _undetermined()
  3979. * @plugin checkbox
  3980. */
  3981. this._undetermined = function () {
  3982. var i, j, m = this._model.data, s = this._data.core.selected, p = [], t = this;
  3983. for(i = 0, j = s.length; i < j; i++) {
  3984. if(m[s[i]] && m[s[i]].parents) {
  3985. p = p.concat(m[s[i]].parents);
  3986. }
  3987. }
  3988. // attempt for server side undetermined state
  3989. this.element.find('.jstree-closed').not(':has(ul)')
  3990. .each(function () {
  3991. var tmp = t.get_node(this), tmp2;
  3992. if(!tmp.state.loaded) {
  3993. if(tmp.original && tmp.original.state && tmp.original.state.undetermined && tmp.original.state.undetermined === true) {
  3994. p.push(tmp.id);
  3995. p = p.concat(tmp.parents);
  3996. }
  3997. }
  3998. else {
  3999. for(i = 0, j = tmp.children_d.length; i < j; i++) {
  4000. tmp2 = m[tmp.children_d[i]];
  4001. if(!tmp2.state.loaded && tmp2.original && tmp2.original.state && tmp2.original.state.undetermined && tmp2.original.state.undetermined === true) {
  4002. p.push(tmp2.id);
  4003. p = p.concat(tmp2.parents);
  4004. }
  4005. }
  4006. }
  4007. });
  4008. p = $.vakata.array_unique(p);
  4009. p = $.vakata.array_remove_item(p,'#');
  4010. this.element.find('.jstree-undetermined').removeClass('jstree-undetermined');
  4011. for(i = 0, j = p.length; i < j; i++) {
  4012. if(!m[p[i]].state.selected) {
  4013. s = this.get_node(p[i], true);
  4014. if(s && s.length) {
  4015. s.children('a').children('.jstree-checkbox').addClass('jstree-undetermined');
  4016. }
  4017. }
  4018. }
  4019. };
  4020. this.redraw_node = function(obj, deep, is_callback) {
  4021. obj = parent.redraw_node.call(this, obj, deep, is_callback);
  4022. if(obj) {
  4023. var tmp = obj.getElementsByTagName('A')[0];
  4024. tmp.insertBefore(_i.cloneNode(false), tmp.childNodes[0]);
  4025. }
  4026. if(!is_callback && this.settings.checkbox.three_state) {
  4027. if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); }
  4028. this._data.checkbox.uto = setTimeout($.proxy(this._undetermined, this), 50);
  4029. }
  4030. return obj;
  4031. };
  4032. this.activate_node = function (obj, e) {
  4033. if(this.settings.checkbox.whole_node || $(e.target).hasClass('jstree-checkbox')) {
  4034. e.ctrlKey = true;
  4035. }
  4036. return parent.activate_node.call(this, obj, e);
  4037. };
  4038. /**
  4039. * show the node checkbox icons
  4040. * @name show_checkboxes()
  4041. * @plugin checkbox
  4042. */
  4043. this.show_checkboxes = function () { this._data.core.themes.checkboxes = true; this.element.children("ul").removeClass("jstree-no-checkboxes"); };
  4044. /**
  4045. * hide the node checkbox icons
  4046. * @name hide_checkboxes()
  4047. * @plugin checkbox
  4048. */
  4049. this.hide_checkboxes = function () { this._data.core.themes.checkboxes = false; this.element.children("ul").addClass("jstree-no-checkboxes"); };
  4050. /**
  4051. * toggle the node icons
  4052. * @name toggle_checkboxes()
  4053. * @plugin checkbox
  4054. */
  4055. this.toggle_checkboxes = function () { if(this._data.core.themes.checkboxes) { this.hide_checkboxes(); } else { this.show_checkboxes(); } };
  4056. };
  4057. // include the checkbox plugin by default
  4058. // $.jstree.defaults.plugins.push("checkbox");
  4059. /**
  4060. * ### Contextmenu plugin
  4061. *
  4062. * Shows a context menu when a node is right-clicked.
  4063. */
  4064. // TODO: move logic outside of function + check multiple move
  4065. /**
  4066. * stores all defaults for the contextmenu plugin
  4067. * @name $.jstree.defaults.contextmenu
  4068. * @plugin contextmenu
  4069. */
  4070. $.jstree.defaults.contextmenu = {
  4071. /**
  4072. * a boolean indicating if the node should be selected when the context menu is invoked on it. Defaults to `true`.
  4073. * @name $.jstree.defaults.contextmenu.select_node
  4074. * @plugin contextmenu
  4075. */
  4076. select_node : true,
  4077. /**
  4078. * a boolean indicating if the menu should be shown aligned with the node. Defaults to `true`, otherwise the mouse coordinates are used.
  4079. * @name $.jstree.defaults.contextmenu.show_at_node
  4080. * @plugin contextmenu
  4081. */
  4082. show_at_node : true,
  4083. /**
  4084. * an object of actions, or a function that accepts a node and a callback function and calls the callback function with an object of actions available for that node (you can also return the items too).
  4085. *
  4086. * Each action consists of a key (a unique name) and a value which is an object with the following properties (only label and action are required):
  4087. *
  4088. * * `separator_before` - a boolean indicating if there should be a separator before this item
  4089. * * `separator_after` - a boolean indicating if there should be a separator after this item
  4090. * * `_disabled` - a boolean indicating if this action should be disabled
  4091. * * `label` - a string - the name of the action (could be a function returning a string)
  4092. * * `action` - a function to be executed if this item is chosen
  4093. * * `icon` - a string, can be a path to an icon or a className, if using an image that is in the current directory use a `./` prefix, otherwise it will be detected as a class
  4094. * * `shortcut` - keyCode which will trigger the action if the menu is open (for example `113` for rename, which equals F2)
  4095. * * `shortcut_label` - shortcut label (like for example `F2` for rename)
  4096. *
  4097. * @name $.jstree.defaults.contextmenu.items
  4098. * @plugin contextmenu
  4099. */
  4100. items : function (o, cb) { // Could be an object directly
  4101. return {
  4102. "create" : {
  4103. "separator_before" : false,
  4104. "separator_after" : true,
  4105. "_disabled" : false, //(this.check("create_node", data.reference, {}, "last")),
  4106. "label" : "Create",
  4107. "action" : function (data) {
  4108. var inst = $.jstree.reference(data.reference),
  4109. obj = inst.get_node(data.reference);
  4110. inst.create_node(obj, {}, "last", function (new_node) {
  4111. setTimeout(function () { inst.edit(new_node); },0);
  4112. });
  4113. }
  4114. },
  4115. "rename" : {
  4116. "separator_before" : false,
  4117. "separator_after" : false,
  4118. "_disabled" : false, //(this.check("rename_node", data.reference, this.get_parent(data.reference), "")),
  4119. "label" : "Rename",
  4120. /*
  4121. "shortcut" : 113,
  4122. "shortcut_label" : 'F2',
  4123. "icon" : "glyphicon glyphicon-leaf",
  4124. */
  4125. "action" : function (data) {
  4126. var inst = $.jstree.reference(data.reference),
  4127. obj = inst.get_node(data.reference);
  4128. inst.edit(obj);
  4129. }
  4130. },
  4131. "remove" : {
  4132. "separator_before" : false,
  4133. "icon" : false,
  4134. "separator_after" : false,
  4135. "_disabled" : false, //(this.check("delete_node", data.reference, this.get_parent(data.reference), "")),
  4136. "label" : "Delete",
  4137. "action" : function (data) {
  4138. var inst = $.jstree.reference(data.reference),
  4139. obj = inst.get_node(data.reference);
  4140. if(inst.is_selected(obj)) {
  4141. inst.delete_node(inst.get_selected());
  4142. }
  4143. else {
  4144. inst.delete_node(obj);
  4145. }
  4146. }
  4147. },
  4148. "ccp" : {
  4149. "separator_before" : true,
  4150. "icon" : false,
  4151. "separator_after" : false,
  4152. "label" : "Edit",
  4153. "action" : false,
  4154. "submenu" : {
  4155. "cut" : {
  4156. "separator_before" : false,
  4157. "separator_after" : false,
  4158. "label" : "Cut",
  4159. "action" : function (data) {
  4160. var inst = $.jstree.reference(data.reference),
  4161. obj = inst.get_node(data.reference);
  4162. if(inst.is_selected(obj)) {
  4163. inst.cut(inst.get_selected());
  4164. }
  4165. else {
  4166. inst.cut(obj);
  4167. }
  4168. }
  4169. },
  4170. "copy" : {
  4171. "separator_before" : false,
  4172. "icon" : false,
  4173. "separator_after" : false,
  4174. "label" : "Copy",
  4175. "action" : function (data) {
  4176. var inst = $.jstree.reference(data.reference),
  4177. obj = inst.get_node(data.reference);
  4178. if(inst.is_selected(obj)) {
  4179. inst.copy(inst.get_selected());
  4180. }
  4181. else {
  4182. inst.copy(obj);
  4183. }
  4184. }
  4185. },
  4186. "paste" : {
  4187. "separator_before" : false,
  4188. "icon" : false,
  4189. "_disabled" : function (data) {
  4190. return !$.jstree.reference(data.reference).can_paste();
  4191. },
  4192. "separator_after" : false,
  4193. "label" : "Paste",
  4194. "action" : function (data) {
  4195. var inst = $.jstree.reference(data.reference),
  4196. obj = inst.get_node(data.reference);
  4197. inst.paste(obj);
  4198. }
  4199. }
  4200. }
  4201. }
  4202. };
  4203. }
  4204. };
  4205. $.jstree.plugins.contextmenu = function (options, parent) {
  4206. this.bind = function () {
  4207. parent.bind.call(this);
  4208. var last_ts = 0;
  4209. this.element
  4210. .on("contextmenu.jstree", ".jstree-anchor", $.proxy(function (e) {
  4211. e.preventDefault();
  4212. last_ts = e.ctrlKey ? e.timeStamp : 0;
  4213. if(!this.is_loading(e.currentTarget)) {
  4214. this.show_contextmenu(e.currentTarget, e.pageX, e.pageY, e);
  4215. }
  4216. }, this))
  4217. .on("click.jstree", ".jstree-anchor", $.proxy(function (e) {
  4218. if(this._data.contextmenu.visible && (!last_ts || e.timeStamp - last_ts > 250)) { // work around safari & macOS ctrl+click
  4219. $.vakata.context.hide();
  4220. }
  4221. }, this));
  4222. /*
  4223. if(!('oncontextmenu' in document.body) && ('ontouchstart' in document.body)) {
  4224. var el = null, tm = null;
  4225. this.element
  4226. .on("touchstart", ".jstree-anchor", function (e) {
  4227. el = e.currentTarget;
  4228. tm = +new Date();
  4229. $(document).one("touchend", function (e) {
  4230. e.target = document.elementFromPoint(e.originalEvent.targetTouches[0].pageX - window.pageXOffset, e.originalEvent.targetTouches[0].pageY - window.pageYOffset);
  4231. e.currentTarget = e.target;
  4232. tm = ((+(new Date())) - tm);
  4233. if(e.target === el && tm > 600 && tm < 1000) {
  4234. e.preventDefault();
  4235. $(el).trigger('contextmenu', e);
  4236. }
  4237. el = null;
  4238. tm = null;
  4239. });
  4240. });
  4241. }
  4242. */
  4243. $(document).on("context_hide.vakata", $.proxy(function () { this._data.contextmenu.visible = false; }, this));
  4244. };
  4245. this.teardown = function () {
  4246. if(this._data.contextmenu.visible) {
  4247. $.vakata.context.hide();
  4248. }
  4249. parent.teardown.call(this);
  4250. };
  4251. /**
  4252. * prepare and show the context menu for a node
  4253. * @name show_contextmenu(obj [, x, y])
  4254. * @param {mixed} obj the node
  4255. * @param {Number} x the x-coordinate relative to the document to show the menu at
  4256. * @param {Number} y the y-coordinate relative to the document to show the menu at
  4257. * @param {Object} e the event if available that triggered the contextmenu
  4258. * @plugin contextmenu
  4259. * @trigger show_contextmenu.jstree
  4260. */
  4261. this.show_contextmenu = function (obj, x, y, e) {
  4262. obj = this.get_node(obj);
  4263. if(!obj || obj.id === '#') { return false; }
  4264. var s = this.settings.contextmenu,
  4265. d = this.get_node(obj, true),
  4266. a = d.children(".jstree-anchor"),
  4267. o = false,
  4268. i = false;
  4269. if(s.show_at_node || x === undefined || y === undefined) {
  4270. o = a.offset();
  4271. x = o.left;
  4272. y = o.top + this._data.core.li_height;
  4273. }
  4274. if(this.settings.contextmenu.select_node && !this.is_selected(obj)) {
  4275. this.deselect_all();
  4276. this.select_node(obj, false, false, e);
  4277. }
  4278. i = s.items;
  4279. if($.isFunction(i)) {
  4280. i = i.call(this, obj, $.proxy(function (i) {
  4281. this._show_contextmenu(obj, x, y, i);
  4282. }, this));
  4283. }
  4284. if($.isPlainObject(i)) {
  4285. this._show_contextmenu(obj, x, y, i);
  4286. }
  4287. };
  4288. /**
  4289. * show the prepared context menu for a node
  4290. * @name _show_contextmenu(obj, x, y, i)
  4291. * @param {mixed} obj the node
  4292. * @param {Number} x the x-coordinate relative to the document to show the menu at
  4293. * @param {Number} y the y-coordinate relative to the document to show the menu at
  4294. * @param {Number} i the object of items to show
  4295. * @plugin contextmenu
  4296. * @trigger show_contextmenu.jstree
  4297. * @private
  4298. */
  4299. this._show_contextmenu = function (obj, x, y, i) {
  4300. var d = this.get_node(obj, true),
  4301. a = d.children(".jstree-anchor");
  4302. $(document).one("context_show.vakata", $.proxy(function (e, data) {
  4303. var cls = 'jstree-contextmenu jstree-' + this.get_theme() + '-contextmenu';
  4304. $(data.element).addClass(cls);
  4305. }, this));
  4306. this._data.contextmenu.visible = true;
  4307. $.vakata.context.show(a, { 'x' : x, 'y' : y }, i);
  4308. /**
  4309. * triggered when the contextmenu is shown for a node
  4310. * @event
  4311. * @name show_contextmenu.jstree
  4312. * @param {Object} node the node
  4313. * @param {Number} x the x-coordinate of the menu relative to the document
  4314. * @param {Number} y the y-coordinate of the menu relative to the document
  4315. * @plugin contextmenu
  4316. */
  4317. this.trigger('show_contextmenu', { "node" : obj, "x" : x, "y" : y });
  4318. };
  4319. };
  4320. // contextmenu helper
  4321. (function ($) {
  4322. var right_to_left = false,
  4323. vakata_context = {
  4324. element : false,
  4325. reference : false,
  4326. position_x : 0,
  4327. position_y : 0,
  4328. items : [],
  4329. html : "",
  4330. is_visible : false
  4331. };
  4332. $.vakata.context = {
  4333. settings : {
  4334. hide_onmouseleave : 0,
  4335. icons : true
  4336. },
  4337. _trigger : function (event_name) {
  4338. $(document).triggerHandler("context_" + event_name + ".vakata", {
  4339. "reference" : vakata_context.reference,
  4340. "element" : vakata_context.element,
  4341. "position" : {
  4342. "x" : vakata_context.position_x,
  4343. "y" : vakata_context.position_y
  4344. }
  4345. });
  4346. },
  4347. _execute : function (i) {
  4348. i = vakata_context.items[i];
  4349. return i && (!i._disabled || ($.isFunction(i._disabled) && !i._disabled({ "item" : i, "reference" : vakata_context.reference, "element" : vakata_context.element }))) && i.action ? i.action.call(null, {
  4350. "item" : i,
  4351. "reference" : vakata_context.reference,
  4352. "element" : vakata_context.element,
  4353. "position" : {
  4354. "x" : vakata_context.position_x,
  4355. "y" : vakata_context.position_y
  4356. }
  4357. }) : false;
  4358. },
  4359. _parse : function (o, is_callback) {
  4360. if(!o) { return false; }
  4361. if(!is_callback) {
  4362. vakata_context.html = "";
  4363. vakata_context.items = [];
  4364. }
  4365. var str = "",
  4366. sep = false,
  4367. tmp;
  4368. if(is_callback) { str += "<"+"ul>"; }
  4369. $.each(o, function (i, val) {
  4370. if(!val) { return true; }
  4371. vakata_context.items.push(val);
  4372. if(!sep && val.separator_before) {
  4373. str += "<"+"li class='vakata-context-separator'><"+"a href='#' " + ($.vakata.context.settings.icons ? '' : 'style="margin-left:0px;"') + ">&#160;<"+"/a><"+"/li>";
  4374. }
  4375. sep = false;
  4376. str += "<"+"li class='" + (val._class || "") + (val._disabled === true || ($.isFunction(val._disabled) && val._disabled({ "item" : val, "reference" : vakata_context.reference, "element" : vakata_context.element })) ? " vakata-contextmenu-disabled " : "") + "' "+(val.shortcut?" data-shortcut='"+val.shortcut+"' ":'')+">";
  4377. str += "<"+"a href='#' rel='" + (vakata_context.items.length - 1) + "'>";
  4378. if($.vakata.context.settings.icons) {
  4379. str += "<"+"i ";
  4380. if(val.icon) {
  4381. if(val.icon.indexOf("/") !== -1 || val.icon.indexOf(".") !== -1) { str += " style='background:url(\"" + val.icon + "\") center center no-repeat' "; }
  4382. else { str += " class='" + val.icon + "' "; }
  4383. }
  4384. str += "><"+"/i><"+"span class='vakata-contextmenu-sep'>&#160;<"+"/span>";
  4385. }
  4386. str += ($.isFunction(val.label) ? val.label({ "item" : i, "reference" : vakata_context.reference, "element" : vakata_context.element }) : val.label) + (val.shortcut?' <span class="vakata-contextmenu-shortcut vakata-contextmenu-shortcut-'+val.shortcut+'">'+ (val.shortcut_label || '') +'</span>':'') + "<"+"/a>";
  4387. if(val.submenu) {
  4388. tmp = $.vakata.context._parse(val.submenu, true);
  4389. if(tmp) { str += tmp; }
  4390. }
  4391. str += "<"+"/li>";
  4392. if(val.separator_after) {
  4393. str += "<"+"li class='vakata-context-separator'><"+"a href='#' " + ($.vakata.context.settings.icons ? '' : 'style="margin-left:0px;"') + ">&#160;<"+"/a><"+"/li>";
  4394. sep = true;
  4395. }
  4396. });
  4397. str = str.replace(/<li class\='vakata-context-separator'\><\/li\>$/,"");
  4398. if(is_callback) { str += "</ul>"; }
  4399. /**
  4400. * triggered on the document when the contextmenu is parsed (HTML is built)
  4401. * @event
  4402. * @plugin contextmenu
  4403. * @name context_parse.vakata
  4404. * @param {jQuery} reference the element that was right clicked
  4405. * @param {jQuery} element the DOM element of the menu itself
  4406. * @param {Object} position the x & y coordinates of the menu
  4407. */
  4408. if(!is_callback) { vakata_context.html = str; $.vakata.context._trigger("parse"); }
  4409. return str.length > 10 ? str : false;
  4410. },
  4411. _show_submenu : function (o) {
  4412. o = $(o);
  4413. if(!o.length || !o.children("ul").length) { return; }
  4414. var e = o.children("ul"),
  4415. x = o.offset().left + o.outerWidth(),
  4416. y = o.offset().top,
  4417. w = e.width(),
  4418. h = e.height(),
  4419. dw = $(window).width() + $(window).scrollLeft(),
  4420. dh = $(window).height() + $(window).scrollTop();
  4421. // може да се спести е една проверка - дали няма някой от класовете вече нагоре
  4422. if(right_to_left) {
  4423. o[x - (w + 10 + o.outerWidth()) < 0 ? "addClass" : "removeClass"]("vakata-context-left");
  4424. }
  4425. else {
  4426. o[x + w + 10 > dw ? "addClass" : "removeClass"]("vakata-context-right");
  4427. }
  4428. if(y + h + 10 > dh) {
  4429. e.css("bottom","-1px");
  4430. }
  4431. e.show();
  4432. },
  4433. show : function (reference, position, data) {
  4434. var o, e, x, y, w, h, dw, dh, cond = true;
  4435. if(vakata_context.element && vakata_context.element.length) {
  4436. vakata_context.element.width('');
  4437. }
  4438. switch(cond) {
  4439. case (!position && !reference):
  4440. return false;
  4441. case (!!position && !!reference):
  4442. vakata_context.reference = reference;
  4443. vakata_context.position_x = position.x;
  4444. vakata_context.position_y = position.y;
  4445. break;
  4446. case (!position && !!reference):
  4447. vakata_context.reference = reference;
  4448. o = reference.offset();
  4449. vakata_context.position_x = o.left + reference.outerHeight();
  4450. vakata_context.position_y = o.top;
  4451. break;
  4452. case (!!position && !reference):
  4453. vakata_context.position_x = position.x;
  4454. vakata_context.position_y = position.y;
  4455. break;
  4456. }
  4457. if(!!reference && !data && $(reference).data('vakata_contextmenu')) {
  4458. data = $(reference).data('vakata_contextmenu');
  4459. }
  4460. if($.vakata.context._parse(data)) {
  4461. vakata_context.element.html(vakata_context.html);
  4462. }
  4463. if(vakata_context.items.length) {
  4464. e = vakata_context.element;
  4465. x = vakata_context.position_x;
  4466. y = vakata_context.position_y;
  4467. w = e.width();
  4468. h = e.height();
  4469. dw = $(window).width() + $(window).scrollLeft();
  4470. dh = $(window).height() + $(window).scrollTop();
  4471. if(right_to_left) {
  4472. x -= e.outerWidth();
  4473. if(x < $(window).scrollLeft() + 20) {
  4474. x = $(window).scrollLeft() + 20;
  4475. }
  4476. }
  4477. if(x + w + 20 > dw) {
  4478. x = dw - (w + 20);
  4479. }
  4480. if(y + h + 20 > dh) {
  4481. y = dh - (h + 20);
  4482. }
  4483. vakata_context.element
  4484. .css({ "left" : x, "top" : y })
  4485. .show()
  4486. .find('a:eq(0)').focus().parent().addClass("vakata-context-hover");
  4487. vakata_context.is_visible = true;
  4488. /**
  4489. * triggered on the document when the contextmenu is shown
  4490. * @event
  4491. * @plugin contextmenu
  4492. * @name context_show.vakata
  4493. * @param {jQuery} reference the element that was right clicked
  4494. * @param {jQuery} element the DOM element of the menu itself
  4495. * @param {Object} position the x & y coordinates of the menu
  4496. */
  4497. $.vakata.context._trigger("show");
  4498. }
  4499. },
  4500. hide : function () {
  4501. if(vakata_context.is_visible) {
  4502. vakata_context.element.hide().find("ul").hide().end().find(':focus').blur();
  4503. vakata_context.is_visible = false;
  4504. /**
  4505. * triggered on the document when the contextmenu is hidden
  4506. * @event
  4507. * @plugin contextmenu
  4508. * @name context_hide.vakata
  4509. * @param {jQuery} reference the element that was right clicked
  4510. * @param {jQuery} element the DOM element of the menu itself
  4511. * @param {Object} position the x & y coordinates of the menu
  4512. */
  4513. $.vakata.context._trigger("hide");
  4514. }
  4515. }
  4516. };
  4517. $(function () {
  4518. right_to_left = $("body").css("direction") === "rtl";
  4519. var to = false;
  4520. vakata_context.element = $("<ul class='vakata-context'></ul>");
  4521. vakata_context.element
  4522. .on("mouseenter", "li", function (e) {
  4523. e.stopImmediatePropagation();
  4524. if($.contains(this, e.relatedTarget)) {
  4525. // премахнато заради delegate mouseleave по-долу
  4526. // $(this).find(".vakata-context-hover").removeClass("vakata-context-hover");
  4527. return;
  4528. }
  4529. if(to) { clearTimeout(to); }
  4530. vakata_context.element.find(".vakata-context-hover").removeClass("vakata-context-hover").end();
  4531. $(this)
  4532. .siblings().find("ul").hide().end().end()
  4533. .parentsUntil(".vakata-context", "li").addBack().addClass("vakata-context-hover");
  4534. $.vakata.context._show_submenu(this);
  4535. })
  4536. // тестово - дали не натоварва?
  4537. .on("mouseleave", "li", function (e) {
  4538. if($.contains(this, e.relatedTarget)) { return; }
  4539. $(this).find(".vakata-context-hover").addBack().removeClass("vakata-context-hover");
  4540. })
  4541. .on("mouseleave", function (e) {
  4542. $(this).find(".vakata-context-hover").removeClass("vakata-context-hover");
  4543. if($.vakata.context.settings.hide_onmouseleave) {
  4544. to = setTimeout(
  4545. (function (t) {
  4546. return function () { $.vakata.context.hide(); };
  4547. }(this)), $.vakata.context.settings.hide_onmouseleave);
  4548. }
  4549. })
  4550. .on("click", "a", function (e) {
  4551. e.preventDefault();
  4552. })
  4553. .on("mouseup", "a", function (e) {
  4554. if(!$(this).blur().parent().hasClass("vakata-context-disabled") && $.vakata.context._execute($(this).attr("rel")) !== false) {
  4555. $.vakata.context.hide();
  4556. }
  4557. })
  4558. .on('keydown', 'a', function (e) {
  4559. var o = null;
  4560. switch(e.which) {
  4561. case 13:
  4562. case 32:
  4563. e.type = "mouseup";
  4564. e.preventDefault();
  4565. $(e.currentTarget).trigger(e);
  4566. break;
  4567. case 37:
  4568. if(vakata_context.is_visible) {
  4569. vakata_context.element.find(".vakata-context-hover").last().parents("li:eq(0)").find("ul").hide().find(".vakata-context-hover").removeClass("vakata-context-hover").end().end().children('a').focus();
  4570. e.stopImmediatePropagation();
  4571. e.preventDefault();
  4572. }
  4573. break;
  4574. case 38:
  4575. if(vakata_context.is_visible) {
  4576. o = vakata_context.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").prevAll("li:not(.vakata-context-separator)").first();
  4577. if(!o.length) { o = vakata_context.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").last(); }
  4578. o.addClass("vakata-context-hover").children('a').focus();
  4579. e.stopImmediatePropagation();
  4580. e.preventDefault();
  4581. }
  4582. break;
  4583. case 39:
  4584. if(vakata_context.is_visible) {
  4585. vakata_context.element.find(".vakata-context-hover").last().children("ul").show().children("li:not(.vakata-context-separator)").removeClass("vakata-context-hover").first().addClass("vakata-context-hover").children('a').focus();
  4586. e.stopImmediatePropagation();
  4587. e.preventDefault();
  4588. }
  4589. break;
  4590. case 40:
  4591. if(vakata_context.is_visible) {
  4592. o = vakata_context.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").nextAll("li:not(.vakata-context-separator)").first();
  4593. if(!o.length) { o = vakata_context.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").first(); }
  4594. o.addClass("vakata-context-hover").children('a').focus();
  4595. e.stopImmediatePropagation();
  4596. e.preventDefault();
  4597. }
  4598. break;
  4599. case 27:
  4600. $.vakata.context.hide();
  4601. e.preventDefault();
  4602. break;
  4603. default:
  4604. //console.log(e.which);
  4605. break;
  4606. }
  4607. })
  4608. .on('keydown', function (e) {
  4609. e.preventDefault();
  4610. var a = vakata_context.element.find('.vakata-contextmenu-shortcut-' + e.which).parent();
  4611. if(a.parent().not('.vakata-context-disabled')) {
  4612. a.mouseup();
  4613. }
  4614. })
  4615. .appendTo("body");
  4616. $(document)
  4617. .on("mousedown", function (e) {
  4618. if(vakata_context.is_visible && !$.contains(vakata_context.element[0], e.target)) { $.vakata.context.hide(); }
  4619. })
  4620. .on("context_show.vakata", function (e, data) {
  4621. vakata_context.element.find("li:has(ul)").children("a").addClass("vakata-context-parent");
  4622. if(right_to_left) {
  4623. vakata_context.element.addClass("vakata-context-rtl").css("direction", "rtl");
  4624. }
  4625. // also apply a RTL class?
  4626. vakata_context.element.find("ul").hide().end();
  4627. });
  4628. });
  4629. }($));
  4630. // $.jstree.defaults.plugins.push("contextmenu");
  4631. /**
  4632. * ### Drag'n'drop plugin
  4633. *
  4634. * Enables dragging and dropping of nodes in the tree, resulting in a move or copy operations.
  4635. */
  4636. /**
  4637. * stores all defaults for the drag'n'drop plugin
  4638. * @name $.jstree.defaults.dnd
  4639. * @plugin dnd
  4640. */
  4641. $.jstree.defaults.dnd = {
  4642. /**
  4643. * a boolean indicating if a copy should be possible while dragging (by pressint the meta key or Ctrl). Defaults to `true`.
  4644. * @name $.jstree.defaults.dnd.copy
  4645. * @plugin dnd
  4646. */
  4647. copy : true,
  4648. /**
  4649. * a number indicating how long a node should remain hovered while dragging to be opened. Defaults to `500`.
  4650. * @name $.jstree.defaults.dnd.open_timeout
  4651. * @plugin dnd
  4652. */
  4653. open_timeout : 500,
  4654. /**
  4655. * a function invoked each time a node is about to be dragged, invoked in the tree's scope and receives the nodes about to be dragged as an argument (array) - return `false` to prevent dragging
  4656. * @name $.jstree.defaults.dnd.is_draggable
  4657. * @plugin dnd
  4658. */
  4659. is_draggable : true,
  4660. /**
  4661. * a boolean indicating if checks should constantly be made while the user is dragging the node (as opposed to checking only on drop), default is `true`
  4662. * @name $.jstree.defaults.dnd.check_while_dragging
  4663. * @plugin dnd
  4664. */
  4665. check_while_dragging : true,
  4666. /**
  4667. * a boolean indicating if nodes from this tree should only be copied with dnd (as opposed to moved), default is `false`
  4668. * @name $.jstree.defaults.dnd.always_copy
  4669. * @plugin dnd
  4670. */
  4671. always_copy : false
  4672. };
  4673. // TODO: now check works by checking for each node individually, how about max_children, unique, etc?
  4674. // TODO: drop somewhere else - maybe demo only?
  4675. $.jstree.plugins.dnd = function (options, parent) {
  4676. this.bind = function () {
  4677. parent.bind.call(this);
  4678. this.element
  4679. .on('mousedown.jstree touchstart.jstree', '.jstree-anchor', $.proxy(function (e) {
  4680. var obj = this.get_node(e.target),
  4681. mlt = this.is_selected(obj) ? this.get_selected().length : 1;
  4682. if(obj && obj.id && obj.id !== "#" && (e.which === 1 || e.type === "touchstart") &&
  4683. (this.settings.dnd.is_draggable === true || ($.isFunction(this.settings.dnd.is_draggable) && this.settings.dnd.is_draggable.call(this, (mlt > 1 ? this.get_selected(true) : [obj]))))
  4684. ) {
  4685. this.element.trigger('mousedown.jstree');
  4686. return $.vakata.dnd.start(e, { 'jstree' : true, 'origin' : this, 'obj' : this.get_node(obj,true), 'nodes' : mlt > 1 ? this.get_selected() : [obj.id] }, '<div id="jstree-dnd" class="jstree-' + this.get_theme() + '"><i class="jstree-icon jstree-er"></i>' + (mlt > 1 ? mlt + ' ' + this.get_string('nodes') : this.get_text(e.currentTarget, true)) + '<ins class="jstree-copy" style="display:none;">+</ins></div>');
  4687. }
  4688. }, this));
  4689. };
  4690. };
  4691. $(function() {
  4692. // bind only once for all instances
  4693. var lastmv = false,
  4694. laster = false,
  4695. opento = false,
  4696. marker = $('<div id="jstree-marker">&#160;</div>').hide().appendTo('body');
  4697. $(document)
  4698. .bind('dnd_start.vakata', function (e, data) {
  4699. lastmv = false;
  4700. })
  4701. .bind('dnd_move.vakata', function (e, data) {
  4702. if(opento) { clearTimeout(opento); }
  4703. if(!data.data.jstree) { return; }
  4704. // if we are hovering the marker image do nothing (can happen on "inside" drags)
  4705. if(data.event.target.id && data.event.target.id === 'jstree-marker') {
  4706. return;
  4707. }
  4708. var ins = $.jstree.reference(data.event.target),
  4709. ref = false,
  4710. off = false,
  4711. rel = false,
  4712. l, t, h, p, i, o, ok, t1, t2, op, ps, pr;
  4713. // if we are over an instance
  4714. if(ins && ins._data && ins._data.dnd) {
  4715. marker.attr('class', 'jstree-' + ins.get_theme());
  4716. data.helper
  4717. .children().attr('class', 'jstree-' + ins.get_theme())
  4718. .find('.jstree-copy:eq(0)')[ data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey))) ? 'show' : 'hide' ]();
  4719. // if are hovering the container itself add a new root node
  4720. if( (data.event.target === ins.element[0] || data.event.target === ins.get_container_ul()[0]) && ins.get_container_ul().children().length === 0) {
  4721. ok = true;
  4722. for(t1 = 0, t2 = data.data.nodes.length; t1 < t2; t1++) {
  4723. ok = ok && ins.check( (data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey)) ) ? "copy_node" : "move_node"), (data.data.origin && data.data.origin !== ins ? data.data.origin.get_node(data.data.nodes[t1]) : data.data.nodes[t1]), '#', 'last', { 'dnd' : true, 'ref' : ins.get_node('#'), 'pos' : 'i', 'is_multi' : (data.data.origin && data.data.origin !== ins), 'is_foreign' : (!data.data.origin) });
  4724. if(!ok) { break; }
  4725. }
  4726. if(ok) {
  4727. lastmv = { 'ins' : ins, 'par' : '#', 'pos' : 'last' };
  4728. marker.hide();
  4729. data.helper.find('.jstree-icon:eq(0)').removeClass('jstree-er').addClass('jstree-ok');
  4730. return;
  4731. }
  4732. }
  4733. else {
  4734. // if we are hovering a tree node
  4735. ref = $(data.event.target).closest('a');
  4736. if(ref && ref.length && ref.parent().is('.jstree-closed, .jstree-open, .jstree-leaf')) {
  4737. off = ref.offset();
  4738. rel = data.event.pageY - off.top;
  4739. h = ref.height();
  4740. if(rel < h / 3) {
  4741. o = ['b', 'i', 'a'];
  4742. }
  4743. else if(rel > h - h / 3) {
  4744. o = ['a', 'i', 'b'];
  4745. }
  4746. else {
  4747. o = rel > h / 2 ? ['i', 'a', 'b'] : ['i', 'b', 'a'];
  4748. }
  4749. $.each(o, function (j, v) {
  4750. switch(v) {
  4751. case 'b':
  4752. l = off.left - 6;
  4753. t = off.top - 5;
  4754. p = ins.get_parent(ref);
  4755. i = ref.parent().index();
  4756. break;
  4757. case 'i':
  4758. l = off.left - 2;
  4759. t = off.top - 5 + h / 2 + 1;
  4760. p = ins.get_node(ref.parent()).id;
  4761. i = 0;
  4762. break;
  4763. case 'a':
  4764. l = off.left - 6;
  4765. t = off.top - 5 + h;
  4766. p = ins.get_parent(ref);
  4767. i = ref.parent().index() + 1;
  4768. break;
  4769. }
  4770. /*!
  4771. // TODO: moving inside, but the node is not yet loaded?
  4772. // the check will work anyway, as when moving the node will be loaded first and checked again
  4773. if(v === 'i' && !ins.is_loaded(p)) { }
  4774. */
  4775. ok = true;
  4776. for(t1 = 0, t2 = data.data.nodes.length; t1 < t2; t1++) {
  4777. op = data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey))) ? "copy_node" : "move_node";
  4778. ps = i;
  4779. if(op === "move_node" && v === 'a' && (data.data.origin && data.data.origin === ins) && p === ins.get_parent(data.data.nodes[t1])) {
  4780. pr = ins.get_node(p);
  4781. if(ps > $.inArray(data.data.nodes[t1], pr.children)) {
  4782. ps -= 1;
  4783. }
  4784. }
  4785. ok = ok && ( (ins && ins.settings && ins.settings.dnd && ins.settings.dnd.check_while_dragging === false) || ins.check(op, (data.data.origin && data.data.origin !== ins ? data.data.origin.get_node(data.data.nodes[t1]) : data.data.nodes[t1]), p, ps, { 'dnd' : true, 'ref' : ins.get_node(ref.parent()), 'pos' : v, 'is_multi' : (data.data.origin && data.data.origin !== ins), 'is_foreign' : (!data.data.origin) }) );
  4786. if(!ok) {
  4787. if(ins && ins.last_error) { laster = ins.last_error(); }
  4788. break;
  4789. }
  4790. }
  4791. if(ok) {
  4792. if(v === 'i' && ref.parent().is('.jstree-closed') && ins.settings.dnd.open_timeout) {
  4793. opento = setTimeout((function (x, z) { return function () { x.open_node(z); }; }(ins, ref)), ins.settings.dnd.open_timeout);
  4794. }
  4795. lastmv = { 'ins' : ins, 'par' : p, 'pos' : i };
  4796. marker.css({ 'left' : l + 'px', 'top' : t + 'px' }).show();
  4797. data.helper.find('.jstree-icon:eq(0)').removeClass('jstree-er').addClass('jstree-ok');
  4798. laster = {};
  4799. o = true;
  4800. return false;
  4801. }
  4802. });
  4803. if(o === true) { return; }
  4804. }
  4805. }
  4806. }
  4807. lastmv = false;
  4808. data.helper.find('.jstree-icon').removeClass('jstree-ok').addClass('jstree-er');
  4809. marker.hide();
  4810. })
  4811. .bind('dnd_scroll.vakata', function (e, data) {
  4812. if(!data.data.jstree) { return; }
  4813. marker.hide();
  4814. lastmv = false;
  4815. data.helper.find('.jstree-icon:eq(0)').removeClass('jstree-ok').addClass('jstree-er');
  4816. })
  4817. .bind('dnd_stop.vakata', function (e, data) {
  4818. if(opento) { clearTimeout(opento); }
  4819. if(!data.data.jstree) { return; }
  4820. marker.hide();
  4821. var i, j, nodes = [];
  4822. if(lastmv) {
  4823. for(i = 0, j = data.data.nodes.length; i < j; i++) {
  4824. nodes[i] = data.data.origin ? data.data.origin.get_node(data.data.nodes[i]) : data.data.nodes[i];
  4825. if(data.data.origin) {
  4826. nodes[i].instance = data.data.origin;
  4827. }
  4828. }
  4829. lastmv.ins[ data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey))) ? 'copy_node' : 'move_node' ](nodes, lastmv.par, lastmv.pos);
  4830. }
  4831. else {
  4832. i = $(data.event.target).closest('.jstree');
  4833. if(i.length && laster && laster.error && laster.error === 'check') {
  4834. i = i.jstree(true);
  4835. if(i) {
  4836. i.settings.core.error.call(this, laster);
  4837. }
  4838. }
  4839. }
  4840. })
  4841. .bind('keyup keydown', function (e, data) {
  4842. data = $.vakata.dnd._get();
  4843. if(data.data && data.data.jstree) {
  4844. data.helper.find('.jstree-copy:eq(0)')[ data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (e.metaKey || e.ctrlKey))) ? 'show' : 'hide' ]();
  4845. }
  4846. });
  4847. });
  4848. // helpers
  4849. (function ($) {
  4850. // private variable
  4851. var vakata_dnd = {
  4852. element : false,
  4853. is_down : false,
  4854. is_drag : false,
  4855. helper : false,
  4856. helper_w: 0,
  4857. data : false,
  4858. init_x : 0,
  4859. init_y : 0,
  4860. scroll_l: 0,
  4861. scroll_t: 0,
  4862. scroll_e: false,
  4863. scroll_i: false
  4864. };
  4865. $.vakata.dnd = {
  4866. settings : {
  4867. scroll_speed : 10,
  4868. scroll_proximity : 20,
  4869. helper_left : 5,
  4870. helper_top : 10,
  4871. threshold : 5
  4872. },
  4873. _trigger : function (event_name, e) {
  4874. var data = $.vakata.dnd._get();
  4875. data.event = e;
  4876. $(document).triggerHandler("dnd_" + event_name + ".vakata", data);
  4877. },
  4878. _get : function () {
  4879. return {
  4880. "data" : vakata_dnd.data,
  4881. "element" : vakata_dnd.element,
  4882. "helper" : vakata_dnd.helper
  4883. };
  4884. },
  4885. _clean : function () {
  4886. if(vakata_dnd.helper) { vakata_dnd.helper.remove(); }
  4887. if(vakata_dnd.scroll_i) { clearInterval(vakata_dnd.scroll_i); vakata_dnd.scroll_i = false; }
  4888. vakata_dnd = {
  4889. element : false,
  4890. is_down : false,
  4891. is_drag : false,
  4892. helper : false,
  4893. helper_w: 0,
  4894. data : false,
  4895. init_x : 0,
  4896. init_y : 0,
  4897. scroll_l: 0,
  4898. scroll_t: 0,
  4899. scroll_e: false,
  4900. scroll_i: false
  4901. };
  4902. $(document).off("mousemove touchmove", $.vakata.dnd.drag);
  4903. $(document).off("mouseup touchend", $.vakata.dnd.stop);
  4904. },
  4905. _scroll : function (init_only) {
  4906. if(!vakata_dnd.scroll_e || (!vakata_dnd.scroll_l && !vakata_dnd.scroll_t)) {
  4907. if(vakata_dnd.scroll_i) { clearInterval(vakata_dnd.scroll_i); vakata_dnd.scroll_i = false; }
  4908. return false;
  4909. }
  4910. if(!vakata_dnd.scroll_i) {
  4911. vakata_dnd.scroll_i = setInterval($.vakata.dnd._scroll, 100);
  4912. return false;
  4913. }
  4914. if(init_only === true) { return false; }
  4915. var i = vakata_dnd.scroll_e.scrollTop(),
  4916. j = vakata_dnd.scroll_e.scrollLeft();
  4917. vakata_dnd.scroll_e.scrollTop(i + vakata_dnd.scroll_t * $.vakata.dnd.settings.scroll_speed);
  4918. vakata_dnd.scroll_e.scrollLeft(j + vakata_dnd.scroll_l * $.vakata.dnd.settings.scroll_speed);
  4919. if(i !== vakata_dnd.scroll_e.scrollTop() || j !== vakata_dnd.scroll_e.scrollLeft()) {
  4920. /**
  4921. * triggered on the document when a drag causes an element to scroll
  4922. * @event
  4923. * @plugin dnd
  4924. * @name dnd_scroll.vakata
  4925. * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
  4926. * @param {DOM} element the DOM element being dragged
  4927. * @param {jQuery} helper the helper shown next to the mouse
  4928. * @param {jQuery} event the element that is scrolling
  4929. */
  4930. $.vakata.dnd._trigger("scroll", vakata_dnd.scroll_e);
  4931. }
  4932. },
  4933. start : function (e, data, html) {
  4934. if(e.type === "touchstart" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
  4935. e.pageX = e.originalEvent.changedTouches[0].pageX;
  4936. e.pageY = e.originalEvent.changedTouches[0].pageY;
  4937. e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
  4938. }
  4939. if(vakata_dnd.is_drag) { $.vakata.dnd.stop({}); }
  4940. try {
  4941. e.currentTarget.unselectable = "on";
  4942. e.currentTarget.onselectstart = function() { return false; };
  4943. if(e.currentTarget.style) { e.currentTarget.style.MozUserSelect = "none"; }
  4944. } catch(ignore) { }
  4945. vakata_dnd.init_x = e.pageX;
  4946. vakata_dnd.init_y = e.pageY;
  4947. vakata_dnd.data = data;
  4948. vakata_dnd.is_down = true;
  4949. vakata_dnd.element = e.currentTarget;
  4950. if(html !== false) {
  4951. vakata_dnd.helper = $("<div id='vakata-dnd'></div>").html(html).css({
  4952. "display" : "block",
  4953. "margin" : "0",
  4954. "padding" : "0",
  4955. "position" : "absolute",
  4956. "top" : "-2000px",
  4957. "lineHeight" : "16px",
  4958. "zIndex" : "10000"
  4959. });
  4960. }
  4961. $(document).bind("mousemove touchmove", $.vakata.dnd.drag);
  4962. $(document).bind("mouseup touchend", $.vakata.dnd.stop);
  4963. return false;
  4964. },
  4965. drag : function (e) {
  4966. if(e.type === "touchmove" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
  4967. e.pageX = e.originalEvent.changedTouches[0].pageX;
  4968. e.pageY = e.originalEvent.changedTouches[0].pageY;
  4969. e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
  4970. }
  4971. if(!vakata_dnd.is_down) { return; }
  4972. if(!vakata_dnd.is_drag) {
  4973. if(
  4974. Math.abs(e.pageX - vakata_dnd.init_x) > $.vakata.dnd.settings.threshold ||
  4975. Math.abs(e.pageY - vakata_dnd.init_y) > $.vakata.dnd.settings.threshold
  4976. ) {
  4977. if(vakata_dnd.helper) {
  4978. vakata_dnd.helper.appendTo("body");
  4979. vakata_dnd.helper_w = vakata_dnd.helper.outerWidth();
  4980. }
  4981. vakata_dnd.is_drag = true;
  4982. /**
  4983. * triggered on the document when a drag starts
  4984. * @event
  4985. * @plugin dnd
  4986. * @name dnd_start.vakata
  4987. * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
  4988. * @param {DOM} element the DOM element being dragged
  4989. * @param {jQuery} helper the helper shown next to the mouse
  4990. * @param {Object} event the event that caused the start (probably mousemove)
  4991. */
  4992. $.vakata.dnd._trigger("start", e);
  4993. }
  4994. else { return; }
  4995. }
  4996. var d = false, w = false,
  4997. dh = false, wh = false,
  4998. dw = false, ww = false,
  4999. dt = false, dl = false,
  5000. ht = false, hl = false;
  5001. vakata_dnd.scroll_t = 0;
  5002. vakata_dnd.scroll_l = 0;
  5003. vakata_dnd.scroll_e = false;
  5004. $($(e.target).parentsUntil("body").addBack().get().reverse())
  5005. .filter(function () {
  5006. return (/^auto|scroll$/).test($(this).css("overflow")) &&
  5007. (this.scrollHeight > this.offsetHeight || this.scrollWidth > this.offsetWidth);
  5008. })
  5009. .each(function () {
  5010. var t = $(this), o = t.offset();
  5011. if(this.scrollHeight > this.offsetHeight) {
  5012. if(o.top + t.height() - e.pageY < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = 1; }
  5013. if(e.pageY - o.top < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = -1; }
  5014. }
  5015. if(this.scrollWidth > this.offsetWidth) {
  5016. if(o.left + t.width() - e.pageX < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = 1; }
  5017. if(e.pageX - o.left < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = -1; }
  5018. }
  5019. if(vakata_dnd.scroll_t || vakata_dnd.scroll_l) {
  5020. vakata_dnd.scroll_e = $(this);
  5021. return false;
  5022. }
  5023. });
  5024. if(!vakata_dnd.scroll_e) {
  5025. d = $(document); w = $(window);
  5026. dh = d.height(); wh = w.height();
  5027. dw = d.width(); ww = w.width();
  5028. dt = d.scrollTop(); dl = d.scrollLeft();
  5029. if(dh > wh && e.pageY - dt < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = -1; }
  5030. if(dh > wh && wh - (e.pageY - dt) < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = 1; }
  5031. if(dw > ww && e.pageX - dl < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = -1; }
  5032. if(dw > ww && ww - (e.pageX - dl) < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = 1; }
  5033. if(vakata_dnd.scroll_t || vakata_dnd.scroll_l) {
  5034. vakata_dnd.scroll_e = d;
  5035. }
  5036. }
  5037. if(vakata_dnd.scroll_e) { $.vakata.dnd._scroll(true); }
  5038. if(vakata_dnd.helper) {
  5039. ht = parseInt(e.pageY + $.vakata.dnd.settings.helper_top, 10);
  5040. hl = parseInt(e.pageX + $.vakata.dnd.settings.helper_left, 10);
  5041. if(dh && ht + 25 > dh) { ht = dh - 50; }
  5042. if(dw && hl + vakata_dnd.helper_w > dw) { hl = dw - (vakata_dnd.helper_w + 2); }
  5043. vakata_dnd.helper.css({
  5044. left : hl + "px",
  5045. top : ht + "px"
  5046. });
  5047. }
  5048. /**
  5049. * triggered on the document when a drag is in progress
  5050. * @event
  5051. * @plugin dnd
  5052. * @name dnd_move.vakata
  5053. * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
  5054. * @param {DOM} element the DOM element being dragged
  5055. * @param {jQuery} helper the helper shown next to the mouse
  5056. * @param {Object} event the event that caused this to trigger (most likely mousemove)
  5057. */
  5058. $.vakata.dnd._trigger("move", e);
  5059. },
  5060. stop : function (e) {
  5061. if(e.type === "touchend" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
  5062. e.pageX = e.originalEvent.changedTouches[0].pageX;
  5063. e.pageY = e.originalEvent.changedTouches[0].pageY;
  5064. e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
  5065. }
  5066. if(vakata_dnd.is_drag) {
  5067. /**
  5068. * triggered on the document when a drag stops (the dragged element is dropped)
  5069. * @event
  5070. * @plugin dnd
  5071. * @name dnd_stop.vakata
  5072. * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
  5073. * @param {DOM} element the DOM element being dragged
  5074. * @param {jQuery} helper the helper shown next to the mouse
  5075. * @param {Object} event the event that caused the stop
  5076. */
  5077. $.vakata.dnd._trigger("stop", e);
  5078. }
  5079. $.vakata.dnd._clean();
  5080. }
  5081. };
  5082. }(jQuery));
  5083. // include the dnd plugin by default
  5084. // $.jstree.defaults.plugins.push("dnd");
  5085. /**
  5086. * ### Search plugin
  5087. *
  5088. * Adds search functionality to jsTree.
  5089. */
  5090. /**
  5091. * stores all defaults for the search plugin
  5092. * @name $.jstree.defaults.search
  5093. * @plugin search
  5094. */
  5095. $.jstree.defaults.search = {
  5096. /**
  5097. * a jQuery-like AJAX config, which jstree uses if a server should be queried for results.
  5098. *
  5099. * A `str` (which is the search string) parameter will be added with the request. The expected result is a JSON array with nodes that need to be opened so that matching nodes will be revealed.
  5100. * Leave this setting as `false` to not query the server. You can also set this to a function, which will be invoked in the instance's scope and receive 2 parameters - the search string and the callback to call with the array of nodes to load.
  5101. * @name $.jstree.defaults.search.ajax
  5102. * @plugin search
  5103. */
  5104. ajax : false,
  5105. /**
  5106. * Indicates if the search should be fuzzy or not (should `chnd3` match `child node 3`). Default is `true`.
  5107. * @name $.jstree.defaults.search.fuzzy
  5108. * @plugin search
  5109. */
  5110. fuzzy : true,
  5111. /**
  5112. * Indicates if the search should be case sensitive. Default is `false`.
  5113. * @name $.jstree.defaults.search.case_sensitive
  5114. * @plugin search
  5115. */
  5116. case_sensitive : false,
  5117. /**
  5118. * Indicates if the tree should be filtered to show only matching nodes (keep in mind this can be a heavy on large trees in old browsers). Default is `false`.
  5119. * @name $.jstree.defaults.search.show_only_matches
  5120. * @plugin search
  5121. */
  5122. show_only_matches : false,
  5123. /**
  5124. * Indicates if all nodes opened to reveal the search result, should be closed when the search is cleared or a new search is performed. Default is `true`.
  5125. * @name $.jstree.defaults.search.close_opened_onclear
  5126. * @plugin search
  5127. */
  5128. close_opened_onclear : true,
  5129. /**
  5130. * Indicates if only leaf nodes should be included in search results. Default is `false`.
  5131. * @name $.jstree.defaults.search.search_leaves_only
  5132. * @plugin search
  5133. */
  5134. search_leaves_only : false
  5135. };
  5136. $.jstree.plugins.search = function (options, parent) {
  5137. this.bind = function () {
  5138. parent.bind.call(this);
  5139. this._data.search.str = "";
  5140. this._data.search.dom = $();
  5141. this._data.search.res = [];
  5142. this._data.search.opn = [];
  5143. this.element.on('before_open.jstree', $.proxy(function (e, data) {
  5144. var i, j, f, r = this._data.search.res, s = [], o = $();
  5145. if(r && r.length) {
  5146. this._data.search.dom = $();
  5147. for(i = 0, j = r.length; i < j; i++) {
  5148. s = s.concat(this.get_node(r[i]).parents);
  5149. f = this.get_node(r[i], true);
  5150. if(f) {
  5151. this._data.search.dom = this._data.search.dom.add(f);
  5152. }
  5153. }
  5154. s = $.vakata.array_unique(s);
  5155. for(i = 0, j = s.length; i < j; i++) {
  5156. if(s[i] === "#") { continue; }
  5157. f = this.get_node(s[i], true);
  5158. if(f) {
  5159. o = o.add(f);
  5160. }
  5161. }
  5162. this._data.search.dom.children(".jstree-anchor").addClass('jstree-search');
  5163. if(this.settings.search.show_only_matches && this._data.search.res.length) {
  5164. this.element.find("li").hide().filter('.jstree-last').filter(function() { return this.nextSibling; }).removeClass('jstree-last');
  5165. o = o.add(this._data.search.dom);
  5166. o.parentsUntil(".jstree").addBack().show()
  5167. .filter("ul").each(function () { $(this).children("li:visible").eq(-1).addClass("jstree-last"); });
  5168. }
  5169. }
  5170. }, this));
  5171. if(this.settings.search.show_only_matches) {
  5172. this.element
  5173. .on("search.jstree", function (e, data) {
  5174. if(data.nodes.length) {
  5175. $(this).find("li").hide().filter('.jstree-last').filter(function() { return this.nextSibling; }).removeClass('jstree-last');
  5176. data.nodes.parentsUntil(".jstree").addBack().show()
  5177. .filter("ul").each(function () { $(this).children("li:visible").eq(-1).addClass("jstree-last"); });
  5178. }
  5179. })
  5180. .on("clear_search.jstree", function (e, data) {
  5181. if(data.nodes.length) {
  5182. $(this).find("li").css("display","").filter('.jstree-last').filter(function() { return this.nextSibling; }).removeClass('jstree-last');
  5183. }
  5184. });
  5185. }
  5186. };
  5187. /**
  5188. * used to search the tree nodes for a given string
  5189. * @name search(str [, skip_async])
  5190. * @param {String} str the search string
  5191. * @param {Boolean} skip_async if set to true server will not be queried even if configured
  5192. * @plugin search
  5193. * @trigger search.jstree
  5194. */
  5195. this.search = function (str, skip_async) {
  5196. if(str === false || $.trim(str) === "") {
  5197. return this.clear_search();
  5198. }
  5199. var s = this.settings.search,
  5200. a = s.ajax ? s.ajax : false,
  5201. f = null,
  5202. r = [],
  5203. p = [], i, j;
  5204. if(this._data.search.res.length) {
  5205. this.clear_search();
  5206. }
  5207. if(!skip_async && a !== false) {
  5208. if($.isFunction(a)) {
  5209. return a.call(this, str, $.proxy(function (d) {
  5210. if(d && d.d) { d = d.d; }
  5211. this._load_nodes(!$.isArray(d) ? [] : d, function () {
  5212. this.search(str, true);
  5213. });
  5214. }, this));
  5215. }
  5216. else {
  5217. a = $.extend({}, a);
  5218. if(!a.data) { a.data = {}; }
  5219. a.data.str = str;
  5220. return $.ajax(a)
  5221. .fail($.proxy(function () {
  5222. this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'search', 'id' : 'search_01', 'reason' : 'Could not load search parents', 'data' : JSON.stringify(a) };
  5223. this.settings.core.error.call(this, this._data.core.last_error);
  5224. }, this))
  5225. .done($.proxy(function (d) {
  5226. if(d && d.d) { d = d.d; }
  5227. this._load_nodes(!$.isArray(d) ? [] : d, function () {
  5228. this.search(str, true);
  5229. });
  5230. }, this));
  5231. }
  5232. }
  5233. this._data.search.str = str;
  5234. this._data.search.dom = $();
  5235. this._data.search.res = [];
  5236. this._data.search.opn = [];
  5237. f = new $.vakata.search(str, true, { caseSensitive : s.case_sensitive, fuzzy : s.fuzzy });
  5238. $.each(this._model.data, function (i, v) {
  5239. if(v.text && f.search(v.text).isMatch && (!s.search_leaves_only || (v.state.loaded && v.children.length === 0)) ) {
  5240. r.push(i);
  5241. p = p.concat(v.parents);
  5242. }
  5243. });
  5244. if(r.length) {
  5245. p = $.vakata.array_unique(p);
  5246. this._search_open(p);
  5247. for(i = 0, j = r.length; i < j; i++) {
  5248. f = this.get_node(r[i], true);
  5249. if(f) {
  5250. this._data.search.dom = this._data.search.dom.add(f);
  5251. }
  5252. }
  5253. this._data.search.res = r;
  5254. this._data.search.dom.children(".jstree-anchor").addClass('jstree-search');
  5255. }
  5256. /**
  5257. * triggered after search is complete
  5258. * @event
  5259. * @name search.jstree
  5260. * @param {jQuery} nodes a jQuery collection of matching nodes
  5261. * @param {String} str the search string
  5262. * @param {Array} res a collection of objects represeing the matching nodes
  5263. * @plugin search
  5264. */
  5265. this.trigger('search', { nodes : this._data.search.dom, str : str, res : this._data.search.res });
  5266. };
  5267. /**
  5268. * used to clear the last search (removes classes and shows all nodes if filtering is on)
  5269. * @name clear_search()
  5270. * @plugin search
  5271. * @trigger clear_search.jstree
  5272. */
  5273. this.clear_search = function () {
  5274. this._data.search.dom.children(".jstree-anchor").removeClass("jstree-search");
  5275. if(this.settings.search.close_opened_onclear) {
  5276. this.close_node(this._data.search.opn, 0);
  5277. }
  5278. /**
  5279. * triggered after search is complete
  5280. * @event
  5281. * @name clear_search.jstree
  5282. * @param {jQuery} nodes a jQuery collection of matching nodes (the result from the last search)
  5283. * @param {String} str the search string (the last search string)
  5284. * @param {Array} res a collection of objects represeing the matching nodes (the result from the last search)
  5285. * @plugin search
  5286. */
  5287. this.trigger('clear_search', { 'nodes' : this._data.search.dom, str : this._data.search.str, res : this._data.search.res });
  5288. this._data.search.str = "";
  5289. this._data.search.res = [];
  5290. this._data.search.opn = [];
  5291. this._data.search.dom = $();
  5292. };
  5293. /**
  5294. * opens nodes that need to be opened to reveal the search results. Used only internally.
  5295. * @private
  5296. * @name _search_open(d)
  5297. * @param {Array} d an array of node IDs
  5298. * @plugin search
  5299. */
  5300. this._search_open = function (d) {
  5301. var t = this;
  5302. $.each(d.concat([]), function (i, v) {
  5303. if(v === "#") { return true; }
  5304. try { v = $('#' + v.replace($.jstree.idregex,'\\$&'), t.element); } catch(ignore) { }
  5305. if(v && v.length) {
  5306. if(t.is_closed(v)) {
  5307. t._data.search.opn.push(v[0].id);
  5308. t.open_node(v, function () { t._search_open(d); }, 0);
  5309. }
  5310. }
  5311. });
  5312. };
  5313. };
  5314. // helpers
  5315. (function ($) {
  5316. // from http://kiro.me/projects/fuse.html
  5317. $.vakata.search = function(pattern, txt, options) {
  5318. options = options || {};
  5319. if(options.fuzzy !== false) {
  5320. options.fuzzy = true;
  5321. }
  5322. pattern = options.caseSensitive ? pattern : pattern.toLowerCase();
  5323. var MATCH_LOCATION = options.location || 0,
  5324. MATCH_DISTANCE = options.distance || 100,
  5325. MATCH_THRESHOLD = options.threshold || 0.6,
  5326. patternLen = pattern.length,
  5327. matchmask, pattern_alphabet, match_bitapScore, search;
  5328. if(patternLen > 32) {
  5329. options.fuzzy = false;
  5330. }
  5331. if(options.fuzzy) {
  5332. matchmask = 1 << (patternLen - 1);
  5333. pattern_alphabet = (function () {
  5334. var mask = {},
  5335. i = 0;
  5336. for (i = 0; i < patternLen; i++) {
  5337. mask[pattern.charAt(i)] = 0;
  5338. }
  5339. for (i = 0; i < patternLen; i++) {
  5340. mask[pattern.charAt(i)] |= 1 << (patternLen - i - 1);
  5341. }
  5342. return mask;
  5343. }());
  5344. match_bitapScore = function (e, x) {
  5345. var accuracy = e / patternLen,
  5346. proximity = Math.abs(MATCH_LOCATION - x);
  5347. if(!MATCH_DISTANCE) {
  5348. return proximity ? 1.0 : accuracy;
  5349. }
  5350. return accuracy + (proximity / MATCH_DISTANCE);
  5351. };
  5352. }
  5353. search = function (text) {
  5354. text = options.caseSensitive ? text : text.toLowerCase();
  5355. if(pattern === text || text.indexOf(pattern) !== -1) {
  5356. return {
  5357. isMatch: true,
  5358. score: 0
  5359. };
  5360. }
  5361. if(!options.fuzzy) {
  5362. return {
  5363. isMatch: false,
  5364. score: 1
  5365. };
  5366. }
  5367. var i, j,
  5368. textLen = text.length,
  5369. scoreThreshold = MATCH_THRESHOLD,
  5370. bestLoc = text.indexOf(pattern, MATCH_LOCATION),
  5371. binMin, binMid,
  5372. binMax = patternLen + textLen,
  5373. lastRd, start, finish, rd, charMatch,
  5374. score = 1,
  5375. locations = [];
  5376. if (bestLoc !== -1) {
  5377. scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold);
  5378. bestLoc = text.lastIndexOf(pattern, MATCH_LOCATION + patternLen);
  5379. if (bestLoc !== -1) {
  5380. scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold);
  5381. }
  5382. }
  5383. bestLoc = -1;
  5384. for (i = 0; i < patternLen; i++) {
  5385. binMin = 0;
  5386. binMid = binMax;
  5387. while (binMin < binMid) {
  5388. if (match_bitapScore(i, MATCH_LOCATION + binMid) <= scoreThreshold) {
  5389. binMin = binMid;
  5390. } else {
  5391. binMax = binMid;
  5392. }
  5393. binMid = Math.floor((binMax - binMin) / 2 + binMin);
  5394. }
  5395. binMax = binMid;
  5396. start = Math.max(1, MATCH_LOCATION - binMid + 1);
  5397. finish = Math.min(MATCH_LOCATION + binMid, textLen) + patternLen;
  5398. rd = new Array(finish + 2);
  5399. rd[finish + 1] = (1 << i) - 1;
  5400. for (j = finish; j >= start; j--) {
  5401. charMatch = pattern_alphabet[text.charAt(j - 1)];
  5402. if (i === 0) {
  5403. rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;
  5404. } else {
  5405. rd[j] = ((rd[j + 1] << 1) | 1) & charMatch | (((lastRd[j + 1] | lastRd[j]) << 1) | 1) | lastRd[j + 1];
  5406. }
  5407. if (rd[j] & matchmask) {
  5408. score = match_bitapScore(i, j - 1);
  5409. if (score <= scoreThreshold) {
  5410. scoreThreshold = score;
  5411. bestLoc = j - 1;
  5412. locations.push(bestLoc);
  5413. if (bestLoc > MATCH_LOCATION) {
  5414. start = Math.max(1, 2 * MATCH_LOCATION - bestLoc);
  5415. } else {
  5416. break;
  5417. }
  5418. }
  5419. }
  5420. }
  5421. if (match_bitapScore(i + 1, MATCH_LOCATION) > scoreThreshold) {
  5422. break;
  5423. }
  5424. lastRd = rd;
  5425. }
  5426. return {
  5427. isMatch: bestLoc >= 0,
  5428. score: score
  5429. };
  5430. };
  5431. return txt === true ? { 'search' : search } : search(txt);
  5432. };
  5433. }(jQuery));
  5434. // include the search plugin by default
  5435. // $.jstree.defaults.plugins.push("search");
  5436. /**
  5437. * ### Sort plugin
  5438. *
  5439. * Autmatically sorts all siblings in the tree according to a sorting function.
  5440. */
  5441. /**
  5442. * the settings function used to sort the nodes.
  5443. * It is executed in the tree's context, accepts two nodes as arguments and should return `1` or `-1`.
  5444. * @name $.jstree.defaults.sort
  5445. * @plugin sort
  5446. */
  5447. $.jstree.defaults.sort = function (a, b) {
  5448. //return this.get_type(a) === this.get_type(b) ? (this.get_text(a) > this.get_text(b) ? 1 : -1) : this.get_type(a) >= this.get_type(b);
  5449. return this.get_text(a) > this.get_text(b) ? 1 : -1;
  5450. };
  5451. $.jstree.plugins.sort = function (options, parent) {
  5452. this.bind = function () {
  5453. parent.bind.call(this);
  5454. this.element
  5455. .on("model.jstree", $.proxy(function (e, data) {
  5456. this.sort(data.parent, true);
  5457. }, this))
  5458. .on("rename_node.jstree create_node.jstree", $.proxy(function (e, data) {
  5459. this.sort(data.parent || data.node.parent, false);
  5460. this.redraw_node(data.parent || data.node.parent, true);
  5461. }, this))
  5462. .on("move_node.jstree copy_node.jstree", $.proxy(function (e, data) {
  5463. this.sort(data.parent, false);
  5464. this.redraw_node(data.parent, true);
  5465. }, this));
  5466. };
  5467. /**
  5468. * used to sort a node's children
  5469. * @private
  5470. * @name sort(obj [, deep])
  5471. * @param {mixed} obj the node
  5472. * @param {Boolean} deep if set to `true` nodes are sorted recursively.
  5473. * @plugin sort
  5474. * @trigger search.jstree
  5475. */
  5476. this.sort = function (obj, deep) {
  5477. var i, j;
  5478. obj = this.get_node(obj);
  5479. if(obj && obj.children && obj.children.length) {
  5480. obj.children.sort($.proxy(this.settings.sort, this));
  5481. if(deep) {
  5482. for(i = 0, j = obj.children_d.length; i < j; i++) {
  5483. this.sort(obj.children_d[i], false);
  5484. }
  5485. }
  5486. }
  5487. };
  5488. };
  5489. // include the sort plugin by default
  5490. // $.jstree.defaults.plugins.push("sort");
  5491. /**
  5492. * ### State plugin
  5493. *
  5494. * Saves the state of the tree (selected nodes, opened nodes) on the user's computer using available options (localStorage, cookies, etc)
  5495. */
  5496. var to = false;
  5497. /**
  5498. * stores all defaults for the state plugin
  5499. * @name $.jstree.defaults.state
  5500. * @plugin state
  5501. */
  5502. $.jstree.defaults.state = {
  5503. /**
  5504. * A string for the key to use when saving the current tree (change if using multiple trees in your project). Defaults to `jstree`.
  5505. * @name $.jstree.defaults.state.key
  5506. * @plugin state
  5507. */
  5508. key : 'jstree',
  5509. /**
  5510. * A space separated list of events that trigger a state save. Defaults to `changed.jstree open_node.jstree close_node.jstree`.
  5511. * @name $.jstree.defaults.state.events
  5512. * @plugin state
  5513. */
  5514. events : 'changed.jstree open_node.jstree close_node.jstree',
  5515. /**
  5516. * Time in milliseconds after which the state will expire. Defaults to 'false' meaning - no expire.
  5517. * @name $.jstree.defaults.state.ttl
  5518. * @plugin state
  5519. */
  5520. ttl : false,
  5521. /**
  5522. * A function that will be executed prior to restoring state with one argument - the state object. Can be used to clear unwanted parts of the state.
  5523. * @name $.jstree.defaults.state.filter
  5524. * @plugin state
  5525. */
  5526. filter : false
  5527. };
  5528. $.jstree.plugins.state = function (options, parent) {
  5529. this.bind = function () {
  5530. parent.bind.call(this);
  5531. var bind = $.proxy(function () {
  5532. this.element.on(this.settings.state.events, $.proxy(function () {
  5533. if(to) { clearTimeout(to); }
  5534. to = setTimeout($.proxy(function () { this.save_state(); }, this), 100);
  5535. }, this));
  5536. }, this);
  5537. this.element
  5538. .on("ready.jstree", $.proxy(function (e, data) {
  5539. this.element.one("restore_state.jstree", bind);
  5540. if(!this.restore_state()) { bind(); }
  5541. }, this));
  5542. };
  5543. /**
  5544. * save the state
  5545. * @name save_state()
  5546. * @plugin state
  5547. */
  5548. this.save_state = function () {
  5549. var st = { 'state' : this.get_state(), 'ttl' : this.settings.state.ttl, 'sec' : +(new Date()) };
  5550. $.vakata.storage.set(this.settings.state.key, JSON.stringify(st));
  5551. };
  5552. /**
  5553. * restore the state from the user's computer
  5554. * @name restore_state()
  5555. * @plugin state
  5556. */
  5557. this.restore_state = function () {
  5558. var k = $.vakata.storage.get(this.settings.state.key);
  5559. if(!!k) { try { k = JSON.parse(k); } catch(ex) { return false; } }
  5560. if(!!k && k.ttl && k.sec && +(new Date()) - k.sec > k.ttl) { return false; }
  5561. if(!!k && k.state) { k = k.state; }
  5562. if(!!k && $.isFunction(this.settings.state.filter)) { k = this.settings.state.filter.call(this, k); }
  5563. if(!!k) {
  5564. this.element.one("set_state.jstree", function (e, data) { data.instance.trigger('restore_state', { 'state' : $.extend(true, {}, k) }); });
  5565. this.set_state(k);
  5566. return true;
  5567. }
  5568. return false;
  5569. };
  5570. /**
  5571. * clear the state on the user's computer
  5572. * @name clear_state()
  5573. * @plugin state
  5574. */
  5575. this.clear_state = function () {
  5576. return $.vakata.storage.del(this.settings.state.key);
  5577. };
  5578. };
  5579. (function ($, undefined) {
  5580. $.vakata.storage = {
  5581. // simply specifying the functions in FF throws an error
  5582. set : function (key, val) { return window.localStorage.setItem(key, val); },
  5583. get : function (key) { return window.localStorage.getItem(key); },
  5584. del : function (key) { return window.localStorage.removeItem(key); }
  5585. };
  5586. }(jQuery));
  5587. // include the state plugin by default
  5588. // $.jstree.defaults.plugins.push("state");
  5589. /**
  5590. * ### Types plugin
  5591. *
  5592. * Makes it possible to add predefined types for groups of nodes, which make it possible to easily control nesting rules and icon for each group.
  5593. */
  5594. /**
  5595. * An object storing all types as key value pairs, where the key is the type name and the value is an object that could contain following keys (all optional).
  5596. *
  5597. * * `max_children` the maximum number of immediate children this node type can have. Do not specify or set to `-1` for unlimited.
  5598. * * `max_depth` the maximum number of nesting this node type can have. A value of `1` would mean that the node can have children, but no grandchildren. Do not specify or set to `-1` for unlimited.
  5599. * * `valid_children` an array of node type strings, that nodes of this type can have as children. Do not specify or set to `-1` for no limits.
  5600. * * `icon` a string - can be a path to an icon or a className, if using an image that is in the current directory use a `./` prefix, otherwise it will be detected as a class. Omit to use the default icon from your theme.
  5601. *
  5602. * There are two predefined types:
  5603. *
  5604. * * `#` represents the root of the tree, for example `max_children` would control the maximum number of root nodes.
  5605. * * `default` represents the default node - any settings here will be applied to all nodes that do not have a type specified.
  5606. *
  5607. * @name $.jstree.defaults.types
  5608. * @plugin types
  5609. */
  5610. $.jstree.defaults.types = {
  5611. '#' : {},
  5612. 'default' : {}
  5613. };
  5614. $.jstree.plugins.types = function (options, parent) {
  5615. this.init = function (el, options) {
  5616. var i, j;
  5617. if(options && options.types && options.types['default']) {
  5618. for(i in options.types) {
  5619. if(i !== "default" && i !== "#" && options.types.hasOwnProperty(i)) {
  5620. for(j in options.types['default']) {
  5621. if(options.types['default'].hasOwnProperty(j) && options.types[i][j] === undefined) {
  5622. options.types[i][j] = options.types['default'][j];
  5623. }
  5624. }
  5625. }
  5626. }
  5627. }
  5628. parent.init.call(this, el, options);
  5629. this._model.data['#'].type = '#';
  5630. };
  5631. this.refresh = function (skip_loading) {
  5632. parent.refresh.call(this, skip_loading);
  5633. this._model.data['#'].type = '#';
  5634. };
  5635. this.bind = function () {
  5636. this.element
  5637. .on('model.jstree', $.proxy(function (e, data) {
  5638. var m = this._model.data,
  5639. dpc = data.nodes,
  5640. t = this.settings.types,
  5641. i, j, c = 'default';
  5642. for(i = 0, j = dpc.length; i < j; i++) {
  5643. c = 'default';
  5644. if(m[dpc[i]].original && m[dpc[i]].original.type && t[m[dpc[i]].original.type]) {
  5645. c = m[dpc[i]].original.type;
  5646. }
  5647. if(m[dpc[i]].data && m[dpc[i]].data.jstree && m[dpc[i]].data.jstree.type && t[m[dpc[i]].data.jstree.type]) {
  5648. c = m[dpc[i]].data.jstree.type;
  5649. }
  5650. m[dpc[i]].type = c;
  5651. if(m[dpc[i]].icon === true && t[c].icon !== undefined) {
  5652. m[dpc[i]].icon = t[c].icon;
  5653. }
  5654. }
  5655. }, this));
  5656. parent.bind.call(this);
  5657. };
  5658. this.get_json = function (obj, options, flat) {
  5659. var i, j,
  5660. m = this._model.data,
  5661. opt = options ? $.extend(true, {}, options, {no_id:false}) : {},
  5662. tmp = parent.get_json.call(this, obj, opt, flat);
  5663. if(tmp === false) { return false; }
  5664. if($.isArray(tmp)) {
  5665. for(i = 0, j = tmp.length; i < j; i++) {
  5666. tmp[i].type = tmp[i].id && m[tmp[i].id] && m[tmp[i].id].type ? m[tmp[i].id].type : "default";
  5667. if(options && options.no_id) {
  5668. delete tmp[i].id;
  5669. if(tmp[i].li_attr && tmp[i].li_attr.id) {
  5670. delete tmp[i].li_attr.id;
  5671. }
  5672. }
  5673. }
  5674. }
  5675. else {
  5676. tmp.type = tmp.id && m[tmp.id] && m[tmp.id].type ? m[tmp.id].type : "default";
  5677. if(options && options.no_id) {
  5678. tmp = this._delete_ids(tmp);
  5679. }
  5680. }
  5681. return tmp;
  5682. };
  5683. this._delete_ids = function (tmp) {
  5684. if($.isArray(tmp)) {
  5685. for(var i = 0, j = tmp.length; i < j; i++) {
  5686. tmp[i] = this._delete_ids(tmp[i]);
  5687. }
  5688. return tmp;
  5689. }
  5690. delete tmp.id;
  5691. if(tmp.li_attr && tmp.li_attr.id) {
  5692. delete tmp.li_attr.id;
  5693. }
  5694. if(tmp.children && $.isArray(tmp.children)) {
  5695. tmp.children = this._delete_ids(tmp.children);
  5696. }
  5697. return tmp;
  5698. };
  5699. this.check = function (chk, obj, par, pos, more) {
  5700. if(parent.check.call(this, chk, obj, par, pos, more) === false) { return false; }
  5701. obj = obj && obj.id ? obj : this.get_node(obj);
  5702. par = par && par.id ? par : this.get_node(par);
  5703. var m = obj && obj.id ? $.jstree.reference(obj.id) : null, tmp, d, i, j;
  5704. m = m && m._model && m._model.data ? m._model.data : null;
  5705. switch(chk) {
  5706. case "create_node":
  5707. case "move_node":
  5708. case "copy_node":
  5709. if(chk !== 'move_node' || $.inArray(obj.id, par.children) === -1) {
  5710. tmp = this.get_rules(par);
  5711. if(tmp.max_children !== undefined && tmp.max_children !== -1 && tmp.max_children === par.children.length) {
  5712. this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_01', 'reason' : 'max_children prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
  5713. return false;
  5714. }
  5715. if(tmp.valid_children !== undefined && tmp.valid_children !== -1 && $.inArray(obj.type, tmp.valid_children) === -1) {
  5716. this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_02', 'reason' : 'valid_children prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
  5717. return false;
  5718. }
  5719. if(m && obj.children_d && obj.parents) {
  5720. d = 0;
  5721. for(i = 0, j = obj.children_d.length; i < j; i++) {
  5722. d = Math.max(d, m[obj.children_d[i]].parents.length);
  5723. }
  5724. d = d - obj.parents.length + 1;
  5725. }
  5726. if(d <= 0 || d === undefined) { d = 1; }
  5727. do {
  5728. if(tmp.max_depth !== undefined && tmp.max_depth !== -1 && tmp.max_depth < d) {
  5729. this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_03', 'reason' : 'max_depth prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
  5730. return false;
  5731. }
  5732. par = this.get_node(par.parent);
  5733. tmp = this.get_rules(par);
  5734. d++;
  5735. } while(par);
  5736. }
  5737. break;
  5738. }
  5739. return true;
  5740. };
  5741. /**
  5742. * used to retrieve the type settings object for a node
  5743. * @name get_rules(obj)
  5744. * @param {mixed} obj the node to find the rules for
  5745. * @return {Object}
  5746. * @plugin types
  5747. */
  5748. this.get_rules = function (obj) {
  5749. obj = this.get_node(obj);
  5750. if(!obj) { return false; }
  5751. var tmp = this.get_type(obj, true);
  5752. if(tmp.max_depth === undefined) { tmp.max_depth = -1; }
  5753. if(tmp.max_children === undefined) { tmp.max_children = -1; }
  5754. if(tmp.valid_children === undefined) { tmp.valid_children = -1; }
  5755. return tmp;
  5756. };
  5757. /**
  5758. * used to retrieve the type string or settings object for a node
  5759. * @name get_type(obj [, rules])
  5760. * @param {mixed} obj the node to find the rules for
  5761. * @param {Boolean} rules if set to `true` instead of a string the settings object will be returned
  5762. * @return {String|Object}
  5763. * @plugin types
  5764. */
  5765. this.get_type = function (obj, rules) {
  5766. obj = this.get_node(obj);
  5767. return (!obj) ? false : ( rules ? $.extend({ 'type' : obj.type }, this.settings.types[obj.type]) : obj.type);
  5768. };
  5769. /**
  5770. * used to change a node's type
  5771. * @name set_type(obj, type)
  5772. * @param {mixed} obj the node to change
  5773. * @param {String} type the new type
  5774. * @plugin types
  5775. */
  5776. this.set_type = function (obj, type) {
  5777. var t, t1, t2, old_type, old_icon;
  5778. if($.isArray(obj)) {
  5779. obj = obj.slice();
  5780. for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
  5781. this.set_type(obj[t1], type);
  5782. }
  5783. return true;
  5784. }
  5785. t = this.settings.types;
  5786. obj = this.get_node(obj);
  5787. if(!t[type] || !obj) { return false; }
  5788. old_type = obj.type;
  5789. old_icon = this.get_icon(obj);
  5790. obj.type = type;
  5791. if(old_icon === true || (t[old_type] && t[old_type].icon && old_icon === t[old_type].icon)) {
  5792. this.set_icon(obj, t[type].icon !== undefined ? t[type].icon : true);
  5793. }
  5794. return true;
  5795. };
  5796. };
  5797. // include the types plugin by default
  5798. // $.jstree.defaults.plugins.push("types");
  5799. /**
  5800. * ### Unique plugin
  5801. *
  5802. * Enforces that no nodes with the same name can coexist as siblings.
  5803. */
  5804. $.jstree.plugins.unique = function (options, parent) {
  5805. this.check = function (chk, obj, par, pos, more) {
  5806. if(parent.check.call(this, chk, obj, par, pos, more) === false) { return false; }
  5807. obj = obj && obj.id ? obj : this.get_node(obj);
  5808. par = par && par.id ? par : this.get_node(par);
  5809. if(!par || !par.children) { return true; }
  5810. var n = chk === "rename_node" ? pos : obj.text,
  5811. c = [],
  5812. m = this._model.data, i, j;
  5813. for(i = 0, j = par.children.length; i < j; i++) {
  5814. c.push(m[par.children[i]].text);
  5815. }
  5816. switch(chk) {
  5817. case "delete_node":
  5818. return true;
  5819. case "rename_node":
  5820. case "copy_node":
  5821. i = ($.inArray(n, c) === -1);
  5822. if(!i) {
  5823. this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_01', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
  5824. }
  5825. return i;
  5826. case "move_node":
  5827. i = (obj.parent === par.id || $.inArray(n, c) === -1);
  5828. if(!i) {
  5829. this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_01', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
  5830. }
  5831. return i;
  5832. }
  5833. return true;
  5834. };
  5835. };
  5836. // include the unique plugin by default
  5837. // $.jstree.defaults.plugins.push("unique");
  5838. /**
  5839. * ### Wholerow plugin
  5840. *
  5841. * Makes each node appear block level. Making selection easier. May cause slow down for large trees in old browsers.
  5842. */
  5843. var div = document.createElement('DIV');
  5844. div.setAttribute('unselectable','on');
  5845. div.className = 'jstree-wholerow';
  5846. div.innerHTML = '&#160;';
  5847. $.jstree.plugins.wholerow = function (options, parent) {
  5848. this.bind = function () {
  5849. parent.bind.call(this);
  5850. this.element
  5851. .on('loading', $.proxy(function () {
  5852. div.style.height = this._data.core.li_height + 'px';
  5853. }, this))
  5854. .on('ready.jstree set_state.jstree', $.proxy(function () {
  5855. this.hide_dots();
  5856. }, this))
  5857. .on("ready.jstree", $.proxy(function () {
  5858. this.get_container_ul().addClass('jstree-wholerow-ul');
  5859. }, this))
  5860. .on("deselect_all.jstree", $.proxy(function (e, data) {
  5861. this.element.find('.jstree-wholerow-clicked').removeClass('jstree-wholerow-clicked');
  5862. }, this))
  5863. .on("changed.jstree", $.proxy(function (e, data) {
  5864. this.element.find('.jstree-wholerow-clicked').removeClass('jstree-wholerow-clicked');
  5865. var tmp = false, i, j;
  5866. for(i = 0, j = data.selected.length; i < j; i++) {
  5867. tmp = this.get_node(data.selected[i], true);
  5868. if(tmp && tmp.length) {
  5869. tmp.children('.jstree-wholerow').addClass('jstree-wholerow-clicked');
  5870. }
  5871. }
  5872. }, this))
  5873. .on("open_node.jstree", $.proxy(function (e, data) {
  5874. this.get_node(data.node, true).find('.jstree-clicked').parent().children('.jstree-wholerow').addClass('jstree-wholerow-clicked');
  5875. }, this))
  5876. .on("hover_node.jstree dehover_node.jstree", $.proxy(function (e, data) {
  5877. this.get_node(data.node, true).children('.jstree-wholerow')[e.type === "hover_node"?"addClass":"removeClass"]('jstree-wholerow-hovered');
  5878. }, this))
  5879. .on("contextmenu.jstree", ".jstree-wholerow", $.proxy(function (e) {
  5880. e.preventDefault();
  5881. var tmp = $.Event('contextmenu', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey, pageX : e.pageX, pageY : e.pageY });
  5882. $(e.currentTarget).closest("li").children("a:eq(0)").trigger(tmp);
  5883. }, this))
  5884. .on("click.jstree", ".jstree-wholerow", function (e) {
  5885. e.stopImmediatePropagation();
  5886. var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
  5887. $(e.currentTarget).closest("li").children("a:eq(0)").trigger(tmp).focus();
  5888. })
  5889. .on("click.jstree", ".jstree-leaf > .jstree-ocl", $.proxy(function (e) {
  5890. e.stopImmediatePropagation();
  5891. var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
  5892. $(e.currentTarget).closest("li").children("a:eq(0)").trigger(tmp).focus();
  5893. }, this))
  5894. .on("mouseover.jstree", ".jstree-wholerow, .jstree-icon", $.proxy(function (e) {
  5895. e.stopImmediatePropagation();
  5896. this.hover_node(e.currentTarget);
  5897. return false;
  5898. }, this))
  5899. .on("mouseleave.jstree", ".jstree-node", $.proxy(function (e) {
  5900. this.dehover_node(e.currentTarget);
  5901. }, this));
  5902. };
  5903. this.teardown = function () {
  5904. if(this.settings.wholerow) {
  5905. this.element.find(".jstree-wholerow").remove();
  5906. }
  5907. parent.teardown.call(this);
  5908. };
  5909. this.redraw_node = function(obj, deep, callback) {
  5910. obj = parent.redraw_node.call(this, obj, deep, callback);
  5911. if(obj) {
  5912. var tmp = div.cloneNode(true);
  5913. //tmp.style.height = this._data.core.li_height + 'px';
  5914. if($.inArray(obj.id, this._data.core.selected) !== -1) { tmp.className += ' jstree-wholerow-clicked'; }
  5915. obj.insertBefore(tmp, obj.childNodes[0]);
  5916. }
  5917. return obj;
  5918. };
  5919. };
  5920. // include the wholerow plugin by default
  5921. // $.jstree.defaults.plugins.push("wholerow");
  5922. }));