123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562 |
- Ext.define(null, {
- override: 'Ext.ux.gauge.needle.Abstract',
- compatibility: Ext.isIE10p,
- setTransform: function(centerX, centerY, rotation) {
- var needleGroup = this.getNeedleGroup();
- this.callParent([
- centerX,
- centerY,
- rotation
- ]);
- needleGroup.set({
- transform: getComputedStyle(needleGroup.dom).getPropertyValue('transform')
- });
- },
- updateStyle: function(style) {
- var pathElement;
- this.callParent([
- style
- ]);
- if (Ext.isObject(style) && 'transform' in style) {
- pathElement = this.getNeedlePath();
- pathElement.set({
- transform: getComputedStyle(pathElement.dom).getPropertyValue('transform')
- });
- }
- }
- });
- /**
- * This is a base class for more advanced "simlets" (simulated servers). A simlet is asked
- * to provide a response given a {@link Ext.ux.ajax.SimXhr} instance.
- */
- Ext.define('Ext.ux.ajax.Simlet', function() {
- var urlRegex = /([^?#]*)(#.*)?$/,
- dateRegex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/,
- intRegex = /^[+-]?\d+$/,
- floatRegex = /^[+-]?\d+\.\d+$/;
- function parseParamValue(value) {
- var m;
- if (Ext.isDefined(value)) {
- value = decodeURIComponent(value);
- if (intRegex.test(value)) {
- value = parseInt(value, 10);
- } else if (floatRegex.test(value)) {
- value = parseFloat(value);
- } else if (!!(m = dateRegex.exec(value))) {
- value = new Date(Date.UTC(+m[1], +m[2] - 1, +m[3], +m[4], +m[5], +m[6]));
- }
- }
- return value;
- }
- return {
- alias: 'simlet.basic',
- isSimlet: true,
- responseProps: [
- 'responseText',
- 'responseXML',
- 'status',
- 'statusText',
- 'responseHeaders'
- ],
- /**
- * @cfg {String/Function} responseText
- */
- /**
- * @cfg {String/Function} responseXML
- */
- /**
- * @cfg {Object/Function} responseHeaders
- */
- /**
- * @cfg {Number/Function} status
- */
- status: 200,
- /**
- * @cfg {String/Function} statusText
- */
- statusText: 'OK',
- constructor: function(config) {
- Ext.apply(this, config);
- },
- doGet: function(ctx) {
- return this.handleRequest(ctx);
- },
- doPost: function(ctx) {
- return this.handleRequest(ctx);
- },
- doRedirect: function(ctx) {
- return false;
- },
- doDelete: function(ctx) {
- var me = this,
- xhr = ctx.xhr,
- records = xhr.options.records;
- me.removeFromData(ctx, records);
- },
- /**
- * Performs the action requested by the given XHR and returns an object to be applied
- * on to the XHR (containing `status`, `responseText`, etc.). For the most part,
- * this is delegated to `doMethod` methods on this class, such as `doGet`.
- *
- * @param {Ext.ux.ajax.SimXhr} xhr The simulated XMLHttpRequest instance.
- * @return {Object} The response properties to add to the XMLHttpRequest.
- */
- exec: function(xhr) {
- var me = this,
- ret = {},
- method = 'do' + Ext.String.capitalize(xhr.method.toLowerCase()),
- // doGet
- fn = me[method];
- if (fn) {
- ret = fn.call(me, me.getCtx(xhr.method, xhr.url, xhr));
- } else {
- ret = {
- status: 405,
- statusText: 'Method Not Allowed'
- };
- }
- return ret;
- },
- getCtx: function(method, url, xhr) {
- return {
- method: method,
- params: this.parseQueryString(url),
- url: url,
- xhr: xhr
- };
- },
- handleRequest: function(ctx) {
- var me = this,
- ret = {},
- val;
- Ext.Array.forEach(me.responseProps, function(prop) {
- if (prop in me) {
- val = me[prop];
- if (Ext.isFunction(val)) {
- val = val.call(me, ctx);
- }
- ret[prop] = val;
- }
- });
- return ret;
- },
- openRequest: function(method, url, options, async) {
- var ctx = this.getCtx(method, url),
- redirect = this.doRedirect(ctx),
- xhr;
- if (options.action === 'destroy') {
- method = 'delete';
- }
- if (redirect) {
- xhr = redirect;
- } else {
- xhr = new Ext.ux.ajax.SimXhr({
- mgr: this.manager,
- simlet: this,
- options: options
- });
- xhr.open(method, url, async);
- }
- return xhr;
- },
- parseQueryString: function(str) {
- var m = urlRegex.exec(str),
- ret = {},
- key, value, i, n;
- if (m && m[1]) {
- var pair,
- parts = m[1].split('&');
- for (i = 0 , n = parts.length; i < n; ++i) {
- if ((pair = parts[i].split('='))[0]) {
- key = decodeURIComponent(pair.shift());
- value = parseParamValue((pair.length > 1) ? pair.join('=') : pair[0]);
- if (!(key in ret)) {
- ret[key] = value;
- } else if (Ext.isArray(ret[key])) {
- ret[key].push(value);
- } else {
- ret[key] = [
- ret[key],
- value
- ];
- }
- }
- }
- }
- return ret;
- },
- redirect: function(method, url, params) {
- switch (arguments.length) {
- case 2:
- if (typeof url == 'string') {
- break;
- };
- params = url;
- // fall...
- case 1:
- url = method;
- method = 'GET';
- break;
- }
- if (params) {
- url = Ext.urlAppend(url, Ext.Object.toQueryString(params));
- }
- return this.manager.openRequest(method, url);
- },
- removeFromData: function(ctx, records) {
- var me = this,
- data = me.getData(ctx),
- model = (ctx.xhr.options.proxy && ctx.xhr.options.proxy.getModel()) || {},
- idProperty = model.idProperty || 'id';
- Ext.each(records, function(record) {
- var id = record.get(idProperty);
- for (var i = data.length; i-- > 0; ) {
- if (data[i][idProperty] === id) {
- me.deleteRecord(i);
- break;
- }
- }
- });
- }
- };
- }());
- /**
- * This base class is used to handle data preparation (e.g., sorting, filtering and
- * group summary).
- */
- Ext.define('Ext.ux.ajax.DataSimlet', function() {
- function makeSortFn(def, cmp) {
- var order = def.direction,
- sign = (order && order.toUpperCase() === 'DESC') ? -1 : 1;
- return function(leftRec, rightRec) {
- var lhs = leftRec[def.property],
- rhs = rightRec[def.property],
- c = (lhs < rhs) ? -1 : ((rhs < lhs) ? 1 : 0);
- if (c || !cmp) {
- return c * sign;
- }
- return cmp(leftRec, rightRec);
- };
- }
- function makeSortFns(defs, cmp) {
- for (var sortFn = cmp,
- i = defs && defs.length; i; ) {
- sortFn = makeSortFn(defs[--i], sortFn);
- }
- return sortFn;
- }
- return {
- extend: 'Ext.ux.ajax.Simlet',
- buildNodes: function(node, path) {
- var me = this,
- nodeData = {
- data: []
- },
- len = node.length,
- children, i, child, name;
- me.nodes[path] = nodeData;
- for (i = 0; i < len; ++i) {
- nodeData.data.push(child = node[i]);
- name = child.text || child.title;
- child.id = path ? path + '/' + name : name;
- children = child.children;
- if (!(child.leaf = !children)) {
- delete child.children;
- me.buildNodes(children, child.id);
- }
- }
- },
- deleteRecord: function(pos) {
- if (this.data && typeof this.data !== 'function') {
- Ext.Array.removeAt(this.data, pos);
- }
- },
- fixTree: function(ctx, tree) {
- var me = this,
- node = ctx.params.node,
- nodes;
- if (!(nodes = me.nodes)) {
- me.nodes = nodes = {};
- me.buildNodes(tree, '');
- }
- node = nodes[node];
- if (node) {
- if (me.node) {
- me.node.sortedData = me.sortedData;
- me.node.currentOrder = me.currentOrder;
- }
- me.node = node;
- me.data = node.data;
- me.sortedData = node.sortedData;
- me.currentOrder = node.currentOrder;
- } else {
- me.data = null;
- }
- },
- getData: function(ctx) {
- var me = this,
- params = ctx.params,
- order = (params.filter || '') + (params.group || '') + '-' + (params.sort || '') + '-' + (params.dir || ''),
- tree = me.tree,
- dynamicData, data, fields, sortFn;
- if (tree) {
- me.fixTree(ctx, tree);
- }
- data = me.data;
- if (typeof data === 'function') {
- dynamicData = true;
- data = data.call(this, ctx);
- }
- // If order is '--' then it means we had no order passed, due to the string concat above
- if (!data || order === '--') {
- return data || [];
- }
- if (!dynamicData && order == me.currentOrder) {
- return me.sortedData;
- }
- ctx.filterSpec = params.filter && Ext.decode(params.filter);
- ctx.groupSpec = params.group && Ext.decode(params.group);
- fields = params.sort;
- if (params.dir) {
- fields = [
- {
- direction: params.dir,
- property: fields
- }
- ];
- } else if (params.sort) {
- fields = Ext.decode(params.sort);
- } else {
- fields = null;
- }
- if (ctx.filterSpec) {
- var filters = new Ext.util.FilterCollection();
- filters.add(this.processFilters(ctx.filterSpec));
- data = Ext.Array.filter(data, filters.getFilterFn());
- }
- sortFn = makeSortFns((ctx.sortSpec = fields));
- if (ctx.groupSpec) {
- sortFn = makeSortFns([
- ctx.groupSpec
- ], sortFn);
- }
- // If a straight Ajax request, data may not be an array.
- // If an Array, preserve 'physical' order of raw data...
- data = Ext.isArray(data) ? data.slice(0) : data;
- if (sortFn) {
- Ext.Array.sort(data, sortFn);
- }
- me.sortedData = data;
- me.currentOrder = order;
- return data;
- },
- processFilters: Ext.identityFn,
- getPage: function(ctx, data) {
- var ret = data,
- length = data.length,
- start = ctx.params.start || 0,
- end = ctx.params.limit ? Math.min(length, start + ctx.params.limit) : length;
- if (start || end < length) {
- ret = ret.slice(start, end);
- }
- return ret;
- },
- getGroupSummary: function(groupField, rows, ctx) {
- return rows[0];
- },
- getSummary: function(ctx, data, page) {
- var me = this,
- groupField = ctx.groupSpec.property,
- accum,
- todo = {},
- summary = [],
- fieldValue, lastFieldValue;
- Ext.each(page, function(rec) {
- fieldValue = rec[groupField];
- todo[fieldValue] = true;
- });
- function flush() {
- if (accum) {
- summary.push(me.getGroupSummary(groupField, accum, ctx));
- accum = null;
- }
- }
- // data is ordered primarily by the groupField, so one pass can pick up all
- // the summaries one at a time.
- Ext.each(data, function(rec) {
- fieldValue = rec[groupField];
- if (lastFieldValue !== fieldValue) {
- flush();
- lastFieldValue = fieldValue;
- }
- if (!todo[fieldValue]) {
- // if we have even 1 summary, we have summarized all that we need
- // (again because data and page are ordered by groupField)
- return !summary.length;
- }
- if (accum) {
- accum.push(rec);
- } else {
- accum = [
- rec
- ];
- }
- return true;
- });
- flush();
- // make sure that last pesky summary goes...
- return summary;
- }
- };
- }());
- /**
- * JSON Simlet.
- */
- Ext.define('Ext.ux.ajax.JsonSimlet', {
- extend: 'Ext.ux.ajax.DataSimlet',
- alias: 'simlet.json',
- doGet: function(ctx) {
- var me = this,
- data = me.getData(ctx),
- page = me.getPage(ctx, data),
- reader = ctx.xhr.options.proxy && ctx.xhr.options.proxy.getReader(),
- root = reader && reader.getRootProperty(),
- ret = me.callParent(arguments),
- // pick up status/statusText
- response = {};
- if (root && Ext.isArray(page)) {
- response[root] = page;
- response[reader.getTotalProperty()] = data.length;
- } else {
- response = page;
- }
- if (ctx.groupSpec) {
- response.summaryData = me.getSummary(ctx, data, page);
- }
- ret.responseText = Ext.encode(response);
- return ret;
- },
- doPost: function(ctx) {
- return this.doGet(ctx);
- }
- });
- /**
- * Pivot Simlet does remote pivot calculations.
- * Filtering the pivot results doesn't work.
- */
- Ext.define('Ext.ux.ajax.PivotSimlet', {
- extend: 'Ext.ux.ajax.JsonSimlet',
- alias: 'simlet.pivot',
- lastPost: null,
- // last Ajax params sent to this simlet
- lastResponse: null,
- // last JSON response produced by this simlet
- keysSeparator: '',
- grandTotalKey: '',
- doPost: function(ctx) {
- var me = this,
- ret = me.callParent(arguments);
- // pick up status/statusText
- me.lastResponse = me.processData(me.getData(ctx), Ext.decode(ctx.xhr.body));
- ret.responseText = Ext.encode(me.lastResponse);
- return ret;
- },
- processData: function(data, params) {
- var me = this,
- len = data.length,
- response = {
- success: true,
- leftAxis: [],
- topAxis: [],
- results: []
- },
- leftAxis = new Ext.util.MixedCollection(),
- topAxis = new Ext.util.MixedCollection(),
- results = new Ext.util.MixedCollection(),
- i, j, k, leftKeys, topKeys, item, agg;
- me.lastPost = params;
- me.keysSeparator = params.keysSeparator;
- me.grandTotalKey = params.grandTotalKey;
- for (i = 0; i < len; i++) {
- leftKeys = me.extractValues(data[i], params.leftAxis, leftAxis);
- topKeys = me.extractValues(data[i], params.topAxis, topAxis);
- // add record to grand totals
- me.addResult(data[i], me.grandTotalKey, me.grandTotalKey, results);
- for (j = 0; j < leftKeys.length; j++) {
- // add record to col grand totals
- me.addResult(data[i], leftKeys[j], me.grandTotalKey, results);
- // add record to left/top keys pair
- for (k = 0; k < topKeys.length; k++) {
- me.addResult(data[i], leftKeys[j], topKeys[k], results);
- }
- }
- // add record to row grand totals
- for (j = 0; j < topKeys.length; j++) {
- me.addResult(data[i], me.grandTotalKey, topKeys[j], results);
- }
- }
- // extract items from their left/top collections and build the json response
- response.leftAxis = leftAxis.getRange();
- response.topAxis = topAxis.getRange();
- len = results.getCount();
- for (i = 0; i < len; i++) {
- item = results.getAt(i);
- item.values = {};
- for (j = 0; j < params.aggregate.length; j++) {
- agg = params.aggregate[j];
- item.values[agg.id] = me[agg.aggregator](item.records, agg.dataIndex, item.leftKey, item.topKey);
- }
- delete (item.records);
- response.results.push(item);
- }
- leftAxis.clear();
- topAxis.clear();
- results.clear();
- return response;
- },
- getKey: function(value) {
- var me = this;
- me.keysMap = me.keysMap || {};
- if (!Ext.isDefined(me.keysMap[value])) {
- me.keysMap[value] = Ext.id();
- }
- return me.keysMap[value];
- },
- extractValues: function(record, dimensions, col) {
- var len = dimensions.length,
- keys = [],
- i, j, key, item, dim;
- key = '';
- for (j = 0; j < len; j++) {
- dim = dimensions[j];
- key += (j > 0 ? this.keysSeparator : '') + this.getKey(record[dim.dataIndex]);
- item = col.getByKey(key);
- if (!item) {
- item = col.add(key, {
- key: key,
- value: record[dim.dataIndex],
- dimensionId: dim.id
- });
- }
- keys.push(key);
- }
- return keys;
- },
- addResult: function(record, leftKey, topKey, results) {
- var item = results.getByKey(leftKey + '/' + topKey);
- if (!item) {
- item = results.add(leftKey + '/' + topKey, {
- leftKey: leftKey,
- topKey: topKey,
- records: []
- });
- }
- item.records.push(record);
- },
- sum: function(records, measure, rowGroupKey, colGroupKey) {
- var length = records.length,
- total = 0,
- i;
- for (i = 0; i < length; i++) {
- total += Ext.Number.from(records[i][measure], 0);
- }
- return total;
- },
- avg: function(records, measure, rowGroupKey, colGroupKey) {
- var length = records.length,
- total = 0,
- i;
- for (i = 0; i < length; i++) {
- total += Ext.Number.from(records[i][measure], 0);
- }
- return length > 0 ? (total / length) : 0;
- },
- min: function(records, measure, rowGroupKey, colGroupKey) {
- var data = [],
- length = records.length,
- i, v;
- for (i = 0; i < length; i++) {
- data.push(records[i][measure]);
- }
- v = Ext.Array.min(data);
- return v;
- },
- max: function(records, measure, rowGroupKey, colGroupKey) {
- var data = [],
- length = records.length,
- i;
- for (i = 0; i < length; i++) {
- data.push(records[i][measure]);
- }
- v = Ext.Array.max(data);
- return v;
- },
- count: function(records, measure, rowGroupKey, colGroupKey) {
- return records.length;
- },
- variance: function(records, measure, rowGroupKey, colGroupKey) {
- var me = Ext.pivot.Aggregators,
- length = records.length,
- avg = me.avg.apply(me, arguments),
- total = 0,
- i;
- if (avg > 0) {
- for (i = 0; i < length; i++) {
- total += Math.pow(Ext.Number.from(records[i][measure], 0) - avg, 2);
- }
- }
- return (total > 0 && length > 1) ? (total / (length - 1)) : 0;
- },
- varianceP: function(records, measure, rowGroupKey, colGroupKey) {
- var me = Ext.pivot.Aggregators,
- length = records.length,
- avg = me.avg.apply(me, arguments),
- total = 0,
- i;
- if (avg > 0) {
- for (i = 0; i < length; i++) {
- total += Math.pow(Ext.Number.from(records[i][measure], 0) - avg, 2);
- }
- }
- return (total > 0 && length > 0) ? (total / length) : 0;
- },
- stdDev: function(records, measure, rowGroupKey, colGroupKey) {
- var me = Ext.pivot.Aggregators,
- v = me.variance.apply(me, arguments);
- return v > 0 ? Math.sqrt(v) : 0;
- },
- stdDevP: function(records, measure, rowGroupKey, colGroupKey) {
- var me = Ext.pivot.Aggregators,
- v = me.varianceP.apply(me, arguments);
- return v > 0 ? Math.sqrt(v) : 0;
- }
- });
- /**
- * Simulates an XMLHttpRequest object's methods and properties but is backed by a
- * {@link Ext.ux.ajax.Simlet} instance that provides the data.
- */
- Ext.define('Ext.ux.ajax.SimXhr', {
- readyState: 0,
- mgr: null,
- simlet: null,
- constructor: function(config) {
- var me = this;
- Ext.apply(me, config);
- me.requestHeaders = {};
- },
- abort: function() {
- var me = this;
- if (me.timer) {
- Ext.undefer(me.timer);
- me.timer = null;
- }
- me.aborted = true;
- },
- getAllResponseHeaders: function() {
- var headers = [];
- if (Ext.isObject(this.responseHeaders)) {
- Ext.Object.each(this.responseHeaders, function(name, value) {
- headers.push(name + ': ' + value);
- });
- }
- return headers.join('\r\n');
- },
- getResponseHeader: function(header) {
- var headers = this.responseHeaders;
- return (headers && headers[header]) || null;
- },
- open: function(method, url, async, user, password) {
- var me = this;
- me.method = method;
- me.url = url;
- me.async = async !== false;
- me.user = user;
- me.password = password;
- me.setReadyState(1);
- },
- overrideMimeType: function(mimeType) {
- this.mimeType = mimeType;
- },
- schedule: function() {
- var me = this,
- delay = me.simlet.delay || me.mgr.delay;
- if (delay) {
- me.timer = Ext.defer(function() {
- me.onTick();
- }, delay);
- } else {
- me.onTick();
- }
- },
- send: function(body) {
- var me = this;
- me.body = body;
- if (me.async) {
- me.schedule();
- } else {
- me.onComplete();
- }
- },
- setReadyState: function(state) {
- var me = this;
- if (me.readyState != state) {
- me.readyState = state;
- me.onreadystatechange();
- }
- },
- setRequestHeader: function(header, value) {
- this.requestHeaders[header] = value;
- },
- // handlers
- onreadystatechange: Ext.emptyFn,
- onComplete: function() {
- var me = this,
- callback;
- me.readyState = 4;
- Ext.apply(me, me.simlet.exec(me));
- callback = me.jsonpCallback;
- if (callback) {
- var text = callback + '(' + me.responseText + ')';
- eval(text);
- }
- },
- onTick: function() {
- var me = this;
- me.timer = null;
- me.onComplete();
- me.onreadystatechange && me.onreadystatechange();
- }
- });
- /**
- * This singleton manages simulated Ajax responses. This allows application logic to be
- * written unaware that its Ajax calls are being handled by simulations ("simlets"). This
- * is currently done by hooking {@link Ext.data.Connection} methods, so all users of that
- * class (and {@link Ext.Ajax} since it is a derived class) qualify for simulation.
- *
- * The requires hooks are inserted when either the {@link #init} method is called or the
- * first {@link Ext.ux.ajax.Simlet} is registered. For example:
- *
- * Ext.onReady(function () {
- * initAjaxSim();
- *
- * // normal stuff
- * });
- *
- * function initAjaxSim () {
- * Ext.ux.ajax.SimManager.init({
- * delay: 300
- * }).register({
- * '/app/data/url': {
- * type: 'json', // use JsonSimlet (type is like xtype for components)
- * data: [
- * { foo: 42, bar: 'abc' },
- * ...
- * ]
- * }
- * });
- * }
- *
- * As many URL's as desired can be registered and associated with a {@link Ext.ux.ajax.Simlet}. To make
- * non-simulated Ajax requests once this singleton is initialized, add a `nosim:true` option
- * to the Ajax options:
- *
- * Ext.Ajax.request({
- * url: 'page.php',
- * nosim: true, // ignored by normal Ajax request
- * params: {
- * id: 1
- * },
- * success: function(response){
- * var text = response.responseText;
- * // process server response here
- * }
- * });
- */
- Ext.define('Ext.ux.ajax.SimManager', {
- singleton: true,
- requires: [
- 'Ext.data.Connection',
- 'Ext.ux.ajax.SimXhr',
- 'Ext.ux.ajax.Simlet',
- 'Ext.ux.ajax.JsonSimlet'
- ],
- /**
- * @cfg {Ext.ux.ajax.Simlet} defaultSimlet
- * The {@link Ext.ux.ajax.Simlet} instance to use for non-matching URL's. By default, this will
- * return 404. Set this to null to use real Ajax calls for non-matching URL's.
- */
- /**
- * @cfg {String} defaultType
- * The default `type` to apply to generic {@link Ext.ux.ajax.Simlet} configuration objects. The
- * default is 'basic'.
- */
- defaultType: 'basic',
- /**
- * @cfg {Number} delay
- * The number of milliseconds to delay before delivering a response to an async request.
- */
- delay: 150,
- /**
- * @property {Boolean} ready
- * True once this singleton has initialized and applied its Ajax hooks.
- * @private
- */
- ready: false,
- constructor: function() {
- this.simlets = [];
- },
- getSimlet: function(url) {
- // Strip down to base URL (no query parameters or hash):
- var me = this,
- index = url.indexOf('?'),
- simlets = me.simlets,
- len = simlets.length,
- i, simlet, simUrl, match;
- if (index < 0) {
- index = url.indexOf('#');
- }
- if (index > 0) {
- url = url.substring(0, index);
- }
- for (i = 0; i < len; ++i) {
- simlet = simlets[i];
- simUrl = simlet.url;
- if (simUrl instanceof RegExp) {
- match = simUrl.test(url);
- } else {
- match = simUrl === url;
- }
- if (match) {
- return simlet;
- }
- }
- return me.defaultSimlet;
- },
- getXhr: function(method, url, options, async) {
- var simlet = this.getSimlet(url);
- if (simlet) {
- return simlet.openRequest(method, url, options, async);
- }
- return null;
- },
- /**
- * Initializes this singleton and applies configuration options.
- * @param {Object} config An optional object with configuration properties to apply.
- * @return {Ext.ux.ajax.SimManager} this
- */
- init: function(config) {
- var me = this;
- Ext.apply(me, config);
- if (!me.ready) {
- me.ready = true;
- if (!('defaultSimlet' in me)) {
- me.defaultSimlet = new Ext.ux.ajax.Simlet({
- status: 404,
- statusText: 'Not Found'
- });
- }
- me._openRequest = Ext.data.Connection.prototype.openRequest;
- Ext.data.request.Ajax.override({
- openRequest: function(options, requestOptions, async) {
- var xhr = !options.nosim && me.getXhr(requestOptions.method, requestOptions.url, options, async);
- if (!xhr) {
- xhr = this.callParent(arguments);
- }
- return xhr;
- }
- });
- if (Ext.data.JsonP) {
- Ext.data.JsonP.self.override({
- createScript: function(url, params, options) {
- var fullUrl = Ext.urlAppend(url, Ext.Object.toQueryString(params)),
- script = !options.nosim && me.getXhr('GET', fullUrl, options, true);
- if (!script) {
- script = this.callParent(arguments);
- }
- return script;
- },
- loadScript: function(request) {
- var script = request.script;
- if (script.simlet) {
- script.jsonpCallback = request.params[request.callbackKey];
- script.send(null);
- // Ext.data.JsonP will attempt dom removal of a script tag, so emulate its presence
- request.script = document.createElement('script');
- } else {
- this.callParent(arguments);
- }
- }
- });
- }
- }
- return me;
- },
- openRequest: function(method, url, async) {
- var opt = {
- method: method,
- url: url
- };
- return this._openRequest.call(Ext.data.Connection.prototype, {}, opt, async);
- },
- /**
- * Registeres one or more {@link Ext.ux.ajax.Simlet} instances.
- * @param {Array/Object} simlet Either a {@link Ext.ux.ajax.Simlet} instance or config, an Array
- * of such elements or an Object keyed by URL with values that are {@link Ext.ux.ajax.Simlet}
- * instances or configs.
- */
- register: function(simlet) {
- var me = this;
- me.init();
- function reg(one) {
- var simlet = one;
- if (!simlet.isSimlet) {
- simlet = Ext.create('simlet.' + (simlet.type || simlet.stype || me.defaultType), one);
- }
- me.simlets.push(simlet);
- simlet.manager = me;
- }
- if (Ext.isArray(simlet)) {
- Ext.each(simlet, reg);
- } else if (simlet.isSimlet || simlet.url) {
- reg(simlet);
- } else {
- Ext.Object.each(simlet, function(url, s) {
- s.url = url;
- reg(s);
- });
- }
- return me;
- }
- });
- /**
- * This class simulates XML-based requests.
- */
- Ext.define('Ext.ux.ajax.XmlSimlet', {
- extend: 'Ext.ux.ajax.DataSimlet',
- alias: 'simlet.xml',
- /**
- * This template is used to populate the XML response. The configuration of the Reader
- * is available so that its `root` and `record` properties can be used as well as the
- * `fields` of the associated `model`. But beyond that, the way these pieces are put
- * together in the document requires the flexibility of a template.
- */
- xmlTpl: [
- '<{root}>\n',
- '<tpl for="data">',
- ' <{parent.record}>\n',
- '<tpl for="parent.fields">',
- ' <{name}>{[parent[values.name]]}</{name}>\n',
- '</tpl>',
- ' </{parent.record}>\n',
- '</tpl>',
- '</{root}>'
- ],
- doGet: function(ctx) {
- var me = this,
- data = me.getData(ctx),
- page = me.getPage(ctx, data),
- proxy = ctx.xhr.options.operation.getProxy(),
- reader = proxy && proxy.getReader(),
- model = reader && reader.getModel(),
- ret = me.callParent(arguments),
- // pick up status/statusText
- response = {
- data: page,
- reader: reader,
- fields: model && model.fields,
- root: reader && reader.getRootProperty(),
- record: reader && reader.record
- },
- tpl, xml, doc;
- if (ctx.groupSpec) {
- response.summaryData = me.getSummary(ctx, data, page);
- }
- // If a straight Ajax request there won't be an xmlTpl.
- if (me.xmlTpl) {
- tpl = Ext.XTemplate.getTpl(me, 'xmlTpl');
- xml = tpl.apply(response);
- } else {
- xml = data;
- }
- if (typeof DOMParser != 'undefined') {
- doc = (new DOMParser()).parseFromString(xml, "text/xml");
- } else {
- // IE doesn't have DOMParser, but fortunately, there is an ActiveX for XML
- doc = new ActiveXObject("Microsoft.XMLDOM");
- doc.async = false;
- doc.loadXML(xml);
- }
- ret.responseText = xml;
- ret.responseXML = doc;
- return ret;
- },
- fixTree: function() {
- this.callParent(arguments);
- var buffer = [];
- this.buildTreeXml(this.data, buffer);
- this.data = buffer.join('');
- },
- buildTreeXml: function(nodes, buffer) {
- var rootProperty = this.rootProperty,
- recordProperty = this.recordProperty;
- buffer.push('<', rootProperty, '>');
- Ext.Array.forEach(nodes, function(node) {
- buffer.push('<', recordProperty, '>');
- for (var key in node) {
- if (key == 'children') {
- this.buildTreeXml(node.children, buffer);
- } else {
- buffer.push('<', key, '>', node[key], '</', key, '>');
- }
- }
- buffer.push('</', recordProperty, '>');
- });
- buffer.push('</', rootProperty, '>');
- }
- });
- /**
- * This is the base class for {@link Ext.ux.event.Recorder} and {@link Ext.ux.event.Player}.
- */
- Ext.define('Ext.ux.event.Driver', {
- extend: 'Ext.util.Observable',
- active: null,
- specialKeysByName: {
- PGUP: 33,
- PGDN: 34,
- END: 35,
- HOME: 36,
- LEFT: 37,
- UP: 38,
- RIGHT: 39,
- DOWN: 40
- },
- specialKeysByCode: {},
- /**
- * @event start
- * Fires when this object is started.
- * @param {Ext.ux.event.Driver} this
- */
- /**
- * @event stop
- * Fires when this object is stopped.
- * @param {Ext.ux.event.Driver} this
- */
- getTextSelection: function(el) {
- // See https://code.google.com/p/rangyinputs/source/browse/trunk/rangyinputs_jquery.js
- var doc = el.ownerDocument,
- range, range2, start, end;
- if (typeof el.selectionStart === "number") {
- start = el.selectionStart;
- end = el.selectionEnd;
- } else if (doc.selection) {
- range = doc.selection.createRange();
- range2 = el.createTextRange();
- range2.setEndPoint('EndToStart', range);
- start = range2.text.length;
- end = start + range.text.length;
- }
- return [
- start,
- end
- ];
- },
- getTime: function() {
- return new Date().getTime();
- },
- /**
- * Returns the number of milliseconds since start was called.
- */
- getTimestamp: function() {
- var d = this.getTime();
- return d - this.startTime;
- },
- onStart: function() {},
- onStop: function() {},
- /**
- * Starts this object. If this object is already started, nothing happens.
- */
- start: function() {
- var me = this;
- if (!me.active) {
- me.active = new Date();
- me.startTime = me.getTime();
- me.onStart();
- me.fireEvent('start', me);
- }
- },
- /**
- * Stops this object. If this object is not started, nothing happens.
- */
- stop: function() {
- var me = this;
- if (me.active) {
- me.active = null;
- me.onStop();
- me.fireEvent('stop', me);
- }
- }
- }, function() {
- var proto = this.prototype;
- Ext.Object.each(proto.specialKeysByName, function(name, value) {
- proto.specialKeysByCode[value] = name;
- });
- });
- /**
- * Event maker.
- */
- Ext.define('Ext.ux.event.Maker', {
- eventQueue: [],
- startAfter: 500,
- timerIncrement: 500,
- currentTiming: 0,
- constructor: function(config) {
- var me = this;
- me.currentTiming = me.startAfter;
- if (!Ext.isArray(config)) {
- config = [
- config
- ];
- }
- Ext.Array.each(config, function(item) {
- item.el = item.el || 'el';
- Ext.Array.each(Ext.ComponentQuery.query(item.cmpQuery), function(cmp) {
- var event = {},
- x, y, el;
- if (!item.domQuery) {
- el = cmp[item.el];
- } else {
- el = cmp.el.down(item.domQuery);
- }
- event.target = '#' + el.dom.id;
- event.type = item.type;
- event.button = config.button || 0;
- x = el.getX() + (el.getWidth() / 2);
- y = el.getY() + (el.getHeight() / 2);
- event.xy = [
- x,
- y
- ];
- event.ts = me.currentTiming;
- me.currentTiming += me.timerIncrement;
- me.eventQueue.push(event);
- });
- if (item.screenshot) {
- me.eventQueue[me.eventQueue.length - 1].screenshot = true;
- }
- });
- return me.eventQueue;
- }
- });
- /**
- * @extends Ext.ux.event.Driver
- * This class manages the playback of an array of "event descriptors". For details on the
- * contents of an "event descriptor", see {@link Ext.ux.event.Recorder}. The events recorded by the
- * {@link Ext.ux.event.Recorder} class are designed to serve as input for this class.
- *
- * The simplest use of this class is to instantiate it with an {@link #eventQueue} and call
- * {@link #method-start}. Like so:
- *
- * var player = Ext.create('Ext.ux.event.Player', {
- * eventQueue: [ ... ],
- * speed: 2, // play at 2x speed
- * listeners: {
- * stop: function () {
- * player = null; // all done
- * }
- * }
- * });
- *
- * player.start();
- *
- * A more complex use would be to incorporate keyframe generation after playing certain
- * events.
- *
- * var player = Ext.create('Ext.ux.event.Player', {
- * eventQueue: [ ... ],
- * keyFrameEvents: {
- * click: true
- * },
- * listeners: {
- * stop: function () {
- * // play has completed... probably time for another keyframe...
- * player = null;
- * },
- * keyframe: onKeyFrame
- * }
- * });
- *
- * player.start();
- *
- * If a keyframe can be handled immediately (synchronously), the listener would be:
- *
- * function onKeyFrame () {
- * handleKeyFrame();
- * }
- *
- * If the keyframe event is always handled asynchronously, then the event listener is only
- * a bit more:
- *
- * function onKeyFrame (p, eventDescriptor) {
- * eventDescriptor.defer(); // pause event playback...
- *
- * handleKeyFrame(function () {
- * eventDescriptor.finish(); // ...resume event playback
- * });
- * }
- *
- * Finally, if the keyframe could be either handled synchronously or asynchronously (perhaps
- * differently by browser), a slightly more complex listener is required.
- *
- * function onKeyFrame (p, eventDescriptor) {
- * var async;
- *
- * handleKeyFrame(function () {
- * // either this callback is being called immediately by handleKeyFrame (in
- * // which case async is undefined) or it is being called later (in which case
- * // async will be true).
- *
- * if (async) {
- * eventDescriptor.finish();
- * } else {
- * async = false;
- * }
- * });
- *
- * // either the callback was called (and async is now false) or it was not
- * // called (and async remains undefined).
- *
- * if (async !== false) {
- * eventDescriptor.defer();
- * async = true; // let the callback know that we have gone async
- * }
- * }
- */
- Ext.define('Ext.ux.event.Player', function(Player) {
- var defaults = {},
- mouseEvents = {},
- keyEvents = {},
- doc,
- //HTML events supported
- uiEvents = {},
- //events that bubble by default
- bubbleEvents = {
- //scroll: 1,
- resize: 1,
- reset: 1,
- submit: 1,
- change: 1,
- select: 1,
- error: 1,
- abort: 1
- };
- Ext.each([
- 'click',
- 'dblclick',
- 'mouseover',
- 'mouseout',
- 'mousedown',
- 'mouseup',
- 'mousemove'
- ], function(type) {
- bubbleEvents[type] = defaults[type] = mouseEvents[type] = {
- bubbles: true,
- cancelable: (type != "mousemove"),
- // mousemove cannot be cancelled
- detail: 1,
- screenX: 0,
- screenY: 0,
- clientX: 0,
- clientY: 0,
- ctrlKey: false,
- altKey: false,
- shiftKey: false,
- metaKey: false,
- button: 0
- };
- });
- Ext.each([
- 'keydown',
- 'keyup',
- 'keypress'
- ], function(type) {
- bubbleEvents[type] = defaults[type] = keyEvents[type] = {
- bubbles: true,
- cancelable: true,
- ctrlKey: false,
- altKey: false,
- shiftKey: false,
- metaKey: false,
- keyCode: 0,
- charCode: 0
- };
- });
- Ext.each([
- 'blur',
- 'change',
- 'focus',
- 'resize',
- 'scroll',
- 'select'
- ], function(type) {
- defaults[type] = uiEvents[type] = {
- bubbles: (type in bubbleEvents),
- cancelable: false,
- detail: 1
- };
- });
- var inputSpecialKeys = {
- 8: function(target, start, end) {
- // backspace: 8,
- if (start < end) {
- target.value = target.value.substring(0, start) + target.value.substring(end);
- } else if (start > 0) {
- target.value = target.value.substring(0, --start) + target.value.substring(end);
- }
- this.setTextSelection(target, start, start);
- },
- 46: function(target, start, end) {
- // delete: 46
- if (start < end) {
- target.value = target.value.substring(0, start) + target.value.substring(end);
- } else if (start < target.value.length - 1) {
- target.value = target.value.substring(0, start) + target.value.substring(start + 1);
- }
- this.setTextSelection(target, start, start);
- }
- };
- return {
- extend: 'Ext.ux.event.Driver',
- /**
- * @cfg {Array} eventQueue The event queue to playback. This must be provided before
- * the {@link #method-start} method is called.
- */
- /**
- * @cfg {Object} keyFrameEvents An object that describes the events that should generate
- * keyframe events. For example, `{ click: true }` would generate keyframe events after
- * each `click` event.
- */
- keyFrameEvents: {
- click: true
- },
- /**
- * @cfg {Boolean} pauseForAnimations True to pause event playback during animations, false
- * to ignore animations. Default is true.
- */
- pauseForAnimations: true,
- /**
- * @cfg {Number} speed The playback speed multiplier. Default is 1.0 (to playback at the
- * recorded speed). A value of 2 would playback at 2x speed.
- */
- speed: 1,
- stallTime: 0,
- _inputSpecialKeys: {
- INPUT: inputSpecialKeys,
- TEXTAREA: Ext.apply({}, //13: function (target, start, end) { // enter: 8,
- //TODO ?
- //}
- inputSpecialKeys)
- },
- tagPathRegEx: /(\w+)(?:\[(\d+)\])?/,
- /**
- * @event beforeplay
- * Fires before an event is played.
- * @param {Ext.ux.event.Player} this
- * @param {Object} eventDescriptor The event descriptor about to be played.
- */
- /**
- * @event keyframe
- * Fires when this player reaches a keyframe. Typically, this is after events
- * like `click` are injected and any resulting animations have been completed.
- * @param {Ext.ux.event.Player} this
- * @param {Object} eventDescriptor The keyframe event descriptor.
- */
- constructor: function(config) {
- var me = this;
- me.callParent(arguments);
- me.timerFn = function() {
- me.onTick();
- };
- me.attachTo = me.attachTo || window;
- doc = me.attachTo.document;
- },
- /**
- * Returns the element given is XPath-like description.
- * @param {String} xpath The XPath-like description of the element.
- * @return {HTMLElement}
- */
- getElementFromXPath: function(xpath) {
- var me = this,
- parts = xpath.split('/'),
- regex = me.tagPathRegEx,
- i, n, m, count, tag, child,
- el = me.attachTo.document;
- el = (parts[0] == '~') ? el.body : el.getElementById(parts[0].substring(1));
- // remove '#'
- for (i = 1 , n = parts.length; el && i < n; ++i) {
- m = regex.exec(parts[i]);
- count = m[2] ? parseInt(m[2], 10) : 1;
- tag = m[1].toUpperCase();
- for (child = el.firstChild; child; child = child.nextSibling) {
- if (child.tagName == tag) {
- if (count == 1) {
- break;
- }
- --count;
- }
- }
- el = child;
- }
- return el;
- },
- // Moving across a line break only counts as moving one character in a TextRange, whereas a line break in
- // the textarea value is two characters. This function corrects for that by converting a text offset into a
- // range character offset by subtracting one character for every line break in the textarea prior to the
- // offset
- offsetToRangeCharacterMove: function(el, offset) {
- return offset - (el.value.slice(0, offset).split("\r\n").length - 1);
- },
- setTextSelection: function(el, startOffset, endOffset) {
- // See https://code.google.com/p/rangyinputs/source/browse/trunk/rangyinputs_jquery.js
- if (startOffset < 0) {
- startOffset += el.value.length;
- }
- if (endOffset == null) {
- endOffset = startOffset;
- }
- if (endOffset < 0) {
- endOffset += el.value.length;
- }
- if (typeof el.selectionStart === "number") {
- el.selectionStart = startOffset;
- el.selectionEnd = endOffset;
- } else {
- var range = el.createTextRange();
- var startCharMove = this.offsetToRangeCharacterMove(el, startOffset);
- range.collapse(true);
- if (startOffset == endOffset) {
- range.move("character", startCharMove);
- } else {
- range.moveEnd("character", this.offsetToRangeCharacterMove(el, endOffset));
- range.moveStart("character", startCharMove);
- }
- range.select();
- }
- },
- getTimeIndex: function() {
- var t = this.getTimestamp() - this.stallTime;
- return t * this.speed;
- },
- makeToken: function(eventDescriptor, signal) {
- var me = this,
- t0;
- eventDescriptor[signal] = true;
- eventDescriptor.defer = function() {
- eventDescriptor[signal] = false;
- t0 = me.getTime();
- };
- eventDescriptor.finish = function() {
- eventDescriptor[signal] = true;
- me.stallTime += me.getTime() - t0;
- me.schedule();
- };
- },
- /**
- * This method is called after an event has been played to prepare for the next event.
- * @param {Object} eventDescriptor The descriptor of the event just played.
- */
- nextEvent: function(eventDescriptor) {
- var me = this,
- index = ++me.queueIndex;
- // keyframe events are inserted after a keyFrameEvent is played.
- if (me.keyFrameEvents[eventDescriptor.type]) {
- Ext.Array.insert(me.eventQueue, index, [
- {
- keyframe: true,
- ts: eventDescriptor.ts
- }
- ]);
- }
- },
- /**
- * This method returns the event descriptor at the front of the queue. This does not
- * dequeue the event. Repeated calls return the same object (until {@link #nextEvent}
- * is called).
- */
- peekEvent: function() {
- return this.eventQueue[this.queueIndex] || null;
- },
- /**
- * Replaces an event in the queue with an array of events. This is often used to roll
- * up a multi-step pseudo-event and expand it just-in-time to be played. The process
- * for doing this in a derived class would be this:
- *
- * Ext.define('My.Player', {
- * extend: 'Ext.ux.event.Player',
- *
- * peekEvent: function () {
- * var event = this.callParent();
- *
- * if (event.multiStepSpecial) {
- * this.replaceEvent(null, [
- * ... expand to actual events
- * ]);
- *
- * event = this.callParent(); // get the new next event
- * }
- *
- * return event;
- * }
- * });
- *
- * This method ensures that the `beforeplay` hook (if any) from the replaced event is
- * placed on the first new event and the `afterplay` hook (if any) is placed on the
- * last new event.
- *
- * @param {Number} index The queue index to replace. Pass `null` to replace the event
- * at the current `queueIndex`.
- * @param {Event[]} events The array of events with which to replace the specified
- * event.
- */
- replaceEvent: function(index, events) {
- for (var t,
- i = 0,
- n = events.length; i < n; ++i) {
- if (i) {
- t = events[i - 1];
- delete t.afterplay;
- delete t.screenshot;
- delete events[i].beforeplay;
- }
- }
- Ext.Array.replace(this.eventQueue, (index == null) ? this.queueIndex : index, 1, events);
- },
- /**
- * This method dequeues and injects events until it has arrived at the time index. If
- * no events are ready (based on the time index), this method does nothing.
- * @return {Boolean} True if there is more to do; false if not (at least for now).
- */
- processEvents: function() {
- var me = this,
- animations = me.pauseForAnimations && me.attachTo.Ext.fx.Manager.items,
- eventDescriptor;
- while ((eventDescriptor = me.peekEvent()) !== null) {
- if (animations && animations.getCount()) {
- return true;
- }
- if (eventDescriptor.keyframe) {
- if (!me.processKeyFrame(eventDescriptor)) {
- return false;
- }
- me.nextEvent(eventDescriptor);
- } else if (eventDescriptor.ts <= me.getTimeIndex() && me.fireEvent('beforeplay', me, eventDescriptor) !== false && me.playEvent(eventDescriptor)) {
- me.nextEvent(eventDescriptor);
- } else {
- return true;
- }
- }
- me.stop();
- return false;
- },
- /**
- * This method is called when a keyframe is reached. This will fire the keyframe event.
- * If the keyframe has been handled, true is returned. Otherwise, false is returned.
- * @param {Object} eventDescriptor The event descriptor of the keyframe.
- * @return {Boolean} True if the keyframe was handled, false if not.
- */
- processKeyFrame: function(eventDescriptor) {
- var me = this;
- // only fire keyframe event (and setup the eventDescriptor) once...
- if (!eventDescriptor.defer) {
- me.makeToken(eventDescriptor, 'done');
- me.fireEvent('keyframe', me, eventDescriptor);
- }
- return eventDescriptor.done;
- },
- /**
- * Called to inject the given event on the specified target.
- * @param {HTMLElement} target The target of the event.
- * @param {Object} event The event to inject. The properties of this object should be
- * those of standard DOM events but vary based on the `type` property. For details on
- * event types and their properties, see the class documentation.
- */
- injectEvent: function(target, event) {
- var me = this,
- type = event.type,
- options = Ext.apply({}, event, defaults[type]),
- handler;
- if (type === 'type') {
- handler = me._inputSpecialKeys[target.tagName];
- if (handler) {
- return me.injectTypeInputEvent(target, event, handler);
- }
- return me.injectTypeEvent(target, event);
- }
- if (type === 'focus' && target.focus) {
- target.focus();
- return true;
- }
- if (type === 'blur' && target.blur) {
- target.blur();
- return true;
- }
- if (type === 'scroll') {
- target.scrollLeft = event.pos[0];
- target.scrollTop = event.pos[1];
- return true;
- }
- if (type === 'mduclick') {
- return me.injectEvent(target, Ext.applyIf({
- type: 'mousedown'
- }, event)) && me.injectEvent(target, Ext.applyIf({
- type: 'mouseup'
- }, event)) && me.injectEvent(target, Ext.applyIf({
- type: 'click'
- }, event));
- }
- if (mouseEvents[type]) {
- return Player.injectMouseEvent(target, options, me.attachTo);
- }
- if (keyEvents[type]) {
- return Player.injectKeyEvent(target, options, me.attachTo);
- }
- if (uiEvents[type]) {
- return Player.injectUIEvent(target, type, options.bubbles, options.cancelable, options.view || me.attachTo, options.detail);
- }
- return false;
- },
- injectTypeEvent: function(target, event) {
- var me = this,
- text = event.text,
- xlat = [],
- ch, chUp, i, n, sel, upper, isInput;
- if (text) {
- delete event.text;
- upper = text.toUpperCase();
- for (i = 0 , n = text.length; i < n; ++i) {
- ch = text.charCodeAt(i);
- chUp = upper.charCodeAt(i);
- xlat.push(Ext.applyIf({
- type: 'keydown',
- charCode: chUp,
- keyCode: chUp
- }, event), Ext.applyIf({
- type: 'keypress',
- charCode: ch,
- keyCode: ch
- }, event), Ext.applyIf({
- type: 'keyup',
- charCode: chUp,
- keyCode: chUp
- }, event));
- }
- } else {
- xlat.push(Ext.applyIf({
- type: 'keydown',
- charCode: event.keyCode
- }, event), Ext.applyIf({
- type: 'keyup',
- charCode: event.keyCode
- }, event));
- }
- for (i = 0 , n = xlat.length; i < n; ++i) {
- me.injectEvent(target, xlat[i]);
- }
- return true;
- },
- injectTypeInputEvent: function(target, event, handler) {
- var me = this,
- text = event.text,
- sel, n;
- if (handler) {
- sel = me.getTextSelection(target);
- if (text) {
- n = sel[0];
- target.value = target.value.substring(0, n) + text + target.value.substring(sel[1]);
- n += text.length;
- me.setTextSelection(target, n, n);
- } else {
- if (!(handler = handler[event.keyCode])) {
- // no handler for the special key for this element
- if ('caret' in event) {
- me.setTextSelection(target, event.caret, event.caret);
- } else if (event.selection) {
- me.setTextSelection(target, event.selection[0], event.selection[1]);
- }
- return me.injectTypeEvent(target, event);
- }
- handler.call(this, target, sel[0], sel[1]);
- return true;
- }
- }
- return true;
- },
- playEvent: function(eventDescriptor) {
- var me = this,
- target = me.getElementFromXPath(eventDescriptor.target),
- event;
- if (!target) {
- // not present (yet)... wait for element present...
- // TODO - need a timeout here
- return false;
- }
- if (!me.playEventHook(eventDescriptor, 'beforeplay')) {
- return false;
- }
- if (!eventDescriptor.injected) {
- eventDescriptor.injected = true;
- event = me.translateEvent(eventDescriptor, target);
- me.injectEvent(target, event);
- }
- return me.playEventHook(eventDescriptor, 'afterplay');
- },
- playEventHook: function(eventDescriptor, hookName) {
- var me = this,
- doneName = hookName + '.done',
- firedName = hookName + '.fired',
- hook = eventDescriptor[hookName];
- if (hook && !eventDescriptor[doneName]) {
- if (!eventDescriptor[firedName]) {
- eventDescriptor[firedName] = true;
- me.makeToken(eventDescriptor, doneName);
- if (me.eventScope && Ext.isString(hook)) {
- hook = me.eventScope[hook];
- }
- if (hook) {
- hook.call(me.eventScope || me, eventDescriptor);
- }
- }
- return false;
- }
- return true;
- },
- schedule: function() {
- var me = this;
- if (!me.timer) {
- me.timer = Ext.defer(me.timerFn, 10);
- }
- },
- _translateAcross: [
- 'type',
- 'button',
- 'charCode',
- 'keyCode',
- 'caret',
- 'pos',
- 'text',
- 'selection'
- ],
- translateEvent: function(eventDescriptor, target) {
- var me = this,
- event = {},
- modKeys = eventDescriptor.modKeys || '',
- names = me._translateAcross,
- i = names.length,
- name, xy;
- while (i--) {
- name = names[i];
- if (name in eventDescriptor) {
- event[name] = eventDescriptor[name];
- }
- }
- event.altKey = modKeys.indexOf('A') > 0;
- event.ctrlKey = modKeys.indexOf('C') > 0;
- event.metaKey = modKeys.indexOf('M') > 0;
- event.shiftKey = modKeys.indexOf('S') > 0;
- if (target && 'x' in eventDescriptor) {
- xy = Ext.fly(target).getXY();
- xy[0] += eventDescriptor.x;
- xy[1] += eventDescriptor.y;
- } else if ('x' in eventDescriptor) {
- xy = [
- eventDescriptor.x,
- eventDescriptor.y
- ];
- } else if ('px' in eventDescriptor) {
- xy = [
- eventDescriptor.px,
- eventDescriptor.py
- ];
- }
- if (xy) {
- event.clientX = event.screenX = xy[0];
- event.clientY = event.screenY = xy[1];
- }
- if (eventDescriptor.key) {
- event.keyCode = me.specialKeysByName[eventDescriptor.key];
- }
- if (eventDescriptor.type === 'wheel') {
- if ('onwheel' in me.attachTo.document) {
- event.wheelX = eventDescriptor.dx;
- event.wheelY = eventDescriptor.dy;
- } else {
- event.type = 'mousewheel';
- event.wheelDeltaX = -40 * eventDescriptor.dx;
- event.wheelDeltaY = event.wheelDelta = -40 * eventDescriptor.dy;
- }
- }
- return event;
- },
- //---------------------------------
- // Driver overrides
- onStart: function() {
- var me = this;
- me.queueIndex = 0;
- me.schedule();
- },
- onStop: function() {
- var me = this;
- if (me.timer) {
- Ext.undefer(me.timer);
- me.timer = null;
- }
- },
- //---------------------------------
- onTick: function() {
- var me = this;
- me.timer = null;
- if (me.processEvents()) {
- me.schedule();
- }
- },
- statics: {
- ieButtonCodeMap: {
- 0: 1,
- 1: 4,
- 2: 2
- },
- /**
- * Injects a key event using the given event information to populate the event
- * object.
- *
- * **Note:** `keydown` causes Safari 2.x to crash.
- *
- * @param {HTMLElement} target The target of the given event.
- * @param {Object} options Object object containing all of the event injection
- * options.
- * @param {String} options.type The type of event to fire. This can be any one of
- * the following: `keyup`, `keydown` and `keypress`.
- * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.
- * DOM Level 3 specifies that all key events bubble by default.
- * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled
- * using `preventDefault`. DOM Level 3 specifies that all key events can be
- * cancelled.
- * @param {Boolean} [options.ctrlKey=false] `true` if one of the CTRL keys is
- * pressed while the event is firing.
- * @param {Boolean} [options.altKey=false] `true` if one of the ALT keys is
- * pressed while the event is firing.
- * @param {Boolean} [options.shiftKey=false] `true` if one of the SHIFT keys is
- * pressed while the event is firing.
- * @param {Boolean} [options.metaKey=false] `true` if one of the META keys is
- * pressed while the event is firing.
- * @param {Number} [options.keyCode=0] The code for the key that is in use.
- * @param {Number} [options.charCode=0] The Unicode code for the character
- * associated with the key being used.
- * @param {Window} [view=window] The view containing the target. This is typically
- * the window object.
- * @private
- */
- injectKeyEvent: function(target, options, view) {
- var type = options.type,
- customEvent = null;
- if (type === 'textevent') {
- type = 'keypress';
- }
- view = view || window;
- //check for DOM-compliant browsers first
- if (doc.createEvent) {
- try {
- customEvent = doc.createEvent("KeyEvents");
- // Interesting problem: Firefox implemented a non-standard
- // version of initKeyEvent() based on DOM Level 2 specs.
- // Key event was removed from DOM Level 2 and re-introduced
- // in DOM Level 3 with a different interface. Firefox is the
- // only browser with any implementation of Key Events, so for
- // now, assume it's Firefox if the above line doesn't error.
- // @TODO: Decipher between Firefox's implementation and a correct one.
- customEvent.initKeyEvent(type, options.bubbles, options.cancelable, view, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.keyCode, options.charCode);
- } catch (ex) {
- // If it got here, that means key events aren't officially supported.
- // Safari/WebKit is a real problem now. WebKit 522 won't let you
- // set keyCode, charCode, or other properties if you use a
- // UIEvent, so we first must try to create a generic event. The
- // fun part is that this will throw an error on Safari 2.x. The
- // end result is that we need another try...catch statement just to
- // deal with this mess.
- try {
- //try to create generic event - will fail in Safari 2.x
- customEvent = doc.createEvent("Events");
- } catch (uierror) {
- //the above failed, so create a UIEvent for Safari 2.x
- customEvent = doc.createEvent("UIEvents");
- } finally {
- customEvent.initEvent(type, options.bubbles, options.cancelable);
- customEvent.view = view;
- customEvent.altKey = options.altKey;
- customEvent.ctrlKey = options.ctrlKey;
- customEvent.shiftKey = options.shiftKey;
- customEvent.metaKey = options.metaKey;
- customEvent.keyCode = options.keyCode;
- customEvent.charCode = options.charCode;
- }
- }
- target.dispatchEvent(customEvent);
- } else if (doc.createEventObject) {
- //IE
- customEvent = doc.createEventObject();
- customEvent.bubbles = options.bubbles;
- customEvent.cancelable = options.cancelable;
- customEvent.view = view;
- customEvent.ctrlKey = options.ctrlKey;
- customEvent.altKey = options.altKey;
- customEvent.shiftKey = options.shiftKey;
- customEvent.metaKey = options.metaKey;
- // IE doesn't support charCode explicitly. CharCode should
- // take precedence over any keyCode value for accurate
- // representation.
- customEvent.keyCode = (options.charCode > 0) ? options.charCode : options.keyCode;
- target.fireEvent("on" + type, customEvent);
- } else {
- return false;
- }
- return true;
- },
- /**
- * Injects a mouse event using the given event information to populate the event
- * object.
- *
- * @param {HTMLElement} target The target of the given event.
- * @param {Object} options Object object containing all of the event injection
- * options.
- * @param {String} options.type The type of event to fire. This can be any one of
- * the following: `click`, `dblclick`, `mousedown`, `mouseup`, `mouseout`,
- * `mouseover` and `mousemove`.
- * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.
- * DOM Level 2 specifies that all mouse events bubble by default.
- * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled
- * using `preventDefault`. DOM Level 2 specifies that all mouse events except
- * `mousemove` can be cancelled. This defaults to `false` for `mousemove`.
- * @param {Boolean} [options.ctrlKey=false] `true` if one of the CTRL keys is
- * pressed while the event is firing.
- * @param {Boolean} [options.altKey=false] `true` if one of the ALT keys is
- * pressed while the event is firing.
- * @param {Boolean} [options.shiftKey=false] `true` if one of the SHIFT keys is
- * pressed while the event is firing.
- * @param {Boolean} [options.metaKey=false] `true` if one of the META keys is
- * pressed while the event is firing.
- * @param {Number} [options.detail=1] The number of times the mouse button has
- * been used.
- * @param {Number} [options.screenX=0] The x-coordinate on the screen at which point
- * the event occurred.
- * @param {Number} [options.screenY=0] The y-coordinate on the screen at which point
- * the event occurred.
- * @param {Number} [options.clientX=0] The x-coordinate on the client at which point
- * the event occurred.
- * @param {Number} [options.clientY=0] The y-coordinate on the client at which point
- * the event occurred.
- * @param {Number} [options.button=0] The button being pressed while the event is
- * executing. The value should be 0 for the primary mouse button (typically the
- * left button), 1 for the tertiary mouse button (typically the middle button),
- * and 2 for the secondary mouse button (typically the right button).
- * @param {HTMLElement} [options.relatedTarget=null] For `mouseout` events, this
- * is the element that the mouse has moved to. For `mouseover` events, this is
- * the element that the mouse has moved from. This argument is ignored for all
- * other events.
- * @param {Window} [view=window] The view containing the target. This is typically
- * the window object.
- * @private
- */
- injectMouseEvent: function(target, options, view) {
- var type = options.type,
- customEvent = null;
- view = view || window;
- //check for DOM-compliant browsers first
- if (doc.createEvent) {
- customEvent = doc.createEvent("MouseEvents");
- //Safari 2.x (WebKit 418) still doesn't implement initMouseEvent()
- if (customEvent.initMouseEvent) {
- 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);
- } else {
- //Safari
- //the closest thing available in Safari 2.x is UIEvents
- customEvent = doc.createEvent("UIEvents");
- customEvent.initEvent(type, options.bubbles, options.cancelable);
- customEvent.view = view;
- customEvent.detail = options.detail;
- customEvent.screenX = options.screenX;
- customEvent.screenY = options.screenY;
- customEvent.clientX = options.clientX;
- customEvent.clientY = options.clientY;
- customEvent.ctrlKey = options.ctrlKey;
- customEvent.altKey = options.altKey;
- customEvent.metaKey = options.metaKey;
- customEvent.shiftKey = options.shiftKey;
- customEvent.button = options.button;
- customEvent.relatedTarget = options.relatedTarget;
- }
- /*
- * Check to see if relatedTarget has been assigned. Firefox
- * versions less than 2.0 don't allow it to be assigned via
- * initMouseEvent() and the property is readonly after event
- * creation, so in order to keep YAHOO.util.getRelatedTarget()
- * working, assign to the IE proprietary toElement property
- * for mouseout event and fromElement property for mouseover
- * event.
- */
- if (options.relatedTarget && !customEvent.relatedTarget) {
- if (type == "mouseout") {
- customEvent.toElement = options.relatedTarget;
- } else if (type == "mouseover") {
- customEvent.fromElement = options.relatedTarget;
- }
- }
- target.dispatchEvent(customEvent);
- } else if (doc.createEventObject) {
- //IE
- customEvent = doc.createEventObject();
- customEvent.bubbles = options.bubbles;
- customEvent.cancelable = options.cancelable;
- customEvent.view = view;
- customEvent.detail = options.detail;
- customEvent.screenX = options.screenX;
- customEvent.screenY = options.screenY;
- customEvent.clientX = options.clientX;
- customEvent.clientY = options.clientY;
- customEvent.ctrlKey = options.ctrlKey;
- customEvent.altKey = options.altKey;
- customEvent.metaKey = options.metaKey;
- customEvent.shiftKey = options.shiftKey;
- customEvent.button = Player.ieButtonCodeMap[options.button] || 0;
- /*
- * Have to use relatedTarget because IE won't allow assignment
- * to toElement or fromElement on generic events. This keeps
- * YAHOO.util.customEvent.getRelatedTarget() functional.
- */
- customEvent.relatedTarget = options.relatedTarget;
- target.fireEvent('on' + type, customEvent);
- } else {
- return false;
- }
- return true;
- },
- /**
- * Injects a UI event using the given event information to populate the event
- * object.
- *
- * @param {HTMLElement} target The target of the given event.
- * @param {Object} options
- * @param {String} options.type The type of event to fire. This can be any one of
- * the following: `click`, `dblclick`, `mousedown`, `mouseup`, `mouseout`,
- * `mouseover` and `mousemove`.
- * @param {Boolean} [options.bubbles=true] `tru` if the event can be bubbled up.
- * DOM Level 2 specifies that all mouse events bubble by default.
- * @param {Boolean} [options.cancelable=true] `true` if the event can be canceled
- * using `preventDefault`. DOM Level 2 specifies that all mouse events except
- * `mousemove` can be canceled. This defaults to `false` for `mousemove`.
- * @param {Number} [options.detail=1] The number of times the mouse button has been
- * used.
- * @param {Window} [view=window] The view containing the target. This is typically
- * the window object.
- * @private
- */
- injectUIEvent: function(target, options, view) {
- var customEvent = null;
- view = view || window;
- //check for DOM-compliant browsers first
- if (doc.createEvent) {
- //just a generic UI Event object is needed
- customEvent = doc.createEvent("UIEvents");
- customEvent.initUIEvent(options.type, options.bubbles, options.cancelable, view, options.detail);
- target.dispatchEvent(customEvent);
- } else if (doc.createEventObject) {
- //IE
- customEvent = doc.createEventObject();
- customEvent.bubbles = options.bubbles;
- customEvent.cancelable = options.cancelable;
- customEvent.view = view;
- customEvent.detail = options.detail;
- target.fireEvent("on" + options.type, customEvent);
- } else {
- return false;
- }
- return true;
- }
- }
- };
- });
- // statics
- /**
- * @extends Ext.ux.event.Driver
- * Event recorder.
- */
- Ext.define('Ext.ux.event.Recorder', function(Recorder) {
- function apply() {
- var a = arguments,
- n = a.length,
- obj = {
- kind: 'other'
- },
- i;
- for (i = 0; i < n; ++i) {
- Ext.apply(obj, arguments[i]);
- }
- if (obj.alt && !obj.event) {
- obj.event = obj.alt;
- }
- return obj;
- }
- function key(extra) {
- return apply({
- kind: 'keyboard',
- modKeys: true,
- key: true
- }, extra);
- }
- function mouse(extra) {
- return apply({
- kind: 'mouse',
- button: true,
- modKeys: true,
- xy: true
- }, extra);
- }
- var eventsToRecord = {
- keydown: key(),
- keypress: key(),
- keyup: key(),
- dragmove: mouse({
- alt: 'mousemove',
- pageCoords: true,
- whileDrag: true
- }),
- mousemove: mouse({
- pageCoords: true
- }),
- mouseover: mouse(),
- mouseout: mouse(),
- click: mouse(),
- wheel: mouse({
- wheel: true
- }),
- mousedown: mouse({
- press: true
- }),
- mouseup: mouse({
- release: true
- }),
- scroll: apply({
- listen: false
- }),
- focus: apply(),
- blur: apply()
- };
- for (var key in eventsToRecord) {
- if (!eventsToRecord[key].event) {
- eventsToRecord[key].event = key;
- }
- }
- eventsToRecord.wheel.event = null;
- // must detect later
- return {
- extend: 'Ext.ux.event.Driver',
- /**
- * @event add
- * Fires when an event is added to the recording.
- * @param {Ext.ux.event.Recorder} this
- * @param {Object} eventDescriptor The event descriptor.
- */
- /**
- * @event coalesce
- * Fires when an event is coalesced. This edits the tail of the recorded
- * event list.
- * @param {Ext.ux.event.Recorder} this
- * @param {Object} eventDescriptor The event descriptor that was coalesced.
- */
- eventsToRecord: eventsToRecord,
- ignoreIdRegEx: /ext-gen(?:\d+)/,
- inputRe: /^(input|textarea)$/i,
- constructor: function(config) {
- var me = this,
- events = config && config.eventsToRecord;
- if (events) {
- me.eventsToRecord = Ext.apply(Ext.apply({}, me.eventsToRecord), // duplicate
- events);
- // and merge
- delete config.eventsToRecord;
- }
- // don't smash
- me.callParent(arguments);
- me.clear();
- me.modKeys = [];
- me.attachTo = me.attachTo || window;
- },
- clear: function() {
- this.eventsRecorded = [];
- },
- listenToEvent: function(event) {
- var me = this,
- el = me.attachTo.document.body,
- fn = function() {
- return me.onEvent.apply(me, arguments);
- },
- cleaner = {};
- if (el.attachEvent && el.ownerDocument.documentMode < 10) {
- event = 'on' + event;
- el.attachEvent(event, fn);
- cleaner.destroy = function() {
- if (fn) {
- el.detachEvent(event, fn);
- fn = null;
- }
- };
- } else {
- el.addEventListener(event, fn, true);
- cleaner.destroy = function() {
- if (fn) {
- el.removeEventListener(event, fn, true);
- fn = null;
- }
- };
- }
- return cleaner;
- },
- coalesce: function(rec, ev) {
- var me = this,
- events = me.eventsRecorded,
- length = events.length,
- tail = length && events[length - 1],
- tail2 = (length > 1) && events[length - 2],
- tail3 = (length > 2) && events[length - 3];
- if (!tail) {
- return false;
- }
- if (rec.type === 'mousemove') {
- if (tail.type === 'mousemove' && rec.ts - tail.ts < 200) {
- rec.ts = tail.ts;
- events[length - 1] = rec;
- return true;
- }
- } else if (rec.type === 'click') {
- if (tail2 && tail.type === 'mouseup' && tail2.type === 'mousedown') {
- 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)) {
- events.pop();
- // remove mouseup
- tail2.type = 'mduclick';
- return true;
- }
- }
- } else if (rec.type === 'keyup') {
- // tail3 = { type: "type", text: "..." },
- // tail2 = { type: "keydown", charCode: 65, keyCode: 65 },
- // tail = { type: "keypress", charCode: 97, keyCode: 97 },
- // rec = { type: "keyup", charCode: 65, keyCode: 65 },
- if (tail2 && tail.type === 'keypress' && tail2.type === 'keydown') {
- if (rec.target === tail.target && rec.target === tail2.target) {
- events.pop();
- // remove keypress
- tail2.type = 'type';
- tail2.text = String.fromCharCode(tail.charCode);
- delete tail2.charCode;
- delete tail2.keyCode;
- if (tail3 && tail3.type === 'type') {
- if (tail3.text && tail3.target === tail2.target) {
- tail3.text += tail2.text;
- events.pop();
- }
- }
- return true;
- }
- }
- // tail = { type: "keydown", charCode: 40, keyCode: 40 },
- // rec = { type: "keyup", charCode: 40, keyCode: 40 },
- else if (me.completeKeyStroke(tail, rec)) {
- tail.type = 'type';
- me.completeSpecialKeyStroke(ev.target, tail, rec);
- return true;
- }
- // tail2 = { type: "keydown", charCode: 40, keyCode: 40 },
- // tail = { type: "scroll", ... },
- // rec = { type: "keyup", charCode: 40, keyCode: 40 },
- else if (tail.type === 'scroll' && me.completeKeyStroke(tail2, rec)) {
- tail2.type = 'type';
- me.completeSpecialKeyStroke(ev.target, tail2, rec);
- // swap the order of type and scroll events
- events.pop();
- events.pop();
- events.push(tail, tail2);
- return true;
- }
- }
- return false;
- },
- completeKeyStroke: function(down, up) {
- if (down && down.type === 'keydown' && down.keyCode === up.keyCode) {
- delete down.charCode;
- return true;
- }
- return false;
- },
- completeSpecialKeyStroke: function(target, down, up) {
- var key = this.specialKeysByCode[up.keyCode];
- if (key && this.inputRe.test(target.tagName)) {
- // home,end,arrow keys + shift get crazy, so encode selection/caret
- delete down.keyCode;
- down.key = key;
- down.selection = this.getTextSelection(target);
- if (down.selection[0] === down.selection[1]) {
- down.caret = down.selection[0];
- delete down.selection;
- }
- return true;
- }
- return false;
- },
- getElementXPath: function(el) {
- var me = this,
- good = false,
- xpath = [],
- count, sibling, t, tag;
- for (t = el; t; t = t.parentNode) {
- if (t == me.attachTo.document.body) {
- xpath.unshift('~');
- good = true;
- break;
- }
- if (t.id && !me.ignoreIdRegEx.test(t.id)) {
- xpath.unshift('#' + t.id);
- good = true;
- break;
- }
- for (count = 1 , sibling = t; !!(sibling = sibling.previousSibling); ) {
- if (sibling.tagName == t.tagName) {
- ++count;
- }
- }
- tag = t.tagName.toLowerCase();
- if (count < 2) {
- xpath.unshift(tag);
- } else {
- xpath.unshift(tag + '[' + count + ']');
- }
- }
- return good ? xpath.join('/') : null;
- },
- getRecordedEvents: function() {
- return this.eventsRecorded;
- },
- onEvent: function(ev) {
- var me = this,
- e = new Ext.event.Event(ev),
- info = me.eventsToRecord[e.type],
- root, modKeys, elXY,
- rec = {
- type: e.type,
- ts: me.getTimestamp(),
- target: me.getElementXPath(e.target)
- },
- xy;
- if (!info || !rec.target) {
- return;
- }
- root = e.target.ownerDocument;
- root = root.defaultView || root.parentWindow;
- // Standards || IE
- if (root !== me.attachTo) {
- return;
- }
- if (me.eventsToRecord.scroll) {
- me.syncScroll(e.target);
- }
- if (info.xy) {
- xy = e.getXY();
- if (info.pageCoords || !rec.target) {
- rec.px = xy[0];
- rec.py = xy[1];
- } else {
- elXY = Ext.fly(e.getTarget()).getXY();
- xy[0] -= elXY[0];
- xy[1] -= elXY[1];
- rec.x = xy[0];
- rec.y = xy[1];
- }
- }
- if (info.button) {
- if ('buttons' in ev) {
- rec.button = ev.buttons;
- } else // LEFT=1, RIGHT=2, MIDDLE=4, etc.
- {
- rec.button = ev.button;
- }
- if (!rec.button && info.whileDrag) {
- return;
- }
- }
- if (info.wheel) {
- rec.type = 'wheel';
- if (info.event === 'wheel') {
- // Current FireFox (technically IE9+ if we use addEventListener but
- // checking document.onwheel does not detect this)
- rec.dx = ev.deltaX;
- rec.dy = ev.deltaY;
- } else if (typeof ev.wheelDeltaX === 'number') {
- // new WebKit has both X & Y
- rec.dx = -1 / 40 * ev.wheelDeltaX;
- rec.dy = -1 / 40 * ev.wheelDeltaY;
- } else if (ev.wheelDelta) {
- // old WebKit and IE
- rec.dy = -1 / 40 * ev.wheelDelta;
- } else if (ev.detail) {
- // Old Gecko
- rec.dy = ev.detail;
- }
- }
- if (info.modKeys) {
- me.modKeys[0] = e.altKey ? 'A' : '';
- me.modKeys[1] = e.ctrlKey ? 'C' : '';
- me.modKeys[2] = e.metaKey ? 'M' : '';
- me.modKeys[3] = e.shiftKey ? 'S' : '';
- modKeys = me.modKeys.join('');
- if (modKeys) {
- rec.modKeys = modKeys;
- }
- }
- if (info.key) {
- rec.charCode = e.getCharCode();
- rec.keyCode = e.getKey();
- }
- if (me.coalesce(rec, e)) {
- me.fireEvent('coalesce', me, rec);
- } else {
- me.eventsRecorded.push(rec);
- me.fireEvent('add', me, rec);
- }
- },
- onStart: function() {
- var me = this,
- ddm = me.attachTo.Ext.dd.DragDropManager,
- evproto = me.attachTo.Ext.EventObjectImpl.prototype,
- special = [];
- // FireFox does not support the 'mousewheel' event but does support the
- // 'wheel' event instead.
- Recorder.prototype.eventsToRecord.wheel.event = ('onwheel' in me.attachTo.document) ? 'wheel' : 'mousewheel';
- me.listeners = [];
- Ext.Object.each(me.eventsToRecord, function(name, value) {
- if (value && value.listen !== false) {
- if (!value.event) {
- value.event = name;
- }
- if (value.alt && value.alt !== name) {
- // The 'drag' event is just mousemove while buttons are pressed,
- // so if there is a mousemove entry as well, ignore the drag
- if (!me.eventsToRecord[value.alt]) {
- special.push(value);
- }
- } else {
- me.listeners.push(me.listenToEvent(value.event));
- }
- }
- });
- Ext.each(special, function(info) {
- me.eventsToRecord[info.alt] = info;
- me.listeners.push(me.listenToEvent(info.alt));
- });
- me.ddmStopEvent = ddm.stopEvent;
- ddm.stopEvent = Ext.Function.createSequence(ddm.stopEvent, function(e) {
- me.onEvent(e);
- });
- me.evStopEvent = evproto.stopEvent;
- evproto.stopEvent = Ext.Function.createSequence(evproto.stopEvent, function() {
- me.onEvent(this);
- });
- },
- onStop: function() {
- var me = this;
- Ext.destroy(me.listeners);
- me.listeners = null;
- me.attachTo.Ext.dd.DragDropManager.stopEvent = me.ddmStopEvent;
- me.attachTo.Ext.EventObjectImpl.prototype.stopEvent = me.evStopEvent;
- },
- samePt: function(pt1, pt2) {
- return pt1.x == pt2.x && pt1.y == pt2.y;
- },
- syncScroll: function(el) {
- var me = this,
- ts = me.getTimestamp(),
- oldX, oldY, x, y, scrolled, rec;
- for (var p = el; p; p = p.parentNode) {
- oldX = p.$lastScrollLeft;
- oldY = p.$lastScrollTop;
- x = p.scrollLeft;
- y = p.scrollTop;
- scrolled = false;
- if (oldX !== x) {
- if (x) {
- scrolled = true;
- }
- p.$lastScrollLeft = x;
- }
- if (oldY !== y) {
- if (y) {
- scrolled = true;
- }
- p.$lastScrollTop = y;
- }
- if (scrolled) {
- //console.log('scroll x:' + x + ' y:' + y, p);
- me.eventsRecorded.push(rec = {
- type: 'scroll',
- target: me.getElementXPath(p),
- ts: ts,
- pos: [
- x,
- y
- ]
- });
- me.fireEvent('add', me, rec);
- }
- if (p.tagName === 'BODY') {
- break;
- }
- }
- }
- };
- });
- /**
- * Describes a gauge needle as a shape defined in SVG path syntax.
- *
- * Note: this class and its subclasses are not supposed to be instantiated directly
- * - an object should be passed the gauge's {@link Ext.ux.gauge.Gauge#needle}
- * config instead. Needle instances are also not supposed to be moved
- * between gauges.
- */
- Ext.define('Ext.ux.gauge.needle.Abstract', {
- mixins: [
- 'Ext.mixin.Factoryable'
- ],
- alias: 'gauge.needle.abstract',
- isNeedle: true,
- config: {
- /**
- * The generator function for the needle's shape.
- * Because the gauge component is resizable, and it is generally
- * desirable to resize the needle along with the gauge, the needle's
- * shape should have an ability to grow, typically non-uniformly,
- * which necessitates a generator function that will update the needle's
- * path, so that its proportions are appropriate for the current gauge size.
- *
- * The generator function is given two parameters: the inner and outer
- * radius of the needle. For example, for a straight arrow, the path
- * definition is expected to have the base of the needle at the origin
- * - (0, 0) coordinates - and point downwards. The needle will be automatically
- * translated to the center of the gauge and rotated to represent the current
- * gauge {@link Ext.ux.gauge.Gauge#value value}.
- *
- * @param {Function} path The path generator function.
- * @param {Number} path.innerRadius The function's first parameter.
- * @param {Number} path.outerRadius The function's second parameter.
- * @return {String} path.return The shape of the needle in the SVG path syntax returned by
- * the generator function.
- */
- path: null,
- /**
- * The inner radius of the needle. This works just like the `innerRadius`
- * config of the {@link Ext.ux.gauge.Gauge#trackStyle}.
- * The default value is `25` to make sure the needle doesn't overlap with
- * the value of the gauge shown at its center by default.
- *
- * @param {Number/String} [innerRadius=25]
- */
- innerRadius: 25,
- /**
- * The outer radius of the needle. This works just like the `outerRadius`
- * config of the {@link Ext.ux.gauge.Gauge#trackStyle}.
- *
- * @param {Number/String} [outerRadius='100% - 20']
- */
- outerRadius: '100% - 20',
- /**
- * The shape generated by the {@link #path} function is used as the value
- * for the `d` attribute of the SVG `<path>` element. This element
- * has the default class name of `.x-gauge-needle`, so that CSS can be used
- * to give all gauge needles some common styling. To style a particular needle,
- * one can use this config to add styles to the needle's `<path>` element directly,
- * or use a custom {@link Ext.ux.gauge.Gauge#cls class} for the needle's gauge
- * and style the needle from there.
- *
- * This config is not supposed to be updated manually, the styles should
- * always be updated by the means of the `setStyle` call. For example,
- * this is not allowed:
- *
- * gauge.getStyle().fill = 'red'; // WRONG!
- * gauge.setStyle({ 'fill': 'red' }); // correct
- *
- * Subsequent calls to the `setStyle` will add to the styles set previously
- * or overwrite their values, but won't remove them. If you'd like to style
- * from a clean slate, setting the style to `null` first will remove the styles
- * previously set:
- *
- * gauge.getNeedle().setStyle(null);
- *
- * If an SVG shape was produced by a designer rather than programmatically,
- * in other words, the {@link #path} function returns the same shape regardless
- * of the parameters it was given, the uniform scaling of said shape is the only
- * option, if one wants to use gauges of different sizes. In this case,
- * it's possible to specify the desired scale by using the `transform` style,
- * for example:
- *
- * transform: 'scale(0.35)'
- *
- * @param {Object} style
- */
- style: null,
- /**
- * @private
- * @param {Number} radius
- */
- radius: 0,
- /**
- * @private
- * Expected in the initial config, required during construction.
- * @param {Ext.ux.gauge.Gauge} gauge
- */
- gauge: null
- },
- constructor: function(config) {
- this.initConfig(config);
- },
- applyInnerRadius: function(innerRadius) {
- return this.getGauge().getRadiusFn(innerRadius);
- },
- applyOuterRadius: function(outerRadius) {
- return this.getGauge().getRadiusFn(outerRadius);
- },
- updateRadius: function() {
- this.regeneratePath();
- },
- setTransform: function(centerX, centerY, rotation) {
- var needleGroup = this.getNeedleGroup();
- needleGroup.setStyle('transform', 'translate(' + centerX + 'px,' + centerY + 'px) ' + 'rotate(' + rotation + 'deg)');
- },
- applyPath: function(path) {
- return Ext.isFunction(path) ? path : null;
- },
- updatePath: function(path) {
- this.regeneratePath(path);
- },
- regeneratePath: function(path) {
- path = path || this.getPath();
- var me = this,
- radius = me.getRadius(),
- inner = me.getInnerRadius()(radius),
- outer = me.getOuterRadius()(radius),
- d = outer > inner ? path(inner, outer) : '';
- me.getNeedlePath().dom.setAttribute('d', d);
- },
- getNeedleGroup: function() {
- var gauge = this.getGauge(),
- group = this.needleGroup;
- // The gauge positions the needle by calling its `setTransform` method,
- // which applies a transformation to the needle's group, that contains
- // the actual path element. This is done because we need the ability to
- // transform the path independently from it's position in the gauge.
- // For example, if the needle has to be made bigger, is shouldn't be
- // part of the transform that centers it in the gauge and rotates it
- // to point at the current value.
- if (!group) {
- group = this.needleGroup = Ext.get(document.createElementNS(gauge.svgNS, 'g'));
- gauge.getSvg().appendChild(group);
- }
- return group;
- },
- getNeedlePath: function() {
- var me = this,
- pathElement = me.pathElement;
- if (!pathElement) {
- pathElement = me.pathElement = Ext.get(document.createElementNS(me.getGauge().svgNS, 'path'));
- pathElement.dom.setAttribute('class', Ext.baseCSSPrefix + 'gauge-needle');
- me.getNeedleGroup().appendChild(pathElement);
- }
- return pathElement;
- },
- updateStyle: function(style) {
- var pathElement = this.getNeedlePath();
- // Note that we are setting the `style` attribute, e.g `style="fill: red"`,
- // instead of path attributes individually, e.g. `fill="red"` because
- // the attribute styles defined in CSS classes will override the values
- // of attributes set on the elements individually.
- if (Ext.isObject(style)) {
- pathElement.setStyle(style);
- } else {
- pathElement.dom.removeAttribute('style');
- }
- },
- destroy: function() {
- var me = this;
- me.pathElement = Ext.destroy(me.pathElement);
- me.needleGroup = Ext.destroy(me.needleGroup);
- me.setGauge(null);
- }
- });
- /**
- * Displays a value within the given interval as a gauge. For example:
- *
- * @example
- * Ext.create({
- * xtype: 'panel',
- * renderTo: document.body,
- * width: 200,
- * height: 200,
- * layout: 'fit',
- * items: {
- * xtype: 'gauge',
- * padding: 20,
- * value: 55,
- * minValue: 40,
- * maxValue: 80
- * }
- * });
- *
- * It's also possible to use gauges to create loading indicators:
- *
- * @example
- * Ext.create({
- * xtype: 'panel',
- * renderTo: document.body,
- * width: 200,
- * height: 200,
- * layout: 'fit',
- * items: {
- * xtype: 'gauge',
- * padding: 20,
- * trackStart: 0,
- * trackLength: 360,
- * value: 20,
- * valueStyle: {
- * round: true
- * },
- * textTpl: 'Loading...',
- * animation: {
- * easing: 'linear',
- * duration: 100000
- * }
- * }
- * }).items.first().setAngleOffset(360 * 100);
- *
- * Gauges can contain needles as well.
- *
- * @example
- * Ext.create({
- * xtype: 'panel',
- * renderTo: document.body,
- * width: 200,
- * height: 200,
- * layout: 'fit',
- * items: {
- * xtype: 'gauge',
- * padding: 20,
- * value: 55,
- * minValue: 40,
- * maxValue: 80,
- * needle: 'wedge'
- * }
- * });
- *
- */
- Ext.define('Ext.ux.gauge.Gauge', {
- alternateClassName: 'Ext.ux.Gauge',
- extend: 'Ext.Gadget',
- xtype: 'gauge',
- requires: [
- 'Ext.ux.gauge.needle.Abstract',
- 'Ext.util.Region'
- ],
- config: {
- /**
- * @cfg {Number/String} padding
- * Gauge sector padding in pixels or percent of width/height, whichever is smaller.
- */
- padding: 10,
- /**
- * @cfg {Number} trackStart
- * The angle in the [0, 360) interval at which the gauge's track sector starts.
- * E.g. 0 for 3 o-clock, 90 for 6 o-clock, 180 for 9 o-clock, 270 for noon.
- */
- trackStart: 135,
- /**
- * @cfg {Number} trackLength
- * The angle in the (0, 360] interval to add to the {@link #trackStart} angle
- * to determine the angle at which the track ends.
- */
- trackLength: 270,
- /**
- * @cfg {Number} angleOffset
- * The angle at which the {@link #minValue} starts in case of a circular gauge.
- */
- angleOffset: 0,
- /**
- * @cfg {Number} minValue
- * The minimum value that the gauge can represent.
- */
- minValue: 0,
- /**
- * @cfg {Number} maxValue
- * The maximum value that the gauge can represent.
- */
- maxValue: 100,
- /**
- * @cfg {Number} value
- * The current value of the gauge.
- */
- value: 50,
- /**
- * @cfg {Ext.ux.gauge.needle.Abstract} needle
- * A config object for the needle to be used by the gauge.
- * The needle will track the current {@link #value}.
- * The default needle type is 'diamond', so if a config like
- *
- * needle: {
- * outerRadius: '100%'
- * }
- *
- * is used, the app/view still has to require
- * the `Ext.ux.gauge.needle.Diamond` class.
- * If a type is specified explicitly
- *
- * needle: {
- * type: 'arrow'
- * }
- *
- * it's straightforward which class should be required.
- */
- needle: null,
- needleDefaults: {
- cached: true,
- $value: {
- type: 'diamond'
- }
- },
- /**
- * @cfg {Boolean} [clockwise=true]
- * `true` - {@link #cfg!value} increments in a clockwise fashion
- * `false` - {@link #cfg!value} increments in an anticlockwise fashion
- */
- clockwise: true,
- /**
- * @cfg {Ext.XTemplate} textTpl
- * The template for the text in the center of the gauge.
- * The available data values are:
- * - `value` - The {@link #cfg!value} of the gauge.
- * - `percent` - The value as a percentage between 0 and 100.
- * - `minValue` - The value of the {@link #cfg!minValue} config.
- * - `maxValue` - The value of the {@link #cfg!maxValue} config.
- * - `delta` - The delta between the {@link #cfg!minValue} and {@link #cfg!maxValue}.
- */
- textTpl: [
- '<tpl>{value:number("0.00")}%</tpl>'
- ],
- /**
- * @cfg {String} [textAlign='c-c']
- * If the gauge has a donut hole, the text will be centered inside it.
- * Otherwise, the text will be centered in the middle of the gauge's
- * bounding box. This config allows to alter the position of the text
- * in the latter case. See the docs for the `align` option to the
- * {@link Ext.util.Region#alignTo} method for possible ways of alignment
- * of the text to the guage's bounding box.
- */
- textAlign: 'c-c',
- /**
- * @cfg {Object} textOffset
- * This config can be used to displace the {@link #textTpl text} from its default
- * position in the center of the gauge by providing values for horizontal and
- * vertical displacement.
- * @cfg {Number} textOffset.dx Horizontal displacement.
- * @cfg {Number} textOffset.dy Vertical displacement.
- */
- textOffset: {
- dx: 0,
- dy: 0
- },
- /**
- * @cfg {Object} trackStyle
- * Track sector styles.
- * @cfg {String/Object[]} trackStyle.fill Track sector fill color. Defaults to CSS value.
- * It's also possible to have a linear gradient fill that starts at the top-left corner
- * of the gauge and ends at its bottom-right corner, by providing an array of color stop
- * objects. For example:
- *
- * trackStyle: {
- * fill: [{
- * offset: 0,
- * color: 'green',
- * opacity: 0.8
- * }, {
- * offset: 1,
- * color: 'gold'
- * }]
- * }
- *
- * @cfg {Number} trackStyle.fillOpacity Track sector fill opacity. Defaults to CSS value.
- * @cfg {String} trackStyle.stroke Track sector stroke color. Defaults to CSS value.
- * @cfg {Number} trackStyle.strokeOpacity Track sector stroke opacity. Defaults to CSS value.
- * @cfg {Number} trackStyle.strokeWidth Track sector stroke width. Defaults to CSS value.
- * @cfg {Number/String} [trackStyle.outerRadius='100%'] The outer radius of the track sector.
- * For example:
- *
- * outerRadius: '90%', // 90% of the maximum radius
- * outerRadius: 100, // radius of 100 pixels
- * outerRadius: '70% + 5', // 70% of the maximum radius plus 5 pixels
- * outerRadius: '80% - 10', // 80% of the maximum radius minus 10 pixels
- *
- * @cfg {Number/String} [trackStyle.innerRadius='50%'] The inner radius of the track sector.
- * See the `trackStyle.outerRadius` config documentation for more information.
- * @cfg {Boolean} [trackStyle.round=false] Whether to round the track sector edges or not.
- */
- trackStyle: {
- outerRadius: '100%',
- innerRadius: '100% - 20',
- round: false
- },
- /**
- * @cfg {Object} valueStyle
- * Value sector styles.
- * @cfg {String/Object[]} valueStyle.fill Value sector fill color. Defaults to CSS value.
- * See the `trackStyle.fill` config documentation for more information.
- * @cfg {Number} valueStyle.fillOpacity Value sector fill opacity. Defaults to CSS value.
- * @cfg {String} valueStyle.stroke Value sector stroke color. Defaults to CSS value.
- * @cfg {Number} valueStyle.strokeOpacity Value sector stroke opacity. Defaults to CSS value.
- * @cfg {Number} valueStyle.strokeWidth Value sector stroke width. Defaults to CSS value.
- * @cfg {Number/String} [valueStyle.outerRadius='100% - 4'] The outer radius of the value sector.
- * See the `trackStyle.outerRadius` config documentation for more information.
- * @cfg {Number/String} [valueStyle.innerRadius='50% + 4'] The inner radius of the value sector.
- * See the `trackStyle.outerRadius` config documentation for more information.
- * @cfg {Boolean} [valueStyle.round=false] Whether to round the value sector edges or not.
- */
- valueStyle: {
- outerRadius: '100% - 2',
- innerRadius: '100% - 18',
- round: false
- },
- /**
- * @cfg {Object/Boolean} [animation=true]
- * The animation applied to the gauge on changes to the {@link #value}
- * and the {@link #angleOffset} configs. Defaults to 1 second animation
- * with the 'out' easing.
- * @cfg {Number} animation.duration The duraction of the animation.
- * @cfg {String} animation.easing The easing function to use for the animation.
- * Possible values are:
- * - `linear` - no easing, no acceleration
- * - `in` - accelerating from zero velocity
- * - `out` - (default) decelerating to zero velocity
- * - `inOut` - acceleration until halfway, then deceleration
- */
- animation: true
- },
- baseCls: Ext.baseCSSPrefix + 'gauge',
- template: [
- {
- reference: 'bodyElement',
- children: [
- {
- reference: 'textElement',
- cls: Ext.baseCSSPrefix + 'gauge-text'
- }
- ]
- }
- ],
- defaultBindProperty: 'value',
- pathAttributes: {
- // The properties in the `trackStyle` and `valueStyle` configs
- // that are path attributes.
- fill: true,
- fillOpacity: true,
- stroke: true,
- strokeOpacity: true,
- strokeWidth: true
- },
- easings: {
- linear: Ext.identityFn,
- // cubic easings
- 'in': function(t) {
- return t * t * t;
- },
- out: function(t) {
- return (--t) * t * t + 1;
- },
- inOut: function(t) {
- return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
- }
- },
- resizeDelay: 0,
- // in milliseconds
- resizeTimerId: 0,
- size: null,
- // cached size
- svgNS: 'http://www.w3.org/2000/svg',
- svg: null,
- // SVG document
- defs: null,
- // the `defs` section of the SVG document
- trackArc: null,
- valueArc: null,
- trackGradient: null,
- valueGradient: null,
- fx: null,
- // either the `value` or the `angleOffset` animation
- fxValue: 0,
- // the actual value rendered/animated
- fxAngleOffset: 0,
- constructor: function(config) {
- var me = this;
- me.fitSectorInRectCache = {
- startAngle: null,
- lengthAngle: null,
- minX: null,
- maxX: null,
- minY: null,
- maxY: null
- };
- me.interpolator = me.createInterpolator();
- me.callParent([
- config
- ]);
- me.el.on('resize', 'onElementResize', me);
- },
- doDestroy: function() {
- var me = this;
- Ext.undefer(me.resizeTimerId);
- me.el.un('resize', 'onElementResize', me);
- me.stopAnimation();
- me.setNeedle(null);
- me.trackGradient = Ext.destroy(me.trackGradient);
- me.valueGradient = Ext.destroy(me.valueGradient);
- me.defs = Ext.destroy(me.defs);
- me.svg = Ext.destroy(me.svg);
- me.callParent();
- },
- // <if classic>
- afterComponentLayout: function(width, height, oldWidth, oldHeight) {
- this.callParent([
- width,
- height,
- oldWidth,
- oldHeight
- ]);
- if (Ext.isIE9) {
- this.handleResize();
- }
- },
- // </if>
- onElementResize: function(element, size) {
- this.handleResize(size);
- },
- handleResize: function(size, instantly) {
- var me = this,
- el = me.element;
- if (!(el && (size = size || el.getSize()) && size.width && size.height)) {
- return;
- }
- me.resizeTimerId = Ext.undefer(me.resizeTimerId);
- if (!instantly && me.resizeDelay) {
- me.resizeTimerId = Ext.defer(me.handleResize, me.resizeDelay, me, [
- size,
- true
- ]);
- return;
- }
- me.size = size;
- me.resizeHandler(size);
- },
- updateMinValue: function(minValue) {
- var me = this;
- me.interpolator.setDomain(minValue, me.getMaxValue());
- if (!me.isConfiguring) {
- me.render();
- }
- },
- updateMaxValue: function(maxValue) {
- var me = this;
- me.interpolator.setDomain(me.getMinValue(), maxValue);
- if (!me.isConfiguring) {
- me.render();
- }
- },
- updateAngleOffset: function(angleOffset, oldAngleOffset) {
- var me = this,
- animation = me.getAnimation();
- me.fxAngleOffset = angleOffset;
- if (me.isConfiguring) {
- return;
- }
- if (animation.duration) {
- me.animate(oldAngleOffset, angleOffset, animation.duration, me.easings[animation.easing], function(angleOffset) {
- me.fxAngleOffset = angleOffset;
- me.render();
- });
- } else {
- me.render();
- }
- },
- //<debug>
- applyTrackStart: function(trackStart) {
- if (trackStart < 0 || trackStart >= 360) {
- Ext.raise("'trackStart' should be within [0, 360).");
- }
- return trackStart;
- },
- applyTrackLength: function(trackLength) {
- if (trackLength <= 0 || trackLength > 360) {
- Ext.raise("'trackLength' should be within (0, 360].");
- }
- return trackLength;
- },
- //</debug>
- updateTrackStart: function(trackStart) {
- var me = this;
- if (!me.isConfiguring) {
- me.render();
- }
- },
- updateTrackLength: function(trackLength) {
- var me = this;
- me.interpolator.setRange(0, trackLength);
- if (!me.isConfiguring) {
- me.render();
- }
- },
- applyPadding: function(padding) {
- if (typeof padding === 'string') {
- var ratio = parseFloat(padding) / 100;
- return function(x) {
- return x * ratio;
- };
- }
- return function() {
- return padding;
- };
- },
- updatePadding: function() {
- if (!this.isConfiguring) {
- this.render();
- }
- },
- applyValue: function(value) {
- var minValue = this.getMinValue(),
- maxValue = this.getMaxValue();
- return Math.min(Math.max(value, minValue), maxValue);
- },
- updateValue: function(value, oldValue) {
- var me = this,
- animation = me.getAnimation();
- me.fxValue = value;
- if (me.isConfiguring) {
- return;
- }
- me.writeText();
- if (animation.duration) {
- me.animate(oldValue, value, animation.duration, me.easings[animation.easing], function(value) {
- me.fxValue = value;
- me.render();
- });
- } else {
- me.render();
- }
- },
- applyTextTpl: function(textTpl) {
- if (textTpl && !textTpl.isTemplate) {
- textTpl = new Ext.XTemplate(textTpl);
- }
- return textTpl;
- },
- applyTextOffset: function(offset) {
- offset = offset || {};
- offset.dx = offset.dx || 0;
- offset.dy = offset.dy || 0;
- return offset;
- },
- updateTextTpl: function() {
- this.writeText();
- if (!this.isConfiguring) {
- this.centerText();
- }
- },
- // text will be centered on first size
- writeText: function(options) {
- var me = this,
- value = me.getValue(),
- minValue = me.getMinValue(),
- maxValue = me.getMaxValue(),
- delta = maxValue - minValue,
- textTpl = me.getTextTpl();
- textTpl.overwrite(me.textElement, {
- value: value,
- percent: (value - minValue) / delta * 100,
- minValue: minValue,
- maxValue: maxValue,
- delta: delta
- });
- },
- centerText: function(cx, cy, sectorRegion, innerRadius, outerRadius) {
- var textElement = this.textElement,
- textAlign = this.getTextAlign(),
- alignedRegion, textBox;
- if (Ext.Number.isEqual(innerRadius, 0, 0.1) || sectorRegion.isOutOfBound({
- x: cx,
- y: cy
- })) {
- alignedRegion = textElement.getRegion().alignTo({
- align: textAlign,
- // align text region's center to sector region's center
- target: sectorRegion
- });
- textElement.setLeft(alignedRegion.left);
- textElement.setTop(alignedRegion.top);
- } else {
- textBox = textElement.getBox();
- textElement.setLeft(cx - textBox.width / 2);
- textElement.setTop(cy - textBox.height / 2);
- }
- },
- camelCaseRe: /([a-z])([A-Z])/g,
- /**
- * @private
- */
- camelToHyphen: function(name) {
- return name.replace(this.camelCaseRe, '$1-$2').toLowerCase();
- },
- applyTrackStyle: function(trackStyle) {
- var me = this,
- trackGradient;
- trackStyle.innerRadius = me.getRadiusFn(trackStyle.innerRadius);
- trackStyle.outerRadius = me.getRadiusFn(trackStyle.outerRadius);
- if (Ext.isArray(trackStyle.fill)) {
- trackGradient = me.getTrackGradient();
- me.setGradientStops(trackGradient, trackStyle.fill);
- trackStyle.fill = 'url(#' + trackGradient.dom.getAttribute('id') + ')';
- }
- return trackStyle;
- },
- updateTrackStyle: function(trackStyle) {
- var me = this,
- trackArc = Ext.fly(me.getTrackArc()),
- name;
- for (name in trackStyle) {
- if (name in me.pathAttributes) {
- trackArc.setStyle(me.camelToHyphen(name), trackStyle[name]);
- } else {
- trackArc.setStyle(name, trackStyle[name]);
- }
- }
- },
- applyValueStyle: function(valueStyle) {
- var me = this,
- valueGradient;
- valueStyle.innerRadius = me.getRadiusFn(valueStyle.innerRadius);
- valueStyle.outerRadius = me.getRadiusFn(valueStyle.outerRadius);
- if (Ext.isArray(valueStyle.fill)) {
- valueGradient = me.getValueGradient();
- me.setGradientStops(valueGradient, valueStyle.fill);
- valueStyle.fill = 'url(#' + valueGradient.dom.getAttribute('id') + ')';
- }
- return valueStyle;
- },
- updateValueStyle: function(valueStyle) {
- var me = this,
- valueArc = Ext.fly(me.getValueArc()),
- name;
- for (name in valueStyle) {
- if (name in me.pathAttributes) {
- valueArc.setStyle(me.camelToHyphen(name), valueStyle[name]);
- } else {
- valueArc.setStyle(name, valueStyle[name]);
- }
- }
- },
- /**
- * @private
- */
- getRadiusFn: function(radius) {
- var result, pos, ratio,
- increment = 0;
- if (Ext.isNumber(radius)) {
- result = function() {
- return radius;
- };
- } else if (Ext.isString(radius)) {
- radius = radius.replace(/ /g, '');
- ratio = parseFloat(radius) / 100;
- pos = radius.search('%');
- // E.g. '100% - 4'
- if (pos < radius.length - 1) {
- increment = parseFloat(radius.substr(pos + 1));
- }
- result = function(radius) {
- return radius * ratio + increment;
- };
- result.ratio = ratio;
- }
- return result;
- },
- getSvg: function() {
- var me = this,
- svg = me.svg;
- if (!svg) {
- svg = me.svg = Ext.get(document.createElementNS(me.svgNS, 'svg'));
- me.bodyElement.append(svg);
- }
- return svg;
- },
- getTrackArc: function() {
- var me = this,
- trackArc = me.trackArc;
- if (!trackArc) {
- trackArc = me.trackArc = document.createElementNS(me.svgNS, 'path');
- me.getSvg().append(trackArc, true);
- // Note: Ext.dom.Element.addCls doesn't work on SVG elements,
- // as it simply assigns a class string to el.dom.className,
- // which in case of SVG is no simple string:
- // SVGAnimatedString {baseVal: "x-gauge-track", animVal: "x-gauge-track"}
- trackArc.setAttribute('class', Ext.baseCSSPrefix + 'gauge-track');
- }
- return trackArc;
- },
- getValueArc: function() {
- var me = this,
- valueArc = me.valueArc;
- me.getTrackArc();
- // make sure the track arc is created first for proper draw order
- if (!valueArc) {
- valueArc = me.valueArc = document.createElementNS(me.svgNS, 'path');
- me.getSvg().append(valueArc, true);
- valueArc.setAttribute('class', Ext.baseCSSPrefix + 'gauge-value');
- }
- return valueArc;
- },
- applyNeedle: function(needle, oldNeedle) {
- // Make sure the track and value elements have been already created,
- // so that the needle element renders on top.
- this.getValueArc();
- return Ext.Factory.gaugeNeedle.update(oldNeedle, needle, this, 'createNeedle', 'needleDefaults');
- },
- createNeedle: function(config) {
- return Ext.apply({
- gauge: this
- }, config);
- },
- getDefs: function() {
- var me = this,
- defs = me.defs;
- if (!defs) {
- defs = me.defs = Ext.get(document.createElementNS(me.svgNS, 'defs'));
- me.getSvg().appendChild(defs);
- }
- return defs;
- },
- /**
- * @private
- */
- setGradientSize: function(gradient, x1, y1, x2, y2) {
- gradient.setAttribute('x1', x1);
- gradient.setAttribute('y1', y1);
- gradient.setAttribute('x2', x2);
- gradient.setAttribute('y2', y2);
- },
- /**
- * @private
- */
- resizeGradients: function(size) {
- var me = this,
- trackGradient = me.getTrackGradient(),
- valueGradient = me.getValueGradient(),
- x1 = 0,
- y1 = size.height / 2,
- x2 = size.width,
- y2 = size.height / 2;
- me.setGradientSize(trackGradient.dom, x1, y1, x2, y2);
- me.setGradientSize(valueGradient.dom, x1, y1, x2, y2);
- },
- /**
- * @private
- */
- setGradientStops: function(gradient, stops) {
- var ln = stops.length,
- i, stopCfg, stopEl;
- while (gradient.firstChild) {
- gradient.removeChild(gradient.firstChild);
- }
- for (i = 0; i < ln; i++) {
- stopCfg = stops[i];
- stopEl = document.createElementNS(this.svgNS, 'stop');
- gradient.appendChild(stopEl);
- stopEl.setAttribute('offset', stopCfg.offset);
- stopEl.setAttribute('stop-color', stopCfg.color);
- ('opacity' in stopCfg) && stopEl.setAttribute('stop-opacity', stopCfg.opacity);
- }
- },
- getTrackGradient: function() {
- var me = this,
- trackGradient = me.trackGradient;
- if (!trackGradient) {
- trackGradient = me.trackGradient = Ext.get(document.createElementNS(me.svgNS, 'linearGradient'));
- // Using absolute values for x1, y1, x2, y2 attributes.
- trackGradient.dom.setAttribute('gradientUnits', 'userSpaceOnUse');
- me.getDefs().appendChild(trackGradient);
- Ext.get(trackGradient);
- }
- // assign unique ID
- return trackGradient;
- },
- getValueGradient: function() {
- var me = this,
- valueGradient = me.valueGradient;
- if (!valueGradient) {
- valueGradient = me.valueGradient = Ext.get(document.createElementNS(me.svgNS, 'linearGradient'));
- // Using absolute values for x1, y1, x2, y2 attributes.
- valueGradient.dom.setAttribute('gradientUnits', 'userSpaceOnUse');
- me.getDefs().appendChild(valueGradient);
- Ext.get(valueGradient);
- }
- // assign unique ID
- return valueGradient;
- },
- getArcPoint: function(centerX, centerY, radius, degrees) {
- var radians = degrees / 180 * Math.PI;
- return [
- centerX + radius * Math.cos(radians),
- centerY + radius * Math.sin(radians)
- ];
- },
- isCircle: function(startAngle, endAngle) {
- return Ext.Number.isEqual(Math.abs(endAngle - startAngle), 360, 0.001);
- },
- getArcPath: function(centerX, centerY, innerRadius, outerRadius, startAngle, endAngle, round) {
- var me = this,
- isCircle = me.isCircle(startAngle, endAngle),
- // It's not possible to draw a circle using arcs.
- endAngle = endAngle - 0.01,
- innerStartPoint = me.getArcPoint(centerX, centerY, innerRadius, startAngle),
- innerEndPoint = me.getArcPoint(centerX, centerY, innerRadius, endAngle),
- outerStartPoint = me.getArcPoint(centerX, centerY, outerRadius, startAngle),
- outerEndPoint = me.getArcPoint(centerX, centerY, outerRadius, endAngle),
- large = endAngle - startAngle <= 180 ? 0 : 1,
- path = [
- 'M',
- innerStartPoint[0],
- innerStartPoint[1],
- 'A',
- innerRadius,
- innerRadius,
- 0,
- large,
- 1,
- innerEndPoint[0],
- innerEndPoint[1]
- ],
- capRadius = (outerRadius - innerRadius) / 2;
- if (isCircle) {
- path.push('M', outerEndPoint[0], outerEndPoint[1]);
- } else {
- if (round) {
- path.push('A', capRadius, capRadius, 0, 0, 0, outerEndPoint[0], outerEndPoint[1]);
- } else {
- path.push('L', outerEndPoint[0], outerEndPoint[1]);
- }
- }
- path.push('A', outerRadius, outerRadius, 0, large, 0, outerStartPoint[0], outerStartPoint[1]);
- if (round && !isCircle) {
- path.push('A', capRadius, capRadius, 0, 0, 0, innerStartPoint[0], innerStartPoint[1]);
- }
- path.push('Z');
- return path.join(' ');
- },
- resizeHandler: function(size) {
- var me = this,
- svg = me.getSvg();
- svg.setSize(size);
- me.resizeGradients(size);
- me.render();
- },
- /**
- * @private
- * Creates a linear interpolator function that itself has a few methods:
- * - `setDomain(from, to)`
- * - `setRange(from, to)`
- * - `getDomain` - returns the domain as a [from, to] array
- * - `getRange` - returns the range as a [from, to] array
- * @param {Boolean} [rangeCheck=false]
- * Whether to allow out of bounds values for domain and range.
- * @return {Function} The interpolator function:
- * `interpolator(domainValue, isInvert)`.
- * If the `isInvert` parameter is `true`, the start of domain will correspond
- * to the end of range. This is useful, for example, when you want to render
- * increasing domain values counter-clockwise instead of clockwise.
- */
- createInterpolator: function(rangeCheck) {
- var domainStart = 0,
- domainDelta = 1,
- rangeStart = 0,
- rangeEnd = 1;
- var interpolator = function(x, invert) {
- var t = 0;
- if (domainDelta) {
- t = (x - domainStart) / domainDelta;
- if (rangeCheck) {
- t = Math.max(0, t);
- t = Math.min(1, t);
- }
- if (invert) {
- t = 1 - t;
- }
- }
- return (1 - t) * rangeStart + t * rangeEnd;
- };
- interpolator.setDomain = function(a, b) {
- domainStart = a;
- domainDelta = b - a;
- return this;
- };
- interpolator.setRange = function(a, b) {
- rangeStart = a;
- rangeEnd = b;
- return this;
- };
- interpolator.getDomain = function() {
- return [
- domainStart,
- domainStart + domainDelta
- ];
- };
- interpolator.getRange = function() {
- return [
- rangeStart,
- rangeEnd
- ];
- };
- return interpolator;
- },
- applyAnimation: function(animation) {
- if (true === animation) {
- animation = {};
- } else if (false === animation) {
- animation = {
- duration: 0
- };
- }
- if (!('duration' in animation)) {
- animation.duration = 1000;
- }
- if (!(animation.easing in this.easings)) {
- animation.easing = 'out';
- }
- return animation;
- },
- updateAnimation: function() {
- this.stopAnimation();
- },
- /**
- * @private
- * @param {Number} from
- * @param {Number} to
- * @param {Number} duration
- * @param {Function} easing
- * @param {Function} fn Function to execute on every frame of animation.
- * The function takes a single parameter - the value in the [from, to]
- * range, interpolated based on current time and easing function.
- * With certain easings, the value may overshoot the range slighly.
- * @param {Object} scope
- */
- animate: function(from, to, duration, easing, fn, scope) {
- var me = this,
- start = Ext.now(),
- interpolator = me.createInterpolator().setRange(from, to);
- function frame() {
- var now = Ext.AnimationQueue.frameStartTime,
- t = Math.min(now - start, duration) / duration,
- value = interpolator(easing(t));
- if (scope) {
- if (typeof fn === 'string') {
- scope[fn].call(scope, value);
- } else {
- fn.call(scope, value);
- }
- } else {
- fn(value);
- }
- if (t >= 1) {
- Ext.AnimationQueue.stop(frame, scope);
- me.fx = null;
- }
- }
- me.stopAnimation();
- Ext.AnimationQueue.start(frame, scope);
- me.fx = {
- frame: frame,
- scope: scope
- };
- },
- /**
- * Stops the current {@link #value} or {@link #angleOffset} animation.
- */
- stopAnimation: function() {
- var me = this;
- if (me.fx) {
- Ext.AnimationQueue.stop(me.fx.frame, me.fx.scope);
- me.fx = null;
- }
- },
- unitCircleExtrema: {
- 0: [
- 1,
- 0
- ],
- 90: [
- 0,
- 1
- ],
- 180: [
- -1,
- 0
- ],
- 270: [
- 0,
- -1
- ],
- 360: [
- 1,
- 0
- ],
- 450: [
- 0,
- 1
- ],
- 540: [
- -1,
- 0
- ],
- 630: [
- 0,
- -1
- ]
- },
- /**
- * @private
- */
- getUnitSectorExtrema: function(startAngle, lengthAngle) {
- var extrema = this.unitCircleExtrema,
- points = [],
- angle;
- for (angle in extrema) {
- if (angle > startAngle && angle < startAngle + lengthAngle) {
- points.push(extrema[angle]);
- }
- }
- return points;
- },
- /**
- * @private
- * Given a rect with a known width and height, find the maximum radius of the donut
- * sector that can fit into it, as well as the center point of such a sector.
- * The end and start angles of the sector are also known, as well as the relationship
- * between the inner and outer radii.
- */
- fitSectorInRect: function(width, height, startAngle, lengthAngle, ratio) {
- if (Ext.Number.isEqual(lengthAngle, 360, 0.001)) {
- return {
- cx: width / 2,
- cy: height / 2,
- radius: Math.min(width, height) / 2,
- region: new Ext.util.Region(0, width, height, 0)
- };
- }
- var me = this,
- points, xx, yy, minX, maxX, minY, maxY,
- cache = me.fitSectorInRectCache,
- sameAngles = cache.startAngle === startAngle && cache.lengthAngle === lengthAngle;
- if (sameAngles) {
- minX = cache.minX;
- maxX = cache.maxX;
- minY = cache.minY;
- maxY = cache.maxY;
- } else {
- points = me.getUnitSectorExtrema(startAngle, lengthAngle).concat([
- me.getArcPoint(0, 0, 1, startAngle),
- // start angle outer radius point
- me.getArcPoint(0, 0, ratio, startAngle),
- // start angle inner radius point
- me.getArcPoint(0, 0, 1, startAngle + lengthAngle),
- // end angle outer radius point
- me.getArcPoint(0, 0, ratio, startAngle + lengthAngle)
- ]);
- // end angle inner radius point
- xx = points.map(function(point) {
- return point[0];
- });
- yy = points.map(function(point) {
- return point[1];
- });
- // The bounding box of a unit sector with the given properties.
- minX = Math.min.apply(null, xx);
- maxX = Math.max.apply(null, xx);
- minY = Math.min.apply(null, yy);
- maxY = Math.max.apply(null, yy);
- cache.startAngle = startAngle;
- cache.lengthAngle = lengthAngle;
- cache.minX = minX;
- cache.maxX = maxX;
- cache.minY = minY;
- cache.maxY = maxY;
- }
- var sectorWidth = maxX - minX,
- sectorHeight = maxY - minY,
- scaleX = width / sectorWidth,
- scaleY = height / sectorHeight,
- scale = Math.min(scaleX, scaleY),
- // Region constructor takes: top, right, bottom, left.
- sectorRegion = new Ext.util.Region(minY * scale, maxX * scale, maxY * scale, minX * scale),
- rectRegion = new Ext.util.Region(0, width, height, 0),
- alignedRegion = sectorRegion.alignTo({
- align: 'c-c',
- // align sector region's center to rect region's center
- target: rectRegion
- }),
- dx = alignedRegion.left - minX * scale,
- dy = alignedRegion.top - minY * scale;
- return {
- cx: dx,
- cy: dy,
- radius: scale,
- region: alignedRegion
- };
- },
- /**
- * @private
- */
- fitSectorInPaddedRect: function(width, height, padding, startAngle, lengthAngle, ratio) {
- var result = this.fitSectorInRect(width - padding * 2, height - padding * 2, startAngle, lengthAngle, ratio);
- result.cx += padding;
- result.cy += padding;
- result.region.translateBy(padding, padding);
- return result;
- },
- /**
- * @private
- */
- normalizeAngle: function(angle) {
- return (angle % 360 + 360) % 360;
- },
- render: function() {
- if (!this.size) {
- return;
- }
- var me = this,
- textOffset = me.getTextOffset(),
- trackArc = me.getTrackArc(),
- valueArc = me.getValueArc(),
- needle = me.getNeedle(),
- clockwise = me.getClockwise(),
- value = me.fxValue,
- angleOffset = me.fxAngleOffset,
- trackLength = me.getTrackLength(),
- width = me.size.width,
- height = me.size.height,
- paddingFn = me.getPadding(),
- padding = paddingFn(Math.min(width, height)),
- trackStart = me.normalizeAngle(me.getTrackStart() + angleOffset),
- // in the range of [0, 360)
- trackEnd = trackStart + trackLength,
- // in the range of (0, 720)
- valueLength = me.interpolator(value),
- trackStyle = me.getTrackStyle(),
- valueStyle = me.getValueStyle(),
- sector = me.fitSectorInPaddedRect(width, height, padding, trackStart, trackLength, trackStyle.innerRadius.ratio),
- cx = sector.cx,
- cy = sector.cy,
- radius = sector.radius,
- trackInnerRadius = Math.max(0, trackStyle.innerRadius(radius)),
- trackOuterRadius = Math.max(0, trackStyle.outerRadius(radius)),
- valueInnerRadius = Math.max(0, valueStyle.innerRadius(radius)),
- valueOuterRadius = Math.max(0, valueStyle.outerRadius(radius)),
- trackPath = me.getArcPath(cx, cy, trackInnerRadius, trackOuterRadius, trackStart, trackEnd, trackStyle.round),
- valuePath = me.getArcPath(cx, cy, valueInnerRadius, valueOuterRadius, clockwise ? trackStart : trackEnd - valueLength, clockwise ? trackStart + valueLength : trackEnd, valueStyle.round);
- me.centerText(cx + textOffset.dx, cy + textOffset.dy, sector.region, trackInnerRadius, trackOuterRadius);
- trackArc.setAttribute('d', trackPath);
- valueArc.setAttribute('d', valuePath);
- if (needle) {
- needle.setRadius(radius);
- needle.setTransform(cx, cy, -90 + trackStart + valueLength);
- }
- me.fireEvent('render', me);
- }
- });
- Ext.define('Ext.ux.gauge.needle.Arrow', {
- extend: 'Ext.ux.gauge.needle.Abstract',
- alias: 'gauge.needle.arrow',
- config: {
- path: function(ir, or) {
- 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" : '';
- }
- }
- });
- Ext.define('Ext.ux.gauge.needle.Diamond', {
- extend: 'Ext.ux.gauge.needle.Abstract',
- alias: 'gauge.needle.diamond',
- config: {
- path: function(ir, or) {
- return or - ir > 10 ? 'M0,' + ir + ' L-4,' + (ir + 5) + ' L0,' + or + ' L4,' + (ir + 5) + ' Z' : '';
- }
- }
- });
- Ext.define('Ext.ux.gauge.needle.Rectangle', {
- extend: 'Ext.ux.gauge.needle.Abstract',
- alias: 'gauge.needle.rectangle',
- config: {
- path: function(ir, or) {
- return or - ir > 10 ? "M-2," + ir + " L2," + ir + " L2," + or + " L-2," + or + " Z" : '';
- }
- }
- });
- Ext.define('Ext.ux.gauge.needle.Spike', {
- extend: 'Ext.ux.gauge.needle.Abstract',
- alias: 'gauge.needle.spike',
- config: {
- path: function(ir, or) {
- return or - ir > 10 ? "M0," + (ir + 5) + " L-4," + ir + " L0," + or + " L4," + ir + " Z" : '';
- }
- }
- });
- Ext.define('Ext.ux.gauge.needle.Wedge', {
- extend: 'Ext.ux.gauge.needle.Abstract',
- alias: 'gauge.needle.wedge',
- config: {
- path: function(ir, or) {
- return or - ir > 10 ? "M-4," + ir + " L0," + or + " L4," + ir + " Z" : '';
- }
- }
- });
- /**
- * A ratings picker based on `Ext.Gadget`.
- *
- * @example
- * Ext.create({
- * xtype: 'rating',
- * renderTo: Ext.getBody(),
- * listeners: {
- * change: function (picker, value) {
- * console.log('Rating ' + value);
- * }
- * }
- * });
- */
- Ext.define('Ext.ux.rating.Picker', {
- extend: 'Ext.Gadget',
- xtype: 'rating',
- focusable: true,
- /*
- * The "cachedConfig" block is basically the same as "config" except that these
- * values are applied specially to the first instance of the class. After processing
- * these configs, the resulting values are stored on the class `prototype` and the
- * template DOM element also reflects these default values.
- */
- cachedConfig: {
- /**
- * @cfg {String} [family]
- * The CSS `font-family` to use for displaying the `{@link #glyphs}`.
- */
- family: 'monospace',
- /**
- * @cfg {String/String[]/Number[]} [glyphs]
- * Either a string containing the two glyph characters, or an array of two strings
- * containing the individual glyph characters or an array of two numbers with the
- * character codes for the individual glyphs.
- *
- * For example:
- *
- * @example
- * Ext.create({
- * xtype: 'rating',
- * renderTo: Ext.getBody(),
- * glyphs: [ 9671, 9670 ], // '◇◆',
- * listeners: {
- * change: function (picker, value) {
- * console.log('Rating ' + value);
- * }
- * }
- * });
- */
- glyphs: '☆★',
- /**
- * @cfg {Number} [minimum=1]
- * The minimum allowed `{@link #value}` (rating).
- */
- minimum: 1,
- /**
- * @cfg {Number} [limit]
- * The maximum allowed `{@link #value}` (rating).
- */
- limit: 5,
- /**
- * @cfg {String/Object} [overStyle]
- * Optional styles to apply to the rating glyphs when `{@link #trackOver}` is
- * enabled.
- */
- overStyle: null,
- /**
- * @cfg {Number} [rounding=1]
- * The rounding to apply to values. Common choices are 0.5 (for half-steps) or
- * 0.25 (for quarter steps).
- */
- rounding: 1,
- /**
- * @cfg {String} [scale="125%"]
- * The CSS `font-size` to apply to the glyphs. This value defaults to 125% because
- * glyphs in the stock font tend to be too small. When using specially designed
- * "icon fonts" you may want to set this to 100%.
- */
- scale: '125%',
- /**
- * @cfg {String/Object} [selectedStyle]
- * Optional styles to apply to the rating value glyphs.
- */
- selectedStyle: null,
- /**
- * @cfg {Object/String/String[]/Ext.XTemplate/Function} tip
- * A template or a function that produces the tooltip text. The `Object`, `String`
- * and `String[]` forms are converted to an `Ext.XTemplate`. If a function is given,
- * it will be called with an object parameter and should return the tooltip text.
- * The object contains these properties:
- *
- * - component: The rating component requesting the tooltip.
- * - tracking: The current value under the mouse cursor.
- * - trackOver: The value of the `{@link #trackOver}` config.
- * - value: The current value.
- *
- * Templates can use these properties to generate the proper text.
- */
- tip: null,
- /**
- * @cfg {Boolean} [trackOver=true]
- * Determines if mouse movements should temporarily update the displayed value.
- * The actual `value` is only updated on `click` but this rather acts as the
- * "preview" of the value prior to click.
- */
- trackOver: true,
- /**
- * @cfg {Number} value
- * The rating value. This value is bounded by `minimum` and `limit` and is also
- * adjusted by the `rounding`.
- */
- value: null,
- //---------------------------------------------------------------------
- // Private configs
- /**
- * @cfg {String} tooltipText
- * The current tooltip text. This value is set into the DOM by the updater (hence
- * only when it changes). This is intended for use by the tip manager
- * (`{@link Ext.tip.QuickTipManager}`). Developers should never need to set this
- * config since it is handled by virtue of setting other configs (such as the
- * {@link #tooltip} or the {@link #value}.).
- * @private
- */
- tooltipText: null,
- /**
- * @cfg {Number} trackingValue
- * This config is used to when `trackOver` is `true` and represents the tracked
- * value. This config is maintained by our `mousemove` handler. This should not
- * need to be set directly by user code.
- * @private
- */
- trackingValue: null
- },
- config: {
- /**
- * @cfg {Boolean/Object} [animate=false]
- * Specifies an animation to use when changing the `{@link #value}`. When setting
- * this config, it is probably best to set `{@link #trackOver}` to `false`.
- */
- animate: null
- },
- // This object describes our element tree from the root.
- element: {
- cls: 'u' + Ext.baseCSSPrefix + 'rating-picker',
- // Since we are replacing the entire "element" tree, we have to assign this
- // "reference" as would our base class.
- reference: 'element',
- children: [
- {
- reference: 'innerEl',
- cls: 'u' + Ext.baseCSSPrefix + 'rating-picker-inner',
- listeners: {
- click: 'onClick',
- mousemove: 'onMouseMove',
- mouseenter: 'onMouseEnter',
- mouseleave: 'onMouseLeave'
- },
- children: [
- {
- reference: 'valueEl',
- cls: 'u' + Ext.baseCSSPrefix + 'rating-picker-value'
- },
- {
- reference: 'trackerEl',
- cls: 'u' + Ext.baseCSSPrefix + 'rating-picker-tracker'
- }
- ]
- }
- ]
- },
- // Tell the Binding system to default to our "value" config.
- defaultBindProperty: 'value',
- // Enable two-way data binding for the "value" config.
- twoWayBindable: 'value',
- overCls: 'u' + Ext.baseCSSPrefix + 'rating-picker-over',
- trackOverCls: 'u' + Ext.baseCSSPrefix + 'rating-picker-track-over',
- //-------------------------------------------------------------------------
- // Config Appliers
- applyGlyphs: function(value) {
- if (typeof value === 'string') {
- //<debug>
- if (value.length !== 2) {
- Ext.raise('Expected 2 characters for "glyphs" not "' + value + '".');
- }
- //</debug>
- value = [
- value.charAt(0),
- value.charAt(1)
- ];
- } else if (typeof value[0] === 'number') {
- value = [
- String.fromCharCode(value[0]),
- String.fromCharCode(value[1])
- ];
- }
- return value;
- },
- applyOverStyle: function(style) {
- this.trackerEl.applyStyles(style);
- },
- applySelectedStyle: function(style) {
- this.valueEl.applyStyles(style);
- },
- applyTip: function(tip) {
- if (tip && typeof tip !== 'function') {
- if (!tip.isTemplate) {
- tip = new Ext.XTemplate(tip);
- }
- tip = tip.apply.bind(tip);
- }
- return tip;
- },
- applyTrackingValue: function(value) {
- return this.applyValue(value);
- },
- // same rounding as normal value
- applyValue: function(v) {
- if (v !== null) {
- var rounding = this.getRounding(),
- limit = this.getLimit(),
- min = this.getMinimum();
- v = Math.round(Math.round(v / rounding) * rounding * 1000) / 1000;
- v = (v < min) ? min : (v > limit ? limit : v);
- }
- return v;
- },
- //-------------------------------------------------------------------------
- // Event Handlers
- onClick: function(event) {
- var value = this.valueFromEvent(event);
- this.setValue(value);
- },
- onMouseEnter: function() {
- this.element.addCls(this.overCls);
- },
- onMouseLeave: function() {
- this.element.removeCls(this.overCls);
- },
- onMouseMove: function(event) {
- var value = this.valueFromEvent(event);
- this.setTrackingValue(value);
- },
- //-------------------------------------------------------------------------
- // Config Updaters
- updateFamily: function(family) {
- this.element.setStyle('fontFamily', "'" + family + "'");
- },
- updateGlyphs: function() {
- this.refreshGlyphs();
- },
- updateLimit: function() {
- this.refreshGlyphs();
- },
- updateScale: function(size) {
- this.element.setStyle('fontSize', size);
- },
- updateTip: function() {
- this.refreshTip();
- },
- updateTooltipText: function(text) {
- this.setTooltip(text);
- },
- // modern only (replaced by classic override)
- updateTrackingValue: function(value) {
- var me = this,
- trackerEl = me.trackerEl,
- newWidth = me.valueToPercent(value);
- trackerEl.setStyle('width', newWidth);
- me.refreshTip();
- },
- updateTrackOver: function(trackOver) {
- this.element.toggleCls(this.trackOverCls, trackOver);
- },
- updateValue: function(value, oldValue) {
- var me = this,
- animate = me.getAnimate(),
- valueEl = me.valueEl,
- newWidth = me.valueToPercent(value),
- column, record;
- if (me.isConfiguring || !animate) {
- valueEl.setStyle('width', newWidth);
- } else {
- valueEl.stopAnimation();
- valueEl.animate(Ext.merge({
- from: {
- width: me.valueToPercent(oldValue)
- },
- to: {
- width: newWidth
- }
- }, animate));
- }
- me.refreshTip();
- if (!me.isConfiguring) {
- // Since we are (re)configured many times as we are used in a grid cell, we
- // avoid firing the change event unless there are listeners.
- if (me.hasListeners.change) {
- me.fireEvent('change', me, value, oldValue);
- }
- column = me.getWidgetColumn && me.getWidgetColumn();
- record = column && me.getWidgetRecord && me.getWidgetRecord();
- if (record && column.dataIndex) {
- // When used in a widgetcolumn, we should update the backing field. The
- // linkages will be cleared as we are being recycled, so this will only
- // reach this line when we are properly attached to a record and the
- // change is coming from the user (or a call to setValue).
- record.set(column.dataIndex, value);
- }
- }
- },
- //-------------------------------------------------------------------------
- // Config System Optimizations
- //
- // These are to deal with configs that combine to determine what should be
- // rendered in the DOM. For example, "glyphs" and "limit" must both be known
- // to render the proper text nodes. The "tip" and "value" likewise are
- // used to update the tooltipText.
- //
- // To avoid multiple updates to the DOM (one for each config), we simply mark
- // the rendering as invalid and post-process these flags on the tail of any
- // bulk updates.
- afterCachedConfig: function() {
- // Now that we are done setting up the initial values we need to refresh the
- // DOM before we allow Ext.Widget's implementation to cloneNode on it.
- this.refresh();
- return this.callParent(arguments);
- },
- initConfig: function(instanceConfig) {
- this.isConfiguring = true;
- this.callParent([
- instanceConfig
- ]);
- // The firstInstance will already have refreshed the DOM (in afterCacheConfig)
- // but all instances beyond the first need to refresh if they have custom values
- // for one or more configs that affect the DOM (such as "glyphs" and "limit").
- this.refresh();
- },
- setConfig: function() {
- var me = this;
- // Since we could be updating multiple configs, save any updates that need
- // multiple values for afterwards.
- me.isReconfiguring = true;
- me.callParent(arguments);
- me.isReconfiguring = false;
- // Now that all new values are set, we can refresh the DOM.
- me.refresh();
- return me;
- },
- //-------------------------------------------------------------------------
- privates: {
- /**
- * This method returns the DOM text node into which glyphs are placed.
- * @param {HTMLElement} dom The DOM node parent of the text node.
- * @return {HTMLElement} The text node.
- * @private
- */
- getGlyphTextNode: function(dom) {
- var node = dom.lastChild;
- // We want all our text nodes to be at the end of the child list, most
- // especially the text node on the innerEl. That text node affects the
- // default left/right position of our absolutely positioned child divs
- // (trackerEl and valueEl).
- if (!node || node.nodeType !== 3) {
- node = dom.ownerDocument.createTextNode('');
- dom.appendChild(node);
- }
- return node;
- },
- getTooltipData: function() {
- var me = this;
- return {
- component: me,
- tracking: me.getTrackingValue(),
- trackOver: me.getTrackOver(),
- value: me.getValue()
- };
- },
- /**
- * Forcibly refreshes both glyph and tooltip rendering.
- * @private
- */
- refresh: function() {
- var me = this;
- if (me.invalidGlyphs) {
- me.refreshGlyphs(true);
- }
- if (me.invalidTip) {
- me.refreshTip(true);
- }
- },
- /**
- * Refreshes the glyph text rendering unless we are currently performing a
- * bulk config change (initConfig or setConfig).
- * @param {Boolean} now Pass `true` to force the refresh to happen now.
- * @private
- */
- refreshGlyphs: function(now) {
- var me = this,
- later = !now && (me.isConfiguring || me.isReconfiguring),
- el, glyphs, limit, on, off, trackerEl, valueEl;
- if (!later) {
- el = me.getGlyphTextNode(me.innerEl.dom);
- valueEl = me.getGlyphTextNode(me.valueEl.dom);
- trackerEl = me.getGlyphTextNode(me.trackerEl.dom);
- glyphs = me.getGlyphs();
- limit = me.getLimit();
- for (on = off = ''; limit--; ) {
- off += glyphs[0];
- on += glyphs[1];
- }
- el.nodeValue = off;
- valueEl.nodeValue = on;
- trackerEl.nodeValue = on;
- }
- me.invalidGlyphs = later;
- },
- /**
- * Refreshes the tooltip text rendering unless we are currently performing a
- * bulk config change (initConfig or setConfig).
- * @param {Boolean} now Pass `true` to force the refresh to happen now.
- * @private
- */
- refreshTip: function(now) {
- var me = this,
- later = !now && (me.isConfiguring || me.isReconfiguring),
- data, text, tooltip;
- if (!later) {
- tooltip = me.getTip();
- if (tooltip) {
- data = me.getTooltipData();
- text = tooltip(data);
- me.setTooltipText(text);
- }
- }
- me.invalidTip = later;
- },
- /**
- * Convert the coordinates of the given `Event` into a rating value.
- * @param {Ext.event.Event} event The event.
- * @return {Number} The rating based on the given event coordinates.
- * @private
- */
- valueFromEvent: function(event) {
- var me = this,
- el = me.innerEl,
- ex = event.getX(),
- rounding = me.getRounding(),
- cx = el.getX(),
- x = ex - cx,
- w = el.getWidth(),
- limit = me.getLimit(),
- v;
- if (me.getInherited().rtl) {
- x = w - x;
- }
- v = x / w * limit;
- // We have to round up here so that the area we are over is considered
- // the value.
- v = Math.ceil(v / rounding) * rounding;
- return v;
- },
- /**
- * Convert the given rating into a width percentage.
- * @param {Number} value The rating value to convert.
- * @return {String} The width percentage to represent the given value.
- * @private
- */
- valueToPercent: function(value) {
- value = (value / this.getLimit()) * 100;
- return value + '%';
- }
- }
- });
- /**
- * @class Ext.ux.rating.Picker
- */
- Ext.define('Ext.ux.overrides.rating.Picker', {
- override: 'Ext.ux.rating.Picker',
- //<debug>
- initConfig: function(config) {
- if (config && config.tooltip) {
- config.tip = config.tooltip;
- Ext.log.warn('[Ext.ux.rating.Picker] The "tooltip" config was replaced by "tip"');
- }
- this.callParent([
- config
- ]);
- },
- //</debug>
- updateTooltipText: function(text) {
- var innerEl = this.innerEl,
- QuickTips = Ext.tip && Ext.tip.QuickTipManager,
- tip = QuickTips && QuickTips.tip,
- target;
- if (QuickTips) {
- innerEl.dom.setAttribute('data-qtip', text);
- this.trackerEl.dom.setAttribute('data-qtip', text);
- // If the QuickTipManager is active over our widget, we need to update
- // the tooltip text directly.
- target = tip && tip.activeTarget;
- target = target && target.el;
- if (target && innerEl.contains(target)) {
- tip.update(text);
- }
- }
- }
- });
- /**
- * Base class from Ext.ux.TabReorderer.
- */
- Ext.define('Ext.ux.BoxReorderer', {
- extend: 'Ext.plugin.Abstract',
- alias: 'plugin.boxreorderer',
- requires: [
- 'Ext.dd.DD'
- ],
- mixins: {
- observable: 'Ext.util.Observable'
- },
- /**
- * @cfg {String} itemSelector
- * A {@link Ext.DomQuery DomQuery} selector which identifies the encapsulating elements of child
- * Components which participate in reordering.
- */
- itemSelector: '.x-box-item',
- /**
- * @cfg {Mixed} animate
- * If truthy, child reordering is animated so that moved boxes slide smoothly into position.
- * If this option is numeric, it is used as the animation duration in milliseconds.
- */
- animate: 100,
- /**
- * @event StartDrag
- * Fires when dragging of a child Component begins.
- * @param {Ext.ux.BoxReorderer} this
- * @param {Ext.container.Container} container The owning Container
- * @param {Ext.Component} dragCmp The Component being dragged
- * @param {Number} idx The start index of the Component being dragged.
- */
- /**
- * @event Drag
- * Fires during dragging of a child Component.
- * @param {Ext.ux.BoxReorderer} this
- * @param {Ext.container.Container} container The owning Container
- * @param {Ext.Component} dragCmp The Component being dragged
- * @param {Number} startIdx The index position from which the Component was initially dragged.
- * @param {Number} idx The current closest index to which the Component would drop.
- */
- /**
- * @event ChangeIndex
- * Fires when dragging of a child Component causes its drop index to change.
- * @param {Ext.ux.BoxReorderer} this
- * @param {Ext.container.Container} container The owning Container
- * @param {Ext.Component} dragCmp The Component being dragged
- * @param {Number} startIdx The index position from which the Component was initially dragged.
- * @param {Number} idx The current closest index to which the Component would drop.
- */
- /**
- * @event Drop
- * Fires when a child Component is dropped at a new index position.
- * @param {Ext.ux.BoxReorderer} this
- * @param {Ext.container.Container} container The owning Container
- * @param {Ext.Component} dragCmp The Component being dropped
- * @param {Number} startIdx The index position from which the Component was initially dragged.
- * @param {Number} idx The index at which the Component is being dropped.
- */
- constructor: function() {
- this.callParent(arguments);
- this.mixins.observable.constructor.call(this);
- },
- init: function(container) {
- var me = this,
- layout = container.getLayout();
- me.container = container;
- // We must use LTR method names and properties.
- // The underlying Element APIs normalize them.
- me.names = layout._props[layout.type].names;
- // Set our animatePolicy to animate the start position (ie x for HBox, y for VBox)
- me.animatePolicy = {};
- me.animatePolicy[me.names.x] = true;
- // Initialize the DD on first layout, when the innerCt has been created.
- me.container.on({
- scope: me,
- boxready: me.onBoxReady,
- beforedestroy: me.onContainerDestroy
- });
- },
- /**
- * @private
- * Clear up on Container destroy
- */
- onContainerDestroy: function() {
- var dd = this.dd;
- if (dd) {
- dd.unreg();
- this.dd = null;
- }
- },
- onBoxReady: function() {
- var me = this,
- layout = me.container.getLayout(),
- names = me.names,
- dd;
- // Create a DD instance. Poke the handlers in.
- // TODO: Ext5's DD classes should apply config to themselves.
- // TODO: Ext5's DD classes should not use init internally because it collides with use as a plugin
- // TODO: Ext5's DD classes should be Observable.
- // TODO: When all the above are trus, this plugin should extend the DD class.
- dd = me.dd = new Ext.dd.DD(layout.innerCt, me.container.id + '-reorderer');
- Ext.apply(dd, {
- animate: me.animate,
- reorderer: me,
- container: me.container,
- getDragCmp: me.getDragCmp,
- clickValidator: Ext.Function.createInterceptor(dd.clickValidator, me.clickValidator, me, false),
- onMouseDown: me.onMouseDown,
- startDrag: me.startDrag,
- onDrag: me.onDrag,
- endDrag: me.endDrag,
- getNewIndex: me.getNewIndex,
- doSwap: me.doSwap,
- findReorderable: me.findReorderable,
- names: names
- });
- // Decide which dimension we are measuring, and which measurement metric defines
- // the *start* of the box depending upon orientation.
- dd.dim = names.width;
- dd.startAttr = names.beforeX;
- dd.endAttr = names.afterX;
- },
- getDragCmp: function(e) {
- return this.container.getChildByElement(e.getTarget(this.itemSelector, 10));
- },
- // check if the clicked component is reorderable
- clickValidator: function(e) {
- var cmp = this.getDragCmp(e);
- // If cmp is null, this expression MUST be coerced to boolean so that createInterceptor is able to test it against false
- return !!(cmp && cmp.reorderable !== false);
- },
- onMouseDown: function(e) {
- var me = this,
- container = me.container,
- containerBox, cmpEl, cmpBox;
- // Ascertain which child Component is being mousedowned
- me.dragCmp = me.getDragCmp(e);
- if (me.dragCmp) {
- cmpEl = me.dragCmp.getEl();
- me.startIndex = me.curIndex = container.items.indexOf(me.dragCmp);
- // Start position of dragged Component
- cmpBox = cmpEl.getBox();
- // Last tracked start position
- me.lastPos = cmpBox[me.startAttr];
- // Calculate constraints depending upon orientation
- // Calculate offset from mouse to dragEl position
- containerBox = container.el.getBox();
- if (me.dim === 'width') {
- me.minX = containerBox.left;
- me.maxX = containerBox.right - cmpBox.width;
- me.minY = me.maxY = cmpBox.top;
- me.deltaX = e.getX() - cmpBox.left;
- } else {
- me.minY = containerBox.top;
- me.maxY = containerBox.bottom - cmpBox.height;
- me.minX = me.maxX = cmpBox.left;
- me.deltaY = e.getY() - cmpBox.top;
- }
- me.constrainY = me.constrainX = true;
- }
- },
- startDrag: function() {
- var me = this,
- dragCmp = me.dragCmp;
- if (dragCmp) {
- // For the entire duration of dragging the *Element*, defeat any positioning and animation of the dragged *Component*
- dragCmp.setPosition = Ext.emptyFn;
- dragCmp.animate = false;
- // Animate the BoxLayout just for the duration of the drag operation.
- if (me.animate) {
- me.container.getLayout().animatePolicy = me.reorderer.animatePolicy;
- }
- // We drag the Component element
- me.dragElId = dragCmp.getEl().id;
- me.reorderer.fireEvent('StartDrag', me, me.container, dragCmp, me.curIndex);
- // Suspend events, and set the disabled flag so that the mousedown and mouseup events
- // that are going to take place do not cause any other UI interaction.
- dragCmp.suspendEvents();
- dragCmp.disabled = true;
- dragCmp.el.setStyle('zIndex', 100);
- } else {
- me.dragElId = null;
- }
- },
- /**
- * @private
- * Find next or previous reorderable component index.
- * @param {Number} newIndex The initial drop index.
- * @return {Number} The index of the reorderable component.
- */
- findReorderable: function(newIndex) {
- var me = this,
- items = me.container.items,
- newItem;
- if (items.getAt(newIndex).reorderable === false) {
- newItem = items.getAt(newIndex);
- if (newIndex > me.startIndex) {
- while (newItem && newItem.reorderable === false) {
- newIndex++;
- newItem = items.getAt(newIndex);
- }
- } else {
- while (newItem && newItem.reorderable === false) {
- newIndex--;
- newItem = items.getAt(newIndex);
- }
- }
- }
- newIndex = Math.min(Math.max(newIndex, 0), items.getCount() - 1);
- if (items.getAt(newIndex).reorderable === false) {
- return -1;
- }
- return newIndex;
- },
- /**
- * @private
- * Swap 2 components.
- * @param {Number} newIndex The initial drop index.
- */
- doSwap: function(newIndex) {
- var me = this,
- items = me.container.items,
- container = me.container,
- orig, dest, tmpIndex;
- newIndex = me.findReorderable(newIndex);
- if (newIndex === -1 || newIndex === me.curIndex) {
- return;
- }
- me.reorderer.fireEvent('ChangeIndex', me, container, me.dragCmp, me.startIndex, newIndex);
- orig = items.getAt(me.curIndex);
- dest = items.getAt(newIndex);
- items.remove(orig);
- tmpIndex = Math.min(Math.max(newIndex, 0), items.getCount() - 1);
- items.insert(tmpIndex, orig);
- items.remove(dest);
- items.insert(me.curIndex, dest);
- // Make the Box Container the topmost layout participant during the layout.
- container.updateLayout({
- isRoot: true
- });
- me.curIndex = newIndex;
- },
- onDrag: function(e) {
- var me = this,
- newIndex;
- newIndex = me.getNewIndex(e.getPoint());
- if ((newIndex !== undefined)) {
- me.reorderer.fireEvent('Drag', me, me.container, me.dragCmp, me.startIndex, me.curIndex);
- me.doSwap(newIndex);
- }
- },
- endDrag: function(e) {
- if (e) {
- e.stopEvent();
- }
- var me = this,
- layout = me.container.getLayout(),
- temp;
- if (me.dragCmp) {
- delete me.dragElId;
- // Reinstate the Component's positioning method after mouseup, and allow the layout system to animate it.
- delete me.dragCmp.setPosition;
- me.dragCmp.animate = true;
- // Ensure the lastBox is correct for the animation system to restore to when it creates the "from" animation frame
- me.dragCmp.lastBox[me.names.x] = me.dragCmp.getPosition(true)[me.names.widthIndex];
- // Make the Box Container the topmost layout participant during the layout.
- me.container.updateLayout({
- isRoot: true
- });
- // Attempt to hook into the afteranimate event of the drag Component to call the cleanup
- temp = Ext.fx.Manager.getFxQueue(me.dragCmp.el.id)[0];
- if (temp) {
- temp.on({
- afteranimate: me.reorderer.afterBoxReflow,
- scope: me
- });
- } else // If not animated, clean up after the mouseup has happened so that we don't click the thing being dragged
- {
- Ext.asap(me.reorderer.afterBoxReflow, me);
- }
- if (me.animate) {
- delete layout.animatePolicy;
- }
- me.reorderer.fireEvent('drop', me, me.container, me.dragCmp, me.startIndex, me.curIndex);
- }
- },
- /**
- * @private
- * Called after the boxes have been reflowed after the drop.
- * Re-enabled the dragged Component.
- */
- afterBoxReflow: function() {
- var me = this;
- me.dragCmp.el.setStyle('zIndex', '');
- me.dragCmp.disabled = false;
- me.dragCmp.resumeEvents();
- },
- /**
- * @private
- * Calculate drop index based upon the dragEl's position.
- */
- getNewIndex: function(pointerPos) {
- var me = this,
- dragEl = me.getDragEl(),
- dragBox = Ext.fly(dragEl).getBox(),
- targetEl, targetBox, targetMidpoint,
- i = 0,
- it = me.container.items.items,
- ln = it.length,
- lastPos = me.lastPos;
- me.lastPos = dragBox[me.startAttr];
- for (; i < ln; i++) {
- targetEl = it[i].getEl();
- // Only look for a drop point if this found item is an item according to our selector
- // and is not the item being dragged
- if (targetEl.dom !== dragEl && targetEl.is(me.reorderer.itemSelector)) {
- targetBox = targetEl.getBox();
- targetMidpoint = targetBox[me.startAttr] + (targetBox[me.dim] >> 1);
- if (i < me.curIndex) {
- if ((dragBox[me.startAttr] < lastPos) && (dragBox[me.startAttr] < (targetMidpoint - 5))) {
- return i;
- }
- } else if (i > me.curIndex) {
- if ((dragBox[me.startAttr] > lastPos) && (dragBox[me.endAttr] > (targetMidpoint + 5))) {
- return i;
- }
- }
- }
- }
- }
- });
- /**
- * This plugin can enable a cell to cell drag and drop operation within the same grid view.
- *
- * Note that the plugin must be added to the grid view, not to the grid panel. For example,
- * using {@link Ext.panel.Table viewConfig}:
- *
- * viewConfig: {
- * plugins: {
- * celldragdrop: {
- * // Remove text from source cell and replace with value of emptyText.
- * applyEmptyText: true,
- *
- * //emptyText: Ext.String.htmlEncode('<<foo>>'),
- *
- * // Will only allow drops of the same type.
- * enforceType: true
- * }
- * }
- * }
- */
- Ext.define('Ext.ux.CellDragDrop', {
- extend: 'Ext.plugin.Abstract',
- alias: 'plugin.celldragdrop',
- uses: [
- 'Ext.view.DragZone'
- ],
- /**
- * @cfg {Boolean} enforceType
- * Set to `true` to only allow drops of the same type.
- *
- * Defaults to `false`.
- */
- enforceType: false,
- /**
- * @cfg {Boolean} applyEmptyText
- * If `true`, then use the value of {@link #emptyText} to replace the drag record's value after a node drop.
- * Note that, if dropped on a cell of a different type, it will convert the default text according to its own conversion rules.
- *
- * Defaults to `false`.
- */
- applyEmptyText: false,
- /**
- * @cfg {String} emptyText
- * If {@link #applyEmptyText} is `true`, then this value as the drag record's value after a node drop.
- *
- * Defaults to an empty string.
- */
- emptyText: '',
- /**
- * @cfg {String} dropBackgroundColor
- * The default background color for when a drop is allowed.
- *
- * Defaults to green.
- */
- dropBackgroundColor: 'green',
- /**
- * @cfg {String} noDropBackgroundColor
- * The default background color for when a drop is not allowed.
- *
- * Defaults to red.
- */
- noDropBackgroundColor: 'red',
- /**
- * @cfg {String} dragText
- * The text to show while dragging.
- *
- * Two placeholders can be used in the text:
- *
- * - `{0}` The number of selected items.
- * - `{1}` 's' when more than 1 items (only useful for English).
- * @locale
- */
- dragText: '{0} selected row{1}',
- /**
- * @cfg {String} ddGroup
- * A named drag drop group to which this object belongs. If a group is specified, then both the DragZones and
- * DropZone used by this plugin will only interact with other drag drop objects in the same group.
- */
- ddGroup: "GridDD",
- /**
- * @cfg {Boolean} enableDrop
- * Set to `false` to disallow the View from accepting drop gestures.
- */
- enableDrop: true,
- /**
- * @cfg {Boolean} enableDrag
- * Set to `false` to disallow dragging items from the View.
- */
- enableDrag: true,
- /**
- * @cfg {Object/Boolean} containerScroll
- * True to register this container with the Scrollmanager for auto scrolling during drag operations.
- * A {@link Ext.dd.ScrollManager} configuration may also be passed.
- */
- containerScroll: false,
- init: function(view) {
- var me = this;
- view.on('render', me.onViewRender, me, {
- single: true
- });
- },
- destroy: function() {
- var me = this;
- me.dragZone = me.dropZone = Ext.destroy(me.dragZone, me.dropZone);
- me.callParent();
- },
- enable: function() {
- var me = this;
- if (me.dragZone) {
- me.dragZone.unlock();
- }
- if (me.dropZone) {
- me.dropZone.unlock();
- }
- me.callParent();
- },
- disable: function() {
- var me = this;
- if (me.dragZone) {
- me.dragZone.lock();
- }
- if (me.dropZone) {
- me.dropZone.lock();
- }
- me.callParent();
- },
- onViewRender: function(view) {
- var me = this,
- scrollEl;
- if (me.enableDrag) {
- if (me.containerScroll) {
- scrollEl = view.getEl();
- }
- me.dragZone = new Ext.view.DragZone({
- view: view,
- ddGroup: me.dragGroup || me.ddGroup,
- dragText: me.dragText,
- containerScroll: me.containerScroll,
- scrollEl: scrollEl,
- getDragData: function(e) {
- var view = this.view,
- item = e.getTarget(view.getItemSelector()),
- record = view.getRecord(item),
- cell = e.getTarget(view.getCellSelector()),
- dragEl, header;
- if (item) {
- dragEl = document.createElement('div');
- dragEl.className = 'x-form-text';
- dragEl.appendChild(document.createTextNode(cell.textContent || cell.innerText));
- header = view.getHeaderByCell(cell);
- return {
- event: new Ext.EventObjectImpl(e),
- ddel: dragEl,
- item: e.target,
- columnName: header.dataIndex,
- record: record
- };
- }
- },
- onInitDrag: function(x, y) {
- var self = this,
- data = self.dragData,
- view = self.view,
- selectionModel = view.getSelectionModel(),
- record = data.record,
- el = data.ddel;
- // Update the selection to match what would have been selected if the user had
- // done a full click on the target node rather than starting a drag from it.
- if (!selectionModel.isSelected(record)) {
- selectionModel.select(record, true);
- }
- Ext.fly(self.ddel).update(el.textContent || el.innerText);
- self.proxy.update(self.ddel);
- self.onStartDrag(x, y);
- return true;
- }
- });
- }
- if (me.enableDrop) {
- me.dropZone = new Ext.dd.DropZone(view.el, {
- view: view,
- ddGroup: me.dropGroup || me.ddGroup,
- containerScroll: true,
- getTargetFromEvent: function(e) {
- var self = this,
- view = self.view,
- cell = e.getTarget(view.cellSelector),
- row, header;
- // Ascertain whether the mousemove is within a grid cell.
- if (cell) {
- row = view.findItemByChild(cell);
- header = view.getHeaderByCell(cell);
- if (row && header) {
- return {
- node: cell,
- record: view.getRecord(row),
- columnName: header.dataIndex
- };
- }
- }
- },
- // On Node enter, see if it is valid for us to drop the field on that type of column.
- onNodeEnter: function(target, dd, e, dragData) {
- var self = this,
- destType = target.record.getField(target.columnName).type.toUpperCase(),
- sourceType = dragData.record.getField(dragData.columnName).type.toUpperCase();
- delete self.dropOK;
- // Return if no target node or if over the same cell as the source of the drag.
- if (!target || target.node === dragData.item.parentNode) {
- return;
- }
- // Check whether the data type of the column being dropped on accepts the
- // dragged field type. If so, set dropOK flag, and highlight the target node.
- if (me.enforceType && destType !== sourceType) {
- self.dropOK = false;
- if (me.noDropCls) {
- Ext.fly(target.node).addCls(me.noDropCls);
- } else {
- Ext.fly(target.node).applyStyles({
- backgroundColor: me.noDropBackgroundColor
- });
- }
- return false;
- }
- self.dropOK = true;
- if (me.dropCls) {
- Ext.fly(target.node).addCls(me.dropCls);
- } else {
- Ext.fly(target.node).applyStyles({
- backgroundColor: me.dropBackgroundColor
- });
- }
- },
- // Return the class name to add to the drag proxy. This provides a visual indication
- // of drop allowed or not allowed.
- onNodeOver: function(target, dd, e, dragData) {
- return this.dropOK ? this.dropAllowed : this.dropNotAllowed;
- },
- // Highlight the target node.
- onNodeOut: function(target, dd, e, dragData) {
- var cls = this.dropOK ? me.dropCls : me.noDropCls;
- if (cls) {
- Ext.fly(target.node).removeCls(cls);
- } else {
- Ext.fly(target.node).applyStyles({
- backgroundColor: ''
- });
- }
- },
- // Process the drop event if we have previously ascertained that a drop is OK.
- onNodeDrop: function(target, dd, e, dragData) {
- if (this.dropOK) {
- target.record.set(target.columnName, dragData.record.get(dragData.columnName));
- if (me.applyEmptyText) {
- dragData.record.set(dragData.columnName, me.emptyText);
- }
- return true;
- }
- },
- onCellDrop: Ext.emptyFn
- });
- }
- }
- });
- /**
- * @class Ext.ux.DataTip
- * @extends Ext.tip.ToolTip
- * This plugin implements automatic tooltip generation for an arbitrary number of child nodes *within* a Component.
- *
- * This plugin is applied to a high level Component, which contains repeating elements, and depending on the host Component type,
- * it automatically selects a {@link Ext.ToolTip#delegate delegate} so that it appears when the mouse enters a sub-element.
- *
- * When applied to a GridPanel, this ToolTip appears when over a row, and the Record's data is applied
- * using this object's {@link #tpl} template.
- *
- * When applied to a DataView, this ToolTip appears when over a view node, and the Record's data is applied
- * using this object's {@link #tpl} template.
- *
- * 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
- * using this object's {@link #tpl} template.
- *
- * When applied to a FormPanel, this ToolTip appears when over a Field, and the Field's `tooltip` property is used is applied
- * using this object's {@link #tpl} template, or if it is a string, used as HTML content. If there is no `tooltip` property,
- * the field itself is used as the template's data object.
- *
- * If more complex logic is needed to determine content, then the {@link #beforeshow} event may be used.
- * This class also publishes a **`beforeshowtip`** event through its host Component. The *host Component* fires the
- * **`beforeshowtip`** event.
- */
- Ext.define('Ext.ux.DataTip', function(DataTip) {
- // Target the body (if the host is a Panel), or, if there is no body, the main Element.
- function onHostRender() {
- var e = this.isXType('panel') ? this.body : this.el;
- if (this.dataTip.renderToTarget) {
- this.dataTip.render(e);
- }
- this.dataTip.setTarget(e);
- }
- function updateTip(tip, data) {
- if (tip.rendered) {
- if (tip.host.fireEvent('beforeshowtip', tip.eventHost, tip, data) === false) {
- return false;
- }
- tip.update(data);
- } else {
- if (Ext.isString(data)) {
- tip.html = data;
- } else {
- tip.data = data;
- }
- }
- }
- function beforeViewTipShow(tip) {
- var rec = this.view.getRecord(tip.triggerElement),
- data;
- if (rec) {
- data = tip.initialConfig.data ? Ext.apply(tip.initialConfig.data, rec.data) : rec.data;
- return updateTip(tip, data);
- } else {
- return false;
- }
- }
- function beforeFormTipShow(tip) {
- var field = Ext.getCmp(tip.triggerElement.id);
- if (field && (field.tooltip || tip.tpl)) {
- return updateTip(tip, field.tooltip || field);
- } else {
- return false;
- }
- }
- return {
- extend: 'Ext.tip.ToolTip',
- mixins: {
- plugin: 'Ext.plugin.Abstract'
- },
- alias: 'plugin.datatip',
- lockableScope: 'both',
- constructor: function(config) {
- var me = this;
- me.callParent([
- config
- ]);
- me.mixins.plugin.constructor.call(me, config);
- },
- init: function(host) {
- var me = this;
- me.mixins.plugin.init.call(me, host);
- host.dataTip = me;
- me.host = host;
- if (host.isXType('tablepanel')) {
- me.view = host.getView();
- if (host.ownerLockable) {
- me.host = host.ownerLockable;
- }
- me.delegate = me.delegate || me.view.rowSelector;
- me.on('beforeshow', beforeViewTipShow);
- } else if (host.isXType('dataview')) {
- me.view = me.host;
- me.delegate = me.delegate || host.itemSelector;
- me.on('beforeshow', beforeViewTipShow);
- } else if (host.isXType('form')) {
- me.delegate = '.' + Ext.form.Labelable.prototype.formItemCls;
- me.on('beforeshow', beforeFormTipShow);
- } else if (host.isXType('combobox')) {
- me.view = host.getPicker();
- me.delegate = me.delegate || me.view.getItemSelector();
- me.on('beforeshow', beforeViewTipShow);
- }
- if (host.rendered) {
- onHostRender.call(host);
- } else {
- host.onRender = Ext.Function.createSequence(host.onRender, onHostRender);
- }
- }
- };
- });
- /**
- * Transition plugin for DataViews
- */
- Ext.define('Ext.ux.DataView.Animated', {
- alias: 'plugin.ux-animated-dataview',
- /**
- * @property defaults
- * @type Object
- * Default configuration options for all DataViewTransition instances
- */
- defaults: {
- duration: 750,
- idProperty: 'id'
- },
- /**
- * Creates the plugin instance, applies defaults
- * @constructor
- * @param {Object} config Optional config object
- */
- constructor: function(config) {
- Ext.apply(this, config || {}, this.defaults);
- },
- /**
- * Initializes the transition plugin. Overrides the dataview's default refresh function
- * @param {Ext.view.View} dataview The dataview
- */
- init: function(dataview) {
- var me = this,
- store = dataview.store,
- items = dataview.all,
- task = {
- interval: 20
- },
- duration = me.duration;
- /**
- * @property dataview
- * @type Ext.view.View
- * Reference to the DataView this instance is bound to
- */
- me.dataview = dataview;
- dataview.blockRefresh = true;
- dataview.updateIndexes = Ext.Function.createSequence(dataview.updateIndexes, function() {
- this.getTargetEl().select(this.itemSelector).each(function(element, composite, index) {
- element.dom.id = Ext.util.Format.format("{0}-{1}", dataview.id, store.getAt(index).internalId);
- }, this);
- }, dataview);
- /**
- * @property dataviewID
- * @type String
- * The string ID of the DataView component. This is used internally when animating child objects
- */
- me.dataviewID = dataview.id;
- /**
- * @property cachedStoreData
- * @type Object
- * A cache of existing store data, keyed by id. This is used to determine
- * whether any items were added or removed from the store on data change
- */
- me.cachedStoreData = {};
- //catch the store data with the snapshot immediately
- me.cacheStoreData(store.data || store.snapshot);
- dataview.on('resize', function() {
- var store = dataview.store;
- if (store.getCount() > 0) {}
- }, // reDraw.call(this, store);
- this);
- // Buffer listenher so that rapid calls, for example a filter followed by a sort
- // Only produce one redraw.
- dataview.store.on({
- datachanged: reDraw,
- scope: this,
- buffer: 50
- });
- function reDraw() {
- var parentEl = dataview.getTargetEl(),
- parentElY = parentEl.getY(),
- parentElPaddingTop = parentEl.getPadding('t'),
- added = me.getAdded(store),
- removed = me.getRemoved(store),
- remaining = me.getRemaining(store),
- itemArray, i, id,
- itemFly = new Ext.dom.Fly(),
- rtl = me.dataview.getInherited().rtl,
- oldPos, newPos,
- styleSide = rtl ? 'right' : 'left',
- newStyle = {};
- // Not yet rendered
- if (!parentEl) {
- return;
- }
- // Collect nodes that will be removed in the forthcoming refresh so
- // that we can put them back in order to fade them out
- Ext.iterate(removed, function(recId, item) {
- id = me.dataviewID + '-' + recId;
- // Stop any animations for removed items and ensure th.
- Ext.fx.Manager.stopAnimation(id);
- item.dom = Ext.getDom(id);
- if (!item.dom) {
- delete removed[recId];
- }
- });
- me.cacheStoreData(store);
- // stores the current top and left values for each element (discovered below)
- var oldPositions = {},
- newPositions = {};
- // Find current positions of elements which are to remain after the refresh.
- Ext.iterate(remaining, function(id, item) {
- if (itemFly.attach(Ext.getDom(me.dataviewID + '-' + id))) {
- oldPos = oldPositions[id] = {
- top: itemFly.getY() - parentElY - itemFly.getMargin('t') - parentElPaddingTop
- };
- oldPos[styleSide] = me.getItemX(itemFly);
- } else {
- delete remaining[id];
- }
- });
- // The view MUST refresh, creating items in the natural flow, and collecting the items
- // so that its item collection is consistent.
- dataview.refresh();
- // Replace removed nodes so that they can be faded out, THEN removed
- Ext.iterate(removed, function(id, item) {
- parentEl.dom.appendChild(item.dom);
- itemFly.attach(item.dom).animate({
- duration: duration,
- opacity: 0,
- callback: function(anim) {
- var el = Ext.get(anim.target.id);
- if (el) {
- el.destroy();
- }
- }
- });
- delete item.dom;
- });
- // We have taken care of any removals.
- // If the store is empty, we are done.
- if (!store.getCount()) {
- return;
- }
- // Collect the correct new positions after the refresh
- itemArray = items.slice();
- // Reverse order so that moving to absolute position does not affect the position of
- // the next one we're looking at.
- for (i = itemArray.length - 1; i >= 0; i--) {
- id = store.getAt(i).internalId;
- itemFly.attach(itemArray[i]);
- newPositions[id] = {
- dom: itemFly.dom,
- top: itemFly.getY() - parentElY - itemFly.getMargin('t') - parentElPaddingTop
- };
- newPositions[id][styleSide] = me.getItemX(itemFly);
- // We're going to absolutely position each item.
- // If it is a "remaining" one from last refesh, shunt it back to
- // its old position from where it will be animated.
- newPos = oldPositions[id] || newPositions[id];
- // set absolute positioning on all DataView items. We need to set position, left and
- // top at the same time to avoid any flickering
- newStyle.position = 'absolute';
- newStyle.top = newPos.top + "px";
- newStyle[styleSide] = newPos.left + "px";
- itemFly.applyStyles(newStyle);
- }
- // This is the function which moves remaining items to their new position
- var doAnimate = function() {
- var elapsed = new Date() - task.taskStartTime,
- fraction = elapsed / duration;
- if (fraction >= 1) {
- // At end, return all items to natural flow.
- newStyle.position = newStyle.top = newStyle[styleSide] = '';
- for (id in newPositions) {
- itemFly.attach(newPositions[id].dom).applyStyles(newStyle);
- }
- Ext.TaskManager.stop(task);
- } else {
- // In frame, move each "remaining" item according to time elapsed
- for (id in remaining) {
- var oldPos = oldPositions[id],
- newPos = newPositions[id],
- oldTop = oldPos.top,
- newTop = newPos.top,
- oldLeft = oldPos[styleSide],
- newLeft = newPos[styleSide],
- diffTop = fraction * Math.abs(oldTop - newTop),
- diffLeft = fraction * Math.abs(oldLeft - newLeft),
- midTop = oldTop > newTop ? oldTop - diffTop : oldTop + diffTop,
- midLeft = oldLeft > newLeft ? oldLeft - diffLeft : oldLeft + diffLeft;
- newStyle.top = midTop + "px";
- newStyle[styleSide] = midLeft + "px";
- itemFly.attach(newPos.dom).applyStyles(newStyle);
- }
- }
- };
- // Fade in new items
- Ext.iterate(added, function(id, item) {
- if (itemFly.attach(Ext.getDom(me.dataviewID + '-' + id))) {
- itemFly.setOpacity(0);
- itemFly.animate({
- duration: duration,
- opacity: 1
- });
- }
- });
- // Stop any previous animations
- Ext.TaskManager.stop(task);
- task.run = doAnimate;
- Ext.TaskManager.start(task);
- me.cacheStoreData(store);
- }
- },
- getItemX: function(el) {
- var rtl = this.dataview.getInherited().rtl,
- parentEl = el.up('');
- if (rtl) {
- return parentEl.getViewRegion().right - el.getRegion().right + el.getMargin('r');
- } else {
- return el.getX() - parentEl.getX() - el.getMargin('l') - parentEl.getPadding('l');
- }
- },
- /**
- * Caches the records from a store locally for comparison later
- * @param {Ext.data.Store} store The store to cache data from
- */
- cacheStoreData: function(store) {
- var cachedStoreData = this.cachedStoreData = {};
- store.each(function(record) {
- cachedStoreData[record.internalId] = record;
- });
- },
- /**
- * Returns all records that were already in the DataView
- * @return {Object} All existing records
- */
- getExisting: function() {
- return this.cachedStoreData;
- },
- /**
- * Returns the total number of items that are currently visible in the DataView
- * @return {Number} The number of existing items
- */
- getExistingCount: function() {
- var count = 0,
- items = this.getExisting();
- for (var k in items) {
- count++;
- }
- return count;
- },
- /**
- * Returns all records in the given store that were not already present
- * @param {Ext.data.Store} store The updated store instance
- * @return {Object} Object of records not already present in the dataview in format {id: record}
- */
- getAdded: function(store) {
- var cachedStoreData = this.cachedStoreData,
- added = {};
- store.each(function(record) {
- if (cachedStoreData[record.internalId] == null) {
- added[record.internalId] = record;
- }
- });
- return added;
- },
- /**
- * Returns all records that are present in the DataView but not the new store
- * @param {Ext.data.Store} store The updated store instance
- * @return {Array} Array of records that used to be present
- */
- getRemoved: function(store) {
- var cachedStoreData = this.cachedStoreData,
- removed = {},
- id;
- for (id in cachedStoreData) {
- if (store.findBy(function(record) {
- return record.internalId == id;
- }) == -1) {
- removed[id] = cachedStoreData[id];
- }
- }
- return removed;
- },
- /**
- * Returns all records that are already present and are still present in the new store
- * @param {Ext.data.Store} store The updated store instance
- * @return {Object} Object of records that are still present from last time in format {id: record}
- */
- getRemaining: function(store) {
- var cachedStoreData = this.cachedStoreData,
- remaining = {};
- store.each(function(record) {
- if (cachedStoreData[record.internalId] != null) {
- remaining[record.internalId] = record;
- }
- });
- return remaining;
- }
- });
- /**
- *
- */
- Ext.define('Ext.ux.DataView.DragSelector', {
- requires: [
- 'Ext.dd.DragTracker',
- 'Ext.util.Region'
- ],
- alias: 'plugin.dataviewdragselector',
- /**
- * Initializes the plugin by setting up the drag tracker
- */
- init: function(dataview) {
- var scroller = dataview.getScrollable();
- // If the client dataview is scrollable, and this is a PointerEvents device
- // we cannot intercept the pointer to inplement dragselect.
- if (scroller && (scroller.getX() || scroller.getY()) && (Ext.supports.PointerEvents || Ext.supports.MSPointerEvents)) {
- //<debug>
- Ext.log.warn('DragSelector not available on PointerEvent devices');
- //</debug>
- return;
- }
- /**
- * @property dataview
- * @type Ext.view.View
- * The DataView bound to this instance
- */
- this.dataview = dataview;
- dataview.mon(dataview, {
- beforecontainerclick: this.cancelClick,
- scope: this,
- render: {
- fn: this.onRender,
- scope: this,
- single: true
- }
- });
- },
- /**
- * @private
- * Called when the attached DataView is rendered. This sets up the DragTracker instance that will be used
- * to created a dragged selection area
- */
- onRender: function() {
- /**
- * @property tracker
- * @type Ext.dd.DragTracker
- * The DragTracker attached to this instance. Note that the 4 on* functions are called in the scope of the
- * DragTracker ('this' refers to the DragTracker inside those functions), so we pass a reference to the
- * DragSelector so that we can call this class's functions.
- */
- this.tracker = Ext.create('Ext.dd.DragTracker', {
- dataview: this.dataview,
- el: this.dataview.el,
- onBeforeStart: this.onBeforeStart,
- onStart: this.onStart.bind(this),
- onDrag: this.onDrag.bind(this),
- onEnd: Ext.Function.createDelayed(this.onEnd, 100, this)
- });
- /**
- * @property dragRegion
- * @type Ext.util.Region
- * Represents the region currently dragged out by the user. This is used to figure out which dataview nodes are
- * in the selected area and to set the size of the Proxy element used to highlight the current drag area
- */
- this.dragRegion = Ext.create('Ext.util.Region');
- },
- /**
- * @private
- * Listener attached to the DragTracker's onBeforeStart event. Returns false if the drag didn't start within the
- * DataView's el
- */
- onBeforeStart: function(e) {
- return e.target === this.dataview.getEl().dom;
- },
- /**
- * @private
- * Listener attached to the DragTracker's onStart event. Cancel's the DataView's containerclick event from firing
- * and sets the start co-ordinates of the Proxy element. Clears any existing DataView selection
- * @param {Ext.event.Event} e The click event
- */
- onStart: function(e) {
- var dataview = this.dataview;
- // Flag which controls whether the cancelClick method vetoes the processing of the DataView's containerclick event.
- // On IE (where else), this needs to remain set for a millisecond after mouseup because even though the mouse has
- // moved, the mouseup will still trigger a click event.
- this.dragging = true;
- //here we reset and show the selection proxy element and cache the regions each item in the dataview take up
- this.fillRegions();
- this.getProxy().show();
- dataview.getSelectionModel().deselectAll();
- },
- /**
- * @private
- * Reusable handler that's used to cancel the container click event when dragging on the dataview. See onStart for
- * details
- */
- cancelClick: function() {
- return !this.dragging;
- },
- /**
- * @private
- * Listener attached to the DragTracker's onDrag event. Figures out how large the drag selection area should be and
- * updates the proxy element's size to match. Then iterates over all of the rendered items and marks them selected
- * if the drag region touches them
- * @param {Ext.event.Event} e The drag event
- */
- onDrag: function(e) {
- var selModel = this.dataview.getSelectionModel(),
- dragRegion = this.dragRegion,
- bodyRegion = this.bodyRegion,
- proxy = this.getProxy(),
- regions = this.regions,
- length = regions.length,
- startXY = this.tracker.startXY,
- currentXY = this.tracker.getXY(),
- minX = Math.min(startXY[0], currentXY[0]),
- minY = Math.min(startXY[1], currentXY[1]),
- width = Math.abs(startXY[0] - currentXY[0]),
- height = Math.abs(startXY[1] - currentXY[1]),
- region, selected, i;
- Ext.apply(dragRegion, {
- top: minY,
- left: minX,
- right: minX + width,
- bottom: minY + height
- });
- dragRegion.constrainTo(bodyRegion);
- proxy.setBox(dragRegion);
- for (i = 0; i < length; i++) {
- region = regions[i];
- selected = dragRegion.intersect(region);
- if (selected) {
- selModel.select(i, true);
- } else {
- selModel.deselect(i);
- }
- }
- },
- /**
- * @method
- * @private
- * Listener attached to the DragTracker's onEnd event. This is a delayed function which executes 1
- * millisecond after it has been called. This is because the dragging flag must remain active to cancel
- * the containerclick event which the mouseup event will trigger.
- * @param {Ext.event.Event} e The event object
- */
- onEnd: function(e) {
- var dataview = this.dataview,
- selModel = dataview.getSelectionModel();
- this.dragging = false;
- this.getProxy().hide();
- },
- /**
- * @private
- * Creates a Proxy element that will be used to highlight the drag selection region
- * @return {Ext.Element} The Proxy element
- */
- getProxy: function() {
- if (!this.proxy) {
- this.proxy = this.dataview.getEl().createChild({
- tag: 'div',
- cls: 'x-view-selector'
- });
- }
- return this.proxy;
- },
- /**
- * @private
- * Gets the region taken up by each rendered node in the DataView. We use these regions to figure out which nodes
- * to select based on the selector region the user has dragged out
- */
- fillRegions: function() {
- var dataview = this.dataview,
- regions = this.regions = [];
- dataview.all.each(function(node) {
- regions.push(node.getRegion());
- });
- this.bodyRegion = dataview.getEl().getRegion();
- }
- });
- /**
- * ## Basic DataView with Draggable mixin.
- *
- * Ext.Loader.setPath('Ext.ux', '../../../SDK/extjs/examples/ux');
- *
- * Ext.define('My.cool.View', {
- * extend: 'Ext.view.View',
- *
- * mixins: {
- * draggable: 'Ext.ux.DataView.Draggable'
- * },
- *
- * initComponent: function() {
- * this.mixins.draggable.init(this, {
- * ddConfig: {
- * ddGroup: 'someGroup'
- * }
- * });
- *
- * this.callParent(arguments);
- * }
- * });
- *
- * Ext.onReady(function () {
- * Ext.create('Ext.data.Store', {
- * storeId: 'baseball',
- * fields: ['team', 'established'],
- * data: [
- * { team: 'Atlanta Braves', established: '1871' },
- * { team: 'Miami Marlins', established: '1993' },
- * { team: 'New York Mets', established: '1962' },
- * { team: 'Philadelphia Phillies', established: '1883' },
- * { team: 'Washington Nationals', established: '1969' }
- * ]
- * });
- *
- * Ext.create('My.cool.View', {
- * store: Ext.StoreMgr.get('baseball'),
- * tpl: [
- * '<tpl for=".">',
- * '<p class="team">',
- * 'The {team} were founded in {established}.',
- * '</p>',
- * '</tpl>'
- * ],
- * itemSelector: 'p.team',
- * renderTo: Ext.getBody()
- * });
- * });
- */
- Ext.define('Ext.ux.DataView.Draggable', {
- requires: 'Ext.dd.DragZone',
- /**
- * @cfg {String} ghostCls The CSS class added to the outermost element of the created ghost proxy
- * (defaults to 'x-dataview-draggable-ghost')
- */
- ghostCls: 'x-dataview-draggable-ghost',
- /**
- * @cfg {Ext.XTemplate/Array} ghostTpl The template used in the ghost DataView
- */
- ghostTpl: [
- '<tpl for=".">',
- '{title}',
- '</tpl>'
- ],
- /**
- * @cfg {Object} ddConfig Config object that is applied to the internally created DragZone
- */
- /**
- * @cfg {String} ghostConfig Config object that is used to configure the internally created DataView
- */
- init: function(dataview, config) {
- /**
- * @property dataview
- * @type Ext.view.View
- * The Ext.view.View instance that this DragZone is attached to
- */
- this.dataview = dataview;
- dataview.on('render', this.onRender, this);
- Ext.apply(this, {
- itemSelector: dataview.itemSelector,
- ghostConfig: {}
- }, config || {});
- Ext.applyIf(this.ghostConfig, {
- itemSelector: 'img',
- cls: this.ghostCls,
- tpl: this.ghostTpl
- });
- },
- /**
- * @private
- * Called when the attached DataView is rendered. Sets up the internal DragZone
- */
- onRender: function() {
- var me = this,
- config = Ext.apply({}, me.ddConfig || {}, {
- dvDraggable: me,
- dataview: me.dataview,
- getDragData: me.getDragData,
- getTreeNode: me.getTreeNode,
- afterRepair: me.afterRepair,
- getRepairXY: me.getRepairXY
- });
- /**
- * @property dragZone
- * @type Ext.dd.DragZone
- * The attached DragZone instane
- */
- me.dragZone = Ext.create('Ext.dd.DragZone', me.dataview.getEl(), config);
- // This is for https://www.w3.org/TR/pointerevents/ platforms.
- // On these platforms, the pointerdown event (single touchstart) is reserved for
- // initiating a scroll gesture. Setting the items draggable defeats that and
- // enables the touchstart event to trigger a drag.
- //
- // Two finger dragging will still scroll on these platforms.
- me.dataview.setItemsDraggable(true);
- },
- getDragData: function(e) {
- var draggable = this.dvDraggable,
- dataview = this.dataview,
- selModel = dataview.getSelectionModel(),
- target = e.getTarget(draggable.itemSelector),
- selected, dragData;
- if (target) {
- // preventDefault is needed here to avoid the browser dragging the image
- // instead of dragging the container like it's supposed to
- e.preventDefault();
- if (!dataview.isSelected(target)) {
- selModel.select(dataview.getRecord(target));
- }
- selected = dataview.getSelectedNodes();
- dragData = {
- copy: true,
- nodes: selected,
- records: selModel.getSelection(),
- item: true
- };
- if (selected.length === 1) {
- dragData.single = true;
- dragData.ddel = target;
- } else {
- dragData.multi = true;
- dragData.ddel = draggable.prepareGhost(selModel.getSelection());
- }
- return dragData;
- }
- return false;
- },
- getTreeNode: function() {},
- // console.log('test');
- afterRepair: function() {
- this.dragging = false;
- var nodes = this.dragData.nodes,
- length = nodes.length,
- i;
- //FIXME: Ext.fly does not work here for some reason, only frames the last node
- for (i = 0; i < length; i++) {
- Ext.get(nodes[i]).frame('#8db2e3', 1);
- }
- },
- /**
- * @private
- * Returns the x and y co-ordinates that the dragged item should be animated back to if it was dropped on an
- * invalid drop target. If we're dragging more than one item we don't animate back and just allow afterRepair
- * to frame each dropped item.
- */
- getRepairXY: function(e) {
- if (this.dragData.multi) {
- return false;
- } else {
- var repairEl = Ext.get(this.dragData.ddel),
- repairXY = repairEl.getXY();
- //take the item's margins and padding into account to make the repair animation line up perfectly
- repairXY[0] += repairEl.getPadding('t') + repairEl.getMargin('t');
- repairXY[1] += repairEl.getPadding('l') + repairEl.getMargin('l');
- return repairXY;
- }
- },
- /**
- * Updates the internal ghost DataView by ensuring it is rendered and contains the correct records
- * @param {Array} records The set of records that is currently selected in the parent DataView
- * @return {HTMLElement} The Ghost DataView's encapsulating HTMLElement.
- */
- prepareGhost: function(records) {
- return this.createGhost(records).getEl().dom;
- },
- /**
- * @private
- * Creates the 'ghost' DataView that follows the mouse cursor during the drag operation. This div is usually a
- * lighter-weight representation of just the nodes that are selected in the parent DataView.
- */
- createGhost: function(records) {
- var me = this,
- store;
- if (me.ghost) {
- (store = me.ghost.store).loadRecords(records);
- } else {
- store = Ext.create('Ext.data.Store', {
- model: records[0].self
- });
- store.loadRecords(records);
- me.ghost = Ext.create('Ext.view.View', Ext.apply({
- renderTo: document.createElement('div'),
- store: store
- }, me.ghostConfig));
- me.ghost.container.skipGarbageCollection = me.ghost.el.skipGarbageCollection = true;
- }
- store.clearData();
- return me.ghost;
- },
- destroy: function() {
- var ghost = this.ghost;
- if (ghost) {
- ghost.container.destroy();
- ghost.destroy();
- }
- this.callParent();
- }
- });
- /**
- *
- */
- Ext.define('Ext.ux.DataView.LabelEditor', {
- extend: 'Ext.Editor',
- alias: 'plugin.dataviewlabeleditor',
- alignment: 'tl-tl',
- completeOnEnter: true,
- cancelOnEsc: true,
- shim: false,
- autoSize: {
- width: 'boundEl',
- height: 'field'
- },
- labelSelector: 'x-editable',
- requires: [
- 'Ext.form.field.Text'
- ],
- constructor: function(config) {
- config.field = config.field || Ext.create('Ext.form.field.Text', {
- allowOnlyWhitespace: false,
- selectOnFocus: true
- });
- this.callParent([
- config
- ]);
- },
- init: function(view) {
- this.view = view;
- this.mon(view, 'afterrender', this.bindEvents, this);
- this.on('complete', this.onSave, this);
- },
- // initialize events
- bindEvents: function() {
- this.mon(this.view.getEl(), {
- click: {
- fn: this.onClick,
- scope: this
- }
- });
- },
- // on mousedown show editor
- onClick: function(e, target) {
- var me = this,
- item, record;
- if (Ext.fly(target).hasCls(me.labelSelector) && !me.editing && !e.ctrlKey && !e.shiftKey) {
- e.stopEvent();
- item = me.view.findItemByChild(target);
- record = me.view.store.getAt(me.view.indexOf(item));
- me.startEdit(target, record.data[me.dataIndex]);
- me.activeRecord = record;
- } else if (me.editing) {
- me.field.blur();
- e.preventDefault();
- }
- },
- // update record
- onSave: function(ed, value) {
- this.activeRecord.set(this.dataIndex, value);
- }
- });
- /**
- * @class Ext.ux.DataViewTransition
- * Transition plugin for DataViews
- */
- Ext.ux.DataViewTransition = Ext.extend(Object, {
- /**
- * @property defaults
- * @type Object
- * Default configuration options for all DataViewTransition instances
- */
- defaults: {
- duration: 750,
- idProperty: 'id'
- },
- /**
- * Creates the plugin instance, applies defaults
- * @constructor
- * @param {Object} config Optional config object
- */
- constructor: function(config) {
- Ext.apply(this, config || {}, this.defaults);
- },
- /**
- * Initializes the transition plugin. Overrides the dataview's default refresh function
- * @param {Ext.view.View} dataview The dataview
- */
- init: function(dataview) {
- /**
- * @property dataview
- * @type Ext.view.View
- * Reference to the DataView this instance is bound to
- */
- this.dataview = dataview;
- var idProperty = this.idProperty;
- dataview.blockRefresh = true;
- dataview.updateIndexes = Ext.Function.createSequence(dataview.updateIndexes, function() {
- this.getTargetEl().select(this.itemSelector).each(function(element, composite, index) {
- element.id = element.dom.id = Ext.util.Format.format("{0}-{1}", dataview.id, dataview.store.getAt(index).get(idProperty));
- }, this);
- }, dataview);
- /**
- * @property dataviewID
- * @type String
- * The string ID of the DataView component. This is used internally when animating child objects
- */
- this.dataviewID = dataview.id;
- /**
- * @property cachedStoreData
- * @type Object
- * A cache of existing store data, keyed by id. This is used to determine
- * whether any items were added or removed from the store on data change
- */
- this.cachedStoreData = {};
- //var store = dataview.store;
- //catch the store data with the snapshot immediately
- this.cacheStoreData(dataview.store.snapshot);
- dataview.store.on('datachanged', function(store) {
- var parentEl = dataview.getTargetEl(),
- calcItem = store.getAt(0),
- added = this.getAdded(store),
- removed = this.getRemoved(store),
- previous = this.getRemaining(store),
- existing = Ext.apply({}, previous, added);
- //hide old items
- Ext.each(removed, function(item) {
- Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).animate({
- remove: false,
- duration: duration,
- opacity: 0,
- useDisplay: true
- });
- }, this);
- //store is empty
- if (calcItem == undefined) {
- this.cacheStoreData(store);
- return;
- }
- var el = Ext.get(this.dataviewID + "-" + calcItem.get(this.idProperty));
- //calculate the number of rows and columns we have
- var itemCount = store.getCount(),
- itemWidth = el.getMargin('lr') + el.getWidth(),
- itemHeight = el.getMargin('bt') + el.getHeight(),
- dvWidth = parentEl.getWidth(),
- columns = Math.floor(dvWidth / itemWidth),
- rows = Math.ceil(itemCount / columns),
- currentRows = Math.ceil(this.getExistingCount() / columns);
- //make sure the correct styles are applied to the parent element
- parentEl.applyStyles({
- display: 'block',
- position: 'relative'
- });
- //stores the current top and left values for each element (discovered below)
- var oldPositions = {},
- newPositions = {},
- elCache = {};
- //find current positions of each element and save a reference in the elCache
- Ext.iterate(previous, function(id, item) {
- var id = item.get(this.idProperty),
- el = elCache[id] = Ext.get(this.dataviewID + '-' + id);
- oldPositions[id] = {
- top: el.getY() - parentEl.getY() - el.getMargin('t') - parentEl.getPadding('t'),
- left: el.getX() - parentEl.getX() - el.getMargin('l') - parentEl.getPadding('l')
- };
- }, this);
- //set absolute positioning on all DataView items. We need to set position, left and
- //top at the same time to avoid any flickering
- Ext.iterate(previous, function(id, item) {
- var oldPos = oldPositions[id],
- el = elCache[id];
- if (el.getStyle('position') != 'absolute') {
- elCache[id].applyStyles({
- position: 'absolute',
- left: oldPos.left + "px",
- top: oldPos.top + "px",
- //we set the width here to make ListViews work correctly. This is not needed for DataViews
- width: el.getWidth(!Ext.isIE || Ext.isStrict),
- height: el.getHeight(!Ext.isIE || Ext.isStrict)
- });
- }
- });
- //get new positions
- var index = 0;
- Ext.iterate(store.data.items, function(item) {
- var id = item.get(idProperty),
- el = elCache[id];
- var column = index % columns,
- row = Math.floor(index / columns),
- top = row * itemHeight,
- left = column * itemWidth;
- newPositions[id] = {
- top: top,
- left: left
- };
- index++;
- }, this);
- //do the movements
- var startTime = new Date(),
- duration = this.duration,
- dataviewID = this.dataviewID;
- var doAnimate = function() {
- var elapsed = new Date() - startTime,
- fraction = elapsed / duration;
- if (fraction >= 1) {
- for (var id in newPositions) {
- Ext.fly(dataviewID + '-' + id).applyStyles({
- top: newPositions[id].top + "px",
- left: newPositions[id].left + "px"
- });
- }
- Ext.TaskManager.stop(task);
- } else {
- //move each item
- for (var id in newPositions) {
- if (!previous[id]) {
-
- continue;
- }
-
- var oldPos = oldPositions[id],
- newPos = newPositions[id],
- oldTop = oldPos.top,
- newTop = newPos.top,
- oldLeft = oldPos.left,
- newLeft = newPos.left,
- diffTop = fraction * Math.abs(oldTop - newTop),
- diffLeft = fraction * Math.abs(oldLeft - newLeft),
- midTop = oldTop > newTop ? oldTop - diffTop : oldTop + diffTop,
- midLeft = oldLeft > newLeft ? oldLeft - diffLeft : oldLeft + diffLeft;
- Ext.fly(dataviewID + '-' + id).applyStyles({
- top: midTop + "px",
- left: midLeft + "px"
- });
- }
- }
- };
- var task = {
- run: doAnimate,
- interval: 20,
- scope: this
- };
- Ext.TaskManager.start(task);
- //<debug>
- var count = 0;
- for (var k in added) {
- count++;
- }
- if (Ext.global.console && Ext.global.console.log) {
- Ext.global.console.log('added:', count);
- }
- //</debug>
- //show new items
- Ext.iterate(added, function(id, item) {
- Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).applyStyles({
- top: newPositions[item.get(this.idProperty)].top + "px",
- left: newPositions[item.get(this.idProperty)].left + "px"
- });
- Ext.fly(this.dataviewID + '-' + item.get(this.idProperty)).animate({
- remove: false,
- duration: duration,
- opacity: 1
- });
- }, this);
- this.cacheStoreData(store);
- }, this);
- },
- /**
- * Caches the records from a store locally for comparison later
- * @param {Ext.data.Store} store The store to cache data from
- */
- cacheStoreData: function(store) {
- this.cachedStoreData = {};
- store.each(function(record) {
- this.cachedStoreData[record.get(this.idProperty)] = record;
- }, this);
- },
- /**
- * Returns all records that were already in the DataView
- * @return {Object} All existing records
- */
- getExisting: function() {
- return this.cachedStoreData;
- },
- /**
- * Returns the total number of items that are currently visible in the DataView
- * @return {Number} The number of existing items
- */
- getExistingCount: function() {
- var count = 0,
- items = this.getExisting();
- for (var k in items) count++;
- return count;
- },
- /**
- * Returns all records in the given store that were not already present
- * @param {Ext.data.Store} store The updated store instance
- * @return {Object} Object of records not already present in the dataview in format {id: record}
- */
- getAdded: function(store) {
- var added = {};
- store.each(function(record) {
- if (this.cachedStoreData[record.get(this.idProperty)] == undefined) {
- added[record.get(this.idProperty)] = record;
- }
- }, this);
- return added;
- },
- /**
- * Returns all records that are present in the DataView but not the new store
- * @param {Ext.data.Store} store The updated store instance
- * @return {Array} Array of records that used to be present
- */
- getRemoved: function(store) {
- var removed = [];
- for (var id in this.cachedStoreData) {
- if (store.findExact(this.idProperty, Number(id)) == -1) {
- removed.push(this.cachedStoreData[id]);
- }
- }
- return removed;
- },
- /**
- * Returns all records that are already present and are still present in the new store
- * @param {Ext.data.Store} store The updated store instance
- * @return {Object} Object of records that are still present from last time in format {id: record}
- */
- getRemaining: function(store) {
- var remaining = {};
- store.each(function(record) {
- if (this.cachedStoreData[record.get(this.idProperty)] != undefined) {
- remaining[record.get(this.idProperty)] = record;
- }
- }, this);
- return remaining;
- }
- });
- /**
- * An explorer component for navigating hierarchical content. Consists of a breadcrumb bar
- * at the top, tree navigation on the left, and a center panel which displays the contents
- * of a given node.
- */
- Ext.define('Ext.ux.Explorer', {
- extend: 'Ext.panel.Panel',
- xtype: 'explorer',
- requires: [
- 'Ext.layout.container.Border',
- 'Ext.toolbar.Breadcrumb',
- 'Ext.tree.Panel'
- ],
- config: {
- /**
- * @cfg {Object} breadcrumb
- * Configuration object for the breadcrumb toolbar
- */
- breadcrumb: {
- dock: 'top',
- xtype: 'breadcrumb',
- reference: 'breadcrumb'
- },
- /**
- * @cfg {Object} contentView
- * Configuration object for the "content" data view
- */
- contentView: {
- xtype: 'dataview',
- reference: 'contentView',
- region: 'center',
- cls: Ext.baseCSSPrefix + 'explorer-view',
- itemSelector: '.' + Ext.baseCSSPrefix + 'explorer-item',
- 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>'
- },
- /**
- * @cfg {Ext.data.TreeStore} store
- * The TreeStore to use as the data source
- */
- store: null,
- /**
- * @cfg {Object} tree
- * Configuration object for the tree
- */
- tree: {
- xtype: 'treepanel',
- reference: 'tree',
- region: 'west',
- width: 200
- }
- },
- renderConfig: {
- /**
- * @cfg {Ext.data.TreeModel} selection
- * The selected node
- * @accessor
- */
- selection: null
- },
- layout: 'border',
- referenceHolder: true,
- defaultListenerScope: true,
- cls: Ext.baseCSSPrefix + 'explorer',
- initComponent: function() {
- var me = this,
- store = me.getStore();
- //<debug>
- if (!store) {
- Ext.raise('Ext.ux.Explorer requires a store.');
- }
- //</debug>
- me.dockedItems = [
- me.getBreadcrumb()
- ];
- me.items = [
- me.getTree(),
- me.getContentView()
- ];
- me.callParent();
- },
- applyBreadcrumb: function(breadcrumb) {
- var store = this.getStore();
- breadcrumb = Ext.create(Ext.apply({
- store: store,
- selection: store.getRoot()
- }, breadcrumb));
- breadcrumb.on('selectionchange', '_onBreadcrumbSelectionChange', this);
- return breadcrumb;
- },
- applyContentView: function(contentView) {
- /**
- * @property {Ext.data.Store} contentStore
- * @private
- * The backing store for the content view
- */
- var contentStore = this.contentStore = new Ext.data.Store({
- model: this.getStore().model
- });
- contentView = Ext.create(Ext.apply({
- store: contentStore
- }, contentView));
- return contentView;
- },
- applyTree: function(tree) {
- tree = Ext.create(Ext.apply({
- store: this.getStore()
- }, tree));
- tree.on('selectionchange', '_onTreeSelectionChange', this);
- return tree;
- },
- updateSelection: function(node) {
- var me = this,
- refs = me.getReferences(),
- breadcrumb = refs.breadcrumb,
- tree = refs.tree,
- treeSelectionModel = tree.getSelectionModel(),
- contentStore = me.contentStore,
- parentNode, treeView;
- if (breadcrumb.getSelection() !== node) {
- breadcrumb.setSelection(node);
- }
- if (treeSelectionModel.getSelection()[0] !== node) {
- treeSelectionModel.select([
- node
- ]);
- parentNode = node.parentNode;
- if (parentNode) {
- parentNode.expand();
- }
- treeView = tree.getView();
- treeView.scrollRowIntoView(treeView.getRow(node));
- }
- contentStore.removeAll();
- contentStore.add(node.hasChildNodes() ? node.childNodes : [
- node
- ]);
- },
- updateStore: function(store) {
- this.getBreadcrumb().setStore(store);
- },
- privates: {
- /**
- * Handles the tree's selectionchange event
- * @private
- * @param {Ext.tree.Panel} tree
- * @param {Ext.data.TreeModel[]} selection
- */
- _onTreeSelectionChange: function(tree, selection) {
- this.setSelection(selection[0]);
- },
- /**
- * Handles the breadcrumb bar's selectionchange event
- */
- _onBreadcrumbSelectionChange: function(breadcrumb, selection) {
- this.setSelection(selection);
- }
- }
- });
- /**
- * A plugin for Field Components which creates clones of the Field for as
- * long as the user keeps filling them. Leaving the final one blank ends the repeating series.
- *
- * Usage:
- *
- * items: [{
- * xtype: 'combo',
- * plugins: {
- * fieldreplicator: true
- * },
- * triggerAction: 'all',
- * fieldLabel: 'Select recipient',
- * store: recipientStore
- * }]
- *
- */
- Ext.define('Ext.ux.FieldReplicator', {
- alias: 'plugin.fieldreplicator',
- init: function(field) {
- // Assign the field an id grouping it with fields cloned from it. If it already
- // has an id that means it is itself a clone.
- if (!field.replicatorId) {
- field.replicatorId = Ext.id();
- }
- field.on('blur', this.onBlur, this);
- },
- onBlur: function(field) {
- var ownerCt = field.ownerCt,
- replicatorId = field.replicatorId,
- isEmpty = Ext.isEmpty(field.getRawValue()),
- siblings = ownerCt.query('[replicatorId=' + replicatorId + ']'),
- isLastInGroup = siblings[siblings.length - 1] === field,
- clone, idx;
- // If a field before the final one was blanked out, remove it
- if (isEmpty && !isLastInGroup) {
- Ext.defer(field.destroy, 10, field);
- }
- //delay to allow tab key to move focus first
- // If the field is the last in the list and has a value, add a cloned field after it
- else if (!isEmpty && isLastInGroup) {
- if (field.onReplicate) {
- field.onReplicate();
- }
- clone = field.cloneConfig({
- replicatorId: replicatorId
- });
- idx = ownerCt.items.indexOf(field);
- ownerCt.add(idx + 1, clone);
- }
- }
- });
- /**
- * The GMap Panel UX extends `Ext.panel.Panel` in order to display Google Maps.
- *
- * It is important to note that you must include the following Google Maps API above bootstrap.js in your
- * application's index.html file (or equivilant).
- *
- * <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?v=3&sensor=false"></script>
- *
- * It is important to note that due to the Google Maps loader, you cannot currently include
- * the above JS resource in the Cmd generated app.json file. Doing so interferes with the loading of
- * Ext JS and Google Maps.
- *
- * The following example creates a window containing a GMap Panel. In this case, the center
- * is set as geoCodeAddr, which is a string that Google translates into longitude and latitude.
- *
- * var mapwin = Ext.create('Ext.Window', {
- * layout: 'fit',
- * title: 'GMap Window',
- * width: 450,
- * height: 250,
- * items: {
- * xtype: 'gmappanel',
- * gmapType: 'map',
- * center: {
- * geoCodeAddr: "221B Baker Street",
- * marker: {
- * title: 'Holmes Home'
- * }
- * },
- * mapOptions : {
- * mapTypeId: google.maps.MapTypeId.ROADMAP
- * }
- * }
- * }).show();
- *
- */
- Ext.define('Ext.ux.GMapPanel', {
- extend: 'Ext.panel.Panel',
- alias: 'widget.gmappanel',
- requires: [
- 'Ext.window.MessageBox'
- ],
- initComponent: function() {
- Ext.applyIf(this, {
- plain: true,
- gmapType: 'map',
- border: false
- });
- this.callParent();
- },
- onBoxReady: function() {
- var center = this.center;
- this.callParent(arguments);
- if (center) {
- if (center.geoCodeAddr) {
- this.lookupCode(center.geoCodeAddr, center.marker);
- } else {
- this.createMap(center);
- }
- } else {
- Ext.raise('center is required');
- }
- },
- createMap: function(center, marker) {
- var options = Ext.apply({}, this.mapOptions);
- options = Ext.applyIf(options, {
- zoom: 14,
- center: center,
- mapTypeId: google.maps.MapTypeId.HYBRID
- });
- this.gmap = new google.maps.Map(this.body.dom, options);
- if (marker) {
- this.addMarker(Ext.applyIf(marker, {
- position: center
- }));
- }
- Ext.each(this.markers, this.addMarker, this);
- this.fireEvent('mapready', this, this.gmap);
- },
- addMarker: function(marker) {
- marker = Ext.apply({
- map: this.gmap
- }, marker);
- if (!marker.position) {
- marker.position = new google.maps.LatLng(marker.lat, marker.lng);
- }
- var o = new google.maps.Marker(marker);
- Ext.Object.each(marker.listeners, function(name, fn) {
- google.maps.event.addListener(o, name, fn);
- });
- return o;
- },
- lookupCode: function(addr, marker) {
- this.geocoder = new google.maps.Geocoder();
- this.geocoder.geocode({
- address: addr
- }, Ext.Function.bind(this.onLookupComplete, this, [
- marker
- ], true));
- },
- onLookupComplete: function(data, response, marker) {
- if (response != 'OK') {
- Ext.MessageBox.alert('Error', 'An error occured: "' + response + '"');
- return;
- }
- this.createMap(data[0].geometry.location, marker);
- },
- afterComponentLayout: function(w, h) {
- this.callParent(arguments);
- this.redraw();
- },
- redraw: function() {
- var map = this.gmap;
- if (map) {
- google.maps.event.trigger(map, 'resize');
- }
- }
- });
- /**
- * Barebones iframe implementation.
- */
- Ext.define('Ext.ux.IFrame', {
- extend: 'Ext.Component',
- alias: 'widget.uxiframe',
- loadMask: 'Loading...',
- src: 'about:blank',
- renderTpl: [
- '<iframe src="{src}" id="{id}-iframeEl" data-ref="iframeEl" name="{frameName}" width="100%" height="100%" frameborder="0"></iframe>'
- ],
- childEls: [
- 'iframeEl'
- ],
- initComponent: function() {
- this.callParent();
- this.frameName = this.frameName || this.id + '-frame';
- },
- initEvents: function() {
- var me = this;
- me.callParent();
- me.iframeEl.on('load', me.onLoad, me);
- },
- initRenderData: function() {
- return Ext.apply(this.callParent(), {
- src: this.src,
- frameName: this.frameName
- });
- },
- getBody: function() {
- var doc = this.getDoc();
- return doc.body || doc.documentElement;
- },
- getDoc: function() {
- try {
- return this.getWin().document;
- } catch (ex) {
- return null;
- }
- },
- getWin: function() {
- var me = this,
- name = me.frameName,
- win = Ext.isIE ? me.iframeEl.dom.contentWindow : window.frames[name];
- return win;
- },
- getFrame: function() {
- var me = this;
- return me.iframeEl.dom;
- },
- onLoad: function() {
- var me = this,
- doc = me.getDoc();
- if (doc) {
- this.el.unmask();
- this.fireEvent('load', this);
- } else if (me.src) {
- this.el.unmask();
- this.fireEvent('error', this);
- }
- },
- load: function(src) {
- var me = this,
- text = me.loadMask,
- frame = me.getFrame();
- if (me.fireEvent('beforeload', me, src) !== false) {
- if (text && me.el) {
- me.el.mask(text);
- }
- frame.src = me.src = (src || me.src);
- }
- }
- });
- /*
- * Note: Event relayers are not needed here because the combination of the gesture system and
- * normal focus/blur will handle it.
- * Tested with the examples/classic/desktop app.
- */
- /*
- * TODO items:
- *
- * Iframe should clean up any Ext.dom.Element wrappers around its window, document
- * documentElement and body when it is destroyed. This helps prevent "Permission Denied"
- * errors in IE when Ext.dom.GarbageCollector tries to access those objects on an orphaned
- * iframe. Permission Denied errors can occur in one of the following 2 scenarios:
- *
- * a. When an iframe is removed from the document, and all references to it have been
- * removed, IE will "clear" the window object. At this point the window object becomes
- * completely inaccessible - accessing any of its properties results in a "Permission
- * Denied" error. http://msdn.microsoft.com/en-us/library/ie/hh180174(v=vs.85).aspx
- *
- * b. When an iframe is unloaded (either by navigating to a new url, or via document.open/
- * document.write, new html and body elements are created and the old the html and body
- * elements are orphaned. Accessing the html and body elements or any of their properties
- * results in a "Permission Denied" error.
- */
- /**
- * Basic status bar component that can be used as the bottom toolbar of any {@link Ext.Panel}. In addition to
- * supporting the standard {@link Ext.toolbar.Toolbar} interface for adding buttons, menus and other items, the StatusBar
- * provides a greedy status element that can be aligned to either side and has convenient methods for setting the
- * status text and icon. You can also indicate that something is processing using the {@link #showBusy} method.
- *
- * Ext.create('Ext.Panel', {
- * title: 'StatusBar',
- * // etc.
- * bbar: Ext.create('Ext.ux.StatusBar', {
- * id: 'my-status',
- *
- * // defaults to use when the status is cleared:
- * defaultText: 'Default status text',
- * defaultIconCls: 'default-icon',
- *
- * // values to set initially:
- * text: 'Ready',
- * iconCls: 'ready-icon',
- *
- * // any standard Toolbar items:
- * items: [{
- * text: 'A Button'
- * }, '-', 'Plain Text']
- * })
- * });
- *
- * // Update the status bar later in code:
- * var sb = Ext.getCmp('my-status');
- * sb.setStatus({
- * text: 'OK',
- * iconCls: 'ok-icon',
- * clear: true // auto-clear after a set interval
- * });
- *
- * // Set the status bar to show that something is processing:
- * sb.showBusy();
- *
- * // processing....
- *
- * sb.clearStatus(); // once completeed
- *
- */
- Ext.define('Ext.ux.statusbar.StatusBar', {
- extend: 'Ext.toolbar.Toolbar',
- xtype: 'statusbar',
- alternateClassName: 'Ext.ux.StatusBar',
- requires: [
- 'Ext.toolbar.TextItem'
- ],
- /**
- * @cfg {String} statusAlign
- * The alignment of the status element within the overall StatusBar layout. When the StatusBar is rendered,
- * it creates an internal div containing the status text and icon. Any additional Toolbar items added in the
- * StatusBar's {@link #cfg-items} config, or added via {@link #method-add} or any of the supported add* methods, will be
- * rendered, in added order, to the opposite side. The status element is greedy, so it will automatically
- * expand to take up all sapce left over by any other items. Example usage:
- *
- * // Create a left-aligned status bar containing a button,
- * // separator and text item that will be right-aligned (default):
- * Ext.create('Ext.Panel', {
- * title: 'StatusBar',
- * // etc.
- * bbar: Ext.create('Ext.ux.statusbar.StatusBar', {
- * defaultText: 'Default status text',
- * id: 'status-id',
- * items: [{
- * text: 'A Button'
- * }, '-', 'Plain Text']
- * })
- * });
- *
- * // By adding the statusAlign config, this will create the
- * // exact same toolbar, except the status and toolbar item
- * // layout will be reversed from the previous example:
- * Ext.create('Ext.Panel', {
- * title: 'StatusBar',
- * // etc.
- * bbar: Ext.create('Ext.ux.statusbar.StatusBar', {
- * defaultText: 'Default status text',
- * id: 'status-id',
- * statusAlign: 'right',
- * items: [{
- * text: 'A Button'
- * }, '-', 'Plain Text']
- * })
- * });
- */
- /**
- * @cfg {String} [defaultText='']
- * The default {@link #text} value. This will be used anytime the status bar is cleared with the
- * `useDefaults:true` option.
- */
- /**
- * @cfg {String} [defaultIconCls='']
- * The default {@link #iconCls} value (see the iconCls docs for additional details about customizing the icon).
- * This will be used anytime the status bar is cleared with the `useDefaults:true` option.
- */
- /**
- * @cfg {String} text
- * A string that will be <b>initially</b> set as the status message. This string
- * will be set as innerHTML (html tags are accepted) for the toolbar item.
- * If not specified, the value set for {@link #defaultText} will be used.
- */
- /**
- * @cfg [iconCls='']
- * @inheritdoc Ext.panel.Header#cfg-iconCls
- * @localdoc **Note:** This CSS class will be **initially** set as the status bar
- * icon. See also {@link #defaultIconCls} and {@link #busyIconCls}.
- *
- * Example usage:
- *
- * // Example CSS rule:
- * .x-statusbar .x-status-custom {
- * padding-left: 25px;
- * background: transparent url(images/custom-icon.gif) no-repeat 3px 2px;
- * }
- *
- * // Setting a default icon:
- * var sb = Ext.create('Ext.ux.statusbar.StatusBar', {
- * defaultIconCls: 'x-status-custom'
- * });
- *
- * // Changing the icon:
- * sb.setStatus({
- * text: 'New status',
- * iconCls: 'x-status-custom'
- * });
- */
- /**
- * @cfg {String} cls
- * The base class applied to the containing element for this component on render.
- */
- cls: 'x-statusbar',
- /**
- * @cfg {String} busyIconCls
- * The default {@link #iconCls} applied when calling {@link #showBusy}.
- * It can be overridden at any time by passing the `iconCls` argument into {@link #showBusy}.
- */
- busyIconCls: 'x-status-busy',
- /**
- * @cfg {String} busyText
- * The default {@link #text} applied when calling {@link #showBusy}.
- * It can be overridden at any time by passing the `text` argument into {@link #showBusy}.
- */
- busyText: 'Loading...',
- /**
- * @cfg {Number} autoClear
- * The number of milliseconds to wait after setting the status via
- * {@link #setStatus} before automatically clearing the status text and icon.
- * Note that this only applies when passing the `clear` argument to {@link #setStatus}
- * since that is the only way to defer clearing the status. This can
- * be overridden by specifying a different `wait` value in {@link #setStatus}.
- * Calls to {@link #clearStatus} always clear the status bar immediately and ignore this value.
- */
- autoClear: 5000,
- /**
- * @cfg {String} emptyText
- * The text string to use if no text has been set. If there are no other items in
- * the toolbar using an empty string (`''`) for this value would end up in the toolbar
- * height collapsing since the empty string will not maintain the toolbar height.
- * Use `''` if the toolbar should collapse in height vertically when no text is
- * specified and there are no other items in the toolbar.
- */
- emptyText: ' ',
- /**
- * @private
- */
- activeThreadId: 0,
- initComponent: function() {
- var right = this.statusAlign === 'right';
- this.callParent(arguments);
- this.currIconCls = this.iconCls || this.defaultIconCls;
- this.statusEl = Ext.create('Ext.toolbar.TextItem', {
- cls: 'x-status-text ' + (this.currIconCls || ''),
- text: this.text || this.defaultText || ''
- });
- if (right) {
- this.cls += ' x-status-right';
- this.add('->');
- this.add(this.statusEl);
- } else {
- this.insert(0, this.statusEl);
- this.insert(1, '->');
- }
- },
- /**
- * Sets the status {@link #text} and/or {@link #iconCls}. Also supports automatically clearing the
- * status that was set after a specified interval.
- *
- * Example usage:
- *
- * // Simple call to update the text
- * statusBar.setStatus('New status');
- *
- * // Set the status and icon, auto-clearing with default options:
- * statusBar.setStatus({
- * text: 'New status',
- * iconCls: 'x-status-custom',
- * clear: true
- * });
- *
- * // Auto-clear with custom options:
- * statusBar.setStatus({
- * text: 'New status',
- * iconCls: 'x-status-custom',
- * clear: {
- * wait: 8000,
- * anim: false,
- * useDefaults: false
- * }
- * });
- *
- * @param {Object/String} config A config object specifying what status to set, or a string assumed
- * to be the status text (and all other options are defaulted as explained below). A config
- * object containing any or all of the following properties can be passed:
- *
- * @param {String} config.text The status text to display. If not specified, any current
- * status text will remain unchanged.
- *
- * @param {String} config.iconCls The CSS class used to customize the status icon (see
- * {@link #iconCls} for details). If not specified, any current iconCls will remain unchanged.
- *
- * @param {Boolean/Number/Object} config.clear Allows you to set an internal callback that will
- * automatically clear the status text and iconCls after a specified amount of time has passed. If clear is not
- * specified, the new status will not be auto-cleared and will stay until updated again or cleared using
- * {@link #clearStatus}. If `true` is passed, the status will be cleared using {@link #autoClear},
- * {@link #defaultText} and {@link #defaultIconCls} via a fade out animation. If a numeric value is passed,
- * it will be used as the callback interval (in milliseconds), overriding the {@link #autoClear} value.
- * All other options will be defaulted as with the boolean option. To customize any other options,
- * you can pass an object in the format:
- *
- * @param {Number} config.clear.wait The number of milliseconds to wait before clearing
- * (defaults to {@link #autoClear}).
- * @param {Boolean} config.clear.anim False to clear the status immediately once the callback
- * executes (defaults to true which fades the status out).
- * @param {Boolean} config.clear.useDefaults False to completely clear the status text and iconCls
- * (defaults to true which uses {@link #defaultText} and {@link #defaultIconCls}).
- *
- * @return {Ext.ux.statusbar.StatusBar} this
- */
- setStatus: function(config) {
- var me = this;
- config = config || {};
- Ext.suspendLayouts();
- if (Ext.isString(config)) {
- config = {
- text: config
- };
- }
- if (config.text !== undefined) {
- me.setText(config.text);
- }
- if (config.iconCls !== undefined) {
- me.setIcon(config.iconCls);
- }
- if (config.clear) {
- var c = config.clear,
- wait = me.autoClear,
- defaults = {
- useDefaults: true,
- anim: true
- };
- if (Ext.isObject(c)) {
- c = Ext.applyIf(c, defaults);
- if (c.wait) {
- wait = c.wait;
- }
- } else if (Ext.isNumber(c)) {
- wait = c;
- c = defaults;
- } else if (Ext.isBoolean(c)) {
- c = defaults;
- }
- c.threadId = this.activeThreadId;
- Ext.defer(me.clearStatus, wait, me, [
- c
- ]);
- }
- Ext.resumeLayouts(true);
- return me;
- },
- /**
- * Clears the status {@link #text} and {@link #iconCls}. Also supports clearing via an optional fade out animation.
- *
- * @param {Object} [config] A config object containing any or all of the following properties. If this
- * object is not specified the status will be cleared using the defaults below:
- * @param {Boolean} config.anim True to clear the status by fading out the status element (defaults
- * to false which clears immediately).
- * @param {Boolean} config.useDefaults True to reset the text and icon using {@link #defaultText} and
- * {@link #defaultIconCls} (defaults to false which sets the text to '' and removes any existing icon class).
- *
- * @return {Ext.ux.statusbar.StatusBar} this
- */
- clearStatus: function(config) {
- config = config || {};
- var me = this,
- statusEl = me.statusEl;
- if (me.destroyed || config.threadId && config.threadId !== me.activeThreadId) {
- // this means the current call was made internally, but a newer
- // thread has set a message since this call was deferred. Since
- // we don't want to overwrite a newer message just ignore.
- return me;
- }
- var text = config.useDefaults ? me.defaultText : me.emptyText,
- iconCls = config.useDefaults ? (me.defaultIconCls ? me.defaultIconCls : '') : '';
- if (config.anim) {
- // animate the statusEl Ext.Element
- statusEl.el.puff({
- remove: false,
- useDisplay: true,
- callback: function() {
- statusEl.el.show();
- me.setStatus({
- text: text,
- iconCls: iconCls
- });
- }
- });
- } else {
- me.setStatus({
- text: text,
- iconCls: iconCls
- });
- }
- return me;
- },
- /**
- * Convenience method for setting the status text directly. For more flexible options see {@link #setStatus}.
- * @param {String} text (optional) The text to set (defaults to '')
- * @return {Ext.ux.statusbar.StatusBar} this
- */
- setText: function(text) {
- var me = this;
- me.activeThreadId++;
- me.text = text || '';
- if (me.rendered) {
- me.statusEl.setText(me.text);
- }
- return me;
- },
- /**
- * Returns the current status text.
- * @return {String} The status text
- */
- getText: function() {
- return this.text;
- },
- /**
- * Convenience method for setting the status icon directly. For more flexible options see {@link #setStatus}.
- * See {@link #iconCls} for complete details about customizing the icon.
- * @param {String} cls (optional) The icon class to set (defaults to '', and any current icon class is removed)
- * @return {Ext.ux.statusbar.StatusBar} this
- */
- setIcon: function(cls) {
- var me = this;
- me.activeThreadId++;
- cls = cls || '';
- if (me.rendered) {
- if (me.currIconCls) {
- me.statusEl.removeCls(me.currIconCls);
- me.currIconCls = null;
- }
- if (cls.length > 0) {
- me.statusEl.addCls(cls);
- me.currIconCls = cls;
- }
- } else {
- me.currIconCls = cls;
- }
- return me;
- },
- /**
- * Convenience method for setting the status text and icon to special values that are pre-configured to indicate
- * a "busy" state, usually for loading or processing activities.
- *
- * @param {Object/String} config (optional) A config object in the same format supported by {@link #setStatus}, or a
- * string to use as the status text (in which case all other options for setStatus will be defaulted). Use the
- * `text` and/or `iconCls` properties on the config to override the default {@link #busyText}
- * and {@link #busyIconCls} settings. If the config argument is not specified, {@link #busyText} and
- * {@link #busyIconCls} will be used in conjunction with all of the default options for {@link #setStatus}.
- * @return {Ext.ux.statusbar.StatusBar} this
- */
- showBusy: function(config) {
- if (Ext.isString(config)) {
- config = {
- text: config
- };
- }
- config = Ext.applyIf(config || {}, {
- text: this.busyText,
- iconCls: this.busyIconCls
- });
- return this.setStatus(config);
- }
- });
- /**
- * A GridPanel class with live search support.
- */
- Ext.define('Ext.ux.LiveSearchGridPanel', {
- extend: 'Ext.grid.Panel',
- requires: [
- 'Ext.toolbar.TextItem',
- 'Ext.form.field.Checkbox',
- 'Ext.form.field.Text',
- 'Ext.ux.statusbar.StatusBar'
- ],
- /**
- * @private
- * search value initialization
- */
- searchValue: null,
- /**
- * @private
- * The matched positions from the most recent search
- */
- matches: [],
- /**
- * @private
- * The current index matched.
- */
- currentIndex: null,
- /**
- * @private
- * The generated regular expression used for searching.
- */
- searchRegExp: null,
- /**
- * @private
- * Case sensitive mode.
- */
- caseSensitive: false,
- /**
- * @private
- * Regular expression mode.
- */
- regExpMode: false,
- /**
- * @cfg {String} matchCls
- * The matched string css classe.
- */
- matchCls: 'x-livesearch-match',
- defaultStatusText: 'Nothing Found',
- // Component initialization override: adds the top and bottom toolbars and setup headers renderer.
- initComponent: function() {
- var me = this;
- me.tbar = [
- 'Search',
- {
- xtype: 'textfield',
- name: 'searchField',
- hideLabel: true,
- width: 200,
- listeners: {
- change: {
- fn: me.onTextFieldChange,
- scope: this,
- buffer: 500
- }
- }
- },
- {
- xtype: 'button',
- text: '<',
- tooltip: 'Find Previous Row',
- handler: me.onPreviousClick,
- scope: me
- },
- {
- xtype: 'button',
- text: '>',
- tooltip: 'Find Next Row',
- handler: me.onNextClick,
- scope: me
- },
- '-',
- {
- xtype: 'checkbox',
- hideLabel: true,
- margin: '0 0 0 4px',
- handler: me.regExpToggle,
- scope: me
- },
- 'Regular expression',
- {
- xtype: 'checkbox',
- hideLabel: true,
- margin: '0 0 0 4px',
- handler: me.caseSensitiveToggle,
- scope: me
- },
- 'Case sensitive'
- ];
- me.bbar = new Ext.ux.StatusBar({
- defaultText: me.defaultStatusText,
- name: 'searchStatusBar'
- });
- me.callParent(arguments);
- },
- // afterRender override: it adds textfield and statusbar reference and start monitoring keydown events in textfield input
- afterRender: function() {
- var me = this;
- me.callParent(arguments);
- me.textField = me.down('textfield[name=searchField]');
- me.statusBar = me.down('statusbar[name=searchStatusBar]');
- me.view.on('cellkeydown', me.focusTextField, me);
- },
- focusTextField: function(view, td, cellIndex, record, tr, rowIndex, e, eOpts) {
- if (e.getKey() === e.S) {
- e.preventDefault();
- this.textField.focus();
- }
- },
- // detects html tag
- tagsRe: /<[^>]*>/gm,
- // DEL ASCII code
- tagsProtect: '\x0f',
- /**
- * In normal mode it returns the value with protected regexp characters.
- * In regular expression mode it returns the raw value except if the regexp is invalid.
- * @return {String} The value to process or null if the textfield value is blank or invalid.
- * @private
- */
- getSearchValue: function() {
- var me = this,
- value = me.textField.getValue();
- if (value === '') {
- return null;
- }
- if (!me.regExpMode) {
- value = Ext.String.escapeRegex(value);
- } else {
- try {
- new RegExp(value);
- } catch (error) {
- me.statusBar.setStatus({
- text: error.message,
- iconCls: 'x-status-error'
- });
- return null;
- }
- // this is stupid
- if (value === '^' || value === '$') {
- return null;
- }
- }
- return value;
- },
- /**
- * Finds all strings that matches the searched value in each grid cells.
- * @private
- */
- onTextFieldChange: function() {
- var me = this,
- count = 0,
- view = me.view,
- cellSelector = view.cellSelector,
- innerSelector = view.innerSelector,
- columns = me.visibleColumnManager.getColumns();
- view.refresh();
- // reset the statusbar
- me.statusBar.setStatus({
- text: me.defaultStatusText,
- iconCls: ''
- });
- me.searchValue = me.getSearchValue();
- me.matches = [];
- me.currentIndex = null;
- if (me.searchValue !== null) {
- me.searchRegExp = new RegExp(me.getSearchValue(), 'g' + (me.caseSensitive ? '' : 'i'));
- me.store.each(function(record, idx) {
- var node = view.getNode(record);
- if (node) {
- Ext.Array.forEach(columns, function(column) {
- var cell = Ext.fly(node).down(column.getCellInnerSelector(), true),
- matches, cellHTML, seen;
- if (cell) {
- matches = cell.innerHTML.match(me.tagsRe);
- cellHTML = cell.innerHTML.replace(me.tagsRe, me.tagsProtect);
- // populate indexes array, set currentIndex, and replace wrap matched string in a span
- cellHTML = cellHTML.replace(me.searchRegExp, function(m) {
- ++count;
- if (!seen) {
- me.matches.push({
- record: record,
- column: column
- });
- seen = true;
- }
- return '<span class="' + me.matchCls + '">' + m + '</span>';
- }, me);
- // restore protected tags
- Ext.each(matches, function(match) {
- cellHTML = cellHTML.replace(me.tagsProtect, match);
- });
- // update cell html
- cell.innerHTML = cellHTML;
- }
- });
- }
- }, me);
- // results found
- if (count) {
- me.currentIndex = 0;
- me.gotoCurrent();
- me.statusBar.setStatus({
- text: Ext.String.format('{0} match{1} found.', count, count === 1 ? 'es' : ''),
- iconCls: 'x-status-valid'
- });
- }
- }
- // no results found
- if (me.currentIndex === null) {
- me.getSelectionModel().deselectAll();
- me.textField.focus();
- }
- },
- /**
- * Selects the previous row containing a match.
- * @private
- */
- onPreviousClick: function() {
- var me = this,
- matches = me.matches,
- len = matches.length,
- idx = me.currentIndex;
- if (len) {
- me.currentIndex = idx === 0 ? len - 1 : idx - 1;
- me.gotoCurrent();
- }
- },
- /**
- * Selects the next row containing a match.
- * @private
- */
- onNextClick: function() {
- var me = this,
- matches = me.matches,
- len = matches.length,
- idx = me.currentIndex;
- if (len) {
- me.currentIndex = idx === len - 1 ? 0 : idx + 1;
- me.gotoCurrent();
- }
- },
- /**
- * Switch to case sensitive mode.
- * @private
- */
- caseSensitiveToggle: function(checkbox, checked) {
- this.caseSensitive = checked;
- this.onTextFieldChange();
- },
- /**
- * Switch to regular expression mode
- * @private
- */
- regExpToggle: function(checkbox, checked) {
- this.regExpMode = checked;
- this.onTextFieldChange();
- },
- privates: {
- gotoCurrent: function() {
- var pos = this.matches[this.currentIndex];
- this.getNavigationModel().setPosition(pos.record, pos.column);
- this.getSelectionModel().select(pos.record);
- }
- }
- });
- /**
- * The Preview Plugin enables toggle of a configurable preview of all visible records.
- *
- * Note: This plugin does NOT assert itself against an existing RowBody feature and may conflict with
- * another instance of the same plugin.
- */
- Ext.define('Ext.ux.PreviewPlugin', {
- extend: 'Ext.plugin.Abstract',
- alias: 'plugin.preview',
- requires: [
- 'Ext.grid.feature.RowBody'
- ],
- /**
- * @private
- * css class to use to hide the body
- */
- hideBodyCls: 'x-grid-row-body-hidden',
- /**
- * @cfg {String} bodyField
- * Field to display in the preview. Must be a field within the Model definition
- * that the store is using.
- */
- bodyField: '',
- /**
- * @cfg {Boolean} previewExpanded
- */
- previewExpanded: true,
- /**
- * Plugin may be safely declared on either a panel.Grid or a Grid View/viewConfig
- * @param {Ext.grid.Panel/Ext.view.View} target
- */
- setCmp: function(target) {
- this.callParent(arguments);
- // Resolve grid from view as necessary
- var me = this,
- grid = me.cmp = target.isXType('gridview') ? target.grid : target,
- bodyField = me.bodyField,
- hideBodyCls = me.hideBodyCls,
- feature = Ext.create('Ext.grid.feature.RowBody', {
- grid: grid,
- getAdditionalData: function(data, idx, model, rowValues) {
- var getAdditionalData = Ext.grid.feature.RowBody.prototype.getAdditionalData,
- additionalData = {
- rowBody: data[bodyField],
- rowBodyCls: grid.getView().previewExpanded ? '' : hideBodyCls
- };
- if (Ext.isFunction(getAdditionalData)) {
- // "this" is the RowBody object hjere. Do not change to "me"
- Ext.apply(additionalData, getAdditionalData.apply(this, arguments));
- }
- return additionalData;
- }
- }),
- initFeature = function(grid, view) {
- view.previewExpanded = me.previewExpanded;
- // By this point, existing features are already in place, so this must be initialized and added
- view.featuresMC.add(feature);
- feature.init(grid);
- };
- // The grid has already created its view
- if (grid.view) {
- initFeature(grid, grid.view);
- } else // At the time a grid creates its plugins, it has not created all the things
- // it needs to create its view correctly.
- // Process the view and init the RowBody Feature as soon as the view is created.
- {
- grid.on({
- viewcreated: initFeature,
- single: true
- });
- }
- },
- /**
- * Toggle between the preview being expanded/hidden on all rows
- * @param {Boolean} expanded Pass true to expand the record and false to not show the preview.
- */
- toggleExpanded: function(expanded) {
- var grid = this.getCmp(),
- view = grid && grid.getView(),
- bufferedRenderer = view.bufferedRenderer,
- scrollManager = view.scrollManager;
- if (grid && view && expanded !== view.previewExpanded) {
- this.previewExpanded = view.previewExpanded = !!expanded;
- view.refreshView();
- // If we are using the touch scroller, ensure that the scroller knows about
- // the correct scrollable range
- if (scrollManager) {
- if (bufferedRenderer) {
- bufferedRenderer.stretchView(view, bufferedRenderer.getScrollHeight(true));
- } else {
- scrollManager.refresh(true);
- }
- }
- }
- }
- });
- /**
- * Plugin for displaying a progressbar inside of a paging toolbar
- * instead of plain text.
- */
- Ext.define('Ext.ux.ProgressBarPager', {
- alias: 'plugin.ux-progressbarpager',
- requires: [
- 'Ext.ProgressBar'
- ],
- /**
- * @cfg {Number} width
- * <p>The default progress bar width. Default is 225.</p>
- */
- width: 225,
- /**
- * @cfg {String} defaultText
- * <p>The text to display while the store is loading. Default is 'Loading...'</p>
- */
- defaultText: 'Loading...',
- /**
- * @cfg {Object} defaultAnimCfg
- * <p>A {@link Ext.fx.Anim Ext.fx.Anim} configuration object.</p>
- */
- defaultAnimCfg: {
- duration: 1000,
- easing: 'bounceOut'
- },
- /**
- * Creates new ProgressBarPager.
- * @param {Object} config Configuration options
- */
- constructor: function(config) {
- if (config) {
- Ext.apply(this, config);
- }
- },
- init: function(parent) {
- var displayItem;
- if (parent.displayInfo) {
- this.parent = parent;
- displayItem = parent.child("#displayItem");
- if (displayItem) {
- parent.remove(displayItem, true);
- }
- this.progressBar = Ext.create('Ext.ProgressBar', {
- text: this.defaultText,
- width: this.width,
- animate: this.defaultAnimCfg,
- style: {
- cursor: 'pointer'
- },
- listeners: {
- el: {
- scope: this,
- click: this.handleProgressBarClick
- }
- }
- });
- parent.displayItem = this.progressBar;
- parent.add(parent.displayItem);
- Ext.apply(parent, this.parentOverrides);
- }
- },
- /**
- * This method handles the click for the progress bar
- * @private
- */
- handleProgressBarClick: function(e) {
- var parent = this.parent,
- displayItem = parent.displayItem,
- box = this.progressBar.getBox(),
- xy = e.getXY(),
- position = xy[0] - box.x,
- store = parent.store,
- pageSize = parent.pageSize || store.pageSize,
- pages = Math.ceil(store.getTotalCount() / pageSize),
- newPage = Math.max(Math.ceil(position / (displayItem.width / pages)), 1);
- store.loadPage(newPage);
- },
- /**
- * @private
- */
- parentOverrides: {
- /**
- * This method updates the information via the progress bar.
- * @private
- */
- updateInfo: function() {
- if (this.displayItem) {
- var count = this.store.getCount(),
- pageData = this.getPageData(),
- message = count === 0 ? this.emptyMsg : Ext.String.format(this.displayMsg, pageData.fromRecord, pageData.toRecord, this.store.getTotalCount()),
- percentage = pageData.pageCount > 0 ? (pageData.currentPage / pageData.pageCount) : 0;
- this.displayItem.updateProgress(percentage, message, this.animate || this.defaultAnimConfig);
- }
- }
- }
- });
- /**
- * @deprecated 4.0.0 Ext.ux.RowExpander has been promoted to the core framework. Use
- * {@link Ext.grid.plugin.RowExpander} instead.
- *
- * Ext.ux.RowExpander is now just an empty stub that extends Ext.grid.plugin.RowExpander
- * for backward compatibility reasons.
- */
- Ext.define('Ext.ux.RowExpander', {
- extend: 'Ext.grid.plugin.RowExpander'
- });
- /**
- * Plugin for PagingToolbar which replaces the textfield input with a slider
- */
- Ext.define('Ext.ux.SlidingPager', {
- alias: 'plugin.ux-slidingpager',
- requires: [
- 'Ext.slider.Single',
- 'Ext.slider.Tip'
- ],
- /**
- * Creates new SlidingPager.
- * @param {Object} config Configuration options
- */
- constructor: function(config) {
- if (config) {
- Ext.apply(this, config);
- }
- },
- init: function(pbar) {
- var idx = pbar.items.indexOf(pbar.child("#inputItem")),
- slider;
- Ext.each(pbar.items.getRange(idx - 2, idx + 2), function(c) {
- c.hide();
- });
- slider = Ext.create('Ext.slider.Single', {
- width: 114,
- minValue: 1,
- maxValue: 1,
- hideLabel: true,
- tipText: function(thumb) {
- return Ext.String.format('Page <b>{0}</b> of <b>{1}</b>', thumb.value, thumb.slider.maxValue);
- },
- listeners: {
- changecomplete: function(s, v) {
- pbar.store.loadPage(v);
- }
- }
- });
- pbar.insert(idx + 1, slider);
- pbar.on({
- change: function(pb, data) {
- slider.setMaxValue(data.pageCount);
- slider.setValue(data.currentPage);
- }
- });
- }
- });
- /**
- * UX used to provide a spotlight around a specified component/element.
- */
- Ext.define('Ext.ux.Spotlight', {
- /**
- * @private
- * The baseCls for the spotlight elements
- */
- baseCls: 'x-spotlight',
- /**
- * @cfg animate {Boolean} True to animate the spotlight change
- * (defaults to true)
- */
- animate: true,
- /**
- * @cfg duration {Integer} The duration of the animation, in milliseconds
- * (defaults to 250)
- */
- duration: 250,
- /**
- * @cfg easing {String} The type of easing for the spotlight animatation
- * (defaults to null)
- */
- easing: null,
- /**
- * @private
- * True if the spotlight is active on the element
- */
- active: false,
- constructor: function(config) {
- Ext.apply(this, config);
- },
- /**
- * Create all the elements for the spotlight
- */
- createElements: function() {
- var me = this,
- baseCls = me.baseCls,
- body = Ext.getBody();
- me.right = body.createChild({
- cls: baseCls
- });
- me.left = body.createChild({
- cls: baseCls
- });
- me.top = body.createChild({
- cls: baseCls
- });
- me.bottom = body.createChild({
- cls: baseCls
- });
- me.all = Ext.create('Ext.CompositeElement', [
- me.right,
- me.left,
- me.top,
- me.bottom
- ]);
- },
- /**
- * Show the spotlight
- */
- show: function(el, callback, scope) {
- var me = this;
- //get the target element
- me.el = Ext.get(el);
- //create the elements if they don't already exist
- if (!me.right) {
- me.createElements();
- }
- if (!me.active) {
- //if the spotlight is not active, show it
- me.all.setDisplayed('');
- me.active = true;
- Ext.on('resize', me.syncSize, me);
- me.applyBounds(me.animate, false);
- } else {
- //if the spotlight is currently active, just move it
- me.applyBounds(false, false);
- }
- },
- /**
- * Hide the spotlight
- */
- hide: function(callback, scope) {
- var me = this;
- Ext.un('resize', me.syncSize, me);
- me.applyBounds(me.animate, true);
- },
- /**
- * Resizes the spotlight with the window size.
- */
- syncSize: function() {
- this.applyBounds(false, false);
- },
- /**
- * Resizes the spotlight depending on the arguments
- * @param {Boolean} animate True to animate the changing of the bounds
- * @param {Boolean} reverse True to reverse the animation
- */
- applyBounds: function(animate, reverse) {
- var me = this,
- box = me.el.getBox(),
- //get the current view width and height
- viewWidth = Ext.Element.getViewportWidth(),
- viewHeight = Ext.Element.getViewportHeight(),
- i = 0,
- config = false,
- from, to, clone;
- //where the element should start (if animation)
- from = {
- right: {
- x: box.right,
- y: viewHeight,
- width: (viewWidth - box.right),
- height: 0
- },
- left: {
- x: 0,
- y: 0,
- width: box.x,
- height: 0
- },
- top: {
- x: viewWidth,
- y: 0,
- width: 0,
- height: box.y
- },
- bottom: {
- x: 0,
- y: (box.y + box.height),
- width: 0,
- height: (viewHeight - (box.y + box.height)) + 'px'
- }
- };
- //where the element needs to finish
- to = {
- right: {
- x: box.right,
- y: box.y,
- width: (viewWidth - box.right) + 'px',
- height: (viewHeight - box.y) + 'px'
- },
- left: {
- x: 0,
- y: 0,
- width: box.x + 'px',
- height: (box.y + box.height) + 'px'
- },
- top: {
- x: box.x,
- y: 0,
- width: (viewWidth - box.x) + 'px',
- height: box.y + 'px'
- },
- bottom: {
- x: 0,
- y: (box.y + box.height),
- width: (box.x + box.width) + 'px',
- height: (viewHeight - (box.y + box.height)) + 'px'
- }
- };
- //reverse the objects
- if (reverse) {
- clone = Ext.clone(from);
- from = to;
- to = clone;
- }
- if (animate) {
- Ext.Array.forEach([
- 'right',
- 'left',
- 'top',
- 'bottom'
- ], function(side) {
- me[side].setBox(from[side]);
- me[side].animate({
- duration: me.duration,
- easing: me.easing,
- to: to[side]
- });
- }, this);
- } else {
- Ext.Array.forEach([
- 'right',
- 'left',
- 'top',
- 'bottom'
- ], function(side) {
- me[side].setBox(Ext.apply(from[side], to[side]));
- me[side].repaint();
- }, this);
- }
- },
- /**
- * Removes all the elements for the spotlight
- */
- destroy: function() {
- var me = this;
- Ext.destroy(me.right, me.left, me.top, me.bottom);
- delete me.el;
- delete me.all;
- me.callParent();
- }
- });
- /**
- * Plugin for adding a close context menu to tabs. Note that the menu respects
- * the closable configuration on the tab. As such, commands like remove others
- * and remove all will not remove items that are not closable.
- */
- Ext.define('Ext.ux.TabCloseMenu', {
- extend: 'Ext.plugin.Abstract',
- alias: 'plugin.tabclosemenu',
- mixins: {
- observable: 'Ext.util.Observable'
- },
- /**
- * @cfg {String} closeTabText
- * The text for closing the current tab.
- */
- closeTabText: 'Close Tab',
- /**
- * @cfg {Boolean} showCloseOthers
- * Indicates whether to show the 'Close Others' option.
- */
- showCloseOthers: true,
- /**
- * @cfg {String} closeOthersTabsText
- * The text for closing all tabs except the current one.
- */
- closeOthersTabsText: 'Close Other Tabs',
- /**
- * @cfg {Boolean} showCloseAll
- * Indicates whether to show the 'Close All' option.
- */
- showCloseAll: true,
- /**
- * @cfg {String} closeAllTabsText
- * The text for closing all tabs.
- */
- closeAllTabsText: 'Close All Tabs',
- /**
- * @cfg {Array} extraItemsHead
- * An array of additional context menu items to add to the front of the context menu.
- */
- extraItemsHead: null,
- /**
- * @cfg {Array} extraItemsTail
- * An array of additional context menu items to add to the end of the context menu.
- */
- extraItemsTail: null,
- //public
- constructor: function(config) {
- this.callParent([
- config
- ]);
- this.mixins.observable.constructor.call(this, config);
- },
- init: function(tabpanel) {
- this.tabPanel = tabpanel;
- this.tabBar = tabpanel.down("tabbar");
- this.mon(this.tabPanel, {
- scope: this,
- afterlayout: this.onAfterLayout,
- single: true
- });
- },
- onAfterLayout: function() {
- this.mon(this.tabBar.el, {
- scope: this,
- contextmenu: this.onContextMenu,
- delegate: '.x-tab'
- });
- },
- destroy: function() {
- Ext.destroy(this.menu);
- this.callParent();
- },
- /**
- * @private
- */
- onContextMenu: function(event, target) {
- var me = this,
- menu = me.createMenu(),
- disableAll = true,
- disableOthers = true,
- tab = me.tabBar.getChildByElement(target),
- index = me.tabBar.items.indexOf(tab);
- me.item = me.tabPanel.getComponent(index);
- menu.child('#close').setDisabled(!me.item.closable);
- if (me.showCloseAll || me.showCloseOthers) {
- me.tabPanel.items.each(function(item) {
- if (item.closable) {
- disableAll = false;
- if (item !== me.item) {
- disableOthers = false;
- return false;
- }
- }
- return true;
- });
- if (me.showCloseAll) {
- menu.child('#closeAll').setDisabled(disableAll);
- }
- if (me.showCloseOthers) {
- menu.child('#closeOthers').setDisabled(disableOthers);
- }
- }
- event.preventDefault();
- me.fireEvent('beforemenu', menu, me.item, me);
- menu.showAt(event.getXY());
- },
- createMenu: function() {
- var me = this;
- if (!me.menu) {
- var items = [
- {
- itemId: 'close',
- text: me.closeTabText,
- scope: me,
- handler: me.onClose
- }
- ];
- if (me.showCloseAll || me.showCloseOthers) {
- items.push('-');
- }
- if (me.showCloseOthers) {
- items.push({
- itemId: 'closeOthers',
- text: me.closeOthersTabsText,
- scope: me,
- handler: me.onCloseOthers
- });
- }
- if (me.showCloseAll) {
- items.push({
- itemId: 'closeAll',
- text: me.closeAllTabsText,
- scope: me,
- handler: me.onCloseAll
- });
- }
- if (me.extraItemsHead) {
- items = me.extraItemsHead.concat(items);
- }
- if (me.extraItemsTail) {
- items = items.concat(me.extraItemsTail);
- }
- me.menu = Ext.create('Ext.menu.Menu', {
- items: items,
- listeners: {
- hide: me.onHideMenu,
- scope: me
- }
- });
- }
- return me.menu;
- },
- onHideMenu: function() {
- var me = this;
- me.fireEvent('aftermenu', me.menu, me);
- },
- onClose: function() {
- this.tabPanel.remove(this.item);
- },
- onCloseOthers: function() {
- this.doClose(true);
- },
- onCloseAll: function() {
- this.doClose(false);
- },
- doClose: function(excludeActive) {
- var items = [];
- this.tabPanel.items.each(function(item) {
- if (item.closable) {
- if (!excludeActive || item !== this.item) {
- items.push(item);
- }
- }
- }, this);
- Ext.suspendLayouts();
- Ext.Array.forEach(items, function(item) {
- this.tabPanel.remove(item);
- }, this);
- Ext.resumeLayouts(true);
- }
- });
- /**
- * This plugin allow you to reorder tabs of a TabPanel.
- */
- Ext.define('Ext.ux.TabReorderer', {
- extend: 'Ext.ux.BoxReorderer',
- alias: 'plugin.tabreorderer',
- itemSelector: '.' + Ext.baseCSSPrefix + 'tab',
- init: function(tabPanel) {
- var me = this;
- me.callParent([
- tabPanel.getTabBar()
- ]);
- // Ensure reorderable property is copied into dynamically added tabs
- tabPanel.onAdd = Ext.Function.createSequence(tabPanel.onAdd, me.onAdd);
- },
- onBoxReady: function() {
- var tabs, len,
- i = 0,
- tab;
- this.callParent(arguments);
- // Copy reorderable property from card into tab
- for (tabs = this.container.items.items , len = tabs.length; i < len; i++) {
- tab = tabs[i];
- if (tab.card) {
- tab.reorderable = tab.card.reorderable;
- }
- }
- },
- onAdd: function(card, index) {
- card.tab.reorderable = card.reorderable;
- },
- afterBoxReflow: function() {
- var me = this;
- // Cannot use callParent, this is not called in the scope of this plugin, but that of its Ext.dd.DD object
- Ext.ux.BoxReorderer.prototype.afterBoxReflow.apply(me, arguments);
- // Move the associated card to match the tab order
- if (me.dragCmp) {
- me.container.tabPanel.setActiveTab(me.dragCmp.card);
- me.container.tabPanel.move(me.dragCmp.card, me.curIndex);
- }
- }
- });
- Ext.ns('Ext.ux');
- /**
- * Plugin for adding a tab menu to a TabBar is the Tabs overflow.
- */
- Ext.define('Ext.ux.TabScrollerMenu', {
- alias: 'plugin.tabscrollermenu',
- requires: [
- 'Ext.menu.Menu'
- ],
- /**
- * @cfg {Number} pageSize How many items to allow per submenu.
- */
- pageSize: 10,
- /**
- * @cfg {Number} maxText How long should the title of each {@link Ext.menu.Item} be.
- */
- maxText: 15,
- /**
- * @cfg {String} menuPrefixText Text to prefix the submenus.
- */
- menuPrefixText: 'Items',
- /**
- * Creates new TabScrollerMenu.
- * @param {Object} config Configuration options
- */
- constructor: function(config) {
- Ext.apply(this, config);
- },
- /**
- * @private
- */
- init: function(tabPanel) {
- var me = this;
- me.tabPanel = tabPanel;
- tabPanel.on({
- render: function() {
- me.tabBar = tabPanel.tabBar;
- me.layout = me.tabBar.layout;
- me.layout.overflowHandler.handleOverflow = me.showButton.bind(me);
- me.layout.overflowHandler.clearOverflow = Ext.Function.createSequence(me.layout.overflowHandler.clearOverflow, me.hideButton, me);
- },
- destroy: me.destroy,
- scope: me,
- single: true
- });
- },
- showButton: function() {
- var me = this,
- result = Ext.getClass(me.layout.overflowHandler).prototype.handleOverflow.apply(me.layout.overflowHandler, arguments),
- button = me.menuButton;
- if (me.tabPanel.items.getCount() > 1) {
- if (!button) {
- button = me.menuButton = me.tabBar.body.createChild({
- cls: Ext.baseCSSPrefix + 'tab-tabmenu-right'
- }, me.tabBar.body.child('.' + Ext.baseCSSPrefix + 'box-scroller-right'));
- button.addClsOnOver(Ext.baseCSSPrefix + 'tab-tabmenu-over');
- button.on('click', me.showTabsMenu, me);
- }
- button.setVisibilityMode(Ext.dom.Element.DISPLAY);
- button.show();
- result.reservedSpace += button.getWidth();
- } else {
- me.hideButton();
- }
- return result;
- },
- hideButton: function() {
- var me = this;
- if (me.menuButton) {
- me.menuButton.hide();
- }
- },
- /**
- * Returns an the current page size (this.pageSize);
- * @return {Number} this.pageSize The current page size.
- */
- getPageSize: function() {
- return this.pageSize;
- },
- /**
- * Sets the number of menu items per submenu "page size".
- * @param {Number} pageSize The page size
- */
- setPageSize: function(pageSize) {
- this.pageSize = pageSize;
- },
- /**
- * Returns the current maxText length;
- * @return {Number} this.maxText The current max text length.
- */
- getMaxText: function() {
- return this.maxText;
- },
- /**
- * Sets the maximum text size for each menu item.
- * @param {Number} t The max text per each menu item.
- */
- setMaxText: function(t) {
- this.maxText = t;
- },
- /**
- * Returns the current menu prefix text String.;
- * @return {String} this.menuPrefixText The current menu prefix text.
- */
- getMenuPrefixText: function() {
- return this.menuPrefixText;
- },
- /**
- * Sets the menu prefix text String.
- * @param {String} t The menu prefix text.
- */
- setMenuPrefixText: function(t) {
- this.menuPrefixText = t;
- },
- showTabsMenu: function(e) {
- var me = this;
- if (me.tabsMenu) {
- me.tabsMenu.removeAll();
- } else {
- me.tabsMenu = new Ext.menu.Menu();
- }
- me.generateTabMenuItems();
- var target = Ext.get(e.getTarget()),
- xy = target.getXY();
- //Y param + 24 pixels
- xy[1] += 24;
- me.tabsMenu.showAt(xy);
- },
- /**
- * @private
- */
- generateTabMenuItems: function() {
- var me = this,
- tabPanel = me.tabPanel,
- curActive = tabPanel.getActiveTab(),
- allItems = tabPanel.items.getRange(),
- pageSize = me.getPageSize(),
- tabsMenu = me.tabsMenu,
- totalItems, numSubMenus, remainder, i, curPage, menuItems, x, item, start, index;
- tabsMenu.suspendLayouts();
- allItems = Ext.Array.filter(allItems, function(item) {
- if (item.id == curActive.id) {
- return false;
- }
- return item.hidden ? !!item.hiddenByLayout : true;
- });
- totalItems = allItems.length;
- numSubMenus = Math.floor(totalItems / pageSize);
- remainder = totalItems % pageSize;
- if (totalItems > pageSize) {
- // Loop through all of the items and create submenus in chunks of 10
- for (i = 0; i < numSubMenus; i++) {
- curPage = (i + 1) * pageSize;
- menuItems = [];
- for (x = 0; x < pageSize; x++) {
- index = x + curPage - pageSize;
- item = allItems[index];
- menuItems.push(me.autoGenMenuItem(item));
- }
- tabsMenu.add({
- text: me.getMenuPrefixText() + ' ' + (curPage - pageSize + 1) + ' - ' + curPage,
- menu: menuItems
- });
- }
- // remaining items
- if (remainder > 0) {
- start = numSubMenus * pageSize;
- menuItems = [];
- for (i = start; i < totalItems; i++) {
- item = allItems[i];
- menuItems.push(me.autoGenMenuItem(item));
- }
- me.tabsMenu.add({
- text: me.menuPrefixText + ' ' + (start + 1) + ' - ' + (start + menuItems.length),
- menu: menuItems
- });
- }
- } else {
- for (i = 0; i < totalItems; ++i) {
- tabsMenu.add(me.autoGenMenuItem(allItems[i]));
- }
- }
- tabsMenu.resumeLayouts(true);
- },
- /**
- * @private
- */
- autoGenMenuItem: function(item) {
- var maxText = this.getMaxText(),
- text = Ext.util.Format.ellipsis(item.title, maxText);
- return {
- text: text,
- handler: this.showTabFromMenu,
- scope: this,
- disabled: item.disabled,
- tabToShow: item,
- iconCls: item.iconCls
- };
- },
- /**
- * @private
- */
- showTabFromMenu: function(menuItem) {
- this.tabPanel.setActiveTab(menuItem.tabToShow);
- },
- destroy: function() {
- Ext.destroy(this.tabsMenu, this.menuButton);
- this.callParent();
- }
- });
- /**
- * Plugin which allows items to be dropped onto a toolbar and be turned into new Toolbar items.
- * To use the plugin, you just need to provide a createItem implementation that takes the drop
- * data as an argument and returns an object that can be placed onto the toolbar. Example:
- * <pre>
- * Ext.create('Ext.ux.ToolbarDroppable', {
- * createItem: function(data) {
- * return Ext.create('Ext.Button', {text: data.text});
- * }
- * });
- * </pre>
- * The afterLayout function can also be overridden, and is called after a new item has been
- * created and inserted into the Toolbar. Use this for any logic that needs to be run after
- * the item has been created.
- */
- Ext.define('Ext.ux.ToolbarDroppable', {
- /**
- * Creates new ToolbarDroppable.
- * @param {Object} config Config options.
- */
- constructor: function(config) {
- Ext.apply(this, config);
- },
- /**
- * Initializes the plugin and saves a reference to the toolbar
- * @param {Ext.toolbar.Toolbar} toolbar The toolbar instance
- */
- init: function(toolbar) {
- /**
- * @property toolbar
- * @type Ext.toolbar.Toolbar
- * The toolbar instance that this plugin is tied to
- */
- this.toolbar = toolbar;
- this.toolbar.on({
- scope: this,
- render: this.createDropTarget
- });
- },
- /**
- * Creates a drop target on the toolbar
- */
- createDropTarget: function() {
- /**
- * @property dropTarget
- * @type Ext.dd.DropTarget
- * The drop target attached to the toolbar instance
- */
- this.dropTarget = Ext.create('Ext.dd.DropTarget', this.toolbar.getEl(), {
- notifyOver: this.notifyOver.bind(this),
- notifyDrop: this.notifyDrop.bind(this)
- });
- },
- /**
- * Adds the given DD Group to the drop target
- * @param {String} ddGroup The DD Group
- */
- addDDGroup: function(ddGroup) {
- this.dropTarget.addToGroup(ddGroup);
- },
- /**
- * Calculates the location on the toolbar to create the new sorter button based on the XY of the
- * drag event
- * @param {Ext.event.Event} e The event object
- * @return {Number} The index at which to insert the new button
- */
- calculateEntryIndex: function(e) {
- var entryIndex = 0,
- toolbar = this.toolbar,
- items = toolbar.items.items,
- count = items.length,
- xHover = e.getXY()[0],
- index = 0,
- el, xTotal, width, midpoint;
- for (; index < count; index++) {
- el = items[index].getEl();
- xTotal = el.getXY()[0];
- width = el.getWidth();
- midpoint = xTotal + width / 2;
- if (xHover < midpoint) {
- entryIndex = index;
- break;
- } else {
- entryIndex = index + 1;
- }
- }
- return entryIndex;
- },
- /**
- * Returns true if the drop is allowed on the drop target. This function can be overridden
- * and defaults to simply return true
- * @param {Object} data Arbitrary data from the drag source
- * @return {Boolean} True if the drop is allowed
- */
- canDrop: function(data) {
- return true;
- },
- /**
- * Custom notifyOver method which will be used in the plugin's internal DropTarget
- * @return {String} The CSS class to add
- */
- notifyOver: function(dragSource, event, data) {
- return this.canDrop.apply(this, arguments) ? this.dropTarget.dropAllowed : this.dropTarget.dropNotAllowed;
- },
- /**
- * Called when the drop has been made. Creates the new toolbar item, places it at the correct location
- * and calls the afterLayout callback.
- */
- notifyDrop: function(dragSource, event, data) {
- var canAdd = this.canDrop(dragSource, event, data),
- tbar = this.toolbar;
- if (canAdd) {
- var entryIndex = this.calculateEntryIndex(event);
- tbar.insert(entryIndex, this.createItem(data));
- this.afterLayout();
- }
- return canAdd;
- },
- /**
- * Creates the new toolbar item based on drop data. This method must be implemented by the plugin instance
- * @param {Object} data Arbitrary data from the drop
- * @return {Mixed} An item that can be added to a toolbar
- */
- createItem: function(data) {
- //<debug>
- Ext.raise("The createItem method must be implemented in the ToolbarDroppable plugin");
- },
- //</debug>
- /**
- * @method
- * Called after a new button has been created and added to the toolbar. Add any required cleanup logic here
- */
- afterLayout: Ext.emptyFn
- });
- /**
- * A Picker field that contains a tree panel on its popup, enabling selection of tree nodes.
- */
- Ext.define('Ext.ux.TreePicker', {
- extend: 'Ext.form.field.Picker',
- xtype: 'treepicker',
- uses: [
- 'Ext.tree.Panel'
- ],
- triggerCls: Ext.baseCSSPrefix + 'form-arrow-trigger',
- config: {
- /**
- * @cfg {Ext.data.TreeStore} store
- * A tree store that the tree picker will be bound to
- */
- store: null,
- /**
- * @cfg {String} displayField
- * The field inside the model that will be used as the node's text.
- * Defaults to the default value of {@link Ext.tree.Panel}'s `displayField` configuration.
- */
- displayField: null,
- /**
- * @cfg {Array} columns
- * An optional array of columns for multi-column trees
- */
- columns: null,
- /**
- * @cfg {Boolean} selectOnTab
- * Whether the Tab key should select the currently highlighted item. Defaults to `true`.
- */
- selectOnTab: true,
- /**
- * @cfg {Number} maxPickerHeight
- * The maximum height of the tree dropdown. Defaults to 300.
- */
- maxPickerHeight: 300,
- /**
- * @cfg {Number} minPickerHeight
- * The minimum height of the tree dropdown. Defaults to 100.
- */
- minPickerHeight: 100
- },
- editable: false,
- /**
- * @event select
- * Fires when a tree node is selected
- * @param {Ext.ux.TreePicker} picker This tree picker
- * @param {Ext.data.Model} record The selected record
- */
- initComponent: function() {
- var me = this;
- me.callParent(arguments);
- me.mon(me.store, {
- scope: me,
- load: me.onLoad,
- update: me.onUpdate
- });
- },
- /**
- * Creates and returns the tree panel to be used as this field's picker.
- */
- createPicker: function() {
- var me = this,
- picker = new Ext.tree.Panel({
- baseCls: Ext.baseCSSPrefix + 'boundlist',
- shrinkWrapDock: 2,
- store: me.store,
- floating: true,
- displayField: me.displayField,
- columns: me.columns,
- minHeight: me.minPickerHeight,
- maxHeight: me.maxPickerHeight,
- manageHeight: false,
- shadow: false,
- listeners: {
- scope: me,
- itemclick: me.onItemClick,
- itemkeydown: me.onPickerKeyDown
- }
- }),
- view = picker.getView();
- if (Ext.isIE9 && Ext.isStrict) {
- // In IE9 strict mode, the tree view grows by the height of the horizontal scroll bar when the items are highlighted or unhighlighted.
- // Also when items are collapsed or expanded the height of the view is off. Forcing a repaint fixes the problem.
- view.on({
- scope: me,
- highlightitem: me.repaintPickerView,
- unhighlightitem: me.repaintPickerView,
- afteritemexpand: me.repaintPickerView,
- afteritemcollapse: me.repaintPickerView
- });
- }
- return picker;
- },
- /**
- * repaints the tree view
- */
- repaintPickerView: function() {
- var style = this.picker.getView().getEl().dom.style;
- // can't use Element.repaint because it contains a setTimeout, which results in a flicker effect
- style.display = style.display;
- },
- /**
- * Handles a click even on a tree node
- * @private
- * @param {Ext.tree.View} view
- * @param {Ext.data.Model} record
- * @param {HTMLElement} node
- * @param {Number} rowIndex
- * @param {Ext.event.Event} e
- */
- onItemClick: function(view, record, node, rowIndex, e) {
- this.selectItem(record);
- },
- /**
- * Handles a keypress event on the picker element
- * @private
- * @param {Ext.tree.View} treeView
- * @param {Ext.data.Model} record
- * @param {HTMLElement} item
- * @param {Number} index
- * @param {Ext.event.Event} e
- */
- onPickerKeyDown: function(treeView, record, item, index, e) {
- var key = e.getKey();
- if (key === e.ENTER || (key === e.TAB && this.selectOnTab)) {
- this.selectItem(record);
- }
- },
- /**
- * Changes the selection to a given record and closes the picker
- * @private
- * @param {Ext.data.Model} record
- */
- selectItem: function(record) {
- var me = this;
- me.setValue(record.getId());
- me.fireEvent('select', me, record);
- me.collapse();
- },
- /**
- * Runs when the picker is expanded. Selects the appropriate tree node based on the value of the input element,
- * and focuses the picker so that keyboard navigation will work.
- * @private
- */
- onExpand: function() {
- var picker = this.picker,
- store = picker.store,
- value = this.value,
- node;
- if (value) {
- node = store.getNodeById(value);
- }
- if (!node) {
- node = store.getRoot();
- }
- picker.ensureVisible(node, {
- select: true,
- focus: true
- });
- },
- /**
- * Sets the specified value into the field
- * @param {Mixed} value
- * @return {Ext.ux.TreePicker} this
- */
- setValue: function(value) {
- var me = this,
- record;
- me.value = value;
- if (me.store.loading) {
- // Called while the Store is loading. Ensure it is processed by the onLoad method.
- return me;
- }
- // try to find a record in the store that matches the value
- record = value ? me.store.getNodeById(value) : me.store.getRoot();
- if (value === undefined) {
- record = me.store.getRoot();
- me.value = record.getId();
- } else {
- record = me.store.getNodeById(value);
- }
- // set the raw value to the record's display field if a record was found
- me.setRawValue(record ? record.get(me.displayField) : '');
- return me;
- },
- getSubmitValue: function() {
- return this.value;
- },
- /**
- * Returns the current data value of the field (the idProperty of the record)
- * @return {Number}
- */
- getValue: function() {
- return this.value;
- },
- /**
- * Handles the store's load event.
- * @private
- */
- onLoad: function() {
- var value = this.value;
- if (value) {
- this.setValue(value);
- }
- },
- onUpdate: function(store, rec, type, modifiedFieldNames) {
- var display = this.displayField;
- if (type === 'edit' && modifiedFieldNames && Ext.Array.contains(modifiedFieldNames, display) && this.value === rec.getId()) {
- this.setRawValue(rec.get(display));
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.ux.colorpick.Selection', {
- mixinId: 'colorselection',
- config: {
- /**
- * @cfg {"hex6"/"hex8"/"#hex6"/"#hex8"/"rgb"/"rgba"/"HEX6"/"HEX8"/"#HEX6"/"#HEX8"/"RGB"/"RGBA"} [format=hex6]
- * The color format to for the `value` config. The `value` can be set using any
- * supported format or named color, but the stored value will always be in this
- * format.
- *
- * Supported formats are:
- *
- * - hex6 - For example "ffaa00" (Note: does not preserve transparency).
- * - hex8 - For example "ffaa00ff" - the last 2 digits represent transparency
- * - #hex6 - For example "#ffaa00" (same as "hex6" but with a leading "#").
- * - #hex8 - For example "#ffaa00ff" (same as "hex8" but with a leading "#").
- * - rgb - For example "rgb(255,255,0)" (Note: does not preserve transparency).
- * - rgba - For example "rgba(255,255,0,.25)"
- * - HEX6 - Same as "hex6" but upper case.
- * - HEX8 - Same as "hex8" but upper case.
- * - #HEX6 - Same as "#hex6" but upper case.
- * - #HEX8 - Same as "#hex8" but upper case.
- * - RGB - Same as "rgb" but upper case.
- * - RGBA - Same as "rgba" but upper case.
- */
- format: 'hex6',
- /**
- * @cfg {String} [value=FF0000]
- * The initial color to highlight; see {@link #format} for supported formats.
- */
- value: 'FF0000',
- /**
- * @cfg {Object} color
- * This config property is used internally by the UI to maintain the full color.
- * Changes to this config are automatically reflected in `value` and vise-versa.
- * Setting `value` can, however, cause the alpha to be dropped if the new value
- * does not contain an alpha component.
- * @private
- */
- color: null,
- previousColor: null,
- /**
- * @cfg {String} [alphaDecimalFormat=#.##]
- * The format used by {@link Ext.util.Format#number} to format the alpha channel's
- * value.
- * @since 7.0.0
- */
- alphaDecimalFormat: '#.##'
- },
- applyColor: function(color) {
- var c = color;
- if (Ext.isString(c)) {
- c = Ext.ux.colorpick.ColorUtils.parseColor(color, this.getAlphaDecimalFormat());
- }
- return c;
- },
- applyFormat: function(format) {
- var formats = Ext.ux.colorpick.ColorUtils.formats;
- if (!formats.hasOwnProperty(format)) {
- //<debug>
- Ext.raise('The specified format "' + format + '" is invalid.');
- //</debug>
- return;
- }
- return format;
- },
- applyValue: function(color) {
- // Transform whatever incoming color we get to the proper format
- var c = Ext.ux.colorpick.ColorUtils.parseColor(color || '#000000', this.getAlphaDecimalFormat());
- return this.formatColor(c);
- },
- formatColor: function(color) {
- return Ext.ux.colorpick.ColorUtils.formats[this.getFormat()](color);
- },
- updateColor: function(color) {
- var me = this;
- // If the "color" is changed (via internal changes in the UI), update "value" as
- // well. Since these are always tracking each other, we guard against the case
- // where we are being updated *because* "value" is being set.
- if (!me.syncing) {
- me.syncing = true;
- me.setValue(me.formatColor(color));
- me.syncing = false;
- }
- },
- updateValue: function(value, oldValue) {
- var me = this;
- // If the "value" is changed, update "color" as well. Since these are always
- // tracking each other, we guard against the case where we are being updated
- // *because* "color" is being set.
- if (!me.syncing) {
- me.syncing = true;
- me.setColor(value);
- me.syncing = false;
- }
- this.fireEvent('change', me, value, oldValue);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.ux.colorpick.ColorUtils', function(ColorUtils) {
- var oldIE = Ext.isIE && Ext.ieVersion < 10;
- return {
- singleton: true,
- constructor: function() {
- ColorUtils = this;
- },
- backgroundTpl: oldIE ? 'filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, ' + 'startColorstr=\'#{alpha}{hex}\', endColorstr=\'#{alpha}{hex}\');' : 'background: {rgba};',
- setBackground: oldIE ? function(el, color) {
- if (el) {
- var tpl = Ext.XTemplate.getTpl(ColorUtils, 'backgroundTpl'),
- data = {
- hex: ColorUtils.rgb2hex(color.r, color.g, color.b),
- alpha: Math.floor(color.a * 255).toString(16)
- },
- bgStyle = tpl.apply(data);
- el.applyStyles(bgStyle);
- }
- } : function(el, color) {
- if (el) {
- var tpl = Ext.XTemplate.getTpl(ColorUtils, 'backgroundTpl'),
- data = {
- rgba: ColorUtils.getRGBAString(color)
- },
- bgStyle = tpl.apply(data);
- el.applyStyles(bgStyle);
- }
- },
- // parse and format functions under objects that match supported format config
- // values of the color picker; parse() methods receive the supplied color value
- // as a string (i.e "FFAAAA") and return an object form, just like the one
- // ColorPickerModel vm "selectedColor" uses. That same object form is used as a
- // parameter to the format() methods, where the appropriate string form is expected
- // for the return result
- formats: {
- // "RGB(100,100,100)"
- RGB: function(colorO) {
- return ColorUtils.getRGBString(colorO).toUpperCase();
- },
- // "RGBA(100,100,100,0.5)"
- RGBA: function(colorO) {
- return ColorUtils.getRGBAString(colorO).toUpperCase();
- },
- // "FFAA00"
- HEX6: function(colorO) {
- return ColorUtils.rgb2hex(colorO.r, colorO.g, colorO.b);
- },
- // "FFAA00FF" (last 2 are opacity)
- HEX8: function(colorO) {
- var hex = ColorUtils.rgb2hex(colorO.r, colorO.g, colorO.b),
- opacityHex = Math.round(colorO.a * 255).toString(16);
- if (opacityHex.length < 2) {
- hex += '0';
- }
- hex += opacityHex.toUpperCase();
- return hex;
- }
- },
- hexRe: /^#?(([0-9a-f]{8})|((?:[0-9a-f]{3}){1,2}))$/i,
- rgbaAltRe: /^rgba\(\s*([\w#\d]+)\s*,\s*([\d\.]+)\s*\)$/i,
- rgbaRe: /^rgba\(\s*([\d\.]+)\s*,\s*([\d\.]+)\s*,\s*([\d\.]+)\s*,\s*([\d\.]+)\s*\)$/i,
- rgbRe: /^rgb\(\s*([\d\.]+)\s*,\s*([\d\.]+)\s*,\s*([\d\.]+)\s*\)$/i,
- /**
- * Turn a string to a color object. Supports these formats:
- *
- * - "#ABC" (HEX short)
- * - "#ABCDEF" (HEX)
- * - "#ABCDEFDD" (HEX with opacity)
- * - "red" (named colors - see
- * [Web Colors](http://en.wikipedia.org/wiki/Web_colors) for a full list)
- * - "rgba(r,g,b,a)" i.e "rgba(255,0,0,1)" (a == alpha == 0-1)
- * - "rgba(red, 0.4)"
- * - "rgba(#ABC, 0.9)"
- * - "rgba(#ABCDEF, 0.8)"
- *
- * @param {String} color The color string to parse.
- * @param {String} alphaFormat The format of decimal places for the Alpha channel.
- * @return {Object} Object with various color properties.
- * @return {Number} return.r The red component (0-255).
- * @return {Number} return.g The green component (0-255).
- * @return {Number} return.b The blue component (0-255).
- * @return {Number} return.a The red component (0-1).
- * @return {Number} return.h The hue component (0-1).
- * @return {Number} return.s The saturation component (0-1).
- * @return {Number} return.v The value component (0-1).
- */
- parseColor: function(color, alphaFormat) {
- if (!color) {
- return null;
- }
- var me = this,
- rgb = me.colorMap[color],
- match, ret, hsv;
- if (rgb) {
- ret = {
- r: rgb[0],
- g: rgb[1],
- b: rgb[2],
- a: 1
- };
- } else if (color === 'transparent') {
- ret = {
- r: 0,
- g: 0,
- b: 0,
- a: 0
- };
- } else {
- match = me.hexRe.exec(color);
- if (match) {
- match = match[1];
- // the captured hex
- switch (match.length) {
- default:
- return null;
- case 3:
- ret = {
- //double the number (e.g. 6 - > 66, a -> aa) and convert to decimal
- r: parseInt(match[0] + match[0], 16),
- g: parseInt(match[1] + match[1], 16),
- b: parseInt(match[2] + match[2], 16),
- a: 1
- };
- break;
- case 6:
- case 8:
- ret = {
- r: parseInt(match.substr(0, 2), 16),
- g: parseInt(match.substr(2, 2), 16),
- b: parseInt(match.substr(4, 2), 16),
- a: parseInt(match.substr(6, 2) || 'ff', 16) / 255
- };
- break;
- }
- } else {
- match = me.rgbaRe.exec(color);
- if (match) {
- // proper css => rgba(r,g,b,a)
- ret = {
- r: parseFloat(match[1]),
- g: parseFloat(match[2]),
- b: parseFloat(match[3]),
- a: parseFloat(match[4])
- };
- } else {
- match = me.rgbaAltRe.exec(color);
- if (match) {
- // scss shorthands =? rgba(red, 0.4), rgba(#222, 0.9), rgba(#444433, 0.8)
- ret = me.parseColor(match[1]);
- // we have HSV filled in, so poke on "a" and we're done
- ret.a = parseFloat(match[2]);
- return ret;
- }
- match = me.rgbRe.exec(color);
- if (match) {
- ret = {
- r: parseFloat(match[1]),
- g: parseFloat(match[2]),
- b: parseFloat(match[3]),
- a: 1
- };
- } else {
- return null;
- }
- }
- }
- }
- // format alpha channel
- if (alphaFormat) {
- ret.a = Ext.util.Format.number(ret.a, alphaFormat);
- }
- hsv = this.rgb2hsv(ret.r, ret.g, ret.b);
- return Ext.apply(ret, hsv);
- },
- isValid: function(color) {
- return ColorUtils.parseColor(color) !== null;
- },
- /**
- *
- * @param rgba
- * @return {String}
- */
- getRGBAString: function(rgba) {
- return "rgba(" + rgba.r + "," + rgba.g + "," + rgba.b + "," + rgba.a + ")";
- },
- /**
- * Returns a rgb css string whith this color (without the alpha channel)
- * @param rgb
- * @return {String}
- */
- getRGBString: function(rgb) {
- return "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")";
- },
- /**
- * Following standard math to convert from hsl to rgb
- * Check out wikipedia page for more information on how this works
- * h => [0,1]
- * s,l => [0,1]
- * @param h
- * @param s
- * @param v
- * @return {Object} An object with "r", "g" and "b" color properties.
- */
- hsv2rgb: function(h, s, v) {
- h = h * 360;
- if (h === 360) {
- h = 0;
- }
- var c = v * s;
- var hprime = h / 60;
- var x = c * (1 - Math.abs(hprime % 2 - 1));
- var rgb = [
- 0,
- 0,
- 0
- ];
- switch (Math.floor(hprime)) {
- case 0:
- rgb = [
- c,
- x,
- 0
- ];
- break;
- case 1:
- rgb = [
- x,
- c,
- 0
- ];
- break;
- case 2:
- rgb = [
- 0,
- c,
- x
- ];
- break;
- case 3:
- rgb = [
- 0,
- x,
- c
- ];
- break;
- case 4:
- rgb = [
- x,
- 0,
- c
- ];
- break;
- case 5:
- rgb = [
- c,
- 0,
- x
- ];
- break;
- default:
- // <debug>
- console.error("unknown color " + h + ' ' + s + " " + v);
- // </debug>
- break;
- }
- var m = v - c;
- rgb[0] += m;
- rgb[1] += m;
- rgb[2] += m;
- rgb[0] = Math.round(rgb[0] * 255);
- rgb[1] = Math.round(rgb[1] * 255);
- rgb[2] = Math.round(rgb[2] * 255);
- return {
- r: rgb[0],
- g: rgb[1],
- b: rgb[2]
- };
- },
- /**
- * http://en.wikipedia.org/wiki/HSL_and_HSV
- * @param {Number} r The red component (0-255).
- * @param {Number} g The green component (0-255).
- * @param {Number} b The blue component (0-255).
- * @return {Object} An object with "h", "s" and "v" color properties.
- */
- rgb2hsv: function(r, g, b) {
- r = r / 255;
- g = g / 255;
- b = b / 255;
- var M = Math.max(r, g, b);
- var m = Math.min(r, g, b);
- var c = M - m;
- var hprime = 0;
- if (c !== 0) {
- if (M === r) {
- hprime = ((g - b) / c) % 6;
- } else if (M === g) {
- hprime = ((b - r) / c) + 2;
- } else if (M === b) {
- hprime = ((r - g) / c) + 4;
- }
- }
- var h = hprime * 60;
- if (h === 360) {
- h = 0;
- }
- var v = M;
- var s = 0;
- if (c !== 0) {
- s = c / v;
- }
- h = h / 360;
- if (h < 0) {
- h = h + 1;
- }
- return {
- h: h,
- s: s,
- v: v
- };
- },
- /**
- *
- * @param r
- * @param g
- * @param b
- * @return {String}
- */
- rgb2hex: function(r, g, b) {
- r = r.toString(16);
- g = g.toString(16);
- b = b.toString(16);
- if (r.length < 2) {
- r = '0' + r;
- }
- if (g.length < 2) {
- g = '0' + g;
- }
- if (b.length < 2) {
- b = '0' + b;
- }
- return (r + g + b).toUpperCase();
- },
- colorMap: {
- aliceblue: [
- 240,
- 248,
- 255
- ],
- antiquewhite: [
- 250,
- 235,
- 215
- ],
- aqua: [
- 0,
- 255,
- 255
- ],
- aquamarine: [
- 127,
- 255,
- 212
- ],
- azure: [
- 240,
- 255,
- 255
- ],
- beige: [
- 245,
- 245,
- 220
- ],
- bisque: [
- 255,
- 228,
- 196
- ],
- black: [
- 0,
- 0,
- 0
- ],
- blanchedalmond: [
- 255,
- 235,
- 205
- ],
- blue: [
- 0,
- 0,
- 255
- ],
- blueviolet: [
- 138,
- 43,
- 226
- ],
- brown: [
- 165,
- 42,
- 42
- ],
- burlywood: [
- 222,
- 184,
- 135
- ],
- cadetblue: [
- 95,
- 158,
- 160
- ],
- chartreuse: [
- 127,
- 255,
- 0
- ],
- chocolate: [
- 210,
- 105,
- 30
- ],
- coral: [
- 255,
- 127,
- 80
- ],
- cornflowerblue: [
- 100,
- 149,
- 237
- ],
- cornsilk: [
- 255,
- 248,
- 220
- ],
- crimson: [
- 220,
- 20,
- 60
- ],
- cyan: [
- 0,
- 255,
- 255
- ],
- darkblue: [
- 0,
- 0,
- 139
- ],
- darkcyan: [
- 0,
- 139,
- 139
- ],
- darkgoldenrod: [
- 184,
- 132,
- 11
- ],
- darkgray: [
- 169,
- 169,
- 169
- ],
- darkgreen: [
- 0,
- 100,
- 0
- ],
- darkgrey: [
- 169,
- 169,
- 169
- ],
- darkkhaki: [
- 189,
- 183,
- 107
- ],
- darkmagenta: [
- 139,
- 0,
- 139
- ],
- darkolivegreen: [
- 85,
- 107,
- 47
- ],
- darkorange: [
- 255,
- 140,
- 0
- ],
- darkorchid: [
- 153,
- 50,
- 204
- ],
- darkred: [
- 139,
- 0,
- 0
- ],
- darksalmon: [
- 233,
- 150,
- 122
- ],
- darkseagreen: [
- 143,
- 188,
- 143
- ],
- darkslateblue: [
- 72,
- 61,
- 139
- ],
- darkslategray: [
- 47,
- 79,
- 79
- ],
- darkslategrey: [
- 47,
- 79,
- 79
- ],
- darkturquoise: [
- 0,
- 206,
- 209
- ],
- darkviolet: [
- 148,
- 0,
- 211
- ],
- deeppink: [
- 255,
- 20,
- 147
- ],
- deepskyblue: [
- 0,
- 191,
- 255
- ],
- dimgray: [
- 105,
- 105,
- 105
- ],
- dimgrey: [
- 105,
- 105,
- 105
- ],
- dodgerblue: [
- 30,
- 144,
- 255
- ],
- firebrick: [
- 178,
- 34,
- 34
- ],
- floralwhite: [
- 255,
- 255,
- 240
- ],
- forestgreen: [
- 34,
- 139,
- 34
- ],
- fuchsia: [
- 255,
- 0,
- 255
- ],
- gainsboro: [
- 220,
- 220,
- 220
- ],
- ghostwhite: [
- 248,
- 248,
- 255
- ],
- gold: [
- 255,
- 215,
- 0
- ],
- goldenrod: [
- 218,
- 165,
- 32
- ],
- gray: [
- 128,
- 128,
- 128
- ],
- green: [
- 0,
- 128,
- 0
- ],
- greenyellow: [
- 173,
- 255,
- 47
- ],
- grey: [
- 128,
- 128,
- 128
- ],
- honeydew: [
- 240,
- 255,
- 240
- ],
- hotpink: [
- 255,
- 105,
- 180
- ],
- indianred: [
- 205,
- 92,
- 92
- ],
- indigo: [
- 75,
- 0,
- 130
- ],
- ivory: [
- 255,
- 255,
- 240
- ],
- khaki: [
- 240,
- 230,
- 140
- ],
- lavender: [
- 230,
- 230,
- 250
- ],
- lavenderblush: [
- 255,
- 240,
- 245
- ],
- lawngreen: [
- 124,
- 252,
- 0
- ],
- lemonchiffon: [
- 255,
- 250,
- 205
- ],
- lightblue: [
- 173,
- 216,
- 230
- ],
- lightcoral: [
- 240,
- 128,
- 128
- ],
- lightcyan: [
- 224,
- 255,
- 255
- ],
- lightgoldenrodyellow: [
- 250,
- 250,
- 210
- ],
- lightgray: [
- 211,
- 211,
- 211
- ],
- lightgreen: [
- 144,
- 238,
- 144
- ],
- lightgrey: [
- 211,
- 211,
- 211
- ],
- lightpink: [
- 255,
- 182,
- 193
- ],
- lightsalmon: [
- 255,
- 160,
- 122
- ],
- lightseagreen: [
- 32,
- 178,
- 170
- ],
- lightskyblue: [
- 135,
- 206,
- 250
- ],
- lightslategray: [
- 119,
- 136,
- 153
- ],
- lightslategrey: [
- 119,
- 136,
- 153
- ],
- lightsteelblue: [
- 176,
- 196,
- 222
- ],
- lightyellow: [
- 255,
- 255,
- 224
- ],
- lime: [
- 0,
- 255,
- 0
- ],
- limegreen: [
- 50,
- 205,
- 50
- ],
- linen: [
- 250,
- 240,
- 230
- ],
- magenta: [
- 255,
- 0,
- 255
- ],
- maroon: [
- 128,
- 0,
- 0
- ],
- mediumaquamarine: [
- 102,
- 205,
- 170
- ],
- mediumblue: [
- 0,
- 0,
- 205
- ],
- mediumorchid: [
- 186,
- 85,
- 211
- ],
- mediumpurple: [
- 147,
- 112,
- 219
- ],
- mediumseagreen: [
- 60,
- 179,
- 113
- ],
- mediumslateblue: [
- 123,
- 104,
- 238
- ],
- mediumspringgreen: [
- 0,
- 250,
- 154
- ],
- mediumturquoise: [
- 72,
- 209,
- 204
- ],
- mediumvioletred: [
- 199,
- 21,
- 133
- ],
- midnightblue: [
- 25,
- 25,
- 112
- ],
- mintcream: [
- 245,
- 255,
- 250
- ],
- mistyrose: [
- 255,
- 228,
- 225
- ],
- moccasin: [
- 255,
- 228,
- 181
- ],
- navajowhite: [
- 255,
- 222,
- 173
- ],
- navy: [
- 0,
- 0,
- 128
- ],
- oldlace: [
- 253,
- 245,
- 230
- ],
- olive: [
- 128,
- 128,
- 0
- ],
- olivedrab: [
- 107,
- 142,
- 35
- ],
- orange: [
- 255,
- 165,
- 0
- ],
- orangered: [
- 255,
- 69,
- 0
- ],
- orchid: [
- 218,
- 112,
- 214
- ],
- palegoldenrod: [
- 238,
- 232,
- 170
- ],
- palegreen: [
- 152,
- 251,
- 152
- ],
- paleturquoise: [
- 175,
- 238,
- 238
- ],
- palevioletred: [
- 219,
- 112,
- 147
- ],
- papayawhip: [
- 255,
- 239,
- 213
- ],
- peachpuff: [
- 255,
- 218,
- 185
- ],
- peru: [
- 205,
- 133,
- 63
- ],
- pink: [
- 255,
- 192,
- 203
- ],
- plum: [
- 221,
- 160,
- 203
- ],
- powderblue: [
- 176,
- 224,
- 230
- ],
- purple: [
- 128,
- 0,
- 128
- ],
- red: [
- 255,
- 0,
- 0
- ],
- rosybrown: [
- 188,
- 143,
- 143
- ],
- royalblue: [
- 65,
- 105,
- 225
- ],
- saddlebrown: [
- 139,
- 69,
- 19
- ],
- salmon: [
- 250,
- 128,
- 114
- ],
- sandybrown: [
- 244,
- 164,
- 96
- ],
- seagreen: [
- 46,
- 139,
- 87
- ],
- seashell: [
- 255,
- 245,
- 238
- ],
- sienna: [
- 160,
- 82,
- 45
- ],
- silver: [
- 192,
- 192,
- 192
- ],
- skyblue: [
- 135,
- 206,
- 235
- ],
- slateblue: [
- 106,
- 90,
- 205
- ],
- slategray: [
- 119,
- 128,
- 144
- ],
- slategrey: [
- 119,
- 128,
- 144
- ],
- snow: [
- 255,
- 255,
- 250
- ],
- springgreen: [
- 0,
- 255,
- 127
- ],
- steelblue: [
- 70,
- 130,
- 180
- ],
- tan: [
- 210,
- 180,
- 140
- ],
- teal: [
- 0,
- 128,
- 128
- ],
- thistle: [
- 216,
- 191,
- 216
- ],
- tomato: [
- 255,
- 99,
- 71
- ],
- turquoise: [
- 64,
- 224,
- 208
- ],
- violet: [
- 238,
- 130,
- 238
- ],
- wheat: [
- 245,
- 222,
- 179
- ],
- white: [
- 255,
- 255,
- 255
- ],
- whitesmoke: [
- 245,
- 245,
- 245
- ],
- yellow: [
- 255,
- 255,
- 0
- ],
- yellowgreen: [
- 154,
- 205,
- 5
- ]
- }
- };
- }, function(ColorUtils) {
- var formats = ColorUtils.formats,
- lowerized = {};
- formats['#HEX6'] = function(color) {
- return '#' + formats.HEX6(color);
- };
- formats['#HEX8'] = function(color) {
- return '#' + formats.HEX8(color);
- };
- Ext.Object.each(formats, function(name, fn) {
- lowerized[name.toLowerCase()] = function(color) {
- var ret = fn(color);
- return ret.toLowerCase();
- };
- });
- Ext.apply(formats, lowerized);
- });
- /**
- * @private
- */
- Ext.define('Ext.ux.colorpick.ColorMapController', {
- extend: 'Ext.app.ViewController',
- alias: 'controller.colorpickercolormapcontroller',
- requires: [
- 'Ext.ux.colorpick.ColorUtils'
- ],
- // After the component is rendered
- onFirstBoxReady: function() {
- var me = this,
- colorMap = me.getView(),
- dragHandle = colorMap.down('#dragHandle'),
- dd = dragHandle.dd;
- // configure draggable constraints
- dd.constrain = true;
- dd.constrainTo = colorMap.getEl();
- dd.initialConstrainTo = dd.constrainTo;
- // needed otheriwse error EXTJS-13187
- // event handlers
- dd.on('drag', Ext.bind(me.onHandleDrag, me));
- me.mon(colorMap.getEl(), {
- mousedown: me.onMouseDown,
- dragstart: me.onDragStart,
- scope: me
- });
- },
- // Fires when handle is dragged; propagates "handledrag" event on the ColorMap
- // with parameters "percentX" and "percentY", both 0-1, representing the handle
- // position on the color map, relative to the container
- onHandleDrag: function(componentDragger, e) {
- var me = this,
- container = me.getView(),
- // the Color Map
- dragHandle = container.down('#dragHandle'),
- x = dragHandle.getX() - container.getX(),
- y = dragHandle.getY() - container.getY(),
- containerEl = container.getEl(),
- containerWidth = containerEl.getWidth(),
- containerHeight = containerEl.getHeight(),
- xRatio = x / containerWidth,
- yRatio = y / containerHeight;
- // Adjust x/y ratios for dragger always being 1 pixel from the edge on the right
- if (xRatio > 0.99) {
- xRatio = 1;
- }
- if (yRatio > 0.99) {
- yRatio = 1;
- }
- container.fireEvent('handledrag', xRatio, yRatio);
- },
- // Whenever we mousedown over the colormap area
- onMouseDown: function(e) {
- var me = this,
- container = me.getView(),
- dragHandle = container.down('#dragHandle');
- // position drag handle accordingly
- dragHandle.setY(e.getY());
- dragHandle.setX(e.getX());
- me.onHandleDrag();
- // tie into the default dd mechanism
- dragHandle.dd.onMouseDown(e, dragHandle.dd.el);
- },
- // Whenever we start a drag over the colormap area
- onDragStart: function(e) {
- var me = this,
- container = me.getView(),
- dragHandle = container.down('#dragHandle');
- // tie into the default dd mechanism
- dragHandle.dd.onDragStart(e, dragHandle.dd.el);
- },
- // Whenever the map is clicked (but not the drag handle) we need to position
- // the drag handle to the point of click
- onMapClick: function(e) {
- var me = this,
- container = me.getView(),
- // the Color Map
- dragHandle = container.down('#dragHandle'),
- cXY = container.getXY(),
- eXY = e.getXY(),
- left, top;
- left = eXY[0] - cXY[0];
- top = eXY[1] - cXY[1];
- dragHandle.getEl().setStyle({
- left: left + 'px',
- top: top + 'px'
- });
- me.onHandleDrag();
- },
- // Whenever the underlying binding data is changed we need to
- // update position of the dragger; active drag state has been
- // accounted for earlier
- onColorBindingChanged: function(selectedColor) {
- var me = this,
- vm = me.getViewModel(),
- rgba = vm.get('selectedColor'),
- hsv,
- container = me.getView(),
- // the Color Map
- dragHandle = container.down('#dragHandle'),
- containerEl = container.getEl(),
- containerWidth = containerEl.getWidth(),
- containerHeight = containerEl.getHeight(),
- xRatio, yRatio, left, top;
- // Color map selection really only depends on saturation and value of the color
- hsv = Ext.ux.colorpick.ColorUtils.rgb2hsv(rgba.r, rgba.g, rgba.b);
- // x-axis of color map with value 0-1 translates to saturation
- xRatio = hsv.s;
- left = containerWidth * xRatio;
- // y-axis of color map with value 0-1 translates to reverse of "value"
- yRatio = 1 - hsv.v;
- top = containerHeight * yRatio;
- // Position dragger
- dragHandle.getEl().setStyle({
- left: left + 'px',
- top: top + 'px'
- });
- },
- // Whenever only Hue changes we can update the
- // background color of the color map
- // Param "hue" has value of 0-1
- onHueBindingChanged: function(hue) {
- var me = this,
- vm = me.getViewModel(),
- fullColorRGB, hex;
- fullColorRGB = Ext.ux.colorpick.ColorUtils.hsv2rgb(hue, 1, 1);
- hex = Ext.ux.colorpick.ColorUtils.rgb2hex(fullColorRGB.r, fullColorRGB.g, fullColorRGB.b);
- me.getView().getEl().applyStyles({
- 'background-color': '#' + hex
- });
- }
- });
- /**
- * The main colorful square for selecting color shades by dragging around the
- * little circle.
- * @private
- */
- Ext.define('Ext.ux.colorpick.ColorMap', {
- extend: 'Ext.container.Container',
- alias: 'widget.colorpickercolormap',
- controller: 'colorpickercolormapcontroller',
- requires: [
- 'Ext.ux.colorpick.ColorMapController'
- ],
- cls: Ext.baseCSSPrefix + 'colorpicker-colormap',
- // This is the drag "circle"; note it's 1x1 in size to allow full
- // travel around the color map; the inner div has the bigger image
- items: [
- {
- xtype: 'component',
- cls: Ext.baseCSSPrefix + 'colorpicker-colormap-draghandle-container',
- itemId: 'dragHandle',
- width: 1,
- height: 1,
- draggable: true,
- html: '<div class="' + Ext.baseCSSPrefix + 'colorpicker-colormap-draghandle"></div>'
- }
- ],
- listeners: {
- boxready: {
- single: true,
- fn: 'onFirstBoxReady',
- scope: 'controller'
- },
- colorbindingchanged: {
- fn: 'onColorBindingChanged',
- scope: 'controller'
- },
- huebindingchanged: {
- fn: 'onHueBindingChanged',
- scope: 'controller'
- }
- },
- afterRender: function() {
- var me = this,
- src = me.mapGradientUrl,
- el = me.el;
- me.callParent();
- if (!src) {
- // We do this trick to allow the Sass to calculate resource image path for
- // our package and pick up the proper image URL here.
- src = el.getStyle('background-image');
- src = src.substring(4, src.length - 1);
- // strip off outer "url(...)"
- // In IE8 this path will have quotes around it
- if (src.indexOf('"') === 0) {
- src = src.substring(1, src.length - 1);
- }
- // Then remember it on our prototype for any subsequent instances.
- Ext.ux.colorpick.ColorMap.prototype.mapGradientUrl = src;
- }
- // Now clear that style because it will conflict with the background-color
- el.setStyle('background-image', 'none');
- // Create the image with transparent PNG with black and white gradient shades;
- // it blends with the background color (which changes with hue selection). This
- // must be an IMG in order to properly stretch to fit.
- el = me.layout.getElementTarget();
- // the el for items and html
- el.createChild({
- tag: 'img',
- cls: Ext.baseCSSPrefix + 'colorpicker-colormap-blender',
- src: src
- });
- },
- // Called via data binding whenever selectedColor changes; fires "colorbindingchanged"
- setPosition: function(data) {
- var me = this,
- dragHandle = me.down('#dragHandle');
- // Too early in the render cycle? Skip event
- if (!dragHandle.dd || !dragHandle.dd.constrain) {
- return;
- }
- // User actively dragging? Skip event
- if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
- return;
- }
- me.fireEvent('colorbindingchanged', data);
- },
- // Called via data binding whenever selectedColor.h changes; fires "huebindingchanged" event
- setHue: function(hue) {
- var me = this;
- // Too early in the render cycle? Skip event
- if (!me.getEl()) {
- return;
- }
- me.fireEvent('huebindingchanged', hue);
- }
- });
- /**
- * View Model that holds the "selectedColor" of the color picker container.
- */
- Ext.define('Ext.ux.colorpick.SelectorModel', {
- extend: 'Ext.app.ViewModel',
- alias: 'viewmodel.colorpick-selectormodel',
- requires: [
- 'Ext.ux.colorpick.ColorUtils'
- ],
- data: {
- selectedColor: {
- r: 255,
- // red
- g: 255,
- // green
- b: 255,
- // blue
- h: 0,
- // hue,
- s: 1,
- // saturation
- v: 1,
- // value
- a: 1
- },
- // alpha (opacity)
- previousColor: {
- r: 0,
- // red
- g: 0,
- // green
- b: 0,
- // blue
- h: 0,
- // hue,
- s: 1,
- // saturation
- v: 1,
- // value
- a: 1
- }
- },
- // alpha (opacity)
- formulas: {
- // Hexadecimal representation of the color
- hex: {
- get: function(get) {
- var r = get('selectedColor.r').toString(16),
- g = get('selectedColor.g').toString(16),
- b = get('selectedColor.b').toString(16),
- result;
- result = Ext.ux.colorpick.ColorUtils.rgb2hex(r, g, b);
- return '#' + result;
- },
- set: function(hex) {
- var rgb = Ext.ux.colorpick.ColorUtils.parseColor(hex);
- this.changeRGB(rgb);
- }
- },
- // "R" in "RGB"
- red: {
- get: function(get) {
- return get('selectedColor.r');
- },
- set: function(r) {
- this.changeRGB({
- r: r
- });
- }
- },
- // "G" in "RGB"
- green: {
- get: function(get) {
- return get('selectedColor.g');
- },
- set: function(g) {
- this.changeRGB({
- g: g
- });
- }
- },
- // "B" in "RGB"
- blue: {
- get: function(get) {
- return get('selectedColor.b');
- },
- set: function(b) {
- this.changeRGB({
- b: b
- });
- }
- },
- // "H" in HSV
- hue: {
- get: function(get) {
- return get('selectedColor.h') * 360;
- },
- set: function(hue) {
- this.changeHSV({
- h: hue / 360
- });
- }
- },
- // "S" in HSV
- saturation: {
- get: function(get) {
- return get('selectedColor.s') * 100;
- },
- set: function(saturation) {
- this.changeHSV({
- s: saturation / 100
- });
- }
- },
- // "V" in HSV
- value: {
- get: function(get) {
- var v = get('selectedColor.v');
- return v * 100;
- },
- set: function(value) {
- this.changeHSV({
- v: value / 100
- });
- }
- },
- alpha: {
- get: function(data) {
- var a = data('selectedColor.a');
- return a * 100;
- },
- set: function(alpha) {
- this.set('selectedColor', Ext.applyIf({
- a: alpha / 100
- }, this.data.selectedColor));
- }
- }
- },
- // formulas
- changeHSV: function(hsv) {
- Ext.applyIf(hsv, this.data.selectedColor);
- var rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hsv.h, hsv.s, hsv.v);
- hsv.r = rgb.r;
- hsv.g = rgb.g;
- hsv.b = rgb.b;
- this.set('selectedColor', hsv);
- },
- changeRGB: function(rgb) {
- Ext.applyIf(rgb, this.data.selectedColor);
- var hsv = Ext.ux.colorpick.ColorUtils.rgb2hsv(rgb.r, rgb.g, rgb.b);
- rgb.h = hsv.h;
- rgb.s = hsv.s;
- rgb.v = hsv.v;
- this.set('selectedColor', rgb);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.ux.colorpick.SelectorController', {
- extend: 'Ext.app.ViewController',
- alias: 'controller.colorpick-selectorcontroller',
- requires: [
- 'Ext.ux.colorpick.ColorUtils'
- ],
- destroy: function() {
- var me = this,
- view = me.getView(),
- childViewModel = view.childViewModel;
- if (childViewModel) {
- childViewModel.destroy();
- view.childViewModel = null;
- }
- me.callParent();
- },
- changeHSV: function(hsv) {
- var view = this.getView(),
- color = view.getColor(),
- rgb;
- // Put in values we are not changing (like A, but also missing HSV values)
- Ext.applyIf(hsv, color);
- // Now that HSV is complete, recalculate RGB and combine them
- rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hsv.h, hsv.s, hsv.v);
- Ext.apply(hsv, rgb);
- view.setColor(hsv);
- },
- // Updates Saturation/Value in the model based on ColorMap; params:
- // xPercent - where is the handle relative to the color map width
- // yPercent - where is the handle relative to the color map height
- onColorMapHandleDrag: function(xPercent, yPercent) {
- this.changeHSV({
- s: xPercent,
- v: 1 - yPercent
- });
- },
- // Updates HSV Value in the model and downstream RGB settings
- onValueSliderHandleDrag: function(yPercent) {
- this.changeHSV({
- v: 1 - yPercent
- });
- },
- // Updates HSV Saturation in the model and downstream RGB settings
- onSaturationSliderHandleDrag: function(yPercent) {
- this.changeHSV({
- s: 1 - yPercent
- });
- },
- // Updates Hue in the model and downstream RGB settings
- onHueSliderHandleDrag: function(yPercent) {
- this.changeHSV({
- h: 1 - yPercent
- });
- },
- onAlphaSliderHandleDrag: function(yPercent) {
- var view = this.getView(),
- color = view.getColor(),
- newColor = Ext.applyIf({
- a: 1 - yPercent
- }, color);
- view.setColor(newColor);
- view.el.repaint();
- },
- onPreviousColorSelected: function(comp, color) {
- var view = this.getView();
- view.setColor(color);
- },
- onOK: function() {
- var me = this,
- view = me.getView();
- view.fireEvent('ok', view, view.getValue());
- },
- onCancel: function() {
- this.fireViewEvent('cancel', this.getView());
- },
- onResize: function() {
- var me = this,
- view = me.getView(),
- vm = view.childViewModel,
- refs = me.getReferences(),
- h, s, v, a;
- // Skip initial rendering resize
- if (!me.hasResizedOnce) {
- me.hasResizedOnce = true;
- return;
- }
- h = vm.get('hue');
- s = vm.get('saturation');
- v = vm.get('value');
- a = vm.get('alpha');
- // Reposition the colormap's & sliders' drag handles
- refs.colorMap.setPosition(vm.getData());
- refs.hueSlider.setHue(h);
- refs.satSlider.setSaturation(s);
- refs.valueSlider.setValue(v);
- refs.alphaSlider.setAlpha(a);
- }
- });
- /**
- * A basic component that changes background color, with considerations for opacity
- * support (checkered background image and IE8 support).
- */
- Ext.define('Ext.ux.colorpick.ColorPreview', {
- extend: 'Ext.Component',
- alias: 'widget.colorpickercolorpreview',
- requires: [
- 'Ext.util.Format',
- 'Ext.XTemplate'
- ],
- //hack to solve issue with IE, when applying a filter the click listener is not being fired.
- style: 'position: relative',
- 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>',
- //eo hack
- cls: Ext.baseCSSPrefix + 'colorpreview',
- height: 256,
- onRender: function() {
- var me = this;
- me.callParent(arguments);
- me.mon(me.el.down('.btn'), 'click', me.onClick, me);
- },
- onClick: function() {
- this.fireEvent('click', this, this.color);
- },
- // Called via databinding - update background color whenever ViewModel changes
- setColor: function(color) {
- var me = this,
- el = me.getEl();
- // Too early in rendering cycle; skip
- if (!el) {
- return;
- }
- me.color = color;
- me.applyBgStyle(color);
- },
- 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 */
- 'background: {rgba};'),
- applyBgStyle: function(color) {
- var me = this,
- colorUtils = Ext.ux.colorpick.ColorUtils,
- filterSelector = '.' + Ext.baseCSSPrefix + 'colorpreview-filter',
- el = me.getEl().down(filterSelector),
- hex, alpha, rgba, bgStyle;
- hex = colorUtils.rgb2hex(color.r, color.g, color.b);
- alpha = Ext.util.Format.hex(Math.floor(color.a * 255), 2);
- rgba = colorUtils.getRGBAString(color);
- bgStyle = this.bgStyleTpl.apply({
- hex: hex,
- hexAlpha: alpha,
- rgba: rgba
- });
- el.applyStyles(bgStyle);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.ux.colorpick.SliderController', {
- extend: 'Ext.app.ViewController',
- alias: 'controller.colorpick-slidercontroller',
- // After the component is rendered
- boxReady: function(view) {
- var me = this,
- container = me.getDragContainer(),
- dragHandle = me.getDragHandle(),
- dd = dragHandle.dd;
- // configure draggable constraints
- dd.constrain = true;
- dd.constrainTo = container.getEl();
- dd.initialConstrainTo = dd.constrainTo;
- // needed otherwise error EXTJS-13187
- // event handlers
- dd.on('drag', me.onHandleDrag, me);
- },
- getDragHandle: function() {
- return this.view.lookupReference('dragHandle');
- },
- getDragContainer: function() {
- return this.view.lookupReference('dragHandleContainer');
- },
- // Fires when handle is dragged; fires "handledrag" event on the slider
- // with parameter "percentY" 0-1, representing the handle position on the slider
- // relative to the height
- onHandleDrag: function(e) {
- var me = this,
- view = me.getView(),
- container = me.getDragContainer(),
- dragHandle = me.getDragHandle(),
- y = dragHandle.getY() - container.getY(),
- containerEl = container.getEl(),
- containerHeight = containerEl.getHeight(),
- yRatio = y / containerHeight;
- // Adjust y ratio for dragger always being 1 pixel from the edge on the bottom
- if (yRatio > 0.99) {
- yRatio = 1;
- }
- view.fireEvent('handledrag', yRatio);
- },
- // Whenever we mousedown over the slider area
- onMouseDown: function(e) {
- var me = this,
- dragHandle = me.getDragHandle(),
- y = e.getY();
- // position drag handle accordingly
- dragHandle.setY(y);
- me.onHandleDrag();
- dragHandle.el.repaint();
- // tie into the default dd mechanism
- dragHandle.dd.onMouseDown(e, dragHandle.dd.el);
- },
- // Whenever we start a drag over the colormap area
- onDragStart: function(e) {
- var me = this,
- dragHandle = me.getDragHandle();
- // tie into the default dd mechanism
- dragHandle.dd.onDragStart(e, dragHandle.dd.el);
- },
- onMouseUp: function() {
- var dragHandle = this.getDragHandle();
- dragHandle.dd.dragEnded = true;
- }
- });
- // work around DragTracker bug
- /**
- * Parent view for the 4 sliders seen on the color picker window.
- * @private
- */
- Ext.define('Ext.ux.colorpick.Slider', {
- extend: 'Ext.container.Container',
- xtype: 'colorpickerslider',
- controller: 'colorpick-slidercontroller',
- afterRender: function() {
- this.callParent(arguments);
- var width = this.width,
- dragCt = this.lookupReference('dragHandleContainer'),
- dragWidth = dragCt.getWidth();
- dragCt.el.setStyle('left', ((width - dragWidth) / 2) + 'px');
- },
- baseCls: Ext.baseCSSPrefix + 'colorpicker-slider',
- requires: [
- 'Ext.ux.colorpick.SliderController'
- ],
- referenceHolder: true,
- listeners: {
- element: 'el',
- mousedown: 'onMouseDown',
- mouseup: 'onMouseUp',
- dragstart: 'onDragStart'
- },
- // Container for the drag handle; needed since the slider
- // is of static size, while the parent container positions
- // it in the center; this is what receives the beautiful
- // color gradients for the visual
- items: {
- xtype: 'container',
- cls: Ext.baseCSSPrefix + 'colorpicker-draghandle-container',
- reference: 'dragHandleContainer',
- height: '100%',
- // This is the drag handle; note it's 100%x1 in size to allow full
- // vertical drag travel; the inner div has the bigger image
- items: {
- xtype: 'component',
- cls: Ext.baseCSSPrefix + 'colorpicker-draghandle-outer',
- reference: 'dragHandle',
- width: '100%',
- height: 1,
- draggable: true,
- html: '<div class="' + Ext.baseCSSPrefix + 'colorpicker-draghandle"></div>'
- }
- },
- // <debug>
- // Called via data binding whenever selectedColor.h changes;
- setHue: function() {
- Ext.raise('Must implement setHue() in a child class!');
- },
- // </debug>
- getDragHandle: function() {
- return this.lookupReference('dragHandle');
- },
- getDragContainer: function() {
- return this.lookupReference('dragHandleContainer');
- }
- });
- /**
- * Used for "Alpha" slider.
- * @private
- */
- Ext.define('Ext.ux.colorpick.SliderAlpha', {
- extend: 'Ext.ux.colorpick.Slider',
- alias: 'widget.colorpickerslideralpha',
- cls: Ext.baseCSSPrefix + 'colorpicker-alpha',
- requires: [
- 'Ext.XTemplate'
- ],
- 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 */
- 'background: -moz-linear-gradient(top, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* FF3.6+ */
- 'background: -webkit-linear-gradient(top,rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* Chrome10+,Safari5.1+ */
- 'background: -o-linear-gradient(top, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* Opera 11.10+ */
- 'background: -ms-linear-gradient(top, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);' + /* IE10+ */
- 'background: linear-gradient(to bottom, rgba({r}, {g}, {b}, 1) 0%, rgba({r}, {g}, {b}, 0) 100%);'),
- /* W3C */
- // Called via data binding whenever selectedColor.a changes; param is 0-100
- setAlpha: function(value) {
- var me = this,
- container = me.getDragContainer(),
- dragHandle = me.getDragHandle(),
- containerEl = container.getEl(),
- containerHeight = containerEl.getHeight(),
- el, top;
- // Too early in the render cycle? Skip event
- if (!dragHandle.dd || !dragHandle.dd.constrain) {
- return;
- }
- // User actively dragging? Skip event
- if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
- return;
- }
- // y-axis of slider with value 0-1 translates to reverse of "value"
- top = containerHeight * (1 - (value / 100));
- // Position dragger
- el = dragHandle.getEl();
- el.setStyle({
- top: top + 'px'
- });
- },
- // Called via data binding whenever selectedColor.h changes; hue param is 0-1
- setColor: function(color) {
- var me = this,
- container = me.getDragContainer(),
- hex, el;
- // Too early in the render cycle? Skip event
- if (!me.getEl()) {
- return;
- }
- // Determine HEX for new hue and set as background based on template
- hex = Ext.ux.colorpick.ColorUtils.rgb2hex(color.r, color.g, color.b);
- el = container.getEl().first();
- el.applyStyles(me.gradientStyleTpl.apply({
- hex: hex,
- r: color.r,
- g: color.g,
- b: color.b
- }));
- }
- });
- /**
- * Used for "Saturation" slider
- * @private
- */
- Ext.define('Ext.ux.colorpick.SliderSaturation', {
- extend: 'Ext.ux.colorpick.Slider',
- alias: 'widget.colorpickerslidersaturation',
- cls: Ext.baseCSSPrefix + 'colorpicker-saturation',
- gradientStyleTpl: Ext.create('Ext.XTemplate', Ext.isIE && Ext.ieVersion < 10 ? 'filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr=\'#{hex}\', endColorstr=\'#ffffff\');' : /* IE6-9 */
- 'background: -mox-linear-gradient(top, #{hex} 0%, #ffffff 100%);' + /* FF3.6+ */
- 'background: -webkit-linear-gradient(top, #{hex} 0%,#ffffff 100%);' + /* Chrome10+,Safari5.1+ */
- 'background: -o-linear-gradient(top, #{hex} 0%,#ffffff 100%);' + /* Opera 11.10+ */
- 'background: -ms-linear-gradient(top, #{hex} 0%,#ffffff 100%);' + /* IE10+ */
- 'background: linear-gradient(to bottom, #{hex} 0%,#ffffff 100%);'),
- /* W3C */
- // Called via data binding whenever selectedColor.s changes; saturation param is 0-100
- setSaturation: function(saturation) {
- var me = this,
- container = me.getDragContainer(),
- dragHandle = me.getDragHandle(),
- containerEl = container.getEl(),
- containerHeight = containerEl.getHeight(),
- yRatio, top;
- // Too early in the render cycle? Skip event
- if (!dragHandle.dd || !dragHandle.dd.constrain) {
- return;
- }
- // User actively dragging? Skip event
- if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
- return;
- }
- // y-axis of slider with value 0-1 translates to reverse of "saturation"
- yRatio = 1 - (saturation / 100);
- top = containerHeight * yRatio;
- // Position dragger
- dragHandle.getEl().setStyle({
- top: top + 'px'
- });
- },
- // Called via data binding whenever selectedColor.h changes; hue param is 0-1
- setHue: function(hue) {
- var me = this,
- container = me.getDragContainer(),
- rgb, hex;
- // Too early in the render cycle? Skip event
- if (!me.getEl()) {
- return;
- }
- // Determine HEX for new hue and set as background based on template
- rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hue, 1, 1);
- hex = Ext.ux.colorpick.ColorUtils.rgb2hex(rgb.r, rgb.g, rgb.b);
- container.getEl().applyStyles(me.gradientStyleTpl.apply({
- hex: hex
- }));
- }
- });
- /**
- * Used for "Value" slider.
- * @private
- */
- Ext.define('Ext.ux.colorpick.SliderValue', {
- extend: 'Ext.ux.colorpick.Slider',
- alias: 'widget.colorpickerslidervalue',
- cls: Ext.baseCSSPrefix + 'colorpicker-value',
- requires: [
- 'Ext.XTemplate'
- ],
- gradientStyleTpl: Ext.create('Ext.XTemplate', Ext.isIE && Ext.ieVersion < 10 ? 'filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr=\'#{hex}\', endColorstr=\'#000000\');' : /* IE6-9 */
- 'background: -mox-linear-gradient(top, #{hex} 0%, #000000 100%);' + /* FF3.6+ */
- 'background: -webkit-linear-gradient(top, #{hex} 0%,#000000 100%);' + /* Chrome10+,Safari5.1+ */
- 'background: -o-linear-gradient(top, #{hex} 0%,#000000 100%);' + /* Opera 11.10+ */
- 'background: -ms-linear-gradient(top, #{hex} 0%,#000000 100%);' + /* IE10+ */
- 'background: linear-gradient(to bottom, #{hex} 0%,#000000 100%);'),
- /* W3C */
- // Called via data binding whenever selectedColor.v changes; value param is 0-100
- setValue: function(value) {
- var me = this,
- container = me.getDragContainer(),
- dragHandle = me.getDragHandle(),
- containerEl = container.getEl(),
- containerHeight = containerEl.getHeight(),
- yRatio, top;
- // Too early in the render cycle? Skip event
- if (!dragHandle.dd || !dragHandle.dd.constrain) {
- return;
- }
- // User actively dragging? Skip event
- if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
- return;
- }
- // y-axis of slider with value 0-1 translates to reverse of "value"
- yRatio = 1 - (value / 100);
- top = containerHeight * yRatio;
- // Position dragger
- dragHandle.getEl().setStyle({
- top: top + 'px'
- });
- },
- // Called via data binding whenever selectedColor.h changes; hue param is 0-1
- setHue: function(hue) {
- var me = this,
- container = me.getDragContainer(),
- rgb, hex;
- // Too early in the render cycle? Skip event
- if (!me.getEl()) {
- return;
- }
- // Determine HEX for new hue and set as background based on template
- rgb = Ext.ux.colorpick.ColorUtils.hsv2rgb(hue, 1, 1);
- hex = Ext.ux.colorpick.ColorUtils.rgb2hex(rgb.r, rgb.g, rgb.b);
- container.getEl().applyStyles(me.gradientStyleTpl.apply({
- hex: hex
- }));
- }
- });
- /**
- * Used for "Hue" slider.
- * @private
- */
- Ext.define('Ext.ux.colorpick.SliderHue', {
- extend: 'Ext.ux.colorpick.Slider',
- alias: 'widget.colorpickersliderhue',
- cls: Ext.baseCSSPrefix + 'colorpicker-hue',
- afterRender: function() {
- var me = this,
- src = me.gradientUrl,
- el = me.el;
- me.callParent();
- if (!src) {
- // We do this trick to allow the Sass to calculate resource image path for
- // our package and pick up the proper image URL here.
- src = el.getStyle('background-image');
- src = src.substring(4, src.length - 1);
- // strip off outer "url(...)"
- // In IE8 this path will have quotes around it
- if (src.indexOf('"') === 0) {
- src = src.substring(1, src.length - 1);
- }
- // Then remember it on our prototype for any subsequent instances.
- Ext.ux.colorpick.SliderHue.prototype.gradientUrl = src;
- }
- // Now clear that style because it will conflict with the background-color
- el.setStyle('background-image', 'none');
- // Create the image with the background PNG
- el = me.getDragContainer().layout.getElementTarget();
- // the el for items and html
- el.createChild({
- tag: 'img',
- cls: Ext.baseCSSPrefix + 'colorpicker-hue-gradient',
- src: src
- });
- },
- // Called via data binding whenever selectedColor.h changes; hue param is 0-1
- setHue: function(hue) {
- var me = this,
- container = me.getDragContainer(),
- dragHandle = me.getDragHandle(),
- containerEl = container.getEl(),
- containerHeight = containerEl.getHeight(),
- el, top;
- // Too early in the render cycle? Skip event
- if (!dragHandle.dd || !dragHandle.dd.constrain) {
- return;
- }
- // User actively dragging? Skip event
- if (typeof dragHandle.dd.dragEnded !== 'undefined' && !dragHandle.dd.dragEnded) {
- return;
- }
- // y-axis of slider with value 0-1 translates to reverse of "hue"
- top = containerHeight * (1 - hue);
- // Position dragger
- el = dragHandle.getEl();
- el.setStyle({
- top: top + 'px'
- });
- }
- });
- /**
- * Sencha Pro Services presents xtype "colorselector".
- * API has been kept as close to the regular colorpicker as possible. The Selector can be
- * rendered to any container.
- *
- * The defaul selected color is configurable via {@link #value} config. Usually used in
- * forms via {@link Ext.ux.colorpick.Button} or {@link Ext.ux.colorpick.Field}.
- *
- * Typically you will need to listen for the change event to be notified when the user
- * chooses a color. Alternatively, you can bind to the "value" config
- *
- * @example
- * Ext.create('Ext.ux.colorpick.Selector', {
- * value : '993300', // initial selected color
- * renderTo : Ext.getBody(),
- * listeners: {
- * change: function (colorselector, color) {
- * console.log('New color: ' + color);
- * }
- * }
- * });
- */
- Ext.define('Ext.ux.colorpick.Selector', {
- extend: 'Ext.container.Container',
- xtype: 'colorselector',
- mixins: [
- 'Ext.ux.colorpick.Selection'
- ],
- controller: 'colorpick-selectorcontroller',
- requires: [
- 'Ext.layout.container.HBox',
- 'Ext.form.field.Text',
- 'Ext.form.field.Number',
- 'Ext.ux.colorpick.ColorMap',
- 'Ext.ux.colorpick.SelectorModel',
- 'Ext.ux.colorpick.SelectorController',
- 'Ext.ux.colorpick.ColorPreview',
- 'Ext.ux.colorpick.Slider',
- 'Ext.ux.colorpick.SliderAlpha',
- 'Ext.ux.colorpick.SliderSaturation',
- 'Ext.ux.colorpick.SliderValue',
- 'Ext.ux.colorpick.SliderHue'
- ],
- config: {
- hexReadOnly: true
- },
- width: 580,
- // default width and height gives 255x255 color map in Crisp
- height: 337,
- cls: Ext.baseCSSPrefix + 'colorpicker',
- padding: 10,
- layout: {
- type: 'hbox',
- align: 'stretch'
- },
- defaultBindProperty: 'value',
- twoWayBindable: [
- 'value'
- ],
- /**
- * @cfg fieldWidth {Number} Width of the text fields on the container (excluding HEX);
- * since the width of the slider containers is the same as the text field under it
- * (it's the same vbox column), changing this value will also affect the spacing between
- * the sliders.
- */
- fieldWidth: 50,
- /**
- * @cfg fieldPad {Number} padding between the sliders and HEX/R/G/B fields.
- */
- fieldPad: 5,
- /**
- * @cfg {Boolean} [showPreviousColor]
- * Whether "previous color" region (in upper right, below the selected color preview) should be shown;
- * these are relied upon by the {@link Ext.ux.colorpick.Button} and the {@link Ext.ux.colorpick.Field}.
- */
- showPreviousColor: false,
- /**
- * @cfg {Boolean} [showOkCancelButtons]
- * Whether Ok and Cancel buttons (in upper right, below the selected color preview) should be shown;
- * these are relied upon by the {@link Ext.ux.colorpick.Button} and the {@link Ext.ux.colorpick.Field}.
- */
- showOkCancelButtons: false,
- /**
- * @event change
- * Fires when a color is selected. Simply dragging sliders around will trigger this.
- * @param {Ext.ux.colorpick.Selector} this
- * @param {String} color The value of the selected color as per specified {@link #format}.
- * @param {String} previousColor The previous color value.
- */
- /**
- * @event ok
- * Fires when OK button is clicked (see {@link #showOkCancelButtons}).
- * @param {Ext.ux.colorpick.Selector} this
- * @param {String} color The value of the selected color as per specified {@link #format}.
- */
- /**
- * @event cancel
- * Fires when Cancel button is clicked (see {@link #showOkCancelButtons}).
- * @param {Ext.ux.colorpick.Selector} this
- */
- listeners: {
- resize: 'onResize'
- },
- constructor: function(config) {
- var me = this,
- childViewModel = Ext.Factory.viewModel('colorpick-selectormodel');
- // Since this component needs to present its value as a thing to which users can
- // bind, we create an internal VM for our purposes.
- me.childViewModel = childViewModel;
- me.items = [
- me.getMapAndHexRGBFields(childViewModel),
- me.getSliderAndHField(childViewModel),
- me.getSliderAndSField(childViewModel),
- me.getSliderAndVField(childViewModel),
- me.getSliderAndAField(childViewModel),
- me.getPreviewAndButtons(childViewModel, config)
- ];
- me.childViewModel.bind('{selectedColor}', function(color) {
- me.setColor(color);
- });
- me.callParent([
- config
- ]);
- },
- updateColor: function(color) {
- var me = this;
- me.mixins.colorselection.updateColor.call(me, color);
- me.childViewModel.set('selectedColor', color);
- },
- updatePreviousColor: function(color) {
- this.childViewModel.set('previousColor', color);
- },
- // Splits up view declaration for readability
- // "Map" and HEX/R/G/B fields
- getMapAndHexRGBFields: function(childViewModel) {
- var me = this,
- fieldMargin = {
- top: 0,
- right: me.fieldPad,
- bottom: 0,
- left: 0
- },
- fieldWidth = me.fieldWidth;
- return {
- xtype: 'container',
- viewModel: childViewModel,
- cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
- flex: 1,
- layout: {
- type: 'vbox',
- align: 'stretch'
- },
- margin: '0 10 0 0',
- items: [
- // "MAP"
- {
- xtype: 'colorpickercolormap',
- reference: 'colorMap',
- flex: 1,
- bind: {
- position: {
- bindTo: '{selectedColor}',
- deep: true
- },
- hue: '{selectedColor.h}'
- },
- listeners: {
- handledrag: 'onColorMapHandleDrag'
- }
- },
- // HEX/R/G/B FIELDS
- {
- xtype: 'container',
- layout: 'hbox',
- defaults: {
- labelAlign: 'top',
- labelSeparator: '',
- allowBlank: false,
- onChange: function() {
- // prevent data binding propagation if bad value
- if (this.isValid()) {
- // this is kind of dirty and ideally we would extend these fields
- // and override the method, but works for now
- Ext.form.field.Base.prototype.onChange.apply(this, arguments);
- }
- }
- },
- items: [
- {
- xtype: 'textfield',
- fieldLabel: 'HEX',
- flex: 1,
- bind: '{hex}',
- margin: fieldMargin,
- regex: /^#[0-9a-f]{6}$/i,
- readonly: me.getHexReadOnly()
- },
- {
- xtype: 'numberfield',
- fieldLabel: 'R',
- bind: '{red}',
- width: fieldWidth,
- hideTrigger: true,
- maxValue: 255,
- minValue: 0,
- margin: fieldMargin
- },
- {
- xtype: 'numberfield',
- fieldLabel: 'G',
- bind: '{green}',
- width: fieldWidth,
- hideTrigger: true,
- maxValue: 255,
- minValue: 0,
- margin: fieldMargin
- },
- {
- xtype: 'numberfield',
- fieldLabel: 'B',
- bind: '{blue}',
- width: fieldWidth,
- hideTrigger: true,
- maxValue: 255,
- minValue: 0,
- margin: 0
- }
- ]
- }
- ]
- };
- },
- // Splits up view declaration for readability
- // Slider and H field
- getSliderAndHField: function(childViewModel) {
- var me = this,
- fieldWidth = me.fieldWidth;
- return {
- xtype: 'container',
- viewModel: childViewModel,
- cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
- width: fieldWidth,
- layout: {
- type: 'vbox',
- align: 'stretch'
- },
- items: [
- {
- xtype: 'colorpickersliderhue',
- reference: 'hueSlider',
- flex: 1,
- bind: {
- hue: '{selectedColor.h}'
- },
- width: fieldWidth,
- listeners: {
- handledrag: 'onHueSliderHandleDrag'
- }
- },
- {
- xtype: 'numberfield',
- fieldLabel: 'H',
- labelAlign: 'top',
- labelSeparator: '',
- bind: '{hue}',
- hideTrigger: true,
- maxValue: 360,
- minValue: 0,
- allowBlank: false,
- margin: 0
- }
- ]
- };
- },
- // Splits up view declaration for readability
- // Slider and S field
- getSliderAndSField: function(childViewModel) {
- var me = this,
- fieldWidth = me.fieldWidth;
- return {
- xtype: 'container',
- viewModel: childViewModel,
- cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
- width: fieldWidth,
- layout: {
- type: 'vbox',
- align: 'stretch'
- },
- margin: {
- right: me.fieldPad,
- left: me.fieldPad
- },
- items: [
- {
- xtype: 'colorpickerslidersaturation',
- reference: 'satSlider',
- flex: 1,
- bind: {
- saturation: '{saturation}',
- hue: '{selectedColor.h}'
- },
- width: fieldWidth,
- listeners: {
- handledrag: 'onSaturationSliderHandleDrag'
- }
- },
- {
- xtype: 'numberfield',
- fieldLabel: 'S',
- labelAlign: 'top',
- labelSeparator: '',
- bind: '{saturation}',
- hideTrigger: true,
- maxValue: 100,
- minValue: 0,
- allowBlank: false,
- margin: 0
- }
- ]
- };
- },
- // Splits up view declaration for readability
- // Slider and V field
- getSliderAndVField: function(childViewModel) {
- var me = this,
- fieldWidth = me.fieldWidth;
- return {
- xtype: 'container',
- viewModel: childViewModel,
- cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
- width: fieldWidth,
- layout: {
- type: 'vbox',
- align: 'stretch'
- },
- items: [
- {
- xtype: 'colorpickerslidervalue',
- reference: 'valueSlider',
- flex: 1,
- bind: {
- value: '{value}',
- hue: '{selectedColor.h}'
- },
- width: fieldWidth,
- listeners: {
- handledrag: 'onValueSliderHandleDrag'
- }
- },
- {
- xtype: 'numberfield',
- fieldLabel: 'V',
- labelAlign: 'top',
- labelSeparator: '',
- bind: '{value}',
- hideTrigger: true,
- maxValue: 100,
- minValue: 0,
- allowBlank: false,
- margin: 0
- }
- ]
- };
- },
- // Splits up view declaration for readability
- // Slider and A field
- getSliderAndAField: function(childViewModel) {
- var me = this,
- fieldWidth = me.fieldWidth;
- return {
- xtype: 'container',
- viewModel: childViewModel,
- cls: Ext.baseCSSPrefix + 'colorpicker-escape-overflow',
- width: fieldWidth,
- layout: {
- type: 'vbox',
- align: 'stretch'
- },
- margin: {
- left: me.fieldPad
- },
- items: [
- {
- xtype: 'colorpickerslideralpha',
- reference: 'alphaSlider',
- flex: 1,
- bind: {
- alpha: '{alpha}',
- color: {
- bindTo: '{selectedColor}',
- deep: true
- }
- },
- width: fieldWidth,
- listeners: {
- handledrag: 'onAlphaSliderHandleDrag'
- }
- },
- {
- xtype: 'numberfield',
- fieldLabel: 'A',
- labelAlign: 'top',
- labelSeparator: '',
- bind: '{alpha}',
- hideTrigger: true,
- maxValue: 100,
- minValue: 0,
- allowBlank: false,
- margin: 0
- }
- ]
- };
- },
- // Splits up view declaration for readability
- // Preview current/previous color squares and OK and Cancel buttons
- getPreviewAndButtons: function(childViewModel, config) {
- // selected color preview is always shown
- var items = [
- {
- xtype: 'colorpickercolorpreview',
- flex: 1,
- bind: {
- color: {
- bindTo: '{selectedColor}',
- deep: true
- }
- }
- }
- ];
- // previous color preview is optional
- if (config.showPreviousColor) {
- items.push({
- xtype: 'colorpickercolorpreview',
- flex: 1,
- bind: {
- color: {
- bindTo: '{previousColor}',
- deep: true
- }
- },
- listeners: {
- click: 'onPreviousColorSelected'
- }
- });
- }
- // Ok/Cancel buttons are optional
- if (config.showOkCancelButtons) {
- items.push({
- xtype: 'button',
- text: 'OK',
- margin: '10 0 0 0',
- padding: '10 0 10 0',
- handler: 'onOK'
- }, {
- xtype: 'button',
- text: 'Cancel',
- margin: '10 0 0 0',
- padding: '10 0 10 0',
- handler: 'onCancel'
- });
- }
- return {
- xtype: 'container',
- viewModel: childViewModel,
- width: 70,
- margin: '0 0 0 10',
- items: items,
- layout: {
- type: 'vbox',
- align: 'stretch'
- }
- };
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.ux.colorpick.ButtonController', {
- extend: 'Ext.app.ViewController',
- alias: 'controller.colorpick-buttoncontroller',
- requires: [
- 'Ext.window.Window',
- 'Ext.layout.container.Fit',
- 'Ext.ux.colorpick.Selector',
- 'Ext.ux.colorpick.ColorUtils'
- ],
- afterRender: function(view) {
- view.updateColor(view.getColor());
- },
- destroy: function() {
- var view = this.getView(),
- colorPickerWindow = view.colorPickerWindow;
- if (colorPickerWindow) {
- colorPickerWindow.destroy();
- view.colorPickerWindow = view.colorPicker = null;
- }
- this.callParent();
- },
- getPopup: function() {
- var view = this.getView(),
- popup = view.colorPickerWindow,
- selector;
- if (!popup) {
- popup = Ext.create(view.getPopup());
- view.colorPickerWindow = popup;
- popup.colorPicker = view.colorPicker = selector = popup.lookupReference('selector');
- selector.setFormat(view.getFormat());
- selector.on({
- ok: 'onColorPickerOK',
- cancel: 'onColorPickerCancel',
- scope: this
- });
- popup.on({
- close: 'onColorPickerCancel',
- scope: this
- });
- }
- return popup;
- },
- // When button is clicked show the color picker window
- onClick: function() {
- var me = this,
- view = me.getView(),
- color = view.getColor(),
- popup = me.getPopup(),
- colorPicker = popup.colorPicker;
- colorPicker.setColor(color);
- colorPicker.setPreviousColor(color);
- popup.showBy(view, 'tl-br?');
- },
- onColorPickerOK: function(picker) {
- var view = this.getView(),
- color = picker.getColor(),
- cpWin = view.colorPickerWindow;
- cpWin.hide();
- view.setColor(color);
- },
- onColorPickerCancel: function() {
- var view = this.getView(),
- cpWin = view.colorPickerWindow;
- cpWin.hide();
- },
- syncColor: function(color) {
- var view = this.getView();
- Ext.ux.colorpick.ColorUtils.setBackground(view.filterEl, color);
- }
- });
- /**
- * A simple color swatch that can be clicked to bring up the color selector.
- *
- * The selected color is configurable via {@link #value}.
- *
- * @example
- * Ext.create('Ext.ux.colorpick.Button', {
- * value: '993300', // initial selected color
- * renderTo: Ext.getBody(),
- *
- * listeners: {
- * select: function(picker, selColor) {
- * Ext.Msg.alert('Color', selColor);
- * }
- * }
- * });
- */
- Ext.define('Ext.ux.colorpick.Button', {
- extend: 'Ext.Component',
- xtype: 'colorbutton',
- controller: 'colorpick-buttoncontroller',
- mixins: [
- 'Ext.ux.colorpick.Selection'
- ],
- requires: [
- 'Ext.ux.colorpick.ButtonController'
- ],
- baseCls: Ext.baseCSSPrefix + 'colorpicker-button',
- width: 20,
- height: 20,
- childEls: [
- 'btnEl',
- 'filterEl'
- ],
- config: {
- /**
- * @cfg {Object} popup
- * This object configures the popup window and colorselector component displayed
- * when this button is clicked. Applications should not need to configure this.
- * @private
- */
- popup: {
- lazy: true,
- $value: {
- xtype: 'window',
- closeAction: 'hide',
- referenceHolder: true,
- minWidth: 540,
- minHeight: 200,
- layout: 'fit',
- header: false,
- resizable: true,
- items: {
- xtype: 'colorselector',
- reference: 'selector',
- showPreviousColor: true,
- showOkCancelButtons: true
- }
- }
- }
- },
- defaultBindProperty: 'value',
- twoWayBindable: 'value',
- // Solve issue with IE, when applying a filter the click listener is not being fired.
- 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>',
- listeners: {
- click: 'onClick',
- element: 'btnEl'
- },
- /**
- * @event change
- * Fires when a color is selected.
- * @param {Ext.ux.colorpick.Selector} this
- * @param {String} color The value of the selected color as per specified {@link #format}.
- * @param {String} previousColor The previous color value.
- */
- updateColor: function(color) {
- var me = this,
- cp = me.colorPicker;
- me.mixins.colorselection.updateColor.call(me, color);
- Ext.ux.colorpick.ColorUtils.setBackground(me.filterEl, color);
- if (cp) {
- cp.setColor(color);
- }
- },
- // Sets this.format and color picker's setFormat()
- updateFormat: function(format) {
- var cp = this.colorPicker;
- if (cp) {
- cp.setFormat(format);
- }
- }
- });
- /**
- * A field that can be clicked to bring up the color picker.
- * The selected color is configurable via {@link #value}.
- *
- * @example
- * Ext.create({
- * xtype: 'colorfield',
- * renderTo: Ext.getBody(),
- *
- * value: '#993300', // initial selected color
- *
- * listeners : {
- * change: function (field, color) {
- * console.log('New color: ' + color);
- * }
- * }
- * });
- */
- Ext.define('Ext.ux.colorpick.Field', {
- extend: 'Ext.form.field.Picker',
- xtype: 'colorfield',
- mixins: [
- 'Ext.ux.colorpick.Selection'
- ],
- requires: [
- 'Ext.window.Window',
- 'Ext.ux.colorpick.Selector',
- 'Ext.ux.colorpick.ColorUtils',
- 'Ext.layout.container.Fit'
- ],
- editable: false,
- matchFieldWidth: false,
- // picker is usually wider than field
- // "Color Swatch" shown on the left of the field
- beforeBodyEl: [
- '<div class="' + Ext.baseCSSPrefix + 'colorpicker-field-swatch">' + '<div id="{id}-swatchEl" data-ref="swatchEl" class="' + Ext.baseCSSPrefix + 'colorpicker-field-swatch-inner"></div>' + '</div>'
- ],
- cls: Ext.baseCSSPrefix + 'colorpicker-field',
- childEls: [
- 'swatchEl'
- ],
- checkChangeEvents: [
- 'change'
- ],
- config: {
- /**
- * @cfg {Object} popup
- * This object configures the popup window and colorselector component displayed
- * when this button is clicked. Applications should not need to configure this.
- * @private
- */
- popup: {
- lazy: true,
- $value: {
- xtype: 'window',
- closeAction: 'hide',
- referenceHolder: true,
- minWidth: 540,
- minHeight: 200,
- layout: 'fit',
- header: false,
- resizable: true,
- items: {
- xtype: 'colorselector',
- reference: 'selector',
- showPreviousColor: true,
- showOkCancelButtons: true
- }
- }
- }
- },
- /**
- * @event change
- * Fires when a color is selected or if the field value is updated (if {@link #editable}).
- * @param {Ext.ux.colorpick.Field} this
- * @param {String} color The value of the selected color as per specified {@link #format}.
- * @param {String} previousColor The previous color value.
- */
- initComponent: function() {
- var me = this;
- me.callParent();
- me.on('change', me.onHexChange);
- },
- // NOTE: Since much of the logic of a picker class is overriding methods from the
- // base class, we don't bother to split out the small remainder as a controller.
- afterRender: function() {
- this.callParent();
- this.updateValue(this.value);
- },
- // override as required by parent pickerfield
- createPicker: function() {
- var me = this,
- popup = me.getPopup(),
- picker;
- // the window will actually be shown and will house the picker
- me.colorPickerWindow = popup = Ext.create(popup);
- me.colorPicker = picker = popup.lookupReference('selector');
- picker.setFormat(me.getFormat());
- picker.setColor(me.getColor());
- picker.setHexReadOnly(!me.editable);
- picker.on({
- ok: 'onColorPickerOK',
- cancel: 'onColorPickerCancel',
- scope: me
- });
- popup.on({
- close: 'onColorPickerCancel',
- scope: me
- });
- return me.colorPickerWindow;
- },
- // When the Ok button is clicked on color picker, preserve the previous value
- onColorPickerOK: function(colorPicker) {
- this.setColor(colorPicker.getColor());
- this.collapse();
- },
- onColorPickerCancel: function() {
- this.collapse();
- },
- onExpand: function() {
- var color = this.getColor();
- this.colorPicker.setPreviousColor(color);
- },
- onHexChange: function(field) {
- if (field.validate()) {
- this.setValue(field.getValue());
- }
- },
- // Expects value formatted as per "format" config
- setValue: function(color) {
- var me = this;
- if (Ext.ux.colorpick.ColorUtils.isValid(color)) {
- color = me.applyValue(color);
- me.callParent([
- color
- ]);
- // always update in case opacity changes, even if value doesn't have it
- // to handle "hex6" non-opacity type of format
- me.updateValue(color);
- }
- },
- // Sets this.format and color picker's setFormat()
- updateFormat: function(format) {
- var cp = this.colorPicker;
- if (cp) {
- cp.setFormat(format);
- }
- },
- updateValue: function(color) {
- var me = this,
- c;
- // If the "value" is changed, update "color" as well. Since these are always
- // tracking each other, we guard against the case where we are being updated
- // *because* "color" is being set.
- if (!me.syncing) {
- me.syncing = true;
- me.setColor(color);
- me.syncing = false;
- }
- c = me.getColor();
- if (c) {
- Ext.ux.colorpick.ColorUtils.setBackground(me.swatchEl, c);
- if (me.colorPicker) {
- me.colorPicker.setColor(c);
- }
- }
- },
- validator: function(val) {
- if (!Ext.ux.colorpick.ColorUtils.isValid(val)) {
- return this.invalidText;
- }
- return true;
- }
- });
- /**
- * Paging Memory Proxy, allows to use paging grid with in memory dataset
- */
- Ext.define('Ext.ux.data.PagingMemoryProxy', {
- extend: 'Ext.data.proxy.Memory',
- alias: 'proxy.pagingmemory',
- alternateClassName: 'Ext.data.PagingMemoryProxy',
- constructor: function() {
- Ext.log.warn('Ext.ux.data.PagingMemoryProxy functionality has been merged into Ext.data.proxy.Memory by using the enablePaging flag.');
- this.callParent(arguments);
- },
- read: function(operation, callback, scope) {
- var reader = this.getReader(),
- result = reader.read(this.data),
- sorters, filters, sorterFn, records;
- scope = scope || this;
- // filtering
- filters = operation.filters;
- if (filters.length > 0) {
- //at this point we have an array of Ext.util.Filter objects to filter with,
- //so here we construct a function that combines these filters by ANDing them together
- records = [];
- Ext.each(result.records, function(record) {
- var isMatch = true,
- length = filters.length,
- i;
- for (i = 0; i < length; i++) {
- var filter = filters[i],
- fn = filter.filterFn,
- scope = filter.scope;
- isMatch = isMatch && fn.call(scope, record);
- }
- if (isMatch) {
- records.push(record);
- }
- }, this);
- result.records = records;
- result.totalRecords = result.total = records.length;
- }
- // sorting
- sorters = operation.sorters;
- if (sorters.length > 0) {
- //construct an amalgamated sorter function which combines all of the Sorters passed
- sorterFn = function(r1, r2) {
- var result = sorters[0].sort(r1, r2),
- length = sorters.length,
- i;
- //if we have more than one sorter, OR any additional sorter functions together
- for (i = 1; i < length; i++) {
- result = result || sorters[i].sort.call(this, r1, r2);
- }
- return result;
- };
- result.records.sort(sorterFn);
- }
- // paging (use undefined cause start can also be 0 (thus false))
- if (operation.start !== undefined && operation.limit !== undefined) {
- result.records = result.records.slice(operation.start, operation.start + operation.limit);
- result.count = result.records.length;
- }
- Ext.apply(operation, {
- resultSet: result
- });
- operation.setCompleted();
- operation.setSuccessful();
- Ext.defer(function() {
- Ext.callback(callback, scope, [
- operation
- ]);
- }, 10);
- }
- });
- /**
- * This class is used as a grid `plugin`. It provides a DropZone which cooperates with
- * DragZones whose dragData contains a "field" property representing a form Field.
- * Fields may be dropped onto grid data cells containing a matching data type.
- */
- Ext.define('Ext.ux.dd.CellFieldDropZone', {
- extend: 'Ext.dd.DropZone',
- alias: 'plugin.ux-cellfielddropzone',
- containerScroll: true,
- /**
- * @cfg {Function/String} onCellDrop
- * The function to call on a cell data drop, or the name of the function on the
- * corresponding `{@link Ext.app.ViewController controller}`. For details on the
- * parameters, see `{@link #method!onCellDrop onCellDrop}`.
- */
- /**
- * This method is called when a field is dropped on a cell. This method is normally
- * replaced by the `{@link #cfg!onCellDrop onCellDrop}` config property passed to the
- * constructor.
- * @param {String} fieldName The name of the field.
- * @param {Mixed} value The value of the field.
- * @method onCellDrop
- */
- onCellDrop: Ext.emptyFn,
- constructor: function(cfg) {
- if (cfg) {
- var me = this,
- ddGroup = cfg.ddGroup,
- onCellDrop = cfg.onCellDrop;
- if (onCellDrop) {
- if (typeof onCellDrop === 'string') {
- me.onCellDropFn = onCellDrop;
- me.onCellDrop = me.callCellDrop;
- } else {
- me.onCellDrop = onCellDrop;
- }
- }
- if (ddGroup) {
- me.ddGroup = ddGroup;
- }
- }
- },
- init: function(grid) {
- var me = this;
- // Call the DropZone constructor using the View's scrolling element
- // only after the grid has been rendered.
- if (grid.rendered) {
- me.grid = grid;
- grid.getView().on({
- render: function(v) {
- me.view = v;
- Ext.ux.dd.CellFieldDropZone.superclass.constructor.call(me, me.view.el);
- },
- single: true
- });
- } else {
- grid.on('render', me.init, me, {
- single: true
- });
- }
- },
- getTargetFromEvent: function(e) {
- var me = this,
- v = me.view;
- // Ascertain whether the mousemove is within a grid cell
- var cell = e.getTarget(v.getCellSelector());
- if (cell) {
- // We *are* within a grid cell, so ask the View exactly which one,
- // Extract data from the Model to create a target object for
- // processing in subsequent onNodeXXXX methods. Note that the target does
- // not have to be a DOM element. It can be whatever the noNodeXXX methods are
- // programmed to expect.
- var row = v.findItemByChild(cell),
- columnIndex = cell.cellIndex;
- if (row && Ext.isDefined(columnIndex)) {
- return {
- node: cell,
- record: v.getRecord(row),
- fieldName: me.grid.getVisibleColumnManager().getColumns()[columnIndex].dataIndex
- };
- }
- }
- },
- onNodeEnter: function(target, dd, e, dragData) {
- // On Node enter, see if it is valid for us to drop the field on that type of
- // column.
- delete this.dropOK;
- if (!target) {
- return;
- }
- // Check that a field is being dragged.
- var f = dragData.field;
- if (!f) {
- return;
- }
- // Check whether the data type of the column being dropped on accepts the
- // dragged field type. If so, set dropOK flag, and highlight the target node.
- var field = target.record.fieldsMap[target.fieldName];
- if (field.isNumeric) {
- if (!f.isXType('numberfield')) {
- return;
- }
- } else if (field.isDateField) {
- if (!f.isXType('datefield')) {
- return;
- }
- } else if (field.isBooleanField) {
- if (!f.isXType('checkbox')) {
- return;
- }
- }
- this.dropOK = true;
- Ext.fly(target.node).addCls('x-drop-target-active');
- },
- onNodeOver: function(target, dd, e, dragData) {
- // Return the class name to add to the drag proxy. This provides a visual
- // indication of drop allowed or not allowed.
- return this.dropOK ? this.dropAllowed : this.dropNotAllowed;
- },
- onNodeOut: function(target, dd, e, dragData) {
- Ext.fly(target.node).removeCls('x-drop-target-active');
- },
- onNodeDrop: function(target, dd, e, dragData) {
- // Process the drop event if we have previously ascertained that a drop is OK.
- if (this.dropOK) {
- var value = dragData.field.getValue();
- target.record.set(target.fieldName, value);
- this.onCellDrop(target.fieldName, value);
- return true;
- }
- },
- callCellDrop: function(fieldName, value) {
- Ext.callback(this.onCellDropFn, null, [
- fieldName,
- value
- ], 0, this.grid);
- }
- });
- Ext.define('Ext.ux.dd.PanelFieldDragZone', {
- extend: 'Ext.dd.DragZone',
- alias: 'plugin.ux-panelfielddragzone',
- scroll: false,
- constructor: function(cfg) {
- if (cfg) {
- if (cfg.ddGroup) {
- this.ddGroup = cfg.ddGroup;
- }
- }
- },
- init: function(panel) {
- var el;
- // Call the DragZone's constructor. The Panel must have been rendered.
- // Panel is an HtmlElement
- if (panel.nodeType) {
- // Called via dragzone::init
- Ext.ux.dd.PanelFieldDragZone.superclass.init.apply(this, arguments);
- } else // Panel is a Component - need the el
- {
- // Called via plugin::init
- if (panel.rendered) {
- el = panel.getEl();
- el.unselectable();
- Ext.ux.dd.PanelFieldDragZone.superclass.constructor.call(this, el);
- } else {
- panel.on('afterrender', this.init, this, {
- single: true
- });
- }
- }
- },
- getDragData: function(e) {
- // On mousedown, we ascertain whether it is on one of our draggable Fields.
- // If so, we collect data about the draggable object, and return a drag data
- // object which contains our own data, plus a "ddel" property which is a DOM
- // node which provides a "view" of the dragged data.
- var targetLabel = e.getTarget('label', null, true),
- text, oldMark, field, dragEl;
- if (targetLabel) {
- // Get the data we are dragging: the Field
- // create a ddel for the drag proxy to display
- field = Ext.getCmp(targetLabel.up('.' + Ext.form.Labelable.prototype.formItemCls).id);
- // Temporary prevent marking the field as invalid, since it causes changes
- // to the underlying dom element which can cause problems in IE
- oldMark = field.preventMark;
- field.preventMark = true;
- if (field.isValid()) {
- field.preventMark = oldMark;
- dragEl = document.createElement('div');
- dragEl.className = Ext.baseCSSPrefix + 'form-text';
- text = field.getRawValue();
- dragEl.innerHTML = Ext.isEmpty(text) ? ' ' : text;
- Ext.fly(dragEl).setWidth(field.getEl().getWidth());
- return {
- field: field,
- ddel: dragEl
- };
- }
- e.stopEvent();
- field.preventMark = oldMark;
- }
- },
- getRepairXY: function() {
- // The coordinates to slide the drag proxy back to on failed drop.
- return this.dragData.field.getEl().getXY();
- }
- });
- /*!
- * Ext JS Library
- * Copyright(c) 2006-2014 Sencha Inc.
- * licensing@sencha.com
- * http://www.sencha.com/license
- */
- /**
- * @class Ext.ux.desktop.Desktop
- * @extends Ext.panel.Panel
- * <p>This class manages the wallpaper, shortcuts and taskbar.</p>
- */
- Ext.define('Ext.ux.desktop.Desktop', {
- extend: 'Ext.panel.Panel',
- alias: 'widget.desktop',
- uses: [
- 'Ext.util.MixedCollection',
- 'Ext.menu.Menu',
- 'Ext.view.View',
- // dataview
- 'Ext.window.Window',
- 'Ext.ux.desktop.TaskBar',
- 'Ext.ux.desktop.Wallpaper'
- ],
- activeWindowCls: 'ux-desktop-active-win',
- inactiveWindowCls: 'ux-desktop-inactive-win',
- lastActiveWindow: null,
- border: false,
- html: ' ',
- layout: 'fit',
- xTickSize: 1,
- yTickSize: 1,
- app: null,
- /**
- * @cfg {Array/Ext.data.Store} shortcuts
- * The items to add to the DataView. This can be a {@link Ext.data.Store Store} or a
- * simple array. Items should minimally provide the fields in the
- * {@link Ext.ux.desktop.ShortcutModel Shortcut}.
- */
- shortcuts: null,
- /**
- * @cfg {String} shortcutItemSelector
- * This property is passed to the DataView for the desktop to select shortcut items.
- * If the {@link #shortcutTpl} is modified, this will probably need to be modified as
- * well.
- */
- shortcutItemSelector: 'div.ux-desktop-shortcut',
- /**
- * @cfg {String} shortcutTpl
- * This XTemplate is used to render items in the DataView. If this is changed, the
- * {@link #shortcutItemSelector} will probably also need to changed.
- */
- shortcutTpl: [
- '<tpl for=".">',
- '<div class="ux-desktop-shortcut" id="{name}-shortcut">',
- '<div class="ux-desktop-shortcut-icon {iconCls}">',
- '<img src="',
- Ext.BLANK_IMAGE_URL,
- '" title="{name}">',
- '</div>',
- '<span class="ux-desktop-shortcut-text">{name}</span>',
- '</div>',
- '</tpl>',
- '<div class="x-clear"></div>'
- ],
- /**
- * @cfg {Object} taskbarConfig
- * The config object for the TaskBar.
- */
- taskbarConfig: null,
- windowMenu: null,
- initComponent: function() {
- var me = this;
- me.windowMenu = new Ext.menu.Menu(me.createWindowMenu());
- me.bbar = me.taskbar = new Ext.ux.desktop.TaskBar(me.taskbarConfig);
- me.taskbar.windowMenu = me.windowMenu;
- me.windows = new Ext.util.MixedCollection();
- me.contextMenu = new Ext.menu.Menu(me.createDesktopMenu());
- me.items = [
- {
- xtype: 'wallpaper',
- id: me.id + '_wallpaper'
- },
- me.createDataView()
- ];
- me.callParent();
- me.shortcutsView = me.items.getAt(1);
- me.shortcutsView.on('itemclick', me.onShortcutItemClick, me);
- var wallpaper = me.wallpaper;
- me.wallpaper = me.items.getAt(0);
- if (wallpaper) {
- me.setWallpaper(wallpaper, me.wallpaperStretch);
- }
- },
- afterRender: function() {
- var me = this;
- me.callParent();
- me.el.on('contextmenu', me.onDesktopMenu, me);
- },
- //------------------------------------------------------
- // Overrideable configuration creation methods
- createDataView: function() {
- var me = this;
- return {
- xtype: 'dataview',
- overItemCls: 'x-view-over',
- trackOver: true,
- itemSelector: me.shortcutItemSelector,
- store: me.shortcuts,
- style: {
- position: 'absolute'
- },
- x: 0,
- y: 0,
- tpl: new Ext.XTemplate(me.shortcutTpl)
- };
- },
- createDesktopMenu: function() {
- var me = this,
- ret = {
- items: me.contextMenuItems || []
- };
- if (ret.items.length) {
- ret.items.push('-');
- }
- ret.items.push({
- text: 'Tile',
- handler: me.tileWindows,
- scope: me,
- minWindows: 1
- }, {
- text: 'Cascade',
- handler: me.cascadeWindows,
- scope: me,
- minWindows: 1
- });
- return ret;
- },
- createWindowMenu: function() {
- var me = this;
- return {
- defaultAlign: 'br-tr',
- items: [
- {
- text: 'Restore',
- handler: me.onWindowMenuRestore,
- scope: me
- },
- {
- text: 'Minimize',
- handler: me.onWindowMenuMinimize,
- scope: me
- },
- {
- text: 'Maximize',
- handler: me.onWindowMenuMaximize,
- scope: me
- },
- '-',
- {
- text: 'Close',
- handler: me.onWindowMenuClose,
- scope: me
- }
- ],
- listeners: {
- beforeshow: me.onWindowMenuBeforeShow,
- hide: me.onWindowMenuHide,
- scope: me
- }
- };
- },
- //------------------------------------------------------
- // Event handler methods
- onDesktopMenu: function(e) {
- var me = this,
- menu = me.contextMenu;
- e.stopEvent();
- if (!menu.rendered) {
- menu.on('beforeshow', me.onDesktopMenuBeforeShow, me);
- }
- menu.showAt(e.getXY());
- menu.doConstrain();
- },
- onDesktopMenuBeforeShow: function(menu) {
- var me = this,
- count = me.windows.getCount();
- menu.items.each(function(item) {
- var min = item.minWindows || 0;
- item.setDisabled(count < min);
- });
- },
- onShortcutItemClick: function(dataView, record) {
- var me = this,
- module = me.app.getModule(record.data.module),
- win = module && module.createWindow();
- if (win) {
- me.restoreWindow(win);
- }
- },
- onWindowClose: function(win) {
- var me = this;
- me.windows.remove(win);
- me.taskbar.removeTaskButton(win.taskButton);
- me.updateActiveWindow();
- },
- //------------------------------------------------------
- // Window context menu handlers
- onWindowMenuBeforeShow: function(menu) {
- var items = menu.items.items,
- win = menu.theWin;
- items[0].setDisabled(win.maximized !== true && win.hidden !== true);
- // Restore
- items[1].setDisabled(win.minimized === true);
- // Minimize
- items[2].setDisabled(win.maximized === true || win.hidden === true);
- },
- // Maximize
- onWindowMenuClose: function() {
- var me = this,
- win = me.windowMenu.theWin;
- win.close();
- },
- onWindowMenuHide: function(menu) {
- Ext.defer(function() {
- menu.theWin = null;
- }, 1);
- },
- onWindowMenuMaximize: function() {
- var me = this,
- win = me.windowMenu.theWin;
- win.maximize();
- win.toFront();
- },
- onWindowMenuMinimize: function() {
- var me = this,
- win = me.windowMenu.theWin;
- win.minimize();
- },
- onWindowMenuRestore: function() {
- var me = this,
- win = me.windowMenu.theWin;
- me.restoreWindow(win);
- },
- //------------------------------------------------------
- // Dynamic (re)configuration methods
- getWallpaper: function() {
- return this.wallpaper.wallpaper;
- },
- setTickSize: function(xTickSize, yTickSize) {
- var me = this,
- xt = me.xTickSize = xTickSize,
- yt = me.yTickSize = (arguments.length > 1) ? yTickSize : xt;
- me.windows.each(function(win) {
- var dd = win.dd,
- resizer = win.resizer;
- dd.xTickSize = xt;
- dd.yTickSize = yt;
- resizer.widthIncrement = xt;
- resizer.heightIncrement = yt;
- });
- },
- setWallpaper: function(wallpaper, stretch) {
- this.wallpaper.setWallpaper(wallpaper, stretch);
- return this;
- },
- //------------------------------------------------------
- // Window management methods
- cascadeWindows: function() {
- var x = 0,
- y = 0,
- zmgr = this.getDesktopZIndexManager();
- zmgr.eachBottomUp(function(win) {
- if (win.isWindow && win.isVisible() && !win.maximized) {
- win.setPosition(x, y);
- x += 20;
- y += 20;
- }
- });
- },
- createWindow: function(config, cls) {
- var me = this,
- win,
- cfg = Ext.applyIf(config || {}, {
- stateful: false,
- isWindow: true,
- constrainHeader: true,
- minimizable: true,
- maximizable: true
- });
- cls = cls || Ext.window.Window;
- win = me.add(new cls(cfg));
- me.windows.add(win);
- win.taskButton = me.taskbar.addTaskButton(win);
- win.animateTarget = win.taskButton.el;
- win.on({
- activate: me.updateActiveWindow,
- beforeshow: me.updateActiveWindow,
- deactivate: me.updateActiveWindow,
- minimize: me.minimizeWindow,
- destroy: me.onWindowClose,
- scope: me
- });
- win.on({
- boxready: function() {
- win.dd.xTickSize = me.xTickSize;
- win.dd.yTickSize = me.yTickSize;
- if (win.resizer) {
- win.resizer.widthIncrement = me.xTickSize;
- win.resizer.heightIncrement = me.yTickSize;
- }
- },
- single: true
- });
- // replace normal window close w/fadeOut animation:
- win.doClose = function() {
- win.doClose = Ext.emptyFn;
- // dblclick can call again...
- win.el.disableShadow();
- win.el.fadeOut({
- listeners: {
- afteranimate: function() {
- win.destroy();
- }
- }
- });
- };
- return win;
- },
- getActiveWindow: function() {
- var win = null,
- zmgr = this.getDesktopZIndexManager();
- if (zmgr) {
- // We cannot rely on activate/deactive because that fires against non-Window
- // components in the stack.
- zmgr.eachTopDown(function(comp) {
- if (comp.isWindow && !comp.hidden) {
- win = comp;
- return false;
- }
- return true;
- });
- }
- return win;
- },
- getDesktopZIndexManager: function() {
- var windows = this.windows;
- // TODO - there has to be a better way to get this...
- return (windows.getCount() && windows.getAt(0).zIndexManager) || null;
- },
- getWindow: function(id) {
- return this.windows.get(id);
- },
- minimizeWindow: function(win) {
- win.minimized = true;
- win.hide();
- },
- restoreWindow: function(win) {
- if (win.isVisible()) {
- win.restore();
- win.toFront();
- } else {
- win.show();
- }
- return win;
- },
- tileWindows: function() {
- var me = this,
- availWidth = me.body.getWidth(true);
- var x = me.xTickSize,
- y = me.yTickSize,
- nextY = y;
- me.windows.each(function(win) {
- if (win.isVisible() && !win.maximized) {
- var w = win.el.getWidth();
- // Wrap to next row if we are not at the line start and this Window will
- // go off the end
- if (x > me.xTickSize && x + w > availWidth) {
- x = me.xTickSize;
- y = nextY;
- }
- win.setPosition(x, y);
- x += w + me.xTickSize;
- nextY = Math.max(nextY, y + win.el.getHeight() + me.yTickSize);
- }
- });
- },
- updateActiveWindow: function() {
- var me = this,
- activeWindow = me.getActiveWindow(),
- last = me.lastActiveWindow;
- if (last && last.destroyed) {
- me.lastActiveWindow = null;
- return;
- }
- if (activeWindow === last) {
- return;
- }
- if (last) {
- if (last.el.dom) {
- last.addCls(me.inactiveWindowCls);
- last.removeCls(me.activeWindowCls);
- }
- last.active = false;
- }
- me.lastActiveWindow = activeWindow;
- if (activeWindow) {
- activeWindow.addCls(me.activeWindowCls);
- activeWindow.removeCls(me.inactiveWindowCls);
- activeWindow.minimized = false;
- activeWindow.active = true;
- }
- me.taskbar.setActiveButton(activeWindow && activeWindow.taskButton);
- }
- });
- /**
- * Ext JS Library
- * Copyright(c) 2006-2014 Sencha Inc.
- * licensing@sencha.com
- * http://www.sencha.com/license
- * @class Ext.ux.desktop.App
- */
- Ext.define('Ext.ux.desktop.App', {
- mixins: {
- observable: 'Ext.util.Observable'
- },
- requires: [
- 'Ext.container.Viewport',
- 'Ext.ux.desktop.Desktop'
- ],
- isReady: false,
- modules: null,
- useQuickTips: true,
- constructor: function(config) {
- var me = this;
- me.mixins.observable.constructor.call(this, config);
- if (Ext.isReady) {
- Ext.defer(me.init, 10, me);
- } else {
- Ext.onReady(me.init, me);
- }
- },
- init: function() {
- var me = this,
- desktopCfg;
- if (me.useQuickTips) {
- Ext.QuickTips.init();
- }
- me.modules = me.getModules();
- if (me.modules) {
- me.initModules(me.modules);
- }
- desktopCfg = me.getDesktopConfig();
- me.desktop = new Ext.ux.desktop.Desktop(desktopCfg);
- me.viewport = new Ext.container.Viewport({
- layout: 'fit',
- items: [
- me.desktop
- ]
- });
- Ext.getWin().on('beforeunload', me.onUnload, me);
- me.isReady = true;
- me.fireEvent('ready', me);
- },
- /**
- * This method returns the configuration object for the Desktop object. A derived
- * class can override this method, call the base version to build the config and
- * then modify the returned object before returning it.
- */
- getDesktopConfig: function() {
- var me = this,
- cfg = {
- app: me,
- taskbarConfig: me.getTaskbarConfig()
- };
- Ext.apply(cfg, me.desktopConfig);
- return cfg;
- },
- getModules: Ext.emptyFn,
- /**
- * This method returns the configuration object for the Start Button. A derived
- * class can override this method, call the base version to build the config and
- * then modify the returned object before returning it.
- */
- getStartConfig: function() {
- var me = this,
- cfg = {
- app: me,
- menu: []
- },
- launcher;
- Ext.apply(cfg, me.startConfig);
- Ext.each(me.modules, function(module) {
- launcher = module.launcher;
- if (launcher) {
- launcher.handler = launcher.handler || Ext.bind(me.createWindow, me, [
- module
- ]);
- cfg.menu.push(module.launcher);
- }
- });
- return cfg;
- },
- createWindow: function(module) {
- var window = module.createWindow();
- window.show();
- },
- /**
- * This method returns the configuration object for the TaskBar. A derived class
- * can override this method, call the base version to build the config and then
- * modify the returned object before returning it.
- */
- getTaskbarConfig: function() {
- var me = this,
- cfg = {
- app: me,
- startConfig: me.getStartConfig()
- };
- Ext.apply(cfg, me.taskbarConfig);
- return cfg;
- },
- initModules: function(modules) {
- var me = this;
- Ext.each(modules, function(module) {
- module.app = me;
- });
- },
- getModule: function(name) {
- var ms = this.modules;
- for (var i = 0,
- len = ms.length; i < len; i++) {
- var m = ms[i];
- if (m.id == name || m.appType == name) {
- return m;
- }
- }
- return null;
- },
- onReady: function(fn, scope) {
- if (this.isReady) {
- fn.call(scope, this);
- } else {
- this.on({
- ready: fn,
- scope: scope,
- single: true
- });
- }
- },
- getDesktop: function() {
- return this.desktop;
- },
- onUnload: function(e) {
- if (this.fireEvent('beforeunload', this) === false) {
- e.stopEvent();
- }
- }
- });
- /*!
- * Ext JS Library
- * Copyright(c) 2006-2014 Sencha Inc.
- * licensing@sencha.com
- * http://www.sencha.com/license
- */
- Ext.define('Ext.ux.desktop.Module', {
- mixins: {
- observable: 'Ext.util.Observable'
- },
- constructor: function(config) {
- this.mixins.observable.constructor.call(this, config);
- this.init();
- },
- init: Ext.emptyFn
- });
- /*!
- * Ext JS Library
- * Copyright(c) 2006-2014 Sencha Inc.
- * licensing@sencha.com
- * http://www.sencha.com/license
- */
- /**
- * @class Ext.ux.desktop.ShortcutModel
- * @extends Ext.data.Model
- * This model defines the minimal set of fields for desktop shortcuts.
- */
- Ext.define('Ext.ux.desktop.ShortcutModel', {
- extend: 'Ext.data.Model',
- fields: [
- {
- name: 'name',
- convert: Ext.String.createVarName
- },
- {
- name: 'iconCls'
- },
- {
- name: 'module'
- }
- ]
- });
- /**
- * Ext JS Library
- * Copyright(c) 2006-2014 Sencha Inc.
- * licensing@sencha.com
- * http://www.sencha.com/license
- * @class Ext.ux.desktop.StartMenu
- */
- Ext.define('Ext.ux.desktop.StartMenu', {
- extend: 'Ext.menu.Menu',
- // We want header styling like a Panel
- baseCls: Ext.baseCSSPrefix + 'panel',
- // Special styling within
- cls: 'x-menu ux-start-menu',
- bodyCls: 'ux-start-menu-body',
- defaultAlign: 'bl-tl',
- iconCls: 'user',
- bodyBorder: true,
- width: 300,
- initComponent: function() {
- var me = this;
- me.layout.align = 'stretch';
- me.items = me.menu;
- me.callParent();
- me.toolbar = new Ext.toolbar.Toolbar(Ext.apply({
- dock: 'right',
- cls: 'ux-start-menu-toolbar',
- vertical: true,
- width: 100,
- layout: {
- align: 'stretch'
- }
- }, me.toolConfig));
- me.addDocked(me.toolbar);
- delete me.toolItems;
- },
- addMenuItem: function() {
- var cmp = this.menu;
- cmp.add.apply(cmp, arguments);
- },
- addToolItem: function() {
- var cmp = this.toolbar;
- cmp.add.apply(cmp, arguments);
- }
- });
- // StartMenu
- /*!
- * Ext JS Library
- * Copyright(c) 2006-2014 Sencha Inc.
- * licensing@sencha.com
- * http://www.sencha.com/license
- */
- /**
- * @class Ext.ux.desktop.TaskBar
- * @extends Ext.toolbar.Toolbar
- */
- Ext.define('Ext.ux.desktop.TaskBar', {
- // This must be a toolbar. we rely on acquired toolbar classes and inherited toolbar methods for our
- // child items to instantiate and render correctly.
- extend: 'Ext.toolbar.Toolbar',
- requires: [
- 'Ext.button.Button',
- 'Ext.resizer.Splitter',
- 'Ext.menu.Menu',
- 'Ext.ux.desktop.StartMenu'
- ],
- alias: 'widget.taskbar',
- cls: 'ux-taskbar',
- /**
- * @cfg {String} startBtnText
- * The text for the Start Button.
- */
- startBtnText: 'Start',
- initComponent: function() {
- var me = this;
- me.startMenu = new Ext.ux.desktop.StartMenu(me.startConfig);
- me.quickStart = new Ext.toolbar.Toolbar(me.getQuickStart());
- me.windowBar = new Ext.toolbar.Toolbar(me.getWindowBarConfig());
- me.tray = new Ext.toolbar.Toolbar(me.getTrayConfig());
- me.items = [
- {
- xtype: 'button',
- cls: 'ux-start-button',
- iconCls: 'ux-start-button-icon',
- menu: me.startMenu,
- menuAlign: 'bl-tl',
- text: me.startBtnText
- },
- me.quickStart,
- {
- xtype: 'splitter',
- html: ' ',
- height: 14,
- width: 2,
- // TODO - there should be a CSS way here
- cls: 'x-toolbar-separator x-toolbar-separator-horizontal'
- },
- me.windowBar,
- '-',
- me.tray
- ];
- me.callParent();
- },
- afterLayout: function() {
- var me = this;
- me.callParent();
- me.windowBar.el.on('contextmenu', me.onButtonContextMenu, me);
- },
- /**
- * This method returns the configuration object for the Quick Start toolbar. A derived
- * class can override this method, call the base version to build the config and
- * then modify the returned object before returning it.
- */
- getQuickStart: function() {
- var me = this,
- ret = {
- minWidth: 20,
- width: Ext.themeName === 'neptune' ? 70 : 60,
- items: [],
- enableOverflow: true
- };
- Ext.each(this.quickStart, function(item) {
- ret.items.push({
- tooltip: {
- text: item.name,
- align: 'bl-tl'
- },
- //tooltip: item.name,
- overflowText: item.name,
- iconCls: item.iconCls,
- module: item.module,
- handler: me.onQuickStartClick,
- scope: me
- });
- });
- return ret;
- },
- /**
- * This method returns the configuration object for the Tray toolbar. A derived
- * class can override this method, call the base version to build the config and
- * then modify the returned object before returning it.
- */
- getTrayConfig: function() {
- var ret = {
- items: this.trayItems
- };
- delete this.trayItems;
- return ret;
- },
- getWindowBarConfig: function() {
- return {
- flex: 1,
- cls: 'ux-desktop-windowbar',
- items: [
- ' '
- ],
- layout: {
- overflowHandler: 'Scroller'
- }
- };
- },
- getWindowBtnFromEl: function(el) {
- var c = this.windowBar.getChildByElement(el);
- return c || null;
- },
- onQuickStartClick: function(btn) {
- var module = this.app.getModule(btn.module),
- window;
- if (module) {
- window = module.createWindow();
- window.show();
- }
- },
- onButtonContextMenu: function(e) {
- var me = this,
- t = e.getTarget(),
- btn = me.getWindowBtnFromEl(t);
- if (btn) {
- e.stopEvent();
- me.windowMenu.theWin = btn.win;
- me.windowMenu.showBy(t);
- }
- },
- onWindowBtnClick: function(btn) {
- var win = btn.win;
- if (win.minimized || win.hidden) {
- btn.disable();
- win.show(null, function() {
- btn.enable();
- });
- } else if (win.active) {
- btn.disable();
- win.on('hide', function() {
- btn.enable();
- }, null, {
- single: true
- });
- win.minimize();
- } else {
- win.toFront();
- }
- },
- addTaskButton: function(win) {
- var config = {
- iconCls: win.iconCls,
- enableToggle: true,
- toggleGroup: 'all',
- width: 140,
- margin: '0 2 0 3',
- text: Ext.util.Format.ellipsis(win.title, 20),
- listeners: {
- click: this.onWindowBtnClick,
- scope: this
- },
- win: win
- };
- var cmp = this.windowBar.add(config);
- cmp.toggle(true);
- return cmp;
- },
- removeTaskButton: function(btn) {
- var found,
- me = this;
- me.windowBar.items.each(function(item) {
- if (item === btn) {
- found = item;
- }
- return !found;
- });
- if (found) {
- me.windowBar.remove(found);
- }
- return found;
- },
- setActiveButton: function(btn) {
- if (btn) {
- btn.toggle(true);
- } else {
- this.windowBar.items.each(function(item) {
- if (item.isButton) {
- item.toggle(false);
- }
- });
- }
- }
- });
- /**
- * @class Ext.ux.desktop.TrayClock
- * @extends Ext.toolbar.TextItem
- * This class displays a clock on the toolbar.
- */
- Ext.define('Ext.ux.desktop.TrayClock', {
- extend: 'Ext.toolbar.TextItem',
- alias: 'widget.trayclock',
- cls: 'ux-desktop-trayclock',
- html: ' ',
- timeFormat: 'g:i A',
- tpl: '{time}',
- initComponent: function() {
- var me = this;
- me.callParent();
- if (typeof (me.tpl) == 'string') {
- me.tpl = new Ext.XTemplate(me.tpl);
- }
- },
- afterRender: function() {
- var me = this;
- Ext.defer(me.updateTime, 100, me);
- me.callParent();
- },
- doDestroy: function() {
- var me = this;
- if (me.timer) {
- window.clearTimeout(me.timer);
- me.timer = null;
- }
- me.callParent();
- },
- updateTime: function() {
- var me = this,
- time = Ext.Date.format(new Date(), me.timeFormat),
- text = me.tpl.apply({
- time: time
- });
- if (me.lastText != text) {
- me.setText(text);
- me.lastText = text;
- }
- me.timer = Ext.defer(me.updateTime, 10000, me);
- }
- });
- /*!
- * Ext JS Library
- * Copyright(c) 2006-2015 Sencha Inc.
- * licensing@sencha.com
- * http://www.sencha.com/license
- */
- /**
- * From code originally written by David Davis
- *
- * For HTML5 video to work, your server must
- * send the right content type, for more info see:
- * <http://developer.mozilla.org/En/HTML/Element/Video>
- */
- Ext.define('Ext.ux.desktop.Video', {
- extend: 'Ext.panel.Panel',
- alias: 'widget.video',
- layout: 'fit',
- autoplay: false,
- controls: true,
- bodyStyle: 'background-color:#000;color:#fff',
- html: '',
- tpl: [
- '<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%">',
- '<tpl for="src">',
- '<source src="{src}" type="{type}"/>',
- '</tpl>',
- '{html}',
- '</video>'
- ],
- initComponent: function() {
- var me = this,
- fallback, size, cfg, el;
- if (me.fallbackHTML) {
- fallback = me.fallbackHTML;
- } else {
- fallback = "Your browser does not support HTML5 Video. ";
- if (Ext.isChrome) {
- fallback += 'Upgrade Chrome.';
- } else if (Ext.isGecko) {
- fallback += 'Upgrade to Firefox 3.5 or newer.';
- } else {
- var chrome = '<a href="http://www.google.com/chrome">Chrome</a>';
- fallback += 'Please try <a href="http://www.mozilla.com">Firefox</a>';
- if (Ext.isIE) {
- fallback += ', ' + chrome + ' or <a href="http://www.apple.com/safari/">Safari</a>.';
- } else {
- fallback += ' or ' + chrome + '.';
- }
- }
- }
- me.fallbackHTML = fallback;
- cfg = me.data = Ext.copyTo({
- tag: 'video',
- html: fallback
- }, me, 'id,poster,start,loopstart,loopend,playcount,autobuffer,loop');
- // just having the params exist enables them
- if (me.autoplay) {
- cfg.autoplay = 1;
- }
- if (me.controls) {
- cfg.controls = 1;
- }
- // handle multiple sources
- if (Ext.isArray(me.src)) {
- cfg.src = me.src;
- } else {
- cfg.src = [
- {
- src: me.src
- }
- ];
- }
- me.callParent();
- },
- afterRender: function() {
- var me = this;
- me.callParent();
- me.video = me.body.getById(me.id + '-video');
- el = me.video.dom;
- me.supported = (el && el.tagName.toLowerCase() == 'video');
- if (me.supported) {
- me.video.on('error', me.onVideoError, me);
- }
- },
- getFallback: function() {
- return '<h1 style="background-color:#ff4f4f;padding: 10px;">' + this.fallbackHTML + '</h1>';
- },
- onVideoError: function() {
- var me = this;
- me.video.remove();
- me.supported = false;
- me.body.createChild(me.getFallback());
- },
- doDestroy: function() {
- var me = this;
- var video = me.video;
- if (me.supported && video) {
- var videoDom = video.dom;
- if (videoDom && videoDom.pause) {
- videoDom.pause();
- }
- video.remove();
- me.video = null;
- }
- me.callParent();
- }
- });
- /*!
- * Ext JS Library
- * Copyright(c) 2006-2014 Sencha Inc.
- * licensing@sencha.com
- * http://www.sencha.com/license
- */
- /**
- * @class Ext.ux.desktop.Wallpaper
- * @extends Ext.Component
- * <p>This component renders an image that stretches to fill the component.</p>
- */
- Ext.define('Ext.ux.desktop.Wallpaper', {
- extend: 'Ext.Component',
- alias: 'widget.wallpaper',
- cls: 'ux-wallpaper',
- html: '<img src="' + Ext.BLANK_IMAGE_URL + '">',
- stretch: false,
- wallpaper: null,
- stateful: true,
- stateId: 'desk-wallpaper',
- afterRender: function() {
- var me = this;
- me.callParent();
- me.setWallpaper(me.wallpaper, me.stretch);
- },
- applyState: function() {
- var me = this,
- old = me.wallpaper;
- me.callParent(arguments);
- if (old != me.wallpaper) {
- me.setWallpaper(me.wallpaper);
- }
- },
- getState: function() {
- return this.wallpaper && {
- wallpaper: this.wallpaper
- };
- },
- setWallpaper: function(wallpaper, stretch) {
- var me = this,
- imgEl, bkgnd;
- me.stretch = (stretch !== false);
- me.wallpaper = wallpaper;
- if (me.rendered) {
- imgEl = me.el.dom.firstChild;
- if (!wallpaper || wallpaper == Ext.BLANK_IMAGE_URL) {
- Ext.fly(imgEl).hide();
- } else if (me.stretch) {
- imgEl.src = wallpaper;
- me.el.removeCls('ux-wallpaper-tiled');
- Ext.fly(imgEl).setStyle({
- width: '100%',
- height: '100%'
- }).show();
- } else {
- Ext.fly(imgEl).hide();
- bkgnd = 'url(' + wallpaper + ')';
- me.el.addCls('ux-wallpaper-tiled');
- }
- me.el.setStyle({
- backgroundImage: bkgnd || ''
- });
- if (me.stateful) {
- me.saveState();
- }
- }
- return me;
- }
- });
- /**
- * Recorder manager.
- * Used as a bookmarklet:
- *
- * javascript:void(window.open("../ux/event/RecorderManager.html","recmgr"))
- */
- Ext.define('Ext.ux.event.RecorderManager', {
- extend: 'Ext.panel.Panel',
- alias: 'widget.eventrecordermanager',
- uses: [
- 'Ext.ux.event.Recorder',
- 'Ext.ux.event.Player'
- ],
- layout: 'fit',
- buttonAlign: 'left',
- eventsToIgnore: {
- mousemove: 1,
- mouseover: 1,
- mouseout: 1
- },
- bodyBorder: false,
- playSpeed: 1,
- initComponent: function() {
- var me = this;
- me.recorder = new Ext.ux.event.Recorder({
- attachTo: me.attachTo,
- listeners: {
- add: me.updateEvents,
- coalesce: me.updateEvents,
- buffer: 200,
- scope: me
- }
- });
- me.recorder.eventsToRecord = Ext.apply({}, me.recorder.eventsToRecord);
- function speed(text, value) {
- return {
- text: text,
- speed: value,
- group: 'speed',
- checked: value == me.playSpeed,
- handler: me.onPlaySpeed,
- scope: me
- };
- }
- me.tbar = [
- {
- text: 'Record',
- xtype: 'splitbutton',
- whenIdle: true,
- handler: me.onRecord,
- scope: me,
- menu: me.makeRecordButtonMenu()
- },
- {
- text: 'Play',
- xtype: 'splitbutton',
- whenIdle: true,
- handler: me.onPlay,
- scope: me,
- menu: [
- speed('Qarter Speed (0.25x)', 0.25),
- speed('Half Speed (0.5x)', 0.5),
- speed('3/4 Speed (0.75x)', 0.75),
- '-',
- speed('Recorded Speed (1x)', 1),
- speed('Double Speed (2x)', 2),
- speed('Quad Speed (4x)', 4),
- '-',
- speed('Full Speed', 1000)
- ]
- },
- {
- text: 'Clear',
- whenIdle: true,
- handler: me.onClear,
- scope: me
- },
- '->',
- {
- text: 'Stop',
- whenActive: true,
- disabled: true,
- handler: me.onStop,
- scope: me
- }
- ];
- var events = me.attachTo && me.attachTo.testEvents;
- me.items = [
- {
- xtype: 'textarea',
- itemId: 'eventView',
- fieldStyle: 'font-family: monospace',
- selectOnFocus: true,
- emptyText: 'Events go here!',
- value: events ? me.stringifyEvents(events) : '',
- scrollToBottom: function() {
- var inputEl = this.inputEl.dom;
- inputEl.scrollTop = inputEl.scrollHeight;
- }
- }
- ];
- me.fbar = [
- {
- xtype: 'tbtext',
- text: 'Attached To: ' + (me.attachTo && me.attachTo.location.href)
- }
- ];
- me.callParent();
- },
- makeRecordButtonMenu: function() {
- var ret = [],
- subs = {},
- eventsToRec = this.recorder.eventsToRecord,
- ignoredEvents = this.eventsToIgnore;
- Ext.Object.each(eventsToRec, function(name, value) {
- var sub = subs[value.kind];
- if (!sub) {
- subs[value.kind] = sub = [];
- ret.push({
- text: value.kind,
- menu: sub
- });
- }
- sub.push({
- text: name,
- checked: true,
- handler: function(menuItem) {
- if (menuItem.checked) {
- eventsToRec[name] = value;
- } else {
- delete eventsToRec[name];
- }
- }
- });
- if (ignoredEvents[name]) {
- sub[sub.length - 1].checked = false;
- Ext.defer(function() {
- delete eventsToRec[name];
- }, 1);
- }
- });
- function less(lhs, rhs) {
- return (lhs.text < rhs.text) ? -1 : ((rhs.text < lhs.text) ? 1 : 0);
- }
- ret.sort(less);
- Ext.Array.each(ret, function(sub) {
- sub.menu.sort(less);
- });
- return ret;
- },
- getEventView: function() {
- return this.down('#eventView');
- },
- onClear: function() {
- var view = this.getEventView();
- view.setValue('');
- },
- onPlay: function() {
- var me = this,
- view = me.getEventView(),
- events = view.getValue();
- if (events) {
- events = Ext.decode(events);
- if (events.length) {
- me.player = Ext.create('Ext.ux.event.Player', {
- attachTo: window.opener,
- eventQueue: events,
- speed: me.playSpeed,
- listeners: {
- stop: me.onPlayStop,
- scope: me
- }
- });
- me.player.start();
- me.syncBtnUI();
- }
- }
- },
- onPlayStop: function() {
- this.player = null;
- this.syncBtnUI();
- },
- onPlaySpeed: function(menuitem) {
- this.playSpeed = menuitem.speed;
- },
- onRecord: function() {
- this.recorder.start();
- this.syncBtnUI();
- },
- onStop: function() {
- var me = this;
- if (me.player) {
- me.player.stop();
- me.player = null;
- } else {
- me.recorder.stop();
- }
- me.syncBtnUI();
- me.updateEvents();
- },
- syncBtnUI: function() {
- var me = this,
- idle = !me.player && !me.recorder.active;
- Ext.each(me.query('[whenIdle]'), function(btn) {
- btn.setDisabled(!idle);
- });
- Ext.each(me.query('[whenActive]'), function(btn) {
- btn.setDisabled(idle);
- });
- var view = me.getEventView();
- view.setReadOnly(!idle);
- },
- stringifyEvents: function(events) {
- var line,
- lines = [];
- Ext.each(events, function(ev) {
- line = [];
- Ext.Object.each(ev, function(name, value) {
- if (line.length) {
- line.push(', ');
- } else {
- line.push(' { ');
- }
- line.push(name, ': ');
- line.push(Ext.encode(value));
- });
- line.push(' }');
- lines.push(line.join(''));
- });
- return '[\n' + lines.join(',\n') + '\n]';
- },
- updateEvents: function() {
- var me = this,
- text = me.stringifyEvents(me.recorder.getRecordedEvents()),
- view = me.getEventView();
- view.setValue(text);
- view.scrollToBottom();
- }
- });
- /**
- * A control that allows selection of multiple items in a list.
- */
- Ext.define('Ext.ux.form.MultiSelect', {
- extend: 'Ext.form.FieldContainer',
- mixins: [
- 'Ext.util.StoreHolder',
- 'Ext.form.field.Field'
- ],
- alternateClassName: 'Ext.ux.Multiselect',
- alias: [
- 'widget.multiselectfield',
- 'widget.multiselect'
- ],
- requires: [
- 'Ext.panel.Panel',
- 'Ext.view.BoundList',
- 'Ext.layout.container.Fit'
- ],
- uses: [
- 'Ext.view.DragZone',
- 'Ext.view.DropZone'
- ],
- layout: 'anchor',
- /**
- * @cfg {String} [dragGroup=""] The ddgroup name for the MultiSelect DragZone.
- */
- /**
- * @cfg {String} [dropGroup=""] The ddgroup name for the MultiSelect DropZone.
- */
- /**
- * @cfg {String} [title=""] A title for the underlying panel.
- */
- /**
- * @cfg {Boolean} [ddReorder=false] Whether the items in the MultiSelect list are drag/drop reorderable.
- */
- ddReorder: false,
- /**
- * @cfg {Object/Array} tbar An optional toolbar to be inserted at the top of the control's selection list.
- * This can be a {@link Ext.toolbar.Toolbar} object, a toolbar config, or an array of buttons/button configs
- * to be added to the toolbar. See {@link Ext.panel.Panel#tbar}.
- */
- /**
- * @cfg {String} [appendOnly=false] `true` if the list should only allow append drops when drag/drop is enabled.
- * This is useful for lists which are sorted.
- */
- appendOnly: false,
- /**
- * @cfg {String} [displayField="text"] Name of the desired display field in the dataset.
- */
- displayField: 'text',
- /**
- * @cfg {String} [valueField="text"] Name of the desired value field in the dataset.
- */
- /**
- * @cfg {Boolean} [allowBlank=true] `false` to require at least one item in the list to be selected, `true` to allow no
- * selection.
- */
- allowBlank: true,
- /**
- * @cfg {Number} [minSelections=0] Minimum number of selections allowed.
- */
- minSelections: 0,
- /**
- * @cfg {Number} [maxSelections=Number.MAX_VALUE] Maximum number of selections allowed.
- */
- maxSelections: Number.MAX_VALUE,
- /**
- * @cfg {String} [blankText="This field is required"] Default text displayed when the control contains no items.
- */
- blankText: 'This field is required',
- /**
- * @cfg {String} [minSelectionsText="Minimum {0}item(s) required"]
- * Validation message displayed when {@link #minSelections} is not met.
- * The {0} token will be replaced by the value of {@link #minSelections}.
- */
- minSelectionsText: 'Minimum {0} item(s) required',
- /**
- * @cfg {String} [maxSelectionsText="Maximum {0}item(s) allowed"]
- * Validation message displayed when {@link #maxSelections} is not met
- * The {0} token will be replaced by the value of {@link #maxSelections}.
- */
- maxSelectionsText: 'Maximum {0} item(s) required',
- /**
- * @cfg {String} [delimiter=","] The string used to delimit the selected values when {@link #getSubmitValue submitting}
- * the field as part of a form. If you wish to have the selected values submitted as separate
- * parameters rather than a single delimited parameter, set this to `null`.
- */
- delimiter: ',',
- /**
- * @cfg {String} [dragText="{0} Item{1}"] The text to show while dragging items.
- * {0} will be replaced by the number of items. {1} will be replaced by the plural
- * form if there is more than 1 item.
- */
- dragText: '{0} Item{1}',
- /**
- * @cfg {Ext.data.Store/Array} store The data source to which this MultiSelect is bound (defaults to `undefined`).
- * Acceptable values for this property are:
- * <div class="mdetail-params"><ul>
- * <li><b>any {@link Ext.data.Store Store} subclass</b></li>
- * <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.ArrayStore} internally.
- * <div class="mdetail-params"><ul>
- * <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">
- * A 1-dimensional array will automatically be expanded (each array item will be the combo
- * {@link #valueField value} and {@link #displayField text})</div></li>
- * <li><b>2-dimensional array</b> : (e.g., <tt>[['f','Foo'],['b','Bar']]</tt>)<div class="sub-desc">
- * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
- * {@link #valueField value}, while the value at index 1 is assumed to be the combo {@link #displayField text}.
- * </div></li></ul></div></li></ul></div>
- */
- ignoreSelectChange: 0,
- /**
- * @cfg {Object} listConfig
- * An optional set of configuration properties that will be passed to the {@link Ext.view.BoundList}'s constructor.
- * Any configuration that is valid for BoundList can be included.
- */
- /**
- * @cfg {Number} [pageSize=10] The number of items to advance on pageUp and pageDown
- */
- pageSize: 10,
- initComponent: function() {
- var me = this;
- me.items = me.setupItems();
- me.bindStore(me.store, true);
- me.callParent();
- me.initField();
- },
- setupItems: function() {
- var me = this;
- me.boundList = new Ext.view.BoundList(Ext.apply({
- anchor: 'none 100%',
- border: 1,
- multiSelect: true,
- store: me.store,
- displayField: me.displayField,
- disabled: me.disabled,
- tabIndex: 0,
- navigationModel: {
- type: 'default'
- }
- }, me.listConfig));
- me.boundList.getNavigationModel().addKeyBindings({
- pageUp: me.onKeyPageUp,
- pageDown: me.onKeyPageDown,
- scope: me
- });
- me.boundList.getSelectionModel().on('selectionchange', me.onSelectChange, me);
- // Boundlist expects a reference to its pickerField for when an item is selected (see Boundlist#onItemClick).
- me.boundList.pickerField = me;
- // Only need to wrap the BoundList in a Panel if we have a title.
- if (!me.title) {
- return me.boundList;
- }
- // Wrap to add a title
- me.boundList.border = false;
- return {
- xtype: 'panel',
- isAriaRegion: false,
- border: true,
- anchor: 'none 100%',
- layout: 'anchor',
- title: me.title,
- tbar: me.tbar,
- items: me.boundList
- };
- },
- onSelectChange: function(selModel, selections) {
- if (!this.ignoreSelectChange) {
- this.setValue(selections);
- }
- },
- getSelected: function() {
- return this.boundList.getSelectionModel().getSelection();
- },
- // compare array values
- isEqual: function(v1, v2) {
- var fromArray = Ext.Array.from,
- i = 0,
- len;
- v1 = fromArray(v1);
- v2 = fromArray(v2);
- len = v1.length;
- if (len !== v2.length) {
- return false;
- }
- for (; i < len; i++) {
- if (v2[i] !== v1[i]) {
- return false;
- }
- }
- return true;
- },
- afterRender: function() {
- var me = this,
- boundList, scrollable, records, panel;
- me.callParent();
- boundList = me.boundList;
- scrollable = boundList && boundList.getScrollable();
- if (me.selectOnRender) {
- records = me.getRecordsForValue(me.value);
- if (records.length) {
- ++me.ignoreSelectChange;
- boundList.getSelectionModel().select(records);
- --me.ignoreSelectChange;
- }
- delete me.toSelect;
- }
- if (me.ddReorder && !me.dragGroup && !me.dropGroup) {
- me.dragGroup = me.dropGroup = 'MultiselectDD-' + Ext.id();
- }
- if (me.draggable || me.dragGroup) {
- me.dragZone = Ext.create('Ext.view.DragZone', {
- view: boundList,
- ddGroup: me.dragGroup,
- dragText: me.dragText,
- containerScroll: !!scrollable,
- scrollEl: scrollable && scrollable.getElement()
- });
- }
- if (me.droppable || me.dropGroup) {
- me.dropZone = Ext.create('Ext.view.DropZone', {
- view: boundList,
- ddGroup: me.dropGroup,
- handleNodeDrop: function(data, dropRecord, position) {
- var view = this.view,
- store = view.getStore(),
- records = data.records,
- index;
- // remove the Models from the source Store
- data.view.store.remove(records);
- index = store.indexOf(dropRecord);
- if (position === 'after') {
- index++;
- }
- store.insert(index, records);
- view.getSelectionModel().select(records);
- me.fireEvent('drop', me, records);
- }
- });
- }
- panel = me.down('panel');
- if (panel && boundList) {
- boundList.ariaEl.dom.setAttribute('aria-labelledby', panel.header.id + '-title-textEl');
- }
- },
- onKeyPageUp: function(e) {
- var me = this,
- pageSize = me.pageSize,
- boundList = me.boundList,
- nm = boundList.getNavigationModel(),
- oldIdx, newIdx;
- oldIdx = nm.recordIndex;
- // Unlike up arrow, pgUp does not wrap but goes to the first item
- newIdx = oldIdx > pageSize ? oldIdx - pageSize : 0;
- nm.setPosition(newIdx, e);
- },
- onKeyPageDown: function(e) {
- var me = this,
- pageSize = me.pageSize,
- boundList = me.boundList,
- nm = boundList.getNavigationModel(),
- count, oldIdx, newIdx;
- count = boundList.getStore().getCount();
- oldIdx = nm.recordIndex;
- // Unlike down arrow, pgDown does not wrap but goes to the last item
- newIdx = oldIdx < (count - pageSize) ? oldIdx + pageSize : count - 1;
- nm.setPosition(newIdx, e);
- },
- isValid: function() {
- var me = this,
- disabled = me.disabled,
- validate = me.forceValidation || !disabled;
- return validate ? me.validateValue(me.value) : disabled;
- },
- validateValue: function(value) {
- var me = this,
- errors = me.getErrors(value),
- isValid = Ext.isEmpty(errors);
- if (!me.preventMark) {
- if (isValid) {
- me.clearInvalid();
- } else {
- me.markInvalid(errors);
- }
- }
- return isValid;
- },
- markInvalid: function(errors) {
- // Save the message and fire the 'invalid' event
- var me = this,
- oldMsg = me.getActiveError();
- me.setActiveErrors(Ext.Array.from(errors));
- if (oldMsg !== me.getActiveError()) {
- me.updateLayout();
- }
- },
- /**
- * Clear any invalid styles/messages for this field.
- *
- * __Note:__ this method does not cause the Field's {@link #validate} or {@link #isValid} methods to return `true`
- * if the value does not _pass_ validation. So simply clearing a field's errors will not necessarily allow
- * submission of forms submitted with the {@link Ext.form.action.Submit#clientValidation} option set.
- */
- clearInvalid: function() {
- // Clear the message and fire the 'valid' event
- var me = this,
- hadError = me.hasActiveError();
- me.unsetActiveError();
- if (hadError) {
- me.updateLayout();
- }
- },
- getSubmitData: function() {
- var me = this,
- data = null,
- val;
- if (!me.disabled && me.submitValue && !me.isFileUpload()) {
- val = me.getSubmitValue();
- if (val !== null) {
- data = {};
- data[me.getName()] = val;
- }
- }
- return data;
- },
- /**
- * Returns the value that would be included in a standard form submit for this field.
- *
- * @return {String} The value to be submitted, or `null`.
- */
- getSubmitValue: function() {
- var me = this,
- delimiter = me.delimiter,
- val = me.getValue();
- return Ext.isString(delimiter) ? val.join(delimiter) : val;
- },
- getValue: function() {
- return this.value || [];
- },
- getRecordsForValue: function(value) {
- var me = this,
- records = [],
- all = me.store.getRange(),
- valueField = me.valueField,
- i = 0,
- allLen = all.length,
- rec, j, valueLen;
- for (valueLen = value.length; i < valueLen; ++i) {
- for (j = 0; j < allLen; ++j) {
- rec = all[j];
- if (rec.get(valueField) == value[i]) {
- records.push(rec);
- }
- }
- }
- return records;
- },
- setupValue: function(value) {
- var delimiter = this.delimiter,
- valueField = this.valueField,
- i = 0,
- out, len, item;
- if (Ext.isDefined(value)) {
- if (delimiter && Ext.isString(value)) {
- value = value.split(delimiter);
- } else if (!Ext.isArray(value)) {
- value = [
- value
- ];
- }
- for (len = value.length; i < len; ++i) {
- item = value[i];
- if (item && item.isModel) {
- value[i] = item.get(valueField);
- }
- }
- out = Ext.Array.unique(value);
- } else {
- out = [];
- }
- return out;
- },
- setValue: function(value) {
- var me = this,
- selModel = me.boundList.getSelectionModel(),
- store = me.store;
- // Store not loaded yet - we cannot set the value
- if (!store.getCount()) {
- store.on({
- load: Ext.Function.bind(me.setValue, me, [
- value
- ]),
- single: true
- });
- return;
- }
- value = me.setupValue(value);
- me.mixins.field.setValue.call(me, value);
- if (me.rendered) {
- ++me.ignoreSelectChange;
- selModel.deselectAll();
- if (value.length) {
- selModel.select(me.getRecordsForValue(value));
- }
- --me.ignoreSelectChange;
- } else {
- me.selectOnRender = true;
- }
- },
- clearValue: function() {
- this.setValue([]);
- },
- onEnable: function() {
- var list = this.boundList;
- this.callParent();
- if (list) {
- list.enable();
- }
- },
- onDisable: function() {
- var list = this.boundList;
- this.callParent();
- if (list) {
- list.disable();
- }
- },
- getErrors: function(value) {
- var me = this,
- format = Ext.String.format,
- errors = [],
- numSelected;
- value = Ext.Array.from(value || me.getValue());
- numSelected = value.length;
- if (!me.allowBlank && numSelected < 1) {
- errors.push(me.blankText);
- }
- if (numSelected < me.minSelections) {
- errors.push(format(me.minSelectionsText, me.minSelections));
- }
- if (numSelected > me.maxSelections) {
- errors.push(format(me.maxSelectionsText, me.maxSelections));
- }
- return errors;
- },
- doDestroy: function() {
- var me = this;
- me.bindStore(null);
- Ext.destroy(me.dragZone, me.dropZone, me.keyNav);
- me.callParent();
- },
- onBindStore: function(store) {
- var me = this,
- boundList = this.boundList;
- if (store.autoCreated) {
- me.resolveDisplayField();
- }
- if (!Ext.isDefined(me.valueField)) {
- me.valueField = me.displayField;
- }
- if (boundList) {
- boundList.bindStore(store);
- }
- },
- /**
- * Applies auto-created store fields to field and boundlist
- * @private
- */
- resolveDisplayField: function() {
- var me = this,
- boundList = me.boundList,
- store = me.getStore();
- me.valueField = me.displayField = 'field1';
- if (!store.expanded) {
- me.displayField = 'field2';
- }
- if (boundList) {
- boundList.setDisplayField(me.displayField);
- }
- }
- });
- /*
- * Note that this control will most likely remain as an example, and not as a core Ext form
- * control. However, the API will be changing in a future release and so should not yet be
- * treated as a final, stable API at this time.
- */
- /**
- * A control that allows selection of between two Ext.ux.form.MultiSelect controls.
- */
- Ext.define('Ext.ux.form.ItemSelector', {
- extend: 'Ext.ux.form.MultiSelect',
- alias: [
- 'widget.itemselectorfield',
- 'widget.itemselector'
- ],
- alternateClassName: [
- 'Ext.ux.ItemSelector'
- ],
- requires: [
- 'Ext.button.Button',
- 'Ext.ux.form.MultiSelect'
- ],
- /**
- * @cfg {Boolean} [hideNavIcons=false] True to hide the navigation icons
- */
- hideNavIcons: false,
- /**
- * @cfg {Array} buttons Defines the set of buttons that should be displayed in between the ItemSelector
- * fields. Defaults to <tt>['top', 'up', 'add', 'remove', 'down', 'bottom']</tt>. These names are used
- * to build the button CSS class names, and to look up the button text labels in {@link #buttonsText}.
- * This can be overridden with a custom Array to change which buttons are displayed or their order.
- */
- buttons: [
- 'top',
- 'up',
- 'add',
- 'remove',
- 'down',
- 'bottom'
- ],
- /**
- * @cfg {Object} buttonsText The tooltips for the {@link #buttons}.
- * Labels for buttons.
- */
- buttonsText: {
- top: "Move to Top",
- up: "Move Up",
- add: "Add to Selected",
- remove: "Remove from Selected",
- down: "Move Down",
- bottom: "Move to Bottom"
- },
- layout: {
- type: 'hbox',
- align: 'stretch'
- },
- ariaRole: 'group',
- initComponent: function() {
- var me = this;
- me.ddGroup = me.id + '-dd';
- me.ariaRenderAttributes = me.ariaRenderAttributes || {};
- me.ariaRenderAttributes['aria-labelledby'] = me.id + '-labelEl';
- me.callParent();
- // bindStore must be called after the fromField has been created because
- // it copies records from our configured Store into the fromField's Store
- me.bindStore(me.store);
- },
- createList: function(title) {
- var me = this;
- return Ext.create('Ext.ux.form.MultiSelect', {
- // We don't want the multiselects themselves to act like fields,
- // so override these methods to prevent them from including
- // any of their values
- submitValue: false,
- getSubmitData: function() {
- return null;
- },
- getModelData: function() {
- return null;
- },
- flex: 1,
- dragGroup: me.ddGroup,
- dropGroup: me.ddGroup,
- title: title,
- store: {
- model: me.store.model,
- data: []
- },
- displayField: me.displayField,
- valueField: me.valueField,
- disabled: me.disabled,
- listeners: {
- boundList: {
- scope: me,
- itemdblclick: me.onItemDblClick,
- drop: me.syncValue
- }
- }
- });
- },
- setupItems: function() {
- var me = this;
- me.fromField = me.createList(me.fromTitle);
- me.toField = me.createList(me.toTitle);
- return [
- me.fromField,
- {
- xtype: 'toolbar',
- margin: '0 4',
- padding: 0,
- layout: {
- type: 'vbox',
- pack: 'center'
- },
- items: me.createButtons()
- },
- me.toField
- ];
- },
- createButtons: function() {
- var me = this,
- buttons = [];
- if (!me.hideNavIcons) {
- Ext.Array.forEach(me.buttons, function(name) {
- buttons.push({
- xtype: 'button',
- ui: 'default',
- tooltip: me.buttonsText[name],
- ariaLabel: me.buttonsText[name],
- handler: me['on' + Ext.String.capitalize(name) + 'BtnClick'],
- cls: Ext.baseCSSPrefix + 'form-itemselector-btn',
- iconCls: Ext.baseCSSPrefix + 'form-itemselector-' + name,
- navBtn: true,
- scope: me,
- margin: '4 0 0 0'
- });
- });
- }
- return buttons;
- },
- /**
- * Get the selected records from the specified list.
- *
- * Records will be returned *in store order*, not in order of selection.
- * @param {Ext.view.BoundList} list The list to read selections from.
- * @return {Ext.data.Model[]} The selected records in store order.
- *
- */
- getSelections: function(list) {
- var store = list.getStore();
- return Ext.Array.sort(list.getSelectionModel().getSelection(), function(a, b) {
- a = store.indexOf(a);
- b = store.indexOf(b);
- if (a < b) {
- return -1;
- } else if (a > b) {
- return 1;
- }
- return 0;
- });
- },
- onTopBtnClick: function() {
- var list = this.toField.boundList,
- store = list.getStore(),
- selected = this.getSelections(list);
- store.suspendEvents();
- store.remove(selected, true);
- store.insert(0, selected);
- store.resumeEvents();
- list.refresh();
- this.syncValue();
- list.getSelectionModel().select(selected);
- },
- onBottomBtnClick: function() {
- var list = this.toField.boundList,
- store = list.getStore(),
- selected = this.getSelections(list);
- store.suspendEvents();
- store.remove(selected, true);
- store.add(selected);
- store.resumeEvents();
- list.refresh();
- this.syncValue();
- list.getSelectionModel().select(selected);
- },
- onUpBtnClick: function() {
- var list = this.toField.boundList,
- store = list.getStore(),
- selected = this.getSelections(list),
- rec,
- i = 0,
- len = selected.length,
- index = 0;
- // Move each selection up by one place if possible
- store.suspendEvents();
- for (; i < len; ++i , index++) {
- rec = selected[i];
- index = Math.max(index, store.indexOf(rec) - 1);
- store.remove(rec, true);
- store.insert(index, rec);
- }
- store.resumeEvents();
- list.refresh();
- this.syncValue();
- list.getSelectionModel().select(selected);
- },
- onDownBtnClick: function() {
- var list = this.toField.boundList,
- store = list.getStore(),
- selected = this.getSelections(list),
- rec,
- i = selected.length - 1,
- index = store.getCount() - 1;
- // Move each selection down by one place if possible
- store.suspendEvents();
- for (; i > -1; --i , index--) {
- rec = selected[i];
- index = Math.min(index, store.indexOf(rec) + 1);
- store.remove(rec, true);
- store.insert(index, rec);
- }
- store.resumeEvents();
- list.refresh();
- this.syncValue();
- list.getSelectionModel().select(selected);
- },
- onAddBtnClick: function() {
- var me = this,
- selected = me.getSelections(me.fromField.boundList);
- me.moveRec(true, selected);
- me.toField.boundList.getSelectionModel().select(selected);
- },
- onRemoveBtnClick: function() {
- var me = this,
- selected = me.getSelections(me.toField.boundList);
- me.moveRec(false, selected);
- me.fromField.boundList.getSelectionModel().select(selected);
- },
- moveRec: function(add, recs) {
- var me = this,
- fromField = me.fromField,
- toField = me.toField,
- fromStore = add ? fromField.store : toField.store,
- toStore = add ? toField.store : fromField.store;
- fromStore.suspendEvents();
- toStore.suspendEvents();
- fromStore.remove(recs);
- toStore.add(recs);
- fromStore.resumeEvents();
- toStore.resumeEvents();
- // If the list item was focused when moved (e.g. via double-click)
- // then removing it will cause the focus to be thrown back to the
- // document body. Which might disrupt things if ItemSelector is
- // contained by a floating thingie like a Menu.
- // Focusing the list itself will prevent that.
- if (fromField.boundList.containsFocus) {
- fromField.boundList.focus();
- }
- fromField.boundList.refresh();
- toField.boundList.refresh();
- me.syncValue();
- },
- // Synchronizes the submit value with the current state of the toStore
- syncValue: function() {
- var me = this;
- me.mixins.field.setValue.call(me, me.setupValue(me.toField.store.getRange()));
- },
- onItemDblClick: function(view, rec) {
- this.moveRec(view === this.fromField.boundList, rec);
- },
- setValue: function(value) {
- var me = this,
- fromField = me.fromField,
- toField = me.toField,
- fromStore = fromField.store,
- toStore = toField.store,
- selected;
- // Wait for from store to be loaded
- if (!me.fromStorePopulated) {
- me.fromField.store.on({
- load: Ext.Function.bind(me.setValue, me, [
- value
- ]),
- single: true
- });
- return;
- }
- value = me.setupValue(value);
- me.mixins.field.setValue.call(me, value);
- selected = me.getRecordsForValue(value);
- // Clear both left and right Stores.
- // Both stores must not fire events during this process.
- fromStore.suspendEvents();
- toStore.suspendEvents();
- fromStore.removeAll();
- toStore.removeAll();
- // Reset fromStore
- me.populateFromStore(me.store);
- // Copy selection across to toStore
- Ext.Array.forEach(selected, function(rec) {
- // In the from store, move it over
- if (fromStore.indexOf(rec) > -1) {
- fromStore.remove(rec);
- }
- toStore.add(rec);
- });
- // Stores may now fire events
- fromStore.resumeEvents();
- toStore.resumeEvents();
- // Refresh both sides and then update the app layout
- Ext.suspendLayouts();
- fromField.boundList.refresh();
- toField.boundList.refresh();
- Ext.resumeLayouts(true);
- },
- onBindStore: function(store, initial) {
- var me = this,
- fromField = me.fromField,
- toField = me.toField;
- if (fromField) {
- fromField.store.removeAll();
- toField.store.removeAll();
- if (store.autoCreated) {
- fromField.resolveDisplayField();
- toField.resolveDisplayField();
- me.resolveDisplayField();
- }
- if (!Ext.isDefined(me.valueField)) {
- me.valueField = me.displayField;
- }
- // Add everything to the from field as soon as the Store is loaded
- if (store.getCount()) {
- me.populateFromStore(store);
- } else {
- me.store.on('load', me.populateFromStore, me);
- }
- }
- },
- populateFromStore: function(store) {
- var fromStore = this.fromField.store;
- // Flag set when the fromStore has been loaded
- this.fromStorePopulated = true;
- fromStore.add(store.getRange());
- // setValue waits for the from Store to be loaded
- fromStore.fireEvent('load', fromStore);
- },
- onEnable: function() {
- var me = this;
- me.callParent();
- me.fromField.enable();
- me.toField.enable();
- Ext.Array.forEach(me.query('[navBtn]'), function(btn) {
- btn.enable();
- });
- },
- onDisable: function() {
- var me = this;
- me.callParent();
- me.fromField.disable();
- me.toField.disable();
- Ext.Array.forEach(me.query('[navBtn]'), function(btn) {
- btn.disable();
- });
- },
- doDestroy: function() {
- this.bindStore(null);
- this.callParent();
- }
- });
- /**
- *
- */
- Ext.define('Ext.ux.form.SearchField', {
- extend: 'Ext.form.field.Text',
- alias: 'widget.searchfield',
- triggers: {
- clear: {
- weight: 0,
- cls: Ext.baseCSSPrefix + 'form-clear-trigger',
- hidden: true,
- handler: 'onClearClick',
- scope: 'this'
- },
- search: {
- weight: 1,
- cls: Ext.baseCSSPrefix + 'form-search-trigger',
- handler: 'onSearchClick',
- scope: 'this'
- }
- },
- hasSearch: false,
- paramName: 'query',
- initComponent: function() {
- var me = this,
- store = me.store,
- proxy;
- me.callParent(arguments);
- me.on('specialkey', function(f, e) {
- if (e.getKey() == e.ENTER) {
- me.onSearchClick();
- }
- });
- if (!store || !store.isStore) {
- store = me.store = Ext.data.StoreManager.lookup(store);
- }
- // We're going to use filtering
- store.setRemoteFilter(true);
- // Set up the proxy to encode the filter in the simplest way as a name/value pair
- proxy = me.store.getProxy();
- proxy.setFilterParam(me.paramName);
- proxy.encodeFilters = function(filters) {
- return filters[0].getValue();
- };
- },
- onClearClick: function() {
- var me = this,
- activeFilter = me.activeFilter;
- if (activeFilter) {
- me.setValue('');
- me.store.getFilters().remove(activeFilter);
- me.activeFilter = null;
- me.getTrigger('clear').hide();
- me.updateLayout();
- }
- },
- onSearchClick: function() {
- var me = this,
- value = me.getValue();
- if (value.length > 0) {
- // Param name is ignored here since we use custom encoding in the proxy.
- // id is used by the Store to replace any previous filter
- me.activeFilter = new Ext.util.Filter({
- property: me.paramName,
- value: value
- });
- me.store.getFilters().add(me.activeFilter);
- me.getTrigger('clear').show();
- me.updateLayout();
- }
- }
- });
- /**
- * A small grid nested within a parent grid's row.
- *
- * See the [Kitchen Sink](http://dev.sencha.com/extjs/5.0.1/examples/kitchensink/#customer-grid) for example usage.
- */
- Ext.define('Ext.ux.grid.SubTable', {
- extend: 'Ext.grid.plugin.RowExpander',
- alias: 'plugin.subtable',
- rowBodyTpl: [
- '<table class="' + Ext.baseCSSPrefix + 'grid-subtable">',
- '{%',
- 'this.owner.renderTable(out, values);',
- '%}',
- '</table>'
- ],
- init: function(grid) {
- var me = this,
- columns = me.columns,
- len, i, columnCfg;
- me.callParent(arguments);
- me.columns = [];
- if (columns) {
- for (i = 0 , len = columns.length; i < len; ++i) {
- // Don't register with the component manager, we create them to use
- // their rendering smarts, but don't want to treat them as real components
- columnCfg = Ext.apply({
- preventRegister: true
- }, columns[i]);
- columnCfg.xtype = columnCfg.xtype || 'gridcolumn';
- me.columns.push(Ext.widget(columnCfg));
- }
- }
- },
- destroy: function() {
- var columns = this.columns,
- len, i;
- if (columns) {
- for (i = 0 , len = columns.length; i < len; ++i) {
- columns[i].destroy();
- }
- }
- this.columns = null;
- this.callParent();
- },
- getRowBodyFeatureData: function(record, idx, rowValues) {
- this.callParent(arguments);
- rowValues.rowBodyCls += ' ' + Ext.baseCSSPrefix + 'grid-subtable-row';
- },
- renderTable: function(out, rowValues) {
- var me = this,
- columns = me.columns,
- numColumns = columns.length,
- associatedRecords = me.getAssociatedRecords(rowValues.record),
- recCount = associatedRecords.length,
- rec, column, i, j, value;
- out.push('<thead>');
- for (j = 0; j < numColumns; j++) {
- out.push('<th class="' + Ext.baseCSSPrefix + 'grid-subtable-header">', columns[j].text, '</th>');
- }
- out.push('</thead><tbody>');
- for (i = 0; i < recCount; i++) {
- rec = associatedRecords[i];
- out.push('<tr>');
- for (j = 0; j < numColumns; j++) {
- column = columns[j];
- value = rec.get(column.dataIndex);
- if (column.renderer && column.renderer.call) {
- value = column.renderer.call(column.scope || me, value, {}, rec);
- }
- out.push('<td class="' + Ext.baseCSSPrefix + 'grid-subtable-cell"');
- if (column.width != null) {
- out.push(' style="width:' + column.width + 'px"');
- }
- out.push('><div class="' + Ext.baseCSSPrefix + 'grid-cell-inner">', value, '</div></td>');
- }
- out.push('</tr>');
- }
- out.push('</tbody>');
- },
- getRowBodyContentsFn: function(rowBodyTpl) {
- var me = this;
- return function(rowValues) {
- rowBodyTpl.owner = me;
- return rowBodyTpl.applyTemplate(rowValues);
- };
- },
- getAssociatedRecords: function(record) {
- return record[this.association]().getRange();
- }
- });
- /**
- * A Grid which creates itself from an existing HTML table element.
- */
- Ext.define('Ext.ux.grid.TransformGrid', {
- extend: 'Ext.grid.Panel',
- /**
- * Creates the grid from HTML table element.
- * @param {String/HTMLElement/Ext.Element} table The table element from which this grid will be created -
- * The table MUST have some type of size defined for the grid to fill. The container will be
- * automatically set to position relative if it isn't already.
- * @param {Object} [config] A config object that sets properties on this grid and has two additional (optional)
- * properties: fields and columns which allow for customizing data fields and columns for this grid.
- */
- constructor: function(table, config) {
- config = Ext.apply({}, config);
- table = this.table = Ext.get(table);
- var configFields = config.fields || [],
- configColumns = config.columns || [],
- fields = [],
- cols = [],
- headers = table.query("thead th"),
- i = 0,
- len = headers.length,
- data = table.dom,
- width, height, store, col, text, name;
- for (; i < len; ++i) {
- col = headers[i];
- text = col.innerHTML;
- name = 'tcol-' + i;
- fields.push(Ext.applyIf(configFields[i] || {}, {
- name: name,
- mapping: 'td:nth(' + (i + 1) + ')/@innerHTML'
- }));
- cols.push(Ext.applyIf(configColumns[i] || {}, {
- text: text,
- dataIndex: name,
- width: col.offsetWidth,
- tooltip: col.title,
- sortable: true
- }));
- }
- if (config.width) {
- width = config.width;
- } else {
- width = table.getWidth() + 1;
- }
- if (config.height) {
- height = config.height;
- }
- Ext.applyIf(config, {
- store: {
- data: data,
- fields: fields,
- proxy: {
- type: 'memory',
- reader: {
- record: 'tbody tr',
- type: 'xml'
- }
- }
- },
- columns: cols,
- width: width,
- height: height
- });
- this.callParent([
- config
- ]);
- if (config.remove !== false) {
- // Don't use table.remove() as that destroys the row/cell data in the table in
- // IE6-7 so it cannot be read by the data reader.
- data.parentNode.removeChild(data);
- }
- },
- doDestroy: function() {
- this.table.remove();
- this.tabl = null;
- this.callParent();
- }
- });
- /**
- * This plugin ensures that its associated grid or tree always has a selection record. The
- * only exception is, of course, when there are no records in the store.
- * @since 6.0.2
- */
- Ext.define('Ext.ux.grid.plugin.AutoSelector', {
- extend: 'Ext.plugin.Abstract',
- alias: 'plugin.gridautoselector',
- config: {
- store: null
- },
- init: function(grid) {
- //<debug>
- if (!grid.isXType('tablepanel')) {
- Ext.raise('The gridautoselector plugin is designed only for grids and trees');
- }
- //</debug>
- var me = this;
- me.grid = grid;
- me.watchGrid();
- grid.on({
- reconfigure: me.watchGrid,
- scope: me
- });
- },
- destroy: function() {
- this.setStore(null);
- this.grid = null;
- this.callParent();
- },
- ensureSelection: function() {
- var grid = this.grid,
- store = grid.getStore(),
- selection;
- if (store.getCount()) {
- selection = grid.getSelection();
- if (!selection || !selection.length) {
- grid.getSelectionModel().select(0);
- }
- }
- },
- watchGrid: function() {
- this.setStore(this.grid.getStore());
- this.ensureSelection();
- },
- updateStore: function(store) {
- var me = this;
- Ext.destroy(me.storeListeners);
- me.storeListeners = store && store.on({
- // We could go from 0 records to 1+ records... now we can select one!
- add: me.ensureSelection,
- // We might remove the selected record...
- remove: me.ensureSelection,
- destroyable: true,
- scope: me
- });
- }
- });
- /**
- * A simple grid-like layout for proportionally dividing container space and allocating it
- * to each item. All items in this layout are given one or more percentage sizes and CSS
- * `float:left` is used to provide the wrapping.
- *
- * To select which of the percentage sizes an item uses, this layout adds a viewport
- * {@link #states size-dependent} class name to the container. The style sheet must
- * provide the rules to select the desired size using the {@link #responsivecolumn-item}
- * mixin.
- *
- * For example, a panel in a responsive column layout might add the following styles:
- *
- * .my-panel {
- * // consume 50% of the available space inside the container by default
- * @include responsivecolumn-item(50%);
- *
- * .x-responsivecolumn-small & {
- * // consume 100% of available space in "small" mode
- * // (viewport width < 1000 by default)
- * @include responsivecolumn-item(100%);
- * }
- * }
- *
- * Alternatively, instead of targeting specific panels in CSS, you can create reusable
- * classes:
- *
- * .big-50 {
- * // consume 50% of the available space inside the container by default
- * @include responsivecolumn-item(50%);
- * }
- *
- * .x-responsivecolumn-small {
- * > .small-100 {
- * @include responsivecolumn-item(100%);
- * }
- * }
- *
- * These can be added to components in the layout using the `responsiveCls` config:
- *
- * items: [{
- * xtype: 'my-panel',
- *
- * // Use 50% of space when viewport is "big" and 100% when viewport
- * // is "small":
- * responsiveCls: 'big-50 small-100'
- * }]
- *
- * The `responsiveCls` config is provided by this layout to avoid overwriting classes
- * specified using `cls` or other standard configs.
- *
- * Internally, this layout simply uses `float:left` and CSS `calc()` (except on IE8) to
- * "flex" each item. The calculation is always based on a percentage with a spacing taken
- * into account to separate the items from each other.
- */
- Ext.define('Ext.ux.layout.ResponsiveColumn', {
- extend: 'Ext.layout.container.Auto',
- alias: 'layout.responsivecolumn',
- /**
- * @cfg {Object} states
- *
- * A set of layout state names corresponding to viewport size thresholds. One of the
- * states will be used to assign the responsive column CSS class to the container to
- * trigger appropriate item sizing.
- *
- * For example:
- *
- * layout: {
- * type: 'responsivecolumn',
- * states: {
- * small: 800,
- * medium: 1200,
- * large: 0
- * }
- * }
- *
- * Given the above set of responsive states, one of the following CSS classes will be
- * added to the container:
- *
- * - `x-responsivecolumn-small` - If the viewport is <= 800px
- * - `x-responsivecolumn-medium` - If the viewport is > 800px and <= 1200px
- * - `x-responsivecolumn-large` - If the viewport is > 1200px
- *
- * For sake of efficiency these classes are based on the size of the browser viewport
- * (the browser window) and not on the container size. As the size of the viewport
- * changes, this layout will maintain the appropriate CSS class on the container which
- * will then activate the appropriate CSS rules to size the child items.
- */
- states: {
- small: 1000,
- large: 0
- },
- _responsiveCls: Ext.baseCSSPrefix + 'responsivecolumn',
- initLayout: function() {
- this.innerCtCls += ' ' + this._responsiveCls;
- this.callParent();
- },
- beginLayout: function(ownerContext) {
- var me = this,
- viewportWidth = Ext.Element.getViewportWidth(),
- states = me.states,
- activeThreshold = Infinity,
- innerCt = me.innerCt,
- currentState = me._currentState,
- name, threshold, newState;
- for (name in states) {
- threshold = states[name] || Infinity;
- if (viewportWidth <= threshold && threshold <= activeThreshold) {
- activeThreshold = threshold;
- newState = name;
- }
- }
- if (newState !== currentState) {
- innerCt.replaceCls(currentState, newState, me._responsiveCls);
- me._currentState = newState;
- }
- me.callParent(arguments);
- },
- onAdd: function(item) {
- this.callParent([
- item
- ]);
- var responsiveCls = item.responsiveCls;
- if (responsiveCls) {
- item.addCls(responsiveCls);
- }
- }
- }, //--------------------------------------------------------------------------------------
- // IE8 does not support CSS calc expressions, so we have to fallback to more traditional
- // for of layout. This is very similar but much simpler than Column layout.
- //
- function(Responsive) {
- if (Ext.isIE8) {
- Responsive.override({
- responsiveSizePolicy: {
- readsWidth: 0,
- readsHeight: 0,
- setsWidth: 1,
- setsHeight: 0
- },
- setsItemSize: true,
- calculateItems: function(ownerContext, containerSize) {
- var me = this,
- targetContext = ownerContext.targetContext,
- items = ownerContext.childItems,
- len = items.length,
- gotWidth = containerSize.gotWidth,
- contentWidth = containerSize.width,
- blocked, availableWidth, i, itemContext, itemMarginWidth, itemWidth;
- // No parallel measurement, cannot lay out boxes.
- if (gotWidth === false) {
- targetContext.domBlock(me, 'width');
- return false;
-
- }
- if (!gotWidth) {
- // gotWidth is undefined, which means we must be width shrink wrap.
- // Cannot calculate item widths if we're shrink wrapping.
- return true;
- }
- for (i = 0; i < len; ++i) {
- itemContext = items[i];
- // The mixin encodes these in background-position syles since it is
- // unlikely a component will have a background-image.
- itemWidth = parseInt(itemContext.el.getStyle('background-position-x'), 10);
- itemMarginWidth = parseInt(itemContext.el.getStyle('background-position-y'), 10);
- itemContext.setWidth((itemWidth / 100 * (contentWidth - itemMarginWidth)) - itemMarginWidth);
- }
- ownerContext.setContentWidth(contentWidth + ownerContext.paddingContext.getPaddingInfo().width);
- return true;
- },
- getItemSizePolicy: function() {
- return this.responsiveSizePolicy;
- }
- });
- }
- });
- /**
- * A {@link Ext.ux.statusbar.StatusBar} plugin that provides automatic error
- * notification when the associated form contains validation errors.
- */
- Ext.define('Ext.ux.statusbar.ValidationStatus', {
- extend: 'Ext.Component',
- alias: 'plugin.validationstatus',
- requires: [
- 'Ext.util.MixedCollection'
- ],
- /**
- * @cfg {String} errorIconCls
- * The {@link Ext.ux.statusbar.StatusBar#iconCls iconCls} value to be applied
- * to the status message when there is a validation error.
- */
- errorIconCls: 'x-status-error',
- /**
- * @cfg {String} errorListCls
- * The css class to be used for the error list when there are validation errors.
- */
- errorListCls: 'x-status-error-list',
- /**
- * @cfg {String} validIconCls
- * The {@link Ext.ux.statusbar.StatusBar#iconCls iconCls} value to be applied
- * to the status message when the form validates.
- */
- validIconCls: 'x-status-valid',
- /**
- * @cfg {String} showText
- * The {@link Ext.ux.statusbar.StatusBar#text text} value to be applied when
- * there is a form validation error.
- */
- showText: 'The form has errors (click for details...)',
- /**
- * @cfg {String} hideText
- * The {@link Ext.ux.statusbar.StatusBar#text text} value to display when
- * the error list is displayed.
- */
- hideText: 'Click again to hide the error list',
- /**
- * @cfg {String} submitText
- * The {@link Ext.ux.statusbar.StatusBar#text text} value to be applied when
- * the form is being submitted.
- */
- submitText: 'Saving...',
- /**
- * @private
- */
- init: function(sb) {
- var me = this;
- me.statusBar = sb;
- sb.on({
- single: true,
- scope: me,
- render: me.onStatusbarRender
- });
- sb.on({
- click: {
- element: 'el',
- fn: me.onStatusClick,
- scope: me,
- buffer: 200
- }
- });
- },
- onStatusbarRender: function(sb) {
- var me = this,
- startMonitor = function() {
- me.monitor = true;
- };
- me.monitor = true;
- me.errors = Ext.create('Ext.util.MixedCollection');
- me.listAlign = (sb.statusAlign === 'right' ? 'br-tr?' : 'bl-tl?');
- if (me.form) {
- // Allow either an id, or a reference to be specified as the form name.
- me.formPanel = Ext.getCmp(me.form) || me.statusBar.lookupController().lookupReference(me.form);
- me.basicForm = me.formPanel.getForm();
- me.startMonitoring();
- me.basicForm.on({
- beforeaction: function(f, action) {
- if (action.type === 'submit') {
- // Ignore monitoring while submitting otherwise the field validation
- // events cause the status message to reset too early
- me.monitor = false;
- }
- }
- });
- me.formPanel.on({
- beforedestroy: me.destroy,
- scope: me
- });
- me.basicForm.on('actioncomplete', startMonitor);
- me.basicForm.on('actionfailed', startMonitor);
- }
- },
- /**
- * @private
- */
- startMonitoring: function() {
- this.basicForm.getFields().each(function(f) {
- f.on('validitychange', this.onFieldValidation, this);
- }, this);
- },
- /**
- * @private
- */
- stopMonitoring: function() {
- var form = this.basicForm;
- if (!form.destroyed) {
- form.getFields().each(function(f) {
- f.un('validitychange', this.onFieldValidation, this);
- }, this);
- }
- },
- doDestroy: function() {
- Ext.destroy(this.msgEl);
- this.stopMonitoring();
- this.statusBar.statusEl.un('click', this.onStatusClick, this);
- this.callParent();
- },
- /**
- * @private
- */
- onFieldValidation: function(f, isValid) {
- var me = this,
- msg;
- if (!me.monitor) {
- return false;
- }
- msg = f.getErrors()[0];
- if (msg) {
- me.errors.add(f.id, {
- field: f,
- msg: msg
- });
- } else {
- me.errors.removeAtKey(f.id);
- }
- this.updateErrorList();
- if (me.errors.getCount() > 0) {
- if (me.statusBar.getText() !== me.showText) {
- me.statusBar.setStatus({
- text: me.showText,
- iconCls: me.errorIconCls
- });
- }
- } else {
- me.statusBar.clearStatus().setIcon(me.validIconCls);
- }
- },
- /**
- * @private
- */
- updateErrorList: function() {
- var me = this,
- msg,
- msgEl = me.getMsgEl();
- if (me.errors.getCount() > 0) {
- msg = [
- '<ul>'
- ];
- this.errors.each(function(err) {
- msg.push('<li id="x-err-', err.field.id, '"><a href="#">', err.msg, '</a></li>');
- });
- msg.push('</ul>');
- msgEl.update(msg.join(''));
- } else {
- msgEl.update('');
- }
- // reset msgEl size
- msgEl.setSize('auto', 'auto');
- },
- /**
- * @private
- */
- getMsgEl: function() {
- var me = this,
- msgEl = me.msgEl,
- t;
- if (!msgEl) {
- msgEl = me.msgEl = Ext.DomHelper.append(Ext.getBody(), {
- cls: me.errorListCls
- }, true);
- msgEl.hide();
- msgEl.on('click', function(e) {
- t = e.getTarget('li', 10, true);
- if (t) {
- Ext.getCmp(t.id.split('x-err-')[1]).focus();
- me.hideErrors();
- }
- }, null, {
- stopEvent: true
- });
- }
- // prevent anchor click navigation
- return msgEl;
- },
- /**
- * @private
- */
- showErrors: function() {
- var me = this;
- me.updateErrorList();
- me.getMsgEl().alignTo(me.statusBar.getEl(), me.listAlign).slideIn('b', {
- duration: 300,
- easing: 'easeOut'
- });
- me.statusBar.setText(me.hideText);
- me.formPanel.body.on('click', me.hideErrors, me, {
- single: true
- });
- },
- // hide if the user clicks directly into the form
- /**
- * @private
- */
- hideErrors: function() {
- var el = this.getMsgEl();
- if (el.isVisible()) {
- el.slideOut('b', {
- duration: 300,
- easing: 'easeIn'
- });
- this.statusBar.setText(this.showText);
- }
- this.formPanel.body.un('click', this.hideErrors, this);
- },
- /**
- * @private
- */
- onStatusClick: function() {
- if (this.getMsgEl().isVisible()) {
- this.hideErrors();
- } else if (this.errors.getCount() > 0) {
- this.showErrors();
- }
- }
- });
|