ux-debug.js 493 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562
  1. Ext.define(null, {
  2. override: 'Ext.ux.gauge.needle.Abstract',
  3. compatibility: Ext.isIE10p,
  4. setTransform: function(centerX, centerY, rotation) {
  5. var needleGroup = this.getNeedleGroup();
  6. this.callParent([
  7. centerX,
  8. centerY,
  9. rotation
  10. ]);
  11. needleGroup.set({
  12. transform: getComputedStyle(needleGroup.dom).getPropertyValue('transform')
  13. });
  14. },
  15. updateStyle: function(style) {
  16. var pathElement;
  17. this.callParent([
  18. style
  19. ]);
  20. if (Ext.isObject(style) && 'transform' in style) {
  21. pathElement = this.getNeedlePath();
  22. pathElement.set({
  23. transform: getComputedStyle(pathElement.dom).getPropertyValue('transform')
  24. });
  25. }
  26. }
  27. });
  28. /**
  29. * This is a base class for more advanced "simlets" (simulated servers). A simlet is asked
  30. * to provide a response given a {@link Ext.ux.ajax.SimXhr} instance.
  31. */
  32. Ext.define('Ext.ux.ajax.Simlet', function() {
  33. var urlRegex = /([^?#]*)(#.*)?$/,
  34. dateRegex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/,
  35. intRegex = /^[+-]?\d+$/,
  36. floatRegex = /^[+-]?\d+\.\d+$/;
  37. function parseParamValue(value) {
  38. var m;
  39. if (Ext.isDefined(value)) {
  40. value = decodeURIComponent(value);
  41. if (intRegex.test(value)) {
  42. value = parseInt(value, 10);
  43. } else if (floatRegex.test(value)) {
  44. value = parseFloat(value);
  45. } else if (!!(m = dateRegex.exec(value))) {
  46. value = new Date(Date.UTC(+m[1], +m[2] - 1, +m[3], +m[4], +m[5], +m[6]));
  47. }
  48. }
  49. return value;
  50. }
  51. return {
  52. alias: 'simlet.basic',
  53. isSimlet: true,
  54. responseProps: [
  55. 'responseText',
  56. 'responseXML',
  57. 'status',
  58. 'statusText',
  59. 'responseHeaders'
  60. ],
  61. /**
  62. * @cfg {String/Function} responseText
  63. */
  64. /**
  65. * @cfg {String/Function} responseXML
  66. */
  67. /**
  68. * @cfg {Object/Function} responseHeaders
  69. */
  70. /**
  71. * @cfg {Number/Function} status
  72. */
  73. status: 200,
  74. /**
  75. * @cfg {String/Function} statusText
  76. */
  77. statusText: 'OK',
  78. constructor: function(config) {
  79. Ext.apply(this, config);
  80. },
  81. doGet: function(ctx) {
  82. return this.handleRequest(ctx);
  83. },
  84. doPost: function(ctx) {
  85. return this.handleRequest(ctx);
  86. },
  87. doRedirect: function(ctx) {
  88. return false;
  89. },
  90. doDelete: function(ctx) {
  91. var me = this,
  92. xhr = ctx.xhr,
  93. records = xhr.options.records;
  94. me.removeFromData(ctx, records);
  95. },
  96. /**
  97. * Performs the action requested by the given XHR and returns an object to be applied
  98. * on to the XHR (containing `status`, `responseText`, etc.). For the most part,
  99. * this is delegated to `doMethod` methods on this class, such as `doGet`.
  100. *
  101. * @param {Ext.ux.ajax.SimXhr} xhr The simulated XMLHttpRequest instance.
  102. * @return {Object} The response properties to add to the XMLHttpRequest.
  103. */
  104. exec: function(xhr) {
  105. var me = this,
  106. ret = {},
  107. method = 'do' + Ext.String.capitalize(xhr.method.toLowerCase()),
  108. // doGet
  109. fn = me[method];
  110. if (fn) {
  111. ret = fn.call(me, me.getCtx(xhr.method, xhr.url, xhr));
  112. } else {
  113. ret = {
  114. status: 405,
  115. statusText: 'Method Not Allowed'
  116. };
  117. }
  118. return ret;
  119. },
  120. getCtx: function(method, url, xhr) {
  121. return {
  122. method: method,
  123. params: this.parseQueryString(url),
  124. url: url,
  125. xhr: xhr
  126. };
  127. },
  128. handleRequest: function(ctx) {
  129. var me = this,
  130. ret = {},
  131. val;
  132. Ext.Array.forEach(me.responseProps, function(prop) {
  133. if (prop in me) {
  134. val = me[prop];
  135. if (Ext.isFunction(val)) {
  136. val = val.call(me, ctx);
  137. }
  138. ret[prop] = val;
  139. }
  140. });
  141. return ret;
  142. },
  143. openRequest: function(method, url, options, async) {
  144. var ctx = this.getCtx(method, url),
  145. redirect = this.doRedirect(ctx),
  146. xhr;
  147. if (options.action === 'destroy') {
  148. method = 'delete';
  149. }
  150. if (redirect) {
  151. xhr = redirect;
  152. } else {
  153. xhr = new Ext.ux.ajax.SimXhr({
  154. mgr: this.manager,
  155. simlet: this,
  156. options: options
  157. });
  158. xhr.open(method, url, async);
  159. }
  160. return xhr;
  161. },
  162. parseQueryString: function(str) {
  163. var m = urlRegex.exec(str),
  164. ret = {},
  165. key, value, i, n;
  166. if (m && m[1]) {
  167. var pair,
  168. parts = m[1].split('&');
  169. for (i = 0 , n = parts.length; i < n; ++i) {
  170. if ((pair = parts[i].split('='))[0]) {
  171. key = decodeURIComponent(pair.shift());
  172. value = parseParamValue((pair.length > 1) ? pair.join('=') : pair[0]);
  173. if (!(key in ret)) {
  174. ret[key] = value;
  175. } else if (Ext.isArray(ret[key])) {
  176. ret[key].push(value);
  177. } else {
  178. ret[key] = [
  179. ret[key],
  180. value
  181. ];
  182. }
  183. }
  184. }
  185. }
  186. return ret;
  187. },
  188. redirect: function(method, url, params) {
  189. switch (arguments.length) {
  190. case 2:
  191. if (typeof url == 'string') {
  192. break;
  193. };
  194. params = url;
  195. // fall...
  196. case 1:
  197. url = method;
  198. method = 'GET';
  199. break;
  200. }
  201. if (params) {
  202. url = Ext.urlAppend(url, Ext.Object.toQueryString(params));
  203. }
  204. return this.manager.openRequest(method, url);
  205. },
  206. removeFromData: function(ctx, records) {
  207. var me = this,
  208. data = me.getData(ctx),
  209. model = (ctx.xhr.options.proxy && ctx.xhr.options.proxy.getModel()) || {},
  210. idProperty = model.idProperty || 'id';
  211. Ext.each(records, function(record) {
  212. var id = record.get(idProperty);
  213. for (var i = data.length; i-- > 0; ) {
  214. if (data[i][idProperty] === id) {
  215. me.deleteRecord(i);
  216. break;
  217. }
  218. }
  219. });
  220. }
  221. };
  222. }());
  223. /**
  224. * This base class is used to handle data preparation (e.g., sorting, filtering and
  225. * group summary).
  226. */
  227. Ext.define('Ext.ux.ajax.DataSimlet', function() {
  228. function makeSortFn(def, cmp) {
  229. var order = def.direction,
  230. sign = (order && order.toUpperCase() === 'DESC') ? -1 : 1;
  231. return function(leftRec, rightRec) {
  232. var lhs = leftRec[def.property],
  233. rhs = rightRec[def.property],
  234. c = (lhs < rhs) ? -1 : ((rhs < lhs) ? 1 : 0);
  235. if (c || !cmp) {
  236. return c * sign;
  237. }
  238. return cmp(leftRec, rightRec);
  239. };
  240. }
  241. function makeSortFns(defs, cmp) {
  242. for (var sortFn = cmp,
  243. i = defs && defs.length; i; ) {
  244. sortFn = makeSortFn(defs[--i], sortFn);
  245. }
  246. return sortFn;
  247. }
  248. return {
  249. extend: 'Ext.ux.ajax.Simlet',
  250. buildNodes: function(node, path) {
  251. var me = this,
  252. nodeData = {
  253. data: []
  254. },
  255. len = node.length,
  256. children, i, child, name;
  257. me.nodes[path] = nodeData;
  258. for (i = 0; i < len; ++i) {
  259. nodeData.data.push(child = node[i]);
  260. name = child.text || child.title;
  261. child.id = path ? path + '/' + name : name;
  262. children = child.children;
  263. if (!(child.leaf = !children)) {
  264. delete child.children;
  265. me.buildNodes(children, child.id);
  266. }
  267. }
  268. },
  269. deleteRecord: function(pos) {
  270. if (this.data && typeof this.data !== 'function') {
  271. Ext.Array.removeAt(this.data, pos);
  272. }
  273. },
  274. fixTree: function(ctx, tree) {
  275. var me = this,
  276. node = ctx.params.node,
  277. nodes;
  278. if (!(nodes = me.nodes)) {
  279. me.nodes = nodes = {};
  280. me.buildNodes(tree, '');
  281. }
  282. node = nodes[node];
  283. if (node) {
  284. if (me.node) {
  285. me.node.sortedData = me.sortedData;
  286. me.node.currentOrder = me.currentOrder;
  287. }
  288. me.node = node;
  289. me.data = node.data;
  290. me.sortedData = node.sortedData;
  291. me.currentOrder = node.currentOrder;
  292. } else {
  293. me.data = null;
  294. }
  295. },
  296. getData: function(ctx) {
  297. var me = this,
  298. params = ctx.params,
  299. order = (params.filter || '') + (params.group || '') + '-' + (params.sort || '') + '-' + (params.dir || ''),
  300. tree = me.tree,
  301. dynamicData, data, fields, sortFn;
  302. if (tree) {
  303. me.fixTree(ctx, tree);
  304. }
  305. data = me.data;
  306. if (typeof data === 'function') {
  307. dynamicData = true;
  308. data = data.call(this, ctx);
  309. }
  310. // If order is '--' then it means we had no order passed, due to the string concat above
  311. if (!data || order === '--') {
  312. return data || [];
  313. }
  314. if (!dynamicData && order == me.currentOrder) {
  315. return me.sortedData;
  316. }
  317. ctx.filterSpec = params.filter && Ext.decode(params.filter);
  318. ctx.groupSpec = params.group && Ext.decode(params.group);
  319. fields = params.sort;
  320. if (params.dir) {
  321. fields = [
  322. {
  323. direction: params.dir,
  324. property: fields
  325. }
  326. ];
  327. } else if (params.sort) {
  328. fields = Ext.decode(params.sort);
  329. } else {
  330. fields = null;
  331. }
  332. if (ctx.filterSpec) {
  333. var filters = new Ext.util.FilterCollection();
  334. filters.add(this.processFilters(ctx.filterSpec));
  335. data = Ext.Array.filter(data, filters.getFilterFn());
  336. }
  337. sortFn = makeSortFns((ctx.sortSpec = fields));
  338. if (ctx.groupSpec) {
  339. sortFn = makeSortFns([
  340. ctx.groupSpec
  341. ], sortFn);
  342. }
  343. // If a straight Ajax request, data may not be an array.
  344. // If an Array, preserve 'physical' order of raw data...
  345. data = Ext.isArray(data) ? data.slice(0) : data;
  346. if (sortFn) {
  347. Ext.Array.sort(data, sortFn);
  348. }
  349. me.sortedData = data;
  350. me.currentOrder = order;
  351. return data;
  352. },
  353. processFilters: Ext.identityFn,
  354. getPage: function(ctx, data) {
  355. var ret = data,
  356. length = data.length,
  357. start = ctx.params.start || 0,
  358. end = ctx.params.limit ? Math.min(length, start + ctx.params.limit) : length;
  359. if (start || end < length) {
  360. ret = ret.slice(start, end);
  361. }
  362. return ret;
  363. },
  364. getGroupSummary: function(groupField, rows, ctx) {
  365. return rows[0];
  366. },
  367. getSummary: function(ctx, data, page) {
  368. var me = this,
  369. groupField = ctx.groupSpec.property,
  370. accum,
  371. todo = {},
  372. summary = [],
  373. fieldValue, lastFieldValue;
  374. Ext.each(page, function(rec) {
  375. fieldValue = rec[groupField];
  376. todo[fieldValue] = true;
  377. });
  378. function flush() {
  379. if (accum) {
  380. summary.push(me.getGroupSummary(groupField, accum, ctx));
  381. accum = null;
  382. }
  383. }
  384. // data is ordered primarily by the groupField, so one pass can pick up all
  385. // the summaries one at a time.
  386. Ext.each(data, function(rec) {
  387. fieldValue = rec[groupField];
  388. if (lastFieldValue !== fieldValue) {
  389. flush();
  390. lastFieldValue = fieldValue;
  391. }
  392. if (!todo[fieldValue]) {
  393. // if we have even 1 summary, we have summarized all that we need
  394. // (again because data and page are ordered by groupField)
  395. return !summary.length;
  396. }
  397. if (accum) {
  398. accum.push(rec);
  399. } else {
  400. accum = [
  401. rec
  402. ];
  403. }
  404. return true;
  405. });
  406. flush();
  407. // make sure that last pesky summary goes...
  408. return summary;
  409. }
  410. };
  411. }());
  412. /**
  413. * JSON Simlet.
  414. */
  415. Ext.define('Ext.ux.ajax.JsonSimlet', {
  416. extend: 'Ext.ux.ajax.DataSimlet',
  417. alias: 'simlet.json',
  418. doGet: function(ctx) {
  419. var me = this,
  420. data = me.getData(ctx),
  421. page = me.getPage(ctx, data),
  422. reader = ctx.xhr.options.proxy && ctx.xhr.options.proxy.getReader(),
  423. root = reader && reader.getRootProperty(),
  424. ret = me.callParent(arguments),
  425. // pick up status/statusText
  426. response = {};
  427. if (root && Ext.isArray(page)) {
  428. response[root] = page;
  429. response[reader.getTotalProperty()] = data.length;
  430. } else {
  431. response = page;
  432. }
  433. if (ctx.groupSpec) {
  434. response.summaryData = me.getSummary(ctx, data, page);
  435. }
  436. ret.responseText = Ext.encode(response);
  437. return ret;
  438. },
  439. doPost: function(ctx) {
  440. return this.doGet(ctx);
  441. }
  442. });
  443. /**
  444. * Pivot Simlet does remote pivot calculations.
  445. * Filtering the pivot results doesn't work.
  446. */
  447. Ext.define('Ext.ux.ajax.PivotSimlet', {
  448. extend: 'Ext.ux.ajax.JsonSimlet',
  449. alias: 'simlet.pivot',
  450. lastPost: null,
  451. // last Ajax params sent to this simlet
  452. lastResponse: null,
  453. // last JSON response produced by this simlet
  454. keysSeparator: '',
  455. grandTotalKey: '',
  456. doPost: function(ctx) {
  457. var me = this,
  458. ret = me.callParent(arguments);
  459. // pick up status/statusText
  460. me.lastResponse = me.processData(me.getData(ctx), Ext.decode(ctx.xhr.body));
  461. ret.responseText = Ext.encode(me.lastResponse);
  462. return ret;
  463. },
  464. processData: function(data, params) {
  465. var me = this,
  466. len = data.length,
  467. response = {
  468. success: true,
  469. leftAxis: [],
  470. topAxis: [],
  471. results: []
  472. },
  473. leftAxis = new Ext.util.MixedCollection(),
  474. topAxis = new Ext.util.MixedCollection(),
  475. results = new Ext.util.MixedCollection(),
  476. i, j, k, leftKeys, topKeys, item, agg;
  477. me.lastPost = params;
  478. me.keysSeparator = params.keysSeparator;
  479. me.grandTotalKey = params.grandTotalKey;
  480. for (i = 0; i < len; i++) {
  481. leftKeys = me.extractValues(data[i], params.leftAxis, leftAxis);
  482. topKeys = me.extractValues(data[i], params.topAxis, topAxis);
  483. // add record to grand totals
  484. me.addResult(data[i], me.grandTotalKey, me.grandTotalKey, results);
  485. for (j = 0; j < leftKeys.length; j++) {
  486. // add record to col grand totals
  487. me.addResult(data[i], leftKeys[j], me.grandTotalKey, results);
  488. // add record to left/top keys pair
  489. for (k = 0; k < topKeys.length; k++) {
  490. me.addResult(data[i], leftKeys[j], topKeys[k], results);
  491. }
  492. }
  493. // add record to row grand totals
  494. for (j = 0; j < topKeys.length; j++) {
  495. me.addResult(data[i], me.grandTotalKey, topKeys[j], results);
  496. }
  497. }
  498. // extract items from their left/top collections and build the json response
  499. response.leftAxis = leftAxis.getRange();
  500. response.topAxis = topAxis.getRange();
  501. len = results.getCount();
  502. for (i = 0; i < len; i++) {
  503. item = results.getAt(i);
  504. item.values = {};
  505. for (j = 0; j < params.aggregate.length; j++) {
  506. agg = params.aggregate[j];
  507. item.values[agg.id] = me[agg.aggregator](item.records, agg.dataIndex, item.leftKey, item.topKey);
  508. }
  509. delete (item.records);
  510. response.results.push(item);
  511. }
  512. leftAxis.clear();
  513. topAxis.clear();
  514. results.clear();
  515. return response;
  516. },
  517. getKey: function(value) {
  518. var me = this;
  519. me.keysMap = me.keysMap || {};
  520. if (!Ext.isDefined(me.keysMap[value])) {
  521. me.keysMap[value] = Ext.id();
  522. }
  523. return me.keysMap[value];
  524. },
  525. extractValues: function(record, dimensions, col) {
  526. var len = dimensions.length,
  527. keys = [],
  528. i, j, key, item, dim;
  529. key = '';
  530. for (j = 0; j < len; j++) {
  531. dim = dimensions[j];
  532. key += (j > 0 ? this.keysSeparator : '') + this.getKey(record[dim.dataIndex]);
  533. item = col.getByKey(key);
  534. if (!item) {
  535. item = col.add(key, {
  536. key: key,
  537. value: record[dim.dataIndex],
  538. dimensionId: dim.id
  539. });
  540. }
  541. keys.push(key);
  542. }
  543. return keys;
  544. },
  545. addResult: function(record, leftKey, topKey, results) {
  546. var item = results.getByKey(leftKey + '/' + topKey);
  547. if (!item) {
  548. item = results.add(leftKey + '/' + topKey, {
  549. leftKey: leftKey,
  550. topKey: topKey,
  551. records: []
  552. });
  553. }
  554. item.records.push(record);
  555. },
  556. sum: function(records, measure, rowGroupKey, colGroupKey) {
  557. var length = records.length,
  558. total = 0,
  559. i;
  560. for (i = 0; i < length; i++) {
  561. total += Ext.Number.from(records[i][measure], 0);
  562. }
  563. return total;
  564. },
  565. avg: function(records, measure, rowGroupKey, colGroupKey) {
  566. var length = records.length,
  567. total = 0,
  568. i;
  569. for (i = 0; i < length; i++) {
  570. total += Ext.Number.from(records[i][measure], 0);
  571. }
  572. return length > 0 ? (total / length) : 0;
  573. },
  574. min: function(records, measure, rowGroupKey, colGroupKey) {
  575. var data = [],
  576. length = records.length,
  577. i, v;
  578. for (i = 0; i < length; i++) {
  579. data.push(records[i][measure]);
  580. }
  581. v = Ext.Array.min(data);
  582. return v;
  583. },
  584. max: function(records, measure, rowGroupKey, colGroupKey) {
  585. var data = [],
  586. length = records.length,
  587. i;
  588. for (i = 0; i < length; i++) {
  589. data.push(records[i][measure]);
  590. }
  591. v = Ext.Array.max(data);
  592. return v;
  593. },
  594. count: function(records, measure, rowGroupKey, colGroupKey) {
  595. return records.length;
  596. },
  597. variance: function(records, measure, rowGroupKey, colGroupKey) {
  598. var me = Ext.pivot.Aggregators,
  599. length = records.length,
  600. avg = me.avg.apply(me, arguments),
  601. total = 0,
  602. i;
  603. if (avg > 0) {
  604. for (i = 0; i < length; i++) {
  605. total += Math.pow(Ext.Number.from(records[i][measure], 0) - avg, 2);
  606. }
  607. }
  608. return (total > 0 && length > 1) ? (total / (length - 1)) : 0;
  609. },
  610. varianceP: function(records, measure, rowGroupKey, colGroupKey) {
  611. var me = Ext.pivot.Aggregators,
  612. length = records.length,
  613. avg = me.avg.apply(me, arguments),
  614. total = 0,
  615. i;
  616. if (avg > 0) {
  617. for (i = 0; i < length; i++) {
  618. total += Math.pow(Ext.Number.from(records[i][measure], 0) - avg, 2);
  619. }
  620. }
  621. return (total > 0 && length > 0) ? (total / length) : 0;
  622. },
  623. stdDev: function(records, measure, rowGroupKey, colGroupKey) {
  624. var me = Ext.pivot.Aggregators,
  625. v = me.variance.apply(me, arguments);
  626. return v > 0 ? Math.sqrt(v) : 0;
  627. },
  628. stdDevP: function(records, measure, rowGroupKey, colGroupKey) {
  629. var me = Ext.pivot.Aggregators,
  630. v = me.varianceP.apply(me, arguments);
  631. return v > 0 ? Math.sqrt(v) : 0;
  632. }
  633. });
  634. /**
  635. * Simulates an XMLHttpRequest object's methods and properties but is backed by a
  636. * {@link Ext.ux.ajax.Simlet} instance that provides the data.
  637. */
  638. Ext.define('Ext.ux.ajax.SimXhr', {
  639. readyState: 0,
  640. mgr: null,
  641. simlet: null,
  642. constructor: function(config) {
  643. var me = this;
  644. Ext.apply(me, config);
  645. me.requestHeaders = {};
  646. },
  647. abort: function() {
  648. var me = this;
  649. if (me.timer) {
  650. Ext.undefer(me.timer);
  651. me.timer = null;
  652. }
  653. me.aborted = true;
  654. },
  655. getAllResponseHeaders: function() {
  656. var headers = [];
  657. if (Ext.isObject(this.responseHeaders)) {
  658. Ext.Object.each(this.responseHeaders, function(name, value) {
  659. headers.push(name + ': ' + value);
  660. });
  661. }
  662. return headers.join('\r\n');
  663. },
  664. getResponseHeader: function(header) {
  665. var headers = this.responseHeaders;
  666. return (headers && headers[header]) || null;
  667. },
  668. open: function(method, url, async, user, password) {
  669. var me = this;
  670. me.method = method;
  671. me.url = url;
  672. me.async = async !== false;
  673. me.user = user;
  674. me.password = password;
  675. me.setReadyState(1);
  676. },
  677. overrideMimeType: function(mimeType) {
  678. this.mimeType = mimeType;
  679. },
  680. schedule: function() {
  681. var me = this,
  682. delay = me.simlet.delay || me.mgr.delay;
  683. if (delay) {
  684. me.timer = Ext.defer(function() {
  685. me.onTick();
  686. }, delay);
  687. } else {
  688. me.onTick();
  689. }
  690. },
  691. send: function(body) {
  692. var me = this;
  693. me.body = body;
  694. if (me.async) {
  695. me.schedule();
  696. } else {
  697. me.onComplete();
  698. }
  699. },
  700. setReadyState: function(state) {
  701. var me = this;
  702. if (me.readyState != state) {
  703. me.readyState = state;
  704. me.onreadystatechange();
  705. }
  706. },
  707. setRequestHeader: function(header, value) {
  708. this.requestHeaders[header] = value;
  709. },
  710. // handlers
  711. onreadystatechange: Ext.emptyFn,
  712. onComplete: function() {
  713. var me = this,
  714. callback;
  715. me.readyState = 4;
  716. Ext.apply(me, me.simlet.exec(me));
  717. callback = me.jsonpCallback;
  718. if (callback) {
  719. var text = callback + '(' + me.responseText + ')';
  720. eval(text);
  721. }
  722. },
  723. onTick: function() {
  724. var me = this;
  725. me.timer = null;
  726. me.onComplete();
  727. me.onreadystatechange && me.onreadystatechange();
  728. }
  729. });
  730. /**
  731. * This singleton manages simulated Ajax responses. This allows application logic to be
  732. * written unaware that its Ajax calls are being handled by simulations ("simlets"). This
  733. * is currently done by hooking {@link Ext.data.Connection} methods, so all users of that
  734. * class (and {@link Ext.Ajax} since it is a derived class) qualify for simulation.
  735. *
  736. * The requires hooks are inserted when either the {@link #init} method is called or the
  737. * first {@link Ext.ux.ajax.Simlet} is registered. For example:
  738. *
  739. * Ext.onReady(function () {
  740. * initAjaxSim();
  741. *
  742. * // normal stuff
  743. * });
  744. *
  745. * function initAjaxSim () {
  746. * Ext.ux.ajax.SimManager.init({
  747. * delay: 300
  748. * }).register({
  749. * '/app/data/url': {
  750. * type: 'json', // use JsonSimlet (type is like xtype for components)
  751. * data: [
  752. * { foo: 42, bar: 'abc' },
  753. * ...
  754. * ]
  755. * }
  756. * });
  757. * }
  758. *
  759. * As many URL's as desired can be registered and associated with a {@link Ext.ux.ajax.Simlet}. To make
  760. * non-simulated Ajax requests once this singleton is initialized, add a `nosim:true` option
  761. * to the Ajax options:
  762. *
  763. * Ext.Ajax.request({
  764. * url: 'page.php',
  765. * nosim: true, // ignored by normal Ajax request
  766. * params: {
  767. * id: 1
  768. * },
  769. * success: function(response){
  770. * var text = response.responseText;
  771. * // process server response here
  772. * }
  773. * });
  774. */
  775. Ext.define('Ext.ux.ajax.SimManager', {
  776. singleton: true,
  777. requires: [
  778. 'Ext.data.Connection',
  779. 'Ext.ux.ajax.SimXhr',
  780. 'Ext.ux.ajax.Simlet',
  781. 'Ext.ux.ajax.JsonSimlet'
  782. ],
  783. /**
  784. * @cfg {Ext.ux.ajax.Simlet} defaultSimlet
  785. * The {@link Ext.ux.ajax.Simlet} instance to use for non-matching URL's. By default, this will
  786. * return 404. Set this to null to use real Ajax calls for non-matching URL's.
  787. */
  788. /**
  789. * @cfg {String} defaultType
  790. * The default `type` to apply to generic {@link Ext.ux.ajax.Simlet} configuration objects. The
  791. * default is 'basic'.
  792. */
  793. defaultType: 'basic',
  794. /**
  795. * @cfg {Number} delay
  796. * The number of milliseconds to delay before delivering a response to an async request.
  797. */
  798. delay: 150,
  799. /**
  800. * @property {Boolean} ready
  801. * True once this singleton has initialized and applied its Ajax hooks.
  802. * @private
  803. */
  804. ready: false,
  805. constructor: function() {
  806. this.simlets = [];
  807. },
  808. getSimlet: function(url) {
  809. // Strip down to base URL (no query parameters or hash):
  810. var me = this,
  811. index = url.indexOf('?'),
  812. simlets = me.simlets,
  813. len = simlets.length,
  814. i, simlet, simUrl, match;
  815. if (index < 0) {
  816. index = url.indexOf('#');
  817. }
  818. if (index > 0) {
  819. url = url.substring(0, index);
  820. }
  821. for (i = 0; i < len; ++i) {
  822. simlet = simlets[i];
  823. simUrl = simlet.url;
  824. if (simUrl instanceof RegExp) {
  825. match = simUrl.test(url);
  826. } else {
  827. match = simUrl === url;
  828. }
  829. if (match) {
  830. return simlet;
  831. }
  832. }
  833. return me.defaultSimlet;
  834. },
  835. getXhr: function(method, url, options, async) {
  836. var simlet = this.getSimlet(url);
  837. if (simlet) {
  838. return simlet.openRequest(method, url, options, async);
  839. }
  840. return null;
  841. },
  842. /**
  843. * Initializes this singleton and applies configuration options.
  844. * @param {Object} config An optional object with configuration properties to apply.
  845. * @return {Ext.ux.ajax.SimManager} this
  846. */
  847. init: function(config) {
  848. var me = this;
  849. Ext.apply(me, config);
  850. if (!me.ready) {
  851. me.ready = true;
  852. if (!('defaultSimlet' in me)) {
  853. me.defaultSimlet = new Ext.ux.ajax.Simlet({
  854. status: 404,
  855. statusText: 'Not Found'
  856. });
  857. }
  858. me._openRequest = Ext.data.Connection.prototype.openRequest;
  859. Ext.data.request.Ajax.override({
  860. openRequest: function(options, requestOptions, async) {
  861. var xhr = !options.nosim && me.getXhr(requestOptions.method, requestOptions.url, options, async);
  862. if (!xhr) {
  863. xhr = this.callParent(arguments);
  864. }
  865. return xhr;
  866. }
  867. });
  868. if (Ext.data.JsonP) {
  869. Ext.data.JsonP.self.override({
  870. createScript: function(url, params, options) {
  871. var fullUrl = Ext.urlAppend(url, Ext.Object.toQueryString(params)),
  872. script = !options.nosim && me.getXhr('GET', fullUrl, options, true);
  873. if (!script) {
  874. script = this.callParent(arguments);
  875. }
  876. return script;
  877. },
  878. loadScript: function(request) {
  879. var script = request.script;
  880. if (script.simlet) {
  881. script.jsonpCallback = request.params[request.callbackKey];
  882. script.send(null);
  883. // Ext.data.JsonP will attempt dom removal of a script tag, so emulate its presence
  884. request.script = document.createElement('script');
  885. } else {
  886. this.callParent(arguments);
  887. }
  888. }
  889. });
  890. }
  891. }
  892. return me;
  893. },
  894. openRequest: function(method, url, async) {
  895. var opt = {
  896. method: method,
  897. url: url
  898. };
  899. return this._openRequest.call(Ext.data.Connection.prototype, {}, opt, async);
  900. },
  901. /**
  902. * Registeres one or more {@link Ext.ux.ajax.Simlet} instances.
  903. * @param {Array/Object} simlet Either a {@link Ext.ux.ajax.Simlet} instance or config, an Array
  904. * of such elements or an Object keyed by URL with values that are {@link Ext.ux.ajax.Simlet}
  905. * instances or configs.
  906. */
  907. register: function(simlet) {
  908. var me = this;
  909. me.init();
  910. function reg(one) {
  911. var simlet = one;
  912. if (!simlet.isSimlet) {
  913. simlet = Ext.create('simlet.' + (simlet.type || simlet.stype || me.defaultType), one);
  914. }
  915. me.simlets.push(simlet);
  916. simlet.manager = me;
  917. }
  918. if (Ext.isArray(simlet)) {
  919. Ext.each(simlet, reg);
  920. } else if (simlet.isSimlet || simlet.url) {
  921. reg(simlet);
  922. } else {
  923. Ext.Object.each(simlet, function(url, s) {
  924. s.url = url;
  925. reg(s);
  926. });
  927. }
  928. return me;
  929. }
  930. });
  931. /**
  932. * This class simulates XML-based requests.
  933. */
  934. Ext.define('Ext.ux.ajax.XmlSimlet', {
  935. extend: 'Ext.ux.ajax.DataSimlet',
  936. alias: 'simlet.xml',
  937. /**
  938. * This template is used to populate the XML response. The configuration of the Reader
  939. * is available so that its `root` and `record` properties can be used as well as the
  940. * `fields` of the associated `model`. But beyond that, the way these pieces are put
  941. * together in the document requires the flexibility of a template.
  942. */
  943. xmlTpl: [
  944. '<{root}>\n',
  945. '<tpl for="data">',
  946. ' <{parent.record}>\n',
  947. '<tpl for="parent.fields">',
  948. ' <{name}>{[parent[values.name]]}</{name}>\n',
  949. '</tpl>',
  950. ' </{parent.record}>\n',
  951. '</tpl>',
  952. '</{root}>'
  953. ],
  954. doGet: function(ctx) {
  955. var me = this,
  956. data = me.getData(ctx),
  957. page = me.getPage(ctx, data),
  958. proxy = ctx.xhr.options.operation.getProxy(),
  959. reader = proxy && proxy.getReader(),
  960. model = reader && reader.getModel(),
  961. ret = me.callParent(arguments),
  962. // pick up status/statusText
  963. response = {
  964. data: page,
  965. reader: reader,
  966. fields: model && model.fields,
  967. root: reader && reader.getRootProperty(),
  968. record: reader && reader.record
  969. },
  970. tpl, xml, doc;
  971. if (ctx.groupSpec) {
  972. response.summaryData = me.getSummary(ctx, data, page);
  973. }
  974. // If a straight Ajax request there won't be an xmlTpl.
  975. if (me.xmlTpl) {
  976. tpl = Ext.XTemplate.getTpl(me, 'xmlTpl');
  977. xml = tpl.apply(response);
  978. } else {
  979. xml = data;
  980. }
  981. if (typeof DOMParser != 'undefined') {
  982. doc = (new DOMParser()).parseFromString(xml, "text/xml");
  983. } else {
  984. // IE doesn't have DOMParser, but fortunately, there is an ActiveX for XML
  985. doc = new ActiveXObject("Microsoft.XMLDOM");
  986. doc.async = false;
  987. doc.loadXML(xml);
  988. }
  989. ret.responseText = xml;
  990. ret.responseXML = doc;
  991. return ret;
  992. },
  993. fixTree: function() {
  994. this.callParent(arguments);
  995. var buffer = [];
  996. this.buildTreeXml(this.data, buffer);
  997. this.data = buffer.join('');
  998. },
  999. buildTreeXml: function(nodes, buffer) {
  1000. var rootProperty = this.rootProperty,
  1001. recordProperty = this.recordProperty;
  1002. buffer.push('<', rootProperty, '>');
  1003. Ext.Array.forEach(nodes, function(node) {
  1004. buffer.push('<', recordProperty, '>');
  1005. for (var key in node) {
  1006. if (key == 'children') {
  1007. this.buildTreeXml(node.children, buffer);
  1008. } else {
  1009. buffer.push('<', key, '>', node[key], '</', key, '>');
  1010. }
  1011. }
  1012. buffer.push('</', recordProperty, '>');
  1013. });
  1014. buffer.push('</', rootProperty, '>');
  1015. }
  1016. });
  1017. /**
  1018. * This is the base class for {@link Ext.ux.event.Recorder} and {@link Ext.ux.event.Player}.
  1019. */
  1020. Ext.define('Ext.ux.event.Driver', {
  1021. extend: 'Ext.util.Observable',
  1022. active: null,
  1023. specialKeysByName: {
  1024. PGUP: 33,
  1025. PGDN: 34,
  1026. END: 35,
  1027. HOME: 36,
  1028. LEFT: 37,
  1029. UP: 38,
  1030. RIGHT: 39,
  1031. DOWN: 40
  1032. },
  1033. specialKeysByCode: {},
  1034. /**
  1035. * @event start
  1036. * Fires when this object is started.
  1037. * @param {Ext.ux.event.Driver} this
  1038. */
  1039. /**
  1040. * @event stop
  1041. * Fires when this object is stopped.
  1042. * @param {Ext.ux.event.Driver} this
  1043. */
  1044. getTextSelection: function(el) {
  1045. // See https://code.google.com/p/rangyinputs/source/browse/trunk/rangyinputs_jquery.js
  1046. var doc = el.ownerDocument,
  1047. range, range2, start, end;
  1048. if (typeof el.selectionStart === "number") {
  1049. start = el.selectionStart;
  1050. end = el.selectionEnd;
  1051. } else if (doc.selection) {
  1052. range = doc.selection.createRange();
  1053. range2 = el.createTextRange();
  1054. range2.setEndPoint('EndToStart', range);
  1055. start = range2.text.length;
  1056. end = start + range.text.length;
  1057. }
  1058. return [
  1059. start,
  1060. end
  1061. ];
  1062. },
  1063. getTime: function() {
  1064. return new Date().getTime();
  1065. },
  1066. /**
  1067. * Returns the number of milliseconds since start was called.
  1068. */
  1069. getTimestamp: function() {
  1070. var d = this.getTime();
  1071. return d - this.startTime;
  1072. },
  1073. onStart: function() {},
  1074. onStop: function() {},
  1075. /**
  1076. * Starts this object. If this object is already started, nothing happens.
  1077. */
  1078. start: function() {
  1079. var me = this;
  1080. if (!me.active) {
  1081. me.active = new Date();
  1082. me.startTime = me.getTime();
  1083. me.onStart();
  1084. me.fireEvent('start', me);
  1085. }
  1086. },
  1087. /**
  1088. * Stops this object. If this object is not started, nothing happens.
  1089. */
  1090. stop: function() {
  1091. var me = this;
  1092. if (me.active) {
  1093. me.active = null;
  1094. me.onStop();
  1095. me.fireEvent('stop', me);
  1096. }
  1097. }
  1098. }, function() {
  1099. var proto = this.prototype;
  1100. Ext.Object.each(proto.specialKeysByName, function(name, value) {
  1101. proto.specialKeysByCode[value] = name;
  1102. });
  1103. });
  1104. /**
  1105. * Event maker.
  1106. */
  1107. Ext.define('Ext.ux.event.Maker', {
  1108. eventQueue: [],
  1109. startAfter: 500,
  1110. timerIncrement: 500,
  1111. currentTiming: 0,
  1112. constructor: function(config) {
  1113. var me = this;
  1114. me.currentTiming = me.startAfter;
  1115. if (!Ext.isArray(config)) {
  1116. config = [
  1117. config
  1118. ];
  1119. }
  1120. Ext.Array.each(config, function(item) {
  1121. item.el = item.el || 'el';
  1122. Ext.Array.each(Ext.ComponentQuery.query(item.cmpQuery), function(cmp) {
  1123. var event = {},
  1124. x, y, el;
  1125. if (!item.domQuery) {
  1126. el = cmp[item.el];
  1127. } else {
  1128. el = cmp.el.down(item.domQuery);
  1129. }
  1130. event.target = '#' + el.dom.id;
  1131. event.type = item.type;
  1132. event.button = config.button || 0;
  1133. x = el.getX() + (el.getWidth() / 2);
  1134. y = el.getY() + (el.getHeight() / 2);
  1135. event.xy = [
  1136. x,
  1137. y
  1138. ];
  1139. event.ts = me.currentTiming;
  1140. me.currentTiming += me.timerIncrement;
  1141. me.eventQueue.push(event);
  1142. });
  1143. if (item.screenshot) {
  1144. me.eventQueue[me.eventQueue.length - 1].screenshot = true;
  1145. }
  1146. });
  1147. return me.eventQueue;
  1148. }
  1149. });
  1150. /**
  1151. * @extends Ext.ux.event.Driver
  1152. * This class manages the playback of an array of "event descriptors". For details on the
  1153. * contents of an "event descriptor", see {@link Ext.ux.event.Recorder}. The events recorded by the
  1154. * {@link Ext.ux.event.Recorder} class are designed to serve as input for this class.
  1155. *
  1156. * The simplest use of this class is to instantiate it with an {@link #eventQueue} and call
  1157. * {@link #method-start}. Like so:
  1158. *
  1159. * var player = Ext.create('Ext.ux.event.Player', {
  1160. * eventQueue: [ ... ],
  1161. * speed: 2, // play at 2x speed
  1162. * listeners: {
  1163. * stop: function () {
  1164. * player = null; // all done
  1165. * }
  1166. * }
  1167. * });
  1168. *
  1169. * player.start();
  1170. *
  1171. * A more complex use would be to incorporate keyframe generation after playing certain
  1172. * events.
  1173. *
  1174. * var player = Ext.create('Ext.ux.event.Player', {
  1175. * eventQueue: [ ... ],
  1176. * keyFrameEvents: {
  1177. * click: true
  1178. * },
  1179. * listeners: {
  1180. * stop: function () {
  1181. * // play has completed... probably time for another keyframe...
  1182. * player = null;
  1183. * },
  1184. * keyframe: onKeyFrame
  1185. * }
  1186. * });
  1187. *
  1188. * player.start();
  1189. *
  1190. * If a keyframe can be handled immediately (synchronously), the listener would be:
  1191. *
  1192. * function onKeyFrame () {
  1193. * handleKeyFrame();
  1194. * }
  1195. *
  1196. * If the keyframe event is always handled asynchronously, then the event listener is only
  1197. * a bit more:
  1198. *
  1199. * function onKeyFrame (p, eventDescriptor) {
  1200. * eventDescriptor.defer(); // pause event playback...
  1201. *
  1202. * handleKeyFrame(function () {
  1203. * eventDescriptor.finish(); // ...resume event playback
  1204. * });
  1205. * }
  1206. *
  1207. * Finally, if the keyframe could be either handled synchronously or asynchronously (perhaps
  1208. * differently by browser), a slightly more complex listener is required.
  1209. *
  1210. * function onKeyFrame (p, eventDescriptor) {
  1211. * var async;
  1212. *
  1213. * handleKeyFrame(function () {
  1214. * // either this callback is being called immediately by handleKeyFrame (in
  1215. * // which case async is undefined) or it is being called later (in which case
  1216. * // async will be true).
  1217. *
  1218. * if (async) {
  1219. * eventDescriptor.finish();
  1220. * } else {
  1221. * async = false;
  1222. * }
  1223. * });
  1224. *
  1225. * // either the callback was called (and async is now false) or it was not
  1226. * // called (and async remains undefined).
  1227. *
  1228. * if (async !== false) {
  1229. * eventDescriptor.defer();
  1230. * async = true; // let the callback know that we have gone async
  1231. * }
  1232. * }
  1233. */
  1234. Ext.define('Ext.ux.event.Player', function(Player) {
  1235. var defaults = {},
  1236. mouseEvents = {},
  1237. keyEvents = {},
  1238. doc,
  1239. //HTML events supported
  1240. uiEvents = {},
  1241. //events that bubble by default
  1242. bubbleEvents = {
  1243. //scroll: 1,
  1244. resize: 1,
  1245. reset: 1,
  1246. submit: 1,
  1247. change: 1,
  1248. select: 1,
  1249. error: 1,
  1250. abort: 1
  1251. };
  1252. Ext.each([
  1253. 'click',
  1254. 'dblclick',
  1255. 'mouseover',
  1256. 'mouseout',
  1257. 'mousedown',
  1258. 'mouseup',
  1259. 'mousemove'
  1260. ], function(type) {
  1261. bubbleEvents[type] = defaults[type] = mouseEvents[type] = {
  1262. bubbles: true,
  1263. cancelable: (type != "mousemove"),
  1264. // mousemove cannot be cancelled
  1265. detail: 1,
  1266. screenX: 0,
  1267. screenY: 0,
  1268. clientX: 0,
  1269. clientY: 0,
  1270. ctrlKey: false,
  1271. altKey: false,
  1272. shiftKey: false,
  1273. metaKey: false,
  1274. button: 0
  1275. };
  1276. });
  1277. Ext.each([
  1278. 'keydown',
  1279. 'keyup',
  1280. 'keypress'
  1281. ], function(type) {
  1282. bubbleEvents[type] = defaults[type] = keyEvents[type] = {
  1283. bubbles: true,
  1284. cancelable: true,
  1285. ctrlKey: false,
  1286. altKey: false,
  1287. shiftKey: false,
  1288. metaKey: false,
  1289. keyCode: 0,
  1290. charCode: 0
  1291. };
  1292. });
  1293. Ext.each([
  1294. 'blur',
  1295. 'change',
  1296. 'focus',
  1297. 'resize',
  1298. 'scroll',
  1299. 'select'
  1300. ], function(type) {
  1301. defaults[type] = uiEvents[type] = {
  1302. bubbles: (type in bubbleEvents),
  1303. cancelable: false,
  1304. detail: 1
  1305. };
  1306. });
  1307. var inputSpecialKeys = {
  1308. 8: function(target, start, end) {
  1309. // backspace: 8,
  1310. if (start < end) {
  1311. target.value = target.value.substring(0, start) + target.value.substring(end);
  1312. } else if (start > 0) {
  1313. target.value = target.value.substring(0, --start) + target.value.substring(end);
  1314. }
  1315. this.setTextSelection(target, start, start);
  1316. },
  1317. 46: function(target, start, end) {
  1318. // delete: 46
  1319. if (start < end) {
  1320. target.value = target.value.substring(0, start) + target.value.substring(end);
  1321. } else if (start < target.value.length - 1) {
  1322. target.value = target.value.substring(0, start) + target.value.substring(start + 1);
  1323. }
  1324. this.setTextSelection(target, start, start);
  1325. }
  1326. };
  1327. return {
  1328. extend: 'Ext.ux.event.Driver',
  1329. /**
  1330. * @cfg {Array} eventQueue The event queue to playback. This must be provided before
  1331. * the {@link #method-start} method is called.
  1332. */
  1333. /**
  1334. * @cfg {Object} keyFrameEvents An object that describes the events that should generate
  1335. * keyframe events. For example, `{ click: true }` would generate keyframe events after
  1336. * each `click` event.
  1337. */
  1338. keyFrameEvents: {
  1339. click: true
  1340. },
  1341. /**
  1342. * @cfg {Boolean} pauseForAnimations True to pause event playback during animations, false
  1343. * to ignore animations. Default is true.
  1344. */
  1345. pauseForAnimations: true,
  1346. /**
  1347. * @cfg {Number} speed The playback speed multiplier. Default is 1.0 (to playback at the
  1348. * recorded speed). A value of 2 would playback at 2x speed.
  1349. */
  1350. speed: 1,
  1351. stallTime: 0,
  1352. _inputSpecialKeys: {
  1353. INPUT: inputSpecialKeys,
  1354. TEXTAREA: Ext.apply({}, //13: function (target, start, end) { // enter: 8,
  1355. //TODO ?
  1356. //}
  1357. inputSpecialKeys)
  1358. },
  1359. tagPathRegEx: /(\w+)(?:\[(\d+)\])?/,
  1360. /**
  1361. * @event beforeplay
  1362. * Fires before an event is played.
  1363. * @param {Ext.ux.event.Player} this
  1364. * @param {Object} eventDescriptor The event descriptor about to be played.
  1365. */
  1366. /**
  1367. * @event keyframe
  1368. * Fires when this player reaches a keyframe. Typically, this is after events
  1369. * like `click` are injected and any resulting animations have been completed.
  1370. * @param {Ext.ux.event.Player} this
  1371. * @param {Object} eventDescriptor The keyframe event descriptor.
  1372. */
  1373. constructor: function(config) {
  1374. var me = this;
  1375. me.callParent(arguments);
  1376. me.timerFn = function() {
  1377. me.onTick();
  1378. };
  1379. me.attachTo = me.attachTo || window;
  1380. doc = me.attachTo.document;
  1381. },
  1382. /**
  1383. * Returns the element given is XPath-like description.
  1384. * @param {String} xpath The XPath-like description of the element.
  1385. * @return {HTMLElement}
  1386. */
  1387. getElementFromXPath: function(xpath) {
  1388. var me = this,
  1389. parts = xpath.split('/'),
  1390. regex = me.tagPathRegEx,
  1391. i, n, m, count, tag, child,
  1392. el = me.attachTo.document;
  1393. el = (parts[0] == '~') ? el.body : el.getElementById(parts[0].substring(1));
  1394. // remove '#'
  1395. for (i = 1 , n = parts.length; el && i < n; ++i) {
  1396. m = regex.exec(parts[i]);
  1397. count = m[2] ? parseInt(m[2], 10) : 1;
  1398. tag = m[1].toUpperCase();
  1399. for (child = el.firstChild; child; child = child.nextSibling) {
  1400. if (child.tagName == tag) {
  1401. if (count == 1) {
  1402. break;
  1403. }
  1404. --count;
  1405. }
  1406. }
  1407. el = child;
  1408. }
  1409. return el;
  1410. },
  1411. // Moving across a line break only counts as moving one character in a TextRange, whereas a line break in
  1412. // the textarea value is two characters. This function corrects for that by converting a text offset into a
  1413. // range character offset by subtracting one character for every line break in the textarea prior to the
  1414. // offset
  1415. offsetToRangeCharacterMove: function(el, offset) {
  1416. return offset - (el.value.slice(0, offset).split("\r\n").length - 1);
  1417. },
  1418. setTextSelection: function(el, startOffset, endOffset) {
  1419. // See https://code.google.com/p/rangyinputs/source/browse/trunk/rangyinputs_jquery.js
  1420. if (startOffset < 0) {
  1421. startOffset += el.value.length;
  1422. }
  1423. if (endOffset == null) {
  1424. endOffset = startOffset;
  1425. }
  1426. if (endOffset < 0) {
  1427. endOffset += el.value.length;
  1428. }
  1429. if (typeof el.selectionStart === "number") {
  1430. el.selectionStart = startOffset;
  1431. el.selectionEnd = endOffset;
  1432. } else {
  1433. var range = el.createTextRange();
  1434. var startCharMove = this.offsetToRangeCharacterMove(el, startOffset);
  1435. range.collapse(true);
  1436. if (startOffset == endOffset) {
  1437. range.move("character", startCharMove);
  1438. } else {
  1439. range.moveEnd("character", this.offsetToRangeCharacterMove(el, endOffset));
  1440. range.moveStart("character", startCharMove);
  1441. }
  1442. range.select();
  1443. }
  1444. },
  1445. getTimeIndex: function() {
  1446. var t = this.getTimestamp() - this.stallTime;
  1447. return t * this.speed;
  1448. },
  1449. makeToken: function(eventDescriptor, signal) {
  1450. var me = this,
  1451. t0;
  1452. eventDescriptor[signal] = true;
  1453. eventDescriptor.defer = function() {
  1454. eventDescriptor[signal] = false;
  1455. t0 = me.getTime();
  1456. };
  1457. eventDescriptor.finish = function() {
  1458. eventDescriptor[signal] = true;
  1459. me.stallTime += me.getTime() - t0;
  1460. me.schedule();
  1461. };
  1462. },
  1463. /**
  1464. * This method is called after an event has been played to prepare for the next event.
  1465. * @param {Object} eventDescriptor The descriptor of the event just played.
  1466. */
  1467. nextEvent: function(eventDescriptor) {
  1468. var me = this,
  1469. index = ++me.queueIndex;
  1470. // keyframe events are inserted after a keyFrameEvent is played.
  1471. if (me.keyFrameEvents[eventDescriptor.type]) {
  1472. Ext.Array.insert(me.eventQueue, index, [
  1473. {
  1474. keyframe: true,
  1475. ts: eventDescriptor.ts
  1476. }
  1477. ]);
  1478. }
  1479. },
  1480. /**
  1481. * This method returns the event descriptor at the front of the queue. This does not
  1482. * dequeue the event. Repeated calls return the same object (until {@link #nextEvent}
  1483. * is called).
  1484. */
  1485. peekEvent: function() {
  1486. return this.eventQueue[this.queueIndex] || null;
  1487. },
  1488. /**
  1489. * Replaces an event in the queue with an array of events. This is often used to roll
  1490. * up a multi-step pseudo-event and expand it just-in-time to be played. The process
  1491. * for doing this in a derived class would be this:
  1492. *
  1493. * Ext.define('My.Player', {
  1494. * extend: 'Ext.ux.event.Player',
  1495. *
  1496. * peekEvent: function () {
  1497. * var event = this.callParent();
  1498. *
  1499. * if (event.multiStepSpecial) {
  1500. * this.replaceEvent(null, [
  1501. * ... expand to actual events
  1502. * ]);
  1503. *
  1504. * event = this.callParent(); // get the new next event
  1505. * }
  1506. *
  1507. * return event;
  1508. * }
  1509. * });
  1510. *
  1511. * This method ensures that the `beforeplay` hook (if any) from the replaced event is
  1512. * placed on the first new event and the `afterplay` hook (if any) is placed on the
  1513. * last new event.
  1514. *
  1515. * @param {Number} index The queue index to replace. Pass `null` to replace the event
  1516. * at the current `queueIndex`.
  1517. * @param {Event[]} events The array of events with which to replace the specified
  1518. * event.
  1519. */
  1520. replaceEvent: function(index, events) {
  1521. for (var t,
  1522. i = 0,
  1523. n = events.length; i < n; ++i) {
  1524. if (i) {
  1525. t = events[i - 1];
  1526. delete t.afterplay;
  1527. delete t.screenshot;
  1528. delete events[i].beforeplay;
  1529. }
  1530. }
  1531. Ext.Array.replace(this.eventQueue, (index == null) ? this.queueIndex : index, 1, events);
  1532. },
  1533. /**
  1534. * This method dequeues and injects events until it has arrived at the time index. If
  1535. * no events are ready (based on the time index), this method does nothing.
  1536. * @return {Boolean} True if there is more to do; false if not (at least for now).
  1537. */
  1538. processEvents: function() {
  1539. var me = this,
  1540. animations = me.pauseForAnimations && me.attachTo.Ext.fx.Manager.items,
  1541. eventDescriptor;
  1542. while ((eventDescriptor = me.peekEvent()) !== null) {
  1543. if (animations && animations.getCount()) {
  1544. return true;
  1545. }
  1546. if (eventDescriptor.keyframe) {
  1547. if (!me.processKeyFrame(eventDescriptor)) {
  1548. return false;
  1549. }
  1550. me.nextEvent(eventDescriptor);
  1551. } else if (eventDescriptor.ts <= me.getTimeIndex() && me.fireEvent('beforeplay', me, eventDescriptor) !== false && me.playEvent(eventDescriptor)) {
  1552. me.nextEvent(eventDescriptor);
  1553. } else {
  1554. return true;
  1555. }
  1556. }
  1557. me.stop();
  1558. return false;
  1559. },
  1560. /**
  1561. * This method is called when a keyframe is reached. This will fire the keyframe event.
  1562. * If the keyframe has been handled, true is returned. Otherwise, false is returned.
  1563. * @param {Object} eventDescriptor The event descriptor of the keyframe.
  1564. * @return {Boolean} True if the keyframe was handled, false if not.
  1565. */
  1566. processKeyFrame: function(eventDescriptor) {
  1567. var me = this;
  1568. // only fire keyframe event (and setup the eventDescriptor) once...
  1569. if (!eventDescriptor.defer) {
  1570. me.makeToken(eventDescriptor, 'done');
  1571. me.fireEvent('keyframe', me, eventDescriptor);
  1572. }
  1573. return eventDescriptor.done;
  1574. },
  1575. /**
  1576. * Called to inject the given event on the specified target.
  1577. * @param {HTMLElement} target The target of the event.
  1578. * @param {Object} event The event to inject. The properties of this object should be
  1579. * those of standard DOM events but vary based on the `type` property. For details on
  1580. * event types and their properties, see the class documentation.
  1581. */
  1582. injectEvent: function(target, event) {
  1583. var me = this,
  1584. type = event.type,
  1585. options = Ext.apply({}, event, defaults[type]),
  1586. handler;
  1587. if (type === 'type') {
  1588. handler = me._inputSpecialKeys[target.tagName];
  1589. if (handler) {
  1590. return me.injectTypeInputEvent(target, event, handler);
  1591. }
  1592. return me.injectTypeEvent(target, event);
  1593. }
  1594. if (type === 'focus' && target.focus) {
  1595. target.focus();
  1596. return true;
  1597. }
  1598. if (type === 'blur' && target.blur) {
  1599. target.blur();
  1600. return true;
  1601. }
  1602. if (type === 'scroll') {
  1603. target.scrollLeft = event.pos[0];
  1604. target.scrollTop = event.pos[1];
  1605. return true;
  1606. }
  1607. if (type === 'mduclick') {
  1608. return me.injectEvent(target, Ext.applyIf({
  1609. type: 'mousedown'
  1610. }, event)) && me.injectEvent(target, Ext.applyIf({
  1611. type: 'mouseup'
  1612. }, event)) && me.injectEvent(target, Ext.applyIf({
  1613. type: 'click'
  1614. }, event));
  1615. }
  1616. if (mouseEvents[type]) {
  1617. return Player.injectMouseEvent(target, options, me.attachTo);
  1618. }
  1619. if (keyEvents[type]) {
  1620. return Player.injectKeyEvent(target, options, me.attachTo);
  1621. }
  1622. if (uiEvents[type]) {
  1623. return Player.injectUIEvent(target, type, options.bubbles, options.cancelable, options.view || me.attachTo, options.detail);
  1624. }
  1625. return false;
  1626. },
  1627. injectTypeEvent: function(target, event) {
  1628. var me = this,
  1629. text = event.text,
  1630. xlat = [],
  1631. ch, chUp, i, n, sel, upper, isInput;
  1632. if (text) {
  1633. delete event.text;
  1634. upper = text.toUpperCase();
  1635. for (i = 0 , n = text.length; i < n; ++i) {
  1636. ch = text.charCodeAt(i);
  1637. chUp = upper.charCodeAt(i);
  1638. xlat.push(Ext.applyIf({
  1639. type: 'keydown',
  1640. charCode: chUp,
  1641. keyCode: chUp
  1642. }, event), Ext.applyIf({
  1643. type: 'keypress',
  1644. charCode: ch,
  1645. keyCode: ch
  1646. }, event), Ext.applyIf({
  1647. type: 'keyup',
  1648. charCode: chUp,
  1649. keyCode: chUp
  1650. }, event));
  1651. }
  1652. } else {
  1653. xlat.push(Ext.applyIf({
  1654. type: 'keydown',
  1655. charCode: event.keyCode
  1656. }, event), Ext.applyIf({
  1657. type: 'keyup',
  1658. charCode: event.keyCode
  1659. }, event));
  1660. }
  1661. for (i = 0 , n = xlat.length; i < n; ++i) {
  1662. me.injectEvent(target, xlat[i]);
  1663. }
  1664. return true;
  1665. },
  1666. injectTypeInputEvent: function(target, event, handler) {
  1667. var me = this,
  1668. text = event.text,
  1669. sel, n;
  1670. if (handler) {
  1671. sel = me.getTextSelection(target);
  1672. if (text) {
  1673. n = sel[0];
  1674. target.value = target.value.substring(0, n) + text + target.value.substring(sel[1]);
  1675. n += text.length;
  1676. me.setTextSelection(target, n, n);
  1677. } else {
  1678. if (!(handler = handler[event.keyCode])) {
  1679. // no handler for the special key for this element
  1680. if ('caret' in event) {
  1681. me.setTextSelection(target, event.caret, event.caret);
  1682. } else if (event.selection) {
  1683. me.setTextSelection(target, event.selection[0], event.selection[1]);
  1684. }
  1685. return me.injectTypeEvent(target, event);
  1686. }
  1687. handler.call(this, target, sel[0], sel[1]);
  1688. return true;
  1689. }
  1690. }
  1691. return true;
  1692. },
  1693. playEvent: function(eventDescriptor) {
  1694. var me = this,
  1695. target = me.getElementFromXPath(eventDescriptor.target),
  1696. event;
  1697. if (!target) {
  1698. // not present (yet)... wait for element present...
  1699. // TODO - need a timeout here
  1700. return false;
  1701. }
  1702. if (!me.playEventHook(eventDescriptor, 'beforeplay')) {
  1703. return false;
  1704. }
  1705. if (!eventDescriptor.injected) {
  1706. eventDescriptor.injected = true;
  1707. event = me.translateEvent(eventDescriptor, target);
  1708. me.injectEvent(target, event);
  1709. }
  1710. return me.playEventHook(eventDescriptor, 'afterplay');
  1711. },
  1712. playEventHook: function(eventDescriptor, hookName) {
  1713. var me = this,
  1714. doneName = hookName + '.done',
  1715. firedName = hookName + '.fired',
  1716. hook = eventDescriptor[hookName];
  1717. if (hook && !eventDescriptor[doneName]) {
  1718. if (!eventDescriptor[firedName]) {
  1719. eventDescriptor[firedName] = true;
  1720. me.makeToken(eventDescriptor, doneName);
  1721. if (me.eventScope && Ext.isString(hook)) {
  1722. hook = me.eventScope[hook];
  1723. }
  1724. if (hook) {
  1725. hook.call(me.eventScope || me, eventDescriptor);
  1726. }
  1727. }
  1728. return false;
  1729. }
  1730. return true;
  1731. },
  1732. schedule: function() {
  1733. var me = this;
  1734. if (!me.timer) {
  1735. me.timer = Ext.defer(me.timerFn, 10);
  1736. }
  1737. },
  1738. _translateAcross: [
  1739. 'type',
  1740. 'button',
  1741. 'charCode',
  1742. 'keyCode',
  1743. 'caret',
  1744. 'pos',
  1745. 'text',
  1746. 'selection'
  1747. ],
  1748. translateEvent: function(eventDescriptor, target) {
  1749. var me = this,
  1750. event = {},
  1751. modKeys = eventDescriptor.modKeys || '',
  1752. names = me._translateAcross,
  1753. i = names.length,
  1754. name, xy;
  1755. while (i--) {
  1756. name = names[i];
  1757. if (name in eventDescriptor) {
  1758. event[name] = eventDescriptor[name];
  1759. }
  1760. }
  1761. event.altKey = modKeys.indexOf('A') > 0;
  1762. event.ctrlKey = modKeys.indexOf('C') > 0;
  1763. event.metaKey = modKeys.indexOf('M') > 0;
  1764. event.shiftKey = modKeys.indexOf('S') > 0;
  1765. if (target && 'x' in eventDescriptor) {
  1766. xy = Ext.fly(target).getXY();
  1767. xy[0] += eventDescriptor.x;
  1768. xy[1] += eventDescriptor.y;
  1769. } else if ('x' in eventDescriptor) {
  1770. xy = [
  1771. eventDescriptor.x,
  1772. eventDescriptor.y
  1773. ];
  1774. } else if ('px' in eventDescriptor) {
  1775. xy = [
  1776. eventDescriptor.px,
  1777. eventDescriptor.py
  1778. ];
  1779. }
  1780. if (xy) {
  1781. event.clientX = event.screenX = xy[0];
  1782. event.clientY = event.screenY = xy[1];
  1783. }
  1784. if (eventDescriptor.key) {
  1785. event.keyCode = me.specialKeysByName[eventDescriptor.key];
  1786. }
  1787. if (eventDescriptor.type === 'wheel') {
  1788. if ('onwheel' in me.attachTo.document) {
  1789. event.wheelX = eventDescriptor.dx;
  1790. event.wheelY = eventDescriptor.dy;
  1791. } else {
  1792. event.type = 'mousewheel';
  1793. event.wheelDeltaX = -40 * eventDescriptor.dx;
  1794. event.wheelDeltaY = event.wheelDelta = -40 * eventDescriptor.dy;
  1795. }
  1796. }
  1797. return event;
  1798. },
  1799. //---------------------------------
  1800. // Driver overrides
  1801. onStart: function() {
  1802. var me = this;
  1803. me.queueIndex = 0;
  1804. me.schedule();
  1805. },
  1806. onStop: function() {
  1807. var me = this;
  1808. if (me.timer) {
  1809. Ext.undefer(me.timer);
  1810. me.timer = null;
  1811. }
  1812. },
  1813. //---------------------------------
  1814. onTick: function() {
  1815. var me = this;
  1816. me.timer = null;
  1817. if (me.processEvents()) {
  1818. me.schedule();
  1819. }
  1820. },
  1821. statics: {
  1822. ieButtonCodeMap: {
  1823. 0: 1,
  1824. 1: 4,
  1825. 2: 2
  1826. },
  1827. /**
  1828. * Injects a key event using the given event information to populate the event
  1829. * object.
  1830. *
  1831. * **Note:** `keydown` causes Safari 2.x to crash.
  1832. *
  1833. * @param {HTMLElement} target The target of the given event.
  1834. * @param {Object} options Object object containing all of the event injection
  1835. * options.
  1836. * @param {String} options.type The type of event to fire. This can be any one of
  1837. * the following: `keyup`, `keydown` and `keypress`.
  1838. * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.
  1839. * DOM Level 3 specifies that all key events bubble by default.
  1840. * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled
  1841. * using `preventDefault`. DOM Level 3 specifies that all key events can be
  1842. * cancelled.
  1843. * @param {Boolean} [options.ctrlKey=false] `true` if one of the CTRL keys is
  1844. * pressed while the event is firing.
  1845. * @param {Boolean} [options.altKey=false] `true` if one of the ALT keys is
  1846. * pressed while the event is firing.
  1847. * @param {Boolean} [options.shiftKey=false] `true` if one of the SHIFT keys is
  1848. * pressed while the event is firing.
  1849. * @param {Boolean} [options.metaKey=false] `true` if one of the META keys is
  1850. * pressed while the event is firing.
  1851. * @param {Number} [options.keyCode=0] The code for the key that is in use.
  1852. * @param {Number} [options.charCode=0] The Unicode code for the character
  1853. * associated with the key being used.
  1854. * @param {Window} [view=window] The view containing the target. This is typically
  1855. * the window object.
  1856. * @private
  1857. */
  1858. injectKeyEvent: function(target, options, view) {
  1859. var type = options.type,
  1860. customEvent = null;
  1861. if (type === 'textevent') {
  1862. type = 'keypress';
  1863. }
  1864. view = view || window;
  1865. //check for DOM-compliant browsers first
  1866. if (doc.createEvent) {
  1867. try {
  1868. customEvent = doc.createEvent("KeyEvents");
  1869. // Interesting problem: Firefox implemented a non-standard
  1870. // version of initKeyEvent() based on DOM Level 2 specs.
  1871. // Key event was removed from DOM Level 2 and re-introduced
  1872. // in DOM Level 3 with a different interface. Firefox is the
  1873. // only browser with any implementation of Key Events, so for
  1874. // now, assume it's Firefox if the above line doesn't error.
  1875. // @TODO: Decipher between Firefox's implementation and a correct one.
  1876. customEvent.initKeyEvent(type, options.bubbles, options.cancelable, view, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.keyCode, options.charCode);
  1877. } catch (ex) {
  1878. // If it got here, that means key events aren't officially supported.
  1879. // Safari/WebKit is a real problem now. WebKit 522 won't let you
  1880. // set keyCode, charCode, or other properties if you use a
  1881. // UIEvent, so we first must try to create a generic event. The
  1882. // fun part is that this will throw an error on Safari 2.x. The
  1883. // end result is that we need another try...catch statement just to
  1884. // deal with this mess.
  1885. try {
  1886. //try to create generic event - will fail in Safari 2.x
  1887. customEvent = doc.createEvent("Events");
  1888. } catch (uierror) {
  1889. //the above failed, so create a UIEvent for Safari 2.x
  1890. customEvent = doc.createEvent("UIEvents");
  1891. } finally {
  1892. customEvent.initEvent(type, options.bubbles, options.cancelable);
  1893. customEvent.view = view;
  1894. customEvent.altKey = options.altKey;
  1895. customEvent.ctrlKey = options.ctrlKey;
  1896. customEvent.shiftKey = options.shiftKey;
  1897. customEvent.metaKey = options.metaKey;
  1898. customEvent.keyCode = options.keyCode;
  1899. customEvent.charCode = options.charCode;
  1900. }
  1901. }
  1902. target.dispatchEvent(customEvent);
  1903. } else if (doc.createEventObject) {
  1904. //IE
  1905. customEvent = doc.createEventObject();
  1906. customEvent.bubbles = options.bubbles;
  1907. customEvent.cancelable = options.cancelable;
  1908. customEvent.view = view;
  1909. customEvent.ctrlKey = options.ctrlKey;
  1910. customEvent.altKey = options.altKey;
  1911. customEvent.shiftKey = options.shiftKey;
  1912. customEvent.metaKey = options.metaKey;
  1913. // IE doesn't support charCode explicitly. CharCode should
  1914. // take precedence over any keyCode value for accurate
  1915. // representation.
  1916. customEvent.keyCode = (options.charCode > 0) ? options.charCode : options.keyCode;
  1917. target.fireEvent("on" + type, customEvent);
  1918. } else {
  1919. return false;
  1920. }
  1921. return true;
  1922. },
  1923. /**
  1924. * Injects a mouse event using the given event information to populate the event
  1925. * object.
  1926. *
  1927. * @param {HTMLElement} target The target of the given event.
  1928. * @param {Object} options Object object containing all of the event injection
  1929. * options.
  1930. * @param {String} options.type The type of event to fire. This can be any one of
  1931. * the following: `click`, `dblclick`, `mousedown`, `mouseup`, `mouseout`,
  1932. * `mouseover` and `mousemove`.
  1933. * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.
  1934. * DOM Level 2 specifies that all mouse events bubble by default.
  1935. * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled
  1936. * using `preventDefault`. DOM Level 2 specifies that all mouse events except
  1937. * `mousemove` can be cancelled. This defaults to `false` for `mousemove`.
  1938. * @param {Boolean} [options.ctrlKey=false] `true` if one of the CTRL keys is
  1939. * pressed while the event is firing.
  1940. * @param {Boolean} [options.altKey=false] `true` if one of the ALT keys is
  1941. * pressed while the event is firing.
  1942. * @param {Boolean} [options.shiftKey=false] `true` if one of the SHIFT keys is
  1943. * pressed while the event is firing.
  1944. * @param {Boolean} [options.metaKey=false] `true` if one of the META keys is
  1945. * pressed while the event is firing.
  1946. * @param {Number} [options.detail=1] The number of times the mouse button has
  1947. * been used.
  1948. * @param {Number} [options.screenX=0] The x-coordinate on the screen at which point
  1949. * the event occurred.
  1950. * @param {Number} [options.screenY=0] The y-coordinate on the screen at which point
  1951. * the event occurred.
  1952. * @param {Number} [options.clientX=0] The x-coordinate on the client at which point
  1953. * the event occurred.
  1954. * @param {Number} [options.clientY=0] The y-coordinate on the client at which point
  1955. * the event occurred.
  1956. * @param {Number} [options.button=0] The button being pressed while the event is
  1957. * executing. The value should be 0 for the primary mouse button (typically the
  1958. * left button), 1 for the tertiary mouse button (typically the middle button),
  1959. * and 2 for the secondary mouse button (typically the right button).
  1960. * @param {HTMLElement} [options.relatedTarget=null] For `mouseout` events, this
  1961. * is the element that the mouse has moved to. For `mouseover` events, this is
  1962. * the element that the mouse has moved from. This argument is ignored for all
  1963. * other events.
  1964. * @param {Window} [view=window] The view containing the target. This is typically
  1965. * the window object.
  1966. * @private
  1967. */
  1968. injectMouseEvent: function(target, options, view) {
  1969. var type = options.type,
  1970. customEvent = null;
  1971. view = view || window;
  1972. //check for DOM-compliant browsers first
  1973. if (doc.createEvent) {
  1974. customEvent = doc.createEvent("MouseEvents");
  1975. //Safari 2.x (WebKit 418) still doesn't implement initMouseEvent()
  1976. if (customEvent.initMouseEvent) {
  1977. customEvent.initMouseEvent(type, options.bubbles, options.cancelable, view, options.detail, options.screenX, options.screenY, options.clientX, options.clientY, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, options.relatedTarget);
  1978. } else {
  1979. //Safari
  1980. //the closest thing available in Safari 2.x is UIEvents
  1981. customEvent = doc.createEvent("UIEvents");
  1982. customEvent.initEvent(type, options.bubbles, options.cancelable);
  1983. customEvent.view = view;
  1984. customEvent.detail = options.detail;
  1985. customEvent.screenX = options.screenX;
  1986. customEvent.screenY = options.screenY;
  1987. customEvent.clientX = options.clientX;
  1988. customEvent.clientY = options.clientY;
  1989. customEvent.ctrlKey = options.ctrlKey;
  1990. customEvent.altKey = options.altKey;
  1991. customEvent.metaKey = options.metaKey;
  1992. customEvent.shiftKey = options.shiftKey;
  1993. customEvent.button = options.button;
  1994. customEvent.relatedTarget = options.relatedTarget;
  1995. }
  1996. /*
  1997. * Check to see if relatedTarget has been assigned. Firefox
  1998. * versions less than 2.0 don't allow it to be assigned via
  1999. * initMouseEvent() and the property is readonly after event
  2000. * creation, so in order to keep YAHOO.util.getRelatedTarget()
  2001. * working, assign to the IE proprietary toElement property
  2002. * for mouseout event and fromElement property for mouseover
  2003. * event.
  2004. */
  2005. if (options.relatedTarget && !customEvent.relatedTarget) {
  2006. if (type == "mouseout") {
  2007. customEvent.toElement = options.relatedTarget;
  2008. } else if (type == "mouseover") {
  2009. customEvent.fromElement = options.relatedTarget;
  2010. }
  2011. }
  2012. target.dispatchEvent(customEvent);
  2013. } else if (doc.createEventObject) {
  2014. //IE
  2015. customEvent = doc.createEventObject();
  2016. customEvent.bubbles = options.bubbles;
  2017. customEvent.cancelable = options.cancelable;
  2018. customEvent.view = view;
  2019. customEvent.detail = options.detail;
  2020. customEvent.screenX = options.screenX;
  2021. customEvent.screenY = options.screenY;
  2022. customEvent.clientX = options.clientX;
  2023. customEvent.clientY = options.clientY;
  2024. customEvent.ctrlKey = options.ctrlKey;
  2025. customEvent.altKey = options.altKey;
  2026. customEvent.metaKey = options.metaKey;
  2027. customEvent.shiftKey = options.shiftKey;
  2028. customEvent.button = Player.ieButtonCodeMap[options.button] || 0;
  2029. /*
  2030. * Have to use relatedTarget because IE won't allow assignment
  2031. * to toElement or fromElement on generic events. This keeps
  2032. * YAHOO.util.customEvent.getRelatedTarget() functional.
  2033. */
  2034. customEvent.relatedTarget = options.relatedTarget;
  2035. target.fireEvent('on' + type, customEvent);
  2036. } else {
  2037. return false;
  2038. }
  2039. return true;
  2040. },
  2041. /**
  2042. * Injects a UI event using the given event information to populate the event
  2043. * object.
  2044. *
  2045. * @param {HTMLElement} target The target of the given event.
  2046. * @param {Object} options
  2047. * @param {String} options.type The type of event to fire. This can be any one of
  2048. * the following: `click`, `dblclick`, `mousedown`, `mouseup`, `mouseout`,
  2049. * `mouseover` and `mousemove`.
  2050. * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.
  2051. * DOM Level 2 specifies that all mouse events bubble by default.
  2052. * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled
  2053. * using `preventDefault`. DOM Level 2 specifies that all mouse events except
  2054. * `mousemove` can be canceled. This defaults to `false` for `mousemove`.
  2055. * @param {Number} [options.detail=1] The number of times the mouse button has been
  2056. * used.
  2057. * @param {Window} [view=window] The view containing the target. This is typically
  2058. * the window object.
  2059. * @private
  2060. */
  2061. injectUIEvent: function(target, options, view) {
  2062. var customEvent = null;
  2063. view = view || window;
  2064. //check for DOM-compliant browsers first
  2065. if (doc.createEvent) {
  2066. //just a generic UI Event object is needed
  2067. customEvent = doc.createEvent("UIEvents");
  2068. customEvent.initUIEvent(options.type, options.bubbles, options.cancelable, view, options.detail);
  2069. target.dispatchEvent(customEvent);
  2070. } else if (doc.createEventObject) {
  2071. //IE
  2072. customEvent = doc.createEventObject();
  2073. customEvent.bubbles = options.bubbles;
  2074. customEvent.cancelable = options.cancelable;
  2075. customEvent.view = view;
  2076. customEvent.detail = options.detail;
  2077. target.fireEvent("on" + options.type, customEvent);
  2078. } else {
  2079. return false;
  2080. }
  2081. return true;
  2082. }
  2083. }
  2084. };
  2085. });
  2086. // statics
  2087. /**
  2088. * @extends Ext.ux.event.Driver
  2089. * Event recorder.
  2090. */
  2091. Ext.define('Ext.ux.event.Recorder', function(Recorder) {
  2092. function apply() {
  2093. var a = arguments,
  2094. n = a.length,
  2095. obj = {
  2096. kind: 'other'
  2097. },
  2098. i;
  2099. for (i = 0; i < n; ++i) {
  2100. Ext.apply(obj, arguments[i]);
  2101. }
  2102. if (obj.alt && !obj.event) {
  2103. obj.event = obj.alt;
  2104. }
  2105. return obj;
  2106. }
  2107. function key(extra) {
  2108. return apply({
  2109. kind: 'keyboard',
  2110. modKeys: true,
  2111. key: true
  2112. }, extra);
  2113. }
  2114. function mouse(extra) {
  2115. return apply({
  2116. kind: 'mouse',
  2117. button: true,
  2118. modKeys: true,
  2119. xy: true
  2120. }, extra);
  2121. }
  2122. var eventsToRecord = {
  2123. keydown: key(),
  2124. keypress: key(),
  2125. keyup: key(),
  2126. dragmove: mouse({
  2127. alt: 'mousemove',
  2128. pageCoords: true,
  2129. whileDrag: true
  2130. }),
  2131. mousemove: mouse({
  2132. pageCoords: true
  2133. }),
  2134. mouseover: mouse(),
  2135. mouseout: mouse(),
  2136. click: mouse(),
  2137. wheel: mouse({
  2138. wheel: true
  2139. }),
  2140. mousedown: mouse({
  2141. press: true
  2142. }),
  2143. mouseup: mouse({
  2144. release: true
  2145. }),
  2146. scroll: apply({
  2147. listen: false
  2148. }),
  2149. focus: apply(),
  2150. blur: apply()
  2151. };
  2152. for (var key in eventsToRecord) {
  2153. if (!eventsToRecord[key].event) {
  2154. eventsToRecord[key].event = key;
  2155. }
  2156. }
  2157. eventsToRecord.wheel.event = null;
  2158. // must detect later
  2159. return {
  2160. extend: 'Ext.ux.event.Driver',
  2161. /**
  2162. * @event add
  2163. * Fires when an event is added to the recording.
  2164. * @param {Ext.ux.event.Recorder} this
  2165. * @param {Object} eventDescriptor The event descriptor.
  2166. */
  2167. /**
  2168. * @event coalesce
  2169. * Fires when an event is coalesced. This edits the tail of the recorded
  2170. * event list.
  2171. * @param {Ext.ux.event.Recorder} this
  2172. * @param {Object} eventDescriptor The event descriptor that was coalesced.
  2173. */
  2174. eventsToRecord: eventsToRecord,
  2175. ignoreIdRegEx: /ext-gen(?:\d+)/,
  2176. inputRe: /^(input|textarea)$/i,
  2177. constructor: function(config) {
  2178. var me = this,
  2179. events = config && config.eventsToRecord;
  2180. if (events) {
  2181. me.eventsToRecord = Ext.apply(Ext.apply({}, me.eventsToRecord), // duplicate
  2182. events);
  2183. // and merge
  2184. delete config.eventsToRecord;
  2185. }
  2186. // don't smash
  2187. me.callParent(arguments);
  2188. me.clear();
  2189. me.modKeys = [];
  2190. me.attachTo = me.attachTo || window;
  2191. },
  2192. clear: function() {
  2193. this.eventsRecorded = [];
  2194. },
  2195. listenToEvent: function(event) {
  2196. var me = this,
  2197. el = me.attachTo.document.body,
  2198. fn = function() {
  2199. return me.onEvent.apply(me, arguments);
  2200. },
  2201. cleaner = {};
  2202. if (el.attachEvent && el.ownerDocument.documentMode < 10) {
  2203. event = 'on' + event;
  2204. el.attachEvent(event, fn);
  2205. cleaner.destroy = function() {
  2206. if (fn) {
  2207. el.detachEvent(event, fn);
  2208. fn = null;
  2209. }
  2210. };
  2211. } else {
  2212. el.addEventListener(event, fn, true);
  2213. cleaner.destroy = function() {
  2214. if (fn) {
  2215. el.removeEventListener(event, fn, true);
  2216. fn = null;
  2217. }
  2218. };
  2219. }
  2220. return cleaner;
  2221. },
  2222. coalesce: function(rec, ev) {
  2223. var me = this,
  2224. events = me.eventsRecorded,
  2225. length = events.length,
  2226. tail = length && events[length - 1],
  2227. tail2 = (length > 1) && events[length - 2],
  2228. tail3 = (length > 2) && events[length - 3];
  2229. if (!tail) {
  2230. return false;
  2231. }
  2232. if (rec.type === 'mousemove') {
  2233. if (tail.type === 'mousemove' && rec.ts - tail.ts < 200) {
  2234. rec.ts = tail.ts;
  2235. events[length - 1] = rec;
  2236. return true;
  2237. }
  2238. } else if (rec.type === 'click') {
  2239. if (tail2 && tail.type === 'mouseup' && tail2.type === 'mousedown') {
  2240. if (rec.button == tail.button && rec.button == tail2.button && rec.target == tail.target && rec.target == tail2.target && me.samePt(rec, tail) && me.samePt(rec, tail2)) {
  2241. events.pop();
  2242. // remove mouseup
  2243. tail2.type = 'mduclick';
  2244. return true;
  2245. }
  2246. }
  2247. } else if (rec.type === 'keyup') {
  2248. // tail3 = { type: "type", text: "..." },
  2249. // tail2 = { type: "keydown", charCode: 65, keyCode: 65 },
  2250. // tail = { type: "keypress", charCode: 97, keyCode: 97 },
  2251. // rec = { type: "keyup", charCode: 65, keyCode: 65 },
  2252. if (tail2 && tail.type === 'keypress' && tail2.type === 'keydown') {
  2253. if (rec.target === tail.target && rec.target === tail2.target) {
  2254. events.pop();
  2255. // remove keypress
  2256. tail2.type = 'type';
  2257. tail2.text = String.fromCharCode(tail.charCode);
  2258. delete tail2.charCode;
  2259. delete tail2.keyCode;
  2260. if (tail3 && tail3.type === 'type') {
  2261. if (tail3.text && tail3.target === tail2.target) {
  2262. tail3.text += tail2.text;
  2263. events.pop();
  2264. }
  2265. }
  2266. return true;
  2267. }
  2268. }
  2269. // tail = { type: "keydown", charCode: 40, keyCode: 40 },
  2270. // rec = { type: "keyup", charCode: 40, keyCode: 40 },
  2271. else if (me.completeKeyStroke(tail, rec)) {
  2272. tail.type = 'type';
  2273. me.completeSpecialKeyStroke(ev.target, tail, rec);
  2274. return true;
  2275. }
  2276. // tail2 = { type: "keydown", charCode: 40, keyCode: 40 },
  2277. // tail = { type: "scroll", ... },
  2278. // rec = { type: "keyup", charCode: 40, keyCode: 40 },
  2279. else if (tail.type === 'scroll' && me.completeKeyStroke(tail2, rec)) {
  2280. tail2.type = 'type';
  2281. me.completeSpecialKeyStroke(ev.target, tail2, rec);
  2282. // swap the order of type and scroll events
  2283. events.pop();
  2284. events.pop();
  2285. events.push(tail, tail2);
  2286. return true;
  2287. }
  2288. }
  2289. return false;
  2290. },
  2291. completeKeyStroke: function(down, up) {
  2292. if (down && down.type === 'keydown' && down.keyCode === up.keyCode) {
  2293. delete down.charCode;
  2294. return true;
  2295. }
  2296. return false;
  2297. },
  2298. completeSpecialKeyStroke: function(target, down, up) {
  2299. var key = this.specialKeysByCode[up.keyCode];
  2300. if (key && this.inputRe.test(target.tagName)) {
  2301. // home,end,arrow keys + shift get crazy, so encode selection/caret
  2302. delete down.keyCode;
  2303. down.key = key;
  2304. down.selection = this.getTextSelection(target);
  2305. if (down.selection[0] === down.selection[1]) {
  2306. down.caret = down.selection[0];
  2307. delete down.selection;
  2308. }
  2309. return true;
  2310. }
  2311. return false;
  2312. },
  2313. getElementXPath: function(el) {
  2314. var me = this,
  2315. good = false,
  2316. xpath = [],
  2317. count, sibling, t, tag;
  2318. for (t = el; t; t = t.parentNode) {
  2319. if (t == me.attachTo.document.body) {
  2320. xpath.unshift('~');
  2321. good = true;
  2322. break;
  2323. }
  2324. if (t.id && !me.ignoreIdRegEx.test(t.id)) {
  2325. xpath.unshift('#' + t.id);
  2326. good = true;
  2327. break;
  2328. }
  2329. for (count = 1 , sibling = t; !!(sibling = sibling.previousSibling); ) {
  2330. if (sibling.tagName == t.tagName) {
  2331. ++count;
  2332. }
  2333. }
  2334. tag = t.tagName.toLowerCase();
  2335. if (count < 2) {
  2336. xpath.unshift(tag);
  2337. } else {
  2338. xpath.unshift(tag + '[' + count + ']');
  2339. }
  2340. }
  2341. return good ? xpath.join('/') : null;
  2342. },
  2343. getRecordedEvents: function() {
  2344. return this.eventsRecorded;
  2345. },
  2346. onEvent: function(ev) {
  2347. var me = this,
  2348. e = new Ext.event.Event(ev),
  2349. info = me.eventsToRecord[e.type],
  2350. root, modKeys, elXY,
  2351. rec = {
  2352. type: e.type,
  2353. ts: me.getTimestamp(),
  2354. target: me.getElementXPath(e.target)
  2355. },
  2356. xy;
  2357. if (!info || !rec.target) {
  2358. return;
  2359. }
  2360. root = e.target.ownerDocument;
  2361. root = root.defaultView || root.parentWindow;
  2362. // Standards || IE
  2363. if (root !== me.attachTo) {
  2364. return;
  2365. }
  2366. if (me.eventsToRecord.scroll) {
  2367. me.syncScroll(e.target);
  2368. }
  2369. if (info.xy) {
  2370. xy = e.getXY();
  2371. if (info.pageCoords || !rec.target) {
  2372. rec.px = xy[0];
  2373. rec.py = xy[1];
  2374. } else {
  2375. elXY = Ext.fly(e.getTarget()).getXY();
  2376. xy[0] -= elXY[0];
  2377. xy[1] -= elXY[1];
  2378. rec.x = xy[0];
  2379. rec.y = xy[1];
  2380. }
  2381. }
  2382. if (info.button) {
  2383. if ('buttons' in ev) {
  2384. rec.button = ev.buttons;
  2385. } else // LEFT=1, RIGHT=2, MIDDLE=4, etc.
  2386. {
  2387. rec.button = ev.button;
  2388. }
  2389. if (!rec.button && info.whileDrag) {
  2390. return;
  2391. }
  2392. }
  2393. if (info.wheel) {
  2394. rec.type = 'wheel';
  2395. if (info.event === 'wheel') {
  2396. // Current FireFox (technically IE9+ if we use addEventListener but
  2397. // checking document.onwheel does not detect this)
  2398. rec.dx = ev.deltaX;
  2399. rec.dy = ev.deltaY;
  2400. } else if (typeof ev.wheelDeltaX === 'number') {
  2401. // new WebKit has both X & Y
  2402. rec.dx = -1 / 40 * ev.wheelDeltaX;
  2403. rec.dy = -1 / 40 * ev.wheelDeltaY;
  2404. } else if (ev.wheelDelta) {
  2405. // old WebKit and IE
  2406. rec.dy = -1 / 40 * ev.wheelDelta;
  2407. } else if (ev.detail) {
  2408. // Old Gecko
  2409. rec.dy = ev.detail;
  2410. }
  2411. }
  2412. if (info.modKeys) {
  2413. me.modKeys[0] = e.altKey ? 'A' : '';
  2414. me.modKeys[1] = e.ctrlKey ? 'C' : '';
  2415. me.modKeys[2] = e.metaKey ? 'M' : '';
  2416. me.modKeys[3] = e.shiftKey ? 'S' : '';
  2417. modKeys = me.modKeys.join('');
  2418. if (modKeys) {
  2419. rec.modKeys = modKeys;
  2420. }
  2421. }
  2422. if (info.key) {
  2423. rec.charCode = e.getCharCode();
  2424. rec.keyCode = e.getKey();
  2425. }
  2426. if (me.coalesce(rec, e)) {
  2427. me.fireEvent('coalesce', me, rec);
  2428. } else {
  2429. me.eventsRecorded.push(rec);
  2430. me.fireEvent('add', me, rec);
  2431. }
  2432. },
  2433. onStart: function() {
  2434. var me = this,
  2435. ddm = me.attachTo.Ext.dd.DragDropManager,
  2436. evproto = me.attachTo.Ext.EventObjectImpl.prototype,
  2437. special = [];
  2438. // FireFox does not support the 'mousewheel' event but does support the
  2439. // 'wheel' event instead.
  2440. Recorder.prototype.eventsToRecord.wheel.event = ('onwheel' in me.attachTo.document) ? 'wheel' : 'mousewheel';
  2441. me.listeners = [];
  2442. Ext.Object.each(me.eventsToRecord, function(name, value) {
  2443. if (value && value.listen !== false) {
  2444. if (!value.event) {
  2445. value.event = name;
  2446. }
  2447. if (value.alt && value.alt !== name) {
  2448. // The 'drag' event is just mousemove while buttons are pressed,
  2449. // so if there is a mousemove entry as well, ignore the drag
  2450. if (!me.eventsToRecord[value.alt]) {
  2451. special.push(value);
  2452. }
  2453. } else {
  2454. me.listeners.push(me.listenToEvent(value.event));
  2455. }
  2456. }
  2457. });
  2458. Ext.each(special, function(info) {
  2459. me.eventsToRecord[info.alt] = info;
  2460. me.listeners.push(me.listenToEvent(info.alt));
  2461. });
  2462. me.ddmStopEvent = ddm.stopEvent;
  2463. ddm.stopEvent = Ext.Function.createSequence(ddm.stopEvent, function(e) {
  2464. me.onEvent(e);
  2465. });
  2466. me.evStopEvent = evproto.stopEvent;
  2467. evproto.stopEvent = Ext.Function.createSequence(evproto.stopEvent, function() {
  2468. me.onEvent(this);
  2469. });
  2470. },
  2471. onStop: function() {
  2472. var me = this;
  2473. Ext.destroy(me.listeners);
  2474. me.listeners = null;
  2475. me.attachTo.Ext.dd.DragDropManager.stopEvent = me.ddmStopEvent;
  2476. me.attachTo.Ext.EventObjectImpl.prototype.stopEvent = me.evStopEvent;
  2477. },
  2478. samePt: function(pt1, pt2) {
  2479. return pt1.x == pt2.x && pt1.y == pt2.y;
  2480. },
  2481. syncScroll: function(el) {
  2482. var me = this,
  2483. ts = me.getTimestamp(),
  2484. oldX, oldY, x, y, scrolled, rec;
  2485. for (var p = el; p; p = p.parentNode) {
  2486. oldX = p.$lastScrollLeft;
  2487. oldY = p.$lastScrollTop;
  2488. x = p.scrollLeft;
  2489. y = p.scrollTop;
  2490. scrolled = false;
  2491. if (oldX !== x) {
  2492. if (x) {
  2493. scrolled = true;
  2494. }
  2495. p.$lastScrollLeft = x;
  2496. }
  2497. if (oldY !== y) {
  2498. if (y) {
  2499. scrolled = true;
  2500. }
  2501. p.$lastScrollTop = y;
  2502. }
  2503. if (scrolled) {
  2504. //console.log('scroll x:' + x + ' y:' + y, p);
  2505. me.eventsRecorded.push(rec = {
  2506. type: 'scroll',
  2507. target: me.getElementXPath(p),
  2508. ts: ts,
  2509. pos: [
  2510. x,
  2511. y
  2512. ]
  2513. });
  2514. me.fireEvent('add', me, rec);
  2515. }
  2516. if (p.tagName === 'BODY') {
  2517. break;
  2518. }
  2519. }
  2520. }
  2521. };
  2522. });
  2523. /**
  2524. * Describes a gauge needle as a shape defined in SVG path syntax.
  2525. *
  2526. * Note: this class and its subclasses are not supposed to be instantiated directly
  2527. * - an object should be passed the gauge's {@link Ext.ux.gauge.Gauge#needle}
  2528. * config instead. Needle instances are also not supposed to be moved
  2529. * between gauges.
  2530. */
  2531. Ext.define('Ext.ux.gauge.needle.Abstract', {
  2532. mixins: [
  2533. 'Ext.mixin.Factoryable'
  2534. ],
  2535. alias: 'gauge.needle.abstract',
  2536. isNeedle: true,
  2537. config: {
  2538. /**
  2539. * The generator function for the needle's shape.
  2540. * Because the gauge component is resizable, and it is generally
  2541. * desirable to resize the needle along with the gauge, the needle's
  2542. * shape should have an ability to grow, typically non-uniformly,
  2543. * which necessitates a generator function that will update the needle's
  2544. * path, so that its proportions are appropriate for the current gauge size.
  2545. *
  2546. * The generator function is given two parameters: the inner and outer
  2547. * radius of the needle. For example, for a straight arrow, the path
  2548. * definition is expected to have the base of the needle at the origin
  2549. * - (0, 0) coordinates - and point downwards. The needle will be automatically
  2550. * translated to the center of the gauge and rotated to represent the current
  2551. * gauge {@link Ext.ux.gauge.Gauge#value value}.
  2552. *
  2553. * @param {Function} path The path generator function.
  2554. * @param {Number} path.innerRadius The function's first parameter.
  2555. * @param {Number} path.outerRadius The function's second parameter.
  2556. * @return {String} path.return The shape of the needle in the SVG path syntax returned by
  2557. * the generator function.
  2558. */
  2559. path: null,
  2560. /**
  2561. * The inner radius of the needle. This works just like the `innerRadius`
  2562. * config of the {@link Ext.ux.gauge.Gauge#trackStyle}.
  2563. * The default value is `25` to make sure the needle doesn't overlap with
  2564. * the value of the gauge shown at its center by default.
  2565. *
  2566. * @param {Number/String} [innerRadius=25]
  2567. */
  2568. innerRadius: 25,
  2569. /**
  2570. * The outer radius of the needle. This works just like the `outerRadius`
  2571. * config of the {@link Ext.ux.gauge.Gauge#trackStyle}.
  2572. *
  2573. * @param {Number/String} [outerRadius='100% - 20']
  2574. */
  2575. outerRadius: '100% - 20',
  2576. /**
  2577. * The shape generated by the {@link #path} function is used as the value
  2578. * for the `d` attribute of the SVG `<path>` element. This element
  2579. * has the default class name of `.x-gauge-needle`, so that CSS can be used
  2580. * to give all gauge needles some common styling. To style a particular needle,
  2581. * one can use this config to add styles to the needle's `<path>` element directly,
  2582. * or use a custom {@link Ext.ux.gauge.Gauge#cls class} for the needle's gauge
  2583. * and style the needle from there.
  2584. *
  2585. * This config is not supposed to be updated manually, the styles should
  2586. * always be updated by the means of the `setStyle` call. For example,
  2587. * this is not allowed:
  2588. *
  2589. * gauge.getStyle().fill = 'red'; // WRONG!
  2590. * gauge.setStyle({ 'fill': 'red' }); // correct
  2591. *
  2592. * Subsequent calls to the `setStyle` will add to the styles set previously
  2593. * or overwrite their values, but won't remove them. If you'd like to style
  2594. * from a clean slate, setting the style to `null` first will remove the styles
  2595. * previously set:
  2596. *
  2597. * gauge.getNeedle().setStyle(null);
  2598. *
  2599. * If an SVG shape was produced by a designer rather than programmatically,
  2600. * in other words, the {@link #path} function returns the same shape regardless
  2601. * of the parameters it was given, the uniform scaling of said shape is the only
  2602. * option, if one wants to use gauges of different sizes. In this case,
  2603. * it's possible to specify the desired scale by using the `transform` style,
  2604. * for example:
  2605. *
  2606. * transform: 'scale(0.35)'
  2607. *
  2608. * @param {Object} style
  2609. */
  2610. style: null,
  2611. /**
  2612. * @private
  2613. * @param {Number} radius
  2614. */
  2615. radius: 0,
  2616. /**
  2617. * @private
  2618. * Expected in the initial config, required during construction.
  2619. * @param {Ext.ux.gauge.Gauge} gauge
  2620. */
  2621. gauge: null
  2622. },
  2623. constructor: function(config) {
  2624. this.initConfig(config);
  2625. },
  2626. applyInnerRadius: function(innerRadius) {
  2627. return this.getGauge().getRadiusFn(innerRadius);
  2628. },
  2629. applyOuterRadius: function(outerRadius) {
  2630. return this.getGauge().getRadiusFn(outerRadius);
  2631. },
  2632. updateRadius: function() {
  2633. this.regeneratePath();
  2634. },
  2635. setTransform: function(centerX, centerY, rotation) {
  2636. var needleGroup = this.getNeedleGroup();
  2637. needleGroup.setStyle('transform', 'translate(' + centerX + 'px,' + centerY + 'px) ' + 'rotate(' + rotation + 'deg)');
  2638. },
  2639. applyPath: function(path) {
  2640. return Ext.isFunction(path) ? path : null;
  2641. },
  2642. updatePath: function(path) {
  2643. this.regeneratePath(path);
  2644. },
  2645. regeneratePath: function(path) {
  2646. path = path || this.getPath();
  2647. var me = this,
  2648. radius = me.getRadius(),
  2649. inner = me.getInnerRadius()(radius),
  2650. outer = me.getOuterRadius()(radius),
  2651. d = outer > inner ? path(inner, outer) : '';
  2652. me.getNeedlePath().dom.setAttribute('d', d);
  2653. },
  2654. getNeedleGroup: function() {
  2655. var gauge = this.getGauge(),
  2656. group = this.needleGroup;
  2657. // The gauge positions the needle by calling its `setTransform` method,
  2658. // which applies a transformation to the needle's group, that contains
  2659. // the actual path element. This is done because we need the ability to
  2660. // transform the path independently from it's position in the gauge.
  2661. // For example, if the needle has to be made bigger, is shouldn't be
  2662. // part of the transform that centers it in the gauge and rotates it
  2663. // to point at the current value.
  2664. if (!group) {
  2665. group = this.needleGroup = Ext.get(document.createElementNS(gauge.svgNS, 'g'));
  2666. gauge.getSvg().appendChild(group);
  2667. }
  2668. return group;
  2669. },
  2670. getNeedlePath: function() {
  2671. var me = this,
  2672. pathElement = me.pathElement;
  2673. if (!pathElement) {
  2674. pathElement = me.pathElement = Ext.get(document.createElementNS(me.getGauge().svgNS, 'path'));
  2675. pathElement.dom.setAttribute('class', Ext.baseCSSPrefix + 'gauge-needle');
  2676. me.getNeedleGroup().appendChild(pathElement);
  2677. }
  2678. return pathElement;
  2679. },
  2680. updateStyle: function(style) {
  2681. var pathElement = this.getNeedlePath();
  2682. // Note that we are setting the `style` attribute, e.g `style="fill: red"`,
  2683. // instead of path attributes individually, e.g. `fill="red"` because
  2684. // the attribute styles defined in CSS classes will override the values
  2685. // of attributes set on the elements individually.
  2686. if (Ext.isObject(style)) {
  2687. pathElement.setStyle(style);
  2688. } else {
  2689. pathElement.dom.removeAttribute('style');
  2690. }
  2691. },
  2692. destroy: function() {
  2693. var me = this;
  2694. me.pathElement = Ext.destroy(me.pathElement);
  2695. me.needleGroup = Ext.destroy(me.needleGroup);
  2696. me.setGauge(null);
  2697. }
  2698. });
  2699. /**
  2700. * Displays a value within the given interval as a gauge. For example:
  2701. *
  2702. * @example
  2703. * Ext.create({
  2704. * xtype: 'panel',
  2705. * renderTo: document.body,
  2706. * width: 200,
  2707. * height: 200,
  2708. * layout: 'fit',
  2709. * items: {
  2710. * xtype: 'gauge',
  2711. * padding: 20,
  2712. * value: 55,
  2713. * minValue: 40,
  2714. * maxValue: 80
  2715. * }
  2716. * });
  2717. *
  2718. * It's also possible to use gauges to create loading indicators:
  2719. *
  2720. * @example
  2721. * Ext.create({
  2722. * xtype: 'panel',
  2723. * renderTo: document.body,
  2724. * width: 200,
  2725. * height: 200,
  2726. * layout: 'fit',
  2727. * items: {
  2728. * xtype: 'gauge',
  2729. * padding: 20,
  2730. * trackStart: 0,
  2731. * trackLength: 360,
  2732. * value: 20,
  2733. * valueStyle: {
  2734. * round: true
  2735. * },
  2736. * textTpl: 'Loading...',
  2737. * animation: {
  2738. * easing: 'linear',
  2739. * duration: 100000
  2740. * }
  2741. * }
  2742. * }).items.first().setAngleOffset(360 * 100);
  2743. *
  2744. * Gauges can contain needles as well.
  2745. *
  2746. * @example
  2747. * Ext.create({
  2748. * xtype: 'panel',
  2749. * renderTo: document.body,
  2750. * width: 200,
  2751. * height: 200,
  2752. * layout: 'fit',
  2753. * items: {
  2754. * xtype: 'gauge',
  2755. * padding: 20,
  2756. * value: 55,
  2757. * minValue: 40,
  2758. * maxValue: 80,
  2759. * needle: 'wedge'
  2760. * }
  2761. * });
  2762. *
  2763. */
  2764. Ext.define('Ext.ux.gauge.Gauge', {
  2765. alternateClassName: 'Ext.ux.Gauge',
  2766. extend: 'Ext.Gadget',
  2767. xtype: 'gauge',
  2768. requires: [
  2769. 'Ext.ux.gauge.needle.Abstract',
  2770. 'Ext.util.Region'
  2771. ],
  2772. config: {
  2773. /**
  2774. * @cfg {Number/String} padding
  2775. * Gauge sector padding in pixels or percent of width/height, whichever is smaller.
  2776. */
  2777. padding: 10,
  2778. /**
  2779. * @cfg {Number} trackStart
  2780. * The angle in the [0, 360) interval at which the gauge's track sector starts.
  2781. * E.g. 0 for 3 o-clock, 90 for 6 o-clock, 180 for 9 o-clock, 270 for noon.
  2782. */
  2783. trackStart: 135,
  2784. /**
  2785. * @cfg {Number} trackLength
  2786. * The angle in the (0, 360] interval to add to the {@link #trackStart} angle
  2787. * to determine the angle at which the track ends.
  2788. */
  2789. trackLength: 270,
  2790. /**
  2791. * @cfg {Number} angleOffset
  2792. * The angle at which the {@link #minValue} starts in case of a circular gauge.
  2793. */
  2794. angleOffset: 0,
  2795. /**
  2796. * @cfg {Number} minValue
  2797. * The minimum value that the gauge can represent.
  2798. */
  2799. minValue: 0,
  2800. /**
  2801. * @cfg {Number} maxValue
  2802. * The maximum value that the gauge can represent.
  2803. */
  2804. maxValue: 100,
  2805. /**
  2806. * @cfg {Number} value
  2807. * The current value of the gauge.
  2808. */
  2809. value: 50,
  2810. /**
  2811. * @cfg {Ext.ux.gauge.needle.Abstract} needle
  2812. * A config object for the needle to be used by the gauge.
  2813. * The needle will track the current {@link #value}.
  2814. * The default needle type is 'diamond', so if a config like
  2815. *
  2816. * needle: {
  2817. * outerRadius: '100%'
  2818. * }
  2819. *
  2820. * is used, the app/view still has to require
  2821. * the `Ext.ux.gauge.needle.Diamond` class.
  2822. * If a type is specified explicitly
  2823. *
  2824. * needle: {
  2825. * type: 'arrow'
  2826. * }
  2827. *
  2828. * it's straightforward which class should be required.
  2829. */
  2830. needle: null,
  2831. needleDefaults: {
  2832. cached: true,
  2833. $value: {
  2834. type: 'diamond'
  2835. }
  2836. },
  2837. /**
  2838. * @cfg {Boolean} [clockwise=true]
  2839. * `true` - {@link #cfg!value} increments in a clockwise fashion
  2840. * `false` - {@link #cfg!value} increments in an anticlockwise fashion
  2841. */
  2842. clockwise: true,
  2843. /**
  2844. * @cfg {Ext.XTemplate} textTpl
  2845. * The template for the text in the center of the gauge.
  2846. * The available data values are:
  2847. * - `value` - The {@link #cfg!value} of the gauge.
  2848. * - `percent` - The value as a percentage between 0 and 100.
  2849. * - `minValue` - The value of the {@link #cfg!minValue} config.
  2850. * - `maxValue` - The value of the {@link #cfg!maxValue} config.
  2851. * - `delta` - The delta between the {@link #cfg!minValue} and {@link #cfg!maxValue}.
  2852. */
  2853. textTpl: [
  2854. '<tpl>{value:number("0.00")}%</tpl>'
  2855. ],
  2856. /**
  2857. * @cfg {String} [textAlign='c-c']
  2858. * If the gauge has a donut hole, the text will be centered inside it.
  2859. * Otherwise, the text will be centered in the middle of the gauge's
  2860. * bounding box. This config allows to alter the position of the text
  2861. * in the latter case. See the docs for the `align` option to the
  2862. * {@link Ext.util.Region#alignTo} method for possible ways of alignment
  2863. * of the text to the guage's bounding box.
  2864. */
  2865. textAlign: 'c-c',
  2866. /**
  2867. * @cfg {Object} textOffset
  2868. * This config can be used to displace the {@link #textTpl text} from its default
  2869. * position in the center of the gauge by providing values for horizontal and
  2870. * vertical displacement.
  2871. * @cfg {Number} textOffset.dx Horizontal displacement.
  2872. * @cfg {Number} textOffset.dy Vertical displacement.
  2873. */
  2874. textOffset: {
  2875. dx: 0,
  2876. dy: 0
  2877. },
  2878. /**
  2879. * @cfg {Object} trackStyle
  2880. * Track sector styles.
  2881. * @cfg {String/Object[]} trackStyle.fill Track sector fill color. Defaults to CSS value.
  2882. * It's also possible to have a linear gradient fill that starts at the top-left corner
  2883. * of the gauge and ends at its bottom-right corner, by providing an array of color stop
  2884. * objects. For example:
  2885. *
  2886. * trackStyle: {
  2887. * fill: [{
  2888. * offset: 0,
  2889. * color: 'green',
  2890. * opacity: 0.8
  2891. * }, {
  2892. * offset: 1,
  2893. * color: 'gold'
  2894. * }]
  2895. * }
  2896. *
  2897. * @cfg {Number} trackStyle.fillOpacity Track sector fill opacity. Defaults to CSS value.
  2898. * @cfg {String} trackStyle.stroke Track sector stroke color. Defaults to CSS value.
  2899. * @cfg {Number} trackStyle.strokeOpacity Track sector stroke opacity. Defaults to CSS value.
  2900. * @cfg {Number} trackStyle.strokeWidth Track sector stroke width. Defaults to CSS value.
  2901. * @cfg {Number/String} [trackStyle.outerRadius='100%'] The outer radius of the track sector.
  2902. * For example:
  2903. *
  2904. * outerRadius: '90%', // 90% of the maximum radius
  2905. * outerRadius: 100, // radius of 100 pixels
  2906. * outerRadius: '70% + 5', // 70% of the maximum radius plus 5 pixels
  2907. * outerRadius: '80% - 10', // 80% of the maximum radius minus 10 pixels
  2908. *
  2909. * @cfg {Number/String} [trackStyle.innerRadius='50%'] The inner radius of the track sector.
  2910. * See the `trackStyle.outerRadius` config documentation for more information.
  2911. * @cfg {Boolean} [trackStyle.round=false] Whether to round the track sector edges or not.
  2912. */
  2913. trackStyle: {
  2914. outerRadius: '100%',
  2915. innerRadius: '100% - 20',
  2916. round: false
  2917. },
  2918. /**
  2919. * @cfg {Object} valueStyle
  2920. * Value sector styles.
  2921. * @cfg {String/Object[]} valueStyle.fill Value sector fill color. Defaults to CSS value.
  2922. * See the `trackStyle.fill` config documentation for more information.
  2923. * @cfg {Number} valueStyle.fillOpacity Value sector fill opacity. Defaults to CSS value.
  2924. * @cfg {String} valueStyle.stroke Value sector stroke color. Defaults to CSS value.
  2925. * @cfg {Number} valueStyle.strokeOpacity Value sector stroke opacity. Defaults to CSS value.
  2926. * @cfg {Number} valueStyle.strokeWidth Value sector stroke width. Defaults to CSS value.
  2927. * @cfg {Number/String} [valueStyle.outerRadius='100% - 4'] The outer radius of the value sector.
  2928. * See the `trackStyle.outerRadius` config documentation for more information.
  2929. * @cfg {Number/String} [valueStyle.innerRadius='50% + 4'] The inner radius of the value sector.
  2930. * See the `trackStyle.outerRadius` config documentation for more information.
  2931. * @cfg {Boolean} [valueStyle.round=false] Whether to round the value sector edges or not.
  2932. */
  2933. valueStyle: {
  2934. outerRadius: '100% - 2',
  2935. innerRadius: '100% - 18',
  2936. round: false
  2937. },
  2938. /**
  2939. * @cfg {Object/Boolean} [animation=true]
  2940. * The animation applied to the gauge on changes to the {@link #value}
  2941. * and the {@link #angleOffset} configs. Defaults to 1 second animation
  2942. * with the 'out' easing.
  2943. * @cfg {Number} animation.duration The duraction of the animation.
  2944. * @cfg {String} animation.easing The easing function to use for the animation.
  2945. * Possible values are:
  2946. * - `linear` - no easing, no acceleration
  2947. * - `in` - accelerating from zero velocity
  2948. * - `out` - (default) decelerating to zero velocity
  2949. * - `inOut` - acceleration until halfway, then deceleration
  2950. */
  2951. animation: true
  2952. },
  2953. baseCls: Ext.baseCSSPrefix + 'gauge',
  2954. template: [
  2955. {
  2956. reference: 'bodyElement',
  2957. children: [
  2958. {
  2959. reference: 'textElement',
  2960. cls: Ext.baseCSSPrefix + 'gauge-text'
  2961. }
  2962. ]
  2963. }
  2964. ],
  2965. defaultBindProperty: 'value',
  2966. pathAttributes: {
  2967. // The properties in the `trackStyle` and `valueStyle` configs
  2968. // that are path attributes.
  2969. fill: true,
  2970. fillOpacity: true,
  2971. stroke: true,
  2972. strokeOpacity: true,
  2973. strokeWidth: true
  2974. },
  2975. easings: {
  2976. linear: Ext.identityFn,
  2977. // cubic easings
  2978. 'in': function(t) {
  2979. return t * t * t;
  2980. },
  2981. out: function(t) {
  2982. return (--t) * t * t + 1;
  2983. },
  2984. inOut: function(t) {
  2985. return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
  2986. }
  2987. },
  2988. resizeDelay: 0,
  2989. // in milliseconds
  2990. resizeTimerId: 0,
  2991. size: null,
  2992. // cached size
  2993. svgNS: 'http://www.w3.org/2000/svg',
  2994. svg: null,
  2995. // SVG document
  2996. defs: null,
  2997. // the `defs` section of the SVG document
  2998. trackArc: null,
  2999. valueArc: null,
  3000. trackGradient: null,
  3001. valueGradient: null,
  3002. fx: null,
  3003. // either the `value` or the `angleOffset` animation
  3004. fxValue: 0,
  3005. // the actual value rendered/animated
  3006. fxAngleOffset: 0,
  3007. constructor: function(config) {
  3008. var me = this;
  3009. me.fitSectorInRectCache = {
  3010. startAngle: null,
  3011. lengthAngle: null,
  3012. minX: null,
  3013. maxX: null,
  3014. minY: null,
  3015. maxY: null
  3016. };
  3017. me.interpolator = me.createInterpolator();
  3018. me.callParent([
  3019. config
  3020. ]);
  3021. me.el.on('resize', 'onElementResize', me);
  3022. },
  3023. doDestroy: function() {
  3024. var me = this;
  3025. Ext.undefer(me.resizeTimerId);
  3026. me.el.un('resize', 'onElementResize', me);
  3027. me.stopAnimation();
  3028. me.setNeedle(null);
  3029. me.trackGradient = Ext.destroy(me.trackGradient);
  3030. me.valueGradient = Ext.destroy(me.valueGradient);
  3031. me.defs = Ext.destroy(me.defs);
  3032. me.svg = Ext.destroy(me.svg);
  3033. me.callParent();
  3034. },
  3035. // <if classic>
  3036. afterComponentLayout: function(width, height, oldWidth, oldHeight) {
  3037. this.callParent([
  3038. width,
  3039. height,
  3040. oldWidth,
  3041. oldHeight
  3042. ]);
  3043. if (Ext.isIE9) {
  3044. this.handleResize();
  3045. }
  3046. },
  3047. // </if>
  3048. onElementResize: function(element, size) {
  3049. this.handleResize(size);
  3050. },
  3051. handleResize: function(size, instantly) {
  3052. var me = this,
  3053. el = me.element;
  3054. if (!(el && (size = size || el.getSize()) && size.width && size.height)) {
  3055. return;
  3056. }
  3057. me.resizeTimerId = Ext.undefer(me.resizeTimerId);
  3058. if (!instantly && me.resizeDelay) {
  3059. me.resizeTimerId = Ext.defer(me.handleResize, me.resizeDelay, me, [
  3060. size,
  3061. true
  3062. ]);
  3063. return;
  3064. }
  3065. me.size = size;
  3066. me.resizeHandler(size);
  3067. },
  3068. updateMinValue: function(minValue) {
  3069. var me = this;
  3070. me.interpolator.setDomain(minValue, me.getMaxValue());
  3071. if (!me.isConfiguring) {
  3072. me.render();
  3073. }
  3074. },
  3075. updateMaxValue: function(maxValue) {
  3076. var me = this;
  3077. me.interpolator.setDomain(me.getMinValue(), maxValue);
  3078. if (!me.isConfiguring) {
  3079. me.render();
  3080. }
  3081. },
  3082. updateAngleOffset: function(angleOffset, oldAngleOffset) {
  3083. var me = this,
  3084. animation = me.getAnimation();
  3085. me.fxAngleOffset = angleOffset;
  3086. if (me.isConfiguring) {
  3087. return;
  3088. }
  3089. if (animation.duration) {
  3090. me.animate(oldAngleOffset, angleOffset, animation.duration, me.easings[animation.easing], function(angleOffset) {
  3091. me.fxAngleOffset = angleOffset;
  3092. me.render();
  3093. });
  3094. } else {
  3095. me.render();
  3096. }
  3097. },
  3098. //<debug>
  3099. applyTrackStart: function(trackStart) {
  3100. if (trackStart < 0 || trackStart >= 360) {
  3101. Ext.raise("'trackStart' should be within [0, 360).");
  3102. }
  3103. return trackStart;
  3104. },
  3105. applyTrackLength: function(trackLength) {
  3106. if (trackLength <= 0 || trackLength > 360) {
  3107. Ext.raise("'trackLength' should be within (0, 360].");
  3108. }
  3109. return trackLength;
  3110. },
  3111. //</debug>
  3112. updateTrackStart: function(trackStart) {
  3113. var me = this;
  3114. if (!me.isConfiguring) {
  3115. me.render();
  3116. }
  3117. },
  3118. updateTrackLength: function(trackLength) {
  3119. var me = this;
  3120. me.interpolator.setRange(0, trackLength);
  3121. if (!me.isConfiguring) {
  3122. me.render();
  3123. }
  3124. },
  3125. applyPadding: function(padding) {
  3126. if (typeof padding === 'string') {
  3127. var ratio = parseFloat(padding) / 100;
  3128. return function(x) {
  3129. return x * ratio;
  3130. };
  3131. }
  3132. return function() {
  3133. return padding;
  3134. };
  3135. },
  3136. updatePadding: function() {
  3137. if (!this.isConfiguring) {
  3138. this.render();
  3139. }
  3140. },
  3141. applyValue: function(value) {
  3142. var minValue = this.getMinValue(),
  3143. maxValue = this.getMaxValue();
  3144. return Math.min(Math.max(value, minValue), maxValue);
  3145. },
  3146. updateValue: function(value, oldValue) {
  3147. var me = this,
  3148. animation = me.getAnimation();
  3149. me.fxValue = value;
  3150. if (me.isConfiguring) {
  3151. return;
  3152. }
  3153. me.writeText();
  3154. if (animation.duration) {
  3155. me.animate(oldValue, value, animation.duration, me.easings[animation.easing], function(value) {
  3156. me.fxValue = value;
  3157. me.render();
  3158. });
  3159. } else {
  3160. me.render();
  3161. }
  3162. },
  3163. applyTextTpl: function(textTpl) {
  3164. if (textTpl && !textTpl.isTemplate) {
  3165. textTpl = new Ext.XTemplate(textTpl);
  3166. }
  3167. return textTpl;
  3168. },
  3169. applyTextOffset: function(offset) {
  3170. offset = offset || {};
  3171. offset.dx = offset.dx || 0;
  3172. offset.dy = offset.dy || 0;
  3173. return offset;
  3174. },
  3175. updateTextTpl: function() {
  3176. this.writeText();
  3177. if (!this.isConfiguring) {
  3178. this.centerText();
  3179. }
  3180. },
  3181. // text will be centered on first size
  3182. writeText: function(options) {
  3183. var me = this,
  3184. value = me.getValue(),
  3185. minValue = me.getMinValue(),
  3186. maxValue = me.getMaxValue(),
  3187. delta = maxValue - minValue,
  3188. textTpl = me.getTextTpl();
  3189. textTpl.overwrite(me.textElement, {
  3190. value: value,
  3191. percent: (value - minValue) / delta * 100,
  3192. minValue: minValue,
  3193. maxValue: maxValue,
  3194. delta: delta
  3195. });
  3196. },
  3197. centerText: function(cx, cy, sectorRegion, innerRadius, outerRadius) {
  3198. var textElement = this.textElement,
  3199. textAlign = this.getTextAlign(),
  3200. alignedRegion, textBox;
  3201. if (Ext.Number.isEqual(innerRadius, 0, 0.1) || sectorRegion.isOutOfBound({
  3202. x: cx,
  3203. y: cy
  3204. })) {
  3205. alignedRegion = textElement.getRegion().alignTo({
  3206. align: textAlign,
  3207. // align text region's center to sector region's center
  3208. target: sectorRegion
  3209. });
  3210. textElement.setLeft(alignedRegion.left);
  3211. textElement.setTop(alignedRegion.top);
  3212. } else {
  3213. textBox = textElement.getBox();
  3214. textElement.setLeft(cx - textBox.width / 2);
  3215. textElement.setTop(cy - textBox.height / 2);
  3216. }
  3217. },
  3218. camelCaseRe: /([a-z])([A-Z])/g,
  3219. /**
  3220. * @private
  3221. */
  3222. camelToHyphen: function(name) {
  3223. return name.replace(this.camelCaseRe, '$1-$2').toLowerCase();
  3224. },
  3225. applyTrackStyle: function(trackStyle) {
  3226. var me = this,
  3227. trackGradient;
  3228. trackStyle.innerRadius = me.getRadiusFn(trackStyle.innerRadius);
  3229. trackStyle.outerRadius = me.getRadiusFn(trackStyle.outerRadius);
  3230. if (Ext.isArray(trackStyle.fill)) {
  3231. trackGradient = me.getTrackGradient();
  3232. me.setGradientStops(trackGradient, trackStyle.fill);
  3233. trackStyle.fill = 'url(#' + trackGradient.dom.getAttribute('id') + ')';
  3234. }
  3235. return trackStyle;
  3236. },
  3237. updateTrackStyle: function(trackStyle) {
  3238. var me = this,
  3239. trackArc = Ext.fly(me.getTrackArc()),
  3240. name;
  3241. for (name in trackStyle) {
  3242. if (name in me.pathAttributes) {
  3243. trackArc.setStyle(me.camelToHyphen(name), trackStyle[name]);
  3244. } else {
  3245. trackArc.setStyle(name, trackStyle[name]);
  3246. }
  3247. }
  3248. },
  3249. applyValueStyle: function(valueStyle) {
  3250. var me = this,
  3251. valueGradient;
  3252. valueStyle.innerRadius = me.getRadiusFn(valueStyle.innerRadius);
  3253. valueStyle.outerRadius = me.getRadiusFn(valueStyle.outerRadius);
  3254. if (Ext.isArray(valueStyle.fill)) {
  3255. valueGradient = me.getValueGradient();
  3256. me.setGradientStops(valueGradient, valueStyle.fill);
  3257. valueStyle.fill = 'url(#' + valueGradient.dom.getAttribute('id') + ')';
  3258. }
  3259. return valueStyle;
  3260. },
  3261. updateValueStyle: function(valueStyle) {
  3262. var me = this,
  3263. valueArc = Ext.fly(me.getValueArc()),
  3264. name;
  3265. for (name in valueStyle) {
  3266. if (name in me.pathAttributes) {
  3267. valueArc.setStyle(me.camelToHyphen(name), valueStyle[name]);
  3268. } else {
  3269. valueArc.setStyle(name, valueStyle[name]);
  3270. }
  3271. }
  3272. },
  3273. /**
  3274. * @private
  3275. */
  3276. getRadiusFn: function(radius) {
  3277. var result, pos, ratio,
  3278. increment = 0;
  3279. if (Ext.isNumber(radius)) {
  3280. result = function() {
  3281. return radius;
  3282. };
  3283. } else if (Ext.isString(radius)) {
  3284. radius = radius.replace(/ /g, '');
  3285. ratio = parseFloat(radius) / 100;
  3286. pos = radius.search('%');
  3287. // E.g. '100% - 4'
  3288. if (pos < radius.length - 1) {
  3289. increment = parseFloat(radius.substr(pos + 1));
  3290. }
  3291. result = function(radius) {
  3292. return radius * ratio + increment;
  3293. };
  3294. result.ratio = ratio;
  3295. }
  3296. return result;
  3297. },
  3298. getSvg: function() {
  3299. var me = this,
  3300. svg = me.svg;
  3301. if (!svg) {
  3302. svg = me.svg = Ext.get(document.createElementNS(me.svgNS, 'svg'));
  3303. me.bodyElement.append(svg);
  3304. }
  3305. return svg;
  3306. },
  3307. getTrackArc: function() {
  3308. var me = this,
  3309. trackArc = me.trackArc;
  3310. if (!trackArc) {
  3311. trackArc = me.trackArc = document.createElementNS(me.svgNS, 'path');
  3312. me.getSvg().append(trackArc, true);
  3313. // Note: Ext.dom.Element.addCls doesn't work on SVG elements,
  3314. // as it simply assigns a class string to el.dom.className,
  3315. // which in case of SVG is no simple string:
  3316. // SVGAnimatedString {baseVal: "x-gauge-track", animVal: "x-gauge-track"}
  3317. trackArc.setAttribute('class', Ext.baseCSSPrefix + 'gauge-track');
  3318. }
  3319. return trackArc;
  3320. },
  3321. getValueArc: function() {
  3322. var me = this,
  3323. valueArc = me.valueArc;
  3324. me.getTrackArc();
  3325. // make sure the track arc is created first for proper draw order
  3326. if (!valueArc) {
  3327. valueArc = me.valueArc = document.createElementNS(me.svgNS, 'path');
  3328. me.getSvg().append(valueArc, true);
  3329. valueArc.setAttribute('class', Ext.baseCSSPrefix + 'gauge-value');
  3330. }
  3331. return valueArc;
  3332. },
  3333. applyNeedle: function(needle, oldNeedle) {
  3334. // Make sure the track and value elements have been already created,
  3335. // so that the needle element renders on top.
  3336. this.getValueArc();
  3337. return Ext.Factory.gaugeNeedle.update(oldNeedle, needle, this, 'createNeedle', 'needleDefaults');
  3338. },
  3339. createNeedle: function(config) {
  3340. return Ext.apply({
  3341. gauge: this
  3342. }, config);
  3343. },
  3344. getDefs: function() {
  3345. var me = this,
  3346. defs = me.defs;
  3347. if (!defs) {
  3348. defs = me.defs = Ext.get(document.createElementNS(me.svgNS, 'defs'));
  3349. me.getSvg().appendChild(defs);
  3350. }
  3351. return defs;
  3352. },
  3353. /**
  3354. * @private
  3355. */
  3356. setGradientSize: function(gradient, x1, y1, x2, y2) {
  3357. gradient.setAttribute('x1', x1);
  3358. gradient.setAttribute('y1', y1);
  3359. gradient.setAttribute('x2', x2);
  3360. gradient.setAttribute('y2', y2);
  3361. },
  3362. /**
  3363. * @private
  3364. */
  3365. resizeGradients: function(size) {
  3366. var me = this,
  3367. trackGradient = me.getTrackGradient(),
  3368. valueGradient = me.getValueGradient(),
  3369. x1 = 0,
  3370. y1 = size.height / 2,
  3371. x2 = size.width,
  3372. y2 = size.height / 2;
  3373. me.setGradientSize(trackGradient.dom, x1, y1, x2, y2);
  3374. me.setGradientSize(valueGradient.dom, x1, y1, x2, y2);
  3375. },
  3376. /**
  3377. * @private
  3378. */
  3379. setGradientStops: function(gradient, stops) {
  3380. var ln = stops.length,
  3381. i, stopCfg, stopEl;
  3382. while (gradient.firstChild) {
  3383. gradient.removeChild(gradient.firstChild);
  3384. }
  3385. for (i = 0; i < ln; i++) {
  3386. stopCfg = stops[i];
  3387. stopEl = document.createElementNS(this.svgNS, 'stop');
  3388. gradient.appendChild(stopEl);
  3389. stopEl.setAttribute('offset', stopCfg.offset);
  3390. stopEl.setAttribute('stop-color', stopCfg.color);
  3391. ('opacity' in stopCfg) && stopEl.setAttribute('stop-opacity', stopCfg.opacity);
  3392. }
  3393. },
  3394. getTrackGradient: function() {
  3395. var me = this,
  3396. trackGradient = me.trackGradient;
  3397. if (!trackGradient) {
  3398. trackGradient = me.trackGradient = Ext.get(document.createElementNS(me.svgNS, 'linearGradient'));
  3399. // Using absolute values for x1, y1, x2, y2 attributes.
  3400. trackGradient.dom.setAttribute('gradientUnits', 'userSpaceOnUse');
  3401. me.getDefs().appendChild(trackGradient);
  3402. Ext.get(trackGradient);
  3403. }
  3404. // assign unique ID
  3405. return trackGradient;
  3406. },
  3407. getValueGradient: function() {
  3408. var me = this,
  3409. valueGradient = me.valueGradient;
  3410. if (!valueGradient) {
  3411. valueGradient = me.valueGradient = Ext.get(document.createElementNS(me.svgNS, 'linearGradient'));
  3412. // Using absolute values for x1, y1, x2, y2 attributes.
  3413. valueGradient.dom.setAttribute('gradientUnits', 'userSpaceOnUse');
  3414. me.getDefs().appendChild(valueGradient);
  3415. Ext.get(valueGradient);
  3416. }
  3417. // assign unique ID
  3418. return valueGradient;
  3419. },
  3420. getArcPoint: function(centerX, centerY, radius, degrees) {
  3421. var radians = degrees / 180 * Math.PI;
  3422. return [
  3423. centerX + radius * Math.cos(radians),
  3424. centerY + radius * Math.sin(radians)
  3425. ];
  3426. },
  3427. isCircle: function(startAngle, endAngle) {
  3428. return Ext.Number.isEqual(Math.abs(endAngle - startAngle), 360, 0.001);
  3429. },
  3430. getArcPath: function(centerX, centerY, innerRadius, outerRadius, startAngle, endAngle, round) {
  3431. var me = this,
  3432. isCircle = me.isCircle(startAngle, endAngle),
  3433. // It's not possible to draw a circle using arcs.
  3434. endAngle = endAngle - 0.01,
  3435. innerStartPoint = me.getArcPoint(centerX, centerY, innerRadius, startAngle),
  3436. innerEndPoint = me.getArcPoint(centerX, centerY, innerRadius, endAngle),
  3437. outerStartPoint = me.getArcPoint(centerX, centerY, outerRadius, startAngle),
  3438. outerEndPoint = me.getArcPoint(centerX, centerY, outerRadius, endAngle),
  3439. large = endAngle - startAngle <= 180 ? 0 : 1,
  3440. path = [
  3441. 'M',
  3442. innerStartPoint[0],
  3443. innerStartPoint[1],
  3444. 'A',
  3445. innerRadius,
  3446. innerRadius,
  3447. 0,
  3448. large,
  3449. 1,
  3450. innerEndPoint[0],
  3451. innerEndPoint[1]
  3452. ],
  3453. capRadius = (outerRadius - innerRadius) / 2;
  3454. if (isCircle) {
  3455. path.push('M', outerEndPoint[0], outerEndPoint[1]);
  3456. } else {
  3457. if (round) {
  3458. path.push('A', capRadius, capRadius, 0, 0, 0, outerEndPoint[0], outerEndPoint[1]);
  3459. } else {
  3460. path.push('L', outerEndPoint[0], outerEndPoint[1]);
  3461. }
  3462. }
  3463. path.push('A', outerRadius, outerRadius, 0, large, 0, outerStartPoint[0], outerStartPoint[1]);
  3464. if (round && !isCircle) {
  3465. path.push('A', capRadius, capRadius, 0, 0, 0, innerStartPoint[0], innerStartPoint[1]);
  3466. }
  3467. path.push('Z');
  3468. return path.join(' ');
  3469. },
  3470. resizeHandler: function(size) {
  3471. var me = this,
  3472. svg = me.getSvg();
  3473. svg.setSize(size);
  3474. me.resizeGradients(size);
  3475. me.render();
  3476. },
  3477. /**
  3478. * @private
  3479. * Creates a linear interpolator function that itself has a few methods:
  3480. * - `setDomain(from, to)`
  3481. * - `setRange(from, to)`
  3482. * - `getDomain` - returns the domain as a [from, to] array
  3483. * - `getRange` - returns the range as a [from, to] array
  3484. * @param {Boolean} [rangeCheck=false]
  3485. * Whether to allow out of bounds values for domain and range.
  3486. * @return {Function} The interpolator function:
  3487. * `interpolator(domainValue, isInvert)`.
  3488. * If the `isInvert` parameter is `true`, the start of domain will correspond
  3489. * to the end of range. This is useful, for example, when you want to render
  3490. * increasing domain values counter-clockwise instead of clockwise.
  3491. */
  3492. createInterpolator: function(rangeCheck) {
  3493. var domainStart = 0,
  3494. domainDelta = 1,
  3495. rangeStart = 0,
  3496. rangeEnd = 1;
  3497. var interpolator = function(x, invert) {
  3498. var t = 0;
  3499. if (domainDelta) {
  3500. t = (x - domainStart) / domainDelta;
  3501. if (rangeCheck) {
  3502. t = Math.max(0, t);
  3503. t = Math.min(1, t);
  3504. }
  3505. if (invert) {
  3506. t = 1 - t;
  3507. }
  3508. }
  3509. return (1 - t) * rangeStart + t * rangeEnd;
  3510. };
  3511. interpolator.setDomain = function(a, b) {
  3512. domainStart = a;
  3513. domainDelta = b - a;
  3514. return this;
  3515. };
  3516. interpolator.setRange = function(a, b) {
  3517. rangeStart = a;
  3518. rangeEnd = b;
  3519. return this;
  3520. };
  3521. interpolator.getDomain = function() {
  3522. return [
  3523. domainStart,
  3524. domainStart + domainDelta
  3525. ];
  3526. };
  3527. interpolator.getRange = function() {
  3528. return [
  3529. rangeStart,
  3530. rangeEnd
  3531. ];
  3532. };
  3533. return interpolator;
  3534. },
  3535. applyAnimation: function(animation) {
  3536. if (true === animation) {
  3537. animation = {};
  3538. } else if (false === animation) {
  3539. animation = {
  3540. duration: 0
  3541. };
  3542. }
  3543. if (!('duration' in animation)) {
  3544. animation.duration = 1000;
  3545. }
  3546. if (!(animation.easing in this.easings)) {
  3547. animation.easing = 'out';
  3548. }
  3549. return animation;
  3550. },
  3551. updateAnimation: function() {
  3552. this.stopAnimation();
  3553. },
  3554. /**
  3555. * @private
  3556. * @param {Number} from
  3557. * @param {Number} to
  3558. * @param {Number} duration
  3559. * @param {Function} easing
  3560. * @param {Function} fn Function to execute on every frame of animation.
  3561. * The function takes a single parameter - the value in the [from, to]
  3562. * range, interpolated based on current time and easing function.
  3563. * With certain easings, the value may overshoot the range slighly.
  3564. * @param {Object} scope
  3565. */
  3566. animate: function(from, to, duration, easing, fn, scope) {
  3567. var me = this,
  3568. start = Ext.now(),
  3569. interpolator = me.createInterpolator().setRange(from, to);
  3570. function frame() {
  3571. var now = Ext.AnimationQueue.frameStartTime,
  3572. t = Math.min(now - start, duration) / duration,
  3573. value = interpolator(easing(t));
  3574. if (scope) {
  3575. if (typeof fn === 'string') {
  3576. scope[fn].call(scope, value);
  3577. } else {
  3578. fn.call(scope, value);
  3579. }
  3580. } else {
  3581. fn(value);
  3582. }
  3583. if (t >= 1) {
  3584. Ext.AnimationQueue.stop(frame, scope);
  3585. me.fx = null;
  3586. }
  3587. }
  3588. me.stopAnimation();
  3589. Ext.AnimationQueue.start(frame, scope);
  3590. me.fx = {
  3591. frame: frame,
  3592. scope: scope
  3593. };
  3594. },
  3595. /**
  3596. * Stops the current {@link #value} or {@link #angleOffset} animation.
  3597. */
  3598. stopAnimation: function() {
  3599. var me = this;
  3600. if (me.fx) {
  3601. Ext.AnimationQueue.stop(me.fx.frame, me.fx.scope);
  3602. me.fx = null;
  3603. }
  3604. },
  3605. unitCircleExtrema: {
  3606. 0: [
  3607. 1,
  3608. 0
  3609. ],
  3610. 90: [
  3611. 0,
  3612. 1
  3613. ],
  3614. 180: [
  3615. -1,
  3616. 0
  3617. ],
  3618. 270: [
  3619. 0,
  3620. -1
  3621. ],
  3622. 360: [
  3623. 1,
  3624. 0
  3625. ],
  3626. 450: [
  3627. 0,
  3628. 1
  3629. ],
  3630. 540: [
  3631. -1,
  3632. 0
  3633. ],
  3634. 630: [
  3635. 0,
  3636. -1
  3637. ]
  3638. },
  3639. /**
  3640. * @private
  3641. */
  3642. getUnitSectorExtrema: function(startAngle, lengthAngle) {
  3643. var extrema = this.unitCircleExtrema,
  3644. points = [],
  3645. angle;
  3646. for (angle in extrema) {
  3647. if (angle > startAngle && angle < startAngle + lengthAngle) {
  3648. points.push(extrema[angle]);
  3649. }
  3650. }
  3651. return points;
  3652. },
  3653. /**
  3654. * @private
  3655. * Given a rect with a known width and height, find the maximum radius of the donut
  3656. * sector that can fit into it, as well as the center point of such a sector.
  3657. * The end and start angles of the sector are also known, as well as the relationship
  3658. * between the inner and outer radii.
  3659. */
  3660. fitSectorInRect: function(width, height, startAngle, lengthAngle, ratio) {
  3661. if (Ext.Number.isEqual(lengthAngle, 360, 0.001)) {
  3662. return {
  3663. cx: width / 2,
  3664. cy: height / 2,
  3665. radius: Math.min(width, height) / 2,
  3666. region: new Ext.util.Region(0, width, height, 0)
  3667. };
  3668. }
  3669. var me = this,
  3670. points, xx, yy, minX, maxX, minY, maxY,
  3671. cache = me.fitSectorInRectCache,
  3672. sameAngles = cache.startAngle === startAngle && cache.lengthAngle === lengthAngle;
  3673. if (sameAngles) {
  3674. minX = cache.minX;
  3675. maxX = cache.maxX;
  3676. minY = cache.minY;
  3677. maxY = cache.maxY;
  3678. } else {
  3679. points = me.getUnitSectorExtrema(startAngle, lengthAngle).concat([
  3680. me.getArcPoint(0, 0, 1, startAngle),
  3681. // start angle outer radius point
  3682. me.getArcPoint(0, 0, ratio, startAngle),
  3683. // start angle inner radius point
  3684. me.getArcPoint(0, 0, 1, startAngle + lengthAngle),
  3685. // end angle outer radius point
  3686. me.getArcPoint(0, 0, ratio, startAngle + lengthAngle)
  3687. ]);
  3688. // end angle inner radius point
  3689. xx = points.map(function(point) {
  3690. return point[0];
  3691. });
  3692. yy = points.map(function(point) {
  3693. return point[1];
  3694. });
  3695. // The bounding box of a unit sector with the given properties.
  3696. minX = Math.min.apply(null, xx);
  3697. maxX = Math.max.apply(null, xx);
  3698. minY = Math.min.apply(null, yy);
  3699. maxY = Math.max.apply(null, yy);
  3700. cache.startAngle = startAngle;
  3701. cache.lengthAngle = lengthAngle;
  3702. cache.minX = minX;
  3703. cache.maxX = maxX;
  3704. cache.minY = minY;
  3705. cache.maxY = maxY;
  3706. }
  3707. var sectorWidth = maxX - minX,
  3708. sectorHeight = maxY - minY,
  3709. scaleX = width / sectorWidth,
  3710. scaleY = height / sectorHeight,
  3711. scale = Math.min(scaleX, scaleY),
  3712. // Region constructor takes: top, right, bottom, left.
  3713. sectorRegion = new Ext.util.Region(minY * scale, maxX * scale, maxY * scale, minX * scale),
  3714. rectRegion = new Ext.util.Region(0, width, height, 0),
  3715. alignedRegion = sectorRegion.alignTo({
  3716. align: 'c-c',
  3717. // align sector region's center to rect region's center
  3718. target: rectRegion
  3719. }),
  3720. dx = alignedRegion.left - minX * scale,
  3721. dy = alignedRegion.top - minY * scale;
  3722. return {
  3723. cx: dx,
  3724. cy: dy,
  3725. radius: scale,
  3726. region: alignedRegion
  3727. };
  3728. },
  3729. /**
  3730. * @private
  3731. */
  3732. fitSectorInPaddedRect: function(width, height, padding, startAngle, lengthAngle, ratio) {
  3733. var result = this.fitSectorInRect(width - padding * 2, height - padding * 2, startAngle, lengthAngle, ratio);
  3734. result.cx += padding;
  3735. result.cy += padding;
  3736. result.region.translateBy(padding, padding);
  3737. return result;
  3738. },
  3739. /**
  3740. * @private
  3741. */
  3742. normalizeAngle: function(angle) {
  3743. return (angle % 360 + 360) % 360;
  3744. },
  3745. render: function() {
  3746. if (!this.size) {
  3747. return;
  3748. }
  3749. var me = this,
  3750. textOffset = me.getTextOffset(),
  3751. trackArc = me.getTrackArc(),
  3752. valueArc = me.getValueArc(),
  3753. needle = me.getNeedle(),
  3754. clockwise = me.getClockwise(),
  3755. value = me.fxValue,
  3756. angleOffset = me.fxAngleOffset,
  3757. trackLength = me.getTrackLength(),
  3758. width = me.size.width,
  3759. height = me.size.height,
  3760. paddingFn = me.getPadding(),
  3761. padding = paddingFn(Math.min(width, height)),
  3762. trackStart = me.normalizeAngle(me.getTrackStart() + angleOffset),
  3763. // in the range of [0, 360)
  3764. trackEnd = trackStart + trackLength,
  3765. // in the range of (0, 720)
  3766. valueLength = me.interpolator(value),
  3767. trackStyle = me.getTrackStyle(),
  3768. valueStyle = me.getValueStyle(),
  3769. sector = me.fitSectorInPaddedRect(width, height, padding, trackStart, trackLength, trackStyle.innerRadius.ratio),
  3770. cx = sector.cx,
  3771. cy = sector.cy,
  3772. radius = sector.radius,
  3773. trackInnerRadius = Math.max(0, trackStyle.innerRadius(radius)),
  3774. trackOuterRadius = Math.max(0, trackStyle.outerRadius(radius)),
  3775. valueInnerRadius = Math.max(0, valueStyle.innerRadius(radius)),
  3776. valueOuterRadius = Math.max(0, valueStyle.outerRadius(radius)),
  3777. trackPath = me.getArcPath(cx, cy, trackInnerRadius, trackOuterRadius, trackStart, trackEnd, trackStyle.round),
  3778. valuePath = me.getArcPath(cx, cy, valueInnerRadius, valueOuterRadius, clockwise ? trackStart : trackEnd - valueLength, clockwise ? trackStart + valueLength : trackEnd, valueStyle.round);
  3779. me.centerText(cx + textOffset.dx, cy + textOffset.dy, sector.region, trackInnerRadius, trackOuterRadius);
  3780. trackArc.setAttribute('d', trackPath);
  3781. valueArc.setAttribute('d', valuePath);
  3782. if (needle) {
  3783. needle.setRadius(radius);
  3784. needle.setTransform(cx, cy, -90 + trackStart + valueLength);
  3785. }
  3786. me.fireEvent('render', me);
  3787. }
  3788. });
  3789. Ext.define('Ext.ux.gauge.needle.Arrow', {
  3790. extend: 'Ext.ux.gauge.needle.Abstract',
  3791. alias: 'gauge.needle.arrow',
  3792. config: {
  3793. path: function(ir, or) {
  3794. return or - ir > 30 ? "M0," + (ir + 5) + " L-4," + ir + " L-4," + (ir + 10) + " L-1," + (ir + 15) + " L-1," + (or - 7) + " L-5," + (or - 10) + " L0," + or + " L5," + (or - 10) + " L1," + (or - 7) + " L1," + (ir + 15) + " L4," + (ir + 10) + " L4," + ir + " Z" : '';
  3795. }
  3796. }
  3797. });
  3798. Ext.define('Ext.ux.gauge.needle.Diamond', {
  3799. extend: 'Ext.ux.gauge.needle.Abstract',
  3800. alias: 'gauge.needle.diamond',
  3801. config: {
  3802. path: function(ir, or) {
  3803. return or - ir > 10 ? 'M0,' + ir + ' L-4,' + (ir + 5) + ' L0,' + or + ' L4,' + (ir + 5) + ' Z' : '';
  3804. }
  3805. }
  3806. });
  3807. Ext.define('Ext.ux.gauge.needle.Rectangle', {
  3808. extend: 'Ext.ux.gauge.needle.Abstract',
  3809. alias: 'gauge.needle.rectangle',
  3810. config: {
  3811. path: function(ir, or) {
  3812. return or - ir > 10 ? "M-2," + ir + " L2," + ir + " L2," + or + " L-2," + or + " Z" : '';
  3813. }
  3814. }
  3815. });
  3816. Ext.define('Ext.ux.gauge.needle.Spike', {
  3817. extend: 'Ext.ux.gauge.needle.Abstract',
  3818. alias: 'gauge.needle.spike',
  3819. config: {
  3820. path: function(ir, or) {
  3821. return or - ir > 10 ? "M0," + (ir + 5) + " L-4," + ir + " L0," + or + " L4," + ir + " Z" : '';
  3822. }
  3823. }
  3824. });
  3825. Ext.define('Ext.ux.gauge.needle.Wedge', {
  3826. extend: 'Ext.ux.gauge.needle.Abstract',
  3827. alias: 'gauge.needle.wedge',
  3828. config: {
  3829. path: function(ir, or) {
  3830. return or - ir > 10 ? "M-4," + ir + " L0," + or + " L4," + ir + " Z" : '';
  3831. }
  3832. }
  3833. });
  3834. /**
  3835. * A ratings picker based on `Ext.Gadget`.
  3836. *
  3837. * @example
  3838. * Ext.create({
  3839. * xtype: 'rating',
  3840. * renderTo: Ext.getBody(),
  3841. * listeners: {
  3842. * change: function (picker, value) {
  3843. * console.log('Rating ' + value);
  3844. * }
  3845. * }
  3846. * });
  3847. */
  3848. Ext.define('Ext.ux.rating.Picker', {
  3849. extend: 'Ext.Gadget',
  3850. xtype: 'rating',
  3851. focusable: true,
  3852. /*
  3853. * The "cachedConfig" block is basically the same as "config" except that these
  3854. * values are applied specially to the first instance of the class. After processing
  3855. * these configs, the resulting values are stored on the class `prototype` and the
  3856. * template DOM element also reflects these default values.
  3857. */
  3858. cachedConfig: {
  3859. /**
  3860. * @cfg {String} [family]
  3861. * The CSS `font-family` to use for displaying the `{@link #glyphs}`.
  3862. */
  3863. family: 'monospace',
  3864. /**
  3865. * @cfg {String/String[]/Number[]} [glyphs]
  3866. * Either a string containing the two glyph characters, or an array of two strings
  3867. * containing the individual glyph characters or an array of two numbers with the
  3868. * character codes for the individual glyphs.
  3869. *
  3870. * For example:
  3871. *
  3872. * @example
  3873. * Ext.create({
  3874. * xtype: 'rating',
  3875. * renderTo: Ext.getBody(),
  3876. * glyphs: [ 9671, 9670 ], // '◇◆',
  3877. * listeners: {
  3878. * change: function (picker, value) {
  3879. * console.log('Rating ' + value);
  3880. * }
  3881. * }
  3882. * });
  3883. */
  3884. glyphs: '☆★',
  3885. /**
  3886. * @cfg {Number} [minimum=1]
  3887. * The minimum allowed `{@link #value}` (rating).
  3888. */
  3889. minimum: 1,
  3890. /**
  3891. * @cfg {Number} [limit]
  3892. * The maximum allowed `{@link #value}` (rating).
  3893. */
  3894. limit: 5,
  3895. /**
  3896. * @cfg {String/Object} [overStyle]
  3897. * Optional styles to apply to the rating glyphs when `{@link #trackOver}` is
  3898. * enabled.
  3899. */
  3900. overStyle: null,
  3901. /**
  3902. * @cfg {Number} [rounding=1]
  3903. * The rounding to apply to values. Common choices are 0.5 (for half-steps) or
  3904. * 0.25 (for quarter steps).
  3905. */
  3906. rounding: 1,
  3907. /**
  3908. * @cfg {String} [scale="125%"]
  3909. * The CSS `font-size` to apply to the glyphs. This value defaults to 125% because
  3910. * glyphs in the stock font tend to be too small. When using specially designed
  3911. * "icon fonts" you may want to set this to 100%.
  3912. */
  3913. scale: '125%',
  3914. /**
  3915. * @cfg {String/Object} [selectedStyle]
  3916. * Optional styles to apply to the rating value glyphs.
  3917. */
  3918. selectedStyle: null,
  3919. /**
  3920. * @cfg {Object/String/String[]/Ext.XTemplate/Function} tip
  3921. * A template or a function that produces the tooltip text. The `Object`, `String`
  3922. * and `String[]` forms are converted to an `Ext.XTemplate`. If a function is given,
  3923. * it will be called with an object parameter and should return the tooltip text.
  3924. * The object contains these properties:
  3925. *
  3926. * - component: The rating component requesting the tooltip.
  3927. * - tracking: The current value under the mouse cursor.
  3928. * - trackOver: The value of the `{@link #trackOver}` config.
  3929. * - value: The current value.
  3930. *
  3931. * Templates can use these properties to generate the proper text.
  3932. */
  3933. tip: null,
  3934. /**
  3935. * @cfg {Boolean} [trackOver=true]
  3936. * Determines if mouse movements should temporarily update the displayed value.
  3937. * The actual `value` is only updated on `click` but this rather acts as the
  3938. * "preview" of the value prior to click.
  3939. */
  3940. trackOver: true,
  3941. /**
  3942. * @cfg {Number} value
  3943. * The rating value. This value is bounded by `minimum` and `limit` and is also
  3944. * adjusted by the `rounding`.
  3945. */
  3946. value: null,
  3947. //---------------------------------------------------------------------
  3948. // Private configs
  3949. /**
  3950. * @cfg {String} tooltipText
  3951. * The current tooltip text. This value is set into the DOM by the updater (hence
  3952. * only when it changes). This is intended for use by the tip manager
  3953. * (`{@link Ext.tip.QuickTipManager}`). Developers should never need to set this
  3954. * config since it is handled by virtue of setting other configs (such as the
  3955. * {@link #tooltip} or the {@link #value}.).
  3956. * @private
  3957. */
  3958. tooltipText: null,
  3959. /**
  3960. * @cfg {Number} trackingValue
  3961. * This config is used to when `trackOver` is `true` and represents the tracked
  3962. * value. This config is maintained by our `mousemove` handler. This should not
  3963. * need to be set directly by user code.
  3964. * @private
  3965. */
  3966. trackingValue: null
  3967. },
  3968. config: {
  3969. /**
  3970. * @cfg {Boolean/Object} [animate=false]
  3971. * Specifies an animation to use when changing the `{@link #value}`. When setting
  3972. * this config, it is probably best to set `{@link #trackOver}` to `false`.
  3973. */
  3974. animate: null
  3975. },
  3976. // This object describes our element tree from the root.
  3977. element: {
  3978. cls: 'u' + Ext.baseCSSPrefix + 'rating-picker',
  3979. // Since we are replacing the entire "element" tree, we have to assign this
  3980. // "reference" as would our base class.
  3981. reference: 'element',
  3982. children: [
  3983. {
  3984. reference: 'innerEl',
  3985. cls: 'u' + Ext.baseCSSPrefix + 'rating-picker-inner',
  3986. listeners: {
  3987. click: 'onClick',
  3988. mousemove: 'onMouseMove',
  3989. mouseenter: 'onMouseEnter',
  3990. mouseleave: 'onMouseLeave'
  3991. },
  3992. children: [
  3993. {
  3994. reference: 'valueEl',
  3995. cls: 'u' + Ext.baseCSSPrefix + 'rating-picker-value'
  3996. },
  3997. {
  3998. reference: 'trackerEl',
  3999. cls: 'u' + Ext.baseCSSPrefix + 'rating-picker-tracker'
  4000. }
  4001. ]
  4002. }
  4003. ]
  4004. },
  4005. // Tell the Binding system to default to our "value" config.
  4006. defaultBindProperty: 'value',
  4007. // Enable two-way data binding for the "value" config.
  4008. twoWayBindable: 'value',
  4009. overCls: 'u' + Ext.baseCSSPrefix + 'rating-picker-over',
  4010. trackOverCls: 'u' + Ext.baseCSSPrefix + 'rating-picker-track-over',
  4011. //-------------------------------------------------------------------------
  4012. // Config Appliers
  4013. applyGlyphs: function(value) {
  4014. if (typeof value === 'string') {
  4015. //<debug>
  4016. if (value.length !== 2) {
  4017. Ext.raise('Expected 2 characters for "glyphs" not "' + value + '".');
  4018. }
  4019. //</debug>
  4020. value = [
  4021. value.charAt(0),
  4022. value.charAt(1)
  4023. ];
  4024. } else if (typeof value[0] === 'number') {
  4025. value = [
  4026. String.fromCharCode(value[0]),
  4027. String.fromCharCode(value[1])
  4028. ];
  4029. }
  4030. return value;
  4031. },
  4032. applyOverStyle: function(style) {
  4033. this.trackerEl.applyStyles(style);
  4034. },
  4035. applySelectedStyle: function(style) {
  4036. this.valueEl.applyStyles(style);
  4037. },
  4038. applyTip: function(tip) {
  4039. if (tip && typeof tip !== 'function') {
  4040. if (!tip.isTemplate) {
  4041. tip = new Ext.XTemplate(tip);
  4042. }
  4043. tip = tip.apply.bind(tip);
  4044. }
  4045. return tip;
  4046. },
  4047. applyTrackingValue: function(value) {
  4048. return this.applyValue(value);
  4049. },
  4050. // same rounding as normal value
  4051. applyValue: function(v) {
  4052. if (v !== null) {
  4053. var rounding = this.getRounding(),
  4054. limit = this.getLimit(),
  4055. min = this.getMinimum();
  4056. v = Math.round(Math.round(v / rounding) * rounding * 1000) / 1000;
  4057. v = (v < min) ? min : (v > limit ? limit : v);
  4058. }
  4059. return v;
  4060. },
  4061. //-------------------------------------------------------------------------
  4062. // Event Handlers
  4063. onClick: function(event) {
  4064. var value = this.valueFromEvent(event);
  4065. this.setValue(value);
  4066. },
  4067. onMouseEnter: function() {
  4068. this.element.addCls(this.overCls);
  4069. },
  4070. onMouseLeave: function() {
  4071. this.element.removeCls(this.overCls);
  4072. },
  4073. onMouseMove: function(event) {
  4074. var value = this.valueFromEvent(event);
  4075. this.setTrackingValue(value);
  4076. },
  4077. //-------------------------------------------------------------------------
  4078. // Config Updaters
  4079. updateFamily: function(family) {
  4080. this.element.setStyle('fontFamily', "'" + family + "'");
  4081. },
  4082. updateGlyphs: function() {
  4083. this.refreshGlyphs();
  4084. },
  4085. updateLimit: function() {
  4086. this.refreshGlyphs();
  4087. },
  4088. updateScale: function(size) {
  4089. this.element.setStyle('fontSize', size);
  4090. },
  4091. updateTip: function() {
  4092. this.refreshTip();
  4093. },
  4094. updateTooltipText: function(text) {
  4095. this.setTooltip(text);
  4096. },
  4097. // modern only (replaced by classic override)
  4098. updateTrackingValue: function(value) {
  4099. var me = this,
  4100. trackerEl = me.trackerEl,
  4101. newWidth = me.valueToPercent(value);
  4102. trackerEl.setStyle('width', newWidth);
  4103. me.refreshTip();
  4104. },
  4105. updateTrackOver: function(trackOver) {
  4106. this.element.toggleCls(this.trackOverCls, trackOver);
  4107. },
  4108. updateValue: function(value, oldValue) {
  4109. var me = this,
  4110. animate = me.getAnimate(),
  4111. valueEl = me.valueEl,
  4112. newWidth = me.valueToPercent(value),
  4113. column, record;
  4114. if (me.isConfiguring || !animate) {
  4115. valueEl.setStyle('width', newWidth);
  4116. } else {
  4117. valueEl.stopAnimation();
  4118. valueEl.animate(Ext.merge({
  4119. from: {
  4120. width: me.valueToPercent(oldValue)
  4121. },
  4122. to: {
  4123. width: newWidth
  4124. }
  4125. }, animate));
  4126. }
  4127. me.refreshTip();
  4128. if (!me.isConfiguring) {
  4129. // Since we are (re)configured many times as we are used in a grid cell, we
  4130. // avoid firing the change event unless there are listeners.
  4131. if (me.hasListeners.change) {
  4132. me.fireEvent('change', me, value, oldValue);
  4133. }
  4134. column = me.getWidgetColumn && me.getWidgetColumn();
  4135. record = column && me.getWidgetRecord && me.getWidgetRecord();
  4136. if (record && column.dataIndex) {
  4137. // When used in a widgetcolumn, we should update the backing field. The
  4138. // linkages will be cleared as we are being recycled, so this will only
  4139. // reach this line when we are properly attached to a record and the
  4140. // change is coming from the user (or a call to setValue).
  4141. record.set(column.dataIndex, value);
  4142. }
  4143. }
  4144. },
  4145. //-------------------------------------------------------------------------
  4146. // Config System Optimizations
  4147. //
  4148. // These are to deal with configs that combine to determine what should be
  4149. // rendered in the DOM. For example, "glyphs" and "limit" must both be known
  4150. // to render the proper text nodes. The "tip" and "value" likewise are
  4151. // used to update the tooltipText.
  4152. //
  4153. // To avoid multiple updates to the DOM (one for each config), we simply mark
  4154. // the rendering as invalid and post-process these flags on the tail of any
  4155. // bulk updates.
  4156. afterCachedConfig: function() {
  4157. // Now that we are done setting up the initial values we need to refresh the
  4158. // DOM before we allow Ext.Widget's implementation to cloneNode on it.
  4159. this.refresh();
  4160. return this.callParent(arguments);
  4161. },
  4162. initConfig: function(instanceConfig) {
  4163. this.isConfiguring = true;
  4164. this.callParent([
  4165. instanceConfig
  4166. ]);
  4167. // The firstInstance will already have refreshed the DOM (in afterCacheConfig)
  4168. // but all instances beyond the first need to refresh if they have custom values
  4169. // for one or more configs that affect the DOM (such as "glyphs" and "limit").
  4170. this.refresh();
  4171. },
  4172. setConfig: function() {
  4173. var me = this;
  4174. // Since we could be updating multiple configs, save any updates that need
  4175. // multiple values for afterwards.
  4176. me.isReconfiguring = true;
  4177. me.callParent(arguments);
  4178. me.isReconfiguring = false;
  4179. // Now that all new values are set, we can refresh the DOM.
  4180. me.refresh();
  4181. return me;
  4182. },
  4183. //-------------------------------------------------------------------------
  4184. privates: {
  4185. /**
  4186. * This method returns the DOM text node into which glyphs are placed.
  4187. * @param {HTMLElement} dom The DOM node parent of the text node.
  4188. * @return {HTMLElement} The text node.
  4189. * @private
  4190. */
  4191. getGlyphTextNode: function(dom) {
  4192. var node = dom.lastChild;
  4193. // We want all our text nodes to be at the end of the child list, most
  4194. // especially the text node on the innerEl. That text node affects the
  4195. // default left/right position of our absolutely positioned child divs
  4196. // (trackerEl and valueEl).
  4197. if (!node || node.nodeType !== 3) {
  4198. node = dom.ownerDocument.createTextNode('');
  4199. dom.appendChild(node);
  4200. }
  4201. return node;
  4202. },
  4203. getTooltipData: function() {
  4204. var me = this;
  4205. return {
  4206. component: me,
  4207. tracking: me.getTrackingValue(),
  4208. trackOver: me.getTrackOver(),
  4209. value: me.getValue()
  4210. };
  4211. },
  4212. /**
  4213. * Forcibly refreshes both glyph and tooltip rendering.
  4214. * @private
  4215. */
  4216. refresh: function() {
  4217. var me = this;
  4218. if (me.invalidGlyphs) {
  4219. me.refreshGlyphs(true);
  4220. }
  4221. if (me.invalidTip) {
  4222. me.refreshTip(true);
  4223. }
  4224. },
  4225. /**
  4226. * Refreshes the glyph text rendering unless we are currently performing a
  4227. * bulk config change (initConfig or setConfig).
  4228. * @param {Boolean} now Pass `true` to force the refresh to happen now.
  4229. * @private
  4230. */
  4231. refreshGlyphs: function(now) {
  4232. var me = this,
  4233. later = !now && (me.isConfiguring || me.isReconfiguring),
  4234. el, glyphs, limit, on, off, trackerEl, valueEl;
  4235. if (!later) {
  4236. el = me.getGlyphTextNode(me.innerEl.dom);
  4237. valueEl = me.getGlyphTextNode(me.valueEl.dom);
  4238. trackerEl = me.getGlyphTextNode(me.trackerEl.dom);
  4239. glyphs = me.getGlyphs();
  4240. limit = me.getLimit();
  4241. for (on = off = ''; limit--; ) {
  4242. off += glyphs[0];
  4243. on += glyphs[1];
  4244. }
  4245. el.nodeValue = off;
  4246. valueEl.nodeValue = on;
  4247. trackerEl.nodeValue = on;
  4248. }
  4249. me.invalidGlyphs = later;
  4250. },
  4251. /**
  4252. * Refreshes the tooltip text rendering unless we are currently performing a
  4253. * bulk config change (initConfig or setConfig).
  4254. * @param {Boolean} now Pass `true` to force the refresh to happen now.
  4255. * @private
  4256. */
  4257. refreshTip: function(now) {
  4258. var me = this,
  4259. later = !now && (me.isConfiguring || me.isReconfiguring),
  4260. data, text, tooltip;
  4261. if (!later) {
  4262. tooltip = me.getTip();
  4263. if (tooltip) {
  4264. data = me.getTooltipData();
  4265. text = tooltip(data);
  4266. me.setTooltipText(text);
  4267. }
  4268. }
  4269. me.invalidTip = later;
  4270. },
  4271. /**
  4272. * Convert the coordinates of the given `Event` into a rating value.
  4273. * @param {Ext.event.Event} event The event.
  4274. * @return {Number} The rating based on the given event coordinates.
  4275. * @private
  4276. */
  4277. valueFromEvent: function(event) {
  4278. var me = this,
  4279. el = me.innerEl,
  4280. ex = event.getX(),
  4281. rounding = me.getRounding(),
  4282. cx = el.getX(),
  4283. x = ex - cx,
  4284. w = el.getWidth(),
  4285. limit = me.getLimit(),
  4286. v;
  4287. if (me.getInherited().rtl) {
  4288. x = w - x;
  4289. }
  4290. v = x / w * limit;
  4291. // We have to round up here so that the area we are over is considered
  4292. // the value.
  4293. v = Math.ceil(v / rounding) * rounding;
  4294. return v;
  4295. },
  4296. /**
  4297. * Convert the given rating into a width percentage.
  4298. * @param {Number} value The rating value to convert.
  4299. * @return {String} The width percentage to represent the given value.
  4300. * @private
  4301. */
  4302. valueToPercent: function(value) {
  4303. value = (value / this.getLimit()) * 100;
  4304. return value + '%';
  4305. }
  4306. }
  4307. });
  4308. /**
  4309. * @class Ext.ux.rating.Picker
  4310. */
  4311. Ext.define('Ext.ux.overrides.rating.Picker', {
  4312. override: 'Ext.ux.rating.Picker',
  4313. //<debug>
  4314. initConfig: function(config) {
  4315. if (config && config.tooltip) {
  4316. config.tip = config.tooltip;
  4317. Ext.log.warn('[Ext.ux.rating.Picker] The "tooltip" config was replaced by "tip"');
  4318. }
  4319. this.callParent([
  4320. config
  4321. ]);
  4322. },
  4323. //</debug>
  4324. updateTooltipText: function(text) {
  4325. var innerEl = this.innerEl,
  4326. QuickTips = Ext.tip && Ext.tip.QuickTipManager,
  4327. tip = QuickTips && QuickTips.tip,
  4328. target;
  4329. if (QuickTips) {
  4330. innerEl.dom.setAttribute('data-qtip', text);
  4331. this.trackerEl.dom.setAttribute('data-qtip', text);
  4332. // If the QuickTipManager is active over our widget, we need to update
  4333. // the tooltip text directly.
  4334. target = tip && tip.activeTarget;
  4335. target = target && target.el;
  4336. if (target && innerEl.contains(target)) {
  4337. tip.update(text);
  4338. }
  4339. }
  4340. }
  4341. });
  4342. /**
  4343. * Base class from Ext.ux.TabReorderer.
  4344. */
  4345. Ext.define('Ext.ux.BoxReorderer', {
  4346. extend: 'Ext.plugin.Abstract',
  4347. alias: 'plugin.boxreorderer',
  4348. requires: [
  4349. 'Ext.dd.DD'
  4350. ],
  4351. mixins: {
  4352. observable: 'Ext.util.Observable'
  4353. },
  4354. /**
  4355. * @cfg {String} itemSelector
  4356. * A {@link Ext.DomQuery DomQuery} selector which identifies the encapsulating elements of child
  4357. * Components which participate in reordering.
  4358. */
  4359. itemSelector: '.x-box-item',
  4360. /**
  4361. * @cfg {Mixed} animate
  4362. * If truthy, child reordering is animated so that moved boxes slide smoothly into position.
  4363. * If this option is numeric, it is used as the animation duration in milliseconds.
  4364. */
  4365. animate: 100,
  4366. /**
  4367. * @event StartDrag
  4368. * Fires when dragging of a child Component begins.
  4369. * @param {Ext.ux.BoxReorderer} this
  4370. * @param {Ext.container.Container} container The owning Container
  4371. * @param {Ext.Component} dragCmp The Component being dragged
  4372. * @param {Number} idx The start index of the Component being dragged.
  4373. */
  4374. /**
  4375. * @event Drag
  4376. * Fires during dragging of a child Component.
  4377. * @param {Ext.ux.BoxReorderer} this
  4378. * @param {Ext.container.Container} container The owning Container
  4379. * @param {Ext.Component} dragCmp The Component being dragged
  4380. * @param {Number} startIdx The index position from which the Component was initially dragged.
  4381. * @param {Number} idx The current closest index to which the Component would drop.
  4382. */
  4383. /**
  4384. * @event ChangeIndex
  4385. * Fires when dragging of a child Component causes its drop index to change.
  4386. * @param {Ext.ux.BoxReorderer} this
  4387. * @param {Ext.container.Container} container The owning Container
  4388. * @param {Ext.Component} dragCmp The Component being dragged
  4389. * @param {Number} startIdx The index position from which the Component was initially dragged.
  4390. * @param {Number} idx The current closest index to which the Component would drop.
  4391. */
  4392. /**
  4393. * @event Drop
  4394. * Fires when a child Component is dropped at a new index position.
  4395. * @param {Ext.ux.BoxReorderer} this
  4396. * @param {Ext.container.Container} container The owning Container
  4397. * @param {Ext.Component} dragCmp The Component being dropped
  4398. * @param {Number} startIdx The index position from which the Component was initially dragged.
  4399. * @param {Number} idx The index at which the Component is being dropped.
  4400. */
  4401. constructor: function() {
  4402. this.callParent(arguments);
  4403. this.mixins.observable.constructor.call(this);
  4404. },
  4405. init: function(container) {
  4406. var me = this,
  4407. layout = container.getLayout();
  4408. me.container = container;
  4409. // We must use LTR method names and properties.
  4410. // The underlying Element APIs normalize them.
  4411. me.names = layout._props[layout.type].names;
  4412. // Set our animatePolicy to animate the start position (ie x for HBox, y for VBox)
  4413. me.animatePolicy = {};
  4414. me.animatePolicy[me.names.x] = true;
  4415. // Initialize the DD on first layout, when the innerCt has been created.
  4416. me.container.on({
  4417. scope: me,
  4418. boxready: me.onBoxReady,
  4419. beforedestroy: me.onContainerDestroy
  4420. });
  4421. },
  4422. /**
  4423. * @private
  4424. * Clear up on Container destroy
  4425. */
  4426. onContainerDestroy: function() {
  4427. var dd = this.dd;
  4428. if (dd) {
  4429. dd.unreg();
  4430. this.dd = null;
  4431. }
  4432. },
  4433. onBoxReady: function() {
  4434. var me = this,
  4435. layout = me.container.getLayout(),
  4436. names = me.names,
  4437. dd;
  4438. // Create a DD instance. Poke the handlers in.
  4439. // TODO: Ext5's DD classes should apply config to themselves.
  4440. // TODO: Ext5's DD classes should not use init internally because it collides with use as a plugin
  4441. // TODO: Ext5's DD classes should be Observable.
  4442. // TODO: When all the above are trus, this plugin should extend the DD class.
  4443. dd = me.dd = new Ext.dd.DD(layout.innerCt, me.container.id + '-reorderer');
  4444. Ext.apply(dd, {
  4445. animate: me.animate,
  4446. reorderer: me,
  4447. container: me.container,
  4448. getDragCmp: me.getDragCmp,
  4449. clickValidator: Ext.Function.createInterceptor(dd.clickValidator, me.clickValidator, me, false),
  4450. onMouseDown: me.onMouseDown,
  4451. startDrag: me.startDrag,
  4452. onDrag: me.onDrag,
  4453. endDrag: me.endDrag,
  4454. getNewIndex: me.getNewIndex,
  4455. doSwap: me.doSwap,
  4456. findReorderable: me.findReorderable,
  4457. names: names
  4458. });
  4459. // Decide which dimension we are measuring, and which measurement metric defines
  4460. // the *start* of the box depending upon orientation.
  4461. dd.dim = names.width;
  4462. dd.startAttr = names.beforeX;
  4463. dd.endAttr = names.afterX;
  4464. },
  4465. getDragCmp: function(e) {
  4466. return this.container.getChildByElement(e.getTarget(this.itemSelector, 10));
  4467. },
  4468. // check if the clicked component is reorderable
  4469. clickValidator: function(e) {
  4470. var cmp = this.getDragCmp(e);
  4471. // If cmp is null, this expression MUST be coerced to boolean so that createInterceptor is able to test it against false
  4472. return !!(cmp && cmp.reorderable !== false);
  4473. },
  4474. onMouseDown: function(e) {
  4475. var me = this,
  4476. container = me.container,
  4477. containerBox, cmpEl, cmpBox;
  4478. // Ascertain which child Component is being mousedowned
  4479. me.dragCmp = me.getDragCmp(e);
  4480. if (me.dragCmp) {
  4481. cmpEl = me.dragCmp.getEl();
  4482. me.startIndex = me.curIndex = container.items.indexOf(me.dragCmp);
  4483. // Start position of dragged Component
  4484. cmpBox = cmpEl.getBox();
  4485. // Last tracked start position
  4486. me.lastPos = cmpBox[me.startAttr];
  4487. // Calculate constraints depending upon orientation
  4488. // Calculate offset from mouse to dragEl position
  4489. containerBox = container.el.getBox();
  4490. if (me.dim === 'width') {
  4491. me.minX = containerBox.left;
  4492. me.maxX = containerBox.right - cmpBox.width;
  4493. me.minY = me.maxY = cmpBox.top;
  4494. me.deltaX = e.getX() - cmpBox.left;
  4495. } else {
  4496. me.minY = containerBox.top;
  4497. me.maxY = containerBox.bottom - cmpBox.height;
  4498. me.minX = me.maxX = cmpBox.left;
  4499. me.deltaY = e.getY() - cmpBox.top;
  4500. }
  4501. me.constrainY = me.constrainX = true;
  4502. }
  4503. },
  4504. startDrag: function() {
  4505. var me = this,
  4506. dragCmp = me.dragCmp;
  4507. if (dragCmp) {
  4508. // For the entire duration of dragging the *Element*, defeat any positioning and animation of the dragged *Component*
  4509. dragCmp.setPosition = Ext.emptyFn;
  4510. dragCmp.animate = false;
  4511. // Animate the BoxLayout just for the duration of the drag operation.
  4512. if (me.animate) {
  4513. me.container.getLayout().animatePolicy = me.reorderer.animatePolicy;
  4514. }
  4515. // We drag the Component element
  4516. me.dragElId = dragCmp.getEl().id;
  4517. me.reorderer.fireEvent('StartDrag', me, me.container, dragCmp, me.curIndex);
  4518. // Suspend events, and set the disabled flag so that the mousedown and mouseup events
  4519. // that are going to take place do not cause any other UI interaction.
  4520. dragCmp.suspendEvents();
  4521. dragCmp.disabled = true;
  4522. dragCmp.el.setStyle('zIndex', 100);
  4523. } else {
  4524. me.dragElId = null;
  4525. }
  4526. },
  4527. /**
  4528. * @private
  4529. * Find next or previous reorderable component index.
  4530. * @param {Number} newIndex The initial drop index.
  4531. * @return {Number} The index of the reorderable component.
  4532. */
  4533. findReorderable: function(newIndex) {
  4534. var me = this,
  4535. items = me.container.items,
  4536. newItem;
  4537. if (items.getAt(newIndex).reorderable === false) {
  4538. newItem = items.getAt(newIndex);
  4539. if (newIndex > me.startIndex) {
  4540. while (newItem && newItem.reorderable === false) {
  4541. newIndex++;
  4542. newItem = items.getAt(newIndex);
  4543. }
  4544. } else {
  4545. while (newItem && newItem.reorderable === false) {
  4546. newIndex--;
  4547. newItem = items.getAt(newIndex);
  4548. }
  4549. }
  4550. }
  4551. newIndex = Math.min(Math.max(newIndex, 0), items.getCount() - 1);
  4552. if (items.getAt(newIndex).reorderable === false) {
  4553. return -1;
  4554. }
  4555. return newIndex;
  4556. },
  4557. /**
  4558. * @private
  4559. * Swap 2 components.
  4560. * @param {Number} newIndex The initial drop index.
  4561. */
  4562. doSwap: function(newIndex) {
  4563. var me = this,
  4564. items = me.container.items,
  4565. container = me.container,
  4566. orig, dest, tmpIndex;
  4567. newIndex = me.findReorderable(newIndex);
  4568. if (newIndex === -1 || newIndex === me.curIndex) {
  4569. return;
  4570. }
  4571. me.reorderer.fireEvent('ChangeIndex', me, container, me.dragCmp, me.startIndex, newIndex);
  4572. orig = items.getAt(me.curIndex);
  4573. dest = items.getAt(newIndex);
  4574. items.remove(orig);
  4575. tmpIndex = Math.min(Math.max(newIndex, 0), items.getCount() - 1);
  4576. items.insert(tmpIndex, orig);
  4577. items.remove(dest);
  4578. items.insert(me.curIndex, dest);
  4579. // Make the Box Container the topmost layout participant during the layout.
  4580. container.updateLayout({
  4581. isRoot: true
  4582. });
  4583. me.curIndex = newIndex;
  4584. },
  4585. onDrag: function(e) {
  4586. var me = this,
  4587. newIndex;
  4588. newIndex = me.getNewIndex(e.getPoint());
  4589. if ((newIndex !== undefined)) {
  4590. me.reorderer.fireEvent('Drag', me, me.container, me.dragCmp, me.startIndex, me.curIndex);
  4591. me.doSwap(newIndex);
  4592. }
  4593. },
  4594. endDrag: function(e) {
  4595. if (e) {
  4596. e.stopEvent();
  4597. }
  4598. var me = this,
  4599. layout = me.container.getLayout(),
  4600. temp;
  4601. if (me.dragCmp) {
  4602. delete me.dragElId;
  4603. // Reinstate the Component's positioning method after mouseup, and allow the layout system to animate it.
  4604. delete me.dragCmp.setPosition;
  4605. me.dragCmp.animate = true;
  4606. // Ensure the lastBox is correct for the animation system to restore to when it creates the "from" animation frame
  4607. me.dragCmp.lastBox[me.names.x] = me.dragCmp.getPosition(true)[me.names.widthIndex];
  4608. // Make the Box Container the topmost layout participant during the layout.
  4609. me.container.updateLayout({
  4610. isRoot: true
  4611. });
  4612. // Attempt to hook into the afteranimate event of the drag Component to call the cleanup
  4613. temp = Ext.fx.Manager.getFxQueue(me.dragCmp.el.id)[0];
  4614. if (temp) {
  4615. temp.on({
  4616. afteranimate: me.reorderer.afterBoxReflow,
  4617. scope: me
  4618. });
  4619. } else // If not animated, clean up after the mouseup has happened so that we don't click the thing being dragged
  4620. {
  4621. Ext.asap(me.reorderer.afterBoxReflow, me);
  4622. }
  4623. if (me.animate) {
  4624. delete layout.animatePolicy;
  4625. }
  4626. me.reorderer.fireEvent('drop', me, me.container, me.dragCmp, me.startIndex, me.curIndex);
  4627. }
  4628. },
  4629. /**
  4630. * @private
  4631. * Called after the boxes have been reflowed after the drop.
  4632. * Re-enabled the dragged Component.
  4633. */
  4634. afterBoxReflow: function() {
  4635. var me = this;
  4636. me.dragCmp.el.setStyle('zIndex', '');
  4637. me.dragCmp.disabled = false;
  4638. me.dragCmp.resumeEvents();
  4639. },
  4640. /**
  4641. * @private
  4642. * Calculate drop index based upon the dragEl's position.
  4643. */
  4644. getNewIndex: function(pointerPos) {
  4645. var me = this,
  4646. dragEl = me.getDragEl(),
  4647. dragBox = Ext.fly(dragEl).getBox(),
  4648. targetEl, targetBox, targetMidpoint,
  4649. i = 0,
  4650. it = me.container.items.items,
  4651. ln = it.length,
  4652. lastPos = me.lastPos;
  4653. me.lastPos = dragBox[me.startAttr];
  4654. for (; i < ln; i++) {
  4655. targetEl = it[i].getEl();
  4656. // Only look for a drop point if this found item is an item according to our selector
  4657. // and is not the item being dragged
  4658. if (targetEl.dom !== dragEl && targetEl.is(me.reorderer.itemSelector)) {
  4659. targetBox = targetEl.getBox();
  4660. targetMidpoint = targetBox[me.startAttr] + (targetBox[me.dim] >> 1);
  4661. if (i < me.curIndex) {
  4662. if ((dragBox[me.startAttr] < lastPos) && (dragBox[me.startAttr] < (targetMidpoint - 5))) {
  4663. return i;
  4664. }
  4665. } else if (i > me.curIndex) {
  4666. if ((dragBox[me.startAttr] > lastPos) && (dragBox[me.endAttr] > (targetMidpoint + 5))) {
  4667. return i;
  4668. }
  4669. }
  4670. }
  4671. }
  4672. }
  4673. });
  4674. /**
  4675. * This plugin can enable a cell to cell drag and drop operation within the same grid view.
  4676. *
  4677. * Note that the plugin must be added to the grid view, not to the grid panel. For example,
  4678. * using {@link Ext.panel.Table viewConfig}:
  4679. *
  4680. * viewConfig: {
  4681. * plugins: {
  4682. * celldragdrop: {
  4683. * // Remove text from source cell and replace with value of emptyText.
  4684. * applyEmptyText: true,
  4685. *
  4686. * //emptyText: Ext.String.htmlEncode('<<foo>>'),
  4687. *
  4688. * // Will only allow drops of the same type.
  4689. * enforceType: true
  4690. * }
  4691. * }
  4692. * }
  4693. */
  4694. Ext.define('Ext.ux.CellDragDrop', {
  4695. extend: 'Ext.plugin.Abstract',
  4696. alias: 'plugin.celldragdrop',
  4697. uses: [
  4698. 'Ext.view.DragZone'
  4699. ],
  4700. /**
  4701. * @cfg {Boolean} enforceType
  4702. * Set to `true` to only allow drops of the same type.
  4703. *
  4704. * Defaults to `false`.
  4705. */
  4706. enforceType: false,
  4707. /**
  4708. * @cfg {Boolean} applyEmptyText
  4709. * If `true`, then use the value of {@link #emptyText} to replace the drag record's value after a node drop.
  4710. * Note that, if dropped on a cell of a different type, it will convert the default text according to its own conversion rules.
  4711. *
  4712. * Defaults to `false`.
  4713. */
  4714. applyEmptyText: false,
  4715. /**
  4716. * @cfg {String} emptyText
  4717. * If {@link #applyEmptyText} is `true`, then this value as the drag record's value after a node drop.
  4718. *
  4719. * Defaults to an empty string.
  4720. */
  4721. emptyText: '',
  4722. /**
  4723. * @cfg {String} dropBackgroundColor
  4724. * The default background color for when a drop is allowed.
  4725. *
  4726. * Defaults to green.
  4727. */
  4728. dropBackgroundColor: 'green',
  4729. /**
  4730. * @cfg {String} noDropBackgroundColor
  4731. * The default background color for when a drop is not allowed.
  4732. *
  4733. * Defaults to red.
  4734. */
  4735. noDropBackgroundColor: 'red',
  4736. /**
  4737. * @cfg {String} dragText
  4738. * The text to show while dragging.
  4739. *
  4740. * Two placeholders can be used in the text:
  4741. *
  4742. * - `{0}` The number of selected items.
  4743. * - `{1}` 's' when more than 1 items (only useful for English).
  4744. * @locale
  4745. */
  4746. dragText: '{0} selected row{1}',
  4747. /**
  4748. * @cfg {String} ddGroup
  4749. * A named drag drop group to which this object belongs. If a group is specified, then both the DragZones and
  4750. * DropZone used by this plugin will only interact with other drag drop objects in the same group.
  4751. */
  4752. ddGroup: "GridDD",
  4753. /**
  4754. * @cfg {Boolean} enableDrop
  4755. * Set to `false` to disallow the View from accepting drop gestures.
  4756. */
  4757. enableDrop: true,
  4758. /**
  4759. * @cfg {Boolean} enableDrag
  4760. * Set to `false` to disallow dragging items from the View.
  4761. */
  4762. enableDrag: true,
  4763. /**
  4764. * @cfg {Object/Boolean} containerScroll
  4765. * True to register this container with the Scrollmanager for auto scrolling during drag operations.
  4766. * A {@link Ext.dd.ScrollManager} configuration may also be passed.
  4767. */
  4768. containerScroll: false,
  4769. init: function(view) {
  4770. var me = this;
  4771. view.on('render', me.onViewRender, me, {
  4772. single: true
  4773. });
  4774. },
  4775. destroy: function() {
  4776. var me = this;
  4777. me.dragZone = me.dropZone = Ext.destroy(me.dragZone, me.dropZone);
  4778. me.callParent();
  4779. },
  4780. enable: function() {
  4781. var me = this;
  4782. if (me.dragZone) {
  4783. me.dragZone.unlock();
  4784. }
  4785. if (me.dropZone) {
  4786. me.dropZone.unlock();
  4787. }
  4788. me.callParent();
  4789. },
  4790. disable: function() {
  4791. var me = this;
  4792. if (me.dragZone) {
  4793. me.dragZone.lock();
  4794. }
  4795. if (me.dropZone) {
  4796. me.dropZone.lock();
  4797. }
  4798. me.callParent();
  4799. },
  4800. onViewRender: function(view) {
  4801. var me = this,
  4802. scrollEl;
  4803. if (me.enableDrag) {
  4804. if (me.containerScroll) {
  4805. scrollEl = view.getEl();
  4806. }
  4807. me.dragZone = new Ext.view.DragZone({
  4808. view: view,
  4809. ddGroup: me.dragGroup || me.ddGroup,
  4810. dragText: me.dragText,
  4811. containerScroll: me.containerScroll,
  4812. scrollEl: scrollEl,
  4813. getDragData: function(e) {
  4814. var view = this.view,
  4815. item = e.getTarget(view.getItemSelector()),
  4816. record = view.getRecord(item),
  4817. cell = e.getTarget(view.getCellSelector()),
  4818. dragEl, header;
  4819. if (item) {
  4820. dragEl = document.createElement('div');
  4821. dragEl.className = 'x-form-text';
  4822. dragEl.appendChild(document.createTextNode(cell.textContent || cell.innerText));
  4823. header = view.getHeaderByCell(cell);
  4824. return {
  4825. event: new Ext.EventObjectImpl(e),
  4826. ddel: dragEl,
  4827. item: e.target,
  4828. columnName: header.dataIndex,
  4829. record: record
  4830. };
  4831. }
  4832. },
  4833. onInitDrag: function(x, y) {
  4834. var self = this,
  4835. data = self.dragData,
  4836. view = self.view,
  4837. selectionModel = view.getSelectionModel(),
  4838. record = data.record,
  4839. el = data.ddel;
  4840. // Update the selection to match what would have been selected if the user had
  4841. // done a full click on the target node rather than starting a drag from it.
  4842. if (!selectionModel.isSelected(record)) {
  4843. selectionModel.select(record, true);
  4844. }
  4845. Ext.fly(self.ddel).update(el.textContent || el.innerText);
  4846. self.proxy.update(self.ddel);
  4847. self.onStartDrag(x, y);
  4848. return true;
  4849. }
  4850. });
  4851. }
  4852. if (me.enableDrop) {
  4853. me.dropZone = new Ext.dd.DropZone(view.el, {
  4854. view: view,
  4855. ddGroup: me.dropGroup || me.ddGroup,
  4856. containerScroll: true,
  4857. getTargetFromEvent: function(e) {
  4858. var self = this,
  4859. view = self.view,
  4860. cell = e.getTarget(view.cellSelector),
  4861. row, header;
  4862. // Ascertain whether the mousemove is within a grid cell.
  4863. if (cell) {
  4864. row = view.findItemByChild(cell);
  4865. header = view.getHeaderByCell(cell);
  4866. if (row && header) {
  4867. return {
  4868. node: cell,
  4869. record: view.getRecord(row),
  4870. columnName: header.dataIndex
  4871. };
  4872. }
  4873. }
  4874. },
  4875. // On Node enter, see if it is valid for us to drop the field on that type of column.
  4876. onNodeEnter: function(target, dd, e, dragData) {
  4877. var self = this,
  4878. destType = target.record.getField(target.columnName).type.toUpperCase(),
  4879. sourceType = dragData.record.getField(dragData.columnName).type.toUpperCase();
  4880. delete self.dropOK;
  4881. // Return if no target node or if over the same cell as the source of the drag.
  4882. if (!target || target.node === dragData.item.parentNode) {
  4883. return;
  4884. }
  4885. // Check whether the data type of the column being dropped on accepts the
  4886. // dragged field type. If so, set dropOK flag, and highlight the target node.
  4887. if (me.enforceType && destType !== sourceType) {
  4888. self.dropOK = false;
  4889. if (me.noDropCls) {
  4890. Ext.fly(target.node).addCls(me.noDropCls);
  4891. } else {
  4892. Ext.fly(target.node).applyStyles({
  4893. backgroundColor: me.noDropBackgroundColor
  4894. });
  4895. }
  4896. return false;
  4897. }
  4898. self.dropOK = true;
  4899. if (me.dropCls) {
  4900. Ext.fly(target.node).addCls(me.dropCls);
  4901. } else {
  4902. Ext.fly(target.node).applyStyles({
  4903. backgroundColor: me.dropBackgroundColor
  4904. });
  4905. }
  4906. },
  4907. // Return the class name to add to the drag proxy. This provides a visual indication
  4908. // of drop allowed or not allowed.
  4909. onNodeOver: function(target, dd, e, dragData) {
  4910. return this.dropOK ? this.dropAllowed : this.dropNotAllowed;
  4911. },
  4912. // Highlight the target node.
  4913. onNodeOut: function(target, dd, e, dragData) {
  4914. var cls = this.dropOK ? me.dropCls : me.noDropCls;
  4915. if (cls) {
  4916. Ext.fly(target.node).removeCls(cls);
  4917. } else {
  4918. Ext.fly(target.node).applyStyles({
  4919. backgroundColor: ''
  4920. });
  4921. }
  4922. },
  4923. // Process the drop event if we have previously ascertained that a drop is OK.
  4924. onNodeDrop: function(target, dd, e, dragData) {
  4925. if (this.dropOK) {
  4926. target.record.set(target.columnName, dragData.record.get(dragData.columnName));
  4927. if (me.applyEmptyText) {
  4928. dragData.record.set(dragData.columnName, me.emptyText);
  4929. }
  4930. return true;
  4931. }
  4932. },
  4933. onCellDrop: Ext.emptyFn
  4934. });
  4935. }
  4936. }
  4937. });
  4938. /**
  4939. * @class Ext.ux.DataTip
  4940. * @extends Ext.tip.ToolTip
  4941. * This plugin implements automatic tooltip generation for an arbitrary number of child nodes *within* a Component.
  4942. *
  4943. * This plugin is applied to a high level Component, which contains repeating elements, and depending on the host Component type,
  4944. * it automatically selects a {@link Ext.ToolTip#delegate delegate} so that it appears when the mouse enters a sub-element.
  4945. *
  4946. * When applied to a GridPanel, this ToolTip appears when over a row, and the Record's data is applied
  4947. * using this object's {@link #tpl} template.
  4948. *
  4949. * When applied to a DataView, this ToolTip appears when over a view node, and the Record's data is applied
  4950. * using this object's {@link #tpl} template.
  4951. *
  4952. * When applied to a TreePanel, this ToolTip appears when over a tree node, and the Node's {@link Ext.data.Model} record data is applied
  4953. * using this object's {@link #tpl} template.
  4954. *
  4955. * When applied to a FormPanel, this ToolTip appears when over a Field, and the Field's `tooltip` property is used is applied
  4956. * using this object's {@link #tpl} template, or if it is a string, used as HTML content. If there is no `tooltip` property,
  4957. * the field itself is used as the template's data object.
  4958. *
  4959. * If more complex logic is needed to determine content, then the {@link #beforeshow} event may be used.
  4960. * This class also publishes a **`beforeshowtip`** event through its host Component. The *host Component* fires the
  4961. * **`beforeshowtip`** event.
  4962. */
  4963. Ext.define('Ext.ux.DataTip', function(DataTip) {
  4964. // Target the body (if the host is a Panel), or, if there is no body, the main Element.
  4965. function onHostRender() {
  4966. var e = this.isXType('panel') ? this.body : this.el;
  4967. if (this.dataTip.renderToTarget) {
  4968. this.dataTip.render(e);
  4969. }
  4970. this.dataTip.setTarget(e);
  4971. }
  4972. function updateTip(tip, data) {
  4973. if (tip.rendered) {
  4974. if (tip.host.fireEvent('beforeshowtip', tip.eventHost, tip, data) === false) {
  4975. return false;
  4976. }
  4977. tip.update(data);
  4978. } else {
  4979. if (Ext.isString(data)) {
  4980. tip.html = data;
  4981. } else {
  4982. tip.data = data;
  4983. }
  4984. }
  4985. }
  4986. function beforeViewTipShow(tip) {
  4987. var rec = this.view.getRecord(tip.triggerElement),
  4988. data;
  4989. if (rec) {
  4990. data = tip.initialConfig.data ? Ext.apply(tip.initialConfig.data, rec.data) : rec.data;
  4991. return updateTip(tip, data);
  4992. } else {
  4993. return false;
  4994. }
  4995. }
  4996. function beforeFormTipShow(tip) {
  4997. var field = Ext.getCmp(tip.triggerElement.id);
  4998. if (field && (field.tooltip || tip.tpl)) {
  4999. return updateTip(tip, field.tooltip || field);
  5000. } else {
  5001. return false;
  5002. }
  5003. }
  5004. return {
  5005. extend: 'Ext.tip.ToolTip',
  5006. mixins: {
  5007. plugin: 'Ext.plugin.Abstract'
  5008. },
  5009. alias: 'plugin.datatip',
  5010. lockableScope: 'both',
  5011. constructor: function(config) {
  5012. var me = this;
  5013. me.callParent([
  5014. config
  5015. ]);
  5016. me.mixins.plugin.constructor.call(me, config);
  5017. },
  5018. init: function(host) {
  5019. var me = this;
  5020. me.mixins.plugin.init.call(me, host);
  5021. host.dataTip = me;
  5022. me.host = host;
  5023. if (host.isXType('tablepanel')) {
  5024. me.view = host.getView();
  5025. if (host.ownerLockable) {
  5026. me.host = host.ownerLockable;
  5027. }
  5028. me.delegate = me.delegate || me.view.rowSelector;
  5029. me.on('beforeshow', beforeViewTipShow);
  5030. } else if (host.isXType('dataview')) {
  5031. me.view = me.host;
  5032. me.delegate = me.delegate || host.itemSelector;
  5033. me.on('beforeshow', beforeViewTipShow);
  5034. } else if (host.isXType('form')) {
  5035. me.delegate = '.' + Ext.form.Labelable.prototype.formItemCls;
  5036. me.on('beforeshow', beforeFormTipShow);
  5037. } else if (host.isXType('combobox')) {
  5038. me.view = host.getPicker();
  5039. me.delegate = me.delegate || me.view.getItemSelector();
  5040. me.on('beforeshow', beforeViewTipShow);
  5041. }
  5042. if (host.rendered) {
  5043. onHostRender.call(host);
  5044. } else {
  5045. host.onRender = Ext.Function.createSequence(host.onRender, onHostRender);
  5046. }
  5047. }
  5048. };
  5049. });
  5050. /**
  5051. * Transition plugin for DataViews
  5052. */
  5053. Ext.define('Ext.ux.DataView.Animated', {
  5054. alias: 'plugin.ux-animated-dataview',
  5055. /**
  5056. * @property defaults
  5057. * @type Object
  5058. * Default configuration options for all DataViewTransition instances
  5059. */
  5060. defaults: {
  5061. duration: 750,
  5062. idProperty: 'id'
  5063. },
  5064. /**
  5065. * Creates the plugin instance, applies defaults
  5066. * @constructor
  5067. * @param {Object} config Optional config object
  5068. */
  5069. constructor: function(config) {
  5070. Ext.apply(this, config || {}, this.defaults);
  5071. },
  5072. /**
  5073. * Initializes the transition plugin. Overrides the dataview's default refresh function
  5074. * @param {Ext.view.View} dataview The dataview
  5075. */
  5076. init: function(dataview) {
  5077. var me = this,
  5078. store = dataview.store,
  5079. items = dataview.all,
  5080. task = {
  5081. interval: 20
  5082. },
  5083. duration = me.duration;
  5084. /**
  5085. * @property dataview
  5086. * @type Ext.view.View
  5087. * Reference to the DataView this instance is bound to
  5088. */
  5089. me.dataview = dataview;
  5090. dataview.blockRefresh = true;
  5091. dataview.updateIndexes = Ext.Function.createSequence(dataview.updateIndexes, function() {
  5092. this.getTargetEl().select(this.itemSelector).each(function(element, composite, index) {
  5093. element.dom.id = Ext.util.Format.format("{0}-{1}", dataview.id, store.getAt(index).internalId);
  5094. }, this);
  5095. }, dataview);
  5096. /**
  5097. * @property dataviewID
  5098. * @type String
  5099. * The string ID of the DataView component. This is used internally when animating child objects
  5100. */
  5101. me.dataviewID = dataview.id;
  5102. /**
  5103. * @property cachedStoreData
  5104. * @type Object
  5105. * A cache of existing store data, keyed by id. This is used to determine
  5106. * whether any items were added or removed from the store on data change
  5107. */
  5108. me.cachedStoreData = {};
  5109. //catch the store data with the snapshot immediately
  5110. me.cacheStoreData(store.data || store.snapshot);
  5111. dataview.on('resize', function() {
  5112. var store = dataview.store;
  5113. if (store.getCount() > 0) {}
  5114. }, // reDraw.call(this, store);
  5115. this);
  5116. // Buffer listenher so that rapid calls, for example a filter followed by a sort
  5117. // Only produce one redraw.
  5118. dataview.store.on({
  5119. datachanged: reDraw,
  5120. scope: this,
  5121. buffer: 50
  5122. });
  5123. function reDraw() {
  5124. var parentEl = dataview.getTargetEl(),
  5125. parentElY = parentEl.getY(),
  5126. parentElPaddingTop = parentEl.getPadding('t'),
  5127. added = me.getAdded(store),
  5128. removed = me.getRemoved(store),
  5129. remaining = me.getRemaining(store),
  5130. itemArray, i, id,
  5131. itemFly = new Ext.dom.Fly(),
  5132. rtl = me.dataview.getInherited().rtl,
  5133. oldPos, newPos,
  5134. styleSide = rtl ? 'right' : 'left',
  5135. newStyle = {};
  5136. // Not yet rendered
  5137. if (!parentEl) {
  5138. return;
  5139. }
  5140. // Collect nodes that will be removed in the forthcoming refresh so
  5141. // that we can put them back in order to fade them out
  5142. Ext.iterate(removed, function(recId, item) {
  5143. id = me.dataviewID + '-' + recId;
  5144. // Stop any animations for removed items and ensure th.
  5145. Ext.fx.Manager.stopAnimation(id);
  5146. item.dom = Ext.getDom(id);
  5147. if (!item.dom) {
  5148. delete removed[recId];
  5149. }
  5150. });
  5151. me.cacheStoreData(store);
  5152. // stores the current top and left values for each element (discovered below)
  5153. var oldPositions = {},
  5154. newPositions = {};
  5155. // Find current positions of elements which are to remain after the refresh.
  5156. Ext.iterate(remaining, function(id, item) {
  5157. if (itemFly.attach(Ext.getDom(me.dataviewID + '-' + id))) {
  5158. oldPos = oldPositions[id] = {
  5159. top: itemFly.getY() - parentElY - itemFly.getMargin('t') - parentElPaddingTop
  5160. };
  5161. oldPos[styleSide] = me.getItemX(itemFly);
  5162. } else {
  5163. delete remaining[id];
  5164. }
  5165. });
  5166. // The view MUST refresh, creating items in the natural flow, and collecting the items
  5167. // so that its item collection is consistent.
  5168. dataview.refresh();
  5169. // Replace removed nodes so that they can be faded out, THEN removed
  5170. Ext.iterate(removed, function(id, item) {
  5171. parentEl.dom.appendChild(item.dom);
  5172. itemFly.attach(item.dom).animate({
  5173. duration: duration,
  5174. opacity: 0,
  5175. callback: function(anim) {
  5176. var el = Ext.get(anim.target.id);
  5177. if (el) {
  5178. el.destroy();
  5179. }
  5180. }
  5181. });
  5182. delete item.dom;
  5183. });
  5184. // We have taken care of any removals.
  5185. // If the store is empty, we are done.
  5186. if (!store.getCount()) {
  5187. return;
  5188. }
  5189. // Collect the correct new positions after the refresh
  5190. itemArray = items.slice();
  5191. // Reverse order so that moving to absolute position does not affect the position of
  5192. // the next one we're looking at.
  5193. for (i = itemArray.length - 1; i >= 0; i--) {
  5194. id = store.getAt(i).internalId;
  5195. itemFly.attach(itemArray[i]);
  5196. newPositions[id] = {
  5197. dom: itemFly.dom,
  5198. top: itemFly.getY() - parentElY - itemFly.getMargin('t') - parentElPaddingTop
  5199. };
  5200. newPositions[id][styleSide] = me.getItemX(itemFly);
  5201. // We're going to absolutely position each item.
  5202. // If it is a "remaining" one from last refesh, shunt it back to
  5203. // its old position from where it will be animated.
  5204. newPos = oldPositions[id] || newPositions[id];
  5205. // set absolute positioning on all DataView items. We need to set position, left and
  5206. // top at the same time to avoid any flickering
  5207. newStyle.position = 'absolute';
  5208. newStyle.top = newPos.top + "px";
  5209. newStyle[styleSide] = newPos.left + "px";
  5210. itemFly.applyStyles(newStyle);
  5211. }
  5212. // This is the function which moves remaining items to their new position
  5213. var doAnimate = function() {
  5214. var elapsed = new Date() - task.taskStartTime,
  5215. fraction = elapsed / duration;
  5216. if (fraction >= 1) {
  5217. // At end, return all items to natural flow.
  5218. newStyle.position = newStyle.top = newStyle[styleSide] = '';
  5219. for (id in newPositions) {
  5220. itemFly.attach(newPositions[id].dom).applyStyles(newStyle);
  5221. }
  5222. Ext.TaskManager.stop(task);
  5223. } else {
  5224. // In frame, move each "remaining" item according to time elapsed
  5225. for (id in remaining) {
  5226. var oldPos = oldPositions[id],
  5227. newPos = newPositions[id],
  5228. oldTop = oldPos.top,
  5229. newTop = newPos.top,
  5230. oldLeft = oldPos[styleSide],
  5231. newLeft = newPos[styleSide],
  5232. diffTop = fraction * Math.abs(oldTop - newTop),
  5233. diffLeft = fraction * Math.abs(oldLeft - newLeft),
  5234. midTop = oldTop > newTop ? oldTop - diffTop : oldTop + diffTop,
  5235. midLeft = oldLeft > newLeft ? oldLeft - diffLeft : oldLeft + diffLeft;
  5236. newStyle.top = midTop + "px";
  5237. newStyle[styleSide] = midLeft + "px";
  5238. itemFly.attach(newPos.dom).applyStyles(newStyle);
  5239. }
  5240. }
  5241. };
  5242. // Fade in new items
  5243. Ext.iterate(added, function(id, item) {
  5244. if (itemFly.attach(Ext.getDom(me.dataviewID + '-' + id))) {
  5245. itemFly.setOpacity(0);
  5246. itemFly.animate({
  5247. duration: duration,
  5248. opacity: 1
  5249. });
  5250. }
  5251. });
  5252. // Stop any previous animations
  5253. Ext.TaskManager.stop(task);
  5254. task.run = doAnimate;
  5255. Ext.TaskManager.start(task);
  5256. me.cacheStoreData(store);
  5257. }
  5258. },
  5259. getItemX: function(el) {
  5260. var rtl = this.dataview.getInherited().rtl,
  5261. parentEl = el.up('');
  5262. if (rtl) {
  5263. return parentEl.getViewRegion().right - el.getRegion().right + el.getMargin('r');
  5264. } else {
  5265. return el.getX() - parentEl.getX() - el.getMargin('l') - parentEl.getPadding('l');
  5266. }
  5267. },
  5268. /**
  5269. * Caches the records from a store locally for comparison later
  5270. * @param {Ext.data.Store} store The store to cache data from
  5271. */
  5272. cacheStoreData: function(store) {
  5273. var cachedStoreData = this.cachedStoreData = {};
  5274. store.each(function(record) {
  5275. cachedStoreData[record.internalId] = record;
  5276. });
  5277. },
  5278. /**
  5279. * Returns all records that were already in the DataView
  5280. * @return {Object} All existing records
  5281. */
  5282. getExisting: function() {
  5283. return this.cachedStoreData;
  5284. },
  5285. /**
  5286. * Returns the total number of items that are currently visible in the DataView
  5287. * @return {Number} The number of existing items
  5288. */
  5289. getExistingCount: function() {
  5290. var count = 0,
  5291. items = this.getExisting();
  5292. for (var k in items) {
  5293. count++;
  5294. }
  5295. return count;
  5296. },
  5297. /**
  5298. * Returns all records in the given store that were not already present
  5299. * @param {Ext.data.Store} store The updated store instance
  5300. * @return {Object} Object of records not already present in the dataview in format {id: record}
  5301. */
  5302. getAdded: function(store) {
  5303. var cachedStoreData = this.cachedStoreData,
  5304. added = {};
  5305. store.each(function(record) {
  5306. if (cachedStoreData[record.internalId] == null) {
  5307. added[record.internalId] = record;
  5308. }
  5309. });
  5310. return added;
  5311. },
  5312. /**
  5313. * Returns all records that are present in the DataView but not the new store
  5314. * @param {Ext.data.Store} store The updated store instance
  5315. * @return {Array} Array of records that used to be present
  5316. */
  5317. getRemoved: function(store) {
  5318. var cachedStoreData = this.cachedStoreData,
  5319. removed = {},
  5320. id;
  5321. for (id in cachedStoreData) {
  5322. if (store.findBy(function(record) {
  5323. return record.internalId == id;
  5324. }) == -1) {
  5325. removed[id] = cachedStoreData[id];
  5326. }
  5327. }
  5328. return removed;
  5329. },
  5330. /**
  5331. * Returns all records that are already present and are still present in the new store
  5332. * @param {Ext.data.Store} store The updated store instance
  5333. * @return {Object} Object of records that are still present from last time in format {id: record}
  5334. */
  5335. getRemaining: function(store) {
  5336. var cachedStoreData = this.cachedStoreData,
  5337. remaining = {};
  5338. store.each(function(record) {
  5339. if (cachedStoreData[record.internalId] != null) {
  5340. remaining[record.internalId] = record;
  5341. }
  5342. });
  5343. return remaining;
  5344. }
  5345. });
  5346. /**
  5347. *
  5348. */
  5349. Ext.define('Ext.ux.DataView.DragSelector', {
  5350. requires: [
  5351. 'Ext.dd.DragTracker',
  5352. 'Ext.util.Region'
  5353. ],
  5354. alias: 'plugin.dataviewdragselector',
  5355. /**
  5356. * Initializes the plugin by setting up the drag tracker
  5357. */
  5358. init: function(dataview) {
  5359. var scroller = dataview.getScrollable();
  5360. // If the client dataview is scrollable, and this is a PointerEvents device
  5361. // we cannot intercept the pointer to inplement dragselect.
  5362. if (scroller && (scroller.getX() || scroller.getY()) && (Ext.supports.PointerEvents || Ext.supports.MSPointerEvents)) {
  5363. //<debug>
  5364. Ext.log.warn('DragSelector not available on PointerEvent devices');
  5365. //</debug>
  5366. return;
  5367. }
  5368. /**
  5369. * @property dataview
  5370. * @type Ext.view.View
  5371. * The DataView bound to this instance
  5372. */
  5373. this.dataview = dataview;
  5374. dataview.mon(dataview, {
  5375. beforecontainerclick: this.cancelClick,
  5376. scope: this,
  5377. render: {
  5378. fn: this.onRender,
  5379. scope: this,
  5380. single: true
  5381. }
  5382. });
  5383. },
  5384. /**
  5385. * @private
  5386. * Called when the attached DataView is rendered. This sets up the DragTracker instance that will be used
  5387. * to created a dragged selection area
  5388. */
  5389. onRender: function() {
  5390. /**
  5391. * @property tracker
  5392. * @type Ext.dd.DragTracker
  5393. * The DragTracker attached to this instance. Note that the 4 on* functions are called in the scope of the
  5394. * DragTracker ('this' refers to the DragTracker inside those functions), so we pass a reference to the
  5395. * DragSelector so that we can call this class's functions.
  5396. */
  5397. this.tracker = Ext.create('Ext.dd.DragTracker', {
  5398. dataview: this.dataview,
  5399. el: this.dataview.el,
  5400. onBeforeStart: this.onBeforeStart,
  5401. onStart: this.onStart.bind(this),
  5402. onDrag: this.onDrag.bind(this),
  5403. onEnd: Ext.Function.createDelayed(this.onEnd, 100, this)
  5404. });
  5405. /**
  5406. * @property dragRegion
  5407. * @type Ext.util.Region
  5408. * Represents the region currently dragged out by the user. This is used to figure out which dataview nodes are
  5409. * in the selected area and to set the size of the Proxy element used to highlight the current drag area
  5410. */
  5411. this.dragRegion = Ext.create('Ext.util.Region');
  5412. },
  5413. /**
  5414. * @private
  5415. * Listener attached to the DragTracker's onBeforeStart event. Returns false if the drag didn't start within the
  5416. * DataView's el
  5417. */
  5418. onBeforeStart: function(e) {
  5419. return e.target === this.dataview.getEl().dom;
  5420. },
  5421. /**
  5422. * @private
  5423. * Listener attached to the DragTracker's onStart event. Cancel's the DataView's containerclick event from firing
  5424. * and sets the start co-ordinates of the Proxy element. Clears any existing DataView selection
  5425. * @param {Ext.event.Event} e The click event
  5426. */
  5427. onStart: function(e) {
  5428. var dataview = this.dataview;
  5429. // Flag which controls whether the cancelClick method vetoes the processing of the DataView's containerclick event.
  5430. // On IE (where else), this needs to remain set for a millisecond after mouseup because even though the mouse has
  5431. // moved, the mouseup will still trigger a click event.
  5432. this.dragging = true;
  5433. //here we reset and show the selection proxy element and cache the regions each item in the dataview take up
  5434. this.fillRegions();
  5435. this.getProxy().show();
  5436. dataview.getSelectionModel().deselectAll();
  5437. },
  5438. /**
  5439. * @private
  5440. * Reusable handler that's used to cancel the container click event when dragging on the dataview. See onStart for
  5441. * details
  5442. */
  5443. cancelClick: function() {
  5444. return !this.dragging;
  5445. },
  5446. /**
  5447. * @private
  5448. * Listener attached to the DragTracker's onDrag event. Figures out how large the drag selection area should be and
  5449. * updates the proxy element's size to match. Then iterates over all of the rendered items and marks them selected
  5450. * if the drag region touches them
  5451. * @param {Ext.event.Event} e The drag event
  5452. */
  5453. onDrag: function(e) {
  5454. var selModel = this.dataview.getSelectionModel(),
  5455. dragRegion = this.dragRegion,
  5456. bodyRegion = this.bodyRegion,
  5457. proxy = this.getProxy(),
  5458. regions = this.regions,
  5459. length = regions.length,
  5460. startXY = this.tracker.startXY,
  5461. currentXY = this.tracker.getXY(),
  5462. minX = Math.min(startXY[0], currentXY[0]),
  5463. minY = Math.min(startXY[1], currentXY[1]),
  5464. width = Math.abs(startXY[0] - currentXY[0]),
  5465. height = Math.abs(startXY[1] - currentXY[1]),
  5466. region, selected, i;
  5467. Ext.apply(dragRegion, {
  5468. top: minY,
  5469. left: minX,
  5470. right: minX + width,
  5471. bottom: minY + height
  5472. });
  5473. dragRegion.constrainTo(bodyRegion);
  5474. proxy.setBox(dragRegion);
  5475. for (i = 0; i < length; i++) {
  5476. region = regions[i];
  5477. selected = dragRegion.intersect(region);
  5478. if (selected) {
  5479. selModel.select(i, true);
  5480. } else {
  5481. selModel.deselect(i);
  5482. }
  5483. }
  5484. },
  5485. /**
  5486. * @method
  5487. * @private
  5488. * Listener attached to the DragTracker's onEnd event. This is a delayed function which executes 1
  5489. * millisecond after it has been called. This is because the dragging flag must remain active to cancel
  5490. * the containerclick event which the mouseup event will trigger.
  5491. * @param {Ext.event.Event} e The event object
  5492. */
  5493. onEnd: function(e) {
  5494. var dataview = this.dataview,
  5495. selModel = dataview.getSelectionModel();
  5496. this.dragging = false;
  5497. this.getProxy().hide();
  5498. },
  5499. /**
  5500. * @private
  5501. * Creates a Proxy element that will be used to highlight the drag selection region
  5502. * @return {Ext.Element} The Proxy element
  5503. */
  5504. getProxy: function() {
  5505. if (!this.proxy) {
  5506. this.proxy = this.dataview.getEl().createChild({
  5507. tag: 'div',
  5508. cls: 'x-view-selector'
  5509. });
  5510. }
  5511. return this.proxy;
  5512. },
  5513. /**
  5514. * @private
  5515. * Gets the region taken up by each rendered node in the DataView. We use these regions to figure out which nodes
  5516. * to select based on the selector region the user has dragged out
  5517. */
  5518. fillRegions: function() {
  5519. var dataview = this.dataview,
  5520. regions = this.regions = [];
  5521. dataview.all.each(function(node) {
  5522. regions.push(node.getRegion());
  5523. });
  5524. this.bodyRegion = dataview.getEl().getRegion();
  5525. }
  5526. });
  5527. /**
  5528. * ## Basic DataView with Draggable mixin.
  5529. *
  5530. * Ext.Loader.setPath('Ext.ux', '../../../SDK/extjs/examples/ux');
  5531. *
  5532. * Ext.define('My.cool.View', {
  5533. * extend: 'Ext.view.View',
  5534. *
  5535. * mixins: {
  5536. * draggable: 'Ext.ux.DataView.Draggable'
  5537. * },
  5538. *
  5539. * initComponent: function() {
  5540. * this.mixins.draggable.init(this, {
  5541. * ddConfig: {
  5542. * ddGroup: 'someGroup'
  5543. * }
  5544. * });
  5545. *
  5546. * this.callParent(arguments);
  5547. * }
  5548. * });
  5549. *
  5550. * Ext.onReady(function () {
  5551. * Ext.create('Ext.data.Store', {
  5552. * storeId: 'baseball',
  5553. * fields: ['team', 'established'],
  5554. * data: [
  5555. * { team: 'Atlanta Braves', established: '1871' },
  5556. * { team: 'Miami Marlins', established: '1993' },
  5557. * { team: 'New York Mets', established: '1962' },
  5558. * { team: 'Philadelphia Phillies', established: '1883' },
  5559. * { team: 'Washington Nationals', established: '1969' }
  5560. * ]
  5561. * });
  5562. *
  5563. * Ext.create('My.cool.View', {
  5564. * store: Ext.StoreMgr.get('baseball'),
  5565. * tpl: [
  5566. * '<tpl for=".">',
  5567. * '<p class="team">',
  5568. * 'The {team} were founded in {established}.',
  5569. * '</p>',
  5570. * '</tpl>'
  5571. * ],
  5572. * itemSelector: 'p.team',
  5573. * renderTo: Ext.getBody()
  5574. * });
  5575. * });
  5576. */
  5577. Ext.define('Ext.ux.DataView.Draggable', {
  5578. requires: 'Ext.dd.DragZone',
  5579. /**
  5580. * @cfg {String} ghostCls The CSS class added to the outermost element of the created ghost proxy
  5581. * (defaults to 'x-dataview-draggable-ghost')
  5582. */
  5583. ghostCls: 'x-dataview-draggable-ghost',
  5584. /**
  5585. * @cfg {Ext.XTemplate/Array} ghostTpl The template used in the ghost DataView
  5586. */
  5587. ghostTpl: [
  5588. '<tpl for=".">',
  5589. '{title}',
  5590. '</tpl>'
  5591. ],
  5592. /**
  5593. * @cfg {Object} ddConfig Config object that is applied to the internally created DragZone
  5594. */
  5595. /**
  5596. * @cfg {String} ghostConfig Config object that is used to configure the internally created DataView
  5597. */
  5598. init: function(dataview, config) {
  5599. /**
  5600. * @property dataview
  5601. * @type Ext.view.View
  5602. * The Ext.view.View instance that this DragZone is attached to
  5603. */
  5604. this.dataview = dataview;
  5605. dataview.on('render', this.onRender, this);
  5606. Ext.apply(this, {
  5607. itemSelector: dataview.itemSelector,
  5608. ghostConfig: {}
  5609. }, config || {});
  5610. Ext.applyIf(this.ghostConfig, {
  5611. itemSelector: 'img',
  5612. cls: this.ghostCls,
  5613. tpl: this.ghostTpl
  5614. });
  5615. },
  5616. /**
  5617. * @private
  5618. * Called when the attached DataView is rendered. Sets up the internal DragZone
  5619. */
  5620. onRender: function() {
  5621. var me = this,
  5622. config = Ext.apply({}, me.ddConfig || {}, {
  5623. dvDraggable: me,
  5624. dataview: me.dataview,
  5625. getDragData: me.getDragData,
  5626. getTreeNode: me.getTreeNode,
  5627. afterRepair: me.afterRepair,
  5628. getRepairXY: me.getRepairXY
  5629. });
  5630. /**
  5631. * @property dragZone
  5632. * @type Ext.dd.DragZone
  5633. * The attached DragZone instane
  5634. */
  5635. me.dragZone = Ext.create('Ext.dd.DragZone', me.dataview.getEl(), config);
  5636. // This is for https://www.w3.org/TR/pointerevents/ platforms.
  5637. // On these platforms, the pointerdown event (single touchstart) is reserved for
  5638. // initiating a scroll gesture. Setting the items draggable defeats that and
  5639. // enables the touchstart event to trigger a drag.
  5640. //
  5641. // Two finger dragging will still scroll on these platforms.
  5642. me.dataview.setItemsDraggable(true);
  5643. },
  5644. getDragData: function(e) {
  5645. var draggable = this.dvDraggable,
  5646. dataview = this.dataview,
  5647. selModel = dataview.getSelectionModel(),
  5648. target = e.getTarget(draggable.itemSelector),
  5649. selected, dragData;
  5650. if (target) {
  5651. // preventDefault is needed here to avoid the browser dragging the image
  5652. // instead of dragging the container like it's supposed to
  5653. e.preventDefault();
  5654. if (!dataview.isSelected(target)) {
  5655. selModel.select(dataview.getRecord(target));
  5656. }
  5657. selected = dataview.getSelectedNodes();
  5658. dragData = {
  5659. copy: true,
  5660. nodes: selected,
  5661. records: selModel.getSelection(),
  5662. item: true
  5663. };
  5664. if (selected.length === 1) {
  5665. dragData.single = true;
  5666. dragData.ddel = target;
  5667. } else {
  5668. dragData.multi = true;
  5669. dragData.ddel = draggable.prepareGhost(selModel.getSelection());
  5670. }
  5671. return dragData;
  5672. }
  5673. return false;
  5674. },
  5675. getTreeNode: function() {},
  5676. // console.log('test');
  5677. afterRepair: function() {
  5678. this.dragging = false;
  5679. var nodes = this.dragData.nodes,
  5680. length = nodes.length,
  5681. i;
  5682. //FIXME: Ext.fly does not work here for some reason, only frames the last node
  5683. for (i = 0; i < length; i++) {
  5684. Ext.get(nodes[i]).frame('#8db2e3', 1);
  5685. }
  5686. },
  5687. /**
  5688. * @private
  5689. * Returns the x and y co-ordinates that the dragged item should be animated back to if it was dropped on an
  5690. * invalid drop target. If we're dragging more than one item we don't animate back and just allow afterRepair
  5691. * to frame each dropped item.
  5692. */
  5693. getRepairXY: function(e) {
  5694. if (this.dragData.multi) {
  5695. return false;
  5696. } else {
  5697. var repairEl = Ext.get(this.dragData.ddel),
  5698. repairXY = repairEl.getXY();
  5699. //take the item's margins and padding into account to make the repair animation line up perfectly
  5700. repairXY[0] += repairEl.getPadding('t') + repairEl.getMargin('t');
  5701. repairXY[1] += repairEl.getPadding('l') + repairEl.getMargin('l');
  5702. return repairXY;
  5703. }
  5704. },
  5705. /**
  5706. * Updates the internal ghost DataView by ensuring it is rendered and contains the correct records
  5707. * @param {Array} records The set of records that is currently selected in the parent DataView
  5708. * @return {HTMLElement} The Ghost DataView's encapsulating HTMLElement.
  5709. */
  5710. prepareGhost: function(records) {
  5711. return this.createGhost(records).getEl().dom;
  5712. },
  5713. /**
  5714. * @private
  5715. * Creates the 'ghost' DataView that follows the mouse cursor during the drag operation. This div is usually a
  5716. * lighter-weight representation of just the nodes that are selected in the parent DataView.
  5717. */
  5718. createGhost: function(records) {
  5719. var me = this,
  5720. store;
  5721. if (me.ghost) {
  5722. (store = me.ghost.store).loadRecords(records);
  5723. } else {
  5724. store = Ext.create('Ext.data.Store', {
  5725. model: records[0].self
  5726. });
  5727. store.loadRecords(records);
  5728. me.ghost = Ext.create('Ext.view.View', Ext.apply({
  5729. renderTo: document.createElement('div'),
  5730. store: store
  5731. }, me.ghostConfig));
  5732. me.ghost.container.skipGarbageCollection = me.ghost.el.skipGarbageCollection = true;
  5733. }
  5734. store.clearData();
  5735. return me.ghost;
  5736. },
  5737. destroy: function() {
  5738. var ghost = this.ghost;
  5739. if (ghost) {
  5740. ghost.container.destroy();
  5741. ghost.destroy();
  5742. }
  5743. this.callParent();
  5744. }
  5745. });
  5746. /**
  5747. *
  5748. */
  5749. Ext.define('Ext.ux.DataView.LabelEditor', {
  5750. extend: 'Ext.Editor',
  5751. alias: 'plugin.dataviewlabeleditor',
  5752. alignment: 'tl-tl',
  5753. completeOnEnter: true,
  5754. cancelOnEsc: true,
  5755. shim: false,
  5756. autoSize: {
  5757. width: 'boundEl',
  5758. height: 'field'
  5759. },
  5760. labelSelector: 'x-editable',
  5761. requires: [
  5762. 'Ext.form.field.Text'
  5763. ],
  5764. constructor: function(config) {
  5765. config.field = config.field || Ext.create('Ext.form.field.Text', {
  5766. allowOnlyWhitespace: false,
  5767. selectOnFocus: true
  5768. });
  5769. this.callParent([
  5770. config
  5771. ]);
  5772. },
  5773. init: function(view) {
  5774. this.view = view;
  5775. this.mon(view, 'afterrender', this.bindEvents, this);
  5776. this.on('complete', this.onSave, this);
  5777. },
  5778. // initialize events
  5779. bindEvents: function() {
  5780. this.mon(this.view.getEl(), {
  5781. click: {
  5782. fn: this.onClick,
  5783. scope: this
  5784. }
  5785. });
  5786. },
  5787. // on mousedown show editor
  5788. onClick: function(e, target) {
  5789. var me = this,
  5790. item, record;
  5791. if (Ext.fly(target).hasCls(me.labelSelector) && !me.editing && !e.ctrlKey && !e.shiftKey) {
  5792. e.stopEvent();
  5793. item = me.view.findItemByChild(target);
  5794. record = me.view.store.getAt(me.view.indexOf(item));
  5795. me.startEdit(target, record.data[me.dataIndex]);
  5796. me.activeRecord = record;
  5797. } else if (me.editing) {
  5798. me.field.blur();
  5799. e.preventDefault();
  5800. }
  5801. },
  5802. // update record
  5803. onSave: function(ed, value) {
  5804. this.activeRecord.set(this.dataIndex, value);
  5805. }
  5806. });
  5807. /**
  5808. * @class Ext.ux.DataViewTransition
  5809. * Transition plugin for DataViews
  5810. */
  5811. Ext.ux.DataViewTransition = Ext.extend(Object, {
  5812. /**
  5813. * @property defaults
  5814. * @type Object
  5815. * Default configuration options for all DataViewTransition instances
  5816. */
  5817. defaults: {
  5818. duration: 750,
  5819. idProperty: 'id'
  5820. },
  5821. /**
  5822. * Creates the plugin instance, applies defaults
  5823. * @constructor
  5824. * @param {Object} config Optional config object
  5825. */
  5826. constructor: function(config) {
  5827. Ext.apply(this, config || {}, this.defaults);
  5828. },
  5829. /**
  5830. * Initializes the transition plugin. Overrides the dataview's default refresh function
  5831. * @param {Ext.view.View} dataview The dataview
  5832. */
  5833. init: function(dataview) {
  5834. /**
  5835. * @property dataview
  5836. * @type Ext.view.View
  5837. * Reference to the DataView this instance is bound to
  5838. */
  5839. this.dataview = dataview;
  5840. var idProperty = this.idProperty;
  5841. dataview.blockRefresh = true;
  5842. dataview.updateIndexes = Ext.Function.createSequence(dataview.updateIndexes, function() {
  5843. this.getTargetEl().select(this.itemSelector).each(function(element, composite, index) {
  5844. element.id = element.dom.id = Ext.util.Format.format("{0}-{1}", dataview.id, dataview.store.getAt(index).get(idProperty));
  5845. }, this);
  5846. }, dataview);
  5847. /**
  5848. * @property dataviewID
  5849. * @type String
  5850. * The string ID of the DataView component. This is used internally when animating child objects
  5851. */
  5852. this.dataviewID = dataview.id;
  5853. /**
  5854. * @property cachedStoreData
  5855. * @type Object
  5856. * A cache of existing store data, keyed by id. This is used to determine
  5857. * whether any items were added or removed from the store on data change
  5858. */
  5859. this.cachedStoreData = {};
  5860. //var store = dataview.store;
  5861. //catch the store data with the snapshot immediately
  5862. this.cacheStoreData(dataview.store.snapshot);
  5863. dataview.store.on('datachanged', function(store) {
  5864. var parentEl = dataview.getTargetEl(),
  5865. calcItem = store.getAt(0),
  5866. added = this.getAdded(store),
  5867. removed = this.getRemoved(store),
  5868. previous = this.getRemaining(store),
  5869. existing = Ext.apply({}, previous, added);
  5870. //hide old items
  5871. Ext.each(removed, function(item) {
  5872. Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).animate({
  5873. remove: false,
  5874. duration: duration,
  5875. opacity: 0,
  5876. useDisplay: true
  5877. });
  5878. }, this);
  5879. //store is empty
  5880. if (calcItem == undefined) {
  5881. this.cacheStoreData(store);
  5882. return;
  5883. }
  5884. var el = Ext.get(this.dataviewID + "-" + calcItem.get(this.idProperty));
  5885. //calculate the number of rows and columns we have
  5886. var itemCount = store.getCount(),
  5887. itemWidth = el.getMargin('lr') + el.getWidth(),
  5888. itemHeight = el.getMargin('bt') + el.getHeight(),
  5889. dvWidth = parentEl.getWidth(),
  5890. columns = Math.floor(dvWidth / itemWidth),
  5891. rows = Math.ceil(itemCount / columns),
  5892. currentRows = Math.ceil(this.getExistingCount() / columns);
  5893. //make sure the correct styles are applied to the parent element
  5894. parentEl.applyStyles({
  5895. display: 'block',
  5896. position: 'relative'
  5897. });
  5898. //stores the current top and left values for each element (discovered below)
  5899. var oldPositions = {},
  5900. newPositions = {},
  5901. elCache = {};
  5902. //find current positions of each element and save a reference in the elCache
  5903. Ext.iterate(previous, function(id, item) {
  5904. var id = item.get(this.idProperty),
  5905. el = elCache[id] = Ext.get(this.dataviewID + '-' + id);
  5906. oldPositions[id] = {
  5907. top: el.getY() - parentEl.getY() - el.getMargin('t') - parentEl.getPadding('t'),
  5908. left: el.getX() - parentEl.getX() - el.getMargin('l') - parentEl.getPadding('l')
  5909. };
  5910. }, this);
  5911. //set absolute positioning on all DataView items. We need to set position, left and
  5912. //top at the same time to avoid any flickering
  5913. Ext.iterate(previous, function(id, item) {
  5914. var oldPos = oldPositions[id],
  5915. el = elCache[id];
  5916. if (el.getStyle('position') != 'absolute') {
  5917. elCache[id].applyStyles({
  5918. position: 'absolute',
  5919. left: oldPos.left + "px",
  5920. top: oldPos.top + "px",
  5921. //we set the width here to make ListViews work correctly. This is not needed for DataViews
  5922. width: el.getWidth(!Ext.isIE || Ext.isStrict),
  5923. height: el.getHeight(!Ext.isIE || Ext.isStrict)
  5924. });
  5925. }
  5926. });
  5927. //get new positions
  5928. var index = 0;
  5929. Ext.iterate(store.data.items, function(item) {
  5930. var id = item.get(idProperty),
  5931. el = elCache[id];
  5932. var column = index % columns,
  5933. row = Math.floor(index / columns),
  5934. top = row * itemHeight,
  5935. left = column * itemWidth;
  5936. newPositions[id] = {
  5937. top: top,
  5938. left: left
  5939. };
  5940. index++;
  5941. }, this);
  5942. //do the movements
  5943. var startTime = new Date(),
  5944. duration = this.duration,
  5945. dataviewID = this.dataviewID;
  5946. var doAnimate = function() {
  5947. var elapsed = new Date() - startTime,
  5948. fraction = elapsed / duration;
  5949. if (fraction >= 1) {
  5950. for (var id in newPositions) {
  5951. Ext.fly(dataviewID + '-' + id).applyStyles({
  5952. top: newPositions[id].top + "px",
  5953. left: newPositions[id].left + "px"
  5954. });
  5955. }
  5956. Ext.TaskManager.stop(task);
  5957. } else {
  5958. //move each item
  5959. for (var id in newPositions) {
  5960. if (!previous[id]) {
  5961. continue;
  5962. }
  5963. var oldPos = oldPositions[id],
  5964. newPos = newPositions[id],
  5965. oldTop = oldPos.top,
  5966. newTop = newPos.top,
  5967. oldLeft = oldPos.left,
  5968. newLeft = newPos.left,
  5969. diffTop = fraction * Math.abs(oldTop - newTop),
  5970. diffLeft = fraction * Math.abs(oldLeft - newLeft),
  5971. midTop = oldTop > newTop ? oldTop - diffTop : oldTop + diffTop,
  5972. midLeft = oldLeft > newLeft ? oldLeft - diffLeft : oldLeft + diffLeft;
  5973. Ext.fly(dataviewID + '-' + id).applyStyles({
  5974. top: midTop + "px",
  5975. left: midLeft + "px"
  5976. });
  5977. }
  5978. }
  5979. };
  5980. var task = {
  5981. run: doAnimate,
  5982. interval: 20,
  5983. scope: this
  5984. };
  5985. Ext.TaskManager.start(task);
  5986. //<debug>
  5987. var count = 0;
  5988. for (var k in added) {
  5989. count++;
  5990. }
  5991. if (Ext.global.console && Ext.global.console.log) {
  5992. Ext.global.console.log('added:', count);
  5993. }
  5994. //</debug>
  5995. //show new items
  5996. Ext.iterate(added, function(id, item) {
  5997. Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).applyStyles({
  5998. top: newPositions[item.get(this.idProperty)].top + "px",
  5999. left: newPositions[item.get(this.idProperty)].left + "px"
  6000. });
  6001. Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).animate({
  6002. remove: false,
  6003. duration: duration,
  6004. opacity: 1
  6005. });
  6006. }, this);
  6007. this.cacheStoreData(store);
  6008. }, this);
  6009. },
  6010. /**
  6011. * Caches the records from a store locally for comparison later
  6012. * @param {Ext.data.Store} store The store to cache data from
  6013. */
  6014. cacheStoreData: function(store) {
  6015. this.cachedStoreData = {};
  6016. store.each(function(record) {
  6017. this.cachedStoreData[record.get(this.idProperty)] = record;
  6018. }, this);
  6019. },
  6020. /**
  6021. * Returns all records that were already in the DataView
  6022. * @return {Object} All existing records
  6023. */
  6024. getExisting: function() {
  6025. return this.cachedStoreData;
  6026. },
  6027. /**
  6028. * Returns the total number of items that are currently visible in the DataView
  6029. * @return {Number} The number of existing items
  6030. */
  6031. getExistingCount: function() {
  6032. var count = 0,
  6033. items = this.getExisting();
  6034. for (var k in items) count++;
  6035. return count;
  6036. },
  6037. /**
  6038. * Returns all records in the given store that were not already present
  6039. * @param {Ext.data.Store} store The updated store instance
  6040. * @return {Object} Object of records not already present in the dataview in format {id: record}
  6041. */
  6042. getAdded: function(store) {
  6043. var added = {};
  6044. store.each(function(record) {
  6045. if (this.cachedStoreData[record.get(this.idProperty)] == undefined) {
  6046. added[record.get(this.idProperty)] = record;
  6047. }
  6048. }, this);
  6049. return added;
  6050. },
  6051. /**
  6052. * Returns all records that are present in the DataView but not the new store
  6053. * @param {Ext.data.Store} store The updated store instance
  6054. * @return {Array} Array of records that used to be present
  6055. */
  6056. getRemoved: function(store) {
  6057. var removed = [];
  6058. for (var id in this.cachedStoreData) {
  6059. if (store.findExact(this.idProperty, Number(id)) == -1) {
  6060. removed.push(this.cachedStoreData[id]);
  6061. }
  6062. }
  6063. return removed;
  6064. },
  6065. /**
  6066. * Returns all records that are already present and are still present in the new store
  6067. * @param {Ext.data.Store} store The updated store instance
  6068. * @return {Object} Object of records that are still present from last time in format {id: record}
  6069. */
  6070. getRemaining: function(store) {
  6071. var remaining = {};
  6072. store.each(function(record) {
  6073. if (this.cachedStoreData[record.get(this.idProperty)] != undefined) {
  6074. remaining[record.get(this.idProperty)] = record;
  6075. }
  6076. }, this);
  6077. return remaining;
  6078. }
  6079. });
  6080. /**
  6081. * An explorer component for navigating hierarchical content. Consists of a breadcrumb bar
  6082. * at the top, tree navigation on the left, and a center panel which displays the contents
  6083. * of a given node.
  6084. */
  6085. Ext.define('Ext.ux.Explorer', {
  6086. extend: 'Ext.panel.Panel',
  6087. xtype: 'explorer',
  6088. requires: [
  6089. 'Ext.layout.container.Border',
  6090. 'Ext.toolbar.Breadcrumb',
  6091. 'Ext.tree.Panel'
  6092. ],
  6093. config: {
  6094. /**
  6095. * @cfg {Object} breadcrumb
  6096. * Configuration object for the breadcrumb toolbar
  6097. */
  6098. breadcrumb: {
  6099. dock: 'top',
  6100. xtype: 'breadcrumb',
  6101. reference: 'breadcrumb'
  6102. },
  6103. /**
  6104. * @cfg {Object} contentView
  6105. * Configuration object for the "content" data view
  6106. */
  6107. contentView: {
  6108. xtype: 'dataview',
  6109. reference: 'contentView',
  6110. region: 'center',
  6111. cls: Ext.baseCSSPrefix + 'explorer-view',
  6112. itemSelector: '.' + Ext.baseCSSPrefix + 'explorer-item',
  6113. tpl: '<tpl for=".">' + '<div class="' + Ext.baseCSSPrefix + 'explorer-item">' + '<div class="{iconCls}">' + '<div class="' + Ext.baseCSSPrefix + 'explorer-node-icon' + '{[values.leaf ? " ' + Ext.baseCSSPrefix + 'explorer-leaf-icon' + '" : ""]}' + '">' + '</div>' + '<div class="' + Ext.baseCSSPrefix + 'explorer-item-text">{text}</div>' + '</div>' + '</div>' + '</tpl>'
  6114. },
  6115. /**
  6116. * @cfg {Ext.data.TreeStore} store
  6117. * The TreeStore to use as the data source
  6118. */
  6119. store: null,
  6120. /**
  6121. * @cfg {Object} tree
  6122. * Configuration object for the tree
  6123. */
  6124. tree: {
  6125. xtype: 'treepanel',
  6126. reference: 'tree',
  6127. region: 'west',
  6128. width: 200
  6129. }
  6130. },
  6131. renderConfig: {
  6132. /**
  6133. * @cfg {Ext.data.TreeModel} selection
  6134. * The selected node
  6135. * @accessor
  6136. */
  6137. selection: null
  6138. },
  6139. layout: 'border',
  6140. referenceHolder: true,
  6141. defaultListenerScope: true,
  6142. cls: Ext.baseCSSPrefix + 'explorer',
  6143. initComponent: function() {
  6144. var me = this,
  6145. store = me.getStore();
  6146. //<debug>
  6147. if (!store) {
  6148. Ext.raise('Ext.ux.Explorer requires a store.');
  6149. }
  6150. //</debug>
  6151. me.dockedItems = [
  6152. me.getBreadcrumb()
  6153. ];
  6154. me.items = [
  6155. me.getTree(),
  6156. me.getContentView()
  6157. ];
  6158. me.callParent();
  6159. },
  6160. applyBreadcrumb: function(breadcrumb) {
  6161. var store = this.getStore();
  6162. breadcrumb = Ext.create(Ext.apply({
  6163. store: store,
  6164. selection: store.getRoot()
  6165. }, breadcrumb));
  6166. breadcrumb.on('selectionchange', '_onBreadcrumbSelectionChange', this);
  6167. return breadcrumb;
  6168. },
  6169. applyContentView: function(contentView) {
  6170. /**
  6171. * @property {Ext.data.Store} contentStore
  6172. * @private
  6173. * The backing store for the content view
  6174. */
  6175. var contentStore = this.contentStore = new Ext.data.Store({
  6176. model: this.getStore().model
  6177. });
  6178. contentView = Ext.create(Ext.apply({
  6179. store: contentStore
  6180. }, contentView));
  6181. return contentView;
  6182. },
  6183. applyTree: function(tree) {
  6184. tree = Ext.create(Ext.apply({
  6185. store: this.getStore()
  6186. }, tree));
  6187. tree.on('selectionchange', '_onTreeSelectionChange', this);
  6188. return tree;
  6189. },
  6190. updateSelection: function(node) {
  6191. var me = this,
  6192. refs = me.getReferences(),
  6193. breadcrumb = refs.breadcrumb,
  6194. tree = refs.tree,
  6195. treeSelectionModel = tree.getSelectionModel(),
  6196. contentStore = me.contentStore,
  6197. parentNode, treeView;
  6198. if (breadcrumb.getSelection() !== node) {
  6199. breadcrumb.setSelection(node);
  6200. }
  6201. if (treeSelectionModel.getSelection()[0] !== node) {
  6202. treeSelectionModel.select([
  6203. node
  6204. ]);
  6205. parentNode = node.parentNode;
  6206. if (parentNode) {
  6207. parentNode.expand();
  6208. }
  6209. treeView = tree.getView();
  6210. treeView.scrollRowIntoView(treeView.getRow(node));
  6211. }
  6212. contentStore.removeAll();
  6213. contentStore.add(node.hasChildNodes() ? node.childNodes : [
  6214. node
  6215. ]);
  6216. },
  6217. updateStore: function(store) {
  6218. this.getBreadcrumb().setStore(store);
  6219. },
  6220. privates: {
  6221. /**
  6222. * Handles the tree's selectionchange event
  6223. * @private
  6224. * @param {Ext.tree.Panel} tree
  6225. * @param {Ext.data.TreeModel[]} selection
  6226. */
  6227. _onTreeSelectionChange: function(tree, selection) {
  6228. this.setSelection(selection[0]);
  6229. },
  6230. /**
  6231. * Handles the breadcrumb bar's selectionchange event
  6232. */
  6233. _onBreadcrumbSelectionChange: function(breadcrumb, selection) {
  6234. this.setSelection(selection);
  6235. }
  6236. }
  6237. });
  6238. /**
  6239. * A plugin for Field Components which creates clones of the Field for as
  6240. * long as the user keeps filling them. Leaving the final one blank ends the repeating series.
  6241. *
  6242. * Usage:
  6243. *
  6244. * items: [{
  6245. * xtype: 'combo',
  6246. * plugins: {
  6247. * fieldreplicator: true
  6248. * },
  6249. * triggerAction: 'all',
  6250. * fieldLabel: 'Select recipient',
  6251. * store: recipientStore
  6252. * }]
  6253. *
  6254. */
  6255. Ext.define('Ext.ux.FieldReplicator', {
  6256. alias: 'plugin.fieldreplicator',
  6257. init: function(field) {
  6258. // Assign the field an id grouping it with fields cloned from it. If it already
  6259. // has an id that means it is itself a clone.
  6260. if (!field.replicatorId) {
  6261. field.replicatorId = Ext.id();
  6262. }
  6263. field.on('blur', this.onBlur, this);
  6264. },
  6265. onBlur: function(field) {
  6266. var ownerCt = field.ownerCt,
  6267. replicatorId = field.replicatorId,
  6268. isEmpty = Ext.isEmpty(field.getRawValue()),
  6269. siblings = ownerCt.query('[replicatorId=' + replicatorId + ']'),
  6270. isLastInGroup = siblings[siblings.length - 1] === field,
  6271. clone, idx;
  6272. // If a field before the final one was blanked out, remove it
  6273. if (isEmpty && !isLastInGroup) {
  6274. Ext.defer(field.destroy, 10, field);
  6275. }
  6276. //delay to allow tab key to move focus first
  6277. // If the field is the last in the list and has a value, add a cloned field after it
  6278. else if (!isEmpty && isLastInGroup) {
  6279. if (field.onReplicate) {
  6280. field.onReplicate();
  6281. }
  6282. clone = field.cloneConfig({
  6283. replicatorId: replicatorId
  6284. });
  6285. idx = ownerCt.items.indexOf(field);
  6286. ownerCt.add(idx + 1, clone);
  6287. }
  6288. }
  6289. });
  6290. /**
  6291. * The GMap Panel UX extends `Ext.panel.Panel` in order to display Google Maps.
  6292. *
  6293. * It is important to note that you must include the following Google Maps API above bootstrap.js in your
  6294. * application's index.html file (or equivilant).
  6295. *
  6296. * <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?v=3&sensor=false"></script>
  6297. *
  6298. * It is important to note that due to the Google Maps loader, you cannot currently include
  6299. * the above JS resource in the Cmd generated app.json file. Doing so interferes with the loading of
  6300. * Ext JS and Google Maps.
  6301. *
  6302. * The following example creates a window containing a GMap Panel. In this case, the center
  6303. * is set as geoCodeAddr, which is a string that Google translates into longitude and latitude.
  6304. *
  6305. * var mapwin = Ext.create('Ext.Window', {
  6306. * layout: 'fit',
  6307. * title: 'GMap Window',
  6308. * width: 450,
  6309. * height: 250,
  6310. * items: {
  6311. * xtype: 'gmappanel',
  6312. * gmapType: 'map',
  6313. * center: {
  6314. * geoCodeAddr: "221B Baker Street",
  6315. * marker: {
  6316. * title: 'Holmes Home'
  6317. * }
  6318. * },
  6319. * mapOptions : {
  6320. * mapTypeId: google.maps.MapTypeId.ROADMAP
  6321. * }
  6322. * }
  6323. * }).show();
  6324. *
  6325. */
  6326. Ext.define('Ext.ux.GMapPanel', {
  6327. extend: 'Ext.panel.Panel',
  6328. alias: 'widget.gmappanel',
  6329. requires: [
  6330. 'Ext.window.MessageBox'
  6331. ],
  6332. initComponent: function() {
  6333. Ext.applyIf(this, {
  6334. plain: true,
  6335. gmapType: 'map',
  6336. border: false
  6337. });
  6338. this.callParent();
  6339. },
  6340. onBoxReady: function() {
  6341. var center = this.center;
  6342. this.callParent(arguments);
  6343. if (center) {
  6344. if (center.geoCodeAddr) {
  6345. this.lookupCode(center.geoCodeAddr, center.marker);
  6346. } else {
  6347. this.createMap(center);
  6348. }
  6349. } else {
  6350. Ext.raise('center is required');
  6351. }
  6352. },
  6353. createMap: function(center, marker) {
  6354. var options = Ext.apply({}, this.mapOptions);
  6355. options = Ext.applyIf(options, {
  6356. zoom: 14,
  6357. center: center,
  6358. mapTypeId: google.maps.MapTypeId.HYBRID
  6359. });
  6360. this.gmap = new google.maps.Map(this.body.dom, options);
  6361. if (marker) {
  6362. this.addMarker(Ext.applyIf(marker, {
  6363. position: center
  6364. }));
  6365. }
  6366. Ext.each(this.markers, this.addMarker, this);
  6367. this.fireEvent('mapready', this, this.gmap);
  6368. },
  6369. addMarker: function(marker) {
  6370. marker = Ext.apply({
  6371. map: this.gmap
  6372. }, marker);
  6373. if (!marker.position) {
  6374. marker.position = new google.maps.LatLng(marker.lat, marker.lng);
  6375. }
  6376. var o = new google.maps.Marker(marker);
  6377. Ext.Object.each(marker.listeners, function(name, fn) {
  6378. google.maps.event.addListener(o, name, fn);
  6379. });
  6380. return o;
  6381. },
  6382. lookupCode: function(addr, marker) {
  6383. this.geocoder = new google.maps.Geocoder();
  6384. this.geocoder.geocode({
  6385. address: addr
  6386. }, Ext.Function.bind(this.onLookupComplete, this, [
  6387. marker
  6388. ], true));
  6389. },
  6390. onLookupComplete: function(data, response, marker) {
  6391. if (response != 'OK') {
  6392. Ext.MessageBox.alert('Error', 'An error occured: "' + response + '"');
  6393. return;
  6394. }
  6395. this.createMap(data[0].geometry.location, marker);
  6396. },
  6397. afterComponentLayout: function(w, h) {
  6398. this.callParent(arguments);
  6399. this.redraw();
  6400. },
  6401. redraw: function() {
  6402. var map = this.gmap;
  6403. if (map) {
  6404. google.maps.event.trigger(map, 'resize');
  6405. }
  6406. }
  6407. });
  6408. /**
  6409. * Barebones iframe implementation.
  6410. */
  6411. Ext.define('Ext.ux.IFrame', {
  6412. extend: 'Ext.Component',
  6413. alias: 'widget.uxiframe',
  6414. loadMask: 'Loading...',
  6415. src: 'about:blank',
  6416. renderTpl: [
  6417. '<iframe src="{src}" id="{id}-iframeEl" data-ref="iframeEl" name="{frameName}" width="100%" height="100%" frameborder="0"></iframe>'
  6418. ],
  6419. childEls: [
  6420. 'iframeEl'
  6421. ],
  6422. initComponent: function() {
  6423. this.callParent();
  6424. this.frameName = this.frameName || this.id + '-frame';
  6425. },
  6426. initEvents: function() {
  6427. var me = this;
  6428. me.callParent();
  6429. me.iframeEl.on('load', me.onLoad, me);
  6430. },
  6431. initRenderData: function() {
  6432. return Ext.apply(this.callParent(), {
  6433. src: this.src,
  6434. frameName: this.frameName
  6435. });
  6436. },
  6437. getBody: function() {
  6438. var doc = this.getDoc();
  6439. return doc.body || doc.documentElement;
  6440. },
  6441. getDoc: function() {
  6442. try {
  6443. return this.getWin().document;
  6444. } catch (ex) {
  6445. return null;
  6446. }
  6447. },
  6448. getWin: function() {
  6449. var me = this,
  6450. name = me.frameName,
  6451. win = Ext.isIE ? me.iframeEl.dom.contentWindow : window.frames[name];
  6452. return win;
  6453. },
  6454. getFrame: function() {
  6455. var me = this;
  6456. return me.iframeEl.dom;
  6457. },
  6458. onLoad: function() {
  6459. var me = this,
  6460. doc = me.getDoc();
  6461. if (doc) {
  6462. this.el.unmask();
  6463. this.fireEvent('load', this);
  6464. } else if (me.src) {
  6465. this.el.unmask();
  6466. this.fireEvent('error', this);
  6467. }
  6468. },
  6469. load: function(src) {
  6470. var me = this,
  6471. text = me.loadMask,
  6472. frame = me.getFrame();
  6473. if (me.fireEvent('beforeload', me, src) !== false) {
  6474. if (text && me.el) {
  6475. me.el.mask(text);
  6476. }
  6477. frame.src = me.src = (src || me.src);
  6478. }
  6479. }
  6480. });
  6481. /*
  6482. * Note: Event relayers are not needed here because the combination of the gesture system and
  6483. * normal focus/blur will handle it.
  6484. * Tested with the examples/classic/desktop app.
  6485. */
  6486. /*
  6487. * TODO items:
  6488. *
  6489. * Iframe should clean up any Ext.dom.Element wrappers around its window, document
  6490. * documentElement and body when it is destroyed. This helps prevent "Permission Denied"
  6491. * errors in IE when Ext.dom.GarbageCollector tries to access those objects on an orphaned
  6492. * iframe. Permission Denied errors can occur in one of the following 2 scenarios:
  6493. *
  6494. * a. When an iframe is removed from the document, and all references to it have been
  6495. * removed, IE will "clear" the window object. At this point the window object becomes
  6496. * completely inaccessible - accessing any of its properties results in a "Permission
  6497. * Denied" error. http://msdn.microsoft.com/en-us/library/ie/hh180174(v=vs.85).aspx
  6498. *
  6499. * b. When an iframe is unloaded (either by navigating to a new url, or via document.open/
  6500. * document.write, new html and body elements are created and the old the html and body
  6501. * elements are orphaned. Accessing the html and body elements or any of their properties
  6502. * results in a "Permission Denied" error.
  6503. */
  6504. /**
  6505. * Basic status bar component that can be used as the bottom toolbar of any {@link Ext.Panel}. In addition to
  6506. * supporting the standard {@link Ext.toolbar.Toolbar} interface for adding buttons, menus and other items, the StatusBar
  6507. * provides a greedy status element that can be aligned to either side and has convenient methods for setting the
  6508. * status text and icon. You can also indicate that something is processing using the {@link #showBusy} method.
  6509. *
  6510. * Ext.create('Ext.Panel', {
  6511. * title: 'StatusBar',
  6512. * // etc.
  6513. * bbar: Ext.create('Ext.ux.StatusBar', {
  6514. * id: 'my-status',
  6515. *
  6516. * // defaults to use when the status is cleared:
  6517. * defaultText: 'Default status text',
  6518. * defaultIconCls: 'default-icon',
  6519. *
  6520. * // values to set initially:
  6521. * text: 'Ready',
  6522. * iconCls: 'ready-icon',
  6523. *
  6524. * // any standard Toolbar items:
  6525. * items: [{
  6526. * text: 'A Button'
  6527. * }, '-', 'Plain Text']
  6528. * })
  6529. * });
  6530. *
  6531. * // Update the status bar later in code:
  6532. * var sb = Ext.getCmp('my-status');
  6533. * sb.setStatus({
  6534. * text: 'OK',
  6535. * iconCls: 'ok-icon',
  6536. * clear: true // auto-clear after a set interval
  6537. * });
  6538. *
  6539. * // Set the status bar to show that something is processing:
  6540. * sb.showBusy();
  6541. *
  6542. * // processing....
  6543. *
  6544. * sb.clearStatus(); // once completeed
  6545. *
  6546. */
  6547. Ext.define('Ext.ux.statusbar.StatusBar', {
  6548. extend: 'Ext.toolbar.Toolbar',
  6549. xtype: 'statusbar',
  6550. alternateClassName: 'Ext.ux.StatusBar',
  6551. requires: [
  6552. 'Ext.toolbar.TextItem'
  6553. ],
  6554. /**
  6555. * @cfg {String} statusAlign
  6556. * The alignment of the status element within the overall StatusBar layout. When the StatusBar is rendered,
  6557. * it creates an internal div containing the status text and icon. Any additional Toolbar items added in the
  6558. * StatusBar's {@link #cfg-items} config, or added via {@link #method-add} or any of the supported add* methods, will be
  6559. * rendered, in added order, to the opposite side. The status element is greedy, so it will automatically
  6560. * expand to take up all sapce left over by any other items. Example usage:
  6561. *
  6562. * // Create a left-aligned status bar containing a button,
  6563. * // separator and text item that will be right-aligned (default):
  6564. * Ext.create('Ext.Panel', {
  6565. * title: 'StatusBar',
  6566. * // etc.
  6567. * bbar: Ext.create('Ext.ux.statusbar.StatusBar', {
  6568. * defaultText: 'Default status text',
  6569. * id: 'status-id',
  6570. * items: [{
  6571. * text: 'A Button'
  6572. * }, '-', 'Plain Text']
  6573. * })
  6574. * });
  6575. *
  6576. * // By adding the statusAlign config, this will create the
  6577. * // exact same toolbar, except the status and toolbar item
  6578. * // layout will be reversed from the previous example:
  6579. * Ext.create('Ext.Panel', {
  6580. * title: 'StatusBar',
  6581. * // etc.
  6582. * bbar: Ext.create('Ext.ux.statusbar.StatusBar', {
  6583. * defaultText: 'Default status text',
  6584. * id: 'status-id',
  6585. * statusAlign: 'right',
  6586. * items: [{
  6587. * text: 'A Button'
  6588. * }, '-', 'Plain Text']
  6589. * })
  6590. * });
  6591. */
  6592. /**
  6593. * @cfg {String} [defaultText='']
  6594. * The default {@link #text} value. This will be used anytime the status bar is cleared with the
  6595. * `useDefaults:true` option.
  6596. */
  6597. /**
  6598. * @cfg {String} [defaultIconCls='']
  6599. * The default {@link #iconCls} value (see the iconCls docs for additional details about customizing the icon).
  6600. * This will be used anytime the status bar is cleared with the `useDefaults:true` option.
  6601. */
  6602. /**
  6603. * @cfg {String} text
  6604. * A string that will be <b>initially</b> set as the status message. This string
  6605. * will be set as innerHTML (html tags are accepted) for the toolbar item.
  6606. * If not specified, the value set for {@link #defaultText} will be used.
  6607. */
  6608. /**
  6609. * @cfg [iconCls='']
  6610. * @inheritdoc Ext.panel.Header#cfg-iconCls
  6611. * @localdoc **Note:** This CSS class will be **initially** set as the status bar
  6612. * icon. See also {@link #defaultIconCls} and {@link #busyIconCls}.
  6613. *
  6614. * Example usage:
  6615. *
  6616. * // Example CSS rule:
  6617. * .x-statusbar .x-status-custom {
  6618. * padding-left: 25px;
  6619. * background: transparent url(images/custom-icon.gif) no-repeat 3px 2px;
  6620. * }
  6621. *
  6622. * // Setting a default icon:
  6623. * var sb = Ext.create('Ext.ux.statusbar.StatusBar', {
  6624. * defaultIconCls: 'x-status-custom'
  6625. * });
  6626. *
  6627. * // Changing the icon:
  6628. * sb.setStatus({
  6629. * text: 'New status',
  6630. * iconCls: 'x-status-custom'
  6631. * });
  6632. */
  6633. /**
  6634. * @cfg {String} cls
  6635. * The base class applied to the containing element for this component on render.
  6636. */
  6637. cls: 'x-statusbar',
  6638. /**
  6639. * @cfg {String} busyIconCls
  6640. * The default {@link #iconCls} applied when calling {@link #showBusy}.
  6641. * It can be overridden at any time by passing the `iconCls` argument into {@link #showBusy}.
  6642. */
  6643. busyIconCls: 'x-status-busy',
  6644. /**
  6645. * @cfg {String} busyText
  6646. * The default {@link #text} applied when calling {@link #showBusy}.
  6647. * It can be overridden at any time by passing the `text` argument into {@link #showBusy}.
  6648. */
  6649. busyText: 'Loading...',
  6650. /**
  6651. * @cfg {Number} autoClear
  6652. * The number of milliseconds to wait after setting the status via
  6653. * {@link #setStatus} before automatically clearing the status text and icon.
  6654. * Note that this only applies when passing the `clear` argument to {@link #setStatus}
  6655. * since that is the only way to defer clearing the status. This can
  6656. * be overridden by specifying a different `wait` value in {@link #setStatus}.
  6657. * Calls to {@link #clearStatus} always clear the status bar immediately and ignore this value.
  6658. */
  6659. autoClear: 5000,
  6660. /**
  6661. * @cfg {String} emptyText
  6662. * The text string to use if no text has been set. If there are no other items in
  6663. * the toolbar using an empty string (`''`) for this value would end up in the toolbar
  6664. * height collapsing since the empty string will not maintain the toolbar height.
  6665. * Use `''` if the toolbar should collapse in height vertically when no text is
  6666. * specified and there are no other items in the toolbar.
  6667. */
  6668. emptyText: '&#160;',
  6669. /**
  6670. * @private
  6671. */
  6672. activeThreadId: 0,
  6673. initComponent: function() {
  6674. var right = this.statusAlign === 'right';
  6675. this.callParent(arguments);
  6676. this.currIconCls = this.iconCls || this.defaultIconCls;
  6677. this.statusEl = Ext.create('Ext.toolbar.TextItem', {
  6678. cls: 'x-status-text ' + (this.currIconCls || ''),
  6679. text: this.text || this.defaultText || ''
  6680. });
  6681. if (right) {
  6682. this.cls += ' x-status-right';
  6683. this.add('->');
  6684. this.add(this.statusEl);
  6685. } else {
  6686. this.insert(0, this.statusEl);
  6687. this.insert(1, '->');
  6688. }
  6689. },
  6690. /**
  6691. * Sets the status {@link #text} and/or {@link #iconCls}. Also supports automatically clearing the
  6692. * status that was set after a specified interval.
  6693. *
  6694. * Example usage:
  6695. *
  6696. * // Simple call to update the text
  6697. * statusBar.setStatus('New status');
  6698. *
  6699. * // Set the status and icon, auto-clearing with default options:
  6700. * statusBar.setStatus({
  6701. * text: 'New status',
  6702. * iconCls: 'x-status-custom',
  6703. * clear: true
  6704. * });
  6705. *
  6706. * // Auto-clear with custom options:
  6707. * statusBar.setStatus({
  6708. * text: 'New status',
  6709. * iconCls: 'x-status-custom',
  6710. * clear: {
  6711. * wait: 8000,
  6712. * anim: false,
  6713. * useDefaults: false
  6714. * }
  6715. * });
  6716. *
  6717. * @param {Object/String} config A config object specifying what status to set, or a string assumed
  6718. * to be the status text (and all other options are defaulted as explained below). A config
  6719. * object containing any or all of the following properties can be passed:
  6720. *
  6721. * @param {String} config.text The status text to display. If not specified, any current
  6722. * status text will remain unchanged.
  6723. *
  6724. * @param {String} config.iconCls The CSS class used to customize the status icon (see
  6725. * {@link #iconCls} for details). If not specified, any current iconCls will remain unchanged.
  6726. *
  6727. * @param {Boolean/Number/Object} config.clear Allows you to set an internal callback that will
  6728. * automatically clear the status text and iconCls after a specified amount of time has passed. If clear is not
  6729. * specified, the new status will not be auto-cleared and will stay until updated again or cleared using
  6730. * {@link #clearStatus}. If `true` is passed, the status will be cleared using {@link #autoClear},
  6731. * {@link #defaultText} and {@link #defaultIconCls} via a fade out animation. If a numeric value is passed,
  6732. * it will be used as the callback interval (in milliseconds), overriding the {@link #autoClear} value.
  6733. * All other options will be defaulted as with the boolean option. To customize any other options,
  6734. * you can pass an object in the format:
  6735. *
  6736. * @param {Number} config.clear.wait The number of milliseconds to wait before clearing
  6737. * (defaults to {@link #autoClear}).
  6738. * @param {Boolean} config.clear.anim False to clear the status immediately once the callback
  6739. * executes (defaults to true which fades the status out).
  6740. * @param {Boolean} config.clear.useDefaults False to completely clear the status text and iconCls
  6741. * (defaults to true which uses {@link #defaultText} and {@link #defaultIconCls}).
  6742. *
  6743. * @return {Ext.ux.statusbar.StatusBar} this
  6744. */
  6745. setStatus: function(config) {
  6746. var me = this;
  6747. config = config || {};
  6748. Ext.suspendLayouts();
  6749. if (Ext.isString(config)) {
  6750. config = {
  6751. text: config
  6752. };
  6753. }
  6754. if (config.text !== undefined) {
  6755. me.setText(config.text);
  6756. }
  6757. if (config.iconCls !== undefined) {
  6758. me.setIcon(config.iconCls);
  6759. }
  6760. if (config.clear) {
  6761. var c = config.clear,
  6762. wait = me.autoClear,
  6763. defaults = {
  6764. useDefaults: true,
  6765. anim: true
  6766. };
  6767. if (Ext.isObject(c)) {
  6768. c = Ext.applyIf(c, defaults);
  6769. if (c.wait) {
  6770. wait = c.wait;
  6771. }
  6772. } else if (Ext.isNumber(c)) {
  6773. wait = c;
  6774. c = defaults;
  6775. } else if (Ext.isBoolean(c)) {
  6776. c = defaults;
  6777. }
  6778. c.threadId = this.activeThreadId;
  6779. Ext.defer(me.clearStatus, wait, me, [
  6780. c
  6781. ]);
  6782. }
  6783. Ext.resumeLayouts(true);
  6784. return me;
  6785. },
  6786. /**
  6787. * Clears the status {@link #text} and {@link #iconCls}. Also supports clearing via an optional fade out animation.
  6788. *
  6789. * @param {Object} [config] A config object containing any or all of the following properties. If this
  6790. * object is not specified the status will be cleared using the defaults below:
  6791. * @param {Boolean} config.anim True to clear the status by fading out the status element (defaults
  6792. * to false which clears immediately).
  6793. * @param {Boolean} config.useDefaults True to reset the text and icon using {@link #defaultText} and
  6794. * {@link #defaultIconCls} (defaults to false which sets the text to '' and removes any existing icon class).
  6795. *
  6796. * @return {Ext.ux.statusbar.StatusBar} this
  6797. */
  6798. clearStatus: function(config) {
  6799. config = config || {};
  6800. var me = this,
  6801. statusEl = me.statusEl;
  6802. if (me.destroyed || config.threadId && config.threadId !== me.activeThreadId) {
  6803. // this means the current call was made internally, but a newer
  6804. // thread has set a message since this call was deferred. Since
  6805. // we don't want to overwrite a newer message just ignore.
  6806. return me;
  6807. }
  6808. var text = config.useDefaults ? me.defaultText : me.emptyText,
  6809. iconCls = config.useDefaults ? (me.defaultIconCls ? me.defaultIconCls : '') : '';
  6810. if (config.anim) {
  6811. // animate the statusEl Ext.Element
  6812. statusEl.el.puff({
  6813. remove: false,
  6814. useDisplay: true,
  6815. callback: function() {
  6816. statusEl.el.show();
  6817. me.setStatus({
  6818. text: text,
  6819. iconCls: iconCls
  6820. });
  6821. }
  6822. });
  6823. } else {
  6824. me.setStatus({
  6825. text: text,
  6826. iconCls: iconCls
  6827. });
  6828. }
  6829. return me;
  6830. },
  6831. /**
  6832. * Convenience method for setting the status text directly. For more flexible options see {@link #setStatus}.
  6833. * @param {String} text (optional) The text to set (defaults to '')
  6834. * @return {Ext.ux.statusbar.StatusBar} this
  6835. */
  6836. setText: function(text) {
  6837. var me = this;
  6838. me.activeThreadId++;
  6839. me.text = text || '';
  6840. if (me.rendered) {
  6841. me.statusEl.setText(me.text);
  6842. }
  6843. return me;
  6844. },
  6845. /**
  6846. * Returns the current status text.
  6847. * @return {String} The status text
  6848. */
  6849. getText: function() {
  6850. return this.text;
  6851. },
  6852. /**
  6853. * Convenience method for setting the status icon directly. For more flexible options see {@link #setStatus}.
  6854. * See {@link #iconCls} for complete details about customizing the icon.
  6855. * @param {String} cls (optional) The icon class to set (defaults to '', and any current icon class is removed)
  6856. * @return {Ext.ux.statusbar.StatusBar} this
  6857. */
  6858. setIcon: function(cls) {
  6859. var me = this;
  6860. me.activeThreadId++;
  6861. cls = cls || '';
  6862. if (me.rendered) {
  6863. if (me.currIconCls) {
  6864. me.statusEl.removeCls(me.currIconCls);
  6865. me.currIconCls = null;
  6866. }
  6867. if (cls.length > 0) {
  6868. me.statusEl.addCls(cls);
  6869. me.currIconCls = cls;
  6870. }
  6871. } else {
  6872. me.currIconCls = cls;
  6873. }
  6874. return me;
  6875. },
  6876. /**
  6877. * Convenience method for setting the status text and icon to special values that are pre-configured to indicate
  6878. * a "busy" state, usually for loading or processing activities.
  6879. *
  6880. * @param {Object/String} config (optional) A config object in the same format supported by {@link #setStatus}, or a
  6881. * string to use as the status text (in which case all other options for setStatus will be defaulted). Use the
  6882. * `text` and/or `iconCls` properties on the config to override the default {@link #busyText}
  6883. * and {@link #busyIconCls} settings. If the config argument is not specified, {@link #busyText} and
  6884. * {@link #busyIconCls} will be used in conjunction with all of the default options for {@link #setStatus}.
  6885. * @return {Ext.ux.statusbar.StatusBar} this
  6886. */
  6887. showBusy: function(config) {
  6888. if (Ext.isString(config)) {
  6889. config = {
  6890. text: config
  6891. };
  6892. }
  6893. config = Ext.applyIf(config || {}, {
  6894. text: this.busyText,
  6895. iconCls: this.busyIconCls
  6896. });
  6897. return this.setStatus(config);
  6898. }
  6899. });
  6900. /**
  6901. * A GridPanel class with live search support.
  6902. */
  6903. Ext.define('Ext.ux.LiveSearchGridPanel', {
  6904. extend: 'Ext.grid.Panel',
  6905. requires: [
  6906. 'Ext.toolbar.TextItem',
  6907. 'Ext.form.field.Checkbox',
  6908. 'Ext.form.field.Text',
  6909. 'Ext.ux.statusbar.StatusBar'
  6910. ],
  6911. /**
  6912. * @private
  6913. * search value initialization
  6914. */
  6915. searchValue: null,
  6916. /**
  6917. * @private
  6918. * The matched positions from the most recent search
  6919. */
  6920. matches: [],
  6921. /**
  6922. * @private
  6923. * The current index matched.
  6924. */
  6925. currentIndex: null,
  6926. /**
  6927. * @private
  6928. * The generated regular expression used for searching.
  6929. */
  6930. searchRegExp: null,
  6931. /**
  6932. * @private
  6933. * Case sensitive mode.
  6934. */
  6935. caseSensitive: false,
  6936. /**
  6937. * @private
  6938. * Regular expression mode.
  6939. */
  6940. regExpMode: false,
  6941. /**
  6942. * @cfg {String} matchCls
  6943. * The matched string css classe.
  6944. */
  6945. matchCls: 'x-livesearch-match',
  6946. defaultStatusText: 'Nothing Found',
  6947. // Component initialization override: adds the top and bottom toolbars and setup headers renderer.
  6948. initComponent: function() {
  6949. var me = this;
  6950. me.tbar = [
  6951. 'Search',
  6952. {
  6953. xtype: 'textfield',
  6954. name: 'searchField',
  6955. hideLabel: true,
  6956. width: 200,
  6957. listeners: {
  6958. change: {
  6959. fn: me.onTextFieldChange,
  6960. scope: this,
  6961. buffer: 500
  6962. }
  6963. }
  6964. },
  6965. {
  6966. xtype: 'button',
  6967. text: '&lt;',
  6968. tooltip: 'Find Previous Row',
  6969. handler: me.onPreviousClick,
  6970. scope: me
  6971. },
  6972. {
  6973. xtype: 'button',
  6974. text: '&gt;',
  6975. tooltip: 'Find Next Row',
  6976. handler: me.onNextClick,
  6977. scope: me
  6978. },
  6979. '-',
  6980. {
  6981. xtype: 'checkbox',
  6982. hideLabel: true,
  6983. margin: '0 0 0 4px',
  6984. handler: me.regExpToggle,
  6985. scope: me
  6986. },
  6987. 'Regular expression',
  6988. {
  6989. xtype: 'checkbox',
  6990. hideLabel: true,
  6991. margin: '0 0 0 4px',
  6992. handler: me.caseSensitiveToggle,
  6993. scope: me
  6994. },
  6995. 'Case sensitive'
  6996. ];
  6997. me.bbar = new Ext.ux.StatusBar({
  6998. defaultText: me.defaultStatusText,
  6999. name: 'searchStatusBar'
  7000. });
  7001. me.callParent(arguments);
  7002. },
  7003. // afterRender override: it adds textfield and statusbar reference and start monitoring keydown events in textfield input
  7004. afterRender: function() {
  7005. var me = this;
  7006. me.callParent(arguments);
  7007. me.textField = me.down('textfield[name=searchField]');
  7008. me.statusBar = me.down('statusbar[name=searchStatusBar]');
  7009. me.view.on('cellkeydown', me.focusTextField, me);
  7010. },
  7011. focusTextField: function(view, td, cellIndex, record, tr, rowIndex, e, eOpts) {
  7012. if (e.getKey() === e.S) {
  7013. e.preventDefault();
  7014. this.textField.focus();
  7015. }
  7016. },
  7017. // detects html tag
  7018. tagsRe: /<[^>]*>/gm,
  7019. // DEL ASCII code
  7020. tagsProtect: '\x0f',
  7021. /**
  7022. * In normal mode it returns the value with protected regexp characters.
  7023. * In regular expression mode it returns the raw value except if the regexp is invalid.
  7024. * @return {String} The value to process or null if the textfield value is blank or invalid.
  7025. * @private
  7026. */
  7027. getSearchValue: function() {
  7028. var me = this,
  7029. value = me.textField.getValue();
  7030. if (value === '') {
  7031. return null;
  7032. }
  7033. if (!me.regExpMode) {
  7034. value = Ext.String.escapeRegex(value);
  7035. } else {
  7036. try {
  7037. new RegExp(value);
  7038. } catch (error) {
  7039. me.statusBar.setStatus({
  7040. text: error.message,
  7041. iconCls: 'x-status-error'
  7042. });
  7043. return null;
  7044. }
  7045. // this is stupid
  7046. if (value === '^' || value === '$') {
  7047. return null;
  7048. }
  7049. }
  7050. return value;
  7051. },
  7052. /**
  7053. * Finds all strings that matches the searched value in each grid cells.
  7054. * @private
  7055. */
  7056. onTextFieldChange: function() {
  7057. var me = this,
  7058. count = 0,
  7059. view = me.view,
  7060. cellSelector = view.cellSelector,
  7061. innerSelector = view.innerSelector,
  7062. columns = me.visibleColumnManager.getColumns();
  7063. view.refresh();
  7064. // reset the statusbar
  7065. me.statusBar.setStatus({
  7066. text: me.defaultStatusText,
  7067. iconCls: ''
  7068. });
  7069. me.searchValue = me.getSearchValue();
  7070. me.matches = [];
  7071. me.currentIndex = null;
  7072. if (me.searchValue !== null) {
  7073. me.searchRegExp = new RegExp(me.getSearchValue(), 'g' + (me.caseSensitive ? '' : 'i'));
  7074. me.store.each(function(record, idx) {
  7075. var node = view.getNode(record);
  7076. if (node) {
  7077. Ext.Array.forEach(columns, function(column) {
  7078. var cell = Ext.fly(node).down(column.getCellInnerSelector(), true),
  7079. matches, cellHTML, seen;
  7080. if (cell) {
  7081. matches = cell.innerHTML.match(me.tagsRe);
  7082. cellHTML = cell.innerHTML.replace(me.tagsRe, me.tagsProtect);
  7083. // populate indexes array, set currentIndex, and replace wrap matched string in a span
  7084. cellHTML = cellHTML.replace(me.searchRegExp, function(m) {
  7085. ++count;
  7086. if (!seen) {
  7087. me.matches.push({
  7088. record: record,
  7089. column: column
  7090. });
  7091. seen = true;
  7092. }
  7093. return '<span class="' + me.matchCls + '">' + m + '</span>';
  7094. }, me);
  7095. // restore protected tags
  7096. Ext.each(matches, function(match) {
  7097. cellHTML = cellHTML.replace(me.tagsProtect, match);
  7098. });
  7099. // update cell html
  7100. cell.innerHTML = cellHTML;
  7101. }
  7102. });
  7103. }
  7104. }, me);
  7105. // results found
  7106. if (count) {
  7107. me.currentIndex = 0;
  7108. me.gotoCurrent();
  7109. me.statusBar.setStatus({
  7110. text: Ext.String.format('{0} match{1} found.', count, count === 1 ? 'es' : ''),
  7111. iconCls: 'x-status-valid'
  7112. });
  7113. }
  7114. }
  7115. // no results found
  7116. if (me.currentIndex === null) {
  7117. me.getSelectionModel().deselectAll();
  7118. me.textField.focus();
  7119. }
  7120. },
  7121. /**
  7122. * Selects the previous row containing a match.
  7123. * @private
  7124. */
  7125. onPreviousClick: function() {
  7126. var me = this,
  7127. matches = me.matches,
  7128. len = matches.length,
  7129. idx = me.currentIndex;
  7130. if (len) {
  7131. me.currentIndex = idx === 0 ? len - 1 : idx - 1;
  7132. me.gotoCurrent();
  7133. }
  7134. },
  7135. /**
  7136. * Selects the next row containing a match.
  7137. * @private
  7138. */
  7139. onNextClick: function() {
  7140. var me = this,
  7141. matches = me.matches,
  7142. len = matches.length,
  7143. idx = me.currentIndex;
  7144. if (len) {
  7145. me.currentIndex = idx === len - 1 ? 0 : idx + 1;
  7146. me.gotoCurrent();
  7147. }
  7148. },
  7149. /**
  7150. * Switch to case sensitive mode.
  7151. * @private
  7152. */
  7153. caseSensitiveToggle: function(checkbox, checked) {
  7154. this.caseSensitive = checked;
  7155. this.onTextFieldChange();
  7156. },
  7157. /**
  7158. * Switch to regular expression mode
  7159. * @private
  7160. */
  7161. regExpToggle: function(checkbox, checked) {
  7162. this.regExpMode = checked;
  7163. this.onTextFieldChange();
  7164. },
  7165. privates: {
  7166. gotoCurrent: function() {
  7167. var pos = this.matches[this.currentIndex];
  7168. this.getNavigationModel().setPosition(pos.record, pos.column);
  7169. this.getSelectionModel().select(pos.record);
  7170. }
  7171. }
  7172. });
  7173. /**
  7174. * The Preview Plugin enables toggle of a configurable preview of all visible records.
  7175. *
  7176. * Note: This plugin does NOT assert itself against an existing RowBody feature and may conflict with
  7177. * another instance of the same plugin.
  7178. */
  7179. Ext.define('Ext.ux.PreviewPlugin', {
  7180. extend: 'Ext.plugin.Abstract',
  7181. alias: 'plugin.preview',
  7182. requires: [
  7183. 'Ext.grid.feature.RowBody'
  7184. ],
  7185. /**
  7186. * @private
  7187. * css class to use to hide the body
  7188. */
  7189. hideBodyCls: 'x-grid-row-body-hidden',
  7190. /**
  7191. * @cfg {String} bodyField
  7192. * Field to display in the preview. Must be a field within the Model definition
  7193. * that the store is using.
  7194. */
  7195. bodyField: '',
  7196. /**
  7197. * @cfg {Boolean} previewExpanded
  7198. */
  7199. previewExpanded: true,
  7200. /**
  7201. * Plugin may be safely declared on either a panel.Grid or a Grid View/viewConfig
  7202. * @param {Ext.grid.Panel/Ext.view.View} target
  7203. */
  7204. setCmp: function(target) {
  7205. this.callParent(arguments);
  7206. // Resolve grid from view as necessary
  7207. var me = this,
  7208. grid = me.cmp = target.isXType('gridview') ? target.grid : target,
  7209. bodyField = me.bodyField,
  7210. hideBodyCls = me.hideBodyCls,
  7211. feature = Ext.create('Ext.grid.feature.RowBody', {
  7212. grid: grid,
  7213. getAdditionalData: function(data, idx, model, rowValues) {
  7214. var getAdditionalData = Ext.grid.feature.RowBody.prototype.getAdditionalData,
  7215. additionalData = {
  7216. rowBody: data[bodyField],
  7217. rowBodyCls: grid.getView().previewExpanded ? '' : hideBodyCls
  7218. };
  7219. if (Ext.isFunction(getAdditionalData)) {
  7220. // "this" is the RowBody object hjere. Do not change to "me"
  7221. Ext.apply(additionalData, getAdditionalData.apply(this, arguments));
  7222. }
  7223. return additionalData;
  7224. }
  7225. }),
  7226. initFeature = function(grid, view) {
  7227. view.previewExpanded = me.previewExpanded;
  7228. // By this point, existing features are already in place, so this must be initialized and added
  7229. view.featuresMC.add(feature);
  7230. feature.init(grid);
  7231. };
  7232. // The grid has already created its view
  7233. if (grid.view) {
  7234. initFeature(grid, grid.view);
  7235. } else // At the time a grid creates its plugins, it has not created all the things
  7236. // it needs to create its view correctly.
  7237. // Process the view and init the RowBody Feature as soon as the view is created.
  7238. {
  7239. grid.on({
  7240. viewcreated: initFeature,
  7241. single: true
  7242. });
  7243. }
  7244. },
  7245. /**
  7246. * Toggle between the preview being expanded/hidden on all rows
  7247. * @param {Boolean} expanded Pass true to expand the record and false to not show the preview.
  7248. */
  7249. toggleExpanded: function(expanded) {
  7250. var grid = this.getCmp(),
  7251. view = grid && grid.getView(),
  7252. bufferedRenderer = view.bufferedRenderer,
  7253. scrollManager = view.scrollManager;
  7254. if (grid && view && expanded !== view.previewExpanded) {
  7255. this.previewExpanded = view.previewExpanded = !!expanded;
  7256. view.refreshView();
  7257. // If we are using the touch scroller, ensure that the scroller knows about
  7258. // the correct scrollable range
  7259. if (scrollManager) {
  7260. if (bufferedRenderer) {
  7261. bufferedRenderer.stretchView(view, bufferedRenderer.getScrollHeight(true));
  7262. } else {
  7263. scrollManager.refresh(true);
  7264. }
  7265. }
  7266. }
  7267. }
  7268. });
  7269. /**
  7270. * Plugin for displaying a progressbar inside of a paging toolbar
  7271. * instead of plain text.
  7272. */
  7273. Ext.define('Ext.ux.ProgressBarPager', {
  7274. alias: 'plugin.ux-progressbarpager',
  7275. requires: [
  7276. 'Ext.ProgressBar'
  7277. ],
  7278. /**
  7279. * @cfg {Number} width
  7280. * <p>The default progress bar width. Default is 225.</p>
  7281. */
  7282. width: 225,
  7283. /**
  7284. * @cfg {String} defaultText
  7285. * <p>The text to display while the store is loading. Default is 'Loading...'</p>
  7286. */
  7287. defaultText: 'Loading...',
  7288. /**
  7289. * @cfg {Object} defaultAnimCfg
  7290. * <p>A {@link Ext.fx.Anim Ext.fx.Anim} configuration object.</p>
  7291. */
  7292. defaultAnimCfg: {
  7293. duration: 1000,
  7294. easing: 'bounceOut'
  7295. },
  7296. /**
  7297. * Creates new ProgressBarPager.
  7298. * @param {Object} config Configuration options
  7299. */
  7300. constructor: function(config) {
  7301. if (config) {
  7302. Ext.apply(this, config);
  7303. }
  7304. },
  7305. init: function(parent) {
  7306. var displayItem;
  7307. if (parent.displayInfo) {
  7308. this.parent = parent;
  7309. displayItem = parent.child("#displayItem");
  7310. if (displayItem) {
  7311. parent.remove(displayItem, true);
  7312. }
  7313. this.progressBar = Ext.create('Ext.ProgressBar', {
  7314. text: this.defaultText,
  7315. width: this.width,
  7316. animate: this.defaultAnimCfg,
  7317. style: {
  7318. cursor: 'pointer'
  7319. },
  7320. listeners: {
  7321. el: {
  7322. scope: this,
  7323. click: this.handleProgressBarClick
  7324. }
  7325. }
  7326. });
  7327. parent.displayItem = this.progressBar;
  7328. parent.add(parent.displayItem);
  7329. Ext.apply(parent, this.parentOverrides);
  7330. }
  7331. },
  7332. /**
  7333. * This method handles the click for the progress bar
  7334. * @private
  7335. */
  7336. handleProgressBarClick: function(e) {
  7337. var parent = this.parent,
  7338. displayItem = parent.displayItem,
  7339. box = this.progressBar.getBox(),
  7340. xy = e.getXY(),
  7341. position = xy[0] - box.x,
  7342. store = parent.store,
  7343. pageSize = parent.pageSize || store.pageSize,
  7344. pages = Math.ceil(store.getTotalCount() / pageSize),
  7345. newPage = Math.max(Math.ceil(position / (displayItem.width / pages)), 1);
  7346. store.loadPage(newPage);
  7347. },
  7348. /**
  7349. * @private
  7350. */
  7351. parentOverrides: {
  7352. /**
  7353. * This method updates the information via the progress bar.
  7354. * @private
  7355. */
  7356. updateInfo: function() {
  7357. if (this.displayItem) {
  7358. var count = this.store.getCount(),
  7359. pageData = this.getPageData(),
  7360. message = count === 0 ? this.emptyMsg : Ext.String.format(this.displayMsg, pageData.fromRecord, pageData.toRecord, this.store.getTotalCount()),
  7361. percentage = pageData.pageCount > 0 ? (pageData.currentPage / pageData.pageCount) : 0;
  7362. this.displayItem.updateProgress(percentage, message, this.animate || this.defaultAnimConfig);
  7363. }
  7364. }
  7365. }
  7366. });
  7367. /**
  7368. * @deprecated 4.0.0 Ext.ux.RowExpander has been promoted to the core framework. Use
  7369. * {@link Ext.grid.plugin.RowExpander} instead.
  7370. *
  7371. * Ext.ux.RowExpander is now just an empty stub that extends Ext.grid.plugin.RowExpander
  7372. * for backward compatibility reasons.
  7373. */
  7374. Ext.define('Ext.ux.RowExpander', {
  7375. extend: 'Ext.grid.plugin.RowExpander'
  7376. });
  7377. /**
  7378. * Plugin for PagingToolbar which replaces the textfield input with a slider
  7379. */
  7380. Ext.define('Ext.ux.SlidingPager', {
  7381. alias: 'plugin.ux-slidingpager',
  7382. requires: [
  7383. 'Ext.slider.Single',
  7384. 'Ext.slider.Tip'
  7385. ],
  7386. /**
  7387. * Creates new SlidingPager.
  7388. * @param {Object} config Configuration options
  7389. */
  7390. constructor: function(config) {
  7391. if (config) {
  7392. Ext.apply(this, config);
  7393. }
  7394. },
  7395. init: function(pbar) {
  7396. var idx = pbar.items.indexOf(pbar.child("#inputItem")),
  7397. slider;
  7398. Ext.each(pbar.items.getRange(idx - 2, idx + 2), function(c) {
  7399. c.hide();
  7400. });
  7401. slider = Ext.create('Ext.slider.Single', {
  7402. width: 114,
  7403. minValue: 1,
  7404. maxValue: 1,
  7405. hideLabel: true,
  7406. tipText: function(thumb) {
  7407. return Ext.String.format('Page <b>{0}</b> of <b>{1}</b>', thumb.value, thumb.slider.maxValue);
  7408. },
  7409. listeners: {
  7410. changecomplete: function(s, v) {
  7411. pbar.store.loadPage(v);
  7412. }
  7413. }
  7414. });
  7415. pbar.insert(idx + 1, slider);
  7416. pbar.on({
  7417. change: function(pb, data) {
  7418. slider.setMaxValue(data.pageCount);
  7419. slider.setValue(data.currentPage);
  7420. }
  7421. });
  7422. }
  7423. });
  7424. /**
  7425. * UX used to provide a spotlight around a specified component/element.
  7426. */
  7427. Ext.define('Ext.ux.Spotlight', {
  7428. /**
  7429. * @private
  7430. * The baseCls for the spotlight elements
  7431. */
  7432. baseCls: 'x-spotlight',
  7433. /**
  7434. * @cfg animate {Boolean} True to animate the spotlight change
  7435. * (defaults to true)
  7436. */
  7437. animate: true,
  7438. /**
  7439. * @cfg duration {Integer} The duration of the animation, in milliseconds
  7440. * (defaults to 250)
  7441. */
  7442. duration: 250,
  7443. /**
  7444. * @cfg easing {String} The type of easing for the spotlight animatation
  7445. * (defaults to null)
  7446. */
  7447. easing: null,
  7448. /**
  7449. * @private
  7450. * True if the spotlight is active on the element
  7451. */
  7452. active: false,
  7453. constructor: function(config) {
  7454. Ext.apply(this, config);
  7455. },
  7456. /**
  7457. * Create all the elements for the spotlight
  7458. */
  7459. createElements: function() {
  7460. var me = this,
  7461. baseCls = me.baseCls,
  7462. body = Ext.getBody();
  7463. me.right = body.createChild({
  7464. cls: baseCls
  7465. });
  7466. me.left = body.createChild({
  7467. cls: baseCls
  7468. });
  7469. me.top = body.createChild({
  7470. cls: baseCls
  7471. });
  7472. me.bottom = body.createChild({
  7473. cls: baseCls
  7474. });
  7475. me.all = Ext.create('Ext.CompositeElement', [
  7476. me.right,
  7477. me.left,
  7478. me.top,
  7479. me.bottom
  7480. ]);
  7481. },
  7482. /**
  7483. * Show the spotlight
  7484. */
  7485. show: function(el, callback, scope) {
  7486. var me = this;
  7487. //get the target element
  7488. me.el = Ext.get(el);
  7489. //create the elements if they don't already exist
  7490. if (!me.right) {
  7491. me.createElements();
  7492. }
  7493. if (!me.active) {
  7494. //if the spotlight is not active, show it
  7495. me.all.setDisplayed('');
  7496. me.active = true;
  7497. Ext.on('resize', me.syncSize, me);
  7498. me.applyBounds(me.animate, false);
  7499. } else {
  7500. //if the spotlight is currently active, just move it
  7501. me.applyBounds(false, false);
  7502. }
  7503. },
  7504. /**
  7505. * Hide the spotlight
  7506. */
  7507. hide: function(callback, scope) {
  7508. var me = this;
  7509. Ext.un('resize', me.syncSize, me);
  7510. me.applyBounds(me.animate, true);
  7511. },
  7512. /**
  7513. * Resizes the spotlight with the window size.
  7514. */
  7515. syncSize: function() {
  7516. this.applyBounds(false, false);
  7517. },
  7518. /**
  7519. * Resizes the spotlight depending on the arguments
  7520. * @param {Boolean} animate True to animate the changing of the bounds
  7521. * @param {Boolean} reverse True to reverse the animation
  7522. */
  7523. applyBounds: function(animate, reverse) {
  7524. var me = this,
  7525. box = me.el.getBox(),
  7526. //get the current view width and height
  7527. viewWidth = Ext.Element.getViewportWidth(),
  7528. viewHeight = Ext.Element.getViewportHeight(),
  7529. i = 0,
  7530. config = false,
  7531. from, to, clone;
  7532. //where the element should start (if animation)
  7533. from = {
  7534. right: {
  7535. x: box.right,
  7536. y: viewHeight,
  7537. width: (viewWidth - box.right),
  7538. height: 0
  7539. },
  7540. left: {
  7541. x: 0,
  7542. y: 0,
  7543. width: box.x,
  7544. height: 0
  7545. },
  7546. top: {
  7547. x: viewWidth,
  7548. y: 0,
  7549. width: 0,
  7550. height: box.y
  7551. },
  7552. bottom: {
  7553. x: 0,
  7554. y: (box.y + box.height),
  7555. width: 0,
  7556. height: (viewHeight - (box.y + box.height)) + 'px'
  7557. }
  7558. };
  7559. //where the element needs to finish
  7560. to = {
  7561. right: {
  7562. x: box.right,
  7563. y: box.y,
  7564. width: (viewWidth - box.right) + 'px',
  7565. height: (viewHeight - box.y) + 'px'
  7566. },
  7567. left: {
  7568. x: 0,
  7569. y: 0,
  7570. width: box.x + 'px',
  7571. height: (box.y + box.height) + 'px'
  7572. },
  7573. top: {
  7574. x: box.x,
  7575. y: 0,
  7576. width: (viewWidth - box.x) + 'px',
  7577. height: box.y + 'px'
  7578. },
  7579. bottom: {
  7580. x: 0,
  7581. y: (box.y + box.height),
  7582. width: (box.x + box.width) + 'px',
  7583. height: (viewHeight - (box.y + box.height)) + 'px'
  7584. }
  7585. };
  7586. //reverse the objects
  7587. if (reverse) {
  7588. clone = Ext.clone(from);
  7589. from = to;
  7590. to = clone;
  7591. }
  7592. if (animate) {
  7593. Ext.Array.forEach([
  7594. 'right',
  7595. 'left',
  7596. 'top',
  7597. 'bottom'
  7598. ], function(side) {
  7599. me[side].setBox(from[side]);
  7600. me[side].animate({
  7601. duration: me.duration,
  7602. easing: me.easing,
  7603. to: to[side]
  7604. });
  7605. }, this);
  7606. } else {
  7607. Ext.Array.forEach([
  7608. 'right',
  7609. 'left',
  7610. 'top',
  7611. 'bottom'
  7612. ], function(side) {
  7613. me[side].setBox(Ext.apply(from[side], to[side]));
  7614. me[side].repaint();
  7615. }, this);
  7616. }
  7617. },
  7618. /**
  7619. * Removes all the elements for the spotlight
  7620. */
  7621. destroy: function() {
  7622. var me = this;
  7623. Ext.destroy(me.right, me.left, me.top, me.bottom);
  7624. delete me.el;
  7625. delete me.all;
  7626. me.callParent();
  7627. }
  7628. });
  7629. /**
  7630. * Plugin for adding a close context menu to tabs. Note that the menu respects
  7631. * the closable configuration on the tab. As such, commands like remove others
  7632. * and remove all will not remove items that are not closable.
  7633. */
  7634. Ext.define('Ext.ux.TabCloseMenu', {
  7635. extend: 'Ext.plugin.Abstract',
  7636. alias: 'plugin.tabclosemenu',
  7637. mixins: {
  7638. observable: 'Ext.util.Observable'
  7639. },
  7640. /**
  7641. * @cfg {String} closeTabText
  7642. * The text for closing the current tab.
  7643. */
  7644. closeTabText: 'Close Tab',
  7645. /**
  7646. * @cfg {Boolean} showCloseOthers
  7647. * Indicates whether to show the 'Close Others' option.
  7648. */
  7649. showCloseOthers: true,
  7650. /**
  7651. * @cfg {String} closeOthersTabsText
  7652. * The text for closing all tabs except the current one.
  7653. */
  7654. closeOthersTabsText: 'Close Other Tabs',
  7655. /**
  7656. * @cfg {Boolean} showCloseAll
  7657. * Indicates whether to show the 'Close All' option.
  7658. */
  7659. showCloseAll: true,
  7660. /**
  7661. * @cfg {String} closeAllTabsText
  7662. * The text for closing all tabs.
  7663. */
  7664. closeAllTabsText: 'Close All Tabs',
  7665. /**
  7666. * @cfg {Array} extraItemsHead
  7667. * An array of additional context menu items to add to the front of the context menu.
  7668. */
  7669. extraItemsHead: null,
  7670. /**
  7671. * @cfg {Array} extraItemsTail
  7672. * An array of additional context menu items to add to the end of the context menu.
  7673. */
  7674. extraItemsTail: null,
  7675. //public
  7676. constructor: function(config) {
  7677. this.callParent([
  7678. config
  7679. ]);
  7680. this.mixins.observable.constructor.call(this, config);
  7681. },
  7682. init: function(tabpanel) {
  7683. this.tabPanel = tabpanel;
  7684. this.tabBar = tabpanel.down("tabbar");
  7685. this.mon(this.tabPanel, {
  7686. scope: this,
  7687. afterlayout: this.onAfterLayout,
  7688. single: true
  7689. });
  7690. },
  7691. onAfterLayout: function() {
  7692. this.mon(this.tabBar.el, {
  7693. scope: this,
  7694. contextmenu: this.onContextMenu,
  7695. delegate: '.x-tab'
  7696. });
  7697. },
  7698. destroy: function() {
  7699. Ext.destroy(this.menu);
  7700. this.callParent();
  7701. },
  7702. /**
  7703. * @private
  7704. */
  7705. onContextMenu: function(event, target) {
  7706. var me = this,
  7707. menu = me.createMenu(),
  7708. disableAll = true,
  7709. disableOthers = true,
  7710. tab = me.tabBar.getChildByElement(target),
  7711. index = me.tabBar.items.indexOf(tab);
  7712. me.item = me.tabPanel.getComponent(index);
  7713. menu.child('#close').setDisabled(!me.item.closable);
  7714. if (me.showCloseAll || me.showCloseOthers) {
  7715. me.tabPanel.items.each(function(item) {
  7716. if (item.closable) {
  7717. disableAll = false;
  7718. if (item !== me.item) {
  7719. disableOthers = false;
  7720. return false;
  7721. }
  7722. }
  7723. return true;
  7724. });
  7725. if (me.showCloseAll) {
  7726. menu.child('#closeAll').setDisabled(disableAll);
  7727. }
  7728. if (me.showCloseOthers) {
  7729. menu.child('#closeOthers').setDisabled(disableOthers);
  7730. }
  7731. }
  7732. event.preventDefault();
  7733. me.fireEvent('beforemenu', menu, me.item, me);
  7734. menu.showAt(event.getXY());
  7735. },
  7736. createMenu: function() {
  7737. var me = this;
  7738. if (!me.menu) {
  7739. var items = [
  7740. {
  7741. itemId: 'close',
  7742. text: me.closeTabText,
  7743. scope: me,
  7744. handler: me.onClose
  7745. }
  7746. ];
  7747. if (me.showCloseAll || me.showCloseOthers) {
  7748. items.push('-');
  7749. }
  7750. if (me.showCloseOthers) {
  7751. items.push({
  7752. itemId: 'closeOthers',
  7753. text: me.closeOthersTabsText,
  7754. scope: me,
  7755. handler: me.onCloseOthers
  7756. });
  7757. }
  7758. if (me.showCloseAll) {
  7759. items.push({
  7760. itemId: 'closeAll',
  7761. text: me.closeAllTabsText,
  7762. scope: me,
  7763. handler: me.onCloseAll
  7764. });
  7765. }
  7766. if (me.extraItemsHead) {
  7767. items = me.extraItemsHead.concat(items);
  7768. }
  7769. if (me.extraItemsTail) {
  7770. items = items.concat(me.extraItemsTail);
  7771. }
  7772. me.menu = Ext.create('Ext.menu.Menu', {
  7773. items: items,
  7774. listeners: {
  7775. hide: me.onHideMenu,
  7776. scope: me
  7777. }
  7778. });
  7779. }
  7780. return me.menu;
  7781. },
  7782. onHideMenu: function() {
  7783. var me = this;
  7784. me.fireEvent('aftermenu', me.menu, me);
  7785. },
  7786. onClose: function() {
  7787. this.tabPanel.remove(this.item);
  7788. },
  7789. onCloseOthers: function() {
  7790. this.doClose(true);
  7791. },
  7792. onCloseAll: function() {
  7793. this.doClose(false);
  7794. },
  7795. doClose: function(excludeActive) {
  7796. var items = [];
  7797. this.tabPanel.items.each(function(item) {
  7798. if (item.closable) {
  7799. if (!excludeActive || item !== this.item) {
  7800. items.push(item);
  7801. }
  7802. }
  7803. }, this);
  7804. Ext.suspendLayouts();
  7805. Ext.Array.forEach(items, function(item) {
  7806. this.tabPanel.remove(item);
  7807. }, this);
  7808. Ext.resumeLayouts(true);
  7809. }
  7810. });
  7811. /**
  7812. * This plugin allow you to reorder tabs of a TabPanel.
  7813. */
  7814. Ext.define('Ext.ux.TabReorderer', {
  7815. extend: 'Ext.ux.BoxReorderer',
  7816. alias: 'plugin.tabreorderer',
  7817. itemSelector: '.' + Ext.baseCSSPrefix + 'tab',
  7818. init: function(tabPanel) {
  7819. var me = this;
  7820. me.callParent([
  7821. tabPanel.getTabBar()
  7822. ]);
  7823. // Ensure reorderable property is copied into dynamically added tabs
  7824. tabPanel.onAdd = Ext.Function.createSequence(tabPanel.onAdd, me.onAdd);
  7825. },
  7826. onBoxReady: function() {
  7827. var tabs, len,
  7828. i = 0,
  7829. tab;
  7830. this.callParent(arguments);
  7831. // Copy reorderable property from card into tab
  7832. for (tabs = this.container.items.items , len = tabs.length; i < len; i++) {
  7833. tab = tabs[i];
  7834. if (tab.card) {
  7835. tab.reorderable = tab.card.reorderable;
  7836. }
  7837. }
  7838. },
  7839. onAdd: function(card, index) {
  7840. card.tab.reorderable = card.reorderable;
  7841. },
  7842. afterBoxReflow: function() {
  7843. var me = this;
  7844. // Cannot use callParent, this is not called in the scope of this plugin, but that of its Ext.dd.DD object
  7845. Ext.ux.BoxReorderer.prototype.afterBoxReflow.apply(me, arguments);
  7846. // Move the associated card to match the tab order
  7847. if (me.dragCmp) {
  7848. me.container.tabPanel.setActiveTab(me.dragCmp.card);
  7849. me.container.tabPanel.move(me.dragCmp.card, me.curIndex);
  7850. }
  7851. }
  7852. });
  7853. Ext.ns('Ext.ux');
  7854. /**
  7855. * Plugin for adding a tab menu to a TabBar is the Tabs overflow.
  7856. */
  7857. Ext.define('Ext.ux.TabScrollerMenu', {
  7858. alias: 'plugin.tabscrollermenu',
  7859. requires: [
  7860. 'Ext.menu.Menu'
  7861. ],
  7862. /**
  7863. * @cfg {Number} pageSize How many items to allow per submenu.
  7864. */
  7865. pageSize: 10,
  7866. /**
  7867. * @cfg {Number} maxText How long should the title of each {@link Ext.menu.Item} be.
  7868. */
  7869. maxText: 15,
  7870. /**
  7871. * @cfg {String} menuPrefixText Text to prefix the submenus.
  7872. */
  7873. menuPrefixText: 'Items',
  7874. /**
  7875. * Creates new TabScrollerMenu.
  7876. * @param {Object} config Configuration options
  7877. */
  7878. constructor: function(config) {
  7879. Ext.apply(this, config);
  7880. },
  7881. /**
  7882. * @private
  7883. */
  7884. init: function(tabPanel) {
  7885. var me = this;
  7886. me.tabPanel = tabPanel;
  7887. tabPanel.on({
  7888. render: function() {
  7889. me.tabBar = tabPanel.tabBar;
  7890. me.layout = me.tabBar.layout;
  7891. me.layout.overflowHandler.handleOverflow = me.showButton.bind(me);
  7892. me.layout.overflowHandler.clearOverflow = Ext.Function.createSequence(me.layout.overflowHandler.clearOverflow, me.hideButton, me);
  7893. },
  7894. destroy: me.destroy,
  7895. scope: me,
  7896. single: true
  7897. });
  7898. },
  7899. showButton: function() {
  7900. var me = this,
  7901. result = Ext.getClass(me.layout.overflowHandler).prototype.handleOverflow.apply(me.layout.overflowHandler, arguments),
  7902. button = me.menuButton;
  7903. if (me.tabPanel.items.getCount() > 1) {
  7904. if (!button) {
  7905. button = me.menuButton = me.tabBar.body.createChild({
  7906. cls: Ext.baseCSSPrefix + 'tab-tabmenu-right'
  7907. }, me.tabBar.body.child('.' + Ext.baseCSSPrefix + 'box-scroller-right'));
  7908. button.addClsOnOver(Ext.baseCSSPrefix + 'tab-tabmenu-over');
  7909. button.on('click', me.showTabsMenu, me);
  7910. }
  7911. button.setVisibilityMode(Ext.dom.Element.DISPLAY);
  7912. button.show();
  7913. result.reservedSpace += button.getWidth();
  7914. } else {
  7915. me.hideButton();
  7916. }
  7917. return result;
  7918. },
  7919. hideButton: function() {
  7920. var me = this;
  7921. if (me.menuButton) {
  7922. me.menuButton.hide();
  7923. }
  7924. },
  7925. /**
  7926. * Returns an the current page size (this.pageSize);
  7927. * @return {Number} this.pageSize The current page size.
  7928. */
  7929. getPageSize: function() {
  7930. return this.pageSize;
  7931. },
  7932. /**
  7933. * Sets the number of menu items per submenu "page size".
  7934. * @param {Number} pageSize The page size
  7935. */
  7936. setPageSize: function(pageSize) {
  7937. this.pageSize = pageSize;
  7938. },
  7939. /**
  7940. * Returns the current maxText length;
  7941. * @return {Number} this.maxText The current max text length.
  7942. */
  7943. getMaxText: function() {
  7944. return this.maxText;
  7945. },
  7946. /**
  7947. * Sets the maximum text size for each menu item.
  7948. * @param {Number} t The max text per each menu item.
  7949. */
  7950. setMaxText: function(t) {
  7951. this.maxText = t;
  7952. },
  7953. /**
  7954. * Returns the current menu prefix text String.;
  7955. * @return {String} this.menuPrefixText The current menu prefix text.
  7956. */
  7957. getMenuPrefixText: function() {
  7958. return this.menuPrefixText;
  7959. },
  7960. /**
  7961. * Sets the menu prefix text String.
  7962. * @param {String} t The menu prefix text.
  7963. */
  7964. setMenuPrefixText: function(t) {
  7965. this.menuPrefixText = t;
  7966. },
  7967. showTabsMenu: function(e) {
  7968. var me = this;
  7969. if (me.tabsMenu) {
  7970. me.tabsMenu.removeAll();
  7971. } else {
  7972. me.tabsMenu = new Ext.menu.Menu();
  7973. }
  7974. me.generateTabMenuItems();
  7975. var target = Ext.get(e.getTarget()),
  7976. xy = target.getXY();
  7977. //Y param + 24 pixels
  7978. xy[1] += 24;
  7979. me.tabsMenu.showAt(xy);
  7980. },
  7981. /**
  7982. * @private
  7983. */
  7984. generateTabMenuItems: function() {
  7985. var me = this,
  7986. tabPanel = me.tabPanel,
  7987. curActive = tabPanel.getActiveTab(),
  7988. allItems = tabPanel.items.getRange(),
  7989. pageSize = me.getPageSize(),
  7990. tabsMenu = me.tabsMenu,
  7991. totalItems, numSubMenus, remainder, i, curPage, menuItems, x, item, start, index;
  7992. tabsMenu.suspendLayouts();
  7993. allItems = Ext.Array.filter(allItems, function(item) {
  7994. if (item.id == curActive.id) {
  7995. return false;
  7996. }
  7997. return item.hidden ? !!item.hiddenByLayout : true;
  7998. });
  7999. totalItems = allItems.length;
  8000. numSubMenus = Math.floor(totalItems / pageSize);
  8001. remainder = totalItems % pageSize;
  8002. if (totalItems > pageSize) {
  8003. // Loop through all of the items and create submenus in chunks of 10
  8004. for (i = 0; i < numSubMenus; i++) {
  8005. curPage = (i + 1) * pageSize;
  8006. menuItems = [];
  8007. for (x = 0; x < pageSize; x++) {
  8008. index = x + curPage - pageSize;
  8009. item = allItems[index];
  8010. menuItems.push(me.autoGenMenuItem(item));
  8011. }
  8012. tabsMenu.add({
  8013. text: me.getMenuPrefixText() + ' ' + (curPage - pageSize + 1) + ' - ' + curPage,
  8014. menu: menuItems
  8015. });
  8016. }
  8017. // remaining items
  8018. if (remainder > 0) {
  8019. start = numSubMenus * pageSize;
  8020. menuItems = [];
  8021. for (i = start; i < totalItems; i++) {
  8022. item = allItems[i];
  8023. menuItems.push(me.autoGenMenuItem(item));
  8024. }
  8025. me.tabsMenu.add({
  8026. text: me.menuPrefixText + ' ' + (start + 1) + ' - ' + (start + menuItems.length),
  8027. menu: menuItems
  8028. });
  8029. }
  8030. } else {
  8031. for (i = 0; i < totalItems; ++i) {
  8032. tabsMenu.add(me.autoGenMenuItem(allItems[i]));
  8033. }
  8034. }
  8035. tabsMenu.resumeLayouts(true);
  8036. },
  8037. /**
  8038. * @private
  8039. */
  8040. autoGenMenuItem: function(item) {
  8041. var maxText = this.getMaxText(),
  8042. text = Ext.util.Format.ellipsis(item.title, maxText);
  8043. return {
  8044. text: text,
  8045. handler: this.showTabFromMenu,
  8046. scope: this,
  8047. disabled: item.disabled,
  8048. tabToShow: item,
  8049. iconCls: item.iconCls
  8050. };
  8051. },
  8052. /**
  8053. * @private
  8054. */
  8055. showTabFromMenu: function(menuItem) {
  8056. this.tabPanel.setActiveTab(menuItem.tabToShow);
  8057. },
  8058. destroy: function() {
  8059. Ext.destroy(this.tabsMenu, this.menuButton);
  8060. this.callParent();
  8061. }
  8062. });
  8063. /**
  8064. * Plugin which allows items to be dropped onto a toolbar and be turned into new Toolbar items.
  8065. * To use the plugin, you just need to provide a createItem implementation that takes the drop
  8066. * data as an argument and returns an object that can be placed onto the toolbar. Example:
  8067. * <pre>
  8068. * Ext.create('Ext.ux.ToolbarDroppable', {
  8069. * createItem: function(data) {
  8070. * return Ext.create('Ext.Button', {text: data.text});
  8071. * }
  8072. * });
  8073. * </pre>
  8074. * The afterLayout function can also be overridden, and is called after a new item has been
  8075. * created and inserted into the Toolbar. Use this for any logic that needs to be run after
  8076. * the item has been created.
  8077. */
  8078. Ext.define('Ext.ux.ToolbarDroppable', {
  8079. /**
  8080. * Creates new ToolbarDroppable.
  8081. * @param {Object} config Config options.
  8082. */
  8083. constructor: function(config) {
  8084. Ext.apply(this, config);
  8085. },
  8086. /**
  8087. * Initializes the plugin and saves a reference to the toolbar
  8088. * @param {Ext.toolbar.Toolbar} toolbar The toolbar instance
  8089. */
  8090. init: function(toolbar) {
  8091. /**
  8092. * @property toolbar
  8093. * @type Ext.toolbar.Toolbar
  8094. * The toolbar instance that this plugin is tied to
  8095. */
  8096. this.toolbar = toolbar;
  8097. this.toolbar.on({
  8098. scope: this,
  8099. render: this.createDropTarget
  8100. });
  8101. },
  8102. /**
  8103. * Creates a drop target on the toolbar
  8104. */
  8105. createDropTarget: function() {
  8106. /**
  8107. * @property dropTarget
  8108. * @type Ext.dd.DropTarget
  8109. * The drop target attached to the toolbar instance
  8110. */
  8111. this.dropTarget = Ext.create('Ext.dd.DropTarget', this.toolbar.getEl(), {
  8112. notifyOver: this.notifyOver.bind(this),
  8113. notifyDrop: this.notifyDrop.bind(this)
  8114. });
  8115. },
  8116. /**
  8117. * Adds the given DD Group to the drop target
  8118. * @param {String} ddGroup The DD Group
  8119. */
  8120. addDDGroup: function(ddGroup) {
  8121. this.dropTarget.addToGroup(ddGroup);
  8122. },
  8123. /**
  8124. * Calculates the location on the toolbar to create the new sorter button based on the XY of the
  8125. * drag event
  8126. * @param {Ext.event.Event} e The event object
  8127. * @return {Number} The index at which to insert the new button
  8128. */
  8129. calculateEntryIndex: function(e) {
  8130. var entryIndex = 0,
  8131. toolbar = this.toolbar,
  8132. items = toolbar.items.items,
  8133. count = items.length,
  8134. xHover = e.getXY()[0],
  8135. index = 0,
  8136. el, xTotal, width, midpoint;
  8137. for (; index < count; index++) {
  8138. el = items[index].getEl();
  8139. xTotal = el.getXY()[0];
  8140. width = el.getWidth();
  8141. midpoint = xTotal + width / 2;
  8142. if (xHover < midpoint) {
  8143. entryIndex = index;
  8144. break;
  8145. } else {
  8146. entryIndex = index + 1;
  8147. }
  8148. }
  8149. return entryIndex;
  8150. },
  8151. /**
  8152. * Returns true if the drop is allowed on the drop target. This function can be overridden
  8153. * and defaults to simply return true
  8154. * @param {Object} data Arbitrary data from the drag source
  8155. * @return {Boolean} True if the drop is allowed
  8156. */
  8157. canDrop: function(data) {
  8158. return true;
  8159. },
  8160. /**
  8161. * Custom notifyOver method which will be used in the plugin's internal DropTarget
  8162. * @return {String} The CSS class to add
  8163. */
  8164. notifyOver: function(dragSource, event, data) {
  8165. return this.canDrop.apply(this, arguments) ? this.dropTarget.dropAllowed : this.dropTarget.dropNotAllowed;
  8166. },
  8167. /**
  8168. * Called when the drop has been made. Creates the new toolbar item, places it at the correct location
  8169. * and calls the afterLayout callback.
  8170. */
  8171. notifyDrop: function(dragSource, event, data) {
  8172. var canAdd = this.canDrop(dragSource, event, data),
  8173. tbar = this.toolbar;
  8174. if (canAdd) {
  8175. var entryIndex = this.calculateEntryIndex(event);
  8176. tbar.insert(entryIndex, this.createItem(data));
  8177. this.afterLayout();
  8178. }
  8179. return canAdd;
  8180. },
  8181. /**
  8182. * Creates the new toolbar item based on drop data. This method must be implemented by the plugin instance
  8183. * @param {Object} data Arbitrary data from the drop
  8184. * @return {Mixed} An item that can be added to a toolbar
  8185. */
  8186. createItem: function(data) {
  8187. //<debug>
  8188. Ext.raise("The createItem method must be implemented in the ToolbarDroppable plugin");
  8189. },
  8190. //</debug>
  8191. /**
  8192. * @method
  8193. * Called after a new button has been created and added to the toolbar. Add any required cleanup logic here
  8194. */
  8195. afterLayout: Ext.emptyFn
  8196. });
  8197. /**
  8198. * A Picker field that contains a tree panel on its popup, enabling selection of tree nodes.
  8199. */
  8200. Ext.define('Ext.ux.TreePicker', {
  8201. extend: 'Ext.form.field.Picker',
  8202. xtype: 'treepicker',
  8203. uses: [
  8204. 'Ext.tree.Panel'
  8205. ],
  8206. triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',
  8207. config: {
  8208. /**
  8209. * @cfg {Ext.data.TreeStore} store
  8210. * A tree store that the tree picker will be bound to
  8211. */
  8212. store: null,
  8213. /**
  8214. * @cfg {String} displayField
  8215. * The field inside the model that will be used as the node's text.
  8216. * Defaults to the default value of {@link Ext.tree.Panel}'s `displayField` configuration.
  8217. */
  8218. displayField: null,
  8219. /**
  8220. * @cfg {Array} columns
  8221. * An optional array of columns for multi-column trees
  8222. */
  8223. columns: null,
  8224. /**
  8225. * @cfg {Boolean} selectOnTab
  8226. * Whether the Tab key should select the currently highlighted item. Defaults to `true`.
  8227. */
  8228. selectOnTab: true,
  8229. /**
  8230. * @cfg {Number} maxPickerHeight
  8231. * The maximum height of the tree dropdown. Defaults to 300.
  8232. */
  8233. maxPickerHeight: 300,
  8234. /**
  8235. * @cfg {Number} minPickerHeight
  8236. * The minimum height of the tree dropdown. Defaults to 100.
  8237. */
  8238. minPickerHeight: 100
  8239. },
  8240. editable: false,
  8241. /**
  8242. * @event select
  8243. * Fires when a tree node is selected
  8244. * @param {Ext.ux.TreePicker} picker This tree picker
  8245. * @param {Ext.data.Model} record The selected record
  8246. */
  8247. initComponent: function() {
  8248. var me = this;
  8249. me.callParent(arguments);
  8250. me.mon(me.store, {
  8251. scope: me,
  8252. load: me.onLoad,
  8253. update: me.onUpdate
  8254. });
  8255. },
  8256. /**
  8257. * Creates and returns the tree panel to be used as this field's picker.
  8258. */
  8259. createPicker: function() {
  8260. var me = this,
  8261. picker = new Ext.tree.Panel({
  8262. baseCls: Ext.baseCSSPrefix + 'boundlist',
  8263. shrinkWrapDock: 2,
  8264. store: me.store,
  8265. floating: true,
  8266. displayField: me.displayField,
  8267. columns: me.columns,
  8268. minHeight: me.minPickerHeight,
  8269. maxHeight: me.maxPickerHeight,
  8270. manageHeight: false,
  8271. shadow: false,
  8272. listeners: {
  8273. scope: me,
  8274. itemclick: me.onItemClick,
  8275. itemkeydown: me.onPickerKeyDown
  8276. }
  8277. }),
  8278. view = picker.getView();
  8279. if (Ext.isIE9 && Ext.isStrict) {
  8280. // In IE9 strict mode, the tree view grows by the height of the horizontal scroll bar when the items are highlighted or unhighlighted.
  8281. // Also when items are collapsed or expanded the height of the view is off. Forcing a repaint fixes the problem.
  8282. view.on({
  8283. scope: me,
  8284. highlightitem: me.repaintPickerView,
  8285. unhighlightitem: me.repaintPickerView,
  8286. afteritemexpand: me.repaintPickerView,
  8287. afteritemcollapse: me.repaintPickerView
  8288. });
  8289. }
  8290. return picker;
  8291. },
  8292. /**
  8293. * repaints the tree view
  8294. */
  8295. repaintPickerView: function() {
  8296. var style = this.picker.getView().getEl().dom.style;
  8297. // can't use Element.repaint because it contains a setTimeout, which results in a flicker effect
  8298. style.display = style.display;
  8299. },
  8300. /**
  8301. * Handles a click even on a tree node
  8302. * @private
  8303. * @param {Ext.tree.View} view
  8304. * @param {Ext.data.Model} record
  8305. * @param {HTMLElement} node
  8306. * @param {Number} rowIndex
  8307. * @param {Ext.event.Event} e
  8308. */
  8309. onItemClick: function(view, record, node, rowIndex, e) {
  8310. this.selectItem(record);
  8311. },
  8312. /**
  8313. * Handles a keypress event on the picker element
  8314. * @private
  8315. * @param {Ext.tree.View} treeView
  8316. * @param {Ext.data.Model} record
  8317. * @param {HTMLElement} item
  8318. * @param {Number} index
  8319. * @param {Ext.event.Event} e
  8320. */
  8321. onPickerKeyDown: function(treeView, record, item, index, e) {
  8322. var key = e.getKey();
  8323. if (key === e.ENTER || (key === e.TAB && this.selectOnTab)) {
  8324. this.selectItem(record);
  8325. }
  8326. },
  8327. /**
  8328. * Changes the selection to a given record and closes the picker
  8329. * @private
  8330. * @param {Ext.data.Model} record
  8331. */
  8332. selectItem: function(record) {
  8333. var me = this;
  8334. me.setValue(record.getId());
  8335. me.fireEvent('select', me, record);
  8336. me.collapse();
  8337. },
  8338. /**
  8339. * Runs when the picker is expanded. Selects the appropriate tree node based on the value of the input element,
  8340. * and focuses the picker so that keyboard navigation will work.
  8341. * @private
  8342. */
  8343. onExpand: function() {
  8344. var picker = this.picker,
  8345. store = picker.store,
  8346. value = this.value,
  8347. node;
  8348. if (value) {
  8349. node = store.getNodeById(value);
  8350. }
  8351. if (!node) {
  8352. node = store.getRoot();
  8353. }
  8354. picker.ensureVisible(node, {
  8355. select: true,
  8356. focus: true
  8357. });
  8358. },
  8359. /**
  8360. * Sets the specified value into the field
  8361. * @param {Mixed} value
  8362. * @return {Ext.ux.TreePicker} this
  8363. */
  8364. setValue: function(value) {
  8365. var me = this,
  8366. record;
  8367. me.value = value;
  8368. if (me.store.loading) {
  8369. // Called while the Store is loading. Ensure it is processed by the onLoad method.
  8370. return me;
  8371. }
  8372. // try to find a record in the store that matches the value
  8373. record = value ? me.store.getNodeById(value) : me.store.getRoot();
  8374. if (value === undefined) {
  8375. record = me.store.getRoot();
  8376. me.value = record.getId();
  8377. } else {
  8378. record = me.store.getNodeById(value);
  8379. }
  8380. // set the raw value to the record's display field if a record was found
  8381. me.setRawValue(record ? record.get(me.displayField) : '');
  8382. return me;
  8383. },
  8384. getSubmitValue: function() {
  8385. return this.value;
  8386. },
  8387. /**
  8388. * Returns the current data value of the field (the idProperty of the record)
  8389. * @return {Number}
  8390. */
  8391. getValue: function() {
  8392. return this.value;
  8393. },
  8394. /**
  8395. * Handles the store's load event.
  8396. * @private
  8397. */
  8398. onLoad: function() {
  8399. var value = this.value;
  8400. if (value) {
  8401. this.setValue(value);
  8402. }
  8403. },
  8404. onUpdate: function(store, rec, type, modifiedFieldNames) {
  8405. var display = this.displayField;
  8406. if (type === 'edit' && modifiedFieldNames && Ext.Array.contains(modifiedFieldNames, display) && this.value === rec.getId()) {
  8407. this.setRawValue(rec.get(display));
  8408. }
  8409. }
  8410. });
  8411. /**
  8412. * @private
  8413. */
  8414. Ext.define('Ext.ux.colorpick.Selection', {
  8415. mixinId: 'colorselection',
  8416. config: {
  8417. /**
  8418. * @cfg {"hex6"/"hex8"/"#hex6"/"#hex8"/"rgb"/"rgba"/"HEX6"/"HEX8"/"#HEX6"/"#HEX8"/"RGB"/"RGBA"} [format=hex6]
  8419. * The color format to for the `value` config. The `value` can be set using any
  8420. * supported format or named color, but the stored value will always be in this
  8421. * format.
  8422. *
  8423. * Supported formats are:
  8424. *
  8425. * - hex6 - For example "ffaa00" (Note: does not preserve transparency).
  8426. * - hex8 - For example "ffaa00ff" - the last 2 digits represent transparency
  8427. * - #hex6 - For example "#ffaa00" (same as "hex6" but with a leading "#").
  8428. * - #hex8 - For example "#ffaa00ff" (same as "hex8" but with a leading "#").
  8429. * - rgb - For example "rgb(255,255,0)" (Note: does not preserve transparency).
  8430. * - rgba - For example "rgba(255,255,0,.25)"
  8431. * - HEX6 - Same as "hex6" but upper case.
  8432. * - HEX8 - Same as "hex8" but upper case.
  8433. * - #HEX6 - Same as "#hex6" but upper case.
  8434. * - #HEX8 - Same as "#hex8" but upper case.
  8435. * - RGB - Same as "rgb" but upper case.
  8436. * - RGBA - Same as "rgba" but upper case.
  8437. */
  8438. format: 'hex6',
  8439. /**
  8440. * @cfg {String} [value=FF0000]
  8441. * The initial color to highlight; see {@link #format} for supported formats.
  8442. */
  8443. value: 'FF0000',
  8444. /**
  8445. * @cfg {Object} color
  8446. * This config property is used internally by the UI to maintain the full color.
  8447. * Changes to this config are automatically reflected in `value` and vise-versa.
  8448. * Setting `value` can, however, cause the alpha to be dropped if the new value
  8449. * does not contain an alpha component.
  8450. * @private
  8451. */
  8452. color: null,
  8453. previousColor: null,
  8454. /**
  8455. * @cfg {String} [alphaDecimalFormat=#.##]
  8456. * The format used by {@link Ext.util.Format#number} to format the alpha channel's
  8457. * value.
  8458. * @since 7.0.0
  8459. */
  8460. alphaDecimalFormat: '#.##'
  8461. },
  8462. applyColor: function(color) {
  8463. var c = color;
  8464. if (Ext.isString(c)) {
  8465. c = Ext.ux.colorpick.ColorUtils.parseColor(color, this.getAlphaDecimalFormat());
  8466. }
  8467. return c;
  8468. },
  8469. applyFormat: function(format) {
  8470. var formats = Ext.ux.colorpick.ColorUtils.formats;
  8471. if (!formats.hasOwnProperty(format)) {
  8472. //<debug>
  8473. Ext.raise('The specified format "' + format + '" is invalid.');
  8474. //</debug>
  8475. return;
  8476. }
  8477. return format;
  8478. },
  8479. applyValue: function(color) {
  8480. // Transform whatever incoming color we get to the proper format
  8481. var c = Ext.ux.colorpick.ColorUtils.parseColor(color || '#000000', this.getAlphaDecimalFormat());
  8482. return this.formatColor(c);
  8483. },
  8484. formatColor: function(color) {
  8485. return Ext.ux.colorpick.ColorUtils.formats[this.getFormat()](color);
  8486. },
  8487. updateColor: function(color) {
  8488. var me = this;
  8489. // If the "color" is changed (via internal changes in the UI), update "value" as
  8490. // well. Since these are always tracking each other, we guard against the case
  8491. // where we are being updated *because* "value" is being set.
  8492. if (!me.syncing) {
  8493. me.syncing = true;
  8494. me.setValue(me.formatColor(color));
  8495. me.syncing = false;
  8496. }
  8497. },
  8498. updateValue: function(value, oldValue) {
  8499. var me = this;
  8500. // If the "value" is changed, update "color" as well. Since these are always
  8501. // tracking each other, we guard against the case where we are being updated
  8502. // *because* "color" is being set.
  8503. if (!me.syncing) {
  8504. me.syncing = true;
  8505. me.setColor(value);
  8506. me.syncing = false;
  8507. }
  8508. this.fireEvent('change', me, value, oldValue);
  8509. }
  8510. });
  8511. /**
  8512. * @private
  8513. */
  8514. Ext.define('Ext.ux.colorpick.ColorUtils', function(ColorUtils) {
  8515. var oldIE = Ext.isIE && Ext.ieVersion < 10;
  8516. return {
  8517. singleton: true,
  8518. constructor: function() {
  8519. ColorUtils = this;
  8520. },
  8521. backgroundTpl: oldIE ? 'filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, ' + 'startColorstr=\'#{alpha}{hex}\', endColorstr=\'#{alpha}{hex}\');' : 'background: {rgba};',
  8522. setBackground: oldIE ? function(el, color) {
  8523. if (el) {
  8524. var tpl = Ext.XTemplate.getTpl(ColorUtils, 'backgroundTpl'),
  8525. data = {
  8526. hex: ColorUtils.rgb2hex(color.r, color.g, color.b),
  8527. alpha: Math.floor(color.a * 255).toString(16)
  8528. },
  8529. bgStyle = tpl.apply(data);
  8530. el.applyStyles(bgStyle);
  8531. }
  8532. } : function(el, color) {
  8533. if (el) {
  8534. var tpl = Ext.XTemplate.getTpl(ColorUtils, 'backgroundTpl'),
  8535. data = {
  8536. rgba: ColorUtils.getRGBAString(color)
  8537. },
  8538. bgStyle = tpl.apply(data);
  8539. el.applyStyles(bgStyle);
  8540. }
  8541. },
  8542. // parse and format functions under objects that match supported format config
  8543. // values of the color picker; parse() methods receive the supplied color value
  8544. // as a string (i.e "FFAAAA") and return an object form, just like the one
  8545. // ColorPickerModel vm "selectedColor" uses. That same object form is used as a
  8546. // parameter to the format() methods, where the appropriate string form is expected
  8547. // for the return result
  8548. formats: {
  8549. // "RGB(100,100,100)"
  8550. RGB: function(colorO) {
  8551. return ColorUtils.getRGBString(colorO).toUpperCase();
  8552. },
  8553. // "RGBA(100,100,100,0.5)"
  8554. RGBA: function(colorO) {
  8555. return ColorUtils.getRGBAString(colorO).toUpperCase();
  8556. },
  8557. // "FFAA00"
  8558. HEX6: function(colorO) {
  8559. return ColorUtils.rgb2hex(colorO.r, colorO.g, colorO.b);
  8560. },
  8561. // "FFAA00FF" (last 2 are opacity)
  8562. HEX8: function(colorO) {
  8563. var hex = ColorUtils.rgb2hex(colorO.r, colorO.g, colorO.b),
  8564. opacityHex = Math.round(colorO.a * 255).toString(16);
  8565. if (opacityHex.length < 2) {
  8566. hex += '0';
  8567. }
  8568. hex += opacityHex.toUpperCase();
  8569. return hex;
  8570. }
  8571. },
  8572. hexRe: /^#?(([0-9a-f]{8})|((?:[0-9a-f]{3}){1,2}))$/i,
  8573. rgbaAltRe: /^rgba\(\s*([\w#\d]+)\s*,\s*([\d\.]+)\s*\)$/i,
  8574. rgbaRe: /^rgba\(\s*([\d\.]+)\s*,\s*([\d\.]+)\s*,\s*([\d\.]+)\s*,\s*([\d\.]+)\s*\)$/i,
  8575. rgbRe: /^rgb\(\s*([\d\.]+)\s*,\s*([\d\.]+)\s*,\s*([\d\.]+)\s*\)$/i,
  8576. /**
  8577. * Turn a string to a color object. Supports these formats:
  8578. *
  8579. * - "#ABC" (HEX short)
  8580. * - "#ABCDEF" (HEX)
  8581. * - "#ABCDEFDD" (HEX with opacity)
  8582. * - "red" (named colors - see
  8583. * [Web Colors](http://en.wikipedia.org/wiki/Web_colors) for a full list)
  8584. * - "rgba(r,g,b,a)" i.e "rgba(255,0,0,1)" (a == alpha == 0-1)
  8585. * - "rgba(red, 0.4)"
  8586. * - "rgba(#ABC, 0.9)"
  8587. * - "rgba(#ABCDEF, 0.8)"
  8588. *
  8589. * @param {String} color The color string to parse.
  8590. * @param {String} alphaFormat The format of decimal places for the Alpha channel.
  8591. * @return {Object} Object with various color properties.
  8592. * @return {Number} return.r The red component (0-255).
  8593. * @return {Number} return.g The green component (0-255).
  8594. * @return {Number} return.b The blue component (0-255).
  8595. * @return {Number} return.a The red component (0-1).
  8596. * @return {Number} return.h The hue component (0-1).
  8597. * @return {Number} return.s The saturation component (0-1).
  8598. * @return {Number} return.v The value component (0-1).
  8599. */
  8600. parseColor: function(color, alphaFormat) {
  8601. if (!color) {
  8602. return null;
  8603. }
  8604. var me = this,
  8605. rgb = me.colorMap[color],
  8606. match, ret, hsv;
  8607. if (rgb) {
  8608. ret = {
  8609. r: rgb[0],
  8610. g: rgb[1],
  8611. b: rgb[2],
  8612. a: 1
  8613. };
  8614. } else if (color === 'transparent') {
  8615. ret = {
  8616. r: 0,
  8617. g: 0,
  8618. b: 0,
  8619. a: 0
  8620. };
  8621. } else {
  8622. match = me.hexRe.exec(color);
  8623. if (match) {
  8624. match = match[1];
  8625. // the captured hex
  8626. switch (match.length) {
  8627. default:
  8628. return null;
  8629. case 3:
  8630. ret = {
  8631. //double the number (e.g. 6 - > 66, a -> aa) and convert to decimal
  8632. r: parseInt(match[0] + match[0], 16),
  8633. g: parseInt(match[1] + match[1], 16),
  8634. b: parseInt(match[2] + match[2], 16),
  8635. a: 1
  8636. };
  8637. break;
  8638. case 6:
  8639. case 8:
  8640. ret = {
  8641. r: parseInt(match.substr(0, 2), 16),
  8642. g: parseInt(match.substr(2, 2), 16),
  8643. b: parseInt(match.substr(4, 2), 16),
  8644. a: parseInt(match.substr(6, 2) || 'ff', 16) / 255
  8645. };
  8646. break;
  8647. }
  8648. } else {
  8649. match = me.rgbaRe.exec(color);
  8650. if (match) {
  8651. // proper css => rgba(r,g,b,a)
  8652. ret = {
  8653. r: parseFloat(match[1]),
  8654. g: parseFloat(match[2]),
  8655. b: parseFloat(match[3]),
  8656. a: parseFloat(match[4])
  8657. };
  8658. } else {
  8659. match = me.rgbaAltRe.exec(color);
  8660. if (match) {
  8661. // scss shorthands =? rgba(red, 0.4), rgba(#222, 0.9), rgba(#444433, 0.8)
  8662. ret = me.parseColor(match[1]);
  8663. // we have HSV filled in, so poke on "a" and we're done
  8664. ret.a = parseFloat(match[2]);
  8665. return ret;
  8666. }
  8667. match = me.rgbRe.exec(color);
  8668. if (match) {
  8669. ret = {
  8670. r: parseFloat(match[1]),
  8671. g: parseFloat(match[2]),
  8672. b: parseFloat(match[3]),
  8673. a: 1
  8674. };
  8675. } else {
  8676. return null;
  8677. }
  8678. }
  8679. }
  8680. }
  8681. // format alpha channel
  8682. if (alphaFormat) {
  8683. ret.a = Ext.util.Format.number(ret.a, alphaFormat);
  8684. }
  8685. hsv = this.rgb2hsv(ret.r, ret.g, ret.b);
  8686. return Ext.apply(ret, hsv);
  8687. },
  8688. isValid: function(color) {
  8689. return ColorUtils.parseColor(color) !== null;
  8690. },
  8691. /**
  8692. *
  8693. * @param rgba
  8694. * @return {String}
  8695. */
  8696. getRGBAString: function(rgba) {
  8697. return "rgba(" + rgba.r + "," + rgba.g + "," + rgba.b + "," + rgba.a + ")";
  8698. },
  8699. /**
  8700. * Returns a rgb css string whith this color (without the alpha channel)
  8701. * @param rgb
  8702. * @return {String}
  8703. */
  8704. getRGBString: function(rgb) {
  8705. return "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")";
  8706. },
  8707. /**
  8708. * Following standard math to convert from hsl to rgb
  8709. * Check out wikipedia page for more information on how this works
  8710. * h => [0,1]
  8711. * s,l => [0,1]
  8712. * @param h
  8713. * @param s
  8714. * @param v
  8715. * @return {Object} An object with "r", "g" and "b" color properties.
  8716. */
  8717. hsv2rgb: function(h, s, v) {
  8718. h = h * 360;
  8719. if (h === 360) {
  8720. h = 0;
  8721. }
  8722. var c = v * s;
  8723. var hprime = h / 60;
  8724. var x = c * (1 - Math.abs(hprime % 2 - 1));
  8725. var rgb = [
  8726. 0,
  8727. 0,
  8728. 0
  8729. ];
  8730. switch (Math.floor(hprime)) {
  8731. case 0:
  8732. rgb = [
  8733. c,
  8734. x,
  8735. 0
  8736. ];
  8737. break;
  8738. case 1:
  8739. rgb = [
  8740. x,
  8741. c,
  8742. 0
  8743. ];
  8744. break;
  8745. case 2:
  8746. rgb = [
  8747. 0,
  8748. c,
  8749. x
  8750. ];
  8751. break;
  8752. case 3:
  8753. rgb = [
  8754. 0,
  8755. x,
  8756. c
  8757. ];
  8758. break;
  8759. case 4:
  8760. rgb = [
  8761. x,
  8762. 0,
  8763. c
  8764. ];
  8765. break;
  8766. case 5:
  8767. rgb = [
  8768. c,
  8769. 0,
  8770. x
  8771. ];
  8772. break;
  8773. default:
  8774. // <debug>
  8775. console.error("unknown color " + h + ' ' + s + " " + v);
  8776. // </debug>
  8777. break;
  8778. }
  8779. var m = v - c;
  8780. rgb[0] += m;
  8781. rgb[1] += m;
  8782. rgb[2] += m;
  8783. rgb[0] = Math.round(rgb[0] * 255);
  8784. rgb[1] = Math.round(rgb[1] * 255);
  8785. rgb[2] = Math.round(rgb[2] * 255);
  8786. return {
  8787. r: rgb[0],
  8788. g: rgb[1],
  8789. b: rgb[2]
  8790. };
  8791. },
  8792. /**
  8793. * http://en.wikipedia.org/wiki/HSL_and_HSV
  8794. * @param {Number} r The red component (0-255).
  8795. * @param {Number} g The green component (0-255).
  8796. * @param {Number} b The blue component (0-255).
  8797. * @return {Object} An object with "h", "s" and "v" color properties.
  8798. */
  8799. rgb2hsv: function(r, g, b) {
  8800. r = r / 255;
  8801. g = g / 255;
  8802. b = b / 255;
  8803. var M = Math.max(r, g, b);
  8804. var m = Math.min(r, g, b);
  8805. var c = M - m;
  8806. var hprime = 0;
  8807. if (c !== 0) {
  8808. if (M === r) {
  8809. hprime = ((g - b) / c) % 6;
  8810. } else if (M === g) {
  8811. hprime = ((b - r) / c) + 2;
  8812. } else if (M === b) {
  8813. hprime = ((r - g) / c) + 4;
  8814. }
  8815. }
  8816. var h = hprime * 60;
  8817. if (h === 360) {
  8818. h = 0;
  8819. }
  8820. var v = M;
  8821. var s = 0;
  8822. if (c !== 0) {
  8823. s = c / v;
  8824. }
  8825. h = h / 360;
  8826. if (h < 0) {
  8827. h = h + 1;
  8828. }
  8829. return {
  8830. h: h,
  8831. s: s,
  8832. v: v
  8833. };
  8834. },
  8835. /**
  8836. *
  8837. * @param r
  8838. * @param g
  8839. * @param b
  8840. * @return {String}
  8841. */
  8842. rgb2hex: function(r, g, b) {
  8843. r = r.toString(16);
  8844. g = g.toString(16);
  8845. b = b.toString(16);
  8846. if (r.length < 2) {
  8847. r = '0' + r;
  8848. }
  8849. if (g.length < 2) {
  8850. g = '0' + g;
  8851. }
  8852. if (b.length < 2) {
  8853. b = '0' + b;
  8854. }
  8855. return (r + g + b).toUpperCase();
  8856. },
  8857. colorMap: {
  8858. aliceblue: [
  8859. 240,
  8860. 248,
  8861. 255
  8862. ],
  8863. antiquewhite: [
  8864. 250,
  8865. 235,
  8866. 215
  8867. ],
  8868. aqua: [
  8869. 0,
  8870. 255,
  8871. 255
  8872. ],
  8873. aquamarine: [
  8874. 127,
  8875. 255,
  8876. 212
  8877. ],
  8878. azure: [
  8879. 240,
  8880. 255,
  8881. 255
  8882. ],
  8883. beige: [
  8884. 245,
  8885. 245,
  8886. 220
  8887. ],
  8888. bisque: [
  8889. 255,
  8890. 228,
  8891. 196
  8892. ],
  8893. black: [
  8894. 0,
  8895. 0,
  8896. 0
  8897. ],
  8898. blanchedalmond: [
  8899. 255,
  8900. 235,
  8901. 205
  8902. ],
  8903. blue: [
  8904. 0,
  8905. 0,
  8906. 255
  8907. ],
  8908. blueviolet: [
  8909. 138,
  8910. 43,
  8911. 226
  8912. ],
  8913. brown: [
  8914. 165,
  8915. 42,
  8916. 42
  8917. ],
  8918. burlywood: [
  8919. 222,
  8920. 184,
  8921. 135
  8922. ],
  8923. cadetblue: [
  8924. 95,
  8925. 158,
  8926. 160
  8927. ],
  8928. chartreuse: [
  8929. 127,
  8930. 255,
  8931. 0
  8932. ],
  8933. chocolate: [
  8934. 210,
  8935. 105,
  8936. 30
  8937. ],
  8938. coral: [
  8939. 255,
  8940. 127,
  8941. 80
  8942. ],
  8943. cornflowerblue: [
  8944. 100,
  8945. 149,
  8946. 237
  8947. ],
  8948. cornsilk: [
  8949. 255,
  8950. 248,
  8951. 220
  8952. ],
  8953. crimson: [
  8954. 220,
  8955. 20,
  8956. 60
  8957. ],
  8958. cyan: [
  8959. 0,
  8960. 255,
  8961. 255
  8962. ],
  8963. darkblue: [
  8964. 0,
  8965. 0,
  8966. 139
  8967. ],
  8968. darkcyan: [
  8969. 0,
  8970. 139,
  8971. 139
  8972. ],
  8973. darkgoldenrod: [
  8974. 184,
  8975. 132,
  8976. 11
  8977. ],
  8978. darkgray: [
  8979. 169,
  8980. 169,
  8981. 169
  8982. ],
  8983. darkgreen: [
  8984. 0,
  8985. 100,
  8986. 0
  8987. ],
  8988. darkgrey: [
  8989. 169,
  8990. 169,
  8991. 169
  8992. ],
  8993. darkkhaki: [
  8994. 189,
  8995. 183,
  8996. 107
  8997. ],
  8998. darkmagenta: [
  8999. 139,
  9000. 0,
  9001. 139
  9002. ],
  9003. darkolivegreen: [
  9004. 85,
  9005. 107,
  9006. 47
  9007. ],
  9008. darkorange: [
  9009. 255,
  9010. 140,
  9011. 0
  9012. ],
  9013. darkorchid: [
  9014. 153,
  9015. 50,
  9016. 204
  9017. ],
  9018. darkred: [
  9019. 139,
  9020. 0,
  9021. 0
  9022. ],
  9023. darksalmon: [
  9024. 233,
  9025. 150,
  9026. 122
  9027. ],
  9028. darkseagreen: [
  9029. 143,
  9030. 188,
  9031. 143
  9032. ],
  9033. darkslateblue: [
  9034. 72,
  9035. 61,
  9036. 139
  9037. ],
  9038. darkslategray: [
  9039. 47,
  9040. 79,
  9041. 79
  9042. ],
  9043. darkslategrey: [
  9044. 47,
  9045. 79,
  9046. 79
  9047. ],
  9048. darkturquoise: [
  9049. 0,
  9050. 206,
  9051. 209
  9052. ],
  9053. darkviolet: [
  9054. 148,
  9055. 0,
  9056. 211
  9057. ],
  9058. deeppink: [
  9059. 255,
  9060. 20,
  9061. 147
  9062. ],
  9063. deepskyblue: [
  9064. 0,
  9065. 191,
  9066. 255
  9067. ],
  9068. dimgray: [
  9069. 105,
  9070. 105,
  9071. 105
  9072. ],
  9073. dimgrey: [
  9074. 105,
  9075. 105,
  9076. 105
  9077. ],
  9078. dodgerblue: [
  9079. 30,
  9080. 144,
  9081. 255
  9082. ],
  9083. firebrick: [
  9084. 178,
  9085. 34,
  9086. 34
  9087. ],
  9088. floralwhite: [
  9089. 255,
  9090. 255,
  9091. 240
  9092. ],
  9093. forestgreen: [
  9094. 34,
  9095. 139,
  9096. 34
  9097. ],
  9098. fuchsia: [
  9099. 255,
  9100. 0,
  9101. 255
  9102. ],
  9103. gainsboro: [
  9104. 220,
  9105. 220,
  9106. 220
  9107. ],
  9108. ghostwhite: [
  9109. 248,
  9110. 248,
  9111. 255
  9112. ],
  9113. gold: [
  9114. 255,
  9115. 215,
  9116. 0
  9117. ],
  9118. goldenrod: [
  9119. 218,
  9120. 165,
  9121. 32
  9122. ],
  9123. gray: [
  9124. 128,
  9125. 128,
  9126. 128
  9127. ],
  9128. green: [
  9129. 0,
  9130. 128,
  9131. 0
  9132. ],
  9133. greenyellow: [
  9134. 173,
  9135. 255,
  9136. 47
  9137. ],
  9138. grey: [
  9139. 128,
  9140. 128,
  9141. 128
  9142. ],
  9143. honeydew: [
  9144. 240,
  9145. 255,
  9146. 240
  9147. ],
  9148. hotpink: [
  9149. 255,
  9150. 105,
  9151. 180
  9152. ],
  9153. indianred: [
  9154. 205,
  9155. 92,
  9156. 92
  9157. ],
  9158. indigo: [
  9159. 75,
  9160. 0,
  9161. 130
  9162. ],
  9163. ivory: [
  9164. 255,
  9165. 255,
  9166. 240
  9167. ],
  9168. khaki: [
  9169. 240,
  9170. 230,
  9171. 140
  9172. ],
  9173. lavender: [
  9174. 230,
  9175. 230,
  9176. 250
  9177. ],
  9178. lavenderblush: [
  9179. 255,
  9180. 240,
  9181. 245
  9182. ],
  9183. lawngreen: [
  9184. 124,
  9185. 252,
  9186. 0
  9187. ],
  9188. lemonchiffon: [
  9189. 255,
  9190. 250,
  9191. 205
  9192. ],
  9193. lightblue: [
  9194. 173,
  9195. 216,
  9196. 230
  9197. ],
  9198. lightcoral: [
  9199. 240,
  9200. 128,
  9201. 128
  9202. ],
  9203. lightcyan: [
  9204. 224,
  9205. 255,
  9206. 255
  9207. ],
  9208. lightgoldenrodyellow: [
  9209. 250,
  9210. 250,
  9211. 210
  9212. ],
  9213. lightgray: [
  9214. 211,
  9215. 211,
  9216. 211
  9217. ],
  9218. lightgreen: [
  9219. 144,
  9220. 238,
  9221. 144
  9222. ],
  9223. lightgrey: [
  9224. 211,
  9225. 211,
  9226. 211
  9227. ],
  9228. lightpink: [
  9229. 255,
  9230. 182,
  9231. 193
  9232. ],
  9233. lightsalmon: [
  9234. 255,
  9235. 160,
  9236. 122
  9237. ],
  9238. lightseagreen: [
  9239. 32,
  9240. 178,
  9241. 170
  9242. ],
  9243. lightskyblue: [
  9244. 135,
  9245. 206,
  9246. 250
  9247. ],
  9248. lightslategray: [
  9249. 119,
  9250. 136,
  9251. 153
  9252. ],
  9253. lightslategrey: [
  9254. 119,
  9255. 136,
  9256. 153
  9257. ],
  9258. lightsteelblue: [
  9259. 176,
  9260. 196,
  9261. 222
  9262. ],
  9263. lightyellow: [
  9264. 255,
  9265. 255,
  9266. 224
  9267. ],
  9268. lime: [
  9269. 0,
  9270. 255,
  9271. 0
  9272. ],
  9273. limegreen: [
  9274. 50,
  9275. 205,
  9276. 50
  9277. ],
  9278. linen: [
  9279. 250,
  9280. 240,
  9281. 230
  9282. ],
  9283. magenta: [
  9284. 255,
  9285. 0,
  9286. 255
  9287. ],
  9288. maroon: [
  9289. 128,
  9290. 0,
  9291. 0
  9292. ],
  9293. mediumaquamarine: [
  9294. 102,
  9295. 205,
  9296. 170
  9297. ],
  9298. mediumblue: [
  9299. 0,
  9300. 0,
  9301. 205
  9302. ],
  9303. mediumorchid: [
  9304. 186,
  9305. 85,
  9306. 211
  9307. ],
  9308. mediumpurple: [
  9309. 147,
  9310. 112,
  9311. 219
  9312. ],
  9313. mediumseagreen: [
  9314. 60,
  9315. 179,
  9316. 113
  9317. ],
  9318. mediumslateblue: [
  9319. 123,
  9320. 104,
  9321. 238
  9322. ],
  9323. mediumspringgreen: [
  9324. 0,
  9325. 250,
  9326. 154
  9327. ],
  9328. mediumturquoise: [
  9329. 72,
  9330. 209,
  9331. 204
  9332. ],
  9333. mediumvioletred: [
  9334. 199,
  9335. 21,
  9336. 133
  9337. ],
  9338. midnightblue: [
  9339. 25,
  9340. 25,
  9341. 112
  9342. ],
  9343. mintcream: [
  9344. 245,
  9345. 255,
  9346. 250
  9347. ],
  9348. mistyrose: [
  9349. 255,
  9350. 228,
  9351. 225
  9352. ],
  9353. moccasin: [
  9354. 255,
  9355. 228,
  9356. 181
  9357. ],
  9358. navajowhite: [
  9359. 255,
  9360. 222,
  9361. 173
  9362. ],
  9363. navy: [
  9364. 0,
  9365. 0,
  9366. 128
  9367. ],
  9368. oldlace: [
  9369. 253,
  9370. 245,
  9371. 230
  9372. ],
  9373. olive: [
  9374. 128,
  9375. 128,
  9376. 0
  9377. ],
  9378. olivedrab: [
  9379. 107,
  9380. 142,
  9381. 35
  9382. ],
  9383. orange: [
  9384. 255,
  9385. 165,
  9386. 0
  9387. ],
  9388. orangered: [
  9389. 255,
  9390. 69,
  9391. 0
  9392. ],
  9393. orchid: [
  9394. 218,
  9395. 112,
  9396. 214
  9397. ],
  9398. palegoldenrod: [
  9399. 238,
  9400. 232,
  9401. 170
  9402. ],
  9403. palegreen: [
  9404. 152,
  9405. 251,
  9406. 152
  9407. ],
  9408. paleturquoise: [
  9409. 175,
  9410. 238,
  9411. 238
  9412. ],
  9413. palevioletred: [
  9414. 219,
  9415. 112,
  9416. 147
  9417. ],
  9418. papayawhip: [
  9419. 255,
  9420. 239,
  9421. 213
  9422. ],
  9423. peachpuff: [
  9424. 255,
  9425. 218,
  9426. 185
  9427. ],
  9428. peru: [
  9429. 205,
  9430. 133,
  9431. 63
  9432. ],
  9433. pink: [
  9434. 255,
  9435. 192,
  9436. 203
  9437. ],
  9438. plum: [
  9439. 221,
  9440. 160,
  9441. 203
  9442. ],
  9443. powderblue: [
  9444. 176,
  9445. 224,
  9446. 230
  9447. ],
  9448. purple: [
  9449. 128,
  9450. 0,
  9451. 128
  9452. ],
  9453. red: [
  9454. 255,
  9455. 0,
  9456. 0
  9457. ],
  9458. rosybrown: [
  9459. 188,
  9460. 143,
  9461. 143
  9462. ],
  9463. royalblue: [
  9464. 65,
  9465. 105,
  9466. 225
  9467. ],
  9468. saddlebrown: [
  9469. 139,
  9470. 69,
  9471. 19
  9472. ],
  9473. salmon: [
  9474. 250,
  9475. 128,
  9476. 114
  9477. ],
  9478. sandybrown: [
  9479. 244,
  9480. 164,
  9481. 96
  9482. ],
  9483. seagreen: [
  9484. 46,
  9485. 139,
  9486. 87
  9487. ],
  9488. seashell: [
  9489. 255,
  9490. 245,
  9491. 238
  9492. ],
  9493. sienna: [
  9494. 160,
  9495. 82,
  9496. 45
  9497. ],
  9498. silver: [
  9499. 192,
  9500. 192,
  9501. 192
  9502. ],
  9503. skyblue: [
  9504. 135,
  9505. 206,
  9506. 235
  9507. ],
  9508. slateblue: [
  9509. 106,
  9510. 90,
  9511. 205
  9512. ],
  9513. slategray: [
  9514. 119,
  9515. 128,
  9516. 144
  9517. ],
  9518. slategrey: [
  9519. 119,
  9520. 128,
  9521. 144
  9522. ],
  9523. snow: [
  9524. 255,
  9525. 255,
  9526. 250
  9527. ],
  9528. springgreen: [
  9529. 0,
  9530. 255,
  9531. 127
  9532. ],
  9533. steelblue: [
  9534. 70,
  9535. 130,
  9536. 180
  9537. ],
  9538. tan: [
  9539. 210,
  9540. 180,
  9541. 140
  9542. ],
  9543. teal: [
  9544. 0,
  9545. 128,
  9546. 128
  9547. ],
  9548. thistle: [
  9549. 216,
  9550. 191,
  9551. 216
  9552. ],
  9553. tomato: [
  9554. 255,
  9555. 99,
  9556. 71
  9557. ],
  9558. turquoise: [
  9559. 64,
  9560. 224,
  9561. 208
  9562. ],
  9563. violet: [
  9564. 238,
  9565. 130,
  9566. 238
  9567. ],
  9568. wheat: [
  9569. 245,
  9570. 222,
  9571. 179
  9572. ],
  9573. white: [
  9574. 255,
  9575. 255,
  9576. 255
  9577. ],
  9578. whitesmoke: [
  9579. 245,
  9580. 245,
  9581. 245
  9582. ],
  9583. yellow: [
  9584. 255,
  9585. 255,
  9586. 0
  9587. ],
  9588. yellowgreen: [
  9589. 154,
  9590. 205,
  9591. 5
  9592. ]
  9593. }
  9594. };
  9595. }, function(ColorUtils) {
  9596. var formats = ColorUtils.formats,
  9597. lowerized = {};
  9598. formats['#HEX6'] = function(color) {
  9599. return '#' + formats.HEX6(color);
  9600. };
  9601. formats['#HEX8'] = function(color) {
  9602. return '#' + formats.HEX8(color);
  9603. };
  9604. Ext.Object.each(formats, function(name, fn) {
  9605. lowerized[name.toLowerCase()] = function(color) {
  9606. var ret = fn(color);
  9607. return ret.toLowerCase();
  9608. };
  9609. });
  9610. Ext.apply(formats, lowerized);
  9611. });
  9612. /**
  9613. * @private
  9614. */
  9615. Ext.define('Ext.ux.colorpick.ColorMapController', {
  9616. extend: 'Ext.app.ViewController',
  9617. alias: 'controller.colorpickercolormapcontroller',
  9618. requires: [
  9619. 'Ext.ux.colorpick.ColorUtils'
  9620. ],
  9621. // After the component is rendered
  9622. onFirstBoxReady: function() {
  9623. var me = this,
  9624. colorMap = me.getView(),
  9625. dragHandle = colorMap.down('#dragHandle'),
  9626. dd = dragHandle.dd;
  9627. // configure draggable constraints
  9628. dd.constrain = true;
  9629. dd.constrainTo = colorMap.getEl();
  9630. dd.initialConstrainTo = dd.constrainTo;
  9631. // needed otheriwse error EXTJS-13187
  9632. // event handlers
  9633. dd.on('drag', Ext.bind(me.onHandleDrag, me));
  9634. me.mon(colorMap.getEl(), {
  9635. mousedown: me.onMouseDown,
  9636. dragstart: me.onDragStart,
  9637. scope: me
  9638. });
  9639. },
  9640. // Fires when handle is dragged; propagates "handledrag" event on the ColorMap
  9641. // with parameters "percentX" and "percentY", both 0-1, representing the handle
  9642. // position on the color map, relative to the container
  9643. onHandleDrag: function(componentDragger, e) {
  9644. var me = this,
  9645. container = me.getView(),
  9646. // the Color Map
  9647. dragHandle = container.down('#dragHandle'),
  9648. x = dragHandle.getX() - container.getX(),
  9649. y = dragHandle.getY() - container.getY(),
  9650. containerEl = container.getEl(),
  9651. containerWidth = containerEl.getWidth(),
  9652. containerHeight = containerEl.getHeight(),
  9653. xRatio = x / containerWidth,
  9654. yRatio = y / containerHeight;
  9655. // Adjust x/y ratios for dragger always being 1 pixel from the edge on the right
  9656. if (xRatio > 0.99) {
  9657. xRatio = 1;
  9658. }
  9659. if (yRatio > 0.99) {
  9660. yRatio = 1;
  9661. }
  9662. container.fireEvent('handledrag', xRatio, yRatio);
  9663. },
  9664. // Whenever we mousedown over the colormap area
  9665. onMouseDown: function(e) {
  9666. var me = this,
  9667. container = me.getView(),
  9668. dragHandle = container.down('#dragHandle');
  9669. // position drag handle accordingly
  9670. dragHandle.setY(e.getY());
  9671. dragHandle.setX(e.getX());
  9672. me.onHandleDrag();
  9673. // tie into the default dd mechanism
  9674. dragHandle.dd.onMouseDown(e, dragHandle.dd.el);
  9675. },
  9676. // Whenever we start a drag over the colormap area
  9677. onDragStart: function(e) {
  9678. var me = this,
  9679. container = me.getView(),
  9680. dragHandle = container.down('#dragHandle');
  9681. // tie into the default dd mechanism
  9682. dragHandle.dd.onDragStart(e, dragHandle.dd.el);
  9683. },
  9684. // Whenever the map is clicked (but not the drag handle) we need to position
  9685. // the drag handle to the point of click
  9686. onMapClick: function(e) {
  9687. var me = this,
  9688. container = me.getView(),
  9689. // the Color Map
  9690. dragHandle = container.down('#dragHandle'),
  9691. cXY = container.getXY(),
  9692. eXY = e.getXY(),
  9693. left, top;
  9694. left = eXY[0] - cXY[0];
  9695. top = eXY[1] - cXY[1];
  9696. dragHandle.getEl().setStyle({
  9697. left: left + 'px',
  9698. top: top + 'px'
  9699. });
  9700. me.onHandleDrag();
  9701. },
  9702. // Whenever the underlying binding data is changed we need to
  9703. // update position of the dragger; active drag state has been
  9704. // accounted for earlier
  9705. onColorBindingChanged: function(selectedColor) {
  9706. var me = this,
  9707. vm = me.getViewModel(),
  9708. rgba = vm.get('selectedColor'),
  9709. hsv,
  9710. container = me.getView(),
  9711. // the Color Map
  9712. dragHandle = container.down('#dragHandle'),
  9713. containerEl = container.getEl(),
  9714. containerWidth = containerEl.getWidth(),
  9715. containerHeight = containerEl.getHeight(),
  9716. xRatio, yRatio, left, top;
  9717. // Color map selection really only depends on saturation and value of the color
  9718. hsv = Ext.ux.colorpick.ColorUtils.rgb2hsv(rgba.r, rgba.g, rgba.b);
  9719. // x-axis of color map with value 0-1 translates to saturation
  9720. xRatio = hsv.s;
  9721. left = containerWidth * xRatio;
  9722. // y-axis of color map with value 0-1 translates to reverse of "value"
  9723. yRatio = 1 - hsv.v;
  9724. top = containerHeight * yRatio;
  9725. // Position dragger
  9726. dragHandle.getEl().setStyle({
  9727. left: left + 'px',
  9728. top: top + 'px'
  9729. });
  9730. },
  9731. // Whenever only Hue changes we can update the
  9732. // background color of the color map
  9733. // Param "hue" has value of 0-1
  9734. onHueBindingChanged: function(hue) {
  9735. var me = this,
  9736. vm = me.getViewModel(),
  9737. fullColorRGB, hex;
  9738. fullColorRGB = Ext.ux.colorpick.ColorUtils.hsv2rgb(hue, 1, 1);
  9739. hex = Ext.ux.colorpick.ColorUtils.rgb2hex(fullColorRGB.r, fullColorRGB.g, fullColorRGB.b);
  9740. me.getView().getEl().applyStyles({
  9741. 'background-color': '#' + hex
  9742. });
  9743. }
  9744. });
  9745. /**
  9746. * The main colorful square for selecting color shades by dragging around the
  9747. * little circle.
  9748. * @private
  9749. */
  9750. Ext.define('Ext.ux.colorpick.ColorMap', {
  9751. extend: 'Ext.container.Container',
  9752. alias: 'widget.colorpickercolormap',
  9753. controller: 'colorpickercolormapcontroller',
  9754. requires: [
  9755. 'Ext.ux.colorpick.ColorMapController'
  9756. ],
  9757. cls: Ext.baseCSSPrefix + 'colorpicker-colormap',
  9758. // This is the drag "circle"; note it's 1x1 in size to allow full
  9759. // travel around the color map; the inner div has the bigger image
  9760. items: [
  9761. {
  9762. xtype: 'component',
  9763. cls: Ext.baseCSSPrefix + 'colorpicker-colormap-draghandle-container',
  9764. itemId: 'dragHandle',
  9765. width: 1,
  9766. height: 1,
  9767. draggable: true,
  9768. html: '<div class="' + Ext.baseCSSPrefix + 'colorpicker-colormap-draghandle"></div>'
  9769. }
  9770. ],
  9771. listeners: {
  9772. boxready: {
  9773. single: true,
  9774. fn: 'onFirstBoxReady',
  9775. scope: 'controller'
  9776. },
  9777. colorbindingchanged: {
  9778. fn: 'onColorBindingChanged',
  9779. scope: 'controller'
  9780. },
  9781. huebindingchanged: {
  9782. fn: 'onHueBindingChanged',
  9783. scope: 'controller'
  9784. }
  9785. },
  9786. afterRender: function() {
  9787. var me = this,
  9788. src = me.mapGradientUrl,
  9789. el = me.el;
  9790. me.callParent();
  9791. if (!src) {
  9792. // We do this trick to allow the Sass to calculate resource image path for
  9793. // our package and pick up the proper image URL here.
  9794. src = el.getStyle('background-image');
  9795. src = src.substring(4, src.length - 1);
  9796. // strip off outer "url(...)"
  9797. // In IE8 this path will have quotes around it
  9798. if (src.indexOf('"') === 0) {
  9799. src = src.substring(1, src.length - 1);
  9800. }
  9801. // Then remember it on our prototype for any subsequent instances.
  9802. Ext.ux.colorpick.ColorMap.prototype.mapGradientUrl = src;
  9803. }
  9804. // Now clear that style because it will conflict with the background-color
  9805. el.setStyle('background-image', 'none');
  9806. // Create the image with transparent PNG with black and white gradient shades;
  9807. // it blends with the background color (which changes with hue selection). This
  9808. // must be an IMG in order to properly stretch to fit.
  9809. el = me.layout.getElementTarget();
  9810. // the el for items and html
  9811. el.createChild({
  9812. tag: 'img',
  9813. cls: Ext.baseCSSPrefix + 'colorpicker-colormap-blender',
  9814. src: src
  9815. });
  9816. },
  9817. // Called via data binding whenever selectedColor changes; fires "colorbindingchanged"
  9818. setPosition: function(data) {
  9819. var me = this,
  9820. dragHandle = me.down('#dragHandle');
  9821. // Too early in the render cycle? Skip event
  9822. if (!dragHandle.dd || !dragHandle.dd.constrain) {
  9823. return;
  9824. }
  9825. // User actively dragging? Skip event
  9826. if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
  9827. return;
  9828. }
  9829. me.fireEvent('colorbindingchanged', data);
  9830. },
  9831. // Called via data binding whenever selectedColor.h changes; fires "huebindingchanged" event
  9832. setHue: function(hue) {
  9833. var me = this;
  9834. // Too early in the render cycle? Skip event
  9835. if (!me.getEl()) {
  9836. return;
  9837. }
  9838. me.fireEvent('huebindingchanged', hue);
  9839. }
  9840. });
  9841. /**
  9842. * View Model that holds the "selectedColor" of the color picker container.
  9843. */
  9844. Ext.define('Ext.ux.colorpick.SelectorModel', {
  9845. extend: 'Ext.app.ViewModel',
  9846. alias: 'viewmodel.colorpick-selectormodel',
  9847. requires: [
  9848. 'Ext.ux.colorpick.ColorUtils'
  9849. ],
  9850. data: {
  9851. selectedColor: {
  9852. r: 255,
  9853. // red
  9854. g: 255,
  9855. // green
  9856. b: 255,
  9857. // blue
  9858. h: 0,
  9859. // hue,
  9860. s: 1,
  9861. // saturation
  9862. v: 1,
  9863. // value
  9864. a: 1
  9865. },
  9866. // alpha (opacity)
  9867. previousColor: {
  9868. r: 0,
  9869. // red
  9870. g: 0,
  9871. // green
  9872. b: 0,
  9873. // blue
  9874. h: 0,
  9875. // hue,
  9876. s: 1,
  9877. // saturation
  9878. v: 1,
  9879. // value
  9880. a: 1
  9881. }
  9882. },
  9883. // alpha (opacity)
  9884. formulas: {
  9885. // Hexadecimal representation of the color
  9886. hex: {
  9887. get: function(get) {
  9888. var r = get('selectedColor.r').toString(16),
  9889. g = get('selectedColor.g').toString(16),
  9890. b = get('selectedColor.b').toString(16),
  9891. result;
  9892. result = Ext.ux.colorpick.ColorUtils.rgb2hex(r, g, b);
  9893. return '#' + result;
  9894. },
  9895. set: function(hex) {
  9896. var rgb = Ext.ux.colorpick.ColorUtils.parseColor(hex);
  9897. this.changeRGB(rgb);
  9898. }
  9899. },
  9900. // "R" in "RGB"
  9901. red: {
  9902. get: function(get) {
  9903. return get('selectedColor.r');
  9904. },
  9905. set: function(r) {
  9906. this.changeRGB({
  9907. r: r
  9908. });
  9909. }
  9910. },
  9911. // "G" in "RGB"
  9912. green: {
  9913. get: function(get) {
  9914. return get('selectedColor.g');
  9915. },
  9916. set: function(g) {
  9917. this.changeRGB({
  9918. g: g
  9919. });
  9920. }
  9921. },
  9922. // "B" in "RGB"
  9923. blue: {
  9924. get: function(get) {
  9925. return get('selectedColor.b');
  9926. },
  9927. set: function(b) {
  9928. this.changeRGB({
  9929. b: b
  9930. });
  9931. }
  9932. },
  9933. // "H" in HSV
  9934. hue: {
  9935. get: function(get) {
  9936. return get('selectedColor.h') * 360;
  9937. },
  9938. set: function(hue) {
  9939. this.changeHSV({
  9940. h: hue / 360
  9941. });
  9942. }
  9943. },
  9944. // "S" in HSV
  9945. saturation: {
  9946. get: function(get) {
  9947. return get('selectedColor.s') * 100;
  9948. },
  9949. set: function(saturation) {
  9950. this.changeHSV({
  9951. s: saturation / 100
  9952. });
  9953. }
  9954. },
  9955. // "V" in HSV
  9956. value: {
  9957. get: function(get) {
  9958. var v = get('selectedColor.v');
  9959. return v * 100;
  9960. },
  9961. set: function(value) {
  9962. this.changeHSV({
  9963. v: value / 100
  9964. });
  9965. }
  9966. },
  9967. alpha: {
  9968. get: function(data) {
  9969. var a = data('selectedColor.a');
  9970. return a * 100;
  9971. },
  9972. set: function(alpha) {
  9973. this.set('selectedColor', Ext.applyIf({
  9974. a: alpha / 100
  9975. }, this.data.selectedColor));
  9976. }
  9977. }
  9978. },
  9979. // formulas
  9980. changeHSV: function(hsv) {
  9981. Ext.applyIf(hsv, this.data.selectedColor);
  9982. var rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hsv.h, hsv.s, hsv.v);
  9983. hsv.r = rgb.r;
  9984. hsv.g = rgb.g;
  9985. hsv.b = rgb.b;
  9986. this.set('selectedColor', hsv);
  9987. },
  9988. changeRGB: function(rgb) {
  9989. Ext.applyIf(rgb, this.data.selectedColor);
  9990. var hsv = Ext.ux.colorpick.ColorUtils.rgb2hsv(rgb.r, rgb.g, rgb.b);
  9991. rgb.h = hsv.h;
  9992. rgb.s = hsv.s;
  9993. rgb.v = hsv.v;
  9994. this.set('selectedColor', rgb);
  9995. }
  9996. });
  9997. /**
  9998. * @private
  9999. */
  10000. Ext.define('Ext.ux.colorpick.SelectorController', {
  10001. extend: 'Ext.app.ViewController',
  10002. alias: 'controller.colorpick-selectorcontroller',
  10003. requires: [
  10004. 'Ext.ux.colorpick.ColorUtils'
  10005. ],
  10006. destroy: function() {
  10007. var me = this,
  10008. view = me.getView(),
  10009. childViewModel = view.childViewModel;
  10010. if (childViewModel) {
  10011. childViewModel.destroy();
  10012. view.childViewModel = null;
  10013. }
  10014. me.callParent();
  10015. },
  10016. changeHSV: function(hsv) {
  10017. var view = this.getView(),
  10018. color = view.getColor(),
  10019. rgb;
  10020. // Put in values we are not changing (like A, but also missing HSV values)
  10021. Ext.applyIf(hsv, color);
  10022. // Now that HSV is complete, recalculate RGB and combine them
  10023. rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hsv.h, hsv.s, hsv.v);
  10024. Ext.apply(hsv, rgb);
  10025. view.setColor(hsv);
  10026. },
  10027. // Updates Saturation/Value in the model based on ColorMap; params:
  10028. // xPercent - where is the handle relative to the color map width
  10029. // yPercent - where is the handle relative to the color map height
  10030. onColorMapHandleDrag: function(xPercent, yPercent) {
  10031. this.changeHSV({
  10032. s: xPercent,
  10033. v: 1 - yPercent
  10034. });
  10035. },
  10036. // Updates HSV Value in the model and downstream RGB settings
  10037. onValueSliderHandleDrag: function(yPercent) {
  10038. this.changeHSV({
  10039. v: 1 - yPercent
  10040. });
  10041. },
  10042. // Updates HSV Saturation in the model and downstream RGB settings
  10043. onSaturationSliderHandleDrag: function(yPercent) {
  10044. this.changeHSV({
  10045. s: 1 - yPercent
  10046. });
  10047. },
  10048. // Updates Hue in the model and downstream RGB settings
  10049. onHueSliderHandleDrag: function(yPercent) {
  10050. this.changeHSV({
  10051. h: 1 - yPercent
  10052. });
  10053. },
  10054. onAlphaSliderHandleDrag: function(yPercent) {
  10055. var view = this.getView(),
  10056. color = view.getColor(),
  10057. newColor = Ext.applyIf({
  10058. a: 1 - yPercent
  10059. }, color);
  10060. view.setColor(newColor);
  10061. view.el.repaint();
  10062. },
  10063. onPreviousColorSelected: function(comp, color) {
  10064. var view = this.getView();
  10065. view.setColor(color);
  10066. },
  10067. onOK: function() {
  10068. var me = this,
  10069. view = me.getView();
  10070. view.fireEvent('ok', view, view.getValue());
  10071. },
  10072. onCancel: function() {
  10073. this.fireViewEvent('cancel', this.getView());
  10074. },
  10075. onResize: function() {
  10076. var me = this,
  10077. view = me.getView(),
  10078. vm = view.childViewModel,
  10079. refs = me.getReferences(),
  10080. h, s, v, a;
  10081. // Skip initial rendering resize
  10082. if (!me.hasResizedOnce) {
  10083. me.hasResizedOnce = true;
  10084. return;
  10085. }
  10086. h = vm.get('hue');
  10087. s = vm.get('saturation');
  10088. v = vm.get('value');
  10089. a = vm.get('alpha');
  10090. // Reposition the colormap's & sliders' drag handles
  10091. refs.colorMap.setPosition(vm.getData());
  10092. refs.hueSlider.setHue(h);
  10093. refs.satSlider.setSaturation(s);
  10094. refs.valueSlider.setValue(v);
  10095. refs.alphaSlider.setAlpha(a);
  10096. }
  10097. });
  10098. /**
  10099. * A basic component that changes background color, with considerations for opacity
  10100. * support (checkered background image and IE8 support).
  10101. */
  10102. Ext.define('Ext.ux.colorpick.ColorPreview', {
  10103. extend: 'Ext.Component',
  10104. alias: 'widget.colorpickercolorpreview',
  10105. requires: [
  10106. 'Ext.util.Format',
  10107. 'Ext.XTemplate'
  10108. ],
  10109. //hack to solve issue with IE, when applying a filter the click listener is not being fired.
  10110. style: 'position: relative',
  10111. html: '<div class="' + Ext.baseCSSPrefix + 'colorpreview-filter" style="height:100%; width:100%; position: absolute;"></div>' + '<a class="btn" style="height:100%; width:100%; position: absolute;"></a>',
  10112. //eo hack
  10113. cls: Ext.baseCSSPrefix + 'colorpreview',
  10114. height: 256,
  10115. onRender: function() {
  10116. var me = this;
  10117. me.callParent(arguments);
  10118. me.mon(me.el.down('.btn'), 'click', me.onClick, me);
  10119. },
  10120. onClick: function() {
  10121. this.fireEvent('click', this, this.color);
  10122. },
  10123. // Called via databinding - update background color whenever ViewModel changes
  10124. setColor: function(color) {
  10125. var me = this,
  10126. el = me.getEl();
  10127. // Too early in rendering cycle; skip
  10128. if (!el) {
  10129. return;
  10130. }
  10131. me.color = color;
  10132. me.applyBgStyle(color);
  10133. },
  10134. bgStyleTpl: Ext.create('Ext.XTemplate', Ext.isIE && Ext.ieVersion < 10 ? 'filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr=\'#{hexAlpha}{hex}\', endColorstr=\'#{hexAlpha}{hex}\');' : /* IE6-9 */
  10135. 'background: {rgba};'),
  10136. applyBgStyle: function(color) {
  10137. var me = this,
  10138. colorUtils = Ext.ux.colorpick.ColorUtils,
  10139. filterSelector = '.' + Ext.baseCSSPrefix + 'colorpreview-filter',
  10140. el = me.getEl().down(filterSelector),
  10141. hex, alpha, rgba, bgStyle;
  10142. hex = colorUtils.rgb2hex(color.r, color.g, color.b);
  10143. alpha = Ext.util.Format.hex(Math.floor(color.a * 255), 2);
  10144. rgba = colorUtils.getRGBAString(color);
  10145. bgStyle = this.bgStyleTpl.apply({
  10146. hex: hex,
  10147. hexAlpha: alpha,
  10148. rgba: rgba
  10149. });
  10150. el.applyStyles(bgStyle);
  10151. }
  10152. });
  10153. /**
  10154. * @private
  10155. */
  10156. Ext.define('Ext.ux.colorpick.SliderController', {
  10157. extend: 'Ext.app.ViewController',
  10158. alias: 'controller.colorpick-slidercontroller',
  10159. // After the component is rendered
  10160. boxReady: function(view) {
  10161. var me = this,
  10162. container = me.getDragContainer(),
  10163. dragHandle = me.getDragHandle(),
  10164. dd = dragHandle.dd;
  10165. // configure draggable constraints
  10166. dd.constrain = true;
  10167. dd.constrainTo = container.getEl();
  10168. dd.initialConstrainTo = dd.constrainTo;
  10169. // needed otherwise error EXTJS-13187
  10170. // event handlers
  10171. dd.on('drag', me.onHandleDrag, me);
  10172. },
  10173. getDragHandle: function() {
  10174. return this.view.lookupReference('dragHandle');
  10175. },
  10176. getDragContainer: function() {
  10177. return this.view.lookupReference('dragHandleContainer');
  10178. },
  10179. // Fires when handle is dragged; fires "handledrag" event on the slider
  10180. // with parameter "percentY" 0-1, representing the handle position on the slider
  10181. // relative to the height
  10182. onHandleDrag: function(e) {
  10183. var me = this,
  10184. view = me.getView(),
  10185. container = me.getDragContainer(),
  10186. dragHandle = me.getDragHandle(),
  10187. y = dragHandle.getY() - container.getY(),
  10188. containerEl = container.getEl(),
  10189. containerHeight = containerEl.getHeight(),
  10190. yRatio = y / containerHeight;
  10191. // Adjust y ratio for dragger always being 1 pixel from the edge on the bottom
  10192. if (yRatio > 0.99) {
  10193. yRatio = 1;
  10194. }
  10195. view.fireEvent('handledrag', yRatio);
  10196. },
  10197. // Whenever we mousedown over the slider area
  10198. onMouseDown: function(e) {
  10199. var me = this,
  10200. dragHandle = me.getDragHandle(),
  10201. y = e.getY();
  10202. // position drag handle accordingly
  10203. dragHandle.setY(y);
  10204. me.onHandleDrag();
  10205. dragHandle.el.repaint();
  10206. // tie into the default dd mechanism
  10207. dragHandle.dd.onMouseDown(e, dragHandle.dd.el);
  10208. },
  10209. // Whenever we start a drag over the colormap area
  10210. onDragStart: function(e) {
  10211. var me = this,
  10212. dragHandle = me.getDragHandle();
  10213. // tie into the default dd mechanism
  10214. dragHandle.dd.onDragStart(e, dragHandle.dd.el);
  10215. },
  10216. onMouseUp: function() {
  10217. var dragHandle = this.getDragHandle();
  10218. dragHandle.dd.dragEnded = true;
  10219. }
  10220. });
  10221. // work around DragTracker bug
  10222. /**
  10223. * Parent view for the 4 sliders seen on the color picker window.
  10224. * @private
  10225. */
  10226. Ext.define('Ext.ux.colorpick.Slider', {
  10227. extend: 'Ext.container.Container',
  10228. xtype: 'colorpickerslider',
  10229. controller: 'colorpick-slidercontroller',
  10230. afterRender: function() {
  10231. this.callParent(arguments);
  10232. var width = this.width,
  10233. dragCt = this.lookupReference('dragHandleContainer'),
  10234. dragWidth = dragCt.getWidth();
  10235. dragCt.el.setStyle('left', ((width - dragWidth) / 2) + 'px');
  10236. },
  10237. baseCls: Ext.baseCSSPrefix + 'colorpicker-slider',
  10238. requires: [
  10239. 'Ext.ux.colorpick.SliderController'
  10240. ],
  10241. referenceHolder: true,
  10242. listeners: {
  10243. element: 'el',
  10244. mousedown: 'onMouseDown',
  10245. mouseup: 'onMouseUp',
  10246. dragstart: 'onDragStart'
  10247. },
  10248. // Container for the drag handle; needed since the slider
  10249. // is of static size, while the parent container positions
  10250. // it in the center; this is what receives the beautiful
  10251. // color gradients for the visual
  10252. items: {
  10253. xtype: 'container',
  10254. cls: Ext.baseCSSPrefix + 'colorpicker-draghandle-container',
  10255. reference: 'dragHandleContainer',
  10256. height: '100%',
  10257. // This is the drag handle; note it's 100%x1 in size to allow full
  10258. // vertical drag travel; the inner div has the bigger image
  10259. items: {
  10260. xtype: 'component',
  10261. cls: Ext.baseCSSPrefix + 'colorpicker-draghandle-outer',
  10262. reference: 'dragHandle',
  10263. width: '100%',
  10264. height: 1,
  10265. draggable: true,
  10266. html: '<div class="' + Ext.baseCSSPrefix + 'colorpicker-draghandle"></div>'
  10267. }
  10268. },
  10269. // <debug>
  10270. // Called via data binding whenever selectedColor.h changes;
  10271. setHue: function() {
  10272. Ext.raise('Must implement setHue() in a child class!');
  10273. },
  10274. // </debug>
  10275. getDragHandle: function() {
  10276. return this.lookupReference('dragHandle');
  10277. },
  10278. getDragContainer: function() {
  10279. return this.lookupReference('dragHandleContainer');
  10280. }
  10281. });
  10282. /**
  10283. * Used for "Alpha" slider.
  10284. * @private
  10285. */
  10286. Ext.define('Ext.ux.colorpick.SliderAlpha', {
  10287. extend: 'Ext.ux.colorpick.Slider',
  10288. alias: 'widget.colorpickerslideralpha',
  10289. cls: Ext.baseCSSPrefix + 'colorpicker-alpha',
  10290. requires: [
  10291. 'Ext.XTemplate'
  10292. ],
  10293. gradientStyleTpl: Ext.create('Ext.XTemplate', Ext.isIE && Ext.ieVersion < 10 ? 'filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr=\'#FF{hex}\', endColorstr=\'#00{hex}\');' : /* IE6-9 */
  10294. 'background: -moz-linear-gradient(top, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* FF3.6+ */
  10295. 'background: -webkit-linear-gradient(top,rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* Chrome10+,Safari5.1+ */
  10296. 'background: -o-linear-gradient(top, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* Opera 11.10+ */
  10297. 'background: -ms-linear-gradient(top, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* IE10+ */
  10298. 'background: linear-gradient(to bottom, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);'),
  10299. /* W3C */
  10300. // Called via data binding whenever selectedColor.a changes; param is 0-100
  10301. setAlpha: function(value) {
  10302. var me = this,
  10303. container = me.getDragContainer(),
  10304. dragHandle = me.getDragHandle(),
  10305. containerEl = container.getEl(),
  10306. containerHeight = containerEl.getHeight(),
  10307. el, top;
  10308. // Too early in the render cycle? Skip event
  10309. if (!dragHandle.dd || !dragHandle.dd.constrain) {
  10310. return;
  10311. }
  10312. // User actively dragging? Skip event
  10313. if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
  10314. return;
  10315. }
  10316. // y-axis of slider with value 0-1 translates to reverse of "value"
  10317. top = containerHeight * (1 - (value / 100));
  10318. // Position dragger
  10319. el = dragHandle.getEl();
  10320. el.setStyle({
  10321. top: top + 'px'
  10322. });
  10323. },
  10324. // Called via data binding whenever selectedColor.h changes; hue param is 0-1
  10325. setColor: function(color) {
  10326. var me = this,
  10327. container = me.getDragContainer(),
  10328. hex, el;
  10329. // Too early in the render cycle? Skip event
  10330. if (!me.getEl()) {
  10331. return;
  10332. }
  10333. // Determine HEX for new hue and set as background based on template
  10334. hex = Ext.ux.colorpick.ColorUtils.rgb2hex(color.r, color.g, color.b);
  10335. el = container.getEl().first();
  10336. el.applyStyles(me.gradientStyleTpl.apply({
  10337. hex: hex,
  10338. r: color.r,
  10339. g: color.g,
  10340. b: color.b
  10341. }));
  10342. }
  10343. });
  10344. /**
  10345. * Used for "Saturation" slider
  10346. * @private
  10347. */
  10348. Ext.define('Ext.ux.colorpick.SliderSaturation', {
  10349. extend: 'Ext.ux.colorpick.Slider',
  10350. alias: 'widget.colorpickerslidersaturation',
  10351. cls: Ext.baseCSSPrefix + 'colorpicker-saturation',
  10352. gradientStyleTpl: Ext.create('Ext.XTemplate', Ext.isIE && Ext.ieVersion < 10 ? 'filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr=\'#{hex}\', endColorstr=\'#ffffff\');' : /* IE6-9 */
  10353. 'background: -mox-linear-gradient(top, #{hex} 0%, #ffffff 100%);' + /* FF3.6+ */
  10354. 'background: -webkit-linear-gradient(top, #{hex} 0%,#ffffff 100%);' + /* Chrome10+,Safari5.1+ */
  10355. 'background: -o-linear-gradient(top, #{hex} 0%,#ffffff 100%);' + /* Opera 11.10+ */
  10356. 'background: -ms-linear-gradient(top, #{hex} 0%,#ffffff 100%);' + /* IE10+ */
  10357. 'background: linear-gradient(to bottom, #{hex} 0%,#ffffff 100%);'),
  10358. /* W3C */
  10359. // Called via data binding whenever selectedColor.s changes; saturation param is 0-100
  10360. setSaturation: function(saturation) {
  10361. var me = this,
  10362. container = me.getDragContainer(),
  10363. dragHandle = me.getDragHandle(),
  10364. containerEl = container.getEl(),
  10365. containerHeight = containerEl.getHeight(),
  10366. yRatio, top;
  10367. // Too early in the render cycle? Skip event
  10368. if (!dragHandle.dd || !dragHandle.dd.constrain) {
  10369. return;
  10370. }
  10371. // User actively dragging? Skip event
  10372. if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
  10373. return;
  10374. }
  10375. // y-axis of slider with value 0-1 translates to reverse of "saturation"
  10376. yRatio = 1 - (saturation / 100);
  10377. top = containerHeight * yRatio;
  10378. // Position dragger
  10379. dragHandle.getEl().setStyle({
  10380. top: top + 'px'
  10381. });
  10382. },
  10383. // Called via data binding whenever selectedColor.h changes; hue param is 0-1
  10384. setHue: function(hue) {
  10385. var me = this,
  10386. container = me.getDragContainer(),
  10387. rgb, hex;
  10388. // Too early in the render cycle? Skip event
  10389. if (!me.getEl()) {
  10390. return;
  10391. }
  10392. // Determine HEX for new hue and set as background based on template
  10393. rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hue, 1, 1);
  10394. hex = Ext.ux.colorpick.ColorUtils.rgb2hex(rgb.r, rgb.g, rgb.b);
  10395. container.getEl().applyStyles(me.gradientStyleTpl.apply({
  10396. hex: hex
  10397. }));
  10398. }
  10399. });
  10400. /**
  10401. * Used for "Value" slider.
  10402. * @private
  10403. */
  10404. Ext.define('Ext.ux.colorpick.SliderValue', {
  10405. extend: 'Ext.ux.colorpick.Slider',
  10406. alias: 'widget.colorpickerslidervalue',
  10407. cls: Ext.baseCSSPrefix + 'colorpicker-value',
  10408. requires: [
  10409. 'Ext.XTemplate'
  10410. ],
  10411. gradientStyleTpl: Ext.create('Ext.XTemplate', Ext.isIE && Ext.ieVersion < 10 ? 'filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr=\'#{hex}\', endColorstr=\'#000000\');' : /* IE6-9 */
  10412. 'background: -mox-linear-gradient(top, #{hex} 0%, #000000 100%);' + /* FF3.6+ */
  10413. 'background: -webkit-linear-gradient(top, #{hex} 0%,#000000 100%);' + /* Chrome10+,Safari5.1+ */
  10414. 'background: -o-linear-gradient(top, #{hex} 0%,#000000 100%);' + /* Opera 11.10+ */
  10415. 'background: -ms-linear-gradient(top, #{hex} 0%,#000000 100%);' + /* IE10+ */
  10416. 'background: linear-gradient(to bottom, #{hex} 0%,#000000 100%);'),
  10417. /* W3C */
  10418. // Called via data binding whenever selectedColor.v changes; value param is 0-100
  10419. setValue: function(value) {
  10420. var me = this,
  10421. container = me.getDragContainer(),
  10422. dragHandle = me.getDragHandle(),
  10423. containerEl = container.getEl(),
  10424. containerHeight = containerEl.getHeight(),
  10425. yRatio, top;
  10426. // Too early in the render cycle? Skip event
  10427. if (!dragHandle.dd || !dragHandle.dd.constrain) {
  10428. return;
  10429. }
  10430. // User actively dragging? Skip event
  10431. if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
  10432. return;
  10433. }
  10434. // y-axis of slider with value 0-1 translates to reverse of "value"
  10435. yRatio = 1 - (value / 100);
  10436. top = containerHeight * yRatio;
  10437. // Position dragger
  10438. dragHandle.getEl().setStyle({
  10439. top: top + 'px'
  10440. });
  10441. },
  10442. // Called via data binding whenever selectedColor.h changes; hue param is 0-1
  10443. setHue: function(hue) {
  10444. var me = this,
  10445. container = me.getDragContainer(),
  10446. rgb, hex;
  10447. // Too early in the render cycle? Skip event
  10448. if (!me.getEl()) {
  10449. return;
  10450. }
  10451. // Determine HEX for new hue and set as background based on template
  10452. rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hue, 1, 1);
  10453. hex = Ext.ux.colorpick.ColorUtils.rgb2hex(rgb.r, rgb.g, rgb.b);
  10454. container.getEl().applyStyles(me.gradientStyleTpl.apply({
  10455. hex: hex
  10456. }));
  10457. }
  10458. });
  10459. /**
  10460. * Used for "Hue" slider.
  10461. * @private
  10462. */
  10463. Ext.define('Ext.ux.colorpick.SliderHue', {
  10464. extend: 'Ext.ux.colorpick.Slider',
  10465. alias: 'widget.colorpickersliderhue',
  10466. cls: Ext.baseCSSPrefix + 'colorpicker-hue',
  10467. afterRender: function() {
  10468. var me = this,
  10469. src = me.gradientUrl,
  10470. el = me.el;
  10471. me.callParent();
  10472. if (!src) {
  10473. // We do this trick to allow the Sass to calculate resource image path for
  10474. // our package and pick up the proper image URL here.
  10475. src = el.getStyle('background-image');
  10476. src = src.substring(4, src.length - 1);
  10477. // strip off outer "url(...)"
  10478. // In IE8 this path will have quotes around it
  10479. if (src.indexOf('"') === 0) {
  10480. src = src.substring(1, src.length - 1);
  10481. }
  10482. // Then remember it on our prototype for any subsequent instances.
  10483. Ext.ux.colorpick.SliderHue.prototype.gradientUrl = src;
  10484. }
  10485. // Now clear that style because it will conflict with the background-color
  10486. el.setStyle('background-image', 'none');
  10487. // Create the image with the background PNG
  10488. el = me.getDragContainer().layout.getElementTarget();
  10489. // the el for items and html
  10490. el.createChild({
  10491. tag: 'img',
  10492. cls: Ext.baseCSSPrefix + 'colorpicker-hue-gradient',
  10493. src: src
  10494. });
  10495. },
  10496. // Called via data binding whenever selectedColor.h changes; hue param is 0-1
  10497. setHue: function(hue) {
  10498. var me = this,
  10499. container = me.getDragContainer(),
  10500. dragHandle = me.getDragHandle(),
  10501. containerEl = container.getEl(),
  10502. containerHeight = containerEl.getHeight(),
  10503. el, top;
  10504. // Too early in the render cycle? Skip event
  10505. if (!dragHandle.dd || !dragHandle.dd.constrain) {
  10506. return;
  10507. }
  10508. // User actively dragging? Skip event
  10509. if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
  10510. return;
  10511. }
  10512. // y-axis of slider with value 0-1 translates to reverse of "hue"
  10513. top = containerHeight * (1 - hue);
  10514. // Position dragger
  10515. el = dragHandle.getEl();
  10516. el.setStyle({
  10517. top: top + 'px'
  10518. });
  10519. }
  10520. });
  10521. /**
  10522. * Sencha Pro Services presents xtype "colorselector".
  10523. * API has been kept as close to the regular colorpicker as possible. The Selector can be
  10524. * rendered to any container.
  10525. *
  10526. * The defaul selected color is configurable via {@link #value} config. Usually used in
  10527. * forms via {@link Ext.ux.colorpick.Button} or {@link Ext.ux.colorpick.Field}.
  10528. *
  10529. * Typically you will need to listen for the change event to be notified when the user
  10530. * chooses a color. Alternatively, you can bind to the "value" config
  10531. *
  10532. * @example
  10533. * Ext.create('Ext.ux.colorpick.Selector', {
  10534. * value : '993300', // initial selected color
  10535. * renderTo : Ext.getBody(),
  10536. * listeners: {
  10537. * change: function (colorselector, color) {
  10538. * console.log('New color: ' + color);
  10539. * }
  10540. * }
  10541. * });
  10542. */
  10543. Ext.define('Ext.ux.colorpick.Selector', {
  10544. extend: 'Ext.container.Container',
  10545. xtype: 'colorselector',
  10546. mixins: [
  10547. 'Ext.ux.colorpick.Selection'
  10548. ],
  10549. controller: 'colorpick-selectorcontroller',
  10550. requires: [
  10551. 'Ext.layout.container.HBox',
  10552. 'Ext.form.field.Text',
  10553. 'Ext.form.field.Number',
  10554. 'Ext.ux.colorpick.ColorMap',
  10555. 'Ext.ux.colorpick.SelectorModel',
  10556. 'Ext.ux.colorpick.SelectorController',
  10557. 'Ext.ux.colorpick.ColorPreview',
  10558. 'Ext.ux.colorpick.Slider',
  10559. 'Ext.ux.colorpick.SliderAlpha',
  10560. 'Ext.ux.colorpick.SliderSaturation',
  10561. 'Ext.ux.colorpick.SliderValue',
  10562. 'Ext.ux.colorpick.SliderHue'
  10563. ],
  10564. config: {
  10565. hexReadOnly: true
  10566. },
  10567. width: 580,
  10568. // default width and height gives 255x255 color map in Crisp
  10569. height: 337,
  10570. cls: Ext.baseCSSPrefix + 'colorpicker',
  10571. padding: 10,
  10572. layout: {
  10573. type: 'hbox',
  10574. align: 'stretch'
  10575. },
  10576. defaultBindProperty: 'value',
  10577. twoWayBindable: [
  10578. 'value'
  10579. ],
  10580. /**
  10581. * @cfg fieldWidth {Number} Width of the text fields on the container (excluding HEX);
  10582. * since the width of the slider containers is the same as the text field under it
  10583. * (it's the same vbox column), changing this value will also affect the spacing between
  10584. * the sliders.
  10585. */
  10586. fieldWidth: 50,
  10587. /**
  10588. * @cfg fieldPad {Number} padding between the sliders and HEX/R/G/B fields.
  10589. */
  10590. fieldPad: 5,
  10591. /**
  10592. * @cfg {Boolean} [showPreviousColor]
  10593. * Whether "previous color" region (in upper right, below the selected color preview) should be shown;
  10594. * these are relied upon by the {@link Ext.ux.colorpick.Button} and the {@link Ext.ux.colorpick.Field}.
  10595. */
  10596. showPreviousColor: false,
  10597. /**
  10598. * @cfg {Boolean} [showOkCancelButtons]
  10599. * Whether Ok and Cancel buttons (in upper right, below the selected color preview) should be shown;
  10600. * these are relied upon by the {@link Ext.ux.colorpick.Button} and the {@link Ext.ux.colorpick.Field}.
  10601. */
  10602. showOkCancelButtons: false,
  10603. /**
  10604. * @event change
  10605. * Fires when a color is selected. Simply dragging sliders around will trigger this.
  10606. * @param {Ext.ux.colorpick.Selector} this
  10607. * @param {String} color The value of the selected color as per specified {@link #format}.
  10608. * @param {String} previousColor The previous color value.
  10609. */
  10610. /**
  10611. * @event ok
  10612. * Fires when OK button is clicked (see {@link #showOkCancelButtons}).
  10613. * @param {Ext.ux.colorpick.Selector} this
  10614. * @param {String} color The value of the selected color as per specified {@link #format}.
  10615. */
  10616. /**
  10617. * @event cancel
  10618. * Fires when Cancel button is clicked (see {@link #showOkCancelButtons}).
  10619. * @param {Ext.ux.colorpick.Selector} this
  10620. */
  10621. listeners: {
  10622. resize: 'onResize'
  10623. },
  10624. constructor: function(config) {
  10625. var me = this,
  10626. childViewModel = Ext.Factory.viewModel('colorpick-selectormodel');
  10627. // Since this component needs to present its value as a thing to which users can
  10628. // bind, we create an internal VM for our purposes.
  10629. me.childViewModel = childViewModel;
  10630. me.items = [
  10631. me.getMapAndHexRGBFields(childViewModel),
  10632. me.getSliderAndHField(childViewModel),
  10633. me.getSliderAndSField(childViewModel),
  10634. me.getSliderAndVField(childViewModel),
  10635. me.getSliderAndAField(childViewModel),
  10636. me.getPreviewAndButtons(childViewModel, config)
  10637. ];
  10638. me.childViewModel.bind('{selectedColor}', function(color) {
  10639. me.setColor(color);
  10640. });
  10641. me.callParent([
  10642. config
  10643. ]);
  10644. },
  10645. updateColor: function(color) {
  10646. var me = this;
  10647. me.mixins.colorselection.updateColor.call(me, color);
  10648. me.childViewModel.set('selectedColor', color);
  10649. },
  10650. updatePreviousColor: function(color) {
  10651. this.childViewModel.set('previousColor', color);
  10652. },
  10653. // Splits up view declaration for readability
  10654. // "Map" and HEX/R/G/B fields
  10655. getMapAndHexRGBFields: function(childViewModel) {
  10656. var me = this,
  10657. fieldMargin = {
  10658. top: 0,
  10659. right: me.fieldPad,
  10660. bottom: 0,
  10661. left: 0
  10662. },
  10663. fieldWidth = me.fieldWidth;
  10664. return {
  10665. xtype: 'container',
  10666. viewModel: childViewModel,
  10667. cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
  10668. flex: 1,
  10669. layout: {
  10670. type: 'vbox',
  10671. align: 'stretch'
  10672. },
  10673. margin: '0 10 0 0',
  10674. items: [
  10675. // "MAP"
  10676. {
  10677. xtype: 'colorpickercolormap',
  10678. reference: 'colorMap',
  10679. flex: 1,
  10680. bind: {
  10681. position: {
  10682. bindTo: '{selectedColor}',
  10683. deep: true
  10684. },
  10685. hue: '{selectedColor.h}'
  10686. },
  10687. listeners: {
  10688. handledrag: 'onColorMapHandleDrag'
  10689. }
  10690. },
  10691. // HEX/R/G/B FIELDS
  10692. {
  10693. xtype: 'container',
  10694. layout: 'hbox',
  10695. defaults: {
  10696. labelAlign: 'top',
  10697. labelSeparator: '',
  10698. allowBlank: false,
  10699. onChange: function() {
  10700. // prevent data binding propagation if bad value
  10701. if (this.isValid()) {
  10702. // this is kind of dirty and ideally we would extend these fields
  10703. // and override the method, but works for now
  10704. Ext.form.field.Base.prototype.onChange.apply(this, arguments);
  10705. }
  10706. }
  10707. },
  10708. items: [
  10709. {
  10710. xtype: 'textfield',
  10711. fieldLabel: 'HEX',
  10712. flex: 1,
  10713. bind: '{hex}',
  10714. margin: fieldMargin,
  10715. regex: /^#[0-9a-f]{6}$/i,
  10716. readonly: me.getHexReadOnly()
  10717. },
  10718. {
  10719. xtype: 'numberfield',
  10720. fieldLabel: 'R',
  10721. bind: '{red}',
  10722. width: fieldWidth,
  10723. hideTrigger: true,
  10724. maxValue: 255,
  10725. minValue: 0,
  10726. margin: fieldMargin
  10727. },
  10728. {
  10729. xtype: 'numberfield',
  10730. fieldLabel: 'G',
  10731. bind: '{green}',
  10732. width: fieldWidth,
  10733. hideTrigger: true,
  10734. maxValue: 255,
  10735. minValue: 0,
  10736. margin: fieldMargin
  10737. },
  10738. {
  10739. xtype: 'numberfield',
  10740. fieldLabel: 'B',
  10741. bind: '{blue}',
  10742. width: fieldWidth,
  10743. hideTrigger: true,
  10744. maxValue: 255,
  10745. minValue: 0,
  10746. margin: 0
  10747. }
  10748. ]
  10749. }
  10750. ]
  10751. };
  10752. },
  10753. // Splits up view declaration for readability
  10754. // Slider and H field
  10755. getSliderAndHField: function(childViewModel) {
  10756. var me = this,
  10757. fieldWidth = me.fieldWidth;
  10758. return {
  10759. xtype: 'container',
  10760. viewModel: childViewModel,
  10761. cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
  10762. width: fieldWidth,
  10763. layout: {
  10764. type: 'vbox',
  10765. align: 'stretch'
  10766. },
  10767. items: [
  10768. {
  10769. xtype: 'colorpickersliderhue',
  10770. reference: 'hueSlider',
  10771. flex: 1,
  10772. bind: {
  10773. hue: '{selectedColor.h}'
  10774. },
  10775. width: fieldWidth,
  10776. listeners: {
  10777. handledrag: 'onHueSliderHandleDrag'
  10778. }
  10779. },
  10780. {
  10781. xtype: 'numberfield',
  10782. fieldLabel: 'H',
  10783. labelAlign: 'top',
  10784. labelSeparator: '',
  10785. bind: '{hue}',
  10786. hideTrigger: true,
  10787. maxValue: 360,
  10788. minValue: 0,
  10789. allowBlank: false,
  10790. margin: 0
  10791. }
  10792. ]
  10793. };
  10794. },
  10795. // Splits up view declaration for readability
  10796. // Slider and S field
  10797. getSliderAndSField: function(childViewModel) {
  10798. var me = this,
  10799. fieldWidth = me.fieldWidth;
  10800. return {
  10801. xtype: 'container',
  10802. viewModel: childViewModel,
  10803. cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
  10804. width: fieldWidth,
  10805. layout: {
  10806. type: 'vbox',
  10807. align: 'stretch'
  10808. },
  10809. margin: {
  10810. right: me.fieldPad,
  10811. left: me.fieldPad
  10812. },
  10813. items: [
  10814. {
  10815. xtype: 'colorpickerslidersaturation',
  10816. reference: 'satSlider',
  10817. flex: 1,
  10818. bind: {
  10819. saturation: '{saturation}',
  10820. hue: '{selectedColor.h}'
  10821. },
  10822. width: fieldWidth,
  10823. listeners: {
  10824. handledrag: 'onSaturationSliderHandleDrag'
  10825. }
  10826. },
  10827. {
  10828. xtype: 'numberfield',
  10829. fieldLabel: 'S',
  10830. labelAlign: 'top',
  10831. labelSeparator: '',
  10832. bind: '{saturation}',
  10833. hideTrigger: true,
  10834. maxValue: 100,
  10835. minValue: 0,
  10836. allowBlank: false,
  10837. margin: 0
  10838. }
  10839. ]
  10840. };
  10841. },
  10842. // Splits up view declaration for readability
  10843. // Slider and V field
  10844. getSliderAndVField: function(childViewModel) {
  10845. var me = this,
  10846. fieldWidth = me.fieldWidth;
  10847. return {
  10848. xtype: 'container',
  10849. viewModel: childViewModel,
  10850. cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
  10851. width: fieldWidth,
  10852. layout: {
  10853. type: 'vbox',
  10854. align: 'stretch'
  10855. },
  10856. items: [
  10857. {
  10858. xtype: 'colorpickerslidervalue',
  10859. reference: 'valueSlider',
  10860. flex: 1,
  10861. bind: {
  10862. value: '{value}',
  10863. hue: '{selectedColor.h}'
  10864. },
  10865. width: fieldWidth,
  10866. listeners: {
  10867. handledrag: 'onValueSliderHandleDrag'
  10868. }
  10869. },
  10870. {
  10871. xtype: 'numberfield',
  10872. fieldLabel: 'V',
  10873. labelAlign: 'top',
  10874. labelSeparator: '',
  10875. bind: '{value}',
  10876. hideTrigger: true,
  10877. maxValue: 100,
  10878. minValue: 0,
  10879. allowBlank: false,
  10880. margin: 0
  10881. }
  10882. ]
  10883. };
  10884. },
  10885. // Splits up view declaration for readability
  10886. // Slider and A field
  10887. getSliderAndAField: function(childViewModel) {
  10888. var me = this,
  10889. fieldWidth = me.fieldWidth;
  10890. return {
  10891. xtype: 'container',
  10892. viewModel: childViewModel,
  10893. cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
  10894. width: fieldWidth,
  10895. layout: {
  10896. type: 'vbox',
  10897. align: 'stretch'
  10898. },
  10899. margin: {
  10900. left: me.fieldPad
  10901. },
  10902. items: [
  10903. {
  10904. xtype: 'colorpickerslideralpha',
  10905. reference: 'alphaSlider',
  10906. flex: 1,
  10907. bind: {
  10908. alpha: '{alpha}',
  10909. color: {
  10910. bindTo: '{selectedColor}',
  10911. deep: true
  10912. }
  10913. },
  10914. width: fieldWidth,
  10915. listeners: {
  10916. handledrag: 'onAlphaSliderHandleDrag'
  10917. }
  10918. },
  10919. {
  10920. xtype: 'numberfield',
  10921. fieldLabel: 'A',
  10922. labelAlign: 'top',
  10923. labelSeparator: '',
  10924. bind: '{alpha}',
  10925. hideTrigger: true,
  10926. maxValue: 100,
  10927. minValue: 0,
  10928. allowBlank: false,
  10929. margin: 0
  10930. }
  10931. ]
  10932. };
  10933. },
  10934. // Splits up view declaration for readability
  10935. // Preview current/previous color squares and OK and Cancel buttons
  10936. getPreviewAndButtons: function(childViewModel, config) {
  10937. // selected color preview is always shown
  10938. var items = [
  10939. {
  10940. xtype: 'colorpickercolorpreview',
  10941. flex: 1,
  10942. bind: {
  10943. color: {
  10944. bindTo: '{selectedColor}',
  10945. deep: true
  10946. }
  10947. }
  10948. }
  10949. ];
  10950. // previous color preview is optional
  10951. if (config.showPreviousColor) {
  10952. items.push({
  10953. xtype: 'colorpickercolorpreview',
  10954. flex: 1,
  10955. bind: {
  10956. color: {
  10957. bindTo: '{previousColor}',
  10958. deep: true
  10959. }
  10960. },
  10961. listeners: {
  10962. click: 'onPreviousColorSelected'
  10963. }
  10964. });
  10965. }
  10966. // Ok/Cancel buttons are optional
  10967. if (config.showOkCancelButtons) {
  10968. items.push({
  10969. xtype: 'button',
  10970. text: 'OK',
  10971. margin: '10 0 0 0',
  10972. padding: '10 0 10 0',
  10973. handler: 'onOK'
  10974. }, {
  10975. xtype: 'button',
  10976. text: 'Cancel',
  10977. margin: '10 0 0 0',
  10978. padding: '10 0 10 0',
  10979. handler: 'onCancel'
  10980. });
  10981. }
  10982. return {
  10983. xtype: 'container',
  10984. viewModel: childViewModel,
  10985. width: 70,
  10986. margin: '0 0 0 10',
  10987. items: items,
  10988. layout: {
  10989. type: 'vbox',
  10990. align: 'stretch'
  10991. }
  10992. };
  10993. }
  10994. });
  10995. /**
  10996. * @private
  10997. */
  10998. Ext.define('Ext.ux.colorpick.ButtonController', {
  10999. extend: 'Ext.app.ViewController',
  11000. alias: 'controller.colorpick-buttoncontroller',
  11001. requires: [
  11002. 'Ext.window.Window',
  11003. 'Ext.layout.container.Fit',
  11004. 'Ext.ux.colorpick.Selector',
  11005. 'Ext.ux.colorpick.ColorUtils'
  11006. ],
  11007. afterRender: function(view) {
  11008. view.updateColor(view.getColor());
  11009. },
  11010. destroy: function() {
  11011. var view = this.getView(),
  11012. colorPickerWindow = view.colorPickerWindow;
  11013. if (colorPickerWindow) {
  11014. colorPickerWindow.destroy();
  11015. view.colorPickerWindow = view.colorPicker = null;
  11016. }
  11017. this.callParent();
  11018. },
  11019. getPopup: function() {
  11020. var view = this.getView(),
  11021. popup = view.colorPickerWindow,
  11022. selector;
  11023. if (!popup) {
  11024. popup = Ext.create(view.getPopup());
  11025. view.colorPickerWindow = popup;
  11026. popup.colorPicker = view.colorPicker = selector = popup.lookupReference('selector');
  11027. selector.setFormat(view.getFormat());
  11028. selector.on({
  11029. ok: 'onColorPickerOK',
  11030. cancel: 'onColorPickerCancel',
  11031. scope: this
  11032. });
  11033. popup.on({
  11034. close: 'onColorPickerCancel',
  11035. scope: this
  11036. });
  11037. }
  11038. return popup;
  11039. },
  11040. // When button is clicked show the color picker window
  11041. onClick: function() {
  11042. var me = this,
  11043. view = me.getView(),
  11044. color = view.getColor(),
  11045. popup = me.getPopup(),
  11046. colorPicker = popup.colorPicker;
  11047. colorPicker.setColor(color);
  11048. colorPicker.setPreviousColor(color);
  11049. popup.showBy(view, 'tl-br?');
  11050. },
  11051. onColorPickerOK: function(picker) {
  11052. var view = this.getView(),
  11053. color = picker.getColor(),
  11054. cpWin = view.colorPickerWindow;
  11055. cpWin.hide();
  11056. view.setColor(color);
  11057. },
  11058. onColorPickerCancel: function() {
  11059. var view = this.getView(),
  11060. cpWin = view.colorPickerWindow;
  11061. cpWin.hide();
  11062. },
  11063. syncColor: function(color) {
  11064. var view = this.getView();
  11065. Ext.ux.colorpick.ColorUtils.setBackground(view.filterEl, color);
  11066. }
  11067. });
  11068. /**
  11069. * A simple color swatch that can be clicked to bring up the color selector.
  11070. *
  11071. * The selected color is configurable via {@link #value}.
  11072. *
  11073. * @example
  11074. * Ext.create('Ext.ux.colorpick.Button', {
  11075. * value: '993300', // initial selected color
  11076. * renderTo: Ext.getBody(),
  11077. *
  11078. * listeners: {
  11079. * select: function(picker, selColor) {
  11080. * Ext.Msg.alert('Color', selColor);
  11081. * }
  11082. * }
  11083. * });
  11084. */
  11085. Ext.define('Ext.ux.colorpick.Button', {
  11086. extend: 'Ext.Component',
  11087. xtype: 'colorbutton',
  11088. controller: 'colorpick-buttoncontroller',
  11089. mixins: [
  11090. 'Ext.ux.colorpick.Selection'
  11091. ],
  11092. requires: [
  11093. 'Ext.ux.colorpick.ButtonController'
  11094. ],
  11095. baseCls: Ext.baseCSSPrefix + 'colorpicker-button',
  11096. width: 20,
  11097. height: 20,
  11098. childEls: [
  11099. 'btnEl',
  11100. 'filterEl'
  11101. ],
  11102. config: {
  11103. /**
  11104. * @cfg {Object} popup
  11105. * This object configures the popup window and colorselector component displayed
  11106. * when this button is clicked. Applications should not need to configure this.
  11107. * @private
  11108. */
  11109. popup: {
  11110. lazy: true,
  11111. $value: {
  11112. xtype: 'window',
  11113. closeAction: 'hide',
  11114. referenceHolder: true,
  11115. minWidth: 540,
  11116. minHeight: 200,
  11117. layout: 'fit',
  11118. header: false,
  11119. resizable: true,
  11120. items: {
  11121. xtype: 'colorselector',
  11122. reference: 'selector',
  11123. showPreviousColor: true,
  11124. showOkCancelButtons: true
  11125. }
  11126. }
  11127. }
  11128. },
  11129. defaultBindProperty: 'value',
  11130. twoWayBindable: 'value',
  11131. // Solve issue with IE, when applying a filter the click listener is not being fired.
  11132. renderTpl: '<div id="{id}-filterEl" data-ref="filterEl" style="height:100%; width:100%; position: absolute;"></div>' + '<a id="{id}-btnEl" data-ref="btnEl" style="height:100%; width:100%; position: absolute;"></a>',
  11133. listeners: {
  11134. click: 'onClick',
  11135. element: 'btnEl'
  11136. },
  11137. /**
  11138. * @event change
  11139. * Fires when a color is selected.
  11140. * @param {Ext.ux.colorpick.Selector} this
  11141. * @param {String} color The value of the selected color as per specified {@link #format}.
  11142. * @param {String} previousColor The previous color value.
  11143. */
  11144. updateColor: function(color) {
  11145. var me = this,
  11146. cp = me.colorPicker;
  11147. me.mixins.colorselection.updateColor.call(me, color);
  11148. Ext.ux.colorpick.ColorUtils.setBackground(me.filterEl, color);
  11149. if (cp) {
  11150. cp.setColor(color);
  11151. }
  11152. },
  11153. // Sets this.format and color picker's setFormat()
  11154. updateFormat: function(format) {
  11155. var cp = this.colorPicker;
  11156. if (cp) {
  11157. cp.setFormat(format);
  11158. }
  11159. }
  11160. });
  11161. /**
  11162. * A field that can be clicked to bring up the color picker.
  11163. * The selected color is configurable via {@link #value}.
  11164. *
  11165. * @example
  11166. * Ext.create({
  11167. * xtype: 'colorfield',
  11168. * renderTo: Ext.getBody(),
  11169. *
  11170. * value: '#993300', // initial selected color
  11171. *
  11172. * listeners : {
  11173. * change: function (field, color) {
  11174. * console.log('New color: ' + color);
  11175. * }
  11176. * }
  11177. * });
  11178. */
  11179. Ext.define('Ext.ux.colorpick.Field', {
  11180. extend: 'Ext.form.field.Picker',
  11181. xtype: 'colorfield',
  11182. mixins: [
  11183. 'Ext.ux.colorpick.Selection'
  11184. ],
  11185. requires: [
  11186. 'Ext.window.Window',
  11187. 'Ext.ux.colorpick.Selector',
  11188. 'Ext.ux.colorpick.ColorUtils',
  11189. 'Ext.layout.container.Fit'
  11190. ],
  11191. editable: false,
  11192. matchFieldWidth: false,
  11193. // picker is usually wider than field
  11194. // "Color Swatch" shown on the left of the field
  11195. beforeBodyEl: [
  11196. '<div class="' + Ext.baseCSSPrefix + 'colorpicker-field-swatch">' + '<div id="{id}-swatchEl" data-ref="swatchEl" class="' + Ext.baseCSSPrefix + 'colorpicker-field-swatch-inner"></div>' + '</div>'
  11197. ],
  11198. cls: Ext.baseCSSPrefix + 'colorpicker-field',
  11199. childEls: [
  11200. 'swatchEl'
  11201. ],
  11202. checkChangeEvents: [
  11203. 'change'
  11204. ],
  11205. config: {
  11206. /**
  11207. * @cfg {Object} popup
  11208. * This object configures the popup window and colorselector component displayed
  11209. * when this button is clicked. Applications should not need to configure this.
  11210. * @private
  11211. */
  11212. popup: {
  11213. lazy: true,
  11214. $value: {
  11215. xtype: 'window',
  11216. closeAction: 'hide',
  11217. referenceHolder: true,
  11218. minWidth: 540,
  11219. minHeight: 200,
  11220. layout: 'fit',
  11221. header: false,
  11222. resizable: true,
  11223. items: {
  11224. xtype: 'colorselector',
  11225. reference: 'selector',
  11226. showPreviousColor: true,
  11227. showOkCancelButtons: true
  11228. }
  11229. }
  11230. }
  11231. },
  11232. /**
  11233. * @event change
  11234. * Fires when a color is selected or if the field value is updated (if {@link #editable}).
  11235. * @param {Ext.ux.colorpick.Field} this
  11236. * @param {String} color The value of the selected color as per specified {@link #format}.
  11237. * @param {String} previousColor The previous color value.
  11238. */
  11239. initComponent: function() {
  11240. var me = this;
  11241. me.callParent();
  11242. me.on('change', me.onHexChange);
  11243. },
  11244. // NOTE: Since much of the logic of a picker class is overriding methods from the
  11245. // base class, we don't bother to split out the small remainder as a controller.
  11246. afterRender: function() {
  11247. this.callParent();
  11248. this.updateValue(this.value);
  11249. },
  11250. // override as required by parent pickerfield
  11251. createPicker: function() {
  11252. var me = this,
  11253. popup = me.getPopup(),
  11254. picker;
  11255. // the window will actually be shown and will house the picker
  11256. me.colorPickerWindow = popup = Ext.create(popup);
  11257. me.colorPicker = picker = popup.lookupReference('selector');
  11258. picker.setFormat(me.getFormat());
  11259. picker.setColor(me.getColor());
  11260. picker.setHexReadOnly(!me.editable);
  11261. picker.on({
  11262. ok: 'onColorPickerOK',
  11263. cancel: 'onColorPickerCancel',
  11264. scope: me
  11265. });
  11266. popup.on({
  11267. close: 'onColorPickerCancel',
  11268. scope: me
  11269. });
  11270. return me.colorPickerWindow;
  11271. },
  11272. // When the Ok button is clicked on color picker, preserve the previous value
  11273. onColorPickerOK: function(colorPicker) {
  11274. this.setColor(colorPicker.getColor());
  11275. this.collapse();
  11276. },
  11277. onColorPickerCancel: function() {
  11278. this.collapse();
  11279. },
  11280. onExpand: function() {
  11281. var color = this.getColor();
  11282. this.colorPicker.setPreviousColor(color);
  11283. },
  11284. onHexChange: function(field) {
  11285. if (field.validate()) {
  11286. this.setValue(field.getValue());
  11287. }
  11288. },
  11289. // Expects value formatted as per "format" config
  11290. setValue: function(color) {
  11291. var me = this;
  11292. if (Ext.ux.colorpick.ColorUtils.isValid(color)) {
  11293. color = me.applyValue(color);
  11294. me.callParent([
  11295. color
  11296. ]);
  11297. // always update in case opacity changes, even if value doesn't have it
  11298. // to handle "hex6" non-opacity type of format
  11299. me.updateValue(color);
  11300. }
  11301. },
  11302. // Sets this.format and color picker's setFormat()
  11303. updateFormat: function(format) {
  11304. var cp = this.colorPicker;
  11305. if (cp) {
  11306. cp.setFormat(format);
  11307. }
  11308. },
  11309. updateValue: function(color) {
  11310. var me = this,
  11311. c;
  11312. // If the "value" is changed, update "color" as well. Since these are always
  11313. // tracking each other, we guard against the case where we are being updated
  11314. // *because* "color" is being set.
  11315. if (!me.syncing) {
  11316. me.syncing = true;
  11317. me.setColor(color);
  11318. me.syncing = false;
  11319. }
  11320. c = me.getColor();
  11321. if (c) {
  11322. Ext.ux.colorpick.ColorUtils.setBackground(me.swatchEl, c);
  11323. if (me.colorPicker) {
  11324. me.colorPicker.setColor(c);
  11325. }
  11326. }
  11327. },
  11328. validator: function(val) {
  11329. if (!Ext.ux.colorpick.ColorUtils.isValid(val)) {
  11330. return this.invalidText;
  11331. }
  11332. return true;
  11333. }
  11334. });
  11335. /**
  11336. * Paging Memory Proxy, allows to use paging grid with in memory dataset
  11337. */
  11338. Ext.define('Ext.ux.data.PagingMemoryProxy', {
  11339. extend: 'Ext.data.proxy.Memory',
  11340. alias: 'proxy.pagingmemory',
  11341. alternateClassName: 'Ext.data.PagingMemoryProxy',
  11342. constructor: function() {
  11343. Ext.log.warn('Ext.ux.data.PagingMemoryProxy functionality has been merged into Ext.data.proxy.Memory by using the enablePaging flag.');
  11344. this.callParent(arguments);
  11345. },
  11346. read: function(operation, callback, scope) {
  11347. var reader = this.getReader(),
  11348. result = reader.read(this.data),
  11349. sorters, filters, sorterFn, records;
  11350. scope = scope || this;
  11351. // filtering
  11352. filters = operation.filters;
  11353. if (filters.length > 0) {
  11354. //at this point we have an array of Ext.util.Filter objects to filter with,
  11355. //so here we construct a function that combines these filters by ANDing them together
  11356. records = [];
  11357. Ext.each(result.records, function(record) {
  11358. var isMatch = true,
  11359. length = filters.length,
  11360. i;
  11361. for (i = 0; i < length; i++) {
  11362. var filter = filters[i],
  11363. fn = filter.filterFn,
  11364. scope = filter.scope;
  11365. isMatch = isMatch && fn.call(scope, record);
  11366. }
  11367. if (isMatch) {
  11368. records.push(record);
  11369. }
  11370. }, this);
  11371. result.records = records;
  11372. result.totalRecords = result.total = records.length;
  11373. }
  11374. // sorting
  11375. sorters = operation.sorters;
  11376. if (sorters.length > 0) {
  11377. //construct an amalgamated sorter function which combines all of the Sorters passed
  11378. sorterFn = function(r1, r2) {
  11379. var result = sorters[0].sort(r1, r2),
  11380. length = sorters.length,
  11381. i;
  11382. //if we have more than one sorter, OR any additional sorter functions together
  11383. for (i = 1; i < length; i++) {
  11384. result = result || sorters[i].sort.call(this, r1, r2);
  11385. }
  11386. return result;
  11387. };
  11388. result.records.sort(sorterFn);
  11389. }
  11390. // paging (use undefined cause start can also be 0 (thus false))
  11391. if (operation.start !== undefined && operation.limit !== undefined) {
  11392. result.records = result.records.slice(operation.start, operation.start + operation.limit);
  11393. result.count = result.records.length;
  11394. }
  11395. Ext.apply(operation, {
  11396. resultSet: result
  11397. });
  11398. operation.setCompleted();
  11399. operation.setSuccessful();
  11400. Ext.defer(function() {
  11401. Ext.callback(callback, scope, [
  11402. operation
  11403. ]);
  11404. }, 10);
  11405. }
  11406. });
  11407. /**
  11408. * This class is used as a grid `plugin`. It provides a DropZone which cooperates with
  11409. * DragZones whose dragData contains a "field" property representing a form Field.
  11410. * Fields may be dropped onto grid data cells containing a matching data type.
  11411. */
  11412. Ext.define('Ext.ux.dd.CellFieldDropZone', {
  11413. extend: 'Ext.dd.DropZone',
  11414. alias: 'plugin.ux-cellfielddropzone',
  11415. containerScroll: true,
  11416. /**
  11417. * @cfg {Function/String} onCellDrop
  11418. * The function to call on a cell data drop, or the name of the function on the
  11419. * corresponding `{@link Ext.app.ViewController controller}`. For details on the
  11420. * parameters, see `{@link #method!onCellDrop onCellDrop}`.
  11421. */
  11422. /**
  11423. * This method is called when a field is dropped on a cell. This method is normally
  11424. * replaced by the `{@link #cfg!onCellDrop onCellDrop}` config property passed to the
  11425. * constructor.
  11426. * @param {String} fieldName The name of the field.
  11427. * @param {Mixed} value The value of the field.
  11428. * @method onCellDrop
  11429. */
  11430. onCellDrop: Ext.emptyFn,
  11431. constructor: function(cfg) {
  11432. if (cfg) {
  11433. var me = this,
  11434. ddGroup = cfg.ddGroup,
  11435. onCellDrop = cfg.onCellDrop;
  11436. if (onCellDrop) {
  11437. if (typeof onCellDrop === 'string') {
  11438. me.onCellDropFn = onCellDrop;
  11439. me.onCellDrop = me.callCellDrop;
  11440. } else {
  11441. me.onCellDrop = onCellDrop;
  11442. }
  11443. }
  11444. if (ddGroup) {
  11445. me.ddGroup = ddGroup;
  11446. }
  11447. }
  11448. },
  11449. init: function(grid) {
  11450. var me = this;
  11451. // Call the DropZone constructor using the View's scrolling element
  11452. // only after the grid has been rendered.
  11453. if (grid.rendered) {
  11454. me.grid = grid;
  11455. grid.getView().on({
  11456. render: function(v) {
  11457. me.view = v;
  11458. Ext.ux.dd.CellFieldDropZone.superclass.constructor.call(me, me.view.el);
  11459. },
  11460. single: true
  11461. });
  11462. } else {
  11463. grid.on('render', me.init, me, {
  11464. single: true
  11465. });
  11466. }
  11467. },
  11468. getTargetFromEvent: function(e) {
  11469. var me = this,
  11470. v = me.view;
  11471. // Ascertain whether the mousemove is within a grid cell
  11472. var cell = e.getTarget(v.getCellSelector());
  11473. if (cell) {
  11474. // We *are* within a grid cell, so ask the View exactly which one,
  11475. // Extract data from the Model to create a target object for
  11476. // processing in subsequent onNodeXXXX methods. Note that the target does
  11477. // not have to be a DOM element. It can be whatever the noNodeXXX methods are
  11478. // programmed to expect.
  11479. var row = v.findItemByChild(cell),
  11480. columnIndex = cell.cellIndex;
  11481. if (row && Ext.isDefined(columnIndex)) {
  11482. return {
  11483. node: cell,
  11484. record: v.getRecord(row),
  11485. fieldName: me.grid.getVisibleColumnManager().getColumns()[columnIndex].dataIndex
  11486. };
  11487. }
  11488. }
  11489. },
  11490. onNodeEnter: function(target, dd, e, dragData) {
  11491. // On Node enter, see if it is valid for us to drop the field on that type of
  11492. // column.
  11493. delete this.dropOK;
  11494. if (!target) {
  11495. return;
  11496. }
  11497. // Check that a field is being dragged.
  11498. var f = dragData.field;
  11499. if (!f) {
  11500. return;
  11501. }
  11502. // Check whether the data type of the column being dropped on accepts the
  11503. // dragged field type. If so, set dropOK flag, and highlight the target node.
  11504. var field = target.record.fieldsMap[target.fieldName];
  11505. if (field.isNumeric) {
  11506. if (!f.isXType('numberfield')) {
  11507. return;
  11508. }
  11509. } else if (field.isDateField) {
  11510. if (!f.isXType('datefield')) {
  11511. return;
  11512. }
  11513. } else if (field.isBooleanField) {
  11514. if (!f.isXType('checkbox')) {
  11515. return;
  11516. }
  11517. }
  11518. this.dropOK = true;
  11519. Ext.fly(target.node).addCls('x-drop-target-active');
  11520. },
  11521. onNodeOver: function(target, dd, e, dragData) {
  11522. // Return the class name to add to the drag proxy. This provides a visual
  11523. // indication of drop allowed or not allowed.
  11524. return this.dropOK ? this.dropAllowed : this.dropNotAllowed;
  11525. },
  11526. onNodeOut: function(target, dd, e, dragData) {
  11527. Ext.fly(target.node).removeCls('x-drop-target-active');
  11528. },
  11529. onNodeDrop: function(target, dd, e, dragData) {
  11530. // Process the drop event if we have previously ascertained that a drop is OK.
  11531. if (this.dropOK) {
  11532. var value = dragData.field.getValue();
  11533. target.record.set(target.fieldName, value);
  11534. this.onCellDrop(target.fieldName, value);
  11535. return true;
  11536. }
  11537. },
  11538. callCellDrop: function(fieldName, value) {
  11539. Ext.callback(this.onCellDropFn, null, [
  11540. fieldName,
  11541. value
  11542. ], 0, this.grid);
  11543. }
  11544. });
  11545. Ext.define('Ext.ux.dd.PanelFieldDragZone', {
  11546. extend: 'Ext.dd.DragZone',
  11547. alias: 'plugin.ux-panelfielddragzone',
  11548. scroll: false,
  11549. constructor: function(cfg) {
  11550. if (cfg) {
  11551. if (cfg.ddGroup) {
  11552. this.ddGroup = cfg.ddGroup;
  11553. }
  11554. }
  11555. },
  11556. init: function(panel) {
  11557. var el;
  11558. // Call the DragZone's constructor. The Panel must have been rendered.
  11559. // Panel is an HtmlElement
  11560. if (panel.nodeType) {
  11561. // Called via dragzone::init
  11562. Ext.ux.dd.PanelFieldDragZone.superclass.init.apply(this, arguments);
  11563. } else // Panel is a Component - need the el
  11564. {
  11565. // Called via plugin::init
  11566. if (panel.rendered) {
  11567. el = panel.getEl();
  11568. el.unselectable();
  11569. Ext.ux.dd.PanelFieldDragZone.superclass.constructor.call(this, el);
  11570. } else {
  11571. panel.on('afterrender', this.init, this, {
  11572. single: true
  11573. });
  11574. }
  11575. }
  11576. },
  11577. getDragData: function(e) {
  11578. // On mousedown, we ascertain whether it is on one of our draggable Fields.
  11579. // If so, we collect data about the draggable object, and return a drag data
  11580. // object which contains our own data, plus a "ddel" property which is a DOM
  11581. // node which provides a "view" of the dragged data.
  11582. var targetLabel = e.getTarget('label', null, true),
  11583. text, oldMark, field, dragEl;
  11584. if (targetLabel) {
  11585. // Get the data we are dragging: the Field
  11586. // create a ddel for the drag proxy to display
  11587. field = Ext.getCmp(targetLabel.up('.' + Ext.form.Labelable.prototype.formItemCls).id);
  11588. // Temporary prevent marking the field as invalid, since it causes changes
  11589. // to the underlying dom element which can cause problems in IE
  11590. oldMark = field.preventMark;
  11591. field.preventMark = true;
  11592. if (field.isValid()) {
  11593. field.preventMark = oldMark;
  11594. dragEl = document.createElement('div');
  11595. dragEl.className = Ext.baseCSSPrefix + 'form-text';
  11596. text = field.getRawValue();
  11597. dragEl.innerHTML = Ext.isEmpty(text) ? '&#160;' : text;
  11598. Ext.fly(dragEl).setWidth(field.getEl().getWidth());
  11599. return {
  11600. field: field,
  11601. ddel: dragEl
  11602. };
  11603. }
  11604. e.stopEvent();
  11605. field.preventMark = oldMark;
  11606. }
  11607. },
  11608. getRepairXY: function() {
  11609. // The coordinates to slide the drag proxy back to on failed drop.
  11610. return this.dragData.field.getEl().getXY();
  11611. }
  11612. });
  11613. /*!
  11614. * Ext JS Library
  11615. * Copyright(c) 2006-2014 Sencha Inc.
  11616. * licensing@sencha.com
  11617. * http://www.sencha.com/license
  11618. */
  11619. /**
  11620. * @class Ext.ux.desktop.Desktop
  11621. * @extends Ext.panel.Panel
  11622. * <p>This class manages the wallpaper, shortcuts and taskbar.</p>
  11623. */
  11624. Ext.define('Ext.ux.desktop.Desktop', {
  11625. extend: 'Ext.panel.Panel',
  11626. alias: 'widget.desktop',
  11627. uses: [
  11628. 'Ext.util.MixedCollection',
  11629. 'Ext.menu.Menu',
  11630. 'Ext.view.View',
  11631. // dataview
  11632. 'Ext.window.Window',
  11633. 'Ext.ux.desktop.TaskBar',
  11634. 'Ext.ux.desktop.Wallpaper'
  11635. ],
  11636. activeWindowCls: 'ux-desktop-active-win',
  11637. inactiveWindowCls: 'ux-desktop-inactive-win',
  11638. lastActiveWindow: null,
  11639. border: false,
  11640. html: '&#160;',
  11641. layout: 'fit',
  11642. xTickSize: 1,
  11643. yTickSize: 1,
  11644. app: null,
  11645. /**
  11646. * @cfg {Array/Ext.data.Store} shortcuts
  11647. * The items to add to the DataView. This can be a {@link Ext.data.Store Store} or a
  11648. * simple array. Items should minimally provide the fields in the
  11649. * {@link Ext.ux.desktop.ShortcutModel Shortcut}.
  11650. */
  11651. shortcuts: null,
  11652. /**
  11653. * @cfg {String} shortcutItemSelector
  11654. * This property is passed to the DataView for the desktop to select shortcut items.
  11655. * If the {@link #shortcutTpl} is modified, this will probably need to be modified as
  11656. * well.
  11657. */
  11658. shortcutItemSelector: 'div.ux-desktop-shortcut',
  11659. /**
  11660. * @cfg {String} shortcutTpl
  11661. * This XTemplate is used to render items in the DataView. If this is changed, the
  11662. * {@link #shortcutItemSelector} will probably also need to changed.
  11663. */
  11664. shortcutTpl: [
  11665. '<tpl for=".">',
  11666. '<div class="ux-desktop-shortcut" id="{name}-shortcut">',
  11667. '<div class="ux-desktop-shortcut-icon {iconCls}">',
  11668. '<img src="',
  11669. Ext.BLANK_IMAGE_URL,
  11670. '" title="{name}">',
  11671. '</div>',
  11672. '<span class="ux-desktop-shortcut-text">{name}</span>',
  11673. '</div>',
  11674. '</tpl>',
  11675. '<div class="x-clear"></div>'
  11676. ],
  11677. /**
  11678. * @cfg {Object} taskbarConfig
  11679. * The config object for the TaskBar.
  11680. */
  11681. taskbarConfig: null,
  11682. windowMenu: null,
  11683. initComponent: function() {
  11684. var me = this;
  11685. me.windowMenu = new Ext.menu.Menu(me.createWindowMenu());
  11686. me.bbar = me.taskbar = new Ext.ux.desktop.TaskBar(me.taskbarConfig);
  11687. me.taskbar.windowMenu = me.windowMenu;
  11688. me.windows = new Ext.util.MixedCollection();
  11689. me.contextMenu = new Ext.menu.Menu(me.createDesktopMenu());
  11690. me.items = [
  11691. {
  11692. xtype: 'wallpaper',
  11693. id: me.id + '_wallpaper'
  11694. },
  11695. me.createDataView()
  11696. ];
  11697. me.callParent();
  11698. me.shortcutsView = me.items.getAt(1);
  11699. me.shortcutsView.on('itemclick', me.onShortcutItemClick, me);
  11700. var wallpaper = me.wallpaper;
  11701. me.wallpaper = me.items.getAt(0);
  11702. if (wallpaper) {
  11703. me.setWallpaper(wallpaper, me.wallpaperStretch);
  11704. }
  11705. },
  11706. afterRender: function() {
  11707. var me = this;
  11708. me.callParent();
  11709. me.el.on('contextmenu', me.onDesktopMenu, me);
  11710. },
  11711. //------------------------------------------------------
  11712. // Overrideable configuration creation methods
  11713. createDataView: function() {
  11714. var me = this;
  11715. return {
  11716. xtype: 'dataview',
  11717. overItemCls: 'x-view-over',
  11718. trackOver: true,
  11719. itemSelector: me.shortcutItemSelector,
  11720. store: me.shortcuts,
  11721. style: {
  11722. position: 'absolute'
  11723. },
  11724. x: 0,
  11725. y: 0,
  11726. tpl: new Ext.XTemplate(me.shortcutTpl)
  11727. };
  11728. },
  11729. createDesktopMenu: function() {
  11730. var me = this,
  11731. ret = {
  11732. items: me.contextMenuItems || []
  11733. };
  11734. if (ret.items.length) {
  11735. ret.items.push('-');
  11736. }
  11737. ret.items.push({
  11738. text: 'Tile',
  11739. handler: me.tileWindows,
  11740. scope: me,
  11741. minWindows: 1
  11742. }, {
  11743. text: 'Cascade',
  11744. handler: me.cascadeWindows,
  11745. scope: me,
  11746. minWindows: 1
  11747. });
  11748. return ret;
  11749. },
  11750. createWindowMenu: function() {
  11751. var me = this;
  11752. return {
  11753. defaultAlign: 'br-tr',
  11754. items: [
  11755. {
  11756. text: 'Restore',
  11757. handler: me.onWindowMenuRestore,
  11758. scope: me
  11759. },
  11760. {
  11761. text: 'Minimize',
  11762. handler: me.onWindowMenuMinimize,
  11763. scope: me
  11764. },
  11765. {
  11766. text: 'Maximize',
  11767. handler: me.onWindowMenuMaximize,
  11768. scope: me
  11769. },
  11770. '-',
  11771. {
  11772. text: 'Close',
  11773. handler: me.onWindowMenuClose,
  11774. scope: me
  11775. }
  11776. ],
  11777. listeners: {
  11778. beforeshow: me.onWindowMenuBeforeShow,
  11779. hide: me.onWindowMenuHide,
  11780. scope: me
  11781. }
  11782. };
  11783. },
  11784. //------------------------------------------------------
  11785. // Event handler methods
  11786. onDesktopMenu: function(e) {
  11787. var me = this,
  11788. menu = me.contextMenu;
  11789. e.stopEvent();
  11790. if (!menu.rendered) {
  11791. menu.on('beforeshow', me.onDesktopMenuBeforeShow, me);
  11792. }
  11793. menu.showAt(e.getXY());
  11794. menu.doConstrain();
  11795. },
  11796. onDesktopMenuBeforeShow: function(menu) {
  11797. var me = this,
  11798. count = me.windows.getCount();
  11799. menu.items.each(function(item) {
  11800. var min = item.minWindows || 0;
  11801. item.setDisabled(count < min);
  11802. });
  11803. },
  11804. onShortcutItemClick: function(dataView, record) {
  11805. var me = this,
  11806. module = me.app.getModule(record.data.module),
  11807. win = module && module.createWindow();
  11808. if (win) {
  11809. me.restoreWindow(win);
  11810. }
  11811. },
  11812. onWindowClose: function(win) {
  11813. var me = this;
  11814. me.windows.remove(win);
  11815. me.taskbar.removeTaskButton(win.taskButton);
  11816. me.updateActiveWindow();
  11817. },
  11818. //------------------------------------------------------
  11819. // Window context menu handlers
  11820. onWindowMenuBeforeShow: function(menu) {
  11821. var items = menu.items.items,
  11822. win = menu.theWin;
  11823. items[0].setDisabled(win.maximized !== true && win.hidden !== true);
  11824. // Restore
  11825. items[1].setDisabled(win.minimized === true);
  11826. // Minimize
  11827. items[2].setDisabled(win.maximized === true || win.hidden === true);
  11828. },
  11829. // Maximize
  11830. onWindowMenuClose: function() {
  11831. var me = this,
  11832. win = me.windowMenu.theWin;
  11833. win.close();
  11834. },
  11835. onWindowMenuHide: function(menu) {
  11836. Ext.defer(function() {
  11837. menu.theWin = null;
  11838. }, 1);
  11839. },
  11840. onWindowMenuMaximize: function() {
  11841. var me = this,
  11842. win = me.windowMenu.theWin;
  11843. win.maximize();
  11844. win.toFront();
  11845. },
  11846. onWindowMenuMinimize: function() {
  11847. var me = this,
  11848. win = me.windowMenu.theWin;
  11849. win.minimize();
  11850. },
  11851. onWindowMenuRestore: function() {
  11852. var me = this,
  11853. win = me.windowMenu.theWin;
  11854. me.restoreWindow(win);
  11855. },
  11856. //------------------------------------------------------
  11857. // Dynamic (re)configuration methods
  11858. getWallpaper: function() {
  11859. return this.wallpaper.wallpaper;
  11860. },
  11861. setTickSize: function(xTickSize, yTickSize) {
  11862. var me = this,
  11863. xt = me.xTickSize = xTickSize,
  11864. yt = me.yTickSize = (arguments.length > 1) ? yTickSize : xt;
  11865. me.windows.each(function(win) {
  11866. var dd = win.dd,
  11867. resizer = win.resizer;
  11868. dd.xTickSize = xt;
  11869. dd.yTickSize = yt;
  11870. resizer.widthIncrement = xt;
  11871. resizer.heightIncrement = yt;
  11872. });
  11873. },
  11874. setWallpaper: function(wallpaper, stretch) {
  11875. this.wallpaper.setWallpaper(wallpaper, stretch);
  11876. return this;
  11877. },
  11878. //------------------------------------------------------
  11879. // Window management methods
  11880. cascadeWindows: function() {
  11881. var x = 0,
  11882. y = 0,
  11883. zmgr = this.getDesktopZIndexManager();
  11884. zmgr.eachBottomUp(function(win) {
  11885. if (win.isWindow && win.isVisible() && !win.maximized) {
  11886. win.setPosition(x, y);
  11887. x += 20;
  11888. y += 20;
  11889. }
  11890. });
  11891. },
  11892. createWindow: function(config, cls) {
  11893. var me = this,
  11894. win,
  11895. cfg = Ext.applyIf(config || {}, {
  11896. stateful: false,
  11897. isWindow: true,
  11898. constrainHeader: true,
  11899. minimizable: true,
  11900. maximizable: true
  11901. });
  11902. cls = cls || Ext.window.Window;
  11903. win = me.add(new cls(cfg));
  11904. me.windows.add(win);
  11905. win.taskButton = me.taskbar.addTaskButton(win);
  11906. win.animateTarget = win.taskButton.el;
  11907. win.on({
  11908. activate: me.updateActiveWindow,
  11909. beforeshow: me.updateActiveWindow,
  11910. deactivate: me.updateActiveWindow,
  11911. minimize: me.minimizeWindow,
  11912. destroy: me.onWindowClose,
  11913. scope: me
  11914. });
  11915. win.on({
  11916. boxready: function() {
  11917. win.dd.xTickSize = me.xTickSize;
  11918. win.dd.yTickSize = me.yTickSize;
  11919. if (win.resizer) {
  11920. win.resizer.widthIncrement = me.xTickSize;
  11921. win.resizer.heightIncrement = me.yTickSize;
  11922. }
  11923. },
  11924. single: true
  11925. });
  11926. // replace normal window close w/fadeOut animation:
  11927. win.doClose = function() {
  11928. win.doClose = Ext.emptyFn;
  11929. // dblclick can call again...
  11930. win.el.disableShadow();
  11931. win.el.fadeOut({
  11932. listeners: {
  11933. afteranimate: function() {
  11934. win.destroy();
  11935. }
  11936. }
  11937. });
  11938. };
  11939. return win;
  11940. },
  11941. getActiveWindow: function() {
  11942. var win = null,
  11943. zmgr = this.getDesktopZIndexManager();
  11944. if (zmgr) {
  11945. // We cannot rely on activate/deactive because that fires against non-Window
  11946. // components in the stack.
  11947. zmgr.eachTopDown(function(comp) {
  11948. if (comp.isWindow && !comp.hidden) {
  11949. win = comp;
  11950. return false;
  11951. }
  11952. return true;
  11953. });
  11954. }
  11955. return win;
  11956. },
  11957. getDesktopZIndexManager: function() {
  11958. var windows = this.windows;
  11959. // TODO - there has to be a better way to get this...
  11960. return (windows.getCount() && windows.getAt(0).zIndexManager) || null;
  11961. },
  11962. getWindow: function(id) {
  11963. return this.windows.get(id);
  11964. },
  11965. minimizeWindow: function(win) {
  11966. win.minimized = true;
  11967. win.hide();
  11968. },
  11969. restoreWindow: function(win) {
  11970. if (win.isVisible()) {
  11971. win.restore();
  11972. win.toFront();
  11973. } else {
  11974. win.show();
  11975. }
  11976. return win;
  11977. },
  11978. tileWindows: function() {
  11979. var me = this,
  11980. availWidth = me.body.getWidth(true);
  11981. var x = me.xTickSize,
  11982. y = me.yTickSize,
  11983. nextY = y;
  11984. me.windows.each(function(win) {
  11985. if (win.isVisible() && !win.maximized) {
  11986. var w = win.el.getWidth();
  11987. // Wrap to next row if we are not at the line start and this Window will
  11988. // go off the end
  11989. if (x > me.xTickSize && x + w > availWidth) {
  11990. x = me.xTickSize;
  11991. y = nextY;
  11992. }
  11993. win.setPosition(x, y);
  11994. x += w + me.xTickSize;
  11995. nextY = Math.max(nextY, y + win.el.getHeight() + me.yTickSize);
  11996. }
  11997. });
  11998. },
  11999. updateActiveWindow: function() {
  12000. var me = this,
  12001. activeWindow = me.getActiveWindow(),
  12002. last = me.lastActiveWindow;
  12003. if (last && last.destroyed) {
  12004. me.lastActiveWindow = null;
  12005. return;
  12006. }
  12007. if (activeWindow === last) {
  12008. return;
  12009. }
  12010. if (last) {
  12011. if (last.el.dom) {
  12012. last.addCls(me.inactiveWindowCls);
  12013. last.removeCls(me.activeWindowCls);
  12014. }
  12015. last.active = false;
  12016. }
  12017. me.lastActiveWindow = activeWindow;
  12018. if (activeWindow) {
  12019. activeWindow.addCls(me.activeWindowCls);
  12020. activeWindow.removeCls(me.inactiveWindowCls);
  12021. activeWindow.minimized = false;
  12022. activeWindow.active = true;
  12023. }
  12024. me.taskbar.setActiveButton(activeWindow && activeWindow.taskButton);
  12025. }
  12026. });
  12027. /**
  12028. * Ext JS Library
  12029. * Copyright(c) 2006-2014 Sencha Inc.
  12030. * licensing@sencha.com
  12031. * http://www.sencha.com/license
  12032. * @class Ext.ux.desktop.App
  12033. */
  12034. Ext.define('Ext.ux.desktop.App', {
  12035. mixins: {
  12036. observable: 'Ext.util.Observable'
  12037. },
  12038. requires: [
  12039. 'Ext.container.Viewport',
  12040. 'Ext.ux.desktop.Desktop'
  12041. ],
  12042. isReady: false,
  12043. modules: null,
  12044. useQuickTips: true,
  12045. constructor: function(config) {
  12046. var me = this;
  12047. me.mixins.observable.constructor.call(this, config);
  12048. if (Ext.isReady) {
  12049. Ext.defer(me.init, 10, me);
  12050. } else {
  12051. Ext.onReady(me.init, me);
  12052. }
  12053. },
  12054. init: function() {
  12055. var me = this,
  12056. desktopCfg;
  12057. if (me.useQuickTips) {
  12058. Ext.QuickTips.init();
  12059. }
  12060. me.modules = me.getModules();
  12061. if (me.modules) {
  12062. me.initModules(me.modules);
  12063. }
  12064. desktopCfg = me.getDesktopConfig();
  12065. me.desktop = new Ext.ux.desktop.Desktop(desktopCfg);
  12066. me.viewport = new Ext.container.Viewport({
  12067. layout: 'fit',
  12068. items: [
  12069. me.desktop
  12070. ]
  12071. });
  12072. Ext.getWin().on('beforeunload', me.onUnload, me);
  12073. me.isReady = true;
  12074. me.fireEvent('ready', me);
  12075. },
  12076. /**
  12077. * This method returns the configuration object for the Desktop object. A derived
  12078. * class can override this method, call the base version to build the config and
  12079. * then modify the returned object before returning it.
  12080. */
  12081. getDesktopConfig: function() {
  12082. var me = this,
  12083. cfg = {
  12084. app: me,
  12085. taskbarConfig: me.getTaskbarConfig()
  12086. };
  12087. Ext.apply(cfg, me.desktopConfig);
  12088. return cfg;
  12089. },
  12090. getModules: Ext.emptyFn,
  12091. /**
  12092. * This method returns the configuration object for the Start Button. A derived
  12093. * class can override this method, call the base version to build the config and
  12094. * then modify the returned object before returning it.
  12095. */
  12096. getStartConfig: function() {
  12097. var me = this,
  12098. cfg = {
  12099. app: me,
  12100. menu: []
  12101. },
  12102. launcher;
  12103. Ext.apply(cfg, me.startConfig);
  12104. Ext.each(me.modules, function(module) {
  12105. launcher = module.launcher;
  12106. if (launcher) {
  12107. launcher.handler = launcher.handler || Ext.bind(me.createWindow, me, [
  12108. module
  12109. ]);
  12110. cfg.menu.push(module.launcher);
  12111. }
  12112. });
  12113. return cfg;
  12114. },
  12115. createWindow: function(module) {
  12116. var window = module.createWindow();
  12117. window.show();
  12118. },
  12119. /**
  12120. * This method returns the configuration object for the TaskBar. A derived class
  12121. * can override this method, call the base version to build the config and then
  12122. * modify the returned object before returning it.
  12123. */
  12124. getTaskbarConfig: function() {
  12125. var me = this,
  12126. cfg = {
  12127. app: me,
  12128. startConfig: me.getStartConfig()
  12129. };
  12130. Ext.apply(cfg, me.taskbarConfig);
  12131. return cfg;
  12132. },
  12133. initModules: function(modules) {
  12134. var me = this;
  12135. Ext.each(modules, function(module) {
  12136. module.app = me;
  12137. });
  12138. },
  12139. getModule: function(name) {
  12140. var ms = this.modules;
  12141. for (var i = 0,
  12142. len = ms.length; i < len; i++) {
  12143. var m = ms[i];
  12144. if (m.id == name || m.appType == name) {
  12145. return m;
  12146. }
  12147. }
  12148. return null;
  12149. },
  12150. onReady: function(fn, scope) {
  12151. if (this.isReady) {
  12152. fn.call(scope, this);
  12153. } else {
  12154. this.on({
  12155. ready: fn,
  12156. scope: scope,
  12157. single: true
  12158. });
  12159. }
  12160. },
  12161. getDesktop: function() {
  12162. return this.desktop;
  12163. },
  12164. onUnload: function(e) {
  12165. if (this.fireEvent('beforeunload', this) === false) {
  12166. e.stopEvent();
  12167. }
  12168. }
  12169. });
  12170. /*!
  12171. * Ext JS Library
  12172. * Copyright(c) 2006-2014 Sencha Inc.
  12173. * licensing@sencha.com
  12174. * http://www.sencha.com/license
  12175. */
  12176. Ext.define('Ext.ux.desktop.Module', {
  12177. mixins: {
  12178. observable: 'Ext.util.Observable'
  12179. },
  12180. constructor: function(config) {
  12181. this.mixins.observable.constructor.call(this, config);
  12182. this.init();
  12183. },
  12184. init: Ext.emptyFn
  12185. });
  12186. /*!
  12187. * Ext JS Library
  12188. * Copyright(c) 2006-2014 Sencha Inc.
  12189. * licensing@sencha.com
  12190. * http://www.sencha.com/license
  12191. */
  12192. /**
  12193. * @class Ext.ux.desktop.ShortcutModel
  12194. * @extends Ext.data.Model
  12195. * This model defines the minimal set of fields for desktop shortcuts.
  12196. */
  12197. Ext.define('Ext.ux.desktop.ShortcutModel', {
  12198. extend: 'Ext.data.Model',
  12199. fields: [
  12200. {
  12201. name: 'name',
  12202. convert: Ext.String.createVarName
  12203. },
  12204. {
  12205. name: 'iconCls'
  12206. },
  12207. {
  12208. name: 'module'
  12209. }
  12210. ]
  12211. });
  12212. /**
  12213. * Ext JS Library
  12214. * Copyright(c) 2006-2014 Sencha Inc.
  12215. * licensing@sencha.com
  12216. * http://www.sencha.com/license
  12217. * @class Ext.ux.desktop.StartMenu
  12218. */
  12219. Ext.define('Ext.ux.desktop.StartMenu', {
  12220. extend: 'Ext.menu.Menu',
  12221. // We want header styling like a Panel
  12222. baseCls: Ext.baseCSSPrefix + 'panel',
  12223. // Special styling within
  12224. cls: 'x-menu ux-start-menu',
  12225. bodyCls: 'ux-start-menu-body',
  12226. defaultAlign: 'bl-tl',
  12227. iconCls: 'user',
  12228. bodyBorder: true,
  12229. width: 300,
  12230. initComponent: function() {
  12231. var me = this;
  12232. me.layout.align = 'stretch';
  12233. me.items = me.menu;
  12234. me.callParent();
  12235. me.toolbar = new Ext.toolbar.Toolbar(Ext.apply({
  12236. dock: 'right',
  12237. cls: 'ux-start-menu-toolbar',
  12238. vertical: true,
  12239. width: 100,
  12240. layout: {
  12241. align: 'stretch'
  12242. }
  12243. }, me.toolConfig));
  12244. me.addDocked(me.toolbar);
  12245. delete me.toolItems;
  12246. },
  12247. addMenuItem: function() {
  12248. var cmp = this.menu;
  12249. cmp.add.apply(cmp, arguments);
  12250. },
  12251. addToolItem: function() {
  12252. var cmp = this.toolbar;
  12253. cmp.add.apply(cmp, arguments);
  12254. }
  12255. });
  12256. // StartMenu
  12257. /*!
  12258. * Ext JS Library
  12259. * Copyright(c) 2006-2014 Sencha Inc.
  12260. * licensing@sencha.com
  12261. * http://www.sencha.com/license
  12262. */
  12263. /**
  12264. * @class Ext.ux.desktop.TaskBar
  12265. * @extends Ext.toolbar.Toolbar
  12266. */
  12267. Ext.define('Ext.ux.desktop.TaskBar', {
  12268. // This must be a toolbar. we rely on acquired toolbar classes and inherited toolbar methods for our
  12269. // child items to instantiate and render correctly.
  12270. extend: 'Ext.toolbar.Toolbar',
  12271. requires: [
  12272. 'Ext.button.Button',
  12273. 'Ext.resizer.Splitter',
  12274. 'Ext.menu.Menu',
  12275. 'Ext.ux.desktop.StartMenu'
  12276. ],
  12277. alias: 'widget.taskbar',
  12278. cls: 'ux-taskbar',
  12279. /**
  12280. * @cfg {String} startBtnText
  12281. * The text for the Start Button.
  12282. */
  12283. startBtnText: 'Start',
  12284. initComponent: function() {
  12285. var me = this;
  12286. me.startMenu = new Ext.ux.desktop.StartMenu(me.startConfig);
  12287. me.quickStart = new Ext.toolbar.Toolbar(me.getQuickStart());
  12288. me.windowBar = new Ext.toolbar.Toolbar(me.getWindowBarConfig());
  12289. me.tray = new Ext.toolbar.Toolbar(me.getTrayConfig());
  12290. me.items = [
  12291. {
  12292. xtype: 'button',
  12293. cls: 'ux-start-button',
  12294. iconCls: 'ux-start-button-icon',
  12295. menu: me.startMenu,
  12296. menuAlign: 'bl-tl',
  12297. text: me.startBtnText
  12298. },
  12299. me.quickStart,
  12300. {
  12301. xtype: 'splitter',
  12302. html: '&#160;',
  12303. height: 14,
  12304. width: 2,
  12305. // TODO - there should be a CSS way here
  12306. cls: 'x-toolbar-separator x-toolbar-separator-horizontal'
  12307. },
  12308. me.windowBar,
  12309. '-',
  12310. me.tray
  12311. ];
  12312. me.callParent();
  12313. },
  12314. afterLayout: function() {
  12315. var me = this;
  12316. me.callParent();
  12317. me.windowBar.el.on('contextmenu', me.onButtonContextMenu, me);
  12318. },
  12319. /**
  12320. * This method returns the configuration object for the Quick Start toolbar. A derived
  12321. * class can override this method, call the base version to build the config and
  12322. * then modify the returned object before returning it.
  12323. */
  12324. getQuickStart: function() {
  12325. var me = this,
  12326. ret = {
  12327. minWidth: 20,
  12328. width: Ext.themeName === 'neptune' ? 70 : 60,
  12329. items: [],
  12330. enableOverflow: true
  12331. };
  12332. Ext.each(this.quickStart, function(item) {
  12333. ret.items.push({
  12334. tooltip: {
  12335. text: item.name,
  12336. align: 'bl-tl'
  12337. },
  12338. //tooltip: item.name,
  12339. overflowText: item.name,
  12340. iconCls: item.iconCls,
  12341. module: item.module,
  12342. handler: me.onQuickStartClick,
  12343. scope: me
  12344. });
  12345. });
  12346. return ret;
  12347. },
  12348. /**
  12349. * This method returns the configuration object for the Tray toolbar. A derived
  12350. * class can override this method, call the base version to build the config and
  12351. * then modify the returned object before returning it.
  12352. */
  12353. getTrayConfig: function() {
  12354. var ret = {
  12355. items: this.trayItems
  12356. };
  12357. delete this.trayItems;
  12358. return ret;
  12359. },
  12360. getWindowBarConfig: function() {
  12361. return {
  12362. flex: 1,
  12363. cls: 'ux-desktop-windowbar',
  12364. items: [
  12365. '&#160;'
  12366. ],
  12367. layout: {
  12368. overflowHandler: 'Scroller'
  12369. }
  12370. };
  12371. },
  12372. getWindowBtnFromEl: function(el) {
  12373. var c = this.windowBar.getChildByElement(el);
  12374. return c || null;
  12375. },
  12376. onQuickStartClick: function(btn) {
  12377. var module = this.app.getModule(btn.module),
  12378. window;
  12379. if (module) {
  12380. window = module.createWindow();
  12381. window.show();
  12382. }
  12383. },
  12384. onButtonContextMenu: function(e) {
  12385. var me = this,
  12386. t = e.getTarget(),
  12387. btn = me.getWindowBtnFromEl(t);
  12388. if (btn) {
  12389. e.stopEvent();
  12390. me.windowMenu.theWin = btn.win;
  12391. me.windowMenu.showBy(t);
  12392. }
  12393. },
  12394. onWindowBtnClick: function(btn) {
  12395. var win = btn.win;
  12396. if (win.minimized || win.hidden) {
  12397. btn.disable();
  12398. win.show(null, function() {
  12399. btn.enable();
  12400. });
  12401. } else if (win.active) {
  12402. btn.disable();
  12403. win.on('hide', function() {
  12404. btn.enable();
  12405. }, null, {
  12406. single: true
  12407. });
  12408. win.minimize();
  12409. } else {
  12410. win.toFront();
  12411. }
  12412. },
  12413. addTaskButton: function(win) {
  12414. var config = {
  12415. iconCls: win.iconCls,
  12416. enableToggle: true,
  12417. toggleGroup: 'all',
  12418. width: 140,
  12419. margin: '0 2 0 3',
  12420. text: Ext.util.Format.ellipsis(win.title, 20),
  12421. listeners: {
  12422. click: this.onWindowBtnClick,
  12423. scope: this
  12424. },
  12425. win: win
  12426. };
  12427. var cmp = this.windowBar.add(config);
  12428. cmp.toggle(true);
  12429. return cmp;
  12430. },
  12431. removeTaskButton: function(btn) {
  12432. var found,
  12433. me = this;
  12434. me.windowBar.items.each(function(item) {
  12435. if (item === btn) {
  12436. found = item;
  12437. }
  12438. return !found;
  12439. });
  12440. if (found) {
  12441. me.windowBar.remove(found);
  12442. }
  12443. return found;
  12444. },
  12445. setActiveButton: function(btn) {
  12446. if (btn) {
  12447. btn.toggle(true);
  12448. } else {
  12449. this.windowBar.items.each(function(item) {
  12450. if (item.isButton) {
  12451. item.toggle(false);
  12452. }
  12453. });
  12454. }
  12455. }
  12456. });
  12457. /**
  12458. * @class Ext.ux.desktop.TrayClock
  12459. * @extends Ext.toolbar.TextItem
  12460. * This class displays a clock on the toolbar.
  12461. */
  12462. Ext.define('Ext.ux.desktop.TrayClock', {
  12463. extend: 'Ext.toolbar.TextItem',
  12464. alias: 'widget.trayclock',
  12465. cls: 'ux-desktop-trayclock',
  12466. html: '&#160;',
  12467. timeFormat: 'g:i A',
  12468. tpl: '{time}',
  12469. initComponent: function() {
  12470. var me = this;
  12471. me.callParent();
  12472. if (typeof (me.tpl) == 'string') {
  12473. me.tpl = new Ext.XTemplate(me.tpl);
  12474. }
  12475. },
  12476. afterRender: function() {
  12477. var me = this;
  12478. Ext.defer(me.updateTime, 100, me);
  12479. me.callParent();
  12480. },
  12481. doDestroy: function() {
  12482. var me = this;
  12483. if (me.timer) {
  12484. window.clearTimeout(me.timer);
  12485. me.timer = null;
  12486. }
  12487. me.callParent();
  12488. },
  12489. updateTime: function() {
  12490. var me = this,
  12491. time = Ext.Date.format(new Date(), me.timeFormat),
  12492. text = me.tpl.apply({
  12493. time: time
  12494. });
  12495. if (me.lastText != text) {
  12496. me.setText(text);
  12497. me.lastText = text;
  12498. }
  12499. me.timer = Ext.defer(me.updateTime, 10000, me);
  12500. }
  12501. });
  12502. /*!
  12503. * Ext JS Library
  12504. * Copyright(c) 2006-2015 Sencha Inc.
  12505. * licensing@sencha.com
  12506. * http://www.sencha.com/license
  12507. */
  12508. /**
  12509. * From code originally written by David Davis
  12510. *
  12511. * For HTML5 video to work, your server must
  12512. * send the right content type, for more info see:
  12513. * <http://developer.mozilla.org/En/HTML/Element/Video>
  12514. */
  12515. Ext.define('Ext.ux.desktop.Video', {
  12516. extend: 'Ext.panel.Panel',
  12517. alias: 'widget.video',
  12518. layout: 'fit',
  12519. autoplay: false,
  12520. controls: true,
  12521. bodyStyle: 'background-color:#000;color:#fff',
  12522. html: '',
  12523. tpl: [
  12524. '<video id="{id}-video" autoPlay="{autoplay}" controls="{controls}" poster="{poster}" start="{start}" loopstart="{loopstart}" loopend="{loopend}" autobuffer="{autobuffer}" loop="{loop}" style="width:100%;height:100%">',
  12525. '<tpl for="src">',
  12526. '<source src="{src}" type="{type}"/>',
  12527. '</tpl>',
  12528. '{html}',
  12529. '</video>'
  12530. ],
  12531. initComponent: function() {
  12532. var me = this,
  12533. fallback, size, cfg, el;
  12534. if (me.fallbackHTML) {
  12535. fallback = me.fallbackHTML;
  12536. } else {
  12537. fallback = "Your browser does not support HTML5 Video. ";
  12538. if (Ext.isChrome) {
  12539. fallback += 'Upgrade Chrome.';
  12540. } else if (Ext.isGecko) {
  12541. fallback += 'Upgrade to Firefox 3.5 or newer.';
  12542. } else {
  12543. var chrome = '<a href="http://www.google.com/chrome">Chrome</a>';
  12544. fallback += 'Please try <a href="http://www.mozilla.com">Firefox</a>';
  12545. if (Ext.isIE) {
  12546. fallback += ', ' + chrome + ' or <a href="http://www.apple.com/safari/">Safari</a>.';
  12547. } else {
  12548. fallback += ' or ' + chrome + '.';
  12549. }
  12550. }
  12551. }
  12552. me.fallbackHTML = fallback;
  12553. cfg = me.data = Ext.copyTo({
  12554. tag: 'video',
  12555. html: fallback
  12556. }, me, 'id,poster,start,loopstart,loopend,playcount,autobuffer,loop');
  12557. // just having the params exist enables them
  12558. if (me.autoplay) {
  12559. cfg.autoplay = 1;
  12560. }
  12561. if (me.controls) {
  12562. cfg.controls = 1;
  12563. }
  12564. // handle multiple sources
  12565. if (Ext.isArray(me.src)) {
  12566. cfg.src = me.src;
  12567. } else {
  12568. cfg.src = [
  12569. {
  12570. src: me.src
  12571. }
  12572. ];
  12573. }
  12574. me.callParent();
  12575. },
  12576. afterRender: function() {
  12577. var me = this;
  12578. me.callParent();
  12579. me.video = me.body.getById(me.id + '-video');
  12580. el = me.video.dom;
  12581. me.supported = (el && el.tagName.toLowerCase() == 'video');
  12582. if (me.supported) {
  12583. me.video.on('error', me.onVideoError, me);
  12584. }
  12585. },
  12586. getFallback: function() {
  12587. return '<h1 style="background-color:#ff4f4f;padding: 10px;">' + this.fallbackHTML + '</h1>';
  12588. },
  12589. onVideoError: function() {
  12590. var me = this;
  12591. me.video.remove();
  12592. me.supported = false;
  12593. me.body.createChild(me.getFallback());
  12594. },
  12595. doDestroy: function() {
  12596. var me = this;
  12597. var video = me.video;
  12598. if (me.supported && video) {
  12599. var videoDom = video.dom;
  12600. if (videoDom && videoDom.pause) {
  12601. videoDom.pause();
  12602. }
  12603. video.remove();
  12604. me.video = null;
  12605. }
  12606. me.callParent();
  12607. }
  12608. });
  12609. /*!
  12610. * Ext JS Library
  12611. * Copyright(c) 2006-2014 Sencha Inc.
  12612. * licensing@sencha.com
  12613. * http://www.sencha.com/license
  12614. */
  12615. /**
  12616. * @class Ext.ux.desktop.Wallpaper
  12617. * @extends Ext.Component
  12618. * <p>This component renders an image that stretches to fill the component.</p>
  12619. */
  12620. Ext.define('Ext.ux.desktop.Wallpaper', {
  12621. extend: 'Ext.Component',
  12622. alias: 'widget.wallpaper',
  12623. cls: 'ux-wallpaper',
  12624. html: '<img src="' + Ext.BLANK_IMAGE_URL + '">',
  12625. stretch: false,
  12626. wallpaper: null,
  12627. stateful: true,
  12628. stateId: 'desk-wallpaper',
  12629. afterRender: function() {
  12630. var me = this;
  12631. me.callParent();
  12632. me.setWallpaper(me.wallpaper, me.stretch);
  12633. },
  12634. applyState: function() {
  12635. var me = this,
  12636. old = me.wallpaper;
  12637. me.callParent(arguments);
  12638. if (old != me.wallpaper) {
  12639. me.setWallpaper(me.wallpaper);
  12640. }
  12641. },
  12642. getState: function() {
  12643. return this.wallpaper && {
  12644. wallpaper: this.wallpaper
  12645. };
  12646. },
  12647. setWallpaper: function(wallpaper, stretch) {
  12648. var me = this,
  12649. imgEl, bkgnd;
  12650. me.stretch = (stretch !== false);
  12651. me.wallpaper = wallpaper;
  12652. if (me.rendered) {
  12653. imgEl = me.el.dom.firstChild;
  12654. if (!wallpaper || wallpaper == Ext.BLANK_IMAGE_URL) {
  12655. Ext.fly(imgEl).hide();
  12656. } else if (me.stretch) {
  12657. imgEl.src = wallpaper;
  12658. me.el.removeCls('ux-wallpaper-tiled');
  12659. Ext.fly(imgEl).setStyle({
  12660. width: '100%',
  12661. height: '100%'
  12662. }).show();
  12663. } else {
  12664. Ext.fly(imgEl).hide();
  12665. bkgnd = 'url(' + wallpaper + ')';
  12666. me.el.addCls('ux-wallpaper-tiled');
  12667. }
  12668. me.el.setStyle({
  12669. backgroundImage: bkgnd || ''
  12670. });
  12671. if (me.stateful) {
  12672. me.saveState();
  12673. }
  12674. }
  12675. return me;
  12676. }
  12677. });
  12678. /**
  12679. * Recorder manager.
  12680. * Used as a bookmarklet:
  12681. *
  12682. * javascript:void(window.open("../ux/event/RecorderManager.html","recmgr"))
  12683. */
  12684. Ext.define('Ext.ux.event.RecorderManager', {
  12685. extend: 'Ext.panel.Panel',
  12686. alias: 'widget.eventrecordermanager',
  12687. uses: [
  12688. 'Ext.ux.event.Recorder',
  12689. 'Ext.ux.event.Player'
  12690. ],
  12691. layout: 'fit',
  12692. buttonAlign: 'left',
  12693. eventsToIgnore: {
  12694. mousemove: 1,
  12695. mouseover: 1,
  12696. mouseout: 1
  12697. },
  12698. bodyBorder: false,
  12699. playSpeed: 1,
  12700. initComponent: function() {
  12701. var me = this;
  12702. me.recorder = new Ext.ux.event.Recorder({
  12703. attachTo: me.attachTo,
  12704. listeners: {
  12705. add: me.updateEvents,
  12706. coalesce: me.updateEvents,
  12707. buffer: 200,
  12708. scope: me
  12709. }
  12710. });
  12711. me.recorder.eventsToRecord = Ext.apply({}, me.recorder.eventsToRecord);
  12712. function speed(text, value) {
  12713. return {
  12714. text: text,
  12715. speed: value,
  12716. group: 'speed',
  12717. checked: value == me.playSpeed,
  12718. handler: me.onPlaySpeed,
  12719. scope: me
  12720. };
  12721. }
  12722. me.tbar = [
  12723. {
  12724. text: 'Record',
  12725. xtype: 'splitbutton',
  12726. whenIdle: true,
  12727. handler: me.onRecord,
  12728. scope: me,
  12729. menu: me.makeRecordButtonMenu()
  12730. },
  12731. {
  12732. text: 'Play',
  12733. xtype: 'splitbutton',
  12734. whenIdle: true,
  12735. handler: me.onPlay,
  12736. scope: me,
  12737. menu: [
  12738. speed('Qarter Speed (0.25x)', 0.25),
  12739. speed('Half Speed (0.5x)', 0.5),
  12740. speed('3/4 Speed (0.75x)', 0.75),
  12741. '-',
  12742. speed('Recorded Speed (1x)', 1),
  12743. speed('Double Speed (2x)', 2),
  12744. speed('Quad Speed (4x)', 4),
  12745. '-',
  12746. speed('Full Speed', 1000)
  12747. ]
  12748. },
  12749. {
  12750. text: 'Clear',
  12751. whenIdle: true,
  12752. handler: me.onClear,
  12753. scope: me
  12754. },
  12755. '->',
  12756. {
  12757. text: 'Stop',
  12758. whenActive: true,
  12759. disabled: true,
  12760. handler: me.onStop,
  12761. scope: me
  12762. }
  12763. ];
  12764. var events = me.attachTo && me.attachTo.testEvents;
  12765. me.items = [
  12766. {
  12767. xtype: 'textarea',
  12768. itemId: 'eventView',
  12769. fieldStyle: 'font-family: monospace',
  12770. selectOnFocus: true,
  12771. emptyText: 'Events go here!',
  12772. value: events ? me.stringifyEvents(events) : '',
  12773. scrollToBottom: function() {
  12774. var inputEl = this.inputEl.dom;
  12775. inputEl.scrollTop = inputEl.scrollHeight;
  12776. }
  12777. }
  12778. ];
  12779. me.fbar = [
  12780. {
  12781. xtype: 'tbtext',
  12782. text: 'Attached To: ' + (me.attachTo && me.attachTo.location.href)
  12783. }
  12784. ];
  12785. me.callParent();
  12786. },
  12787. makeRecordButtonMenu: function() {
  12788. var ret = [],
  12789. subs = {},
  12790. eventsToRec = this.recorder.eventsToRecord,
  12791. ignoredEvents = this.eventsToIgnore;
  12792. Ext.Object.each(eventsToRec, function(name, value) {
  12793. var sub = subs[value.kind];
  12794. if (!sub) {
  12795. subs[value.kind] = sub = [];
  12796. ret.push({
  12797. text: value.kind,
  12798. menu: sub
  12799. });
  12800. }
  12801. sub.push({
  12802. text: name,
  12803. checked: true,
  12804. handler: function(menuItem) {
  12805. if (menuItem.checked) {
  12806. eventsToRec[name] = value;
  12807. } else {
  12808. delete eventsToRec[name];
  12809. }
  12810. }
  12811. });
  12812. if (ignoredEvents[name]) {
  12813. sub[sub.length - 1].checked = false;
  12814. Ext.defer(function() {
  12815. delete eventsToRec[name];
  12816. }, 1);
  12817. }
  12818. });
  12819. function less(lhs, rhs) {
  12820. return (lhs.text < rhs.text) ? -1 : ((rhs.text < lhs.text) ? 1 : 0);
  12821. }
  12822. ret.sort(less);
  12823. Ext.Array.each(ret, function(sub) {
  12824. sub.menu.sort(less);
  12825. });
  12826. return ret;
  12827. },
  12828. getEventView: function() {
  12829. return this.down('#eventView');
  12830. },
  12831. onClear: function() {
  12832. var view = this.getEventView();
  12833. view.setValue('');
  12834. },
  12835. onPlay: function() {
  12836. var me = this,
  12837. view = me.getEventView(),
  12838. events = view.getValue();
  12839. if (events) {
  12840. events = Ext.decode(events);
  12841. if (events.length) {
  12842. me.player = Ext.create('Ext.ux.event.Player', {
  12843. attachTo: window.opener,
  12844. eventQueue: events,
  12845. speed: me.playSpeed,
  12846. listeners: {
  12847. stop: me.onPlayStop,
  12848. scope: me
  12849. }
  12850. });
  12851. me.player.start();
  12852. me.syncBtnUI();
  12853. }
  12854. }
  12855. },
  12856. onPlayStop: function() {
  12857. this.player = null;
  12858. this.syncBtnUI();
  12859. },
  12860. onPlaySpeed: function(menuitem) {
  12861. this.playSpeed = menuitem.speed;
  12862. },
  12863. onRecord: function() {
  12864. this.recorder.start();
  12865. this.syncBtnUI();
  12866. },
  12867. onStop: function() {
  12868. var me = this;
  12869. if (me.player) {
  12870. me.player.stop();
  12871. me.player = null;
  12872. } else {
  12873. me.recorder.stop();
  12874. }
  12875. me.syncBtnUI();
  12876. me.updateEvents();
  12877. },
  12878. syncBtnUI: function() {
  12879. var me = this,
  12880. idle = !me.player && !me.recorder.active;
  12881. Ext.each(me.query('[whenIdle]'), function(btn) {
  12882. btn.setDisabled(!idle);
  12883. });
  12884. Ext.each(me.query('[whenActive]'), function(btn) {
  12885. btn.setDisabled(idle);
  12886. });
  12887. var view = me.getEventView();
  12888. view.setReadOnly(!idle);
  12889. },
  12890. stringifyEvents: function(events) {
  12891. var line,
  12892. lines = [];
  12893. Ext.each(events, function(ev) {
  12894. line = [];
  12895. Ext.Object.each(ev, function(name, value) {
  12896. if (line.length) {
  12897. line.push(', ');
  12898. } else {
  12899. line.push(' { ');
  12900. }
  12901. line.push(name, ': ');
  12902. line.push(Ext.encode(value));
  12903. });
  12904. line.push(' }');
  12905. lines.push(line.join(''));
  12906. });
  12907. return '[\n' + lines.join(',\n') + '\n]';
  12908. },
  12909. updateEvents: function() {
  12910. var me = this,
  12911. text = me.stringifyEvents(me.recorder.getRecordedEvents()),
  12912. view = me.getEventView();
  12913. view.setValue(text);
  12914. view.scrollToBottom();
  12915. }
  12916. });
  12917. /**
  12918. * A control that allows selection of multiple items in a list.
  12919. */
  12920. Ext.define('Ext.ux.form.MultiSelect', {
  12921. extend: 'Ext.form.FieldContainer',
  12922. mixins: [
  12923. 'Ext.util.StoreHolder',
  12924. 'Ext.form.field.Field'
  12925. ],
  12926. alternateClassName: 'Ext.ux.Multiselect',
  12927. alias: [
  12928. 'widget.multiselectfield',
  12929. 'widget.multiselect'
  12930. ],
  12931. requires: [
  12932. 'Ext.panel.Panel',
  12933. 'Ext.view.BoundList',
  12934. 'Ext.layout.container.Fit'
  12935. ],
  12936. uses: [
  12937. 'Ext.view.DragZone',
  12938. 'Ext.view.DropZone'
  12939. ],
  12940. layout: 'anchor',
  12941. /**
  12942. * @cfg {String} [dragGroup=""] The ddgroup name for the MultiSelect DragZone.
  12943. */
  12944. /**
  12945. * @cfg {String} [dropGroup=""] The ddgroup name for the MultiSelect DropZone.
  12946. */
  12947. /**
  12948. * @cfg {String} [title=""] A title for the underlying panel.
  12949. */
  12950. /**
  12951. * @cfg {Boolean} [ddReorder=false] Whether the items in the MultiSelect list are drag/drop reorderable.
  12952. */
  12953. ddReorder: false,
  12954. /**
  12955. * @cfg {Object/Array} tbar An optional toolbar to be inserted at the top of the control's selection list.
  12956. * This can be a {@link Ext.toolbar.Toolbar} object, a toolbar config, or an array of buttons/button configs
  12957. * to be added to the toolbar. See {@link Ext.panel.Panel#tbar}.
  12958. */
  12959. /**
  12960. * @cfg {String} [appendOnly=false] `true` if the list should only allow append drops when drag/drop is enabled.
  12961. * This is useful for lists which are sorted.
  12962. */
  12963. appendOnly: false,
  12964. /**
  12965. * @cfg {String} [displayField="text"] Name of the desired display field in the dataset.
  12966. */
  12967. displayField: 'text',
  12968. /**
  12969. * @cfg {String} [valueField="text"] Name of the desired value field in the dataset.
  12970. */
  12971. /**
  12972. * @cfg {Boolean} [allowBlank=true] `false` to require at least one item in the list to be selected, `true` to allow no
  12973. * selection.
  12974. */
  12975. allowBlank: true,
  12976. /**
  12977. * @cfg {Number} [minSelections=0] Minimum number of selections allowed.
  12978. */
  12979. minSelections: 0,
  12980. /**
  12981. * @cfg {Number} [maxSelections=Number.MAX_VALUE] Maximum number of selections allowed.
  12982. */
  12983. maxSelections: Number.MAX_VALUE,
  12984. /**
  12985. * @cfg {String} [blankText="This field is required"] Default text displayed when the control contains no items.
  12986. */
  12987. blankText: 'This field is required',
  12988. /**
  12989. * @cfg {String} [minSelectionsText="Minimum {0}item(s) required"]
  12990. * Validation message displayed when {@link #minSelections} is not met.
  12991. * The {0} token will be replaced by the value of {@link #minSelections}.
  12992. */
  12993. minSelectionsText: 'Minimum {0} item(s) required',
  12994. /**
  12995. * @cfg {String} [maxSelectionsText="Maximum {0}item(s) allowed"]
  12996. * Validation message displayed when {@link #maxSelections} is not met
  12997. * The {0} token will be replaced by the value of {@link #maxSelections}.
  12998. */
  12999. maxSelectionsText: 'Maximum {0} item(s) required',
  13000. /**
  13001. * @cfg {String} [delimiter=","] The string used to delimit the selected values when {@link #getSubmitValue submitting}
  13002. * the field as part of a form. If you wish to have the selected values submitted as separate
  13003. * parameters rather than a single delimited parameter, set this to `null`.
  13004. */
  13005. delimiter: ',',
  13006. /**
  13007. * @cfg {String} [dragText="{0} Item{1}"] The text to show while dragging items.
  13008. * {0} will be replaced by the number of items. {1} will be replaced by the plural
  13009. * form if there is more than 1 item.
  13010. */
  13011. dragText: '{0} Item{1}',
  13012. /**
  13013. * @cfg {Ext.data.Store/Array} store The data source to which this MultiSelect is bound (defaults to `undefined`).
  13014. * Acceptable values for this property are:
  13015. * <div class="mdetail-params"><ul>
  13016. * <li><b>any {@link Ext.data.Store Store} subclass</b></li>
  13017. * <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.ArrayStore} internally.
  13018. * <div class="mdetail-params"><ul>
  13019. * <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">
  13020. * A 1-dimensional array will automatically be expanded (each array item will be the combo
  13021. * {@link #valueField value} and {@link #displayField text})</div></li>
  13022. * <li><b>2-dimensional array</b> : (e.g., <tt>[['f','Foo'],['b','Bar']]</tt>)<div class="sub-desc">
  13023. * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
  13024. * {@link #valueField value}, while the value at index 1 is assumed to be the combo {@link #displayField text}.
  13025. * </div></li></ul></div></li></ul></div>
  13026. */
  13027. ignoreSelectChange: 0,
  13028. /**
  13029. * @cfg {Object} listConfig
  13030. * An optional set of configuration properties that will be passed to the {@link Ext.view.BoundList}'s constructor.
  13031. * Any configuration that is valid for BoundList can be included.
  13032. */
  13033. /**
  13034. * @cfg {Number} [pageSize=10] The number of items to advance on pageUp and pageDown
  13035. */
  13036. pageSize: 10,
  13037. initComponent: function() {
  13038. var me = this;
  13039. me.items = me.setupItems();
  13040. me.bindStore(me.store, true);
  13041. me.callParent();
  13042. me.initField();
  13043. },
  13044. setupItems: function() {
  13045. var me = this;
  13046. me.boundList = new Ext.view.BoundList(Ext.apply({
  13047. anchor: 'none 100%',
  13048. border: 1,
  13049. multiSelect: true,
  13050. store: me.store,
  13051. displayField: me.displayField,
  13052. disabled: me.disabled,
  13053. tabIndex: 0,
  13054. navigationModel: {
  13055. type: 'default'
  13056. }
  13057. }, me.listConfig));
  13058. me.boundList.getNavigationModel().addKeyBindings({
  13059. pageUp: me.onKeyPageUp,
  13060. pageDown: me.onKeyPageDown,
  13061. scope: me
  13062. });
  13063. me.boundList.getSelectionModel().on('selectionchange', me.onSelectChange, me);
  13064. // Boundlist expects a reference to its pickerField for when an item is selected (see Boundlist#onItemClick).
  13065. me.boundList.pickerField = me;
  13066. // Only need to wrap the BoundList in a Panel if we have a title.
  13067. if (!me.title) {
  13068. return me.boundList;
  13069. }
  13070. // Wrap to add a title
  13071. me.boundList.border = false;
  13072. return {
  13073. xtype: 'panel',
  13074. isAriaRegion: false,
  13075. border: true,
  13076. anchor: 'none 100%',
  13077. layout: 'anchor',
  13078. title: me.title,
  13079. tbar: me.tbar,
  13080. items: me.boundList
  13081. };
  13082. },
  13083. onSelectChange: function(selModel, selections) {
  13084. if (!this.ignoreSelectChange) {
  13085. this.setValue(selections);
  13086. }
  13087. },
  13088. getSelected: function() {
  13089. return this.boundList.getSelectionModel().getSelection();
  13090. },
  13091. // compare array values
  13092. isEqual: function(v1, v2) {
  13093. var fromArray = Ext.Array.from,
  13094. i = 0,
  13095. len;
  13096. v1 = fromArray(v1);
  13097. v2 = fromArray(v2);
  13098. len = v1.length;
  13099. if (len !== v2.length) {
  13100. return false;
  13101. }
  13102. for (; i < len; i++) {
  13103. if (v2[i] !== v1[i]) {
  13104. return false;
  13105. }
  13106. }
  13107. return true;
  13108. },
  13109. afterRender: function() {
  13110. var me = this,
  13111. boundList, scrollable, records, panel;
  13112. me.callParent();
  13113. boundList = me.boundList;
  13114. scrollable = boundList && boundList.getScrollable();
  13115. if (me.selectOnRender) {
  13116. records = me.getRecordsForValue(me.value);
  13117. if (records.length) {
  13118. ++me.ignoreSelectChange;
  13119. boundList.getSelectionModel().select(records);
  13120. --me.ignoreSelectChange;
  13121. }
  13122. delete me.toSelect;
  13123. }
  13124. if (me.ddReorder && !me.dragGroup && !me.dropGroup) {
  13125. me.dragGroup = me.dropGroup = 'MultiselectDD-' + Ext.id();
  13126. }
  13127. if (me.draggable || me.dragGroup) {
  13128. me.dragZone = Ext.create('Ext.view.DragZone', {
  13129. view: boundList,
  13130. ddGroup: me.dragGroup,
  13131. dragText: me.dragText,
  13132. containerScroll: !!scrollable,
  13133. scrollEl: scrollable && scrollable.getElement()
  13134. });
  13135. }
  13136. if (me.droppable || me.dropGroup) {
  13137. me.dropZone = Ext.create('Ext.view.DropZone', {
  13138. view: boundList,
  13139. ddGroup: me.dropGroup,
  13140. handleNodeDrop: function(data, dropRecord, position) {
  13141. var view = this.view,
  13142. store = view.getStore(),
  13143. records = data.records,
  13144. index;
  13145. // remove the Models from the source Store
  13146. data.view.store.remove(records);
  13147. index = store.indexOf(dropRecord);
  13148. if (position === 'after') {
  13149. index++;
  13150. }
  13151. store.insert(index, records);
  13152. view.getSelectionModel().select(records);
  13153. me.fireEvent('drop', me, records);
  13154. }
  13155. });
  13156. }
  13157. panel = me.down('panel');
  13158. if (panel && boundList) {
  13159. boundList.ariaEl.dom.setAttribute('aria-labelledby', panel.header.id + '-title-textEl');
  13160. }
  13161. },
  13162. onKeyPageUp: function(e) {
  13163. var me = this,
  13164. pageSize = me.pageSize,
  13165. boundList = me.boundList,
  13166. nm = boundList.getNavigationModel(),
  13167. oldIdx, newIdx;
  13168. oldIdx = nm.recordIndex;
  13169. // Unlike up arrow, pgUp does not wrap but goes to the first item
  13170. newIdx = oldIdx > pageSize ? oldIdx - pageSize : 0;
  13171. nm.setPosition(newIdx, e);
  13172. },
  13173. onKeyPageDown: function(e) {
  13174. var me = this,
  13175. pageSize = me.pageSize,
  13176. boundList = me.boundList,
  13177. nm = boundList.getNavigationModel(),
  13178. count, oldIdx, newIdx;
  13179. count = boundList.getStore().getCount();
  13180. oldIdx = nm.recordIndex;
  13181. // Unlike down arrow, pgDown does not wrap but goes to the last item
  13182. newIdx = oldIdx < (count - pageSize) ? oldIdx + pageSize : count - 1;
  13183. nm.setPosition(newIdx, e);
  13184. },
  13185. isValid: function() {
  13186. var me = this,
  13187. disabled = me.disabled,
  13188. validate = me.forceValidation || !disabled;
  13189. return validate ? me.validateValue(me.value) : disabled;
  13190. },
  13191. validateValue: function(value) {
  13192. var me = this,
  13193. errors = me.getErrors(value),
  13194. isValid = Ext.isEmpty(errors);
  13195. if (!me.preventMark) {
  13196. if (isValid) {
  13197. me.clearInvalid();
  13198. } else {
  13199. me.markInvalid(errors);
  13200. }
  13201. }
  13202. return isValid;
  13203. },
  13204. markInvalid: function(errors) {
  13205. // Save the message and fire the 'invalid' event
  13206. var me = this,
  13207. oldMsg = me.getActiveError();
  13208. me.setActiveErrors(Ext.Array.from(errors));
  13209. if (oldMsg !== me.getActiveError()) {
  13210. me.updateLayout();
  13211. }
  13212. },
  13213. /**
  13214. * Clear any invalid styles/messages for this field.
  13215. *
  13216. * __Note:__ this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`
  13217. * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow
  13218. * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
  13219. */
  13220. clearInvalid: function() {
  13221. // Clear the message and fire the 'valid' event
  13222. var me = this,
  13223. hadError = me.hasActiveError();
  13224. me.unsetActiveError();
  13225. if (hadError) {
  13226. me.updateLayout();
  13227. }
  13228. },
  13229. getSubmitData: function() {
  13230. var me = this,
  13231. data = null,
  13232. val;
  13233. if (!me.disabled && me.submitValue && !me.isFileUpload()) {
  13234. val = me.getSubmitValue();
  13235. if (val !== null) {
  13236. data = {};
  13237. data[me.getName()] = val;
  13238. }
  13239. }
  13240. return data;
  13241. },
  13242. /**
  13243. * Returns the value that would be included in a standard form submit for this field.
  13244. *
  13245. * @return {String} The value to be submitted, or `null`.
  13246. */
  13247. getSubmitValue: function() {
  13248. var me = this,
  13249. delimiter = me.delimiter,
  13250. val = me.getValue();
  13251. return Ext.isString(delimiter) ? val.join(delimiter) : val;
  13252. },
  13253. getValue: function() {
  13254. return this.value || [];
  13255. },
  13256. getRecordsForValue: function(value) {
  13257. var me = this,
  13258. records = [],
  13259. all = me.store.getRange(),
  13260. valueField = me.valueField,
  13261. i = 0,
  13262. allLen = all.length,
  13263. rec, j, valueLen;
  13264. for (valueLen = value.length; i < valueLen; ++i) {
  13265. for (j = 0; j < allLen; ++j) {
  13266. rec = all[j];
  13267. if (rec.get(valueField) == value[i]) {
  13268. records.push(rec);
  13269. }
  13270. }
  13271. }
  13272. return records;
  13273. },
  13274. setupValue: function(value) {
  13275. var delimiter = this.delimiter,
  13276. valueField = this.valueField,
  13277. i = 0,
  13278. out, len, item;
  13279. if (Ext.isDefined(value)) {
  13280. if (delimiter && Ext.isString(value)) {
  13281. value = value.split(delimiter);
  13282. } else if (!Ext.isArray(value)) {
  13283. value = [
  13284. value
  13285. ];
  13286. }
  13287. for (len = value.length; i < len; ++i) {
  13288. item = value[i];
  13289. if (item && item.isModel) {
  13290. value[i] = item.get(valueField);
  13291. }
  13292. }
  13293. out = Ext.Array.unique(value);
  13294. } else {
  13295. out = [];
  13296. }
  13297. return out;
  13298. },
  13299. setValue: function(value) {
  13300. var me = this,
  13301. selModel = me.boundList.getSelectionModel(),
  13302. store = me.store;
  13303. // Store not loaded yet - we cannot set the value
  13304. if (!store.getCount()) {
  13305. store.on({
  13306. load: Ext.Function.bind(me.setValue, me, [
  13307. value
  13308. ]),
  13309. single: true
  13310. });
  13311. return;
  13312. }
  13313. value = me.setupValue(value);
  13314. me.mixins.field.setValue.call(me, value);
  13315. if (me.rendered) {
  13316. ++me.ignoreSelectChange;
  13317. selModel.deselectAll();
  13318. if (value.length) {
  13319. selModel.select(me.getRecordsForValue(value));
  13320. }
  13321. --me.ignoreSelectChange;
  13322. } else {
  13323. me.selectOnRender = true;
  13324. }
  13325. },
  13326. clearValue: function() {
  13327. this.setValue([]);
  13328. },
  13329. onEnable: function() {
  13330. var list = this.boundList;
  13331. this.callParent();
  13332. if (list) {
  13333. list.enable();
  13334. }
  13335. },
  13336. onDisable: function() {
  13337. var list = this.boundList;
  13338. this.callParent();
  13339. if (list) {
  13340. list.disable();
  13341. }
  13342. },
  13343. getErrors: function(value) {
  13344. var me = this,
  13345. format = Ext.String.format,
  13346. errors = [],
  13347. numSelected;
  13348. value = Ext.Array.from(value || me.getValue());
  13349. numSelected = value.length;
  13350. if (!me.allowBlank && numSelected < 1) {
  13351. errors.push(me.blankText);
  13352. }
  13353. if (numSelected < me.minSelections) {
  13354. errors.push(format(me.minSelectionsText, me.minSelections));
  13355. }
  13356. if (numSelected > me.maxSelections) {
  13357. errors.push(format(me.maxSelectionsText, me.maxSelections));
  13358. }
  13359. return errors;
  13360. },
  13361. doDestroy: function() {
  13362. var me = this;
  13363. me.bindStore(null);
  13364. Ext.destroy(me.dragZone, me.dropZone, me.keyNav);
  13365. me.callParent();
  13366. },
  13367. onBindStore: function(store) {
  13368. var me = this,
  13369. boundList = this.boundList;
  13370. if (store.autoCreated) {
  13371. me.resolveDisplayField();
  13372. }
  13373. if (!Ext.isDefined(me.valueField)) {
  13374. me.valueField = me.displayField;
  13375. }
  13376. if (boundList) {
  13377. boundList.bindStore(store);
  13378. }
  13379. },
  13380. /**
  13381. * Applies auto-created store fields to field and boundlist
  13382. * @private
  13383. */
  13384. resolveDisplayField: function() {
  13385. var me = this,
  13386. boundList = me.boundList,
  13387. store = me.getStore();
  13388. me.valueField = me.displayField = 'field1';
  13389. if (!store.expanded) {
  13390. me.displayField = 'field2';
  13391. }
  13392. if (boundList) {
  13393. boundList.setDisplayField(me.displayField);
  13394. }
  13395. }
  13396. });
  13397. /*
  13398. * Note that this control will most likely remain as an example, and not as a core Ext form
  13399. * control. However, the API will be changing in a future release and so should not yet be
  13400. * treated as a final, stable API at this time.
  13401. */
  13402. /**
  13403. * A control that allows selection of between two Ext.ux.form.MultiSelect controls.
  13404. */
  13405. Ext.define('Ext.ux.form.ItemSelector', {
  13406. extend: 'Ext.ux.form.MultiSelect',
  13407. alias: [
  13408. 'widget.itemselectorfield',
  13409. 'widget.itemselector'
  13410. ],
  13411. alternateClassName: [
  13412. 'Ext.ux.ItemSelector'
  13413. ],
  13414. requires: [
  13415. 'Ext.button.Button',
  13416. 'Ext.ux.form.MultiSelect'
  13417. ],
  13418. /**
  13419. * @cfg {Boolean} [hideNavIcons=false] True to hide the navigation icons
  13420. */
  13421. hideNavIcons: false,
  13422. /**
  13423. * @cfg {Array} buttons Defines the set of buttons that should be displayed in between the ItemSelector
  13424. * fields. Defaults to <tt>['top', 'up', 'add', 'remove', 'down', 'bottom']</tt>. These names are used
  13425. * to build the button CSS class names, and to look up the button text labels in {@link #buttonsText}.
  13426. * This can be overridden with a custom Array to change which buttons are displayed or their order.
  13427. */
  13428. buttons: [
  13429. 'top',
  13430. 'up',
  13431. 'add',
  13432. 'remove',
  13433. 'down',
  13434. 'bottom'
  13435. ],
  13436. /**
  13437. * @cfg {Object} buttonsText The tooltips for the {@link #buttons}.
  13438. * Labels for buttons.
  13439. */
  13440. buttonsText: {
  13441. top: "Move to Top",
  13442. up: "Move Up",
  13443. add: "Add to Selected",
  13444. remove: "Remove from Selected",
  13445. down: "Move Down",
  13446. bottom: "Move to Bottom"
  13447. },
  13448. layout: {
  13449. type: 'hbox',
  13450. align: 'stretch'
  13451. },
  13452. ariaRole: 'group',
  13453. initComponent: function() {
  13454. var me = this;
  13455. me.ddGroup = me.id + '-dd';
  13456. me.ariaRenderAttributes = me.ariaRenderAttributes || {};
  13457. me.ariaRenderAttributes['aria-labelledby'] = me.id + '-labelEl';
  13458. me.callParent();
  13459. // bindStore must be called after the fromField has been created because
  13460. // it copies records from our configured Store into the fromField's Store
  13461. me.bindStore(me.store);
  13462. },
  13463. createList: function(title) {
  13464. var me = this;
  13465. return Ext.create('Ext.ux.form.MultiSelect', {
  13466. // We don't want the multiselects themselves to act like fields,
  13467. // so override these methods to prevent them from including
  13468. // any of their values
  13469. submitValue: false,
  13470. getSubmitData: function() {
  13471. return null;
  13472. },
  13473. getModelData: function() {
  13474. return null;
  13475. },
  13476. flex: 1,
  13477. dragGroup: me.ddGroup,
  13478. dropGroup: me.ddGroup,
  13479. title: title,
  13480. store: {
  13481. model: me.store.model,
  13482. data: []
  13483. },
  13484. displayField: me.displayField,
  13485. valueField: me.valueField,
  13486. disabled: me.disabled,
  13487. listeners: {
  13488. boundList: {
  13489. scope: me,
  13490. itemdblclick: me.onItemDblClick,
  13491. drop: me.syncValue
  13492. }
  13493. }
  13494. });
  13495. },
  13496. setupItems: function() {
  13497. var me = this;
  13498. me.fromField = me.createList(me.fromTitle);
  13499. me.toField = me.createList(me.toTitle);
  13500. return [
  13501. me.fromField,
  13502. {
  13503. xtype: 'toolbar',
  13504. margin: '0 4',
  13505. padding: 0,
  13506. layout: {
  13507. type: 'vbox',
  13508. pack: 'center'
  13509. },
  13510. items: me.createButtons()
  13511. },
  13512. me.toField
  13513. ];
  13514. },
  13515. createButtons: function() {
  13516. var me = this,
  13517. buttons = [];
  13518. if (!me.hideNavIcons) {
  13519. Ext.Array.forEach(me.buttons, function(name) {
  13520. buttons.push({
  13521. xtype: 'button',
  13522. ui: 'default',
  13523. tooltip: me.buttonsText[name],
  13524. ariaLabel: me.buttonsText[name],
  13525. handler: me['on' + Ext.String.capitalize(name) + 'BtnClick'],
  13526. cls: Ext.baseCSSPrefix + 'form-itemselector-btn',
  13527. iconCls: Ext.baseCSSPrefix + 'form-itemselector-' + name,
  13528. navBtn: true,
  13529. scope: me,
  13530. margin: '4 0 0 0'
  13531. });
  13532. });
  13533. }
  13534. return buttons;
  13535. },
  13536. /**
  13537. * Get the selected records from the specified list.
  13538. *
  13539. * Records will be returned *in store order*, not in order of selection.
  13540. * @param {Ext.view.BoundList} list The list to read selections from.
  13541. * @return {Ext.data.Model[]} The selected records in store order.
  13542. *
  13543. */
  13544. getSelections: function(list) {
  13545. var store = list.getStore();
  13546. return Ext.Array.sort(list.getSelectionModel().getSelection(), function(a, b) {
  13547. a = store.indexOf(a);
  13548. b = store.indexOf(b);
  13549. if (a < b) {
  13550. return -1;
  13551. } else if (a > b) {
  13552. return 1;
  13553. }
  13554. return 0;
  13555. });
  13556. },
  13557. onTopBtnClick: function() {
  13558. var list = this.toField.boundList,
  13559. store = list.getStore(),
  13560. selected = this.getSelections(list);
  13561. store.suspendEvents();
  13562. store.remove(selected, true);
  13563. store.insert(0, selected);
  13564. store.resumeEvents();
  13565. list.refresh();
  13566. this.syncValue();
  13567. list.getSelectionModel().select(selected);
  13568. },
  13569. onBottomBtnClick: function() {
  13570. var list = this.toField.boundList,
  13571. store = list.getStore(),
  13572. selected = this.getSelections(list);
  13573. store.suspendEvents();
  13574. store.remove(selected, true);
  13575. store.add(selected);
  13576. store.resumeEvents();
  13577. list.refresh();
  13578. this.syncValue();
  13579. list.getSelectionModel().select(selected);
  13580. },
  13581. onUpBtnClick: function() {
  13582. var list = this.toField.boundList,
  13583. store = list.getStore(),
  13584. selected = this.getSelections(list),
  13585. rec,
  13586. i = 0,
  13587. len = selected.length,
  13588. index = 0;
  13589. // Move each selection up by one place if possible
  13590. store.suspendEvents();
  13591. for (; i < len; ++i , index++) {
  13592. rec = selected[i];
  13593. index = Math.max(index, store.indexOf(rec) - 1);
  13594. store.remove(rec, true);
  13595. store.insert(index, rec);
  13596. }
  13597. store.resumeEvents();
  13598. list.refresh();
  13599. this.syncValue();
  13600. list.getSelectionModel().select(selected);
  13601. },
  13602. onDownBtnClick: function() {
  13603. var list = this.toField.boundList,
  13604. store = list.getStore(),
  13605. selected = this.getSelections(list),
  13606. rec,
  13607. i = selected.length - 1,
  13608. index = store.getCount() - 1;
  13609. // Move each selection down by one place if possible
  13610. store.suspendEvents();
  13611. for (; i > -1; --i , index--) {
  13612. rec = selected[i];
  13613. index = Math.min(index, store.indexOf(rec) + 1);
  13614. store.remove(rec, true);
  13615. store.insert(index, rec);
  13616. }
  13617. store.resumeEvents();
  13618. list.refresh();
  13619. this.syncValue();
  13620. list.getSelectionModel().select(selected);
  13621. },
  13622. onAddBtnClick: function() {
  13623. var me = this,
  13624. selected = me.getSelections(me.fromField.boundList);
  13625. me.moveRec(true, selected);
  13626. me.toField.boundList.getSelectionModel().select(selected);
  13627. },
  13628. onRemoveBtnClick: function() {
  13629. var me = this,
  13630. selected = me.getSelections(me.toField.boundList);
  13631. me.moveRec(false, selected);
  13632. me.fromField.boundList.getSelectionModel().select(selected);
  13633. },
  13634. moveRec: function(add, recs) {
  13635. var me = this,
  13636. fromField = me.fromField,
  13637. toField = me.toField,
  13638. fromStore = add ? fromField.store : toField.store,
  13639. toStore = add ? toField.store : fromField.store;
  13640. fromStore.suspendEvents();
  13641. toStore.suspendEvents();
  13642. fromStore.remove(recs);
  13643. toStore.add(recs);
  13644. fromStore.resumeEvents();
  13645. toStore.resumeEvents();
  13646. // If the list item was focused when moved (e.g. via double-click)
  13647. // then removing it will cause the focus to be thrown back to the
  13648. // document body. Which might disrupt things if ItemSelector is
  13649. // contained by a floating thingie like a Menu.
  13650. // Focusing the list itself will prevent that.
  13651. if (fromField.boundList.containsFocus) {
  13652. fromField.boundList.focus();
  13653. }
  13654. fromField.boundList.refresh();
  13655. toField.boundList.refresh();
  13656. me.syncValue();
  13657. },
  13658. // Synchronizes the submit value with the current state of the toStore
  13659. syncValue: function() {
  13660. var me = this;
  13661. me.mixins.field.setValue.call(me, me.setupValue(me.toField.store.getRange()));
  13662. },
  13663. onItemDblClick: function(view, rec) {
  13664. this.moveRec(view === this.fromField.boundList, rec);
  13665. },
  13666. setValue: function(value) {
  13667. var me = this,
  13668. fromField = me.fromField,
  13669. toField = me.toField,
  13670. fromStore = fromField.store,
  13671. toStore = toField.store,
  13672. selected;
  13673. // Wait for from store to be loaded
  13674. if (!me.fromStorePopulated) {
  13675. me.fromField.store.on({
  13676. load: Ext.Function.bind(me.setValue, me, [
  13677. value
  13678. ]),
  13679. single: true
  13680. });
  13681. return;
  13682. }
  13683. value = me.setupValue(value);
  13684. me.mixins.field.setValue.call(me, value);
  13685. selected = me.getRecordsForValue(value);
  13686. // Clear both left and right Stores.
  13687. // Both stores must not fire events during this process.
  13688. fromStore.suspendEvents();
  13689. toStore.suspendEvents();
  13690. fromStore.removeAll();
  13691. toStore.removeAll();
  13692. // Reset fromStore
  13693. me.populateFromStore(me.store);
  13694. // Copy selection across to toStore
  13695. Ext.Array.forEach(selected, function(rec) {
  13696. // In the from store, move it over
  13697. if (fromStore.indexOf(rec) > -1) {
  13698. fromStore.remove(rec);
  13699. }
  13700. toStore.add(rec);
  13701. });
  13702. // Stores may now fire events
  13703. fromStore.resumeEvents();
  13704. toStore.resumeEvents();
  13705. // Refresh both sides and then update the app layout
  13706. Ext.suspendLayouts();
  13707. fromField.boundList.refresh();
  13708. toField.boundList.refresh();
  13709. Ext.resumeLayouts(true);
  13710. },
  13711. onBindStore: function(store, initial) {
  13712. var me = this,
  13713. fromField = me.fromField,
  13714. toField = me.toField;
  13715. if (fromField) {
  13716. fromField.store.removeAll();
  13717. toField.store.removeAll();
  13718. if (store.autoCreated) {
  13719. fromField.resolveDisplayField();
  13720. toField.resolveDisplayField();
  13721. me.resolveDisplayField();
  13722. }
  13723. if (!Ext.isDefined(me.valueField)) {
  13724. me.valueField = me.displayField;
  13725. }
  13726. // Add everything to the from field as soon as the Store is loaded
  13727. if (store.getCount()) {
  13728. me.populateFromStore(store);
  13729. } else {
  13730. me.store.on('load', me.populateFromStore, me);
  13731. }
  13732. }
  13733. },
  13734. populateFromStore: function(store) {
  13735. var fromStore = this.fromField.store;
  13736. // Flag set when the fromStore has been loaded
  13737. this.fromStorePopulated = true;
  13738. fromStore.add(store.getRange());
  13739. // setValue waits for the from Store to be loaded
  13740. fromStore.fireEvent('load', fromStore);
  13741. },
  13742. onEnable: function() {
  13743. var me = this;
  13744. me.callParent();
  13745. me.fromField.enable();
  13746. me.toField.enable();
  13747. Ext.Array.forEach(me.query('[navBtn]'), function(btn) {
  13748. btn.enable();
  13749. });
  13750. },
  13751. onDisable: function() {
  13752. var me = this;
  13753. me.callParent();
  13754. me.fromField.disable();
  13755. me.toField.disable();
  13756. Ext.Array.forEach(me.query('[navBtn]'), function(btn) {
  13757. btn.disable();
  13758. });
  13759. },
  13760. doDestroy: function() {
  13761. this.bindStore(null);
  13762. this.callParent();
  13763. }
  13764. });
  13765. /**
  13766. *
  13767. */
  13768. Ext.define('Ext.ux.form.SearchField', {
  13769. extend: 'Ext.form.field.Text',
  13770. alias: 'widget.searchfield',
  13771. triggers: {
  13772. clear: {
  13773. weight: 0,
  13774. cls: Ext.baseCSSPrefix + 'form-clear-trigger',
  13775. hidden: true,
  13776. handler: 'onClearClick',
  13777. scope: 'this'
  13778. },
  13779. search: {
  13780. weight: 1,
  13781. cls: Ext.baseCSSPrefix + 'form-search-trigger',
  13782. handler: 'onSearchClick',
  13783. scope: 'this'
  13784. }
  13785. },
  13786. hasSearch: false,
  13787. paramName: 'query',
  13788. initComponent: function() {
  13789. var me = this,
  13790. store = me.store,
  13791. proxy;
  13792. me.callParent(arguments);
  13793. me.on('specialkey', function(f, e) {
  13794. if (e.getKey() == e.ENTER) {
  13795. me.onSearchClick();
  13796. }
  13797. });
  13798. if (!store || !store.isStore) {
  13799. store = me.store = Ext.data.StoreManager.lookup(store);
  13800. }
  13801. // We're going to use filtering
  13802. store.setRemoteFilter(true);
  13803. // Set up the proxy to encode the filter in the simplest way as a name/value pair
  13804. proxy = me.store.getProxy();
  13805. proxy.setFilterParam(me.paramName);
  13806. proxy.encodeFilters = function(filters) {
  13807. return filters[0].getValue();
  13808. };
  13809. },
  13810. onClearClick: function() {
  13811. var me = this,
  13812. activeFilter = me.activeFilter;
  13813. if (activeFilter) {
  13814. me.setValue('');
  13815. me.store.getFilters().remove(activeFilter);
  13816. me.activeFilter = null;
  13817. me.getTrigger('clear').hide();
  13818. me.updateLayout();
  13819. }
  13820. },
  13821. onSearchClick: function() {
  13822. var me = this,
  13823. value = me.getValue();
  13824. if (value.length > 0) {
  13825. // Param name is ignored here since we use custom encoding in the proxy.
  13826. // id is used by the Store to replace any previous filter
  13827. me.activeFilter = new Ext.util.Filter({
  13828. property: me.paramName,
  13829. value: value
  13830. });
  13831. me.store.getFilters().add(me.activeFilter);
  13832. me.getTrigger('clear').show();
  13833. me.updateLayout();
  13834. }
  13835. }
  13836. });
  13837. /**
  13838. * A small grid nested within a parent grid's row.
  13839. *
  13840. * See the [Kitchen Sink](http://dev.sencha.com/extjs/5.0.1/examples/kitchensink/#customer-grid) for example usage.
  13841. */
  13842. Ext.define('Ext.ux.grid.SubTable', {
  13843. extend: 'Ext.grid.plugin.RowExpander',
  13844. alias: 'plugin.subtable',
  13845. rowBodyTpl: [
  13846. '<table class="' + Ext.baseCSSPrefix + 'grid-subtable">',
  13847. '{%',
  13848. 'this.owner.renderTable(out, values);',
  13849. '%}',
  13850. '</table>'
  13851. ],
  13852. init: function(grid) {
  13853. var me = this,
  13854. columns = me.columns,
  13855. len, i, columnCfg;
  13856. me.callParent(arguments);
  13857. me.columns = [];
  13858. if (columns) {
  13859. for (i = 0 , len = columns.length; i < len; ++i) {
  13860. // Don't register with the component manager, we create them to use
  13861. // their rendering smarts, but don't want to treat them as real components
  13862. columnCfg = Ext.apply({
  13863. preventRegister: true
  13864. }, columns[i]);
  13865. columnCfg.xtype = columnCfg.xtype || 'gridcolumn';
  13866. me.columns.push(Ext.widget(columnCfg));
  13867. }
  13868. }
  13869. },
  13870. destroy: function() {
  13871. var columns = this.columns,
  13872. len, i;
  13873. if (columns) {
  13874. for (i = 0 , len = columns.length; i < len; ++i) {
  13875. columns[i].destroy();
  13876. }
  13877. }
  13878. this.columns = null;
  13879. this.callParent();
  13880. },
  13881. getRowBodyFeatureData: function(record, idx, rowValues) {
  13882. this.callParent(arguments);
  13883. rowValues.rowBodyCls += ' ' + Ext.baseCSSPrefix + 'grid-subtable-row';
  13884. },
  13885. renderTable: function(out, rowValues) {
  13886. var me = this,
  13887. columns = me.columns,
  13888. numColumns = columns.length,
  13889. associatedRecords = me.getAssociatedRecords(rowValues.record),
  13890. recCount = associatedRecords.length,
  13891. rec, column, i, j, value;
  13892. out.push('<thead>');
  13893. for (j = 0; j < numColumns; j++) {
  13894. out.push('<th class="' + Ext.baseCSSPrefix + 'grid-subtable-header">', columns[j].text, '</th>');
  13895. }
  13896. out.push('</thead><tbody>');
  13897. for (i = 0; i < recCount; i++) {
  13898. rec = associatedRecords[i];
  13899. out.push('<tr>');
  13900. for (j = 0; j < numColumns; j++) {
  13901. column = columns[j];
  13902. value = rec.get(column.dataIndex);
  13903. if (column.renderer && column.renderer.call) {
  13904. value = column.renderer.call(column.scope || me, value, {}, rec);
  13905. }
  13906. out.push('<td class="' + Ext.baseCSSPrefix + 'grid-subtable-cell"');
  13907. if (column.width != null) {
  13908. out.push(' style="width:' + column.width + 'px"');
  13909. }
  13910. out.push('><div class="' + Ext.baseCSSPrefix + 'grid-cell-inner">', value, '</div></td>');
  13911. }
  13912. out.push('</tr>');
  13913. }
  13914. out.push('</tbody>');
  13915. },
  13916. getRowBodyContentsFn: function(rowBodyTpl) {
  13917. var me = this;
  13918. return function(rowValues) {
  13919. rowBodyTpl.owner = me;
  13920. return rowBodyTpl.applyTemplate(rowValues);
  13921. };
  13922. },
  13923. getAssociatedRecords: function(record) {
  13924. return record[this.association]().getRange();
  13925. }
  13926. });
  13927. /**
  13928. * A Grid which creates itself from an existing HTML table element.
  13929. */
  13930. Ext.define('Ext.ux.grid.TransformGrid', {
  13931. extend: 'Ext.grid.Panel',
  13932. /**
  13933. * Creates the grid from HTML table element.
  13934. * @param {String/HTMLElement/Ext.Element} table The table element from which this grid will be created -
  13935. * The table MUST have some type of size defined for the grid to fill. The container will be
  13936. * automatically set to position relative if it isn't already.
  13937. * @param {Object} [config] A config object that sets properties on this grid and has two additional (optional)
  13938. * properties: fields and columns which allow for customizing data fields and columns for this grid.
  13939. */
  13940. constructor: function(table, config) {
  13941. config = Ext.apply({}, config);
  13942. table = this.table = Ext.get(table);
  13943. var configFields = config.fields || [],
  13944. configColumns = config.columns || [],
  13945. fields = [],
  13946. cols = [],
  13947. headers = table.query("thead th"),
  13948. i = 0,
  13949. len = headers.length,
  13950. data = table.dom,
  13951. width, height, store, col, text, name;
  13952. for (; i < len; ++i) {
  13953. col = headers[i];
  13954. text = col.innerHTML;
  13955. name = 'tcol-' + i;
  13956. fields.push(Ext.applyIf(configFields[i] || {}, {
  13957. name: name,
  13958. mapping: 'td:nth(' + (i + 1) + ')/@innerHTML'
  13959. }));
  13960. cols.push(Ext.applyIf(configColumns[i] || {}, {
  13961. text: text,
  13962. dataIndex: name,
  13963. width: col.offsetWidth,
  13964. tooltip: col.title,
  13965. sortable: true
  13966. }));
  13967. }
  13968. if (config.width) {
  13969. width = config.width;
  13970. } else {
  13971. width = table.getWidth() + 1;
  13972. }
  13973. if (config.height) {
  13974. height = config.height;
  13975. }
  13976. Ext.applyIf(config, {
  13977. store: {
  13978. data: data,
  13979. fields: fields,
  13980. proxy: {
  13981. type: 'memory',
  13982. reader: {
  13983. record: 'tbody tr',
  13984. type: 'xml'
  13985. }
  13986. }
  13987. },
  13988. columns: cols,
  13989. width: width,
  13990. height: height
  13991. });
  13992. this.callParent([
  13993. config
  13994. ]);
  13995. if (config.remove !== false) {
  13996. // Don't use table.remove() as that destroys the row/cell data in the table in
  13997. // IE6-7 so it cannot be read by the data reader.
  13998. data.parentNode.removeChild(data);
  13999. }
  14000. },
  14001. doDestroy: function() {
  14002. this.table.remove();
  14003. this.tabl = null;
  14004. this.callParent();
  14005. }
  14006. });
  14007. /**
  14008. * This plugin ensures that its associated grid or tree always has a selection record. The
  14009. * only exception is, of course, when there are no records in the store.
  14010. * @since 6.0.2
  14011. */
  14012. Ext.define('Ext.ux.grid.plugin.AutoSelector', {
  14013. extend: 'Ext.plugin.Abstract',
  14014. alias: 'plugin.gridautoselector',
  14015. config: {
  14016. store: null
  14017. },
  14018. init: function(grid) {
  14019. //<debug>
  14020. if (!grid.isXType('tablepanel')) {
  14021. Ext.raise('The gridautoselector plugin is designed only for grids and trees');
  14022. }
  14023. //</debug>
  14024. var me = this;
  14025. me.grid = grid;
  14026. me.watchGrid();
  14027. grid.on({
  14028. reconfigure: me.watchGrid,
  14029. scope: me
  14030. });
  14031. },
  14032. destroy: function() {
  14033. this.setStore(null);
  14034. this.grid = null;
  14035. this.callParent();
  14036. },
  14037. ensureSelection: function() {
  14038. var grid = this.grid,
  14039. store = grid.getStore(),
  14040. selection;
  14041. if (store.getCount()) {
  14042. selection = grid.getSelection();
  14043. if (!selection || !selection.length) {
  14044. grid.getSelectionModel().select(0);
  14045. }
  14046. }
  14047. },
  14048. watchGrid: function() {
  14049. this.setStore(this.grid.getStore());
  14050. this.ensureSelection();
  14051. },
  14052. updateStore: function(store) {
  14053. var me = this;
  14054. Ext.destroy(me.storeListeners);
  14055. me.storeListeners = store && store.on({
  14056. // We could go from 0 records to 1+ records... now we can select one!
  14057. add: me.ensureSelection,
  14058. // We might remove the selected record...
  14059. remove: me.ensureSelection,
  14060. destroyable: true,
  14061. scope: me
  14062. });
  14063. }
  14064. });
  14065. /**
  14066. * A simple grid-like layout for proportionally dividing container space and allocating it
  14067. * to each item. All items in this layout are given one or more percentage sizes and CSS
  14068. * `float:left` is used to provide the wrapping.
  14069. *
  14070. * To select which of the percentage sizes an item uses, this layout adds a viewport
  14071. * {@link #states size-dependent} class name to the container. The style sheet must
  14072. * provide the rules to select the desired size using the {@link #responsivecolumn-item}
  14073. * mixin.
  14074. *
  14075. * For example, a panel in a responsive column layout might add the following styles:
  14076. *
  14077. * .my-panel {
  14078. * // consume 50% of the available space inside the container by default
  14079. * @include responsivecolumn-item(50%);
  14080. *
  14081. * .x-responsivecolumn-small & {
  14082. * // consume 100% of available space in "small" mode
  14083. * // (viewport width < 1000 by default)
  14084. * @include responsivecolumn-item(100%);
  14085. * }
  14086. * }
  14087. *
  14088. * Alternatively, instead of targeting specific panels in CSS, you can create reusable
  14089. * classes:
  14090. *
  14091. * .big-50 {
  14092. * // consume 50% of the available space inside the container by default
  14093. * @include responsivecolumn-item(50%);
  14094. * }
  14095. *
  14096. * .x-responsivecolumn-small {
  14097. * > .small-100 {
  14098. * @include responsivecolumn-item(100%);
  14099. * }
  14100. * }
  14101. *
  14102. * These can be added to components in the layout using the `responsiveCls` config:
  14103. *
  14104. * items: [{
  14105. * xtype: 'my-panel',
  14106. *
  14107. * // Use 50% of space when viewport is "big" and 100% when viewport
  14108. * // is "small":
  14109. * responsiveCls: 'big-50 small-100'
  14110. * }]
  14111. *
  14112. * The `responsiveCls` config is provided by this layout to avoid overwriting classes
  14113. * specified using `cls` or other standard configs.
  14114. *
  14115. * Internally, this layout simply uses `float:left` and CSS `calc()` (except on IE8) to
  14116. * "flex" each item. The calculation is always based on a percentage with a spacing taken
  14117. * into account to separate the items from each other.
  14118. */
  14119. Ext.define('Ext.ux.layout.ResponsiveColumn', {
  14120. extend: 'Ext.layout.container.Auto',
  14121. alias: 'layout.responsivecolumn',
  14122. /**
  14123. * @cfg {Object} states
  14124. *
  14125. * A set of layout state names corresponding to viewport size thresholds. One of the
  14126. * states will be used to assign the responsive column CSS class to the container to
  14127. * trigger appropriate item sizing.
  14128. *
  14129. * For example:
  14130. *
  14131. * layout: {
  14132. * type: 'responsivecolumn',
  14133. * states: {
  14134. * small: 800,
  14135. * medium: 1200,
  14136. * large: 0
  14137. * }
  14138. * }
  14139. *
  14140. * Given the above set of responsive states, one of the following CSS classes will be
  14141. * added to the container:
  14142. *
  14143. * - `x-responsivecolumn-small` - If the viewport is <= 800px
  14144. * - `x-responsivecolumn-medium` - If the viewport is > 800px and <= 1200px
  14145. * - `x-responsivecolumn-large` - If the viewport is > 1200px
  14146. *
  14147. * For sake of efficiency these classes are based on the size of the browser viewport
  14148. * (the browser window) and not on the container size. As the size of the viewport
  14149. * changes, this layout will maintain the appropriate CSS class on the container which
  14150. * will then activate the appropriate CSS rules to size the child items.
  14151. */
  14152. states: {
  14153. small: 1000,
  14154. large: 0
  14155. },
  14156. _responsiveCls: Ext.baseCSSPrefix + 'responsivecolumn',
  14157. initLayout: function() {
  14158. this.innerCtCls += ' ' + this._responsiveCls;
  14159. this.callParent();
  14160. },
  14161. beginLayout: function(ownerContext) {
  14162. var me = this,
  14163. viewportWidth = Ext.Element.getViewportWidth(),
  14164. states = me.states,
  14165. activeThreshold = Infinity,
  14166. innerCt = me.innerCt,
  14167. currentState = me._currentState,
  14168. name, threshold, newState;
  14169. for (name in states) {
  14170. threshold = states[name] || Infinity;
  14171. if (viewportWidth <= threshold && threshold <= activeThreshold) {
  14172. activeThreshold = threshold;
  14173. newState = name;
  14174. }
  14175. }
  14176. if (newState !== currentState) {
  14177. innerCt.replaceCls(currentState, newState, me._responsiveCls);
  14178. me._currentState = newState;
  14179. }
  14180. me.callParent(arguments);
  14181. },
  14182. onAdd: function(item) {
  14183. this.callParent([
  14184. item
  14185. ]);
  14186. var responsiveCls = item.responsiveCls;
  14187. if (responsiveCls) {
  14188. item.addCls(responsiveCls);
  14189. }
  14190. }
  14191. }, //--------------------------------------------------------------------------------------
  14192. // IE8 does not support CSS calc expressions, so we have to fallback to more traditional
  14193. // for of layout. This is very similar but much simpler than Column layout.
  14194. //
  14195. function(Responsive) {
  14196. if (Ext.isIE8) {
  14197. Responsive.override({
  14198. responsiveSizePolicy: {
  14199. readsWidth: 0,
  14200. readsHeight: 0,
  14201. setsWidth: 1,
  14202. setsHeight: 0
  14203. },
  14204. setsItemSize: true,
  14205. calculateItems: function(ownerContext, containerSize) {
  14206. var me = this,
  14207. targetContext = ownerContext.targetContext,
  14208. items = ownerContext.childItems,
  14209. len = items.length,
  14210. gotWidth = containerSize.gotWidth,
  14211. contentWidth = containerSize.width,
  14212. blocked, availableWidth, i, itemContext, itemMarginWidth, itemWidth;
  14213. // No parallel measurement, cannot lay out boxes.
  14214. if (gotWidth === false) {
  14215. targetContext.domBlock(me, 'width');
  14216. return false;
  14217. }
  14218. if (!gotWidth) {
  14219. // gotWidth is undefined, which means we must be width shrink wrap.
  14220. // Cannot calculate item widths if we're shrink wrapping.
  14221. return true;
  14222. }
  14223. for (i = 0; i < len; ++i) {
  14224. itemContext = items[i];
  14225. // The mixin encodes these in background-position syles since it is
  14226. // unlikely a component will have a background-image.
  14227. itemWidth = parseInt(itemContext.el.getStyle('background-position-x'), 10);
  14228. itemMarginWidth = parseInt(itemContext.el.getStyle('background-position-y'), 10);
  14229. itemContext.setWidth((itemWidth / 100 * (contentWidth - itemMarginWidth)) - itemMarginWidth);
  14230. }
  14231. ownerContext.setContentWidth(contentWidth + ownerContext.paddingContext.getPaddingInfo().width);
  14232. return true;
  14233. },
  14234. getItemSizePolicy: function() {
  14235. return this.responsiveSizePolicy;
  14236. }
  14237. });
  14238. }
  14239. });
  14240. /**
  14241. * A {@link Ext.ux.statusbar.StatusBar} plugin that provides automatic error
  14242. * notification when the associated form contains validation errors.
  14243. */
  14244. Ext.define('Ext.ux.statusbar.ValidationStatus', {
  14245. extend: 'Ext.Component',
  14246. alias: 'plugin.validationstatus',
  14247. requires: [
  14248. 'Ext.util.MixedCollection'
  14249. ],
  14250. /**
  14251. * @cfg {String} errorIconCls
  14252. * The {@link Ext.ux.statusbar.StatusBar#iconCls iconCls} value to be applied
  14253. * to the status message when there is a validation error.
  14254. */
  14255. errorIconCls: 'x-status-error',
  14256. /**
  14257. * @cfg {String} errorListCls
  14258. * The css class to be used for the error list when there are validation errors.
  14259. */
  14260. errorListCls: 'x-status-error-list',
  14261. /**
  14262. * @cfg {String} validIconCls
  14263. * The {@link Ext.ux.statusbar.StatusBar#iconCls iconCls} value to be applied
  14264. * to the status message when the form validates.
  14265. */
  14266. validIconCls: 'x-status-valid',
  14267. /**
  14268. * @cfg {String} showText
  14269. * The {@link Ext.ux.statusbar.StatusBar#text text} value to be applied when
  14270. * there is a form validation error.
  14271. */
  14272. showText: 'The form has errors (click for details...)',
  14273. /**
  14274. * @cfg {String} hideText
  14275. * The {@link Ext.ux.statusbar.StatusBar#text text} value to display when
  14276. * the error list is displayed.
  14277. */
  14278. hideText: 'Click again to hide the error list',
  14279. /**
  14280. * @cfg {String} submitText
  14281. * The {@link Ext.ux.statusbar.StatusBar#text text} value to be applied when
  14282. * the form is being submitted.
  14283. */
  14284. submitText: 'Saving...',
  14285. /**
  14286. * @private
  14287. */
  14288. init: function(sb) {
  14289. var me = this;
  14290. me.statusBar = sb;
  14291. sb.on({
  14292. single: true,
  14293. scope: me,
  14294. render: me.onStatusbarRender
  14295. });
  14296. sb.on({
  14297. click: {
  14298. element: 'el',
  14299. fn: me.onStatusClick,
  14300. scope: me,
  14301. buffer: 200
  14302. }
  14303. });
  14304. },
  14305. onStatusbarRender: function(sb) {
  14306. var me = this,
  14307. startMonitor = function() {
  14308. me.monitor = true;
  14309. };
  14310. me.monitor = true;
  14311. me.errors = Ext.create('Ext.util.MixedCollection');
  14312. me.listAlign = (sb.statusAlign === 'right' ? 'br-tr?' : 'bl-tl?');
  14313. if (me.form) {
  14314. // Allow either an id, or a reference to be specified as the form name.
  14315. me.formPanel = Ext.getCmp(me.form) || me.statusBar.lookupController().lookupReference(me.form);
  14316. me.basicForm = me.formPanel.getForm();
  14317. me.startMonitoring();
  14318. me.basicForm.on({
  14319. beforeaction: function(f, action) {
  14320. if (action.type === 'submit') {
  14321. // Ignore monitoring while submitting otherwise the field validation
  14322. // events cause the status message to reset too early
  14323. me.monitor = false;
  14324. }
  14325. }
  14326. });
  14327. me.formPanel.on({
  14328. beforedestroy: me.destroy,
  14329. scope: me
  14330. });
  14331. me.basicForm.on('actioncomplete', startMonitor);
  14332. me.basicForm.on('actionfailed', startMonitor);
  14333. }
  14334. },
  14335. /**
  14336. * @private
  14337. */
  14338. startMonitoring: function() {
  14339. this.basicForm.getFields().each(function(f) {
  14340. f.on('validitychange', this.onFieldValidation, this);
  14341. }, this);
  14342. },
  14343. /**
  14344. * @private
  14345. */
  14346. stopMonitoring: function() {
  14347. var form = this.basicForm;
  14348. if (!form.destroyed) {
  14349. form.getFields().each(function(f) {
  14350. f.un('validitychange', this.onFieldValidation, this);
  14351. }, this);
  14352. }
  14353. },
  14354. doDestroy: function() {
  14355. Ext.destroy(this.msgEl);
  14356. this.stopMonitoring();
  14357. this.statusBar.statusEl.un('click', this.onStatusClick, this);
  14358. this.callParent();
  14359. },
  14360. /**
  14361. * @private
  14362. */
  14363. onFieldValidation: function(f, isValid) {
  14364. var me = this,
  14365. msg;
  14366. if (!me.monitor) {
  14367. return false;
  14368. }
  14369. msg = f.getErrors()[0];
  14370. if (msg) {
  14371. me.errors.add(f.id, {
  14372. field: f,
  14373. msg: msg
  14374. });
  14375. } else {
  14376. me.errors.removeAtKey(f.id);
  14377. }
  14378. this.updateErrorList();
  14379. if (me.errors.getCount() > 0) {
  14380. if (me.statusBar.getText() !== me.showText) {
  14381. me.statusBar.setStatus({
  14382. text: me.showText,
  14383. iconCls: me.errorIconCls
  14384. });
  14385. }
  14386. } else {
  14387. me.statusBar.clearStatus().setIcon(me.validIconCls);
  14388. }
  14389. },
  14390. /**
  14391. * @private
  14392. */
  14393. updateErrorList: function() {
  14394. var me = this,
  14395. msg,
  14396. msgEl = me.getMsgEl();
  14397. if (me.errors.getCount() > 0) {
  14398. msg = [
  14399. '<ul>'
  14400. ];
  14401. this.errors.each(function(err) {
  14402. msg.push('<li id="x-err-', err.field.id, '"><a href="#">', err.msg, '</a></li>');
  14403. });
  14404. msg.push('</ul>');
  14405. msgEl.update(msg.join(''));
  14406. } else {
  14407. msgEl.update('');
  14408. }
  14409. // reset msgEl size
  14410. msgEl.setSize('auto', 'auto');
  14411. },
  14412. /**
  14413. * @private
  14414. */
  14415. getMsgEl: function() {
  14416. var me = this,
  14417. msgEl = me.msgEl,
  14418. t;
  14419. if (!msgEl) {
  14420. msgEl = me.msgEl = Ext.DomHelper.append(Ext.getBody(), {
  14421. cls: me.errorListCls
  14422. }, true);
  14423. msgEl.hide();
  14424. msgEl.on('click', function(e) {
  14425. t = e.getTarget('li', 10, true);
  14426. if (t) {
  14427. Ext.getCmp(t.id.split('x-err-')[1]).focus();
  14428. me.hideErrors();
  14429. }
  14430. }, null, {
  14431. stopEvent: true
  14432. });
  14433. }
  14434. // prevent anchor click navigation
  14435. return msgEl;
  14436. },
  14437. /**
  14438. * @private
  14439. */
  14440. showErrors: function() {
  14441. var me = this;
  14442. me.updateErrorList();
  14443. me.getMsgEl().alignTo(me.statusBar.getEl(), me.listAlign).slideIn('b', {
  14444. duration: 300,
  14445. easing: 'easeOut'
  14446. });
  14447. me.statusBar.setText(me.hideText);
  14448. me.formPanel.body.on('click', me.hideErrors, me, {
  14449. single: true
  14450. });
  14451. },
  14452. // hide if the user clicks directly into the form
  14453. /**
  14454. * @private
  14455. */
  14456. hideErrors: function() {
  14457. var el = this.getMsgEl();
  14458. if (el.isVisible()) {
  14459. el.slideOut('b', {
  14460. duration: 300,
  14461. easing: 'easeIn'
  14462. });
  14463. this.statusBar.setText(this.showText);
  14464. }
  14465. this.formPanel.body.un('click', this.hideErrors, this);
  14466. },
  14467. /**
  14468. * @private
  14469. */
  14470. onStatusClick: function() {
  14471. if (this.getMsgEl().isVisible()) {
  14472. this.hideErrors();
  14473. } else if (this.errors.getCount() > 0) {
  14474. this.showErrors();
  14475. }
  14476. }
  14477. });