sencha-touch-all-debug.js 2.7 MB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976149771497814979149801498114982149831498414985149861498714988149891499014991149921499314994149951499614997149981499915000150011500215003150041500515006150071500815009150101501115012150131501415015150161501715018150191502015021150221502315024150251502615027150281502915030150311503215033150341503515036150371503815039150401504115042150431504415045150461504715048150491505015051150521505315054150551505615057150581505915060150611506215063150641506515066150671506815069150701507115072150731507415075150761507715078150791508015081150821508315084150851508615087150881508915090150911509215093150941509515096150971509815099151001510115102151031510415105151061510715108151091511015111151121511315114151151511615117151181511915120151211512215123151241512515126151271512815129151301513115132151331513415135151361513715138151391514015141151421514315144151451514615147151481514915150151511515215153151541515515156151571515815159151601516115162151631516415165151661516715168151691517015171151721517315174151751517615177151781517915180151811518215183151841518515186151871518815189151901519115192151931519415195151961519715198151991520015201152021520315204152051520615207152081520915210152111521215213152141521515216152171521815219152201522115222152231522415225152261522715228152291523015231152321523315234152351523615237152381523915240152411524215243152441524515246152471524815249152501525115252152531525415255152561525715258152591526015261152621526315264152651526615267152681526915270152711527215273152741527515276152771527815279152801528115282152831528415285152861528715288152891529015291152921529315294152951529615297152981529915300153011530215303153041530515306153071530815309153101531115312153131531415315153161531715318153191532015321153221532315324153251532615327153281532915330153311533215333153341533515336153371533815339153401534115342153431534415345153461534715348153491535015351153521535315354153551535615357153581535915360153611536215363153641536515366153671536815369153701537115372153731537415375153761537715378153791538015381153821538315384153851538615387153881538915390153911539215393153941539515396153971539815399154001540115402154031540415405154061540715408154091541015411154121541315414154151541615417154181541915420154211542215423154241542515426154271542815429154301543115432154331543415435154361543715438154391544015441154421544315444154451544615447154481544915450154511545215453154541545515456154571545815459154601546115462154631546415465154661546715468154691547015471154721547315474154751547615477154781547915480154811548215483154841548515486154871548815489154901549115492154931549415495154961549715498154991550015501155021550315504155051550615507155081550915510155111551215513155141551515516155171551815519155201552115522155231552415525155261552715528155291553015531155321553315534155351553615537155381553915540155411554215543155441554515546155471554815549155501555115552155531555415555155561555715558155591556015561155621556315564155651556615567155681556915570155711557215573155741557515576155771557815579155801558115582155831558415585155861558715588155891559015591155921559315594155951559615597155981559915600156011560215603156041560515606156071560815609156101561115612156131561415615156161561715618156191562015621156221562315624156251562615627156281562915630156311563215633156341563515636156371563815639156401564115642156431564415645156461564715648156491565015651156521565315654156551565615657156581565915660156611566215663156641566515666156671566815669156701567115672156731567415675156761567715678156791568015681156821568315684156851568615687156881568915690156911569215693156941569515696156971569815699157001570115702157031570415705157061570715708157091571015711157121571315714157151571615717157181571915720157211572215723157241572515726157271572815729157301573115732157331573415735157361573715738157391574015741157421574315744157451574615747157481574915750157511575215753157541575515756157571575815759157601576115762157631576415765157661576715768157691577015771157721577315774157751577615777157781577915780157811578215783157841578515786157871578815789157901579115792157931579415795157961579715798157991580015801158021580315804158051580615807158081580915810158111581215813158141581515816158171581815819158201582115822158231582415825158261582715828158291583015831158321583315834158351583615837158381583915840158411584215843158441584515846158471584815849158501585115852158531585415855158561585715858158591586015861158621586315864158651586615867158681586915870158711587215873158741587515876158771587815879158801588115882158831588415885158861588715888158891589015891158921589315894158951589615897158981589915900159011590215903159041590515906159071590815909159101591115912159131591415915159161591715918159191592015921159221592315924159251592615927159281592915930159311593215933159341593515936159371593815939159401594115942159431594415945159461594715948159491595015951159521595315954159551595615957159581595915960159611596215963159641596515966159671596815969159701597115972159731597415975159761597715978159791598015981159821598315984159851598615987159881598915990159911599215993159941599515996159971599815999160001600116002160031600416005160061600716008160091601016011160121601316014160151601616017160181601916020160211602216023160241602516026160271602816029160301603116032160331603416035160361603716038160391604016041160421604316044160451604616047160481604916050160511605216053160541605516056160571605816059160601606116062160631606416065160661606716068160691607016071160721607316074160751607616077160781607916080160811608216083160841608516086160871608816089160901609116092160931609416095160961609716098160991610016101161021610316104161051610616107161081610916110161111611216113161141611516116161171611816119161201612116122161231612416125161261612716128161291613016131161321613316134161351613616137161381613916140161411614216143161441614516146161471614816149161501615116152161531615416155161561615716158161591616016161161621616316164161651616616167161681616916170161711617216173161741617516176161771617816179161801618116182161831618416185161861618716188161891619016191161921619316194161951619616197161981619916200162011620216203162041620516206162071620816209162101621116212162131621416215162161621716218162191622016221162221622316224162251622616227162281622916230162311623216233162341623516236162371623816239162401624116242162431624416245162461624716248162491625016251162521625316254162551625616257162581625916260162611626216263162641626516266162671626816269162701627116272162731627416275162761627716278162791628016281162821628316284162851628616287162881628916290162911629216293162941629516296162971629816299163001630116302163031630416305163061630716308163091631016311163121631316314163151631616317163181631916320163211632216323163241632516326163271632816329163301633116332163331633416335163361633716338163391634016341163421634316344163451634616347163481634916350163511635216353163541635516356163571635816359163601636116362163631636416365163661636716368163691637016371163721637316374163751637616377163781637916380163811638216383163841638516386163871638816389163901639116392163931639416395163961639716398163991640016401164021640316404164051640616407164081640916410164111641216413164141641516416164171641816419164201642116422164231642416425164261642716428164291643016431164321643316434164351643616437164381643916440164411644216443164441644516446164471644816449164501645116452164531645416455164561645716458164591646016461164621646316464164651646616467164681646916470164711647216473164741647516476164771647816479164801648116482164831648416485164861648716488164891649016491164921649316494164951649616497164981649916500165011650216503165041650516506165071650816509165101651116512165131651416515165161651716518165191652016521165221652316524165251652616527165281652916530165311653216533165341653516536165371653816539165401654116542165431654416545165461654716548165491655016551165521655316554165551655616557165581655916560165611656216563165641656516566165671656816569165701657116572165731657416575165761657716578165791658016581165821658316584165851658616587165881658916590165911659216593165941659516596165971659816599166001660116602166031660416605166061660716608166091661016611166121661316614166151661616617166181661916620166211662216623166241662516626166271662816629166301663116632166331663416635166361663716638166391664016641166421664316644166451664616647166481664916650166511665216653166541665516656166571665816659166601666116662166631666416665166661666716668166691667016671166721667316674166751667616677166781667916680166811668216683166841668516686166871668816689166901669116692166931669416695166961669716698166991670016701167021670316704167051670616707167081670916710167111671216713167141671516716167171671816719167201672116722167231672416725167261672716728167291673016731167321673316734167351673616737167381673916740167411674216743167441674516746167471674816749167501675116752167531675416755167561675716758167591676016761167621676316764167651676616767167681676916770167711677216773167741677516776167771677816779167801678116782167831678416785167861678716788167891679016791167921679316794167951679616797167981679916800168011680216803168041680516806168071680816809168101681116812168131681416815168161681716818168191682016821168221682316824168251682616827168281682916830168311683216833168341683516836168371683816839168401684116842168431684416845168461684716848168491685016851168521685316854168551685616857168581685916860168611686216863168641686516866168671686816869168701687116872168731687416875168761687716878168791688016881168821688316884168851688616887168881688916890168911689216893168941689516896168971689816899169001690116902169031690416905169061690716908169091691016911169121691316914169151691616917169181691916920169211692216923169241692516926169271692816929169301693116932169331693416935169361693716938169391694016941169421694316944169451694616947169481694916950169511695216953169541695516956169571695816959169601696116962169631696416965169661696716968169691697016971169721697316974169751697616977169781697916980169811698216983169841698516986169871698816989169901699116992169931699416995169961699716998169991700017001170021700317004170051700617007170081700917010170111701217013170141701517016170171701817019170201702117022170231702417025170261702717028170291703017031170321703317034170351703617037170381703917040170411704217043170441704517046170471704817049170501705117052170531705417055170561705717058170591706017061170621706317064170651706617067170681706917070170711707217073170741707517076170771707817079170801708117082170831708417085170861708717088170891709017091170921709317094170951709617097170981709917100171011710217103171041710517106171071710817109171101711117112171131711417115171161711717118171191712017121171221712317124171251712617127171281712917130171311713217133171341713517136171371713817139171401714117142171431714417145171461714717148171491715017151171521715317154171551715617157171581715917160171611716217163171641716517166171671716817169171701717117172171731717417175171761717717178171791718017181171821718317184171851718617187171881718917190171911719217193171941719517196171971719817199172001720117202172031720417205172061720717208172091721017211172121721317214172151721617217172181721917220172211722217223172241722517226172271722817229172301723117232172331723417235172361723717238172391724017241172421724317244172451724617247172481724917250172511725217253172541725517256172571725817259172601726117262172631726417265172661726717268172691727017271172721727317274172751727617277172781727917280172811728217283172841728517286172871728817289172901729117292172931729417295172961729717298172991730017301173021730317304173051730617307173081730917310173111731217313173141731517316173171731817319173201732117322173231732417325173261732717328173291733017331173321733317334173351733617337173381733917340173411734217343173441734517346173471734817349173501735117352173531735417355173561735717358173591736017361173621736317364173651736617367173681736917370173711737217373173741737517376173771737817379173801738117382173831738417385173861738717388173891739017391173921739317394173951739617397173981739917400174011740217403174041740517406174071740817409174101741117412174131741417415174161741717418174191742017421174221742317424174251742617427174281742917430174311743217433174341743517436174371743817439174401744117442174431744417445174461744717448174491745017451174521745317454174551745617457174581745917460174611746217463174641746517466174671746817469174701747117472174731747417475174761747717478174791748017481174821748317484174851748617487174881748917490174911749217493174941749517496174971749817499175001750117502175031750417505175061750717508175091751017511175121751317514175151751617517175181751917520175211752217523175241752517526175271752817529175301753117532175331753417535175361753717538175391754017541175421754317544175451754617547175481754917550175511755217553175541755517556175571755817559175601756117562175631756417565175661756717568175691757017571175721757317574175751757617577175781757917580175811758217583175841758517586175871758817589175901759117592175931759417595175961759717598175991760017601176021760317604176051760617607176081760917610176111761217613176141761517616176171761817619176201762117622176231762417625176261762717628176291763017631176321763317634176351763617637176381763917640176411764217643176441764517646176471764817649176501765117652176531765417655176561765717658176591766017661176621766317664176651766617667176681766917670176711767217673176741767517676176771767817679176801768117682176831768417685176861768717688176891769017691176921769317694176951769617697176981769917700177011770217703177041770517706177071770817709177101771117712177131771417715177161771717718177191772017721177221772317724177251772617727177281772917730177311773217733177341773517736177371773817739177401774117742177431774417745177461774717748177491775017751177521775317754177551775617757177581775917760177611776217763177641776517766177671776817769177701777117772177731777417775177761777717778177791778017781177821778317784177851778617787177881778917790177911779217793177941779517796177971779817799178001780117802178031780417805178061780717808178091781017811178121781317814178151781617817178181781917820178211782217823178241782517826178271782817829178301783117832178331783417835178361783717838178391784017841178421784317844178451784617847178481784917850178511785217853178541785517856178571785817859178601786117862178631786417865178661786717868178691787017871178721787317874178751787617877178781787917880178811788217883178841788517886178871788817889178901789117892178931789417895178961789717898178991790017901179021790317904179051790617907179081790917910179111791217913179141791517916179171791817919179201792117922179231792417925179261792717928179291793017931179321793317934179351793617937179381793917940179411794217943179441794517946179471794817949179501795117952179531795417955179561795717958179591796017961179621796317964179651796617967179681796917970179711797217973179741797517976179771797817979179801798117982179831798417985179861798717988179891799017991179921799317994179951799617997179981799918000180011800218003180041800518006180071800818009180101801118012180131801418015180161801718018180191802018021180221802318024180251802618027180281802918030180311803218033180341803518036180371803818039180401804118042180431804418045180461804718048180491805018051180521805318054180551805618057180581805918060180611806218063180641806518066180671806818069180701807118072180731807418075180761807718078180791808018081180821808318084180851808618087180881808918090180911809218093180941809518096180971809818099181001810118102181031810418105181061810718108181091811018111181121811318114181151811618117181181811918120181211812218123181241812518126181271812818129181301813118132181331813418135181361813718138181391814018141181421814318144181451814618147181481814918150181511815218153181541815518156181571815818159181601816118162181631816418165181661816718168181691817018171181721817318174181751817618177181781817918180181811818218183181841818518186181871818818189181901819118192181931819418195181961819718198181991820018201182021820318204182051820618207182081820918210182111821218213182141821518216182171821818219182201822118222182231822418225182261822718228182291823018231182321823318234182351823618237182381823918240182411824218243182441824518246182471824818249182501825118252182531825418255182561825718258182591826018261182621826318264182651826618267182681826918270182711827218273182741827518276182771827818279182801828118282182831828418285182861828718288182891829018291182921829318294182951829618297182981829918300183011830218303183041830518306183071830818309183101831118312183131831418315183161831718318183191832018321183221832318324183251832618327183281832918330183311833218333183341833518336183371833818339183401834118342183431834418345183461834718348183491835018351183521835318354183551835618357183581835918360183611836218363183641836518366183671836818369183701837118372183731837418375183761837718378183791838018381183821838318384183851838618387183881838918390183911839218393183941839518396183971839818399184001840118402184031840418405184061840718408184091841018411184121841318414184151841618417184181841918420184211842218423184241842518426184271842818429184301843118432184331843418435184361843718438184391844018441184421844318444184451844618447184481844918450184511845218453184541845518456184571845818459184601846118462184631846418465184661846718468184691847018471184721847318474184751847618477184781847918480184811848218483184841848518486184871848818489184901849118492184931849418495184961849718498184991850018501185021850318504185051850618507185081850918510185111851218513185141851518516185171851818519185201852118522185231852418525185261852718528185291853018531185321853318534185351853618537185381853918540185411854218543185441854518546185471854818549185501855118552185531855418555185561855718558185591856018561185621856318564185651856618567185681856918570185711857218573185741857518576185771857818579185801858118582185831858418585185861858718588185891859018591185921859318594185951859618597185981859918600186011860218603186041860518606186071860818609186101861118612186131861418615186161861718618186191862018621186221862318624186251862618627186281862918630186311863218633186341863518636186371863818639186401864118642186431864418645186461864718648186491865018651186521865318654186551865618657186581865918660186611866218663186641866518666186671866818669186701867118672186731867418675186761867718678186791868018681186821868318684186851868618687186881868918690186911869218693186941869518696186971869818699187001870118702187031870418705187061870718708187091871018711187121871318714187151871618717187181871918720187211872218723187241872518726187271872818729187301873118732187331873418735187361873718738187391874018741187421874318744187451874618747187481874918750187511875218753187541875518756187571875818759187601876118762187631876418765187661876718768187691877018771187721877318774187751877618777187781877918780187811878218783187841878518786187871878818789187901879118792187931879418795187961879718798187991880018801188021880318804188051880618807188081880918810188111881218813188141881518816188171881818819188201882118822188231882418825188261882718828188291883018831188321883318834188351883618837188381883918840188411884218843188441884518846188471884818849188501885118852188531885418855188561885718858188591886018861188621886318864188651886618867188681886918870188711887218873188741887518876188771887818879188801888118882188831888418885188861888718888188891889018891188921889318894188951889618897188981889918900189011890218903189041890518906189071890818909189101891118912189131891418915189161891718918189191892018921189221892318924189251892618927189281892918930189311893218933189341893518936189371893818939189401894118942189431894418945189461894718948189491895018951189521895318954189551895618957189581895918960189611896218963189641896518966189671896818969189701897118972189731897418975189761897718978189791898018981189821898318984189851898618987189881898918990189911899218993189941899518996189971899818999190001900119002190031900419005190061900719008190091901019011190121901319014190151901619017190181901919020190211902219023190241902519026190271902819029190301903119032190331903419035190361903719038190391904019041190421904319044190451904619047190481904919050190511905219053190541905519056190571905819059190601906119062190631906419065190661906719068190691907019071190721907319074190751907619077190781907919080190811908219083190841908519086190871908819089190901909119092190931909419095190961909719098190991910019101191021910319104191051910619107191081910919110191111911219113191141911519116191171911819119191201912119122191231912419125191261912719128191291913019131191321913319134191351913619137191381913919140191411914219143191441914519146191471914819149191501915119152191531915419155191561915719158191591916019161191621916319164191651916619167191681916919170191711917219173191741917519176191771917819179191801918119182191831918419185191861918719188191891919019191191921919319194191951919619197191981919919200192011920219203192041920519206192071920819209192101921119212192131921419215192161921719218192191922019221192221922319224192251922619227192281922919230192311923219233192341923519236192371923819239192401924119242192431924419245192461924719248192491925019251192521925319254192551925619257192581925919260192611926219263192641926519266192671926819269192701927119272192731927419275192761927719278192791928019281192821928319284192851928619287192881928919290192911929219293192941929519296192971929819299193001930119302193031930419305193061930719308193091931019311193121931319314193151931619317193181931919320193211932219323193241932519326193271932819329193301933119332193331933419335193361933719338193391934019341193421934319344193451934619347193481934919350193511935219353193541935519356193571935819359193601936119362193631936419365193661936719368193691937019371193721937319374193751937619377193781937919380193811938219383193841938519386193871938819389193901939119392193931939419395193961939719398193991940019401194021940319404194051940619407194081940919410194111941219413194141941519416194171941819419194201942119422194231942419425194261942719428194291943019431194321943319434194351943619437194381943919440194411944219443194441944519446194471944819449194501945119452194531945419455194561945719458194591946019461194621946319464194651946619467194681946919470194711947219473194741947519476194771947819479194801948119482194831948419485194861948719488194891949019491194921949319494194951949619497194981949919500195011950219503195041950519506195071950819509195101951119512195131951419515195161951719518195191952019521195221952319524195251952619527195281952919530195311953219533195341953519536195371953819539195401954119542195431954419545195461954719548195491955019551195521955319554195551955619557195581955919560195611956219563195641956519566195671956819569195701957119572195731957419575195761957719578195791958019581195821958319584195851958619587195881958919590195911959219593195941959519596195971959819599196001960119602196031960419605196061960719608196091961019611196121961319614196151961619617196181961919620196211962219623196241962519626196271962819629196301963119632196331963419635196361963719638196391964019641196421964319644196451964619647196481964919650196511965219653196541965519656196571965819659196601966119662196631966419665196661966719668196691967019671196721967319674196751967619677196781967919680196811968219683196841968519686196871968819689196901969119692196931969419695196961969719698196991970019701197021970319704197051970619707197081970919710197111971219713197141971519716197171971819719197201972119722197231972419725197261972719728197291973019731197321973319734197351973619737197381973919740197411974219743197441974519746197471974819749197501975119752197531975419755197561975719758197591976019761197621976319764197651976619767197681976919770197711977219773197741977519776197771977819779197801978119782197831978419785197861978719788197891979019791197921979319794197951979619797197981979919800198011980219803198041980519806198071980819809198101981119812198131981419815198161981719818198191982019821198221982319824198251982619827198281982919830198311983219833198341983519836198371983819839198401984119842198431984419845198461984719848198491985019851198521985319854198551985619857198581985919860198611986219863198641986519866198671986819869198701987119872198731987419875198761987719878198791988019881198821988319884198851988619887198881988919890198911989219893198941989519896198971989819899199001990119902199031990419905199061990719908199091991019911199121991319914199151991619917199181991919920199211992219923199241992519926199271992819929199301993119932199331993419935199361993719938199391994019941199421994319944199451994619947199481994919950199511995219953199541995519956199571995819959199601996119962199631996419965199661996719968199691997019971199721997319974199751997619977199781997919980199811998219983199841998519986199871998819989199901999119992199931999419995199961999719998199992000020001200022000320004200052000620007200082000920010200112001220013200142001520016200172001820019200202002120022200232002420025200262002720028200292003020031200322003320034200352003620037200382003920040200412004220043200442004520046200472004820049200502005120052200532005420055200562005720058200592006020061200622006320064200652006620067200682006920070200712007220073200742007520076200772007820079200802008120082200832008420085200862008720088200892009020091200922009320094200952009620097200982009920100201012010220103201042010520106201072010820109201102011120112201132011420115201162011720118201192012020121201222012320124201252012620127201282012920130201312013220133201342013520136201372013820139201402014120142201432014420145201462014720148201492015020151201522015320154201552015620157201582015920160201612016220163201642016520166201672016820169201702017120172201732017420175201762017720178201792018020181201822018320184201852018620187201882018920190201912019220193201942019520196201972019820199202002020120202202032020420205202062020720208202092021020211202122021320214202152021620217202182021920220202212022220223202242022520226202272022820229202302023120232202332023420235202362023720238202392024020241202422024320244202452024620247202482024920250202512025220253202542025520256202572025820259202602026120262202632026420265202662026720268202692027020271202722027320274202752027620277202782027920280202812028220283202842028520286202872028820289202902029120292202932029420295202962029720298202992030020301203022030320304203052030620307203082030920310203112031220313203142031520316203172031820319203202032120322203232032420325203262032720328203292033020331203322033320334203352033620337203382033920340203412034220343203442034520346203472034820349203502035120352203532035420355203562035720358203592036020361203622036320364203652036620367203682036920370203712037220373203742037520376203772037820379203802038120382203832038420385203862038720388203892039020391203922039320394203952039620397203982039920400204012040220403204042040520406204072040820409204102041120412204132041420415204162041720418204192042020421204222042320424204252042620427204282042920430204312043220433204342043520436204372043820439204402044120442204432044420445204462044720448204492045020451204522045320454204552045620457204582045920460204612046220463204642046520466204672046820469204702047120472204732047420475204762047720478204792048020481204822048320484204852048620487204882048920490204912049220493204942049520496204972049820499205002050120502205032050420505205062050720508205092051020511205122051320514205152051620517205182051920520205212052220523205242052520526205272052820529205302053120532205332053420535205362053720538205392054020541205422054320544205452054620547205482054920550205512055220553205542055520556205572055820559205602056120562205632056420565205662056720568205692057020571205722057320574205752057620577205782057920580205812058220583205842058520586205872058820589205902059120592205932059420595205962059720598205992060020601206022060320604206052060620607206082060920610206112061220613206142061520616206172061820619206202062120622206232062420625206262062720628206292063020631206322063320634206352063620637206382063920640206412064220643206442064520646206472064820649206502065120652206532065420655206562065720658206592066020661206622066320664206652066620667206682066920670206712067220673206742067520676206772067820679206802068120682206832068420685206862068720688206892069020691206922069320694206952069620697206982069920700207012070220703207042070520706207072070820709207102071120712207132071420715207162071720718207192072020721207222072320724207252072620727207282072920730207312073220733207342073520736207372073820739207402074120742207432074420745207462074720748207492075020751207522075320754207552075620757207582075920760207612076220763207642076520766207672076820769207702077120772207732077420775207762077720778207792078020781207822078320784207852078620787207882078920790207912079220793207942079520796207972079820799208002080120802208032080420805208062080720808208092081020811208122081320814208152081620817208182081920820208212082220823208242082520826208272082820829208302083120832208332083420835208362083720838208392084020841208422084320844208452084620847208482084920850208512085220853208542085520856208572085820859208602086120862208632086420865208662086720868208692087020871208722087320874208752087620877208782087920880208812088220883208842088520886208872088820889208902089120892208932089420895208962089720898208992090020901209022090320904209052090620907209082090920910209112091220913209142091520916209172091820919209202092120922209232092420925209262092720928209292093020931209322093320934209352093620937209382093920940209412094220943209442094520946209472094820949209502095120952209532095420955209562095720958209592096020961209622096320964209652096620967209682096920970209712097220973209742097520976209772097820979209802098120982209832098420985209862098720988209892099020991209922099320994209952099620997209982099921000210012100221003210042100521006210072100821009210102101121012210132101421015210162101721018210192102021021210222102321024210252102621027210282102921030210312103221033210342103521036210372103821039210402104121042210432104421045210462104721048210492105021051210522105321054210552105621057210582105921060210612106221063210642106521066210672106821069210702107121072210732107421075210762107721078210792108021081210822108321084210852108621087210882108921090210912109221093210942109521096210972109821099211002110121102211032110421105211062110721108211092111021111211122111321114211152111621117211182111921120211212112221123211242112521126211272112821129211302113121132211332113421135211362113721138211392114021141211422114321144211452114621147211482114921150211512115221153211542115521156211572115821159211602116121162211632116421165211662116721168211692117021171211722117321174211752117621177211782117921180211812118221183211842118521186211872118821189211902119121192211932119421195211962119721198211992120021201212022120321204212052120621207212082120921210212112121221213212142121521216212172121821219212202122121222212232122421225212262122721228212292123021231212322123321234212352123621237212382123921240212412124221243212442124521246212472124821249212502125121252212532125421255212562125721258212592126021261212622126321264212652126621267212682126921270212712127221273212742127521276212772127821279212802128121282212832128421285212862128721288212892129021291212922129321294212952129621297212982129921300213012130221303213042130521306213072130821309213102131121312213132131421315213162131721318213192132021321213222132321324213252132621327213282132921330213312133221333213342133521336213372133821339213402134121342213432134421345213462134721348213492135021351213522135321354213552135621357213582135921360213612136221363213642136521366213672136821369213702137121372213732137421375213762137721378213792138021381213822138321384213852138621387213882138921390213912139221393213942139521396213972139821399214002140121402214032140421405214062140721408214092141021411214122141321414214152141621417214182141921420214212142221423214242142521426214272142821429214302143121432214332143421435214362143721438214392144021441214422144321444214452144621447214482144921450214512145221453214542145521456214572145821459214602146121462214632146421465214662146721468214692147021471214722147321474214752147621477214782147921480214812148221483214842148521486214872148821489214902149121492214932149421495214962149721498214992150021501215022150321504215052150621507215082150921510215112151221513215142151521516215172151821519215202152121522215232152421525215262152721528215292153021531215322153321534215352153621537215382153921540215412154221543215442154521546215472154821549215502155121552215532155421555215562155721558215592156021561215622156321564215652156621567215682156921570215712157221573215742157521576215772157821579215802158121582215832158421585215862158721588215892159021591215922159321594215952159621597215982159921600216012160221603216042160521606216072160821609216102161121612216132161421615216162161721618216192162021621216222162321624216252162621627216282162921630216312163221633216342163521636216372163821639216402164121642216432164421645216462164721648216492165021651216522165321654216552165621657216582165921660216612166221663216642166521666216672166821669216702167121672216732167421675216762167721678216792168021681216822168321684216852168621687216882168921690216912169221693216942169521696216972169821699217002170121702217032170421705217062170721708217092171021711217122171321714217152171621717217182171921720217212172221723217242172521726217272172821729217302173121732217332173421735217362173721738217392174021741217422174321744217452174621747217482174921750217512175221753217542175521756217572175821759217602176121762217632176421765217662176721768217692177021771217722177321774217752177621777217782177921780217812178221783217842178521786217872178821789217902179121792217932179421795217962179721798217992180021801218022180321804218052180621807218082180921810218112181221813218142181521816218172181821819218202182121822218232182421825218262182721828218292183021831218322183321834218352183621837218382183921840218412184221843218442184521846218472184821849218502185121852218532185421855218562185721858218592186021861218622186321864218652186621867218682186921870218712187221873218742187521876218772187821879218802188121882218832188421885218862188721888218892189021891218922189321894218952189621897218982189921900219012190221903219042190521906219072190821909219102191121912219132191421915219162191721918219192192021921219222192321924219252192621927219282192921930219312193221933219342193521936219372193821939219402194121942219432194421945219462194721948219492195021951219522195321954219552195621957219582195921960219612196221963219642196521966219672196821969219702197121972219732197421975219762197721978219792198021981219822198321984219852198621987219882198921990219912199221993219942199521996219972199821999220002200122002220032200422005220062200722008220092201022011220122201322014220152201622017220182201922020220212202222023220242202522026220272202822029220302203122032220332203422035220362203722038220392204022041220422204322044220452204622047220482204922050220512205222053220542205522056220572205822059220602206122062220632206422065220662206722068220692207022071220722207322074220752207622077220782207922080220812208222083220842208522086220872208822089220902209122092220932209422095220962209722098220992210022101221022210322104221052210622107221082210922110221112211222113221142211522116221172211822119221202212122122221232212422125221262212722128221292213022131221322213322134221352213622137221382213922140221412214222143221442214522146221472214822149221502215122152221532215422155221562215722158221592216022161221622216322164221652216622167221682216922170221712217222173221742217522176221772217822179221802218122182221832218422185221862218722188221892219022191221922219322194221952219622197221982219922200222012220222203222042220522206222072220822209222102221122212222132221422215222162221722218222192222022221222222222322224222252222622227222282222922230222312223222233222342223522236222372223822239222402224122242222432224422245222462224722248222492225022251222522225322254222552225622257222582225922260222612226222263222642226522266222672226822269222702227122272222732227422275222762227722278222792228022281222822228322284222852228622287222882228922290222912229222293222942229522296222972229822299223002230122302223032230422305223062230722308223092231022311223122231322314223152231622317223182231922320223212232222323223242232522326223272232822329223302233122332223332233422335223362233722338223392234022341223422234322344223452234622347223482234922350223512235222353223542235522356223572235822359223602236122362223632236422365223662236722368223692237022371223722237322374223752237622377223782237922380223812238222383223842238522386223872238822389223902239122392223932239422395223962239722398223992240022401224022240322404224052240622407224082240922410224112241222413224142241522416224172241822419224202242122422224232242422425224262242722428224292243022431224322243322434224352243622437224382243922440224412244222443224442244522446224472244822449224502245122452224532245422455224562245722458224592246022461224622246322464224652246622467224682246922470224712247222473224742247522476224772247822479224802248122482224832248422485224862248722488224892249022491224922249322494224952249622497224982249922500225012250222503225042250522506225072250822509225102251122512225132251422515225162251722518225192252022521225222252322524225252252622527225282252922530225312253222533225342253522536225372253822539225402254122542225432254422545225462254722548225492255022551225522255322554225552255622557225582255922560225612256222563225642256522566225672256822569225702257122572225732257422575225762257722578225792258022581225822258322584225852258622587225882258922590225912259222593225942259522596225972259822599226002260122602226032260422605226062260722608226092261022611226122261322614226152261622617226182261922620226212262222623226242262522626226272262822629226302263122632226332263422635226362263722638226392264022641226422264322644226452264622647226482264922650226512265222653226542265522656226572265822659226602266122662226632266422665226662266722668226692267022671226722267322674226752267622677226782267922680226812268222683226842268522686226872268822689226902269122692226932269422695226962269722698226992270022701227022270322704227052270622707227082270922710227112271222713227142271522716227172271822719227202272122722227232272422725227262272722728227292273022731227322273322734227352273622737227382273922740227412274222743227442274522746227472274822749227502275122752227532275422755227562275722758227592276022761227622276322764227652276622767227682276922770227712277222773227742277522776227772277822779227802278122782227832278422785227862278722788227892279022791227922279322794227952279622797227982279922800228012280222803228042280522806228072280822809228102281122812228132281422815228162281722818228192282022821228222282322824228252282622827228282282922830228312283222833228342283522836228372283822839228402284122842228432284422845228462284722848228492285022851228522285322854228552285622857228582285922860228612286222863228642286522866228672286822869228702287122872228732287422875228762287722878228792288022881228822288322884228852288622887228882288922890228912289222893228942289522896228972289822899229002290122902229032290422905229062290722908229092291022911229122291322914229152291622917229182291922920229212292222923229242292522926229272292822929229302293122932229332293422935229362293722938229392294022941229422294322944229452294622947229482294922950229512295222953229542295522956229572295822959229602296122962229632296422965229662296722968229692297022971229722297322974229752297622977229782297922980229812298222983229842298522986229872298822989229902299122992229932299422995229962299722998229992300023001230022300323004230052300623007230082300923010230112301223013230142301523016230172301823019230202302123022230232302423025230262302723028230292303023031230322303323034230352303623037230382303923040230412304223043230442304523046230472304823049230502305123052230532305423055230562305723058230592306023061230622306323064230652306623067230682306923070230712307223073230742307523076230772307823079230802308123082230832308423085230862308723088230892309023091230922309323094230952309623097230982309923100231012310223103231042310523106231072310823109231102311123112231132311423115231162311723118231192312023121231222312323124231252312623127231282312923130231312313223133231342313523136231372313823139231402314123142231432314423145231462314723148231492315023151231522315323154231552315623157231582315923160231612316223163231642316523166231672316823169231702317123172231732317423175231762317723178231792318023181231822318323184231852318623187231882318923190231912319223193231942319523196231972319823199232002320123202232032320423205232062320723208232092321023211232122321323214232152321623217232182321923220232212322223223232242322523226232272322823229232302323123232232332323423235232362323723238232392324023241232422324323244232452324623247232482324923250232512325223253232542325523256232572325823259232602326123262232632326423265232662326723268232692327023271232722327323274232752327623277232782327923280232812328223283232842328523286232872328823289232902329123292232932329423295232962329723298232992330023301233022330323304233052330623307233082330923310233112331223313233142331523316233172331823319233202332123322233232332423325233262332723328233292333023331233322333323334233352333623337233382333923340233412334223343233442334523346233472334823349233502335123352233532335423355233562335723358233592336023361233622336323364233652336623367233682336923370233712337223373233742337523376233772337823379233802338123382233832338423385233862338723388233892339023391233922339323394233952339623397233982339923400234012340223403234042340523406234072340823409234102341123412234132341423415234162341723418234192342023421234222342323424234252342623427234282342923430234312343223433234342343523436234372343823439234402344123442234432344423445234462344723448234492345023451234522345323454234552345623457234582345923460234612346223463234642346523466234672346823469234702347123472234732347423475234762347723478234792348023481234822348323484234852348623487234882348923490234912349223493234942349523496234972349823499235002350123502235032350423505235062350723508235092351023511235122351323514235152351623517235182351923520235212352223523235242352523526235272352823529235302353123532235332353423535235362353723538235392354023541235422354323544235452354623547235482354923550235512355223553235542355523556235572355823559235602356123562235632356423565235662356723568235692357023571235722357323574235752357623577235782357923580235812358223583235842358523586235872358823589235902359123592235932359423595235962359723598235992360023601236022360323604236052360623607236082360923610236112361223613236142361523616236172361823619236202362123622236232362423625236262362723628236292363023631236322363323634236352363623637236382363923640236412364223643236442364523646236472364823649236502365123652236532365423655236562365723658236592366023661236622366323664236652366623667236682366923670236712367223673236742367523676236772367823679236802368123682236832368423685236862368723688236892369023691236922369323694236952369623697236982369923700237012370223703237042370523706237072370823709237102371123712237132371423715237162371723718237192372023721237222372323724237252372623727237282372923730237312373223733237342373523736237372373823739237402374123742237432374423745237462374723748237492375023751237522375323754237552375623757237582375923760237612376223763237642376523766237672376823769237702377123772237732377423775237762377723778237792378023781237822378323784237852378623787237882378923790237912379223793237942379523796237972379823799238002380123802238032380423805238062380723808238092381023811238122381323814238152381623817238182381923820238212382223823238242382523826238272382823829238302383123832238332383423835238362383723838238392384023841238422384323844238452384623847238482384923850238512385223853238542385523856238572385823859238602386123862238632386423865238662386723868238692387023871238722387323874238752387623877238782387923880238812388223883238842388523886238872388823889238902389123892238932389423895238962389723898238992390023901239022390323904239052390623907239082390923910239112391223913239142391523916239172391823919239202392123922239232392423925239262392723928239292393023931239322393323934239352393623937239382393923940239412394223943239442394523946239472394823949239502395123952239532395423955239562395723958239592396023961239622396323964239652396623967239682396923970239712397223973239742397523976239772397823979239802398123982239832398423985239862398723988239892399023991239922399323994239952399623997239982399924000240012400224003240042400524006240072400824009240102401124012240132401424015240162401724018240192402024021240222402324024240252402624027240282402924030240312403224033240342403524036240372403824039240402404124042240432404424045240462404724048240492405024051240522405324054240552405624057240582405924060240612406224063240642406524066240672406824069240702407124072240732407424075240762407724078240792408024081240822408324084240852408624087240882408924090240912409224093240942409524096240972409824099241002410124102241032410424105241062410724108241092411024111241122411324114241152411624117241182411924120241212412224123241242412524126241272412824129241302413124132241332413424135241362413724138241392414024141241422414324144241452414624147241482414924150241512415224153241542415524156241572415824159241602416124162241632416424165241662416724168241692417024171241722417324174241752417624177241782417924180241812418224183241842418524186241872418824189241902419124192241932419424195241962419724198241992420024201242022420324204242052420624207242082420924210242112421224213242142421524216242172421824219242202422124222242232422424225242262422724228242292423024231242322423324234242352423624237242382423924240242412424224243242442424524246242472424824249242502425124252242532425424255242562425724258242592426024261242622426324264242652426624267242682426924270242712427224273242742427524276242772427824279242802428124282242832428424285242862428724288242892429024291242922429324294242952429624297242982429924300243012430224303243042430524306243072430824309243102431124312243132431424315243162431724318243192432024321243222432324324243252432624327243282432924330243312433224333243342433524336243372433824339243402434124342243432434424345243462434724348243492435024351243522435324354243552435624357243582435924360243612436224363243642436524366243672436824369243702437124372243732437424375243762437724378243792438024381243822438324384243852438624387243882438924390243912439224393243942439524396243972439824399244002440124402244032440424405244062440724408244092441024411244122441324414244152441624417244182441924420244212442224423244242442524426244272442824429244302443124432244332443424435244362443724438244392444024441244422444324444244452444624447244482444924450244512445224453244542445524456244572445824459244602446124462244632446424465244662446724468244692447024471244722447324474244752447624477244782447924480244812448224483244842448524486244872448824489244902449124492244932449424495244962449724498244992450024501245022450324504245052450624507245082450924510245112451224513245142451524516245172451824519245202452124522245232452424525245262452724528245292453024531245322453324534245352453624537245382453924540245412454224543245442454524546245472454824549245502455124552245532455424555245562455724558245592456024561245622456324564245652456624567245682456924570245712457224573245742457524576245772457824579245802458124582245832458424585245862458724588245892459024591245922459324594245952459624597245982459924600246012460224603246042460524606246072460824609246102461124612246132461424615246162461724618246192462024621246222462324624246252462624627246282462924630246312463224633246342463524636246372463824639246402464124642246432464424645246462464724648246492465024651246522465324654246552465624657246582465924660246612466224663246642466524666246672466824669246702467124672246732467424675246762467724678246792468024681246822468324684246852468624687246882468924690246912469224693246942469524696246972469824699247002470124702247032470424705247062470724708247092471024711247122471324714247152471624717247182471924720247212472224723247242472524726247272472824729247302473124732247332473424735247362473724738247392474024741247422474324744247452474624747247482474924750247512475224753247542475524756247572475824759247602476124762247632476424765247662476724768247692477024771247722477324774247752477624777247782477924780247812478224783247842478524786247872478824789247902479124792247932479424795247962479724798247992480024801248022480324804248052480624807248082480924810248112481224813248142481524816248172481824819248202482124822248232482424825248262482724828248292483024831248322483324834248352483624837248382483924840248412484224843248442484524846248472484824849248502485124852248532485424855248562485724858248592486024861248622486324864248652486624867248682486924870248712487224873248742487524876248772487824879248802488124882248832488424885248862488724888248892489024891248922489324894248952489624897248982489924900249012490224903249042490524906249072490824909249102491124912249132491424915249162491724918249192492024921249222492324924249252492624927249282492924930249312493224933249342493524936249372493824939249402494124942249432494424945249462494724948249492495024951249522495324954249552495624957249582495924960249612496224963249642496524966249672496824969249702497124972249732497424975249762497724978249792498024981249822498324984249852498624987249882498924990249912499224993249942499524996249972499824999250002500125002250032500425005250062500725008250092501025011250122501325014250152501625017250182501925020250212502225023250242502525026250272502825029250302503125032250332503425035250362503725038250392504025041250422504325044250452504625047250482504925050250512505225053250542505525056250572505825059250602506125062250632506425065250662506725068250692507025071250722507325074250752507625077250782507925080250812508225083250842508525086250872508825089250902509125092250932509425095250962509725098250992510025101251022510325104251052510625107251082510925110251112511225113251142511525116251172511825119251202512125122251232512425125251262512725128251292513025131251322513325134251352513625137251382513925140251412514225143251442514525146251472514825149251502515125152251532515425155251562515725158251592516025161251622516325164251652516625167251682516925170251712517225173251742517525176251772517825179251802518125182251832518425185251862518725188251892519025191251922519325194251952519625197251982519925200252012520225203252042520525206252072520825209252102521125212252132521425215252162521725218252192522025221252222522325224252252522625227252282522925230252312523225233252342523525236252372523825239252402524125242252432524425245252462524725248252492525025251252522525325254252552525625257252582525925260252612526225263252642526525266252672526825269252702527125272252732527425275252762527725278252792528025281252822528325284252852528625287252882528925290252912529225293252942529525296252972529825299253002530125302253032530425305253062530725308253092531025311253122531325314253152531625317253182531925320253212532225323253242532525326253272532825329253302533125332253332533425335253362533725338253392534025341253422534325344253452534625347253482534925350253512535225353253542535525356253572535825359253602536125362253632536425365253662536725368253692537025371253722537325374253752537625377253782537925380253812538225383253842538525386253872538825389253902539125392253932539425395253962539725398253992540025401254022540325404254052540625407254082540925410254112541225413254142541525416254172541825419254202542125422254232542425425254262542725428254292543025431254322543325434254352543625437254382543925440254412544225443254442544525446254472544825449254502545125452254532545425455254562545725458254592546025461254622546325464254652546625467254682546925470254712547225473254742547525476254772547825479254802548125482254832548425485254862548725488254892549025491254922549325494254952549625497254982549925500255012550225503255042550525506255072550825509255102551125512255132551425515255162551725518255192552025521255222552325524255252552625527255282552925530255312553225533255342553525536255372553825539255402554125542255432554425545255462554725548255492555025551255522555325554255552555625557255582555925560255612556225563255642556525566255672556825569255702557125572255732557425575255762557725578255792558025581255822558325584255852558625587255882558925590255912559225593255942559525596255972559825599256002560125602256032560425605256062560725608256092561025611256122561325614256152561625617256182561925620256212562225623256242562525626256272562825629256302563125632256332563425635256362563725638256392564025641256422564325644256452564625647256482564925650256512565225653256542565525656256572565825659256602566125662256632566425665256662566725668256692567025671256722567325674256752567625677256782567925680256812568225683256842568525686256872568825689256902569125692256932569425695256962569725698256992570025701257022570325704257052570625707257082570925710257112571225713257142571525716257172571825719257202572125722257232572425725257262572725728257292573025731257322573325734257352573625737257382573925740257412574225743257442574525746257472574825749257502575125752257532575425755257562575725758257592576025761257622576325764257652576625767257682576925770257712577225773257742577525776257772577825779257802578125782257832578425785257862578725788257892579025791257922579325794257952579625797257982579925800258012580225803258042580525806258072580825809258102581125812258132581425815258162581725818258192582025821258222582325824258252582625827258282582925830258312583225833258342583525836258372583825839258402584125842258432584425845258462584725848258492585025851258522585325854258552585625857258582585925860258612586225863258642586525866258672586825869258702587125872258732587425875258762587725878258792588025881258822588325884258852588625887258882588925890258912589225893258942589525896258972589825899259002590125902259032590425905259062590725908259092591025911259122591325914259152591625917259182591925920259212592225923259242592525926259272592825929259302593125932259332593425935259362593725938259392594025941259422594325944259452594625947259482594925950259512595225953259542595525956259572595825959259602596125962259632596425965259662596725968259692597025971259722597325974259752597625977259782597925980259812598225983259842598525986259872598825989259902599125992259932599425995259962599725998259992600026001260022600326004260052600626007260082600926010260112601226013260142601526016260172601826019260202602126022260232602426025260262602726028260292603026031260322603326034260352603626037260382603926040260412604226043260442604526046260472604826049260502605126052260532605426055260562605726058260592606026061260622606326064260652606626067260682606926070260712607226073260742607526076260772607826079260802608126082260832608426085260862608726088260892609026091260922609326094260952609626097260982609926100261012610226103261042610526106261072610826109261102611126112261132611426115261162611726118261192612026121261222612326124261252612626127261282612926130261312613226133261342613526136261372613826139261402614126142261432614426145261462614726148261492615026151261522615326154261552615626157261582615926160261612616226163261642616526166261672616826169261702617126172261732617426175261762617726178261792618026181261822618326184261852618626187261882618926190261912619226193261942619526196261972619826199262002620126202262032620426205262062620726208262092621026211262122621326214262152621626217262182621926220262212622226223262242622526226262272622826229262302623126232262332623426235262362623726238262392624026241262422624326244262452624626247262482624926250262512625226253262542625526256262572625826259262602626126262262632626426265262662626726268262692627026271262722627326274262752627626277262782627926280262812628226283262842628526286262872628826289262902629126292262932629426295262962629726298262992630026301263022630326304263052630626307263082630926310263112631226313263142631526316263172631826319263202632126322263232632426325263262632726328263292633026331263322633326334263352633626337263382633926340263412634226343263442634526346263472634826349263502635126352263532635426355263562635726358263592636026361263622636326364263652636626367263682636926370263712637226373263742637526376263772637826379263802638126382263832638426385263862638726388263892639026391263922639326394263952639626397263982639926400264012640226403264042640526406264072640826409264102641126412264132641426415264162641726418264192642026421264222642326424264252642626427264282642926430264312643226433264342643526436264372643826439264402644126442264432644426445264462644726448264492645026451264522645326454264552645626457264582645926460264612646226463264642646526466264672646826469264702647126472264732647426475264762647726478264792648026481264822648326484264852648626487264882648926490264912649226493264942649526496264972649826499265002650126502265032650426505265062650726508265092651026511265122651326514265152651626517265182651926520265212652226523265242652526526265272652826529265302653126532265332653426535265362653726538265392654026541265422654326544265452654626547265482654926550265512655226553265542655526556265572655826559265602656126562265632656426565265662656726568265692657026571265722657326574265752657626577265782657926580265812658226583265842658526586265872658826589265902659126592265932659426595265962659726598265992660026601266022660326604266052660626607266082660926610266112661226613266142661526616266172661826619266202662126622266232662426625266262662726628266292663026631266322663326634266352663626637266382663926640266412664226643266442664526646266472664826649266502665126652266532665426655266562665726658266592666026661266622666326664266652666626667266682666926670266712667226673266742667526676266772667826679266802668126682266832668426685266862668726688266892669026691266922669326694266952669626697266982669926700267012670226703267042670526706267072670826709267102671126712267132671426715267162671726718267192672026721267222672326724267252672626727267282672926730267312673226733267342673526736267372673826739267402674126742267432674426745267462674726748267492675026751267522675326754267552675626757267582675926760267612676226763267642676526766267672676826769267702677126772267732677426775267762677726778267792678026781267822678326784267852678626787267882678926790267912679226793267942679526796267972679826799268002680126802268032680426805268062680726808268092681026811268122681326814268152681626817268182681926820268212682226823268242682526826268272682826829268302683126832268332683426835268362683726838268392684026841268422684326844268452684626847268482684926850268512685226853268542685526856268572685826859268602686126862268632686426865268662686726868268692687026871268722687326874268752687626877268782687926880268812688226883268842688526886268872688826889268902689126892268932689426895268962689726898268992690026901269022690326904269052690626907269082690926910269112691226913269142691526916269172691826919269202692126922269232692426925269262692726928269292693026931269322693326934269352693626937269382693926940269412694226943269442694526946269472694826949269502695126952269532695426955269562695726958269592696026961269622696326964269652696626967269682696926970269712697226973269742697526976269772697826979269802698126982269832698426985269862698726988269892699026991269922699326994269952699626997269982699927000270012700227003270042700527006270072700827009270102701127012270132701427015270162701727018270192702027021270222702327024270252702627027270282702927030270312703227033270342703527036270372703827039270402704127042270432704427045270462704727048270492705027051270522705327054270552705627057270582705927060270612706227063270642706527066270672706827069270702707127072270732707427075270762707727078270792708027081270822708327084270852708627087270882708927090270912709227093270942709527096270972709827099271002710127102271032710427105271062710727108271092711027111271122711327114271152711627117271182711927120271212712227123271242712527126271272712827129271302713127132271332713427135271362713727138271392714027141271422714327144271452714627147271482714927150271512715227153271542715527156271572715827159271602716127162271632716427165271662716727168271692717027171271722717327174271752717627177271782717927180271812718227183271842718527186271872718827189271902719127192271932719427195271962719727198271992720027201272022720327204272052720627207272082720927210272112721227213272142721527216272172721827219272202722127222272232722427225272262722727228272292723027231272322723327234272352723627237272382723927240272412724227243272442724527246272472724827249272502725127252272532725427255272562725727258272592726027261272622726327264272652726627267272682726927270272712727227273272742727527276272772727827279272802728127282272832728427285272862728727288272892729027291272922729327294272952729627297272982729927300273012730227303273042730527306273072730827309273102731127312273132731427315273162731727318273192732027321273222732327324273252732627327273282732927330273312733227333273342733527336273372733827339273402734127342273432734427345273462734727348273492735027351273522735327354273552735627357273582735927360273612736227363273642736527366273672736827369273702737127372273732737427375273762737727378273792738027381273822738327384273852738627387273882738927390273912739227393273942739527396273972739827399274002740127402274032740427405274062740727408274092741027411274122741327414274152741627417274182741927420274212742227423274242742527426274272742827429274302743127432274332743427435274362743727438274392744027441274422744327444274452744627447274482744927450274512745227453274542745527456274572745827459274602746127462274632746427465274662746727468274692747027471274722747327474274752747627477274782747927480274812748227483274842748527486274872748827489274902749127492274932749427495274962749727498274992750027501275022750327504275052750627507275082750927510275112751227513275142751527516275172751827519275202752127522275232752427525275262752727528275292753027531275322753327534275352753627537275382753927540275412754227543275442754527546275472754827549275502755127552275532755427555275562755727558275592756027561275622756327564275652756627567275682756927570275712757227573275742757527576275772757827579275802758127582275832758427585275862758727588275892759027591275922759327594275952759627597275982759927600276012760227603276042760527606276072760827609276102761127612276132761427615276162761727618276192762027621276222762327624276252762627627276282762927630276312763227633276342763527636276372763827639276402764127642276432764427645276462764727648276492765027651276522765327654276552765627657276582765927660276612766227663276642766527666276672766827669276702767127672276732767427675276762767727678276792768027681276822768327684276852768627687276882768927690276912769227693276942769527696276972769827699277002770127702277032770427705277062770727708277092771027711277122771327714277152771627717277182771927720277212772227723277242772527726277272772827729277302773127732277332773427735277362773727738277392774027741277422774327744277452774627747277482774927750277512775227753277542775527756277572775827759277602776127762277632776427765277662776727768277692777027771277722777327774277752777627777277782777927780277812778227783277842778527786277872778827789277902779127792277932779427795277962779727798277992780027801278022780327804278052780627807278082780927810278112781227813278142781527816278172781827819278202782127822278232782427825278262782727828278292783027831278322783327834278352783627837278382783927840278412784227843278442784527846278472784827849278502785127852278532785427855278562785727858278592786027861278622786327864278652786627867278682786927870278712787227873278742787527876278772787827879278802788127882278832788427885278862788727888278892789027891278922789327894278952789627897278982789927900279012790227903279042790527906279072790827909279102791127912279132791427915279162791727918279192792027921279222792327924279252792627927279282792927930279312793227933279342793527936279372793827939279402794127942279432794427945279462794727948279492795027951279522795327954279552795627957279582795927960279612796227963279642796527966279672796827969279702797127972279732797427975279762797727978279792798027981279822798327984279852798627987279882798927990279912799227993279942799527996279972799827999280002800128002280032800428005280062800728008280092801028011280122801328014280152801628017280182801928020280212802228023280242802528026280272802828029280302803128032280332803428035280362803728038280392804028041280422804328044280452804628047280482804928050280512805228053280542805528056280572805828059280602806128062280632806428065280662806728068280692807028071280722807328074280752807628077280782807928080280812808228083280842808528086280872808828089280902809128092280932809428095280962809728098280992810028101281022810328104281052810628107281082810928110281112811228113281142811528116281172811828119281202812128122281232812428125281262812728128281292813028131281322813328134281352813628137281382813928140281412814228143281442814528146281472814828149281502815128152281532815428155281562815728158281592816028161281622816328164281652816628167281682816928170281712817228173281742817528176281772817828179281802818128182281832818428185281862818728188281892819028191281922819328194281952819628197281982819928200282012820228203282042820528206282072820828209282102821128212282132821428215282162821728218282192822028221282222822328224282252822628227282282822928230282312823228233282342823528236282372823828239282402824128242282432824428245282462824728248282492825028251282522825328254282552825628257282582825928260282612826228263282642826528266282672826828269282702827128272282732827428275282762827728278282792828028281282822828328284282852828628287282882828928290282912829228293282942829528296282972829828299283002830128302283032830428305283062830728308283092831028311283122831328314283152831628317283182831928320283212832228323283242832528326283272832828329283302833128332283332833428335283362833728338283392834028341283422834328344283452834628347283482834928350283512835228353283542835528356283572835828359283602836128362283632836428365283662836728368283692837028371283722837328374283752837628377283782837928380283812838228383283842838528386283872838828389283902839128392283932839428395283962839728398283992840028401284022840328404284052840628407284082840928410284112841228413284142841528416284172841828419284202842128422284232842428425284262842728428284292843028431284322843328434284352843628437284382843928440284412844228443284442844528446284472844828449284502845128452284532845428455284562845728458284592846028461284622846328464284652846628467284682846928470284712847228473284742847528476284772847828479284802848128482284832848428485284862848728488284892849028491284922849328494284952849628497284982849928500285012850228503285042850528506285072850828509285102851128512285132851428515285162851728518285192852028521285222852328524285252852628527285282852928530285312853228533285342853528536285372853828539285402854128542285432854428545285462854728548285492855028551285522855328554285552855628557285582855928560285612856228563285642856528566285672856828569285702857128572285732857428575285762857728578285792858028581285822858328584285852858628587285882858928590285912859228593285942859528596285972859828599286002860128602286032860428605286062860728608286092861028611286122861328614286152861628617286182861928620286212862228623286242862528626286272862828629286302863128632286332863428635286362863728638286392864028641286422864328644286452864628647286482864928650286512865228653286542865528656286572865828659286602866128662286632866428665286662866728668286692867028671286722867328674286752867628677286782867928680286812868228683286842868528686286872868828689286902869128692286932869428695286962869728698286992870028701287022870328704287052870628707287082870928710287112871228713287142871528716287172871828719287202872128722287232872428725287262872728728287292873028731287322873328734287352873628737287382873928740287412874228743287442874528746287472874828749287502875128752287532875428755287562875728758287592876028761287622876328764287652876628767287682876928770287712877228773287742877528776287772877828779287802878128782287832878428785287862878728788287892879028791287922879328794287952879628797287982879928800288012880228803288042880528806288072880828809288102881128812288132881428815288162881728818288192882028821288222882328824288252882628827288282882928830288312883228833288342883528836288372883828839288402884128842288432884428845288462884728848288492885028851288522885328854288552885628857288582885928860288612886228863288642886528866288672886828869288702887128872288732887428875288762887728878288792888028881288822888328884288852888628887288882888928890288912889228893288942889528896288972889828899289002890128902289032890428905289062890728908289092891028911289122891328914289152891628917289182891928920289212892228923289242892528926289272892828929289302893128932289332893428935289362893728938289392894028941289422894328944289452894628947289482894928950289512895228953289542895528956289572895828959289602896128962289632896428965289662896728968289692897028971289722897328974289752897628977289782897928980289812898228983289842898528986289872898828989289902899128992289932899428995289962899728998289992900029001290022900329004290052900629007290082900929010290112901229013290142901529016290172901829019290202902129022290232902429025290262902729028290292903029031290322903329034290352903629037290382903929040290412904229043290442904529046290472904829049290502905129052290532905429055290562905729058290592906029061290622906329064290652906629067290682906929070290712907229073290742907529076290772907829079290802908129082290832908429085290862908729088290892909029091290922909329094290952909629097290982909929100291012910229103291042910529106291072910829109291102911129112291132911429115291162911729118291192912029121291222912329124291252912629127291282912929130291312913229133291342913529136291372913829139291402914129142291432914429145291462914729148291492915029151291522915329154291552915629157291582915929160291612916229163291642916529166291672916829169291702917129172291732917429175291762917729178291792918029181291822918329184291852918629187291882918929190291912919229193291942919529196291972919829199292002920129202292032920429205292062920729208292092921029211292122921329214292152921629217292182921929220292212922229223292242922529226292272922829229292302923129232292332923429235292362923729238292392924029241292422924329244292452924629247292482924929250292512925229253292542925529256292572925829259292602926129262292632926429265292662926729268292692927029271292722927329274292752927629277292782927929280292812928229283292842928529286292872928829289292902929129292292932929429295292962929729298292992930029301293022930329304293052930629307293082930929310293112931229313293142931529316293172931829319293202932129322293232932429325293262932729328293292933029331293322933329334293352933629337293382933929340293412934229343293442934529346293472934829349293502935129352293532935429355293562935729358293592936029361293622936329364293652936629367293682936929370293712937229373293742937529376293772937829379293802938129382293832938429385293862938729388293892939029391293922939329394293952939629397293982939929400294012940229403294042940529406294072940829409294102941129412294132941429415294162941729418294192942029421294222942329424294252942629427294282942929430294312943229433294342943529436294372943829439294402944129442294432944429445294462944729448294492945029451294522945329454294552945629457294582945929460294612946229463294642946529466294672946829469294702947129472294732947429475294762947729478294792948029481294822948329484294852948629487294882948929490294912949229493294942949529496294972949829499295002950129502295032950429505295062950729508295092951029511295122951329514295152951629517295182951929520295212952229523295242952529526295272952829529295302953129532295332953429535295362953729538295392954029541295422954329544295452954629547295482954929550295512955229553295542955529556295572955829559295602956129562295632956429565295662956729568295692957029571295722957329574295752957629577295782957929580295812958229583295842958529586295872958829589295902959129592295932959429595295962959729598295992960029601296022960329604296052960629607296082960929610296112961229613296142961529616296172961829619296202962129622296232962429625296262962729628296292963029631296322963329634296352963629637296382963929640296412964229643296442964529646296472964829649296502965129652296532965429655296562965729658296592966029661296622966329664296652966629667296682966929670296712967229673296742967529676296772967829679296802968129682296832968429685296862968729688296892969029691296922969329694296952969629697296982969929700297012970229703297042970529706297072970829709297102971129712297132971429715297162971729718297192972029721297222972329724297252972629727297282972929730297312973229733297342973529736297372973829739297402974129742297432974429745297462974729748297492975029751297522975329754297552975629757297582975929760297612976229763297642976529766297672976829769297702977129772297732977429775297762977729778297792978029781297822978329784297852978629787297882978929790297912979229793297942979529796297972979829799298002980129802298032980429805298062980729808298092981029811298122981329814298152981629817298182981929820298212982229823298242982529826298272982829829298302983129832298332983429835298362983729838298392984029841298422984329844298452984629847298482984929850298512985229853298542985529856298572985829859298602986129862298632986429865298662986729868298692987029871298722987329874298752987629877298782987929880298812988229883298842988529886298872988829889298902989129892298932989429895298962989729898298992990029901299022990329904299052990629907299082990929910299112991229913299142991529916299172991829919299202992129922299232992429925299262992729928299292993029931299322993329934299352993629937299382993929940299412994229943299442994529946299472994829949299502995129952299532995429955299562995729958299592996029961299622996329964299652996629967299682996929970299712997229973299742997529976299772997829979299802998129982299832998429985299862998729988299892999029991299922999329994299952999629997299982999930000300013000230003300043000530006300073000830009300103001130012300133001430015300163001730018300193002030021300223002330024300253002630027300283002930030300313003230033300343003530036300373003830039300403004130042300433004430045300463004730048300493005030051300523005330054300553005630057300583005930060300613006230063300643006530066300673006830069300703007130072300733007430075300763007730078300793008030081300823008330084300853008630087300883008930090300913009230093300943009530096300973009830099301003010130102301033010430105301063010730108301093011030111301123011330114301153011630117301183011930120301213012230123301243012530126301273012830129301303013130132301333013430135301363013730138301393014030141301423014330144301453014630147301483014930150301513015230153301543015530156301573015830159301603016130162301633016430165301663016730168301693017030171301723017330174301753017630177301783017930180301813018230183301843018530186301873018830189301903019130192301933019430195301963019730198301993020030201302023020330204302053020630207302083020930210302113021230213302143021530216302173021830219302203022130222302233022430225302263022730228302293023030231302323023330234302353023630237302383023930240302413024230243302443024530246302473024830249302503025130252302533025430255302563025730258302593026030261302623026330264302653026630267302683026930270302713027230273302743027530276302773027830279302803028130282302833028430285302863028730288302893029030291302923029330294302953029630297302983029930300303013030230303303043030530306303073030830309303103031130312303133031430315303163031730318303193032030321303223032330324303253032630327303283032930330303313033230333303343033530336303373033830339303403034130342303433034430345303463034730348303493035030351303523035330354303553035630357303583035930360303613036230363303643036530366303673036830369303703037130372303733037430375303763037730378303793038030381303823038330384303853038630387303883038930390303913039230393303943039530396303973039830399304003040130402304033040430405304063040730408304093041030411304123041330414304153041630417304183041930420304213042230423304243042530426304273042830429304303043130432304333043430435304363043730438304393044030441304423044330444304453044630447304483044930450304513045230453304543045530456304573045830459304603046130462304633046430465304663046730468304693047030471304723047330474304753047630477304783047930480304813048230483304843048530486304873048830489304903049130492304933049430495304963049730498304993050030501305023050330504305053050630507305083050930510305113051230513305143051530516305173051830519305203052130522305233052430525305263052730528305293053030531305323053330534305353053630537305383053930540305413054230543305443054530546305473054830549305503055130552305533055430555305563055730558305593056030561305623056330564305653056630567305683056930570305713057230573305743057530576305773057830579305803058130582305833058430585305863058730588305893059030591305923059330594305953059630597305983059930600306013060230603306043060530606306073060830609306103061130612306133061430615306163061730618306193062030621306223062330624306253062630627306283062930630306313063230633306343063530636306373063830639306403064130642306433064430645306463064730648306493065030651306523065330654306553065630657306583065930660306613066230663306643066530666306673066830669306703067130672306733067430675306763067730678306793068030681306823068330684306853068630687306883068930690306913069230693306943069530696306973069830699307003070130702307033070430705307063070730708307093071030711307123071330714307153071630717307183071930720307213072230723307243072530726307273072830729307303073130732307333073430735307363073730738307393074030741307423074330744307453074630747307483074930750307513075230753307543075530756307573075830759307603076130762307633076430765307663076730768307693077030771307723077330774307753077630777307783077930780307813078230783307843078530786307873078830789307903079130792307933079430795307963079730798307993080030801308023080330804308053080630807308083080930810308113081230813308143081530816308173081830819308203082130822308233082430825308263082730828308293083030831308323083330834308353083630837308383083930840308413084230843308443084530846308473084830849308503085130852308533085430855308563085730858308593086030861308623086330864308653086630867308683086930870308713087230873308743087530876308773087830879308803088130882308833088430885308863088730888308893089030891308923089330894308953089630897308983089930900309013090230903309043090530906309073090830909309103091130912309133091430915309163091730918309193092030921309223092330924309253092630927309283092930930309313093230933309343093530936309373093830939309403094130942309433094430945309463094730948309493095030951309523095330954309553095630957309583095930960309613096230963309643096530966309673096830969309703097130972309733097430975309763097730978309793098030981309823098330984309853098630987309883098930990309913099230993309943099530996309973099830999310003100131002310033100431005310063100731008310093101031011310123101331014310153101631017310183101931020310213102231023310243102531026310273102831029310303103131032310333103431035310363103731038310393104031041310423104331044310453104631047310483104931050310513105231053310543105531056310573105831059310603106131062310633106431065310663106731068310693107031071310723107331074310753107631077310783107931080310813108231083310843108531086310873108831089310903109131092310933109431095310963109731098310993110031101311023110331104311053110631107311083110931110311113111231113311143111531116311173111831119311203112131122311233112431125311263112731128311293113031131311323113331134311353113631137311383113931140311413114231143311443114531146311473114831149311503115131152311533115431155311563115731158311593116031161311623116331164311653116631167311683116931170311713117231173311743117531176311773117831179311803118131182311833118431185311863118731188311893119031191311923119331194311953119631197311983119931200312013120231203312043120531206312073120831209312103121131212312133121431215312163121731218312193122031221312223122331224312253122631227312283122931230312313123231233312343123531236312373123831239312403124131242312433124431245312463124731248312493125031251312523125331254312553125631257312583125931260312613126231263312643126531266312673126831269312703127131272312733127431275312763127731278312793128031281312823128331284312853128631287312883128931290312913129231293312943129531296312973129831299313003130131302313033130431305313063130731308313093131031311313123131331314313153131631317313183131931320313213132231323313243132531326313273132831329313303133131332313333133431335313363133731338313393134031341313423134331344313453134631347313483134931350313513135231353313543135531356313573135831359313603136131362313633136431365313663136731368313693137031371313723137331374313753137631377313783137931380313813138231383313843138531386313873138831389313903139131392313933139431395313963139731398313993140031401314023140331404314053140631407314083140931410314113141231413314143141531416314173141831419314203142131422314233142431425314263142731428314293143031431314323143331434314353143631437314383143931440314413144231443314443144531446314473144831449314503145131452314533145431455314563145731458314593146031461314623146331464314653146631467314683146931470314713147231473314743147531476314773147831479314803148131482314833148431485314863148731488314893149031491314923149331494314953149631497314983149931500315013150231503315043150531506315073150831509315103151131512315133151431515315163151731518315193152031521315223152331524315253152631527315283152931530315313153231533315343153531536315373153831539315403154131542315433154431545315463154731548315493155031551315523155331554315553155631557315583155931560315613156231563315643156531566315673156831569315703157131572315733157431575315763157731578315793158031581315823158331584315853158631587315883158931590315913159231593315943159531596315973159831599316003160131602316033160431605316063160731608316093161031611316123161331614316153161631617316183161931620316213162231623316243162531626316273162831629316303163131632316333163431635316363163731638316393164031641316423164331644316453164631647316483164931650316513165231653316543165531656316573165831659316603166131662316633166431665316663166731668316693167031671316723167331674316753167631677316783167931680316813168231683316843168531686316873168831689316903169131692316933169431695316963169731698316993170031701317023170331704317053170631707317083170931710317113171231713317143171531716317173171831719317203172131722317233172431725317263172731728317293173031731317323173331734317353173631737317383173931740317413174231743317443174531746317473174831749317503175131752317533175431755317563175731758317593176031761317623176331764317653176631767317683176931770317713177231773317743177531776317773177831779317803178131782317833178431785317863178731788317893179031791317923179331794317953179631797317983179931800318013180231803318043180531806318073180831809318103181131812318133181431815318163181731818318193182031821318223182331824318253182631827318283182931830318313183231833318343183531836318373183831839318403184131842318433184431845318463184731848318493185031851318523185331854318553185631857318583185931860318613186231863318643186531866318673186831869318703187131872318733187431875318763187731878318793188031881318823188331884318853188631887318883188931890318913189231893318943189531896318973189831899319003190131902319033190431905319063190731908319093191031911319123191331914319153191631917319183191931920319213192231923319243192531926319273192831929319303193131932319333193431935319363193731938319393194031941319423194331944319453194631947319483194931950319513195231953319543195531956319573195831959319603196131962319633196431965319663196731968319693197031971319723197331974319753197631977319783197931980319813198231983319843198531986319873198831989319903199131992319933199431995319963199731998319993200032001320023200332004320053200632007320083200932010320113201232013320143201532016320173201832019320203202132022320233202432025320263202732028320293203032031320323203332034320353203632037320383203932040320413204232043320443204532046320473204832049320503205132052320533205432055320563205732058320593206032061320623206332064320653206632067320683206932070320713207232073320743207532076320773207832079320803208132082320833208432085320863208732088320893209032091320923209332094320953209632097320983209932100321013210232103321043210532106321073210832109321103211132112321133211432115321163211732118321193212032121321223212332124321253212632127321283212932130321313213232133321343213532136321373213832139321403214132142321433214432145321463214732148321493215032151321523215332154321553215632157321583215932160321613216232163321643216532166321673216832169321703217132172321733217432175321763217732178321793218032181321823218332184321853218632187321883218932190321913219232193321943219532196321973219832199322003220132202322033220432205322063220732208322093221032211322123221332214322153221632217322183221932220322213222232223322243222532226322273222832229322303223132232322333223432235322363223732238322393224032241322423224332244322453224632247322483224932250322513225232253322543225532256322573225832259322603226132262322633226432265322663226732268322693227032271322723227332274322753227632277322783227932280322813228232283322843228532286322873228832289322903229132292322933229432295322963229732298322993230032301323023230332304323053230632307323083230932310323113231232313323143231532316323173231832319323203232132322323233232432325323263232732328323293233032331323323233332334323353233632337323383233932340323413234232343323443234532346323473234832349323503235132352323533235432355323563235732358323593236032361323623236332364323653236632367323683236932370323713237232373323743237532376323773237832379323803238132382323833238432385323863238732388323893239032391323923239332394323953239632397323983239932400324013240232403324043240532406324073240832409324103241132412324133241432415324163241732418324193242032421324223242332424324253242632427324283242932430324313243232433324343243532436324373243832439324403244132442324433244432445324463244732448324493245032451324523245332454324553245632457324583245932460324613246232463324643246532466324673246832469324703247132472324733247432475324763247732478324793248032481324823248332484324853248632487324883248932490324913249232493324943249532496324973249832499325003250132502325033250432505325063250732508325093251032511325123251332514325153251632517325183251932520325213252232523325243252532526325273252832529325303253132532325333253432535325363253732538325393254032541325423254332544325453254632547325483254932550325513255232553325543255532556325573255832559325603256132562325633256432565325663256732568325693257032571325723257332574325753257632577325783257932580325813258232583325843258532586325873258832589325903259132592325933259432595325963259732598325993260032601326023260332604326053260632607326083260932610326113261232613326143261532616326173261832619326203262132622326233262432625326263262732628326293263032631326323263332634326353263632637326383263932640326413264232643326443264532646326473264832649326503265132652326533265432655326563265732658326593266032661326623266332664326653266632667326683266932670326713267232673326743267532676326773267832679326803268132682326833268432685326863268732688326893269032691326923269332694326953269632697326983269932700327013270232703327043270532706327073270832709327103271132712327133271432715327163271732718327193272032721327223272332724327253272632727327283272932730327313273232733327343273532736327373273832739327403274132742327433274432745327463274732748327493275032751327523275332754327553275632757327583275932760327613276232763327643276532766327673276832769327703277132772327733277432775327763277732778327793278032781327823278332784327853278632787327883278932790327913279232793327943279532796327973279832799328003280132802328033280432805328063280732808328093281032811328123281332814328153281632817328183281932820328213282232823328243282532826328273282832829328303283132832328333283432835328363283732838328393284032841328423284332844328453284632847328483284932850328513285232853328543285532856328573285832859328603286132862328633286432865328663286732868328693287032871328723287332874328753287632877328783287932880328813288232883328843288532886328873288832889328903289132892328933289432895328963289732898328993290032901329023290332904329053290632907329083290932910329113291232913329143291532916329173291832919329203292132922329233292432925329263292732928329293293032931329323293332934329353293632937329383293932940329413294232943329443294532946329473294832949329503295132952329533295432955329563295732958329593296032961329623296332964329653296632967329683296932970329713297232973329743297532976329773297832979329803298132982329833298432985329863298732988329893299032991329923299332994329953299632997329983299933000330013300233003330043300533006330073300833009330103301133012330133301433015330163301733018330193302033021330223302333024330253302633027330283302933030330313303233033330343303533036330373303833039330403304133042330433304433045330463304733048330493305033051330523305333054330553305633057330583305933060330613306233063330643306533066330673306833069330703307133072330733307433075330763307733078330793308033081330823308333084330853308633087330883308933090330913309233093330943309533096330973309833099331003310133102331033310433105331063310733108331093311033111331123311333114331153311633117331183311933120331213312233123331243312533126331273312833129331303313133132331333313433135331363313733138331393314033141331423314333144331453314633147331483314933150331513315233153331543315533156331573315833159331603316133162331633316433165331663316733168331693317033171331723317333174331753317633177331783317933180331813318233183331843318533186331873318833189331903319133192331933319433195331963319733198331993320033201332023320333204332053320633207332083320933210332113321233213332143321533216332173321833219332203322133222332233322433225332263322733228332293323033231332323323333234332353323633237332383323933240332413324233243332443324533246332473324833249332503325133252332533325433255332563325733258332593326033261332623326333264332653326633267332683326933270332713327233273332743327533276332773327833279332803328133282332833328433285332863328733288332893329033291332923329333294332953329633297332983329933300333013330233303333043330533306333073330833309333103331133312333133331433315333163331733318333193332033321333223332333324333253332633327333283332933330333313333233333333343333533336333373333833339333403334133342333433334433345333463334733348333493335033351333523335333354333553335633357333583335933360333613336233363333643336533366333673336833369333703337133372333733337433375333763337733378333793338033381333823338333384333853338633387333883338933390333913339233393333943339533396333973339833399334003340133402334033340433405334063340733408334093341033411334123341333414334153341633417334183341933420334213342233423334243342533426334273342833429334303343133432334333343433435334363343733438334393344033441334423344333444334453344633447334483344933450334513345233453334543345533456334573345833459334603346133462334633346433465334663346733468334693347033471334723347333474334753347633477334783347933480334813348233483334843348533486334873348833489334903349133492334933349433495334963349733498334993350033501335023350333504335053350633507335083350933510335113351233513335143351533516335173351833519335203352133522335233352433525335263352733528335293353033531335323353333534335353353633537335383353933540335413354233543335443354533546335473354833549335503355133552335533355433555335563355733558335593356033561335623356333564335653356633567335683356933570335713357233573335743357533576335773357833579335803358133582335833358433585335863358733588335893359033591335923359333594335953359633597335983359933600336013360233603336043360533606336073360833609336103361133612336133361433615336163361733618336193362033621336223362333624336253362633627336283362933630336313363233633336343363533636336373363833639336403364133642336433364433645336463364733648336493365033651336523365333654336553365633657336583365933660336613366233663336643366533666336673366833669336703367133672336733367433675336763367733678336793368033681336823368333684336853368633687336883368933690336913369233693336943369533696336973369833699337003370133702337033370433705337063370733708337093371033711337123371333714337153371633717337183371933720337213372233723337243372533726337273372833729337303373133732337333373433735337363373733738337393374033741337423374333744337453374633747337483374933750337513375233753337543375533756337573375833759337603376133762337633376433765337663376733768337693377033771337723377333774337753377633777337783377933780337813378233783337843378533786337873378833789337903379133792337933379433795337963379733798337993380033801338023380333804338053380633807338083380933810338113381233813338143381533816338173381833819338203382133822338233382433825338263382733828338293383033831338323383333834338353383633837338383383933840338413384233843338443384533846338473384833849338503385133852338533385433855338563385733858338593386033861338623386333864338653386633867338683386933870338713387233873338743387533876338773387833879338803388133882338833388433885338863388733888338893389033891338923389333894338953389633897338983389933900339013390233903339043390533906339073390833909339103391133912339133391433915339163391733918339193392033921339223392333924339253392633927339283392933930339313393233933339343393533936339373393833939339403394133942339433394433945339463394733948339493395033951339523395333954339553395633957339583395933960339613396233963339643396533966339673396833969339703397133972339733397433975339763397733978339793398033981339823398333984339853398633987339883398933990339913399233993339943399533996339973399833999340003400134002340033400434005340063400734008340093401034011340123401334014340153401634017340183401934020340213402234023340243402534026340273402834029340303403134032340333403434035340363403734038340393404034041340423404334044340453404634047340483404934050340513405234053340543405534056340573405834059340603406134062340633406434065340663406734068340693407034071340723407334074340753407634077340783407934080340813408234083340843408534086340873408834089340903409134092340933409434095340963409734098340993410034101341023410334104341053410634107341083410934110341113411234113341143411534116341173411834119341203412134122341233412434125341263412734128341293413034131341323413334134341353413634137341383413934140341413414234143341443414534146341473414834149341503415134152341533415434155341563415734158341593416034161341623416334164341653416634167341683416934170341713417234173341743417534176341773417834179341803418134182341833418434185341863418734188341893419034191341923419334194341953419634197341983419934200342013420234203342043420534206342073420834209342103421134212342133421434215342163421734218342193422034221342223422334224342253422634227342283422934230342313423234233342343423534236342373423834239342403424134242342433424434245342463424734248342493425034251342523425334254342553425634257342583425934260342613426234263342643426534266342673426834269342703427134272342733427434275342763427734278342793428034281342823428334284342853428634287342883428934290342913429234293342943429534296342973429834299343003430134302343033430434305343063430734308343093431034311343123431334314343153431634317343183431934320343213432234323343243432534326343273432834329343303433134332343333433434335343363433734338343393434034341343423434334344343453434634347343483434934350343513435234353343543435534356343573435834359343603436134362343633436434365343663436734368343693437034371343723437334374343753437634377343783437934380343813438234383343843438534386343873438834389343903439134392343933439434395343963439734398343993440034401344023440334404344053440634407344083440934410344113441234413344143441534416344173441834419344203442134422344233442434425344263442734428344293443034431344323443334434344353443634437344383443934440344413444234443344443444534446344473444834449344503445134452344533445434455344563445734458344593446034461344623446334464344653446634467344683446934470344713447234473344743447534476344773447834479344803448134482344833448434485344863448734488344893449034491344923449334494344953449634497344983449934500345013450234503345043450534506345073450834509345103451134512345133451434515345163451734518345193452034521345223452334524345253452634527345283452934530345313453234533345343453534536345373453834539345403454134542345433454434545345463454734548345493455034551345523455334554345553455634557345583455934560345613456234563345643456534566345673456834569345703457134572345733457434575345763457734578345793458034581345823458334584345853458634587345883458934590345913459234593345943459534596345973459834599346003460134602346033460434605346063460734608346093461034611346123461334614346153461634617346183461934620346213462234623346243462534626346273462834629346303463134632346333463434635346363463734638346393464034641346423464334644346453464634647346483464934650346513465234653346543465534656346573465834659346603466134662346633466434665346663466734668346693467034671346723467334674346753467634677346783467934680346813468234683346843468534686346873468834689346903469134692346933469434695346963469734698346993470034701347023470334704347053470634707347083470934710347113471234713347143471534716347173471834719347203472134722347233472434725347263472734728347293473034731347323473334734347353473634737347383473934740347413474234743347443474534746347473474834749347503475134752347533475434755347563475734758347593476034761347623476334764347653476634767347683476934770347713477234773347743477534776347773477834779347803478134782347833478434785347863478734788347893479034791347923479334794347953479634797347983479934800348013480234803348043480534806348073480834809348103481134812348133481434815348163481734818348193482034821348223482334824348253482634827348283482934830348313483234833348343483534836348373483834839348403484134842348433484434845348463484734848348493485034851348523485334854348553485634857348583485934860348613486234863348643486534866348673486834869348703487134872348733487434875348763487734878348793488034881348823488334884348853488634887348883488934890348913489234893348943489534896348973489834899349003490134902349033490434905349063490734908349093491034911349123491334914349153491634917349183491934920349213492234923349243492534926349273492834929349303493134932349333493434935349363493734938349393494034941349423494334944349453494634947349483494934950349513495234953349543495534956349573495834959349603496134962349633496434965349663496734968349693497034971349723497334974349753497634977349783497934980349813498234983349843498534986349873498834989349903499134992349933499434995349963499734998349993500035001350023500335004350053500635007350083500935010350113501235013350143501535016350173501835019350203502135022350233502435025350263502735028350293503035031350323503335034350353503635037350383503935040350413504235043350443504535046350473504835049350503505135052350533505435055350563505735058350593506035061350623506335064350653506635067350683506935070350713507235073350743507535076350773507835079350803508135082350833508435085350863508735088350893509035091350923509335094350953509635097350983509935100351013510235103351043510535106351073510835109351103511135112351133511435115351163511735118351193512035121351223512335124351253512635127351283512935130351313513235133351343513535136351373513835139351403514135142351433514435145351463514735148351493515035151351523515335154351553515635157351583515935160351613516235163351643516535166351673516835169351703517135172351733517435175351763517735178351793518035181351823518335184351853518635187351883518935190351913519235193351943519535196351973519835199352003520135202352033520435205352063520735208352093521035211352123521335214352153521635217352183521935220352213522235223352243522535226352273522835229352303523135232352333523435235352363523735238352393524035241352423524335244352453524635247352483524935250352513525235253352543525535256352573525835259352603526135262352633526435265352663526735268352693527035271352723527335274352753527635277352783527935280352813528235283352843528535286352873528835289352903529135292352933529435295352963529735298352993530035301353023530335304353053530635307353083530935310353113531235313353143531535316353173531835319353203532135322353233532435325353263532735328353293533035331353323533335334353353533635337353383533935340353413534235343353443534535346353473534835349353503535135352353533535435355353563535735358353593536035361353623536335364353653536635367353683536935370353713537235373353743537535376353773537835379353803538135382353833538435385353863538735388353893539035391353923539335394353953539635397353983539935400354013540235403354043540535406354073540835409354103541135412354133541435415354163541735418354193542035421354223542335424354253542635427354283542935430354313543235433354343543535436354373543835439354403544135442354433544435445354463544735448354493545035451354523545335454354553545635457354583545935460354613546235463354643546535466354673546835469354703547135472354733547435475354763547735478354793548035481354823548335484354853548635487354883548935490354913549235493354943549535496354973549835499355003550135502355033550435505355063550735508355093551035511355123551335514355153551635517355183551935520355213552235523355243552535526355273552835529355303553135532355333553435535355363553735538355393554035541355423554335544355453554635547355483554935550355513555235553355543555535556355573555835559355603556135562355633556435565355663556735568355693557035571355723557335574355753557635577355783557935580355813558235583355843558535586355873558835589355903559135592355933559435595355963559735598355993560035601356023560335604356053560635607356083560935610356113561235613356143561535616356173561835619356203562135622356233562435625356263562735628356293563035631356323563335634356353563635637356383563935640356413564235643356443564535646356473564835649356503565135652356533565435655356563565735658356593566035661356623566335664356653566635667356683566935670356713567235673356743567535676356773567835679356803568135682356833568435685356863568735688356893569035691356923569335694356953569635697356983569935700357013570235703357043570535706357073570835709357103571135712357133571435715357163571735718357193572035721357223572335724357253572635727357283572935730357313573235733357343573535736357373573835739357403574135742357433574435745357463574735748357493575035751357523575335754357553575635757357583575935760357613576235763357643576535766357673576835769357703577135772357733577435775357763577735778357793578035781357823578335784357853578635787357883578935790357913579235793357943579535796357973579835799358003580135802358033580435805358063580735808358093581035811358123581335814358153581635817358183581935820358213582235823358243582535826358273582835829358303583135832358333583435835358363583735838358393584035841358423584335844358453584635847358483584935850358513585235853358543585535856358573585835859358603586135862358633586435865358663586735868358693587035871358723587335874358753587635877358783587935880358813588235883358843588535886358873588835889358903589135892358933589435895358963589735898358993590035901359023590335904359053590635907359083590935910359113591235913359143591535916359173591835919359203592135922359233592435925359263592735928359293593035931359323593335934359353593635937359383593935940359413594235943359443594535946359473594835949359503595135952359533595435955359563595735958359593596035961359623596335964359653596635967359683596935970359713597235973359743597535976359773597835979359803598135982359833598435985359863598735988359893599035991359923599335994359953599635997359983599936000360013600236003360043600536006360073600836009360103601136012360133601436015360163601736018360193602036021360223602336024360253602636027360283602936030360313603236033360343603536036360373603836039360403604136042360433604436045360463604736048360493605036051360523605336054360553605636057360583605936060360613606236063360643606536066360673606836069360703607136072360733607436075360763607736078360793608036081360823608336084360853608636087360883608936090360913609236093360943609536096360973609836099361003610136102361033610436105361063610736108361093611036111361123611336114361153611636117361183611936120361213612236123361243612536126361273612836129361303613136132361333613436135361363613736138361393614036141361423614336144361453614636147361483614936150361513615236153361543615536156361573615836159361603616136162361633616436165361663616736168361693617036171361723617336174361753617636177361783617936180361813618236183361843618536186361873618836189361903619136192361933619436195361963619736198361993620036201362023620336204362053620636207362083620936210362113621236213362143621536216362173621836219362203622136222362233622436225362263622736228362293623036231362323623336234362353623636237362383623936240362413624236243362443624536246362473624836249362503625136252362533625436255362563625736258362593626036261362623626336264362653626636267362683626936270362713627236273362743627536276362773627836279362803628136282362833628436285362863628736288362893629036291362923629336294362953629636297362983629936300363013630236303363043630536306363073630836309363103631136312363133631436315363163631736318363193632036321363223632336324363253632636327363283632936330363313633236333363343633536336363373633836339363403634136342363433634436345363463634736348363493635036351363523635336354363553635636357363583635936360363613636236363363643636536366363673636836369363703637136372363733637436375363763637736378363793638036381363823638336384363853638636387363883638936390363913639236393363943639536396363973639836399364003640136402364033640436405364063640736408364093641036411364123641336414364153641636417364183641936420364213642236423364243642536426364273642836429364303643136432364333643436435364363643736438364393644036441364423644336444364453644636447364483644936450364513645236453364543645536456364573645836459364603646136462364633646436465364663646736468364693647036471364723647336474364753647636477364783647936480364813648236483364843648536486364873648836489364903649136492364933649436495364963649736498364993650036501365023650336504365053650636507365083650936510365113651236513365143651536516365173651836519365203652136522365233652436525365263652736528365293653036531365323653336534365353653636537365383653936540365413654236543365443654536546365473654836549365503655136552365533655436555365563655736558365593656036561365623656336564365653656636567365683656936570365713657236573365743657536576365773657836579365803658136582365833658436585365863658736588365893659036591365923659336594365953659636597365983659936600366013660236603366043660536606366073660836609366103661136612366133661436615366163661736618366193662036621366223662336624366253662636627366283662936630366313663236633366343663536636366373663836639366403664136642366433664436645366463664736648366493665036651366523665336654366553665636657366583665936660366613666236663366643666536666366673666836669366703667136672366733667436675366763667736678366793668036681366823668336684366853668636687366883668936690366913669236693366943669536696366973669836699367003670136702367033670436705367063670736708367093671036711367123671336714367153671636717367183671936720367213672236723367243672536726367273672836729367303673136732367333673436735367363673736738367393674036741367423674336744367453674636747367483674936750367513675236753367543675536756367573675836759367603676136762367633676436765367663676736768367693677036771367723677336774367753677636777367783677936780367813678236783367843678536786367873678836789367903679136792367933679436795367963679736798367993680036801368023680336804368053680636807368083680936810368113681236813368143681536816368173681836819368203682136822368233682436825368263682736828368293683036831368323683336834368353683636837368383683936840368413684236843368443684536846368473684836849368503685136852368533685436855368563685736858368593686036861368623686336864368653686636867368683686936870368713687236873368743687536876368773687836879368803688136882368833688436885368863688736888368893689036891368923689336894368953689636897368983689936900369013690236903369043690536906369073690836909369103691136912369133691436915369163691736918369193692036921369223692336924369253692636927369283692936930369313693236933369343693536936369373693836939369403694136942369433694436945369463694736948369493695036951369523695336954369553695636957369583695936960369613696236963369643696536966369673696836969369703697136972369733697436975369763697736978369793698036981369823698336984369853698636987369883698936990369913699236993369943699536996369973699836999370003700137002370033700437005370063700737008370093701037011370123701337014370153701637017370183701937020370213702237023370243702537026370273702837029370303703137032370333703437035370363703737038370393704037041370423704337044370453704637047370483704937050370513705237053370543705537056370573705837059370603706137062370633706437065370663706737068370693707037071370723707337074370753707637077370783707937080370813708237083370843708537086370873708837089370903709137092370933709437095370963709737098370993710037101371023710337104371053710637107371083710937110371113711237113371143711537116371173711837119371203712137122371233712437125371263712737128371293713037131371323713337134371353713637137371383713937140371413714237143371443714537146371473714837149371503715137152371533715437155371563715737158371593716037161371623716337164371653716637167371683716937170371713717237173371743717537176371773717837179371803718137182371833718437185371863718737188371893719037191371923719337194371953719637197371983719937200372013720237203372043720537206372073720837209372103721137212372133721437215372163721737218372193722037221372223722337224372253722637227372283722937230372313723237233372343723537236372373723837239372403724137242372433724437245372463724737248372493725037251372523725337254372553725637257372583725937260372613726237263372643726537266372673726837269372703727137272372733727437275372763727737278372793728037281372823728337284372853728637287372883728937290372913729237293372943729537296372973729837299373003730137302373033730437305373063730737308373093731037311373123731337314373153731637317373183731937320373213732237323373243732537326373273732837329373303733137332373333733437335373363733737338373393734037341373423734337344373453734637347373483734937350373513735237353373543735537356373573735837359373603736137362373633736437365373663736737368373693737037371373723737337374373753737637377373783737937380373813738237383373843738537386373873738837389373903739137392373933739437395373963739737398373993740037401374023740337404374053740637407374083740937410374113741237413374143741537416374173741837419374203742137422374233742437425374263742737428374293743037431374323743337434374353743637437374383743937440374413744237443374443744537446374473744837449374503745137452374533745437455374563745737458374593746037461374623746337464374653746637467374683746937470374713747237473374743747537476374773747837479374803748137482374833748437485374863748737488374893749037491374923749337494374953749637497374983749937500375013750237503375043750537506375073750837509375103751137512375133751437515375163751737518375193752037521375223752337524375253752637527375283752937530375313753237533375343753537536375373753837539375403754137542375433754437545375463754737548375493755037551375523755337554375553755637557375583755937560375613756237563375643756537566375673756837569375703757137572375733757437575375763757737578375793758037581375823758337584375853758637587375883758937590375913759237593375943759537596375973759837599376003760137602376033760437605376063760737608376093761037611376123761337614376153761637617376183761937620376213762237623376243762537626376273762837629376303763137632376333763437635376363763737638376393764037641376423764337644376453764637647376483764937650376513765237653376543765537656376573765837659376603766137662376633766437665376663766737668376693767037671376723767337674376753767637677376783767937680376813768237683376843768537686376873768837689376903769137692376933769437695376963769737698376993770037701377023770337704377053770637707377083770937710377113771237713377143771537716377173771837719377203772137722377233772437725377263772737728377293773037731377323773337734377353773637737377383773937740377413774237743377443774537746377473774837749377503775137752377533775437755377563775737758377593776037761377623776337764377653776637767377683776937770377713777237773377743777537776377773777837779377803778137782377833778437785377863778737788377893779037791377923779337794377953779637797377983779937800378013780237803378043780537806378073780837809378103781137812378133781437815378163781737818378193782037821378223782337824378253782637827378283782937830378313783237833378343783537836378373783837839378403784137842378433784437845378463784737848378493785037851378523785337854378553785637857378583785937860378613786237863378643786537866378673786837869378703787137872378733787437875378763787737878378793788037881378823788337884378853788637887378883788937890378913789237893378943789537896378973789837899379003790137902379033790437905379063790737908379093791037911379123791337914379153791637917379183791937920379213792237923379243792537926379273792837929379303793137932379333793437935379363793737938379393794037941379423794337944379453794637947379483794937950379513795237953379543795537956379573795837959379603796137962379633796437965379663796737968379693797037971379723797337974379753797637977379783797937980379813798237983379843798537986379873798837989379903799137992379933799437995379963799737998379993800038001380023800338004380053800638007380083800938010380113801238013380143801538016380173801838019380203802138022380233802438025380263802738028380293803038031380323803338034380353803638037380383803938040380413804238043380443804538046380473804838049380503805138052380533805438055380563805738058380593806038061380623806338064380653806638067380683806938070380713807238073380743807538076380773807838079380803808138082380833808438085380863808738088380893809038091380923809338094380953809638097380983809938100381013810238103381043810538106381073810838109381103811138112381133811438115381163811738118381193812038121381223812338124381253812638127381283812938130381313813238133381343813538136381373813838139381403814138142381433814438145381463814738148381493815038151381523815338154381553815638157381583815938160381613816238163381643816538166381673816838169381703817138172381733817438175381763817738178381793818038181381823818338184381853818638187381883818938190381913819238193381943819538196381973819838199382003820138202382033820438205382063820738208382093821038211382123821338214382153821638217382183821938220382213822238223382243822538226382273822838229382303823138232382333823438235382363823738238382393824038241382423824338244382453824638247382483824938250382513825238253382543825538256382573825838259382603826138262382633826438265382663826738268382693827038271382723827338274382753827638277382783827938280382813828238283382843828538286382873828838289382903829138292382933829438295382963829738298382993830038301383023830338304383053830638307383083830938310383113831238313383143831538316383173831838319383203832138322383233832438325383263832738328383293833038331383323833338334383353833638337383383833938340383413834238343383443834538346383473834838349383503835138352383533835438355383563835738358383593836038361383623836338364383653836638367383683836938370383713837238373383743837538376383773837838379383803838138382383833838438385383863838738388383893839038391383923839338394383953839638397383983839938400384013840238403384043840538406384073840838409384103841138412384133841438415384163841738418384193842038421384223842338424384253842638427384283842938430384313843238433384343843538436384373843838439384403844138442384433844438445384463844738448384493845038451384523845338454384553845638457384583845938460384613846238463384643846538466384673846838469384703847138472384733847438475384763847738478384793848038481384823848338484384853848638487384883848938490384913849238493384943849538496384973849838499385003850138502385033850438505385063850738508385093851038511385123851338514385153851638517385183851938520385213852238523385243852538526385273852838529385303853138532385333853438535385363853738538385393854038541385423854338544385453854638547385483854938550385513855238553385543855538556385573855838559385603856138562385633856438565385663856738568385693857038571385723857338574385753857638577385783857938580385813858238583385843858538586385873858838589385903859138592385933859438595385963859738598385993860038601386023860338604386053860638607386083860938610386113861238613386143861538616386173861838619386203862138622386233862438625386263862738628386293863038631386323863338634386353863638637386383863938640386413864238643386443864538646386473864838649386503865138652386533865438655386563865738658386593866038661386623866338664386653866638667386683866938670386713867238673386743867538676386773867838679386803868138682386833868438685386863868738688386893869038691386923869338694386953869638697386983869938700387013870238703387043870538706387073870838709387103871138712387133871438715387163871738718387193872038721387223872338724387253872638727387283872938730387313873238733387343873538736387373873838739387403874138742387433874438745387463874738748387493875038751387523875338754387553875638757387583875938760387613876238763387643876538766387673876838769387703877138772387733877438775387763877738778387793878038781387823878338784387853878638787387883878938790387913879238793387943879538796387973879838799388003880138802388033880438805388063880738808388093881038811388123881338814388153881638817388183881938820388213882238823388243882538826388273882838829388303883138832388333883438835388363883738838388393884038841388423884338844388453884638847388483884938850388513885238853388543885538856388573885838859388603886138862388633886438865388663886738868388693887038871388723887338874388753887638877388783887938880388813888238883388843888538886388873888838889388903889138892388933889438895388963889738898388993890038901389023890338904389053890638907389083890938910389113891238913389143891538916389173891838919389203892138922389233892438925389263892738928389293893038931389323893338934389353893638937389383893938940389413894238943389443894538946389473894838949389503895138952389533895438955389563895738958389593896038961389623896338964389653896638967389683896938970389713897238973389743897538976389773897838979389803898138982389833898438985389863898738988389893899038991389923899338994389953899638997389983899939000390013900239003390043900539006390073900839009390103901139012390133901439015390163901739018390193902039021390223902339024390253902639027390283902939030390313903239033390343903539036390373903839039390403904139042390433904439045390463904739048390493905039051390523905339054390553905639057390583905939060390613906239063390643906539066390673906839069390703907139072390733907439075390763907739078390793908039081390823908339084390853908639087390883908939090390913909239093390943909539096390973909839099391003910139102391033910439105391063910739108391093911039111391123911339114391153911639117391183911939120391213912239123391243912539126391273912839129391303913139132391333913439135391363913739138391393914039141391423914339144391453914639147391483914939150391513915239153391543915539156391573915839159391603916139162391633916439165391663916739168391693917039171391723917339174391753917639177391783917939180391813918239183391843918539186391873918839189391903919139192391933919439195391963919739198391993920039201392023920339204392053920639207392083920939210392113921239213392143921539216392173921839219392203922139222392233922439225392263922739228392293923039231392323923339234392353923639237392383923939240392413924239243392443924539246392473924839249392503925139252392533925439255392563925739258392593926039261392623926339264392653926639267392683926939270392713927239273392743927539276392773927839279392803928139282392833928439285392863928739288392893929039291392923929339294392953929639297392983929939300393013930239303393043930539306393073930839309393103931139312393133931439315393163931739318393193932039321393223932339324393253932639327393283932939330393313933239333393343933539336393373933839339393403934139342393433934439345393463934739348393493935039351393523935339354393553935639357393583935939360393613936239363393643936539366393673936839369393703937139372393733937439375393763937739378393793938039381393823938339384393853938639387393883938939390393913939239393393943939539396393973939839399394003940139402394033940439405394063940739408394093941039411394123941339414394153941639417394183941939420394213942239423394243942539426394273942839429394303943139432394333943439435394363943739438394393944039441394423944339444394453944639447394483944939450394513945239453394543945539456394573945839459394603946139462394633946439465394663946739468394693947039471394723947339474394753947639477394783947939480394813948239483394843948539486394873948839489394903949139492394933949439495394963949739498394993950039501395023950339504395053950639507395083950939510395113951239513395143951539516395173951839519395203952139522395233952439525395263952739528395293953039531395323953339534395353953639537395383953939540395413954239543395443954539546395473954839549395503955139552395533955439555395563955739558395593956039561395623956339564395653956639567395683956939570395713957239573395743957539576395773957839579395803958139582395833958439585395863958739588395893959039591395923959339594395953959639597395983959939600396013960239603396043960539606396073960839609396103961139612396133961439615396163961739618396193962039621396223962339624396253962639627396283962939630396313963239633396343963539636396373963839639396403964139642396433964439645396463964739648396493965039651396523965339654396553965639657396583965939660396613966239663396643966539666396673966839669396703967139672396733967439675396763967739678396793968039681396823968339684396853968639687396883968939690396913969239693396943969539696396973969839699397003970139702397033970439705397063970739708397093971039711397123971339714397153971639717397183971939720397213972239723397243972539726397273972839729397303973139732397333973439735397363973739738397393974039741397423974339744397453974639747397483974939750397513975239753397543975539756397573975839759397603976139762397633976439765397663976739768397693977039771397723977339774397753977639777397783977939780397813978239783397843978539786397873978839789397903979139792397933979439795397963979739798397993980039801398023980339804398053980639807398083980939810398113981239813398143981539816398173981839819398203982139822398233982439825398263982739828398293983039831398323983339834398353983639837398383983939840398413984239843398443984539846398473984839849398503985139852398533985439855398563985739858398593986039861398623986339864398653986639867398683986939870398713987239873398743987539876398773987839879398803988139882398833988439885398863988739888398893989039891398923989339894398953989639897398983989939900399013990239903399043990539906399073990839909399103991139912399133991439915399163991739918399193992039921399223992339924399253992639927399283992939930399313993239933399343993539936399373993839939399403994139942399433994439945399463994739948399493995039951399523995339954399553995639957399583995939960399613996239963399643996539966399673996839969399703997139972399733997439975399763997739978399793998039981399823998339984399853998639987399883998939990399913999239993399943999539996399973999839999400004000140002400034000440005400064000740008400094001040011400124001340014400154001640017400184001940020400214002240023400244002540026400274002840029400304003140032400334003440035400364003740038400394004040041400424004340044400454004640047400484004940050400514005240053400544005540056400574005840059400604006140062400634006440065400664006740068400694007040071400724007340074400754007640077400784007940080400814008240083400844008540086400874008840089400904009140092400934009440095400964009740098400994010040101401024010340104401054010640107401084010940110401114011240113401144011540116401174011840119401204012140122401234012440125401264012740128401294013040131401324013340134401354013640137401384013940140401414014240143401444014540146401474014840149401504015140152401534015440155401564015740158401594016040161401624016340164401654016640167401684016940170401714017240173401744017540176401774017840179401804018140182401834018440185401864018740188401894019040191401924019340194401954019640197401984019940200402014020240203402044020540206402074020840209402104021140212402134021440215402164021740218402194022040221402224022340224402254022640227402284022940230402314023240233402344023540236402374023840239402404024140242402434024440245402464024740248402494025040251402524025340254402554025640257402584025940260402614026240263402644026540266402674026840269402704027140272402734027440275402764027740278402794028040281402824028340284402854028640287402884028940290402914029240293402944029540296402974029840299403004030140302403034030440305403064030740308403094031040311403124031340314403154031640317403184031940320403214032240323403244032540326403274032840329403304033140332403334033440335403364033740338403394034040341403424034340344403454034640347403484034940350403514035240353403544035540356403574035840359403604036140362403634036440365403664036740368403694037040371403724037340374403754037640377403784037940380403814038240383403844038540386403874038840389403904039140392403934039440395403964039740398403994040040401404024040340404404054040640407404084040940410404114041240413404144041540416404174041840419404204042140422404234042440425404264042740428404294043040431404324043340434404354043640437404384043940440404414044240443404444044540446404474044840449404504045140452404534045440455404564045740458404594046040461404624046340464404654046640467404684046940470404714047240473404744047540476404774047840479404804048140482404834048440485404864048740488404894049040491404924049340494404954049640497404984049940500405014050240503405044050540506405074050840509405104051140512405134051440515405164051740518405194052040521405224052340524405254052640527405284052940530405314053240533405344053540536405374053840539405404054140542405434054440545405464054740548405494055040551405524055340554405554055640557405584055940560405614056240563405644056540566405674056840569405704057140572405734057440575405764057740578405794058040581405824058340584405854058640587405884058940590405914059240593405944059540596405974059840599406004060140602406034060440605406064060740608406094061040611406124061340614406154061640617406184061940620406214062240623406244062540626406274062840629406304063140632406334063440635406364063740638406394064040641406424064340644406454064640647406484064940650406514065240653406544065540656406574065840659406604066140662406634066440665406664066740668406694067040671406724067340674406754067640677406784067940680406814068240683406844068540686406874068840689406904069140692406934069440695406964069740698406994070040701407024070340704407054070640707407084070940710407114071240713407144071540716407174071840719407204072140722407234072440725407264072740728407294073040731407324073340734407354073640737407384073940740407414074240743407444074540746407474074840749407504075140752407534075440755407564075740758407594076040761407624076340764407654076640767407684076940770407714077240773407744077540776407774077840779407804078140782407834078440785407864078740788407894079040791407924079340794407954079640797407984079940800408014080240803408044080540806408074080840809408104081140812408134081440815408164081740818408194082040821408224082340824408254082640827408284082940830408314083240833408344083540836408374083840839408404084140842408434084440845408464084740848408494085040851408524085340854408554085640857408584085940860408614086240863408644086540866408674086840869408704087140872408734087440875408764087740878408794088040881408824088340884408854088640887408884088940890408914089240893408944089540896408974089840899409004090140902409034090440905409064090740908409094091040911409124091340914409154091640917409184091940920409214092240923409244092540926409274092840929409304093140932409334093440935409364093740938409394094040941409424094340944409454094640947409484094940950409514095240953409544095540956409574095840959409604096140962409634096440965409664096740968409694097040971409724097340974409754097640977409784097940980409814098240983409844098540986409874098840989409904099140992409934099440995409964099740998409994100041001410024100341004410054100641007410084100941010410114101241013410144101541016410174101841019410204102141022410234102441025410264102741028410294103041031410324103341034410354103641037410384103941040410414104241043410444104541046410474104841049410504105141052410534105441055410564105741058410594106041061410624106341064410654106641067410684106941070410714107241073410744107541076410774107841079410804108141082410834108441085410864108741088410894109041091410924109341094410954109641097410984109941100411014110241103411044110541106411074110841109411104111141112411134111441115411164111741118411194112041121411224112341124411254112641127411284112941130411314113241133411344113541136411374113841139411404114141142411434114441145411464114741148411494115041151411524115341154411554115641157411584115941160411614116241163411644116541166411674116841169411704117141172411734117441175411764117741178411794118041181411824118341184411854118641187411884118941190411914119241193411944119541196411974119841199412004120141202412034120441205412064120741208412094121041211412124121341214412154121641217412184121941220412214122241223412244122541226412274122841229412304123141232412334123441235412364123741238412394124041241412424124341244412454124641247412484124941250412514125241253412544125541256412574125841259412604126141262412634126441265412664126741268412694127041271412724127341274412754127641277412784127941280412814128241283412844128541286412874128841289412904129141292412934129441295412964129741298412994130041301413024130341304413054130641307413084130941310413114131241313413144131541316413174131841319413204132141322413234132441325413264132741328413294133041331413324133341334413354133641337413384133941340413414134241343413444134541346413474134841349413504135141352413534135441355413564135741358413594136041361413624136341364413654136641367413684136941370413714137241373413744137541376413774137841379413804138141382413834138441385413864138741388413894139041391413924139341394413954139641397413984139941400414014140241403414044140541406414074140841409414104141141412414134141441415414164141741418414194142041421414224142341424414254142641427414284142941430414314143241433414344143541436414374143841439414404144141442414434144441445414464144741448414494145041451414524145341454414554145641457414584145941460414614146241463414644146541466414674146841469414704147141472414734147441475414764147741478414794148041481414824148341484414854148641487414884148941490414914149241493414944149541496414974149841499415004150141502415034150441505415064150741508415094151041511415124151341514415154151641517415184151941520415214152241523415244152541526415274152841529415304153141532415334153441535415364153741538415394154041541415424154341544415454154641547415484154941550415514155241553415544155541556415574155841559415604156141562415634156441565415664156741568415694157041571415724157341574415754157641577415784157941580415814158241583415844158541586415874158841589415904159141592415934159441595415964159741598415994160041601416024160341604416054160641607416084160941610416114161241613416144161541616416174161841619416204162141622416234162441625416264162741628416294163041631416324163341634416354163641637416384163941640416414164241643416444164541646416474164841649416504165141652416534165441655416564165741658416594166041661416624166341664416654166641667416684166941670416714167241673416744167541676416774167841679416804168141682416834168441685416864168741688416894169041691416924169341694416954169641697416984169941700417014170241703417044170541706417074170841709417104171141712417134171441715417164171741718417194172041721417224172341724417254172641727417284172941730417314173241733417344173541736417374173841739417404174141742417434174441745417464174741748417494175041751417524175341754417554175641757417584175941760417614176241763417644176541766417674176841769417704177141772417734177441775417764177741778417794178041781417824178341784417854178641787417884178941790417914179241793417944179541796417974179841799418004180141802418034180441805418064180741808418094181041811418124181341814418154181641817418184181941820418214182241823418244182541826418274182841829418304183141832418334183441835418364183741838418394184041841418424184341844418454184641847418484184941850418514185241853418544185541856418574185841859418604186141862418634186441865418664186741868418694187041871418724187341874418754187641877418784187941880418814188241883418844188541886418874188841889418904189141892418934189441895418964189741898418994190041901419024190341904419054190641907419084190941910419114191241913419144191541916419174191841919419204192141922419234192441925419264192741928419294193041931419324193341934419354193641937419384193941940419414194241943419444194541946419474194841949419504195141952419534195441955419564195741958419594196041961419624196341964419654196641967419684196941970419714197241973419744197541976419774197841979419804198141982419834198441985419864198741988419894199041991419924199341994419954199641997419984199942000420014200242003420044200542006420074200842009420104201142012420134201442015420164201742018420194202042021420224202342024420254202642027420284202942030420314203242033420344203542036420374203842039420404204142042420434204442045420464204742048420494205042051420524205342054420554205642057420584205942060420614206242063420644206542066420674206842069420704207142072420734207442075420764207742078420794208042081420824208342084420854208642087420884208942090420914209242093420944209542096420974209842099421004210142102421034210442105421064210742108421094211042111421124211342114421154211642117421184211942120421214212242123421244212542126421274212842129421304213142132421334213442135421364213742138421394214042141421424214342144421454214642147421484214942150421514215242153421544215542156421574215842159421604216142162421634216442165421664216742168421694217042171421724217342174421754217642177421784217942180421814218242183421844218542186421874218842189421904219142192421934219442195421964219742198421994220042201422024220342204422054220642207422084220942210422114221242213422144221542216422174221842219422204222142222422234222442225422264222742228422294223042231422324223342234422354223642237422384223942240422414224242243422444224542246422474224842249422504225142252422534225442255422564225742258422594226042261422624226342264422654226642267422684226942270422714227242273422744227542276422774227842279422804228142282422834228442285422864228742288422894229042291422924229342294422954229642297422984229942300423014230242303423044230542306423074230842309423104231142312423134231442315423164231742318423194232042321423224232342324423254232642327423284232942330423314233242333423344233542336423374233842339423404234142342423434234442345423464234742348423494235042351423524235342354423554235642357423584235942360423614236242363423644236542366423674236842369423704237142372423734237442375423764237742378423794238042381423824238342384423854238642387423884238942390423914239242393423944239542396423974239842399424004240142402424034240442405424064240742408424094241042411424124241342414424154241642417424184241942420424214242242423424244242542426424274242842429424304243142432424334243442435424364243742438424394244042441424424244342444424454244642447424484244942450424514245242453424544245542456424574245842459424604246142462424634246442465424664246742468424694247042471424724247342474424754247642477424784247942480424814248242483424844248542486424874248842489424904249142492424934249442495424964249742498424994250042501425024250342504425054250642507425084250942510425114251242513425144251542516425174251842519425204252142522425234252442525425264252742528425294253042531425324253342534425354253642537425384253942540425414254242543425444254542546425474254842549425504255142552425534255442555425564255742558425594256042561425624256342564425654256642567425684256942570425714257242573425744257542576425774257842579425804258142582425834258442585425864258742588425894259042591425924259342594425954259642597425984259942600426014260242603426044260542606426074260842609426104261142612426134261442615426164261742618426194262042621426224262342624426254262642627426284262942630426314263242633426344263542636426374263842639426404264142642426434264442645426464264742648426494265042651426524265342654426554265642657426584265942660426614266242663426644266542666426674266842669426704267142672426734267442675426764267742678426794268042681426824268342684426854268642687426884268942690426914269242693426944269542696426974269842699427004270142702427034270442705427064270742708427094271042711427124271342714427154271642717427184271942720427214272242723427244272542726427274272842729427304273142732427334273442735427364273742738427394274042741427424274342744427454274642747427484274942750427514275242753427544275542756427574275842759427604276142762427634276442765427664276742768427694277042771427724277342774427754277642777427784277942780427814278242783427844278542786427874278842789427904279142792427934279442795427964279742798427994280042801428024280342804428054280642807428084280942810428114281242813428144281542816428174281842819428204282142822428234282442825428264282742828428294283042831428324283342834428354283642837428384283942840428414284242843428444284542846428474284842849428504285142852428534285442855428564285742858428594286042861428624286342864428654286642867428684286942870428714287242873428744287542876428774287842879428804288142882428834288442885428864288742888428894289042891428924289342894428954289642897428984289942900429014290242903429044290542906429074290842909429104291142912429134291442915429164291742918429194292042921429224292342924429254292642927429284292942930429314293242933429344293542936429374293842939429404294142942429434294442945429464294742948429494295042951429524295342954429554295642957429584295942960429614296242963429644296542966429674296842969429704297142972429734297442975429764297742978429794298042981429824298342984429854298642987429884298942990429914299242993429944299542996429974299842999430004300143002430034300443005430064300743008430094301043011430124301343014430154301643017430184301943020430214302243023430244302543026430274302843029430304303143032430334303443035430364303743038430394304043041430424304343044430454304643047430484304943050430514305243053430544305543056430574305843059430604306143062430634306443065430664306743068430694307043071430724307343074430754307643077430784307943080430814308243083430844308543086430874308843089430904309143092430934309443095430964309743098430994310043101431024310343104431054310643107431084310943110431114311243113431144311543116431174311843119431204312143122431234312443125431264312743128431294313043131431324313343134431354313643137431384313943140431414314243143431444314543146431474314843149431504315143152431534315443155431564315743158431594316043161431624316343164431654316643167431684316943170431714317243173431744317543176431774317843179431804318143182431834318443185431864318743188431894319043191431924319343194431954319643197431984319943200432014320243203432044320543206432074320843209432104321143212432134321443215432164321743218432194322043221432224322343224432254322643227432284322943230432314323243233432344323543236432374323843239432404324143242432434324443245432464324743248432494325043251432524325343254432554325643257432584325943260432614326243263432644326543266432674326843269432704327143272432734327443275432764327743278432794328043281432824328343284432854328643287432884328943290432914329243293432944329543296432974329843299433004330143302433034330443305433064330743308433094331043311433124331343314433154331643317433184331943320433214332243323433244332543326433274332843329433304333143332433334333443335433364333743338433394334043341433424334343344433454334643347433484334943350433514335243353433544335543356433574335843359433604336143362433634336443365433664336743368433694337043371433724337343374433754337643377433784337943380433814338243383433844338543386433874338843389433904339143392433934339443395433964339743398433994340043401434024340343404434054340643407434084340943410434114341243413434144341543416434174341843419434204342143422434234342443425434264342743428434294343043431434324343343434434354343643437434384343943440434414344243443434444344543446434474344843449434504345143452434534345443455434564345743458434594346043461434624346343464434654346643467434684346943470434714347243473434744347543476434774347843479434804348143482434834348443485434864348743488434894349043491434924349343494434954349643497434984349943500435014350243503435044350543506435074350843509435104351143512435134351443515435164351743518435194352043521435224352343524435254352643527435284352943530435314353243533435344353543536435374353843539435404354143542435434354443545435464354743548435494355043551435524355343554435554355643557435584355943560435614356243563435644356543566435674356843569435704357143572435734357443575435764357743578435794358043581435824358343584435854358643587435884358943590435914359243593435944359543596435974359843599436004360143602436034360443605436064360743608436094361043611436124361343614436154361643617436184361943620436214362243623436244362543626436274362843629436304363143632436334363443635436364363743638436394364043641436424364343644436454364643647436484364943650436514365243653436544365543656436574365843659436604366143662436634366443665436664366743668436694367043671436724367343674436754367643677436784367943680436814368243683436844368543686436874368843689436904369143692436934369443695436964369743698436994370043701437024370343704437054370643707437084370943710437114371243713437144371543716437174371843719437204372143722437234372443725437264372743728437294373043731437324373343734437354373643737437384373943740437414374243743437444374543746437474374843749437504375143752437534375443755437564375743758437594376043761437624376343764437654376643767437684376943770437714377243773437744377543776437774377843779437804378143782437834378443785437864378743788437894379043791437924379343794437954379643797437984379943800438014380243803438044380543806438074380843809438104381143812438134381443815438164381743818438194382043821438224382343824438254382643827438284382943830438314383243833438344383543836438374383843839438404384143842438434384443845438464384743848438494385043851438524385343854438554385643857438584385943860438614386243863438644386543866438674386843869438704387143872438734387443875438764387743878438794388043881438824388343884438854388643887438884388943890438914389243893438944389543896438974389843899439004390143902439034390443905439064390743908439094391043911439124391343914439154391643917439184391943920439214392243923439244392543926439274392843929439304393143932439334393443935439364393743938439394394043941439424394343944439454394643947439484394943950439514395243953439544395543956439574395843959439604396143962439634396443965439664396743968439694397043971439724397343974439754397643977439784397943980439814398243983439844398543986439874398843989439904399143992439934399443995439964399743998439994400044001440024400344004440054400644007440084400944010440114401244013440144401544016440174401844019440204402144022440234402444025440264402744028440294403044031440324403344034440354403644037440384403944040440414404244043440444404544046440474404844049440504405144052440534405444055440564405744058440594406044061440624406344064440654406644067440684406944070440714407244073440744407544076440774407844079440804408144082440834408444085440864408744088440894409044091440924409344094440954409644097440984409944100441014410244103441044410544106441074410844109441104411144112441134411444115441164411744118441194412044121441224412344124441254412644127441284412944130441314413244133441344413544136441374413844139441404414144142441434414444145441464414744148441494415044151441524415344154441554415644157441584415944160441614416244163441644416544166441674416844169441704417144172441734417444175441764417744178441794418044181441824418344184441854418644187441884418944190441914419244193441944419544196441974419844199442004420144202442034420444205442064420744208442094421044211442124421344214442154421644217442184421944220442214422244223442244422544226442274422844229442304423144232442334423444235442364423744238442394424044241442424424344244442454424644247442484424944250442514425244253442544425544256442574425844259442604426144262442634426444265442664426744268442694427044271442724427344274442754427644277442784427944280442814428244283442844428544286442874428844289442904429144292442934429444295442964429744298442994430044301443024430344304443054430644307443084430944310443114431244313443144431544316443174431844319443204432144322443234432444325443264432744328443294433044331443324433344334443354433644337443384433944340443414434244343443444434544346443474434844349443504435144352443534435444355443564435744358443594436044361443624436344364443654436644367443684436944370443714437244373443744437544376443774437844379443804438144382443834438444385443864438744388443894439044391443924439344394443954439644397443984439944400444014440244403444044440544406444074440844409444104441144412444134441444415444164441744418444194442044421444224442344424444254442644427444284442944430444314443244433444344443544436444374443844439444404444144442444434444444445444464444744448444494445044451444524445344454444554445644457444584445944460444614446244463444644446544466444674446844469444704447144472444734447444475444764447744478444794448044481444824448344484444854448644487444884448944490444914449244493444944449544496444974449844499445004450144502445034450444505445064450744508445094451044511445124451344514445154451644517445184451944520445214452244523445244452544526445274452844529445304453144532445334453444535445364453744538445394454044541445424454344544445454454644547445484454944550445514455244553445544455544556445574455844559445604456144562445634456444565445664456744568445694457044571445724457344574445754457644577445784457944580445814458244583445844458544586445874458844589445904459144592445934459444595445964459744598445994460044601446024460344604446054460644607446084460944610446114461244613446144461544616446174461844619446204462144622446234462444625446264462744628446294463044631446324463344634446354463644637446384463944640446414464244643446444464544646446474464844649446504465144652446534465444655446564465744658446594466044661446624466344664446654466644667446684466944670446714467244673446744467544676446774467844679446804468144682446834468444685446864468744688446894469044691446924469344694446954469644697446984469944700447014470244703447044470544706447074470844709447104471144712447134471444715447164471744718447194472044721447224472344724447254472644727447284472944730447314473244733447344473544736447374473844739447404474144742447434474444745447464474744748447494475044751447524475344754447554475644757447584475944760447614476244763447644476544766447674476844769447704477144772447734477444775447764477744778447794478044781447824478344784447854478644787447884478944790447914479244793447944479544796447974479844799448004480144802448034480444805448064480744808448094481044811448124481344814448154481644817448184481944820448214482244823448244482544826448274482844829448304483144832448334483444835448364483744838448394484044841448424484344844448454484644847448484484944850448514485244853448544485544856448574485844859448604486144862448634486444865448664486744868448694487044871448724487344874448754487644877448784487944880448814488244883448844488544886448874488844889448904489144892448934489444895448964489744898448994490044901449024490344904449054490644907449084490944910449114491244913449144491544916449174491844919449204492144922449234492444925449264492744928449294493044931449324493344934449354493644937449384493944940449414494244943449444494544946449474494844949449504495144952449534495444955449564495744958449594496044961449624496344964449654496644967449684496944970449714497244973449744497544976449774497844979449804498144982449834498444985449864498744988449894499044991449924499344994449954499644997449984499945000450014500245003450044500545006450074500845009450104501145012450134501445015450164501745018450194502045021450224502345024450254502645027450284502945030450314503245033450344503545036450374503845039450404504145042450434504445045450464504745048450494505045051450524505345054450554505645057450584505945060450614506245063450644506545066450674506845069450704507145072450734507445075450764507745078450794508045081450824508345084450854508645087450884508945090450914509245093450944509545096450974509845099451004510145102451034510445105451064510745108451094511045111451124511345114451154511645117451184511945120451214512245123451244512545126451274512845129451304513145132451334513445135451364513745138451394514045141451424514345144451454514645147451484514945150451514515245153451544515545156451574515845159451604516145162451634516445165451664516745168451694517045171451724517345174451754517645177451784517945180451814518245183451844518545186451874518845189451904519145192451934519445195451964519745198451994520045201452024520345204452054520645207452084520945210452114521245213452144521545216452174521845219452204522145222452234522445225452264522745228452294523045231452324523345234452354523645237452384523945240452414524245243452444524545246452474524845249452504525145252452534525445255452564525745258452594526045261452624526345264452654526645267452684526945270452714527245273452744527545276452774527845279452804528145282452834528445285452864528745288452894529045291452924529345294452954529645297452984529945300453014530245303453044530545306453074530845309453104531145312453134531445315453164531745318453194532045321453224532345324453254532645327453284532945330453314533245333453344533545336453374533845339453404534145342453434534445345453464534745348453494535045351453524535345354453554535645357453584535945360453614536245363453644536545366453674536845369453704537145372453734537445375453764537745378453794538045381453824538345384453854538645387453884538945390453914539245393453944539545396453974539845399454004540145402454034540445405454064540745408454094541045411454124541345414454154541645417454184541945420454214542245423454244542545426454274542845429454304543145432454334543445435454364543745438454394544045441454424544345444454454544645447454484544945450454514545245453454544545545456454574545845459454604546145462454634546445465454664546745468454694547045471454724547345474454754547645477454784547945480454814548245483454844548545486454874548845489454904549145492454934549445495454964549745498454994550045501455024550345504455054550645507455084550945510455114551245513455144551545516455174551845519455204552145522455234552445525455264552745528455294553045531455324553345534455354553645537455384553945540455414554245543455444554545546455474554845549455504555145552455534555445555455564555745558455594556045561455624556345564455654556645567455684556945570455714557245573455744557545576455774557845579455804558145582455834558445585455864558745588455894559045591455924559345594455954559645597455984559945600456014560245603456044560545606456074560845609456104561145612456134561445615456164561745618456194562045621456224562345624456254562645627456284562945630456314563245633456344563545636456374563845639456404564145642456434564445645456464564745648456494565045651456524565345654456554565645657456584565945660456614566245663456644566545666456674566845669456704567145672456734567445675456764567745678456794568045681456824568345684456854568645687456884568945690456914569245693456944569545696456974569845699457004570145702457034570445705457064570745708457094571045711457124571345714457154571645717457184571945720457214572245723457244572545726457274572845729457304573145732457334573445735457364573745738457394574045741457424574345744457454574645747457484574945750457514575245753457544575545756457574575845759457604576145762457634576445765457664576745768457694577045771457724577345774457754577645777457784577945780457814578245783457844578545786457874578845789457904579145792457934579445795457964579745798457994580045801458024580345804458054580645807458084580945810458114581245813458144581545816458174581845819458204582145822458234582445825458264582745828458294583045831458324583345834458354583645837458384583945840458414584245843458444584545846458474584845849458504585145852458534585445855458564585745858458594586045861458624586345864458654586645867458684586945870458714587245873458744587545876458774587845879458804588145882458834588445885458864588745888458894589045891458924589345894458954589645897458984589945900459014590245903459044590545906459074590845909459104591145912459134591445915459164591745918459194592045921459224592345924459254592645927459284592945930459314593245933459344593545936459374593845939459404594145942459434594445945459464594745948459494595045951459524595345954459554595645957459584595945960459614596245963459644596545966459674596845969459704597145972459734597445975459764597745978459794598045981459824598345984459854598645987459884598945990459914599245993459944599545996459974599845999460004600146002460034600446005460064600746008460094601046011460124601346014460154601646017460184601946020460214602246023460244602546026460274602846029460304603146032460334603446035460364603746038460394604046041460424604346044460454604646047460484604946050460514605246053460544605546056460574605846059460604606146062460634606446065460664606746068460694607046071460724607346074460754607646077460784607946080460814608246083460844608546086460874608846089460904609146092460934609446095460964609746098460994610046101461024610346104461054610646107461084610946110461114611246113461144611546116461174611846119461204612146122461234612446125461264612746128461294613046131461324613346134461354613646137461384613946140461414614246143461444614546146461474614846149461504615146152461534615446155461564615746158461594616046161461624616346164461654616646167461684616946170461714617246173461744617546176461774617846179461804618146182461834618446185461864618746188461894619046191461924619346194461954619646197461984619946200462014620246203462044620546206462074620846209462104621146212462134621446215462164621746218462194622046221462224622346224462254622646227462284622946230462314623246233462344623546236462374623846239462404624146242462434624446245462464624746248462494625046251462524625346254462554625646257462584625946260462614626246263462644626546266462674626846269462704627146272462734627446275462764627746278462794628046281462824628346284462854628646287462884628946290462914629246293462944629546296462974629846299463004630146302463034630446305463064630746308463094631046311463124631346314463154631646317463184631946320463214632246323463244632546326463274632846329463304633146332463334633446335463364633746338463394634046341463424634346344463454634646347463484634946350463514635246353463544635546356463574635846359463604636146362463634636446365463664636746368463694637046371463724637346374463754637646377463784637946380463814638246383463844638546386463874638846389463904639146392463934639446395463964639746398463994640046401464024640346404464054640646407464084640946410464114641246413464144641546416464174641846419464204642146422464234642446425464264642746428464294643046431464324643346434464354643646437464384643946440464414644246443464444644546446464474644846449464504645146452464534645446455464564645746458464594646046461464624646346464464654646646467464684646946470464714647246473464744647546476464774647846479464804648146482464834648446485464864648746488464894649046491464924649346494464954649646497464984649946500465014650246503465044650546506465074650846509465104651146512465134651446515465164651746518465194652046521465224652346524465254652646527465284652946530465314653246533465344653546536465374653846539465404654146542465434654446545465464654746548465494655046551465524655346554465554655646557465584655946560465614656246563465644656546566465674656846569465704657146572465734657446575465764657746578465794658046581465824658346584465854658646587465884658946590465914659246593465944659546596465974659846599466004660146602466034660446605466064660746608466094661046611466124661346614466154661646617466184661946620466214662246623466244662546626466274662846629466304663146632466334663446635466364663746638466394664046641466424664346644466454664646647466484664946650466514665246653466544665546656466574665846659466604666146662466634666446665466664666746668466694667046671466724667346674466754667646677466784667946680466814668246683466844668546686466874668846689466904669146692466934669446695466964669746698466994670046701467024670346704467054670646707467084670946710467114671246713467144671546716467174671846719467204672146722467234672446725467264672746728467294673046731467324673346734467354673646737467384673946740467414674246743467444674546746467474674846749467504675146752467534675446755467564675746758467594676046761467624676346764467654676646767467684676946770467714677246773467744677546776467774677846779467804678146782467834678446785467864678746788467894679046791467924679346794467954679646797467984679946800468014680246803468044680546806468074680846809468104681146812468134681446815468164681746818468194682046821468224682346824468254682646827468284682946830468314683246833468344683546836468374683846839468404684146842468434684446845468464684746848468494685046851468524685346854468554685646857468584685946860468614686246863468644686546866468674686846869468704687146872468734687446875468764687746878468794688046881468824688346884468854688646887468884688946890468914689246893468944689546896468974689846899469004690146902469034690446905469064690746908469094691046911469124691346914469154691646917469184691946920469214692246923469244692546926469274692846929469304693146932469334693446935469364693746938469394694046941469424694346944469454694646947469484694946950469514695246953469544695546956469574695846959469604696146962469634696446965469664696746968469694697046971469724697346974469754697646977469784697946980469814698246983469844698546986469874698846989469904699146992469934699446995469964699746998469994700047001470024700347004470054700647007470084700947010470114701247013470144701547016470174701847019470204702147022470234702447025470264702747028470294703047031470324703347034470354703647037470384703947040470414704247043470444704547046470474704847049470504705147052470534705447055470564705747058470594706047061470624706347064470654706647067470684706947070470714707247073470744707547076470774707847079470804708147082470834708447085470864708747088470894709047091470924709347094470954709647097470984709947100471014710247103471044710547106471074710847109471104711147112471134711447115471164711747118471194712047121471224712347124471254712647127471284712947130471314713247133471344713547136471374713847139471404714147142471434714447145471464714747148471494715047151471524715347154471554715647157471584715947160471614716247163471644716547166471674716847169471704717147172471734717447175471764717747178471794718047181471824718347184471854718647187471884718947190471914719247193471944719547196471974719847199472004720147202472034720447205472064720747208472094721047211472124721347214472154721647217472184721947220472214722247223472244722547226472274722847229472304723147232472334723447235472364723747238472394724047241472424724347244472454724647247472484724947250472514725247253472544725547256472574725847259472604726147262472634726447265472664726747268472694727047271472724727347274472754727647277472784727947280472814728247283472844728547286472874728847289472904729147292472934729447295472964729747298472994730047301473024730347304473054730647307473084730947310473114731247313473144731547316473174731847319473204732147322473234732447325473264732747328473294733047331473324733347334473354733647337473384733947340473414734247343473444734547346473474734847349473504735147352473534735447355473564735747358473594736047361473624736347364473654736647367473684736947370473714737247373473744737547376473774737847379473804738147382473834738447385473864738747388473894739047391473924739347394473954739647397473984739947400474014740247403474044740547406474074740847409474104741147412474134741447415474164741747418474194742047421474224742347424474254742647427474284742947430474314743247433474344743547436474374743847439474404744147442474434744447445474464744747448474494745047451474524745347454474554745647457474584745947460474614746247463474644746547466474674746847469474704747147472474734747447475474764747747478474794748047481474824748347484474854748647487474884748947490474914749247493474944749547496474974749847499475004750147502475034750447505475064750747508475094751047511475124751347514475154751647517475184751947520475214752247523475244752547526475274752847529475304753147532475334753447535475364753747538475394754047541475424754347544475454754647547475484754947550475514755247553475544755547556475574755847559475604756147562475634756447565475664756747568475694757047571475724757347574475754757647577475784757947580475814758247583475844758547586475874758847589475904759147592475934759447595475964759747598475994760047601476024760347604476054760647607476084760947610476114761247613476144761547616476174761847619476204762147622476234762447625476264762747628476294763047631476324763347634476354763647637476384763947640476414764247643476444764547646476474764847649476504765147652476534765447655476564765747658476594766047661476624766347664476654766647667476684766947670476714767247673476744767547676476774767847679476804768147682476834768447685476864768747688476894769047691476924769347694476954769647697476984769947700477014770247703477044770547706477074770847709477104771147712477134771447715477164771747718477194772047721477224772347724477254772647727477284772947730477314773247733477344773547736477374773847739477404774147742477434774447745477464774747748477494775047751477524775347754477554775647757477584775947760477614776247763477644776547766477674776847769477704777147772477734777447775477764777747778477794778047781477824778347784477854778647787477884778947790477914779247793477944779547796477974779847799478004780147802478034780447805478064780747808478094781047811478124781347814478154781647817478184781947820478214782247823478244782547826478274782847829478304783147832478334783447835478364783747838478394784047841478424784347844478454784647847478484784947850478514785247853478544785547856478574785847859478604786147862478634786447865478664786747868478694787047871478724787347874478754787647877478784787947880478814788247883478844788547886478874788847889478904789147892478934789447895478964789747898478994790047901479024790347904479054790647907479084790947910479114791247913479144791547916479174791847919479204792147922479234792447925479264792747928479294793047931479324793347934479354793647937479384793947940479414794247943479444794547946479474794847949479504795147952479534795447955479564795747958479594796047961479624796347964479654796647967479684796947970479714797247973479744797547976479774797847979479804798147982479834798447985479864798747988479894799047991479924799347994479954799647997479984799948000480014800248003480044800548006480074800848009480104801148012480134801448015480164801748018480194802048021480224802348024480254802648027480284802948030480314803248033480344803548036480374803848039480404804148042480434804448045480464804748048480494805048051480524805348054480554805648057480584805948060480614806248063480644806548066480674806848069480704807148072480734807448075480764807748078480794808048081480824808348084480854808648087480884808948090480914809248093480944809548096480974809848099481004810148102481034810448105481064810748108481094811048111481124811348114481154811648117481184811948120481214812248123481244812548126481274812848129481304813148132481334813448135481364813748138481394814048141481424814348144481454814648147481484814948150481514815248153481544815548156481574815848159481604816148162481634816448165481664816748168481694817048171481724817348174481754817648177481784817948180481814818248183481844818548186481874818848189481904819148192481934819448195481964819748198481994820048201482024820348204482054820648207482084820948210482114821248213482144821548216482174821848219482204822148222482234822448225482264822748228482294823048231482324823348234482354823648237482384823948240482414824248243482444824548246482474824848249482504825148252482534825448255482564825748258482594826048261482624826348264482654826648267482684826948270482714827248273482744827548276482774827848279482804828148282482834828448285482864828748288482894829048291482924829348294482954829648297482984829948300483014830248303483044830548306483074830848309483104831148312483134831448315483164831748318483194832048321483224832348324483254832648327483284832948330483314833248333483344833548336483374833848339483404834148342483434834448345483464834748348483494835048351483524835348354483554835648357483584835948360483614836248363483644836548366483674836848369483704837148372483734837448375483764837748378483794838048381483824838348384483854838648387483884838948390483914839248393483944839548396483974839848399484004840148402484034840448405484064840748408484094841048411484124841348414484154841648417484184841948420484214842248423484244842548426484274842848429484304843148432484334843448435484364843748438484394844048441484424844348444484454844648447484484844948450484514845248453484544845548456484574845848459484604846148462484634846448465484664846748468484694847048471484724847348474484754847648477484784847948480484814848248483484844848548486484874848848489484904849148492484934849448495484964849748498484994850048501485024850348504485054850648507485084850948510485114851248513485144851548516485174851848519485204852148522485234852448525485264852748528485294853048531485324853348534485354853648537485384853948540485414854248543485444854548546485474854848549485504855148552485534855448555485564855748558485594856048561485624856348564485654856648567485684856948570485714857248573485744857548576485774857848579485804858148582485834858448585485864858748588485894859048591485924859348594485954859648597485984859948600486014860248603486044860548606486074860848609486104861148612486134861448615486164861748618486194862048621486224862348624486254862648627486284862948630486314863248633486344863548636486374863848639486404864148642486434864448645486464864748648486494865048651486524865348654486554865648657486584865948660486614866248663486644866548666486674866848669486704867148672486734867448675486764867748678486794868048681486824868348684486854868648687486884868948690486914869248693486944869548696486974869848699487004870148702487034870448705487064870748708487094871048711487124871348714487154871648717487184871948720487214872248723487244872548726487274872848729487304873148732487334873448735487364873748738487394874048741487424874348744487454874648747487484874948750487514875248753487544875548756487574875848759487604876148762487634876448765487664876748768487694877048771487724877348774487754877648777487784877948780487814878248783487844878548786487874878848789487904879148792487934879448795487964879748798487994880048801488024880348804488054880648807488084880948810488114881248813488144881548816488174881848819488204882148822488234882448825488264882748828488294883048831488324883348834488354883648837488384883948840488414884248843488444884548846488474884848849488504885148852488534885448855488564885748858488594886048861488624886348864488654886648867488684886948870488714887248873488744887548876488774887848879488804888148882488834888448885488864888748888488894889048891488924889348894488954889648897488984889948900489014890248903489044890548906489074890848909489104891148912489134891448915489164891748918489194892048921489224892348924489254892648927489284892948930489314893248933489344893548936489374893848939489404894148942489434894448945489464894748948489494895048951489524895348954489554895648957489584895948960489614896248963489644896548966489674896848969489704897148972489734897448975489764897748978489794898048981489824898348984489854898648987489884898948990489914899248993489944899548996489974899848999490004900149002490034900449005490064900749008490094901049011490124901349014490154901649017490184901949020490214902249023490244902549026490274902849029490304903149032490334903449035490364903749038490394904049041490424904349044490454904649047490484904949050490514905249053490544905549056490574905849059490604906149062490634906449065490664906749068490694907049071490724907349074490754907649077490784907949080490814908249083490844908549086490874908849089490904909149092490934909449095490964909749098490994910049101491024910349104491054910649107491084910949110491114911249113491144911549116491174911849119491204912149122491234912449125491264912749128491294913049131491324913349134491354913649137491384913949140491414914249143491444914549146491474914849149491504915149152491534915449155491564915749158491594916049161491624916349164491654916649167491684916949170491714917249173491744917549176491774917849179491804918149182491834918449185491864918749188491894919049191491924919349194491954919649197491984919949200492014920249203492044920549206492074920849209492104921149212492134921449215492164921749218492194922049221492224922349224492254922649227492284922949230492314923249233492344923549236492374923849239492404924149242492434924449245492464924749248492494925049251492524925349254492554925649257492584925949260492614926249263492644926549266492674926849269492704927149272492734927449275492764927749278492794928049281492824928349284492854928649287492884928949290492914929249293492944929549296492974929849299493004930149302493034930449305493064930749308493094931049311493124931349314493154931649317493184931949320493214932249323493244932549326493274932849329493304933149332493334933449335493364933749338493394934049341493424934349344493454934649347493484934949350493514935249353493544935549356493574935849359493604936149362493634936449365493664936749368493694937049371493724937349374493754937649377493784937949380493814938249383493844938549386493874938849389493904939149392493934939449395493964939749398493994940049401494024940349404494054940649407494084940949410494114941249413494144941549416494174941849419494204942149422494234942449425494264942749428494294943049431494324943349434494354943649437494384943949440494414944249443494444944549446494474944849449494504945149452494534945449455494564945749458494594946049461494624946349464494654946649467494684946949470494714947249473494744947549476494774947849479494804948149482494834948449485494864948749488494894949049491494924949349494494954949649497494984949949500495014950249503495044950549506495074950849509495104951149512495134951449515495164951749518495194952049521495224952349524495254952649527495284952949530495314953249533495344953549536495374953849539495404954149542495434954449545495464954749548495494955049551495524955349554495554955649557495584955949560495614956249563495644956549566495674956849569495704957149572495734957449575495764957749578495794958049581495824958349584495854958649587495884958949590495914959249593495944959549596495974959849599496004960149602496034960449605496064960749608496094961049611496124961349614496154961649617496184961949620496214962249623496244962549626496274962849629496304963149632496334963449635496364963749638496394964049641496424964349644496454964649647496484964949650496514965249653496544965549656496574965849659496604966149662496634966449665496664966749668496694967049671496724967349674496754967649677496784967949680496814968249683496844968549686496874968849689496904969149692496934969449695496964969749698496994970049701497024970349704497054970649707497084970949710497114971249713497144971549716497174971849719497204972149722497234972449725497264972749728497294973049731497324973349734497354973649737497384973949740497414974249743497444974549746497474974849749497504975149752497534975449755497564975749758497594976049761497624976349764497654976649767497684976949770497714977249773497744977549776497774977849779497804978149782497834978449785497864978749788497894979049791497924979349794497954979649797497984979949800498014980249803498044980549806498074980849809498104981149812498134981449815498164981749818498194982049821498224982349824498254982649827498284982949830498314983249833498344983549836498374983849839498404984149842498434984449845498464984749848498494985049851498524985349854498554985649857498584985949860498614986249863498644986549866498674986849869498704987149872498734987449875498764987749878498794988049881498824988349884498854988649887498884988949890498914989249893498944989549896498974989849899499004990149902499034990449905499064990749908499094991049911499124991349914499154991649917499184991949920499214992249923499244992549926499274992849929499304993149932499334993449935499364993749938499394994049941499424994349944499454994649947499484994949950499514995249953499544995549956499574995849959499604996149962499634996449965499664996749968499694997049971499724997349974499754997649977499784997949980499814998249983499844998549986499874998849989499904999149992499934999449995499964999749998499995000050001500025000350004500055000650007500085000950010500115001250013500145001550016500175001850019500205002150022500235002450025500265002750028500295003050031500325003350034500355003650037500385003950040500415004250043500445004550046500475004850049500505005150052500535005450055500565005750058500595006050061500625006350064500655006650067500685006950070500715007250073500745007550076500775007850079500805008150082500835008450085500865008750088500895009050091500925009350094500955009650097500985009950100501015010250103501045010550106501075010850109501105011150112501135011450115501165011750118501195012050121501225012350124501255012650127501285012950130501315013250133501345013550136501375013850139501405014150142501435014450145501465014750148501495015050151501525015350154501555015650157501585015950160501615016250163501645016550166501675016850169501705017150172501735017450175501765017750178501795018050181501825018350184501855018650187501885018950190501915019250193501945019550196501975019850199502005020150202502035020450205502065020750208502095021050211502125021350214502155021650217502185021950220502215022250223502245022550226502275022850229502305023150232502335023450235502365023750238502395024050241502425024350244502455024650247502485024950250502515025250253502545025550256502575025850259502605026150262502635026450265502665026750268502695027050271502725027350274502755027650277502785027950280502815028250283502845028550286502875028850289502905029150292502935029450295502965029750298502995030050301503025030350304503055030650307503085030950310503115031250313503145031550316503175031850319503205032150322503235032450325503265032750328503295033050331503325033350334503355033650337503385033950340503415034250343503445034550346503475034850349503505035150352503535035450355503565035750358503595036050361503625036350364503655036650367503685036950370503715037250373503745037550376503775037850379503805038150382503835038450385503865038750388503895039050391503925039350394503955039650397503985039950400504015040250403504045040550406504075040850409504105041150412504135041450415504165041750418504195042050421504225042350424504255042650427504285042950430504315043250433504345043550436504375043850439504405044150442504435044450445504465044750448504495045050451504525045350454504555045650457504585045950460504615046250463504645046550466504675046850469504705047150472504735047450475504765047750478504795048050481504825048350484504855048650487504885048950490504915049250493504945049550496504975049850499505005050150502505035050450505505065050750508505095051050511505125051350514505155051650517505185051950520505215052250523505245052550526505275052850529505305053150532505335053450535505365053750538505395054050541505425054350544505455054650547505485054950550505515055250553505545055550556505575055850559505605056150562505635056450565505665056750568505695057050571505725057350574505755057650577505785057950580505815058250583505845058550586505875058850589505905059150592505935059450595505965059750598505995060050601506025060350604506055060650607506085060950610506115061250613506145061550616506175061850619506205062150622506235062450625506265062750628506295063050631506325063350634506355063650637506385063950640506415064250643506445064550646506475064850649506505065150652506535065450655506565065750658506595066050661506625066350664506655066650667506685066950670506715067250673506745067550676506775067850679506805068150682506835068450685506865068750688506895069050691506925069350694506955069650697506985069950700507015070250703507045070550706507075070850709507105071150712507135071450715507165071750718507195072050721507225072350724507255072650727507285072950730507315073250733507345073550736507375073850739507405074150742507435074450745507465074750748507495075050751507525075350754507555075650757507585075950760507615076250763507645076550766507675076850769507705077150772507735077450775507765077750778507795078050781507825078350784507855078650787507885078950790507915079250793507945079550796507975079850799508005080150802508035080450805508065080750808508095081050811508125081350814508155081650817508185081950820508215082250823508245082550826508275082850829508305083150832508335083450835508365083750838508395084050841508425084350844508455084650847508485084950850508515085250853508545085550856508575085850859508605086150862508635086450865508665086750868508695087050871508725087350874508755087650877508785087950880508815088250883508845088550886508875088850889508905089150892508935089450895508965089750898508995090050901509025090350904509055090650907509085090950910509115091250913509145091550916509175091850919509205092150922509235092450925509265092750928509295093050931509325093350934509355093650937509385093950940509415094250943509445094550946509475094850949509505095150952509535095450955509565095750958509595096050961509625096350964509655096650967509685096950970509715097250973509745097550976509775097850979509805098150982509835098450985509865098750988509895099050991509925099350994509955099650997509985099951000510015100251003510045100551006510075100851009510105101151012510135101451015510165101751018510195102051021510225102351024510255102651027510285102951030510315103251033510345103551036510375103851039510405104151042510435104451045510465104751048510495105051051510525105351054510555105651057510585105951060510615106251063510645106551066510675106851069510705107151072510735107451075510765107751078510795108051081510825108351084510855108651087510885108951090510915109251093510945109551096510975109851099511005110151102511035110451105511065110751108511095111051111511125111351114511155111651117511185111951120511215112251123511245112551126511275112851129511305113151132511335113451135511365113751138511395114051141511425114351144511455114651147511485114951150511515115251153511545115551156511575115851159511605116151162511635116451165511665116751168511695117051171511725117351174511755117651177511785117951180511815118251183511845118551186511875118851189511905119151192511935119451195511965119751198511995120051201512025120351204512055120651207512085120951210512115121251213512145121551216512175121851219512205122151222512235122451225512265122751228512295123051231512325123351234512355123651237512385123951240512415124251243512445124551246512475124851249512505125151252512535125451255512565125751258512595126051261512625126351264512655126651267512685126951270512715127251273512745127551276512775127851279512805128151282512835128451285512865128751288512895129051291512925129351294512955129651297512985129951300513015130251303513045130551306513075130851309513105131151312513135131451315513165131751318513195132051321513225132351324513255132651327513285132951330513315133251333513345133551336513375133851339513405134151342513435134451345513465134751348513495135051351513525135351354513555135651357513585135951360513615136251363513645136551366513675136851369513705137151372513735137451375513765137751378513795138051381513825138351384513855138651387513885138951390513915139251393513945139551396513975139851399514005140151402514035140451405514065140751408514095141051411514125141351414514155141651417514185141951420514215142251423514245142551426514275142851429514305143151432514335143451435514365143751438514395144051441514425144351444514455144651447514485144951450514515145251453514545145551456514575145851459514605146151462514635146451465514665146751468514695147051471514725147351474514755147651477514785147951480514815148251483514845148551486514875148851489514905149151492514935149451495514965149751498514995150051501515025150351504515055150651507515085150951510515115151251513515145151551516515175151851519515205152151522515235152451525515265152751528515295153051531515325153351534515355153651537515385153951540515415154251543515445154551546515475154851549515505155151552515535155451555515565155751558515595156051561515625156351564515655156651567515685156951570515715157251573515745157551576515775157851579515805158151582515835158451585515865158751588515895159051591515925159351594515955159651597515985159951600516015160251603516045160551606516075160851609516105161151612516135161451615516165161751618516195162051621516225162351624516255162651627516285162951630516315163251633516345163551636516375163851639516405164151642516435164451645516465164751648516495165051651516525165351654516555165651657516585165951660516615166251663516645166551666516675166851669516705167151672516735167451675516765167751678516795168051681516825168351684516855168651687516885168951690516915169251693516945169551696516975169851699517005170151702517035170451705517065170751708517095171051711517125171351714517155171651717517185171951720517215172251723517245172551726517275172851729517305173151732517335173451735517365173751738517395174051741517425174351744517455174651747517485174951750517515175251753517545175551756517575175851759517605176151762517635176451765517665176751768517695177051771517725177351774517755177651777517785177951780517815178251783517845178551786517875178851789517905179151792517935179451795517965179751798517995180051801518025180351804518055180651807518085180951810518115181251813518145181551816518175181851819518205182151822518235182451825518265182751828518295183051831518325183351834518355183651837518385183951840518415184251843518445184551846518475184851849518505185151852518535185451855518565185751858518595186051861518625186351864518655186651867518685186951870518715187251873518745187551876518775187851879518805188151882518835188451885518865188751888518895189051891518925189351894518955189651897518985189951900519015190251903519045190551906519075190851909519105191151912519135191451915519165191751918519195192051921519225192351924519255192651927519285192951930519315193251933519345193551936519375193851939519405194151942519435194451945519465194751948519495195051951519525195351954519555195651957519585195951960519615196251963519645196551966519675196851969519705197151972519735197451975519765197751978519795198051981519825198351984519855198651987519885198951990519915199251993519945199551996519975199851999520005200152002520035200452005520065200752008520095201052011520125201352014520155201652017520185201952020520215202252023520245202552026520275202852029520305203152032520335203452035520365203752038520395204052041520425204352044520455204652047520485204952050520515205252053520545205552056520575205852059520605206152062520635206452065520665206752068520695207052071520725207352074520755207652077520785207952080520815208252083520845208552086520875208852089520905209152092520935209452095520965209752098520995210052101521025210352104521055210652107521085210952110521115211252113521145211552116521175211852119521205212152122521235212452125521265212752128521295213052131521325213352134521355213652137521385213952140521415214252143521445214552146521475214852149521505215152152521535215452155521565215752158521595216052161521625216352164521655216652167521685216952170521715217252173521745217552176521775217852179521805218152182521835218452185521865218752188521895219052191521925219352194521955219652197521985219952200522015220252203522045220552206522075220852209522105221152212522135221452215522165221752218522195222052221522225222352224522255222652227522285222952230522315223252233522345223552236522375223852239522405224152242522435224452245522465224752248522495225052251522525225352254522555225652257522585225952260522615226252263522645226552266522675226852269522705227152272522735227452275522765227752278522795228052281522825228352284522855228652287522885228952290522915229252293522945229552296522975229852299523005230152302523035230452305523065230752308523095231052311523125231352314523155231652317523185231952320523215232252323523245232552326523275232852329523305233152332523335233452335523365233752338523395234052341523425234352344523455234652347523485234952350523515235252353523545235552356523575235852359523605236152362523635236452365523665236752368523695237052371523725237352374523755237652377523785237952380523815238252383523845238552386523875238852389523905239152392523935239452395523965239752398523995240052401524025240352404524055240652407524085240952410524115241252413524145241552416524175241852419524205242152422524235242452425524265242752428524295243052431524325243352434524355243652437524385243952440524415244252443524445244552446524475244852449524505245152452524535245452455524565245752458524595246052461524625246352464524655246652467524685246952470524715247252473524745247552476524775247852479524805248152482524835248452485524865248752488524895249052491524925249352494524955249652497524985249952500525015250252503525045250552506525075250852509525105251152512525135251452515525165251752518525195252052521525225252352524525255252652527525285252952530525315253252533525345253552536525375253852539525405254152542525435254452545525465254752548525495255052551525525255352554525555255652557525585255952560525615256252563525645256552566525675256852569525705257152572525735257452575525765257752578525795258052581525825258352584525855258652587525885258952590525915259252593525945259552596525975259852599526005260152602526035260452605526065260752608526095261052611526125261352614526155261652617526185261952620526215262252623526245262552626526275262852629526305263152632526335263452635526365263752638526395264052641526425264352644526455264652647526485264952650526515265252653526545265552656526575265852659526605266152662526635266452665526665266752668526695267052671526725267352674526755267652677526785267952680526815268252683526845268552686526875268852689526905269152692526935269452695526965269752698526995270052701527025270352704527055270652707527085270952710527115271252713527145271552716527175271852719527205272152722527235272452725527265272752728527295273052731527325273352734527355273652737527385273952740527415274252743527445274552746527475274852749527505275152752527535275452755527565275752758527595276052761527625276352764527655276652767527685276952770527715277252773527745277552776527775277852779527805278152782527835278452785527865278752788527895279052791527925279352794527955279652797527985279952800528015280252803528045280552806528075280852809528105281152812528135281452815528165281752818528195282052821528225282352824528255282652827528285282952830528315283252833528345283552836528375283852839528405284152842528435284452845528465284752848528495285052851528525285352854528555285652857528585285952860528615286252863528645286552866528675286852869528705287152872528735287452875528765287752878528795288052881528825288352884528855288652887528885288952890528915289252893528945289552896528975289852899529005290152902529035290452905529065290752908529095291052911529125291352914529155291652917529185291952920529215292252923529245292552926529275292852929529305293152932529335293452935529365293752938529395294052941529425294352944529455294652947529485294952950529515295252953529545295552956529575295852959529605296152962529635296452965529665296752968529695297052971529725297352974529755297652977529785297952980529815298252983529845298552986529875298852989529905299152992529935299452995529965299752998529995300053001530025300353004530055300653007530085300953010530115301253013530145301553016530175301853019530205302153022530235302453025530265302753028530295303053031530325303353034530355303653037530385303953040530415304253043530445304553046530475304853049530505305153052530535305453055530565305753058530595306053061530625306353064530655306653067530685306953070530715307253073530745307553076530775307853079530805308153082530835308453085530865308753088530895309053091530925309353094530955309653097530985309953100531015310253103531045310553106531075310853109531105311153112531135311453115531165311753118531195312053121531225312353124531255312653127531285312953130531315313253133531345313553136531375313853139531405314153142531435314453145531465314753148531495315053151531525315353154531555315653157531585315953160531615316253163531645316553166531675316853169531705317153172531735317453175531765317753178531795318053181531825318353184531855318653187531885318953190531915319253193531945319553196531975319853199532005320153202532035320453205532065320753208532095321053211532125321353214532155321653217532185321953220532215322253223532245322553226532275322853229532305323153232532335323453235532365323753238532395324053241532425324353244532455324653247532485324953250532515325253253532545325553256532575325853259532605326153262532635326453265532665326753268532695327053271532725327353274532755327653277532785327953280532815328253283532845328553286532875328853289532905329153292532935329453295532965329753298532995330053301533025330353304533055330653307533085330953310533115331253313533145331553316533175331853319533205332153322533235332453325533265332753328533295333053331533325333353334533355333653337533385333953340533415334253343533445334553346533475334853349533505335153352533535335453355533565335753358533595336053361533625336353364533655336653367533685336953370533715337253373533745337553376533775337853379533805338153382533835338453385533865338753388533895339053391533925339353394533955339653397533985339953400534015340253403534045340553406534075340853409534105341153412534135341453415534165341753418534195342053421534225342353424534255342653427534285342953430534315343253433534345343553436534375343853439534405344153442534435344453445534465344753448534495345053451534525345353454534555345653457534585345953460534615346253463534645346553466534675346853469534705347153472534735347453475534765347753478534795348053481534825348353484534855348653487534885348953490534915349253493534945349553496534975349853499535005350153502535035350453505535065350753508535095351053511535125351353514535155351653517535185351953520535215352253523535245352553526535275352853529535305353153532535335353453535535365353753538535395354053541535425354353544535455354653547535485354953550535515355253553535545355553556535575355853559535605356153562535635356453565535665356753568535695357053571535725357353574535755357653577535785357953580535815358253583535845358553586535875358853589535905359153592535935359453595535965359753598535995360053601536025360353604536055360653607536085360953610536115361253613536145361553616536175361853619536205362153622536235362453625536265362753628536295363053631536325363353634536355363653637536385363953640536415364253643536445364553646536475364853649536505365153652536535365453655536565365753658536595366053661536625366353664536655366653667536685366953670536715367253673536745367553676536775367853679536805368153682536835368453685536865368753688536895369053691536925369353694536955369653697536985369953700537015370253703537045370553706537075370853709537105371153712537135371453715537165371753718537195372053721537225372353724537255372653727537285372953730537315373253733537345373553736537375373853739537405374153742537435374453745537465374753748537495375053751537525375353754537555375653757537585375953760537615376253763537645376553766537675376853769537705377153772537735377453775537765377753778537795378053781537825378353784537855378653787537885378953790537915379253793537945379553796537975379853799538005380153802538035380453805538065380753808538095381053811538125381353814538155381653817538185381953820538215382253823538245382553826538275382853829538305383153832538335383453835538365383753838538395384053841538425384353844538455384653847538485384953850538515385253853538545385553856538575385853859538605386153862538635386453865538665386753868538695387053871538725387353874538755387653877538785387953880538815388253883538845388553886538875388853889538905389153892538935389453895538965389753898538995390053901539025390353904539055390653907539085390953910539115391253913539145391553916539175391853919539205392153922539235392453925539265392753928539295393053931539325393353934539355393653937539385393953940539415394253943539445394553946539475394853949539505395153952539535395453955539565395753958539595396053961539625396353964539655396653967539685396953970539715397253973539745397553976539775397853979539805398153982539835398453985539865398753988539895399053991539925399353994539955399653997539985399954000540015400254003540045400554006540075400854009540105401154012540135401454015540165401754018540195402054021540225402354024540255402654027540285402954030540315403254033540345403554036540375403854039540405404154042540435404454045540465404754048540495405054051540525405354054540555405654057540585405954060540615406254063540645406554066540675406854069540705407154072540735407454075540765407754078540795408054081540825408354084540855408654087540885408954090540915409254093540945409554096540975409854099541005410154102541035410454105541065410754108541095411054111541125411354114541155411654117541185411954120541215412254123541245412554126541275412854129541305413154132541335413454135541365413754138541395414054141541425414354144541455414654147541485414954150541515415254153541545415554156541575415854159541605416154162541635416454165541665416754168541695417054171541725417354174541755417654177541785417954180541815418254183541845418554186541875418854189541905419154192541935419454195541965419754198541995420054201542025420354204542055420654207542085420954210542115421254213542145421554216542175421854219542205422154222542235422454225542265422754228542295423054231542325423354234542355423654237542385423954240542415424254243542445424554246542475424854249542505425154252542535425454255542565425754258542595426054261542625426354264542655426654267542685426954270542715427254273542745427554276542775427854279542805428154282542835428454285542865428754288542895429054291542925429354294542955429654297542985429954300543015430254303543045430554306543075430854309543105431154312543135431454315543165431754318543195432054321543225432354324543255432654327543285432954330543315433254333543345433554336543375433854339543405434154342543435434454345543465434754348543495435054351543525435354354543555435654357543585435954360543615436254363543645436554366543675436854369543705437154372543735437454375543765437754378543795438054381543825438354384543855438654387543885438954390543915439254393543945439554396543975439854399544005440154402544035440454405544065440754408544095441054411544125441354414544155441654417544185441954420544215442254423544245442554426544275442854429544305443154432544335443454435544365443754438544395444054441544425444354444544455444654447544485444954450544515445254453544545445554456544575445854459544605446154462544635446454465544665446754468544695447054471544725447354474544755447654477544785447954480544815448254483544845448554486544875448854489544905449154492544935449454495544965449754498544995450054501545025450354504545055450654507545085450954510545115451254513545145451554516545175451854519545205452154522545235452454525545265452754528545295453054531545325453354534545355453654537545385453954540545415454254543545445454554546545475454854549545505455154552545535455454555545565455754558545595456054561545625456354564545655456654567545685456954570545715457254573545745457554576545775457854579545805458154582545835458454585545865458754588545895459054591545925459354594545955459654597545985459954600546015460254603546045460554606546075460854609546105461154612546135461454615546165461754618546195462054621546225462354624546255462654627546285462954630546315463254633546345463554636546375463854639546405464154642546435464454645546465464754648546495465054651546525465354654546555465654657546585465954660546615466254663546645466554666546675466854669546705467154672546735467454675546765467754678546795468054681546825468354684546855468654687546885468954690546915469254693546945469554696546975469854699547005470154702547035470454705547065470754708547095471054711547125471354714547155471654717547185471954720547215472254723547245472554726547275472854729547305473154732547335473454735547365473754738547395474054741547425474354744547455474654747547485474954750547515475254753547545475554756547575475854759547605476154762547635476454765547665476754768547695477054771547725477354774547755477654777547785477954780547815478254783547845478554786547875478854789547905479154792547935479454795547965479754798547995480054801548025480354804548055480654807548085480954810548115481254813548145481554816548175481854819548205482154822548235482454825548265482754828548295483054831548325483354834548355483654837548385483954840548415484254843548445484554846548475484854849548505485154852548535485454855548565485754858548595486054861548625486354864548655486654867548685486954870548715487254873548745487554876548775487854879548805488154882548835488454885548865488754888548895489054891548925489354894548955489654897548985489954900549015490254903549045490554906549075490854909549105491154912549135491454915549165491754918549195492054921549225492354924549255492654927549285492954930549315493254933549345493554936549375493854939549405494154942549435494454945549465494754948549495495054951549525495354954549555495654957549585495954960549615496254963549645496554966549675496854969549705497154972549735497454975549765497754978549795498054981549825498354984549855498654987549885498954990549915499254993549945499554996549975499854999550005500155002550035500455005550065500755008550095501055011550125501355014550155501655017550185501955020550215502255023550245502555026550275502855029550305503155032550335503455035550365503755038550395504055041550425504355044550455504655047550485504955050550515505255053550545505555056550575505855059550605506155062550635506455065550665506755068550695507055071550725507355074550755507655077550785507955080550815508255083550845508555086550875508855089550905509155092550935509455095550965509755098550995510055101551025510355104551055510655107551085510955110551115511255113551145511555116551175511855119551205512155122551235512455125551265512755128551295513055131551325513355134551355513655137551385513955140551415514255143551445514555146551475514855149551505515155152551535515455155551565515755158551595516055161551625516355164551655516655167551685516955170551715517255173551745517555176551775517855179551805518155182551835518455185551865518755188551895519055191551925519355194551955519655197551985519955200552015520255203552045520555206552075520855209552105521155212552135521455215552165521755218552195522055221552225522355224552255522655227552285522955230552315523255233552345523555236552375523855239552405524155242552435524455245552465524755248552495525055251552525525355254552555525655257552585525955260552615526255263552645526555266552675526855269552705527155272552735527455275552765527755278552795528055281552825528355284552855528655287552885528955290552915529255293552945529555296552975529855299553005530155302553035530455305553065530755308553095531055311553125531355314553155531655317553185531955320553215532255323553245532555326553275532855329553305533155332553335533455335553365533755338553395534055341553425534355344553455534655347553485534955350553515535255353553545535555356553575535855359553605536155362553635536455365553665536755368553695537055371553725537355374553755537655377553785537955380553815538255383553845538555386553875538855389553905539155392553935539455395553965539755398553995540055401554025540355404554055540655407554085540955410554115541255413554145541555416554175541855419554205542155422554235542455425554265542755428554295543055431554325543355434554355543655437554385543955440554415544255443554445544555446554475544855449554505545155452554535545455455554565545755458554595546055461554625546355464554655546655467554685546955470554715547255473554745547555476554775547855479554805548155482554835548455485554865548755488554895549055491554925549355494554955549655497554985549955500555015550255503555045550555506555075550855509555105551155512555135551455515555165551755518555195552055521555225552355524555255552655527555285552955530555315553255533555345553555536555375553855539555405554155542555435554455545555465554755548555495555055551555525555355554555555555655557555585555955560555615556255563555645556555566555675556855569555705557155572555735557455575555765557755578555795558055581555825558355584555855558655587555885558955590555915559255593555945559555596555975559855599556005560155602556035560455605556065560755608556095561055611556125561355614556155561655617556185561955620556215562255623556245562555626556275562855629556305563155632556335563455635556365563755638556395564055641556425564355644556455564655647556485564955650556515565255653556545565555656556575565855659556605566155662556635566455665556665566755668556695567055671556725567355674556755567655677556785567955680556815568255683556845568555686556875568855689556905569155692556935569455695556965569755698556995570055701557025570355704557055570655707557085570955710557115571255713557145571555716557175571855719557205572155722557235572455725557265572755728557295573055731557325573355734557355573655737557385573955740557415574255743557445574555746557475574855749557505575155752557535575455755557565575755758557595576055761557625576355764557655576655767557685576955770557715577255773557745577555776557775577855779557805578155782557835578455785557865578755788557895579055791557925579355794557955579655797557985579955800558015580255803558045580555806558075580855809558105581155812558135581455815558165581755818558195582055821558225582355824558255582655827558285582955830558315583255833558345583555836558375583855839558405584155842558435584455845558465584755848558495585055851558525585355854558555585655857558585585955860558615586255863558645586555866558675586855869558705587155872558735587455875558765587755878558795588055881558825588355884558855588655887558885588955890558915589255893558945589555896558975589855899559005590155902559035590455905559065590755908559095591055911559125591355914559155591655917559185591955920559215592255923559245592555926559275592855929559305593155932559335593455935559365593755938559395594055941559425594355944559455594655947559485594955950559515595255953559545595555956559575595855959559605596155962559635596455965559665596755968559695597055971559725597355974559755597655977559785597955980559815598255983559845598555986559875598855989559905599155992559935599455995559965599755998559995600056001560025600356004560055600656007560085600956010560115601256013560145601556016560175601856019560205602156022560235602456025560265602756028560295603056031560325603356034560355603656037560385603956040560415604256043560445604556046560475604856049560505605156052560535605456055560565605756058560595606056061560625606356064560655606656067560685606956070560715607256073560745607556076560775607856079560805608156082560835608456085560865608756088560895609056091560925609356094560955609656097560985609956100561015610256103561045610556106561075610856109561105611156112561135611456115561165611756118561195612056121561225612356124561255612656127561285612956130561315613256133561345613556136561375613856139561405614156142561435614456145561465614756148561495615056151561525615356154561555615656157561585615956160561615616256163561645616556166561675616856169561705617156172561735617456175561765617756178561795618056181561825618356184561855618656187561885618956190561915619256193561945619556196561975619856199562005620156202562035620456205562065620756208562095621056211562125621356214562155621656217562185621956220562215622256223562245622556226562275622856229562305623156232562335623456235562365623756238562395624056241562425624356244562455624656247562485624956250562515625256253562545625556256562575625856259562605626156262562635626456265562665626756268562695627056271562725627356274562755627656277562785627956280562815628256283562845628556286562875628856289562905629156292562935629456295562965629756298562995630056301563025630356304563055630656307563085630956310563115631256313563145631556316563175631856319563205632156322563235632456325563265632756328563295633056331563325633356334563355633656337563385633956340563415634256343563445634556346563475634856349563505635156352563535635456355563565635756358563595636056361563625636356364563655636656367563685636956370563715637256373563745637556376563775637856379563805638156382563835638456385563865638756388563895639056391563925639356394563955639656397563985639956400564015640256403564045640556406564075640856409564105641156412564135641456415564165641756418564195642056421564225642356424564255642656427564285642956430564315643256433564345643556436564375643856439564405644156442564435644456445564465644756448564495645056451564525645356454564555645656457564585645956460564615646256463564645646556466564675646856469564705647156472564735647456475564765647756478564795648056481564825648356484564855648656487564885648956490564915649256493564945649556496564975649856499565005650156502565035650456505565065650756508565095651056511565125651356514565155651656517565185651956520565215652256523565245652556526565275652856529565305653156532565335653456535565365653756538565395654056541565425654356544565455654656547565485654956550565515655256553565545655556556565575655856559565605656156562565635656456565565665656756568565695657056571565725657356574565755657656577565785657956580565815658256583565845658556586565875658856589565905659156592565935659456595565965659756598565995660056601566025660356604566055660656607566085660956610566115661256613566145661556616566175661856619566205662156622566235662456625566265662756628566295663056631566325663356634566355663656637566385663956640566415664256643566445664556646566475664856649566505665156652566535665456655566565665756658566595666056661566625666356664566655666656667566685666956670566715667256673566745667556676566775667856679566805668156682566835668456685566865668756688566895669056691566925669356694566955669656697566985669956700567015670256703567045670556706567075670856709567105671156712567135671456715567165671756718567195672056721567225672356724567255672656727567285672956730567315673256733567345673556736567375673856739567405674156742567435674456745567465674756748567495675056751567525675356754567555675656757567585675956760567615676256763567645676556766567675676856769567705677156772567735677456775567765677756778567795678056781567825678356784567855678656787567885678956790567915679256793567945679556796567975679856799568005680156802568035680456805568065680756808568095681056811568125681356814568155681656817568185681956820568215682256823568245682556826568275682856829568305683156832568335683456835568365683756838568395684056841568425684356844568455684656847568485684956850568515685256853568545685556856568575685856859568605686156862568635686456865568665686756868568695687056871568725687356874568755687656877568785687956880568815688256883568845688556886568875688856889568905689156892568935689456895568965689756898568995690056901569025690356904569055690656907569085690956910569115691256913569145691556916569175691856919569205692156922569235692456925569265692756928569295693056931569325693356934569355693656937569385693956940569415694256943569445694556946569475694856949569505695156952569535695456955569565695756958569595696056961569625696356964569655696656967569685696956970569715697256973569745697556976569775697856979569805698156982569835698456985569865698756988569895699056991569925699356994569955699656997569985699957000570015700257003570045700557006570075700857009570105701157012570135701457015570165701757018570195702057021570225702357024570255702657027570285702957030570315703257033570345703557036570375703857039570405704157042570435704457045570465704757048570495705057051570525705357054570555705657057570585705957060570615706257063570645706557066570675706857069570705707157072570735707457075570765707757078570795708057081570825708357084570855708657087570885708957090570915709257093570945709557096570975709857099571005710157102571035710457105571065710757108571095711057111571125711357114571155711657117571185711957120571215712257123571245712557126571275712857129571305713157132571335713457135571365713757138571395714057141571425714357144571455714657147571485714957150571515715257153571545715557156571575715857159571605716157162571635716457165571665716757168571695717057171571725717357174571755717657177571785717957180571815718257183571845718557186571875718857189571905719157192571935719457195571965719757198571995720057201572025720357204572055720657207572085720957210572115721257213572145721557216572175721857219572205722157222572235722457225572265722757228572295723057231572325723357234572355723657237572385723957240572415724257243572445724557246572475724857249572505725157252572535725457255572565725757258572595726057261572625726357264572655726657267572685726957270572715727257273572745727557276572775727857279572805728157282572835728457285572865728757288572895729057291572925729357294572955729657297572985729957300573015730257303573045730557306573075730857309573105731157312573135731457315573165731757318573195732057321573225732357324573255732657327573285732957330573315733257333573345733557336573375733857339573405734157342573435734457345573465734757348573495735057351573525735357354573555735657357573585735957360573615736257363573645736557366573675736857369573705737157372573735737457375573765737757378573795738057381573825738357384573855738657387573885738957390573915739257393573945739557396573975739857399574005740157402574035740457405574065740757408574095741057411574125741357414574155741657417574185741957420574215742257423574245742557426574275742857429574305743157432574335743457435574365743757438574395744057441574425744357444574455744657447574485744957450574515745257453574545745557456574575745857459574605746157462574635746457465574665746757468574695747057471574725747357474574755747657477574785747957480574815748257483574845748557486574875748857489574905749157492574935749457495574965749757498574995750057501575025750357504575055750657507575085750957510575115751257513575145751557516575175751857519575205752157522575235752457525575265752757528575295753057531575325753357534575355753657537575385753957540575415754257543575445754557546575475754857549575505755157552575535755457555575565755757558575595756057561575625756357564575655756657567575685756957570575715757257573575745757557576575775757857579575805758157582575835758457585575865758757588575895759057591575925759357594575955759657597575985759957600576015760257603576045760557606576075760857609576105761157612576135761457615576165761757618576195762057621576225762357624576255762657627576285762957630576315763257633576345763557636576375763857639576405764157642576435764457645576465764757648576495765057651576525765357654576555765657657576585765957660576615766257663576645766557666576675766857669576705767157672576735767457675576765767757678576795768057681576825768357684576855768657687576885768957690576915769257693576945769557696576975769857699577005770157702577035770457705577065770757708577095771057711577125771357714577155771657717577185771957720577215772257723577245772557726577275772857729577305773157732577335773457735577365773757738577395774057741577425774357744577455774657747577485774957750577515775257753577545775557756577575775857759577605776157762577635776457765577665776757768577695777057771577725777357774577755777657777577785777957780577815778257783577845778557786577875778857789577905779157792577935779457795577965779757798577995780057801578025780357804578055780657807578085780957810578115781257813578145781557816578175781857819578205782157822578235782457825578265782757828578295783057831578325783357834578355783657837578385783957840578415784257843578445784557846578475784857849578505785157852578535785457855578565785757858578595786057861578625786357864578655786657867578685786957870578715787257873578745787557876578775787857879578805788157882578835788457885578865788757888578895789057891578925789357894578955789657897578985789957900579015790257903579045790557906579075790857909579105791157912579135791457915579165791757918579195792057921579225792357924579255792657927579285792957930579315793257933579345793557936579375793857939579405794157942579435794457945579465794757948579495795057951579525795357954579555795657957579585795957960579615796257963579645796557966579675796857969579705797157972579735797457975579765797757978579795798057981579825798357984579855798657987579885798957990579915799257993579945799557996579975799857999580005800158002580035800458005580065800758008580095801058011580125801358014580155801658017580185801958020580215802258023580245802558026580275802858029580305803158032580335803458035580365803758038580395804058041580425804358044580455804658047580485804958050580515805258053580545805558056580575805858059580605806158062580635806458065580665806758068580695807058071580725807358074580755807658077580785807958080580815808258083580845808558086580875808858089580905809158092580935809458095580965809758098580995810058101581025810358104581055810658107581085810958110581115811258113581145811558116581175811858119581205812158122581235812458125581265812758128581295813058131581325813358134581355813658137581385813958140581415814258143581445814558146581475814858149581505815158152581535815458155581565815758158581595816058161581625816358164581655816658167581685816958170581715817258173581745817558176581775817858179581805818158182581835818458185581865818758188581895819058191581925819358194581955819658197581985819958200582015820258203582045820558206582075820858209582105821158212582135821458215582165821758218582195822058221582225822358224582255822658227582285822958230582315823258233582345823558236582375823858239582405824158242582435824458245582465824758248582495825058251582525825358254582555825658257582585825958260582615826258263582645826558266582675826858269582705827158272582735827458275582765827758278582795828058281582825828358284582855828658287582885828958290582915829258293582945829558296582975829858299583005830158302583035830458305583065830758308583095831058311583125831358314583155831658317583185831958320583215832258323583245832558326583275832858329583305833158332583335833458335583365833758338583395834058341583425834358344583455834658347583485834958350583515835258353583545835558356583575835858359583605836158362583635836458365583665836758368583695837058371583725837358374583755837658377583785837958380583815838258383583845838558386583875838858389583905839158392583935839458395583965839758398583995840058401584025840358404584055840658407584085840958410584115841258413584145841558416584175841858419584205842158422584235842458425584265842758428584295843058431584325843358434584355843658437584385843958440584415844258443584445844558446584475844858449584505845158452584535845458455584565845758458584595846058461584625846358464584655846658467584685846958470584715847258473584745847558476584775847858479584805848158482584835848458485584865848758488584895849058491584925849358494584955849658497584985849958500585015850258503585045850558506585075850858509585105851158512585135851458515585165851758518585195852058521585225852358524585255852658527585285852958530585315853258533585345853558536585375853858539585405854158542585435854458545585465854758548585495855058551585525855358554585555855658557585585855958560585615856258563585645856558566585675856858569585705857158572585735857458575585765857758578585795858058581585825858358584585855858658587585885858958590585915859258593585945859558596585975859858599586005860158602586035860458605586065860758608586095861058611586125861358614586155861658617586185861958620586215862258623586245862558626586275862858629586305863158632586335863458635586365863758638586395864058641586425864358644586455864658647586485864958650586515865258653586545865558656586575865858659586605866158662586635866458665586665866758668586695867058671586725867358674586755867658677586785867958680586815868258683586845868558686586875868858689586905869158692586935869458695586965869758698586995870058701587025870358704587055870658707587085870958710587115871258713587145871558716587175871858719587205872158722587235872458725587265872758728587295873058731587325873358734587355873658737587385873958740587415874258743587445874558746587475874858749587505875158752587535875458755587565875758758587595876058761587625876358764587655876658767587685876958770587715877258773587745877558776587775877858779587805878158782587835878458785587865878758788587895879058791587925879358794587955879658797587985879958800588015880258803588045880558806588075880858809588105881158812588135881458815588165881758818588195882058821588225882358824588255882658827588285882958830588315883258833588345883558836588375883858839588405884158842588435884458845588465884758848588495885058851588525885358854588555885658857588585885958860588615886258863588645886558866588675886858869588705887158872588735887458875588765887758878588795888058881588825888358884588855888658887588885888958890588915889258893588945889558896588975889858899589005890158902589035890458905589065890758908589095891058911589125891358914589155891658917589185891958920589215892258923589245892558926589275892858929589305893158932589335893458935589365893758938589395894058941589425894358944589455894658947589485894958950589515895258953589545895558956589575895858959589605896158962589635896458965589665896758968589695897058971589725897358974589755897658977589785897958980589815898258983589845898558986589875898858989589905899158992589935899458995589965899758998589995900059001590025900359004590055900659007590085900959010590115901259013590145901559016590175901859019590205902159022590235902459025590265902759028590295903059031590325903359034590355903659037590385903959040590415904259043590445904559046590475904859049590505905159052590535905459055590565905759058590595906059061590625906359064590655906659067590685906959070590715907259073590745907559076590775907859079590805908159082590835908459085590865908759088590895909059091590925909359094590955909659097590985909959100591015910259103591045910559106591075910859109591105911159112591135911459115591165911759118591195912059121591225912359124591255912659127591285912959130591315913259133591345913559136591375913859139591405914159142591435914459145591465914759148591495915059151591525915359154591555915659157591585915959160591615916259163591645916559166591675916859169591705917159172591735917459175591765917759178591795918059181591825918359184591855918659187591885918959190591915919259193591945919559196591975919859199592005920159202592035920459205592065920759208592095921059211592125921359214592155921659217592185921959220592215922259223592245922559226592275922859229592305923159232592335923459235592365923759238592395924059241592425924359244592455924659247592485924959250592515925259253592545925559256592575925859259592605926159262592635926459265592665926759268592695927059271592725927359274592755927659277592785927959280592815928259283592845928559286592875928859289592905929159292592935929459295592965929759298592995930059301593025930359304593055930659307593085930959310593115931259313593145931559316593175931859319593205932159322593235932459325593265932759328593295933059331593325933359334593355933659337593385933959340593415934259343593445934559346593475934859349593505935159352593535935459355593565935759358593595936059361593625936359364593655936659367593685936959370593715937259373593745937559376593775937859379593805938159382593835938459385593865938759388593895939059391593925939359394593955939659397593985939959400594015940259403594045940559406594075940859409594105941159412594135941459415594165941759418594195942059421594225942359424594255942659427594285942959430594315943259433594345943559436594375943859439594405944159442594435944459445594465944759448594495945059451594525945359454594555945659457594585945959460594615946259463594645946559466594675946859469594705947159472594735947459475594765947759478594795948059481594825948359484594855948659487594885948959490594915949259493594945949559496594975949859499595005950159502595035950459505595065950759508595095951059511595125951359514595155951659517595185951959520595215952259523595245952559526595275952859529595305953159532595335953459535595365953759538595395954059541595425954359544595455954659547595485954959550595515955259553595545955559556595575955859559595605956159562595635956459565595665956759568595695957059571595725957359574595755957659577595785957959580595815958259583595845958559586595875958859589595905959159592595935959459595595965959759598595995960059601596025960359604596055960659607596085960959610596115961259613596145961559616596175961859619596205962159622596235962459625596265962759628596295963059631596325963359634596355963659637596385963959640596415964259643596445964559646596475964859649596505965159652596535965459655596565965759658596595966059661596625966359664596655966659667596685966959670596715967259673596745967559676596775967859679596805968159682596835968459685596865968759688596895969059691596925969359694596955969659697596985969959700597015970259703597045970559706597075970859709597105971159712597135971459715597165971759718597195972059721597225972359724597255972659727597285972959730597315973259733597345973559736597375973859739597405974159742597435974459745597465974759748597495975059751597525975359754597555975659757597585975959760597615976259763597645976559766597675976859769597705977159772597735977459775597765977759778597795978059781597825978359784597855978659787597885978959790597915979259793597945979559796597975979859799598005980159802598035980459805598065980759808598095981059811598125981359814598155981659817598185981959820598215982259823598245982559826598275982859829598305983159832598335983459835598365983759838598395984059841598425984359844598455984659847598485984959850598515985259853598545985559856598575985859859598605986159862598635986459865598665986759868598695987059871598725987359874598755987659877598785987959880598815988259883598845988559886598875988859889598905989159892598935989459895598965989759898598995990059901599025990359904599055990659907599085990959910599115991259913599145991559916599175991859919599205992159922599235992459925599265992759928599295993059931599325993359934599355993659937599385993959940599415994259943599445994559946599475994859949599505995159952599535995459955599565995759958599595996059961599625996359964599655996659967599685996959970599715997259973599745997559976599775997859979599805998159982599835998459985599865998759988599895999059991599925999359994599955999659997599985999960000600016000260003600046000560006600076000860009600106001160012600136001460015600166001760018600196002060021600226002360024600256002660027600286002960030600316003260033600346003560036600376003860039600406004160042600436004460045600466004760048600496005060051600526005360054600556005660057600586005960060600616006260063600646006560066600676006860069600706007160072600736007460075600766007760078600796008060081600826008360084600856008660087600886008960090600916009260093600946009560096600976009860099601006010160102601036010460105601066010760108601096011060111601126011360114601156011660117601186011960120601216012260123601246012560126601276012860129601306013160132601336013460135601366013760138601396014060141601426014360144601456014660147601486014960150601516015260153601546015560156601576015860159601606016160162601636016460165601666016760168601696017060171601726017360174601756017660177601786017960180601816018260183601846018560186601876018860189601906019160192601936019460195601966019760198601996020060201602026020360204602056020660207602086020960210602116021260213602146021560216602176021860219602206022160222602236022460225602266022760228602296023060231602326023360234602356023660237602386023960240602416024260243602446024560246602476024860249602506025160252602536025460255602566025760258602596026060261602626026360264602656026660267602686026960270602716027260273602746027560276602776027860279602806028160282602836028460285602866028760288602896029060291602926029360294602956029660297602986029960300603016030260303603046030560306603076030860309603106031160312603136031460315603166031760318603196032060321603226032360324603256032660327603286032960330603316033260333603346033560336603376033860339603406034160342603436034460345603466034760348603496035060351603526035360354603556035660357603586035960360603616036260363603646036560366603676036860369603706037160372603736037460375603766037760378603796038060381603826038360384603856038660387603886038960390603916039260393603946039560396603976039860399604006040160402604036040460405604066040760408604096041060411604126041360414604156041660417604186041960420604216042260423604246042560426604276042860429604306043160432604336043460435604366043760438604396044060441604426044360444604456044660447604486044960450604516045260453604546045560456604576045860459604606046160462604636046460465604666046760468604696047060471604726047360474604756047660477604786047960480604816048260483604846048560486604876048860489604906049160492604936049460495604966049760498604996050060501605026050360504605056050660507605086050960510605116051260513605146051560516605176051860519605206052160522605236052460525605266052760528605296053060531605326053360534605356053660537605386053960540605416054260543605446054560546605476054860549605506055160552605536055460555605566055760558605596056060561605626056360564605656056660567605686056960570605716057260573605746057560576605776057860579605806058160582605836058460585605866058760588605896059060591605926059360594605956059660597605986059960600606016060260603606046060560606606076060860609606106061160612606136061460615606166061760618606196062060621606226062360624606256062660627606286062960630606316063260633606346063560636606376063860639606406064160642606436064460645606466064760648606496065060651606526065360654606556065660657606586065960660606616066260663606646066560666606676066860669606706067160672606736067460675606766067760678606796068060681606826068360684606856068660687606886068960690606916069260693606946069560696606976069860699607006070160702607036070460705607066070760708607096071060711607126071360714607156071660717607186071960720607216072260723607246072560726607276072860729607306073160732607336073460735607366073760738607396074060741607426074360744607456074660747607486074960750607516075260753607546075560756607576075860759607606076160762607636076460765607666076760768607696077060771607726077360774607756077660777607786077960780607816078260783607846078560786607876078860789607906079160792607936079460795607966079760798607996080060801608026080360804608056080660807608086080960810608116081260813608146081560816608176081860819608206082160822608236082460825608266082760828608296083060831608326083360834608356083660837608386083960840608416084260843608446084560846608476084860849608506085160852608536085460855608566085760858608596086060861608626086360864608656086660867608686086960870608716087260873608746087560876608776087860879608806088160882608836088460885608866088760888608896089060891608926089360894608956089660897608986089960900609016090260903609046090560906609076090860909609106091160912609136091460915609166091760918609196092060921609226092360924609256092660927609286092960930609316093260933609346093560936609376093860939609406094160942609436094460945609466094760948609496095060951609526095360954609556095660957609586095960960609616096260963609646096560966609676096860969609706097160972609736097460975609766097760978609796098060981609826098360984609856098660987609886098960990609916099260993609946099560996609976099860999610006100161002610036100461005610066100761008610096101061011610126101361014610156101661017610186101961020610216102261023610246102561026610276102861029610306103161032610336103461035610366103761038610396104061041610426104361044610456104661047610486104961050610516105261053610546105561056610576105861059610606106161062610636106461065610666106761068610696107061071610726107361074610756107661077610786107961080610816108261083610846108561086610876108861089610906109161092610936109461095610966109761098610996110061101611026110361104611056110661107611086110961110611116111261113611146111561116611176111861119611206112161122611236112461125611266112761128611296113061131611326113361134611356113661137611386113961140611416114261143611446114561146611476114861149611506115161152611536115461155611566115761158611596116061161611626116361164611656116661167611686116961170611716117261173611746117561176611776117861179611806118161182611836118461185611866118761188611896119061191611926119361194611956119661197611986119961200612016120261203612046120561206612076120861209612106121161212612136121461215612166121761218612196122061221612226122361224612256122661227612286122961230612316123261233612346123561236612376123861239612406124161242612436124461245612466124761248612496125061251612526125361254612556125661257612586125961260612616126261263612646126561266612676126861269612706127161272612736127461275612766127761278612796128061281612826128361284612856128661287612886128961290612916129261293612946129561296612976129861299613006130161302613036130461305613066130761308613096131061311613126131361314613156131661317613186131961320613216132261323613246132561326613276132861329613306133161332613336133461335613366133761338613396134061341613426134361344613456134661347613486134961350613516135261353613546135561356613576135861359613606136161362613636136461365613666136761368613696137061371613726137361374613756137661377613786137961380613816138261383613846138561386613876138861389613906139161392613936139461395613966139761398613996140061401614026140361404614056140661407614086140961410614116141261413614146141561416614176141861419614206142161422614236142461425614266142761428614296143061431614326143361434614356143661437614386143961440614416144261443614446144561446614476144861449614506145161452614536145461455614566145761458614596146061461614626146361464614656146661467614686146961470614716147261473614746147561476614776147861479614806148161482614836148461485614866148761488614896149061491614926149361494614956149661497614986149961500615016150261503615046150561506615076150861509615106151161512615136151461515615166151761518615196152061521615226152361524615256152661527615286152961530615316153261533615346153561536615376153861539615406154161542615436154461545615466154761548615496155061551615526155361554615556155661557615586155961560615616156261563615646156561566615676156861569615706157161572615736157461575615766157761578615796158061581615826158361584615856158661587615886158961590615916159261593615946159561596615976159861599616006160161602616036160461605616066160761608616096161061611616126161361614616156161661617616186161961620616216162261623616246162561626616276162861629616306163161632616336163461635616366163761638616396164061641616426164361644616456164661647616486164961650616516165261653616546165561656616576165861659616606166161662616636166461665616666166761668616696167061671616726167361674616756167661677616786167961680616816168261683616846168561686616876168861689616906169161692616936169461695616966169761698616996170061701617026170361704617056170661707617086170961710617116171261713617146171561716617176171861719617206172161722617236172461725617266172761728617296173061731617326173361734617356173661737617386173961740617416174261743617446174561746617476174861749617506175161752617536175461755617566175761758617596176061761617626176361764617656176661767617686176961770617716177261773617746177561776617776177861779617806178161782617836178461785617866178761788617896179061791617926179361794617956179661797617986179961800618016180261803618046180561806618076180861809618106181161812618136181461815618166181761818618196182061821618226182361824618256182661827618286182961830618316183261833618346183561836618376183861839618406184161842618436184461845618466184761848618496185061851618526185361854618556185661857618586185961860618616186261863618646186561866618676186861869618706187161872618736187461875618766187761878618796188061881618826188361884618856188661887618886188961890618916189261893618946189561896618976189861899619006190161902619036190461905619066190761908619096191061911619126191361914619156191661917619186191961920619216192261923619246192561926619276192861929619306193161932619336193461935619366193761938619396194061941619426194361944619456194661947619486194961950619516195261953619546195561956619576195861959619606196161962619636196461965619666196761968619696197061971619726197361974619756197661977619786197961980619816198261983619846198561986619876198861989619906199161992619936199461995619966199761998619996200062001620026200362004620056200662007620086200962010620116201262013620146201562016620176201862019620206202162022620236202462025620266202762028620296203062031620326203362034620356203662037620386203962040620416204262043620446204562046620476204862049620506205162052620536205462055620566205762058620596206062061620626206362064620656206662067620686206962070620716207262073620746207562076620776207862079620806208162082620836208462085620866208762088620896209062091620926209362094620956209662097620986209962100621016210262103621046210562106621076210862109621106211162112621136211462115621166211762118621196212062121621226212362124621256212662127621286212962130621316213262133621346213562136621376213862139621406214162142621436214462145621466214762148621496215062151621526215362154621556215662157621586215962160621616216262163621646216562166621676216862169621706217162172621736217462175621766217762178621796218062181621826218362184621856218662187621886218962190621916219262193621946219562196621976219862199622006220162202622036220462205622066220762208622096221062211622126221362214622156221662217622186221962220622216222262223622246222562226622276222862229622306223162232622336223462235622366223762238622396224062241622426224362244622456224662247622486224962250622516225262253622546225562256622576225862259622606226162262622636226462265622666226762268622696227062271622726227362274622756227662277622786227962280622816228262283622846228562286622876228862289622906229162292622936229462295622966229762298622996230062301623026230362304623056230662307623086230962310623116231262313623146231562316623176231862319623206232162322623236232462325623266232762328623296233062331623326233362334623356233662337623386233962340623416234262343623446234562346623476234862349623506235162352623536235462355623566235762358623596236062361623626236362364623656236662367623686236962370623716237262373623746237562376623776237862379623806238162382623836238462385623866238762388623896239062391623926239362394623956239662397623986239962400624016240262403624046240562406624076240862409624106241162412624136241462415624166241762418624196242062421624226242362424624256242662427624286242962430624316243262433624346243562436624376243862439624406244162442624436244462445624466244762448624496245062451624526245362454624556245662457624586245962460624616246262463624646246562466624676246862469624706247162472624736247462475624766247762478624796248062481624826248362484624856248662487624886248962490624916249262493624946249562496624976249862499625006250162502625036250462505625066250762508625096251062511625126251362514625156251662517625186251962520625216252262523625246252562526625276252862529625306253162532625336253462535625366253762538625396254062541625426254362544625456254662547625486254962550625516255262553625546255562556625576255862559625606256162562625636256462565625666256762568625696257062571625726257362574625756257662577625786257962580625816258262583625846258562586625876258862589625906259162592625936259462595625966259762598625996260062601626026260362604626056260662607626086260962610626116261262613626146261562616626176261862619626206262162622626236262462625626266262762628626296263062631626326263362634626356263662637626386263962640626416264262643626446264562646626476264862649626506265162652626536265462655626566265762658626596266062661626626266362664626656266662667626686266962670626716267262673626746267562676626776267862679626806268162682626836268462685626866268762688626896269062691626926269362694626956269662697626986269962700627016270262703627046270562706627076270862709627106271162712627136271462715627166271762718627196272062721627226272362724627256272662727627286272962730627316273262733627346273562736627376273862739627406274162742627436274462745627466274762748627496275062751627526275362754627556275662757627586275962760627616276262763627646276562766627676276862769627706277162772627736277462775627766277762778627796278062781627826278362784627856278662787627886278962790627916279262793627946279562796627976279862799628006280162802628036280462805628066280762808628096281062811628126281362814628156281662817628186281962820628216282262823628246282562826628276282862829628306283162832628336283462835628366283762838628396284062841628426284362844628456284662847628486284962850628516285262853628546285562856628576285862859628606286162862628636286462865628666286762868628696287062871628726287362874628756287662877628786287962880628816288262883628846288562886628876288862889628906289162892628936289462895628966289762898628996290062901629026290362904629056290662907629086290962910629116291262913629146291562916629176291862919629206292162922629236292462925629266292762928629296293062931629326293362934629356293662937629386293962940629416294262943629446294562946629476294862949629506295162952629536295462955629566295762958629596296062961629626296362964629656296662967629686296962970629716297262973629746297562976629776297862979629806298162982629836298462985629866298762988629896299062991629926299362994629956299662997629986299963000630016300263003630046300563006630076300863009630106301163012630136301463015630166301763018630196302063021630226302363024630256302663027630286302963030630316303263033630346303563036630376303863039630406304163042630436304463045630466304763048630496305063051630526305363054630556305663057630586305963060630616306263063630646306563066630676306863069630706307163072630736307463075630766307763078630796308063081630826308363084630856308663087630886308963090630916309263093630946309563096630976309863099631006310163102631036310463105631066310763108631096311063111631126311363114631156311663117631186311963120631216312263123631246312563126631276312863129631306313163132631336313463135631366313763138631396314063141631426314363144631456314663147631486314963150631516315263153631546315563156631576315863159631606316163162631636316463165631666316763168631696317063171631726317363174631756317663177631786317963180631816318263183631846318563186631876318863189631906319163192631936319463195631966319763198631996320063201632026320363204632056320663207632086320963210632116321263213632146321563216632176321863219632206322163222632236322463225632266322763228632296323063231632326323363234632356323663237632386323963240632416324263243632446324563246632476324863249632506325163252632536325463255632566325763258632596326063261632626326363264632656326663267632686326963270632716327263273632746327563276632776327863279632806328163282632836328463285632866328763288632896329063291632926329363294632956329663297632986329963300633016330263303633046330563306633076330863309633106331163312633136331463315633166331763318633196332063321633226332363324633256332663327633286332963330633316333263333633346333563336633376333863339633406334163342633436334463345633466334763348633496335063351633526335363354633556335663357633586335963360633616336263363633646336563366633676336863369633706337163372633736337463375633766337763378633796338063381633826338363384633856338663387633886338963390633916339263393633946339563396633976339863399634006340163402634036340463405634066340763408634096341063411634126341363414634156341663417634186341963420634216342263423634246342563426634276342863429634306343163432634336343463435634366343763438634396344063441634426344363444634456344663447634486344963450634516345263453634546345563456634576345863459634606346163462634636346463465634666346763468634696347063471634726347363474634756347663477634786347963480634816348263483634846348563486634876348863489634906349163492634936349463495634966349763498634996350063501635026350363504635056350663507635086350963510635116351263513635146351563516635176351863519635206352163522635236352463525635266352763528635296353063531635326353363534635356353663537635386353963540635416354263543635446354563546635476354863549635506355163552635536355463555635566355763558635596356063561635626356363564635656356663567635686356963570635716357263573635746357563576635776357863579635806358163582635836358463585635866358763588635896359063591635926359363594635956359663597635986359963600636016360263603636046360563606636076360863609636106361163612636136361463615636166361763618636196362063621636226362363624636256362663627636286362963630636316363263633636346363563636636376363863639636406364163642636436364463645636466364763648636496365063651636526365363654636556365663657636586365963660636616366263663636646366563666636676366863669636706367163672636736367463675636766367763678636796368063681636826368363684636856368663687636886368963690636916369263693636946369563696636976369863699637006370163702637036370463705637066370763708637096371063711637126371363714637156371663717637186371963720637216372263723637246372563726637276372863729637306373163732637336373463735637366373763738637396374063741637426374363744637456374663747637486374963750637516375263753637546375563756637576375863759637606376163762637636376463765637666376763768637696377063771637726377363774637756377663777637786377963780637816378263783637846378563786637876378863789637906379163792637936379463795637966379763798637996380063801638026380363804638056380663807638086380963810638116381263813638146381563816638176381863819638206382163822638236382463825638266382763828638296383063831638326383363834638356383663837638386383963840638416384263843638446384563846638476384863849638506385163852638536385463855638566385763858638596386063861638626386363864638656386663867638686386963870638716387263873638746387563876638776387863879638806388163882638836388463885638866388763888638896389063891638926389363894638956389663897638986389963900639016390263903639046390563906639076390863909639106391163912639136391463915639166391763918639196392063921639226392363924639256392663927639286392963930639316393263933639346393563936639376393863939639406394163942639436394463945639466394763948639496395063951639526395363954639556395663957639586395963960639616396263963639646396563966639676396863969639706397163972639736397463975639766397763978639796398063981639826398363984639856398663987639886398963990639916399263993639946399563996639976399863999640006400164002640036400464005640066400764008640096401064011640126401364014640156401664017640186401964020640216402264023640246402564026640276402864029640306403164032640336403464035640366403764038640396404064041640426404364044640456404664047640486404964050640516405264053640546405564056640576405864059640606406164062640636406464065640666406764068640696407064071640726407364074640756407664077640786407964080640816408264083640846408564086640876408864089640906409164092640936409464095640966409764098640996410064101641026410364104641056410664107641086410964110641116411264113641146411564116641176411864119641206412164122641236412464125641266412764128641296413064131641326413364134641356413664137641386413964140641416414264143641446414564146641476414864149641506415164152641536415464155641566415764158641596416064161641626416364164641656416664167641686416964170641716417264173641746417564176641776417864179641806418164182641836418464185641866418764188641896419064191641926419364194641956419664197641986419964200642016420264203642046420564206642076420864209642106421164212642136421464215642166421764218642196422064221642226422364224642256422664227642286422964230642316423264233642346423564236642376423864239642406424164242642436424464245642466424764248642496425064251642526425364254642556425664257642586425964260642616426264263642646426564266642676426864269642706427164272642736427464275642766427764278642796428064281642826428364284642856428664287642886428964290642916429264293642946429564296642976429864299643006430164302643036430464305643066430764308643096431064311643126431364314643156431664317643186431964320643216432264323643246432564326643276432864329643306433164332643336433464335643366433764338643396434064341643426434364344643456434664347643486434964350643516435264353643546435564356643576435864359643606436164362643636436464365643666436764368643696437064371643726437364374643756437664377643786437964380643816438264383643846438564386643876438864389643906439164392643936439464395643966439764398643996440064401644026440364404644056440664407644086440964410644116441264413644146441564416644176441864419644206442164422644236442464425644266442764428644296443064431644326443364434644356443664437644386443964440644416444264443644446444564446644476444864449644506445164452644536445464455644566445764458644596446064461644626446364464644656446664467644686446964470644716447264473644746447564476644776447864479644806448164482644836448464485644866448764488644896449064491644926449364494644956449664497644986449964500645016450264503645046450564506645076450864509645106451164512645136451464515645166451764518645196452064521645226452364524645256452664527645286452964530645316453264533645346453564536645376453864539645406454164542645436454464545645466454764548645496455064551645526455364554645556455664557645586455964560645616456264563645646456564566645676456864569645706457164572645736457464575645766457764578645796458064581645826458364584645856458664587645886458964590645916459264593645946459564596645976459864599646006460164602646036460464605646066460764608646096461064611646126461364614646156461664617646186461964620646216462264623646246462564626646276462864629646306463164632646336463464635646366463764638646396464064641646426464364644646456464664647646486464964650646516465264653646546465564656646576465864659646606466164662646636466464665646666466764668646696467064671646726467364674646756467664677646786467964680646816468264683646846468564686646876468864689646906469164692646936469464695646966469764698646996470064701647026470364704647056470664707647086470964710647116471264713647146471564716647176471864719647206472164722647236472464725647266472764728647296473064731647326473364734647356473664737647386473964740647416474264743647446474564746647476474864749647506475164752647536475464755647566475764758647596476064761647626476364764647656476664767647686476964770647716477264773647746477564776647776477864779647806478164782647836478464785647866478764788647896479064791647926479364794647956479664797647986479964800648016480264803648046480564806648076480864809648106481164812648136481464815648166481764818648196482064821648226482364824648256482664827648286482964830648316483264833648346483564836648376483864839648406484164842648436484464845648466484764848648496485064851648526485364854648556485664857648586485964860648616486264863648646486564866648676486864869648706487164872648736487464875648766487764878648796488064881648826488364884648856488664887648886488964890648916489264893648946489564896648976489864899649006490164902649036490464905649066490764908649096491064911649126491364914649156491664917649186491964920649216492264923649246492564926649276492864929649306493164932649336493464935649366493764938649396494064941649426494364944649456494664947649486494964950649516495264953649546495564956649576495864959649606496164962649636496464965649666496764968649696497064971649726497364974649756497664977649786497964980649816498264983649846498564986649876498864989649906499164992649936499464995649966499764998649996500065001650026500365004650056500665007650086500965010650116501265013650146501565016650176501865019650206502165022650236502465025650266502765028650296503065031650326503365034650356503665037650386503965040650416504265043650446504565046650476504865049650506505165052650536505465055650566505765058650596506065061650626506365064650656506665067650686506965070650716507265073650746507565076650776507865079650806508165082650836508465085650866508765088650896509065091650926509365094650956509665097650986509965100651016510265103651046510565106651076510865109651106511165112651136511465115651166511765118651196512065121651226512365124651256512665127651286512965130651316513265133651346513565136651376513865139651406514165142651436514465145651466514765148651496515065151651526515365154651556515665157651586515965160651616516265163651646516565166651676516865169651706517165172651736517465175651766517765178651796518065181651826518365184651856518665187651886518965190651916519265193651946519565196651976519865199652006520165202652036520465205652066520765208652096521065211652126521365214652156521665217652186521965220652216522265223652246522565226652276522865229652306523165232652336523465235652366523765238652396524065241652426524365244652456524665247652486524965250652516525265253652546525565256652576525865259652606526165262652636526465265652666526765268652696527065271652726527365274652756527665277652786527965280652816528265283652846528565286652876528865289652906529165292652936529465295652966529765298652996530065301653026530365304653056530665307653086530965310653116531265313653146531565316653176531865319653206532165322653236532465325653266532765328653296533065331653326533365334653356533665337653386533965340653416534265343653446534565346653476534865349653506535165352653536535465355653566535765358653596536065361653626536365364653656536665367653686536965370653716537265373653746537565376653776537865379653806538165382653836538465385653866538765388653896539065391653926539365394653956539665397653986539965400654016540265403654046540565406654076540865409654106541165412654136541465415654166541765418654196542065421654226542365424654256542665427654286542965430654316543265433654346543565436654376543865439654406544165442654436544465445654466544765448654496545065451654526545365454654556545665457654586545965460654616546265463654646546565466654676546865469654706547165472654736547465475654766547765478654796548065481654826548365484654856548665487654886548965490654916549265493654946549565496654976549865499655006550165502655036550465505655066550765508655096551065511655126551365514655156551665517655186551965520655216552265523655246552565526655276552865529655306553165532655336553465535655366553765538655396554065541655426554365544655456554665547655486554965550655516555265553655546555565556655576555865559655606556165562655636556465565655666556765568655696557065571655726557365574655756557665577655786557965580655816558265583655846558565586655876558865589655906559165592655936559465595655966559765598655996560065601656026560365604656056560665607656086560965610656116561265613656146561565616656176561865619656206562165622656236562465625656266562765628656296563065631656326563365634656356563665637656386563965640656416564265643656446564565646656476564865649656506565165652656536565465655656566565765658656596566065661656626566365664656656566665667656686566965670656716567265673656746567565676656776567865679656806568165682656836568465685656866568765688656896569065691656926569365694656956569665697656986569965700657016570265703657046570565706657076570865709657106571165712657136571465715657166571765718657196572065721657226572365724657256572665727657286572965730657316573265733657346573565736657376573865739657406574165742657436574465745657466574765748657496575065751657526575365754657556575665757657586575965760657616576265763657646576565766657676576865769657706577165772657736577465775657766577765778657796578065781657826578365784657856578665787657886578965790657916579265793657946579565796657976579865799658006580165802658036580465805658066580765808658096581065811658126581365814658156581665817658186581965820658216582265823658246582565826658276582865829658306583165832658336583465835658366583765838658396584065841658426584365844658456584665847658486584965850658516585265853658546585565856658576585865859658606586165862658636586465865658666586765868658696587065871658726587365874658756587665877658786587965880658816588265883658846588565886658876588865889658906589165892658936589465895658966589765898658996590065901659026590365904659056590665907659086590965910659116591265913659146591565916659176591865919659206592165922659236592465925659266592765928659296593065931659326593365934659356593665937659386593965940659416594265943659446594565946659476594865949659506595165952659536595465955659566595765958659596596065961659626596365964659656596665967659686596965970659716597265973659746597565976659776597865979659806598165982659836598465985659866598765988659896599065991659926599365994659956599665997659986599966000660016600266003660046600566006660076600866009660106601166012660136601466015660166601766018660196602066021660226602366024660256602666027660286602966030660316603266033660346603566036660376603866039660406604166042660436604466045660466604766048660496605066051660526605366054660556605666057660586605966060660616606266063660646606566066660676606866069660706607166072660736607466075660766607766078660796608066081660826608366084660856608666087660886608966090660916609266093660946609566096660976609866099661006610166102661036610466105661066610766108661096611066111661126611366114661156611666117661186611966120661216612266123661246612566126661276612866129661306613166132661336613466135661366613766138661396614066141661426614366144661456614666147661486614966150661516615266153661546615566156661576615866159661606616166162661636616466165661666616766168661696617066171661726617366174661756617666177661786617966180661816618266183661846618566186661876618866189661906619166192661936619466195661966619766198661996620066201662026620366204662056620666207662086620966210662116621266213662146621566216662176621866219662206622166222662236622466225662266622766228662296623066231662326623366234662356623666237662386623966240662416624266243662446624566246662476624866249662506625166252662536625466255662566625766258662596626066261662626626366264662656626666267662686626966270662716627266273662746627566276662776627866279662806628166282662836628466285662866628766288662896629066291662926629366294662956629666297662986629966300663016630266303663046630566306663076630866309663106631166312663136631466315663166631766318663196632066321663226632366324663256632666327663286632966330663316633266333663346633566336663376633866339663406634166342663436634466345663466634766348663496635066351663526635366354663556635666357663586635966360663616636266363663646636566366663676636866369663706637166372663736637466375663766637766378663796638066381663826638366384663856638666387663886638966390663916639266393663946639566396663976639866399664006640166402664036640466405664066640766408664096641066411664126641366414664156641666417664186641966420664216642266423664246642566426664276642866429664306643166432664336643466435664366643766438664396644066441664426644366444664456644666447664486644966450664516645266453664546645566456664576645866459664606646166462664636646466465664666646766468664696647066471664726647366474664756647666477664786647966480664816648266483664846648566486664876648866489664906649166492664936649466495664966649766498664996650066501665026650366504665056650666507665086650966510665116651266513665146651566516665176651866519665206652166522665236652466525665266652766528665296653066531665326653366534665356653666537665386653966540665416654266543665446654566546665476654866549665506655166552665536655466555665566655766558665596656066561665626656366564665656656666567665686656966570665716657266573665746657566576665776657866579665806658166582665836658466585665866658766588665896659066591665926659366594665956659666597665986659966600666016660266603666046660566606666076660866609666106661166612666136661466615666166661766618666196662066621666226662366624666256662666627666286662966630666316663266633666346663566636666376663866639666406664166642666436664466645666466664766648666496665066651666526665366654666556665666657666586665966660666616666266663666646666566666666676666866669666706667166672666736667466675666766667766678666796668066681666826668366684666856668666687666886668966690666916669266693666946669566696666976669866699667006670166702667036670466705667066670766708667096671066711667126671366714667156671666717667186671966720667216672266723667246672566726667276672866729667306673166732667336673466735667366673766738667396674066741667426674366744667456674666747667486674966750667516675266753667546675566756667576675866759667606676166762667636676466765667666676766768667696677066771667726677366774667756677666777667786677966780667816678266783667846678566786667876678866789667906679166792667936679466795667966679766798667996680066801668026680366804668056680666807668086680966810668116681266813668146681566816668176681866819668206682166822668236682466825668266682766828668296683066831668326683366834668356683666837668386683966840668416684266843668446684566846668476684866849668506685166852668536685466855668566685766858668596686066861668626686366864668656686666867668686686966870668716687266873668746687566876668776687866879668806688166882668836688466885668866688766888668896689066891668926689366894668956689666897668986689966900669016690266903669046690566906669076690866909669106691166912669136691466915669166691766918669196692066921669226692366924669256692666927669286692966930669316693266933669346693566936669376693866939669406694166942669436694466945669466694766948669496695066951669526695366954669556695666957669586695966960669616696266963669646696566966669676696866969669706697166972669736697466975669766697766978669796698066981669826698366984669856698666987669886698966990669916699266993669946699566996669976699866999670006700167002670036700467005670066700767008670096701067011670126701367014670156701667017670186701967020670216702267023670246702567026670276702867029670306703167032670336703467035670366703767038670396704067041670426704367044670456704667047670486704967050670516705267053670546705567056670576705867059670606706167062670636706467065670666706767068670696707067071670726707367074670756707667077670786707967080670816708267083670846708567086670876708867089670906709167092670936709467095670966709767098670996710067101671026710367104671056710667107671086710967110671116711267113671146711567116671176711867119671206712167122671236712467125671266712767128671296713067131671326713367134671356713667137671386713967140671416714267143671446714567146671476714867149671506715167152671536715467155671566715767158671596716067161671626716367164671656716667167671686716967170671716717267173671746717567176671776717867179671806718167182671836718467185671866718767188671896719067191671926719367194671956719667197671986719967200672016720267203672046720567206672076720867209672106721167212672136721467215672166721767218672196722067221672226722367224672256722667227672286722967230672316723267233672346723567236672376723867239672406724167242672436724467245672466724767248672496725067251672526725367254672556725667257672586725967260672616726267263672646726567266672676726867269672706727167272672736727467275672766727767278672796728067281672826728367284672856728667287672886728967290672916729267293672946729567296672976729867299673006730167302673036730467305673066730767308673096731067311673126731367314673156731667317673186731967320673216732267323673246732567326673276732867329673306733167332673336733467335673366733767338673396734067341673426734367344673456734667347673486734967350673516735267353673546735567356673576735867359673606736167362673636736467365673666736767368673696737067371673726737367374673756737667377673786737967380673816738267383673846738567386673876738867389673906739167392673936739467395673966739767398673996740067401674026740367404674056740667407674086740967410674116741267413674146741567416674176741867419674206742167422674236742467425674266742767428674296743067431674326743367434674356743667437674386743967440674416744267443674446744567446674476744867449674506745167452674536745467455674566745767458674596746067461674626746367464674656746667467674686746967470674716747267473674746747567476674776747867479674806748167482674836748467485674866748767488674896749067491674926749367494674956749667497674986749967500675016750267503675046750567506675076750867509675106751167512675136751467515675166751767518675196752067521675226752367524675256752667527675286752967530675316753267533675346753567536675376753867539675406754167542675436754467545675466754767548675496755067551675526755367554675556755667557675586755967560675616756267563675646756567566675676756867569675706757167572675736757467575675766757767578675796758067581675826758367584675856758667587675886758967590675916759267593675946759567596675976759867599676006760167602676036760467605676066760767608676096761067611676126761367614676156761667617676186761967620676216762267623676246762567626676276762867629676306763167632676336763467635676366763767638676396764067641676426764367644676456764667647676486764967650676516765267653676546765567656676576765867659676606766167662676636766467665676666766767668676696767067671676726767367674676756767667677676786767967680676816768267683676846768567686676876768867689676906769167692676936769467695676966769767698676996770067701677026770367704677056770667707677086770967710677116771267713677146771567716677176771867719677206772167722677236772467725677266772767728677296773067731677326773367734677356773667737677386773967740677416774267743677446774567746677476774867749677506775167752677536775467755677566775767758677596776067761677626776367764677656776667767677686776967770677716777267773677746777567776677776777867779677806778167782677836778467785677866778767788677896779067791677926779367794677956779667797677986779967800678016780267803678046780567806678076780867809678106781167812678136781467815678166781767818678196782067821678226782367824678256782667827678286782967830678316783267833678346783567836678376783867839678406784167842678436784467845678466784767848678496785067851678526785367854678556785667857678586785967860678616786267863678646786567866678676786867869678706787167872678736787467875678766787767878678796788067881678826788367884678856788667887678886788967890678916789267893678946789567896678976789867899679006790167902679036790467905679066790767908679096791067911679126791367914679156791667917679186791967920679216792267923679246792567926679276792867929679306793167932679336793467935679366793767938679396794067941679426794367944679456794667947679486794967950679516795267953679546795567956679576795867959679606796167962679636796467965679666796767968679696797067971679726797367974679756797667977679786797967980679816798267983679846798567986679876798867989679906799167992679936799467995679966799767998679996800068001680026800368004680056800668007680086800968010680116801268013680146801568016680176801868019680206802168022680236802468025680266802768028680296803068031680326803368034680356803668037680386803968040680416804268043680446804568046680476804868049680506805168052680536805468055680566805768058680596806068061680626806368064680656806668067680686806968070680716807268073680746807568076680776807868079680806808168082680836808468085680866808768088680896809068091680926809368094680956809668097680986809968100681016810268103681046810568106681076810868109681106811168112681136811468115681166811768118681196812068121681226812368124681256812668127681286812968130681316813268133681346813568136681376813868139681406814168142681436814468145681466814768148681496815068151681526815368154681556815668157681586815968160681616816268163681646816568166681676816868169681706817168172681736817468175681766817768178681796818068181681826818368184681856818668187681886818968190681916819268193681946819568196681976819868199682006820168202682036820468205682066820768208682096821068211682126821368214682156821668217682186821968220682216822268223682246822568226682276822868229682306823168232682336823468235682366823768238682396824068241682426824368244682456824668247682486824968250682516825268253682546825568256682576825868259682606826168262682636826468265682666826768268682696827068271682726827368274682756827668277682786827968280682816828268283682846828568286682876828868289682906829168292682936829468295682966829768298682996830068301683026830368304683056830668307683086830968310683116831268313683146831568316683176831868319683206832168322683236832468325683266832768328683296833068331683326833368334683356833668337683386833968340683416834268343683446834568346683476834868349683506835168352683536835468355683566835768358683596836068361683626836368364683656836668367683686836968370683716837268373683746837568376683776837868379683806838168382683836838468385683866838768388683896839068391683926839368394683956839668397683986839968400684016840268403684046840568406684076840868409684106841168412684136841468415684166841768418684196842068421684226842368424684256842668427684286842968430684316843268433684346843568436684376843868439684406844168442684436844468445684466844768448684496845068451684526845368454684556845668457684586845968460684616846268463684646846568466684676846868469684706847168472684736847468475684766847768478684796848068481684826848368484684856848668487684886848968490684916849268493684946849568496684976849868499685006850168502685036850468505685066850768508685096851068511685126851368514685156851668517685186851968520685216852268523685246852568526685276852868529685306853168532685336853468535685366853768538685396854068541685426854368544685456854668547685486854968550685516855268553685546855568556685576855868559685606856168562685636856468565685666856768568685696857068571685726857368574685756857668577685786857968580685816858268583685846858568586685876858868589685906859168592685936859468595685966859768598685996860068601686026860368604686056860668607686086860968610686116861268613686146861568616686176861868619686206862168622686236862468625686266862768628686296863068631686326863368634686356863668637686386863968640686416864268643686446864568646686476864868649686506865168652686536865468655686566865768658686596866068661686626866368664686656866668667686686866968670686716867268673686746867568676686776867868679686806868168682686836868468685686866868768688686896869068691686926869368694686956869668697686986869968700687016870268703687046870568706687076870868709687106871168712687136871468715687166871768718687196872068721687226872368724687256872668727687286872968730687316873268733687346873568736687376873868739687406874168742687436874468745687466874768748687496875068751687526875368754687556875668757687586875968760687616876268763687646876568766687676876868769687706877168772687736877468775687766877768778687796878068781687826878368784687856878668787687886878968790687916879268793687946879568796687976879868799688006880168802688036880468805688066880768808688096881068811688126881368814688156881668817688186881968820688216882268823688246882568826688276882868829688306883168832688336883468835688366883768838688396884068841688426884368844688456884668847688486884968850688516885268853688546885568856688576885868859688606886168862688636886468865688666886768868688696887068871688726887368874688756887668877688786887968880688816888268883688846888568886688876888868889688906889168892688936889468895688966889768898688996890068901689026890368904689056890668907689086890968910689116891268913689146891568916689176891868919689206892168922689236892468925689266892768928689296893068931689326893368934689356893668937689386893968940689416894268943689446894568946689476894868949689506895168952689536895468955689566895768958689596896068961689626896368964689656896668967689686896968970689716897268973689746897568976689776897868979689806898168982689836898468985689866898768988689896899068991689926899368994689956899668997689986899969000690016900269003690046900569006690076900869009690106901169012690136901469015690166901769018690196902069021690226902369024690256902669027690286902969030690316903269033690346903569036690376903869039690406904169042690436904469045690466904769048690496905069051690526905369054690556905669057690586905969060690616906269063690646906569066690676906869069690706907169072690736907469075690766907769078690796908069081690826908369084690856908669087690886908969090690916909269093690946909569096690976909869099691006910169102691036910469105691066910769108691096911069111691126911369114691156911669117691186911969120691216912269123691246912569126691276912869129691306913169132691336913469135691366913769138691396914069141691426914369144691456914669147691486914969150691516915269153691546915569156691576915869159691606916169162691636916469165691666916769168691696917069171691726917369174691756917669177691786917969180691816918269183691846918569186691876918869189691906919169192691936919469195691966919769198691996920069201692026920369204692056920669207692086920969210692116921269213692146921569216692176921869219692206922169222692236922469225692266922769228692296923069231692326923369234692356923669237692386923969240692416924269243692446924569246692476924869249692506925169252692536925469255692566925769258692596926069261692626926369264692656926669267692686926969270692716927269273692746927569276692776927869279692806928169282692836928469285692866928769288692896929069291692926929369294692956929669297692986929969300693016930269303693046930569306693076930869309693106931169312693136931469315693166931769318693196932069321693226932369324693256932669327693286932969330693316933269333693346933569336693376933869339693406934169342693436934469345693466934769348693496935069351693526935369354693556935669357693586935969360693616936269363693646936569366693676936869369693706937169372693736937469375693766937769378693796938069381693826938369384693856938669387693886938969390693916939269393693946939569396693976939869399694006940169402694036940469405694066940769408694096941069411694126941369414694156941669417694186941969420694216942269423694246942569426694276942869429694306943169432694336943469435694366943769438694396944069441694426944369444694456944669447694486944969450694516945269453694546945569456694576945869459694606946169462694636946469465694666946769468694696947069471694726947369474694756947669477694786947969480694816948269483694846948569486694876948869489694906949169492694936949469495694966949769498694996950069501695026950369504695056950669507695086950969510695116951269513695146951569516695176951869519695206952169522695236952469525695266952769528695296953069531695326953369534695356953669537695386953969540695416954269543695446954569546695476954869549695506955169552695536955469555695566955769558695596956069561695626956369564695656956669567695686956969570695716957269573695746957569576695776957869579695806958169582695836958469585695866958769588695896959069591695926959369594695956959669597695986959969600696016960269603696046960569606696076960869609696106961169612696136961469615696166961769618696196962069621696226962369624696256962669627696286962969630696316963269633696346963569636696376963869639696406964169642696436964469645696466964769648696496965069651696526965369654696556965669657696586965969660696616966269663696646966569666696676966869669696706967169672696736967469675696766967769678696796968069681696826968369684696856968669687696886968969690696916969269693696946969569696696976969869699697006970169702697036970469705697066970769708697096971069711697126971369714697156971669717697186971969720697216972269723697246972569726697276972869729697306973169732697336973469735697366973769738697396974069741697426974369744697456974669747697486974969750697516975269753697546975569756697576975869759697606976169762697636976469765697666976769768697696977069771697726977369774697756977669777697786977969780697816978269783697846978569786697876978869789697906979169792697936979469795697966979769798697996980069801698026980369804698056980669807698086980969810698116981269813698146981569816698176981869819698206982169822698236982469825698266982769828698296983069831698326983369834698356983669837698386983969840698416984269843698446984569846698476984869849698506985169852698536985469855698566985769858698596986069861698626986369864698656986669867698686986969870698716987269873698746987569876698776987869879698806988169882698836988469885698866988769888698896989069891698926989369894698956989669897698986989969900699016990269903699046990569906699076990869909699106991169912699136991469915699166991769918699196992069921699226992369924699256992669927699286992969930699316993269933699346993569936699376993869939699406994169942699436994469945699466994769948699496995069951699526995369954699556995669957699586995969960699616996269963699646996569966699676996869969699706997169972699736997469975699766997769978699796998069981699826998369984699856998669987699886998969990699916999269993699946999569996699976999869999700007000170002700037000470005700067000770008700097001070011700127001370014700157001670017700187001970020700217002270023700247002570026700277002870029700307003170032700337003470035700367003770038700397004070041700427004370044700457004670047700487004970050700517005270053700547005570056700577005870059700607006170062700637006470065700667006770068700697007070071700727007370074700757007670077700787007970080700817008270083700847008570086700877008870089700907009170092700937009470095700967009770098700997010070101701027010370104701057010670107701087010970110701117011270113701147011570116701177011870119701207012170122701237012470125701267012770128701297013070131701327013370134701357013670137701387013970140701417014270143701447014570146701477014870149701507015170152701537015470155701567015770158701597016070161701627016370164701657016670167701687016970170701717017270173701747017570176701777017870179701807018170182701837018470185701867018770188701897019070191701927019370194701957019670197701987019970200702017020270203702047020570206702077020870209702107021170212702137021470215702167021770218702197022070221702227022370224702257022670227702287022970230702317023270233702347023570236702377023870239702407024170242702437024470245702467024770248702497025070251702527025370254702557025670257702587025970260702617026270263702647026570266702677026870269702707027170272702737027470275702767027770278702797028070281702827028370284702857028670287702887028970290702917029270293702947029570296702977029870299703007030170302703037030470305703067030770308703097031070311703127031370314703157031670317703187031970320703217032270323703247032570326703277032870329703307033170332703337033470335703367033770338703397034070341703427034370344703457034670347703487034970350703517035270353703547035570356703577035870359703607036170362703637036470365703667036770368703697037070371703727037370374703757037670377703787037970380703817038270383703847038570386703877038870389703907039170392703937039470395703967039770398703997040070401704027040370404704057040670407704087040970410704117041270413704147041570416704177041870419704207042170422704237042470425704267042770428704297043070431704327043370434704357043670437704387043970440704417044270443704447044570446704477044870449704507045170452704537045470455704567045770458704597046070461704627046370464704657046670467704687046970470704717047270473704747047570476704777047870479704807048170482704837048470485704867048770488704897049070491704927049370494704957049670497704987049970500705017050270503705047050570506705077050870509705107051170512705137051470515705167051770518705197052070521705227052370524705257052670527705287052970530705317053270533705347053570536705377053870539705407054170542705437054470545705467054770548705497055070551705527055370554705557055670557705587055970560705617056270563705647056570566705677056870569705707057170572705737057470575705767057770578705797058070581705827058370584705857058670587705887058970590705917059270593705947059570596705977059870599706007060170602706037060470605706067060770608706097061070611706127061370614706157061670617706187061970620706217062270623706247062570626706277062870629706307063170632706337063470635706367063770638706397064070641706427064370644706457064670647706487064970650706517065270653706547065570656706577065870659706607066170662706637066470665706667066770668706697067070671706727067370674706757067670677706787067970680706817068270683706847068570686706877068870689706907069170692706937069470695706967069770698706997070070701707027070370704707057070670707707087070970710707117071270713707147071570716707177071870719707207072170722707237072470725707267072770728707297073070731707327073370734707357073670737707387073970740707417074270743707447074570746707477074870749707507075170752707537075470755707567075770758707597076070761707627076370764707657076670767707687076970770707717077270773707747077570776707777077870779707807078170782707837078470785707867078770788707897079070791707927079370794707957079670797707987079970800708017080270803708047080570806708077080870809708107081170812708137081470815708167081770818708197082070821708227082370824708257082670827708287082970830708317083270833708347083570836708377083870839708407084170842708437084470845708467084770848708497085070851708527085370854708557085670857708587085970860708617086270863708647086570866708677086870869708707087170872708737087470875708767087770878708797088070881708827088370884708857088670887708887088970890708917089270893708947089570896708977089870899709007090170902709037090470905709067090770908709097091070911709127091370914709157091670917709187091970920709217092270923709247092570926709277092870929709307093170932709337093470935709367093770938709397094070941709427094370944709457094670947709487094970950709517095270953709547095570956709577095870959709607096170962709637096470965709667096770968709697097070971709727097370974709757097670977709787097970980709817098270983709847098570986709877098870989709907099170992709937099470995709967099770998709997100071001710027100371004710057100671007710087100971010710117101271013710147101571016710177101871019710207102171022710237102471025710267102771028710297103071031710327103371034710357103671037710387103971040710417104271043710447104571046710477104871049710507105171052710537105471055710567105771058710597106071061710627106371064710657106671067710687106971070710717107271073710747107571076710777107871079710807108171082710837108471085710867108771088710897109071091710927109371094710957109671097710987109971100711017110271103711047110571106711077110871109711107111171112711137111471115711167111771118711197112071121711227112371124711257112671127711287112971130711317113271133711347113571136711377113871139711407114171142711437114471145711467114771148711497115071151711527115371154711557115671157711587115971160711617116271163711647116571166711677116871169711707117171172711737117471175711767117771178711797118071181711827118371184711857118671187711887118971190711917119271193711947119571196711977119871199712007120171202712037120471205712067120771208712097121071211712127121371214712157121671217712187121971220712217122271223712247122571226712277122871229712307123171232712337123471235712367123771238712397124071241712427124371244712457124671247712487124971250712517125271253712547125571256712577125871259712607126171262712637126471265712667126771268712697127071271712727127371274712757127671277712787127971280712817128271283712847128571286712877128871289712907129171292712937129471295712967129771298712997130071301713027130371304713057130671307713087130971310713117131271313713147131571316713177131871319713207132171322713237132471325713267132771328713297133071331713327133371334713357133671337713387133971340713417134271343713447134571346713477134871349713507135171352713537135471355713567135771358713597136071361713627136371364713657136671367713687136971370713717137271373713747137571376713777137871379713807138171382713837138471385713867138771388713897139071391713927139371394713957139671397713987139971400714017140271403714047140571406714077140871409714107141171412714137141471415714167141771418714197142071421714227142371424714257142671427714287142971430714317143271433714347143571436714377143871439714407144171442714437144471445714467144771448714497145071451714527145371454714557145671457714587145971460714617146271463714647146571466714677146871469714707147171472714737147471475714767147771478714797148071481714827148371484714857148671487714887148971490714917149271493714947149571496714977149871499715007150171502715037150471505715067150771508715097151071511715127151371514715157151671517715187151971520715217152271523715247152571526715277152871529715307153171532715337153471535715367153771538715397154071541715427154371544715457154671547715487154971550715517155271553715547155571556715577155871559715607156171562715637156471565715667156771568715697157071571715727157371574715757157671577715787157971580715817158271583715847158571586715877158871589715907159171592715937159471595715967159771598715997160071601716027160371604716057160671607716087160971610716117161271613716147161571616716177161871619716207162171622716237162471625716267162771628716297163071631716327163371634716357163671637716387163971640716417164271643716447164571646716477164871649716507165171652716537165471655716567165771658716597166071661716627166371664716657166671667716687166971670716717167271673716747167571676716777167871679716807168171682716837168471685716867168771688716897169071691716927169371694716957169671697716987169971700717017170271703717047170571706717077170871709717107171171712717137171471715717167171771718717197172071721717227172371724717257172671727717287172971730717317173271733717347173571736717377173871739717407174171742717437174471745717467174771748717497175071751717527175371754717557175671757717587175971760717617176271763717647176571766717677176871769717707177171772717737177471775717767177771778717797178071781717827178371784717857178671787717887178971790717917179271793717947179571796717977179871799718007180171802718037180471805718067180771808718097181071811718127181371814718157181671817718187181971820718217182271823718247182571826718277182871829718307183171832718337183471835718367183771838718397184071841718427184371844718457184671847718487184971850718517185271853718547185571856718577185871859718607186171862718637186471865718667186771868718697187071871718727187371874718757187671877718787187971880718817188271883718847188571886718877188871889718907189171892718937189471895718967189771898718997190071901719027190371904719057190671907719087190971910719117191271913719147191571916719177191871919719207192171922719237192471925719267192771928719297193071931719327193371934719357193671937719387193971940719417194271943719447194571946719477194871949719507195171952719537195471955719567195771958719597196071961719627196371964719657196671967719687196971970719717197271973719747197571976719777197871979719807198171982719837198471985719867198771988719897199071991719927199371994719957199671997719987199972000720017200272003720047200572006720077200872009720107201172012720137201472015720167201772018720197202072021720227202372024720257202672027720287202972030720317203272033720347203572036720377203872039720407204172042720437204472045720467204772048720497205072051720527205372054720557205672057720587205972060720617206272063720647206572066720677206872069720707207172072720737207472075720767207772078720797208072081720827208372084720857208672087720887208972090720917209272093720947209572096720977209872099721007210172102721037210472105721067210772108721097211072111721127211372114721157211672117721187211972120721217212272123721247212572126721277212872129721307213172132721337213472135721367213772138721397214072141721427214372144721457214672147721487214972150721517215272153721547215572156721577215872159721607216172162721637216472165721667216772168721697217072171721727217372174721757217672177721787217972180721817218272183721847218572186721877218872189721907219172192721937219472195721967219772198721997220072201722027220372204722057220672207722087220972210722117221272213722147221572216722177221872219722207222172222722237222472225722267222772228722297223072231722327223372234722357223672237722387223972240722417224272243722447224572246722477224872249722507225172252722537225472255722567225772258722597226072261722627226372264722657226672267722687226972270722717227272273722747227572276722777227872279722807228172282722837228472285722867228772288722897229072291722927229372294722957229672297722987229972300723017230272303723047230572306723077230872309723107231172312723137231472315723167231772318723197232072321723227232372324723257232672327723287232972330723317233272333723347233572336723377233872339723407234172342723437234472345723467234772348723497235072351723527235372354723557235672357723587235972360723617236272363723647236572366723677236872369723707237172372723737237472375723767237772378723797238072381723827238372384723857238672387723887238972390723917239272393723947239572396723977239872399724007240172402724037240472405724067240772408724097241072411724127241372414724157241672417724187241972420724217242272423724247242572426724277242872429724307243172432724337243472435724367243772438724397244072441724427244372444724457244672447724487244972450724517245272453724547245572456724577245872459724607246172462724637246472465724667246772468724697247072471724727247372474724757247672477724787247972480724817248272483724847248572486724877248872489724907249172492724937249472495724967249772498724997250072501725027250372504725057250672507725087250972510725117251272513725147251572516725177251872519725207252172522725237252472525725267252772528725297253072531725327253372534725357253672537725387253972540725417254272543725447254572546725477254872549725507255172552725537255472555725567255772558725597256072561725627256372564725657256672567725687256972570725717257272573725747257572576725777257872579725807258172582725837258472585725867258772588725897259072591725927259372594725957259672597725987259972600726017260272603726047260572606726077260872609726107261172612726137261472615726167261772618726197262072621726227262372624726257262672627726287262972630726317263272633726347263572636726377263872639726407264172642726437264472645726467264772648726497265072651726527265372654726557265672657726587265972660726617266272663726647266572666726677266872669726707267172672726737267472675726767267772678726797268072681726827268372684726857268672687726887268972690726917269272693726947269572696726977269872699727007270172702727037270472705727067270772708727097271072711727127271372714727157271672717727187271972720727217272272723727247272572726727277272872729727307273172732727337273472735727367273772738727397274072741727427274372744727457274672747727487274972750727517275272753727547275572756727577275872759727607276172762727637276472765727667276772768727697277072771727727277372774727757277672777727787277972780727817278272783727847278572786727877278872789727907279172792727937279472795727967279772798727997280072801728027280372804728057280672807728087280972810728117281272813728147281572816728177281872819728207282172822728237282472825728267282772828728297283072831728327283372834728357283672837728387283972840728417284272843728447284572846728477284872849728507285172852728537285472855728567285772858728597286072861728627286372864728657286672867728687286972870728717287272873728747287572876728777287872879728807288172882728837288472885728867288772888728897289072891728927289372894728957289672897728987289972900729017290272903729047290572906729077290872909729107291172912729137291472915729167291772918729197292072921729227292372924729257292672927729287292972930729317293272933729347293572936729377293872939729407294172942729437294472945729467294772948729497295072951729527295372954729557295672957729587295972960729617296272963729647296572966729677296872969729707297172972729737297472975729767297772978729797298072981729827298372984729857298672987729887298972990729917299272993729947299572996729977299872999730007300173002730037300473005730067300773008730097301073011730127301373014730157301673017730187301973020730217302273023730247302573026730277302873029730307303173032730337303473035730367303773038730397304073041730427304373044730457304673047730487304973050730517305273053730547305573056730577305873059730607306173062730637306473065730667306773068730697307073071730727307373074730757307673077730787307973080730817308273083730847308573086730877308873089730907309173092730937309473095730967309773098730997310073101731027310373104731057310673107731087310973110731117311273113731147311573116731177311873119731207312173122731237312473125731267312773128731297313073131731327313373134731357313673137731387313973140731417314273143731447314573146731477314873149731507315173152731537315473155731567315773158731597316073161731627316373164731657316673167731687316973170731717317273173731747317573176731777317873179731807318173182731837318473185731867318773188731897319073191731927319373194731957319673197731987319973200732017320273203732047320573206732077320873209732107321173212732137321473215732167321773218732197322073221732227322373224732257322673227732287322973230732317323273233732347323573236732377323873239732407324173242732437324473245732467324773248732497325073251732527325373254732557325673257732587325973260732617326273263732647326573266732677326873269732707327173272732737327473275732767327773278732797328073281732827328373284732857328673287732887328973290732917329273293732947329573296732977329873299733007330173302733037330473305733067330773308733097331073311733127331373314733157331673317733187331973320733217332273323733247332573326733277332873329733307333173332733337333473335733367333773338733397334073341733427334373344733457334673347733487334973350733517335273353733547335573356733577335873359733607336173362733637336473365733667336773368733697337073371733727337373374733757337673377733787337973380733817338273383733847338573386733877338873389733907339173392733937339473395733967339773398733997340073401734027340373404734057340673407734087340973410734117341273413734147341573416734177341873419734207342173422734237342473425734267342773428734297343073431734327343373434734357343673437734387343973440734417344273443734447344573446734477344873449734507345173452734537345473455734567345773458734597346073461734627346373464734657346673467734687346973470734717347273473734747347573476734777347873479734807348173482734837348473485734867348773488734897349073491734927349373494734957349673497734987349973500735017350273503735047350573506735077350873509735107351173512735137351473515735167351773518735197352073521735227352373524735257352673527735287352973530735317353273533735347353573536735377353873539735407354173542735437354473545735467354773548735497355073551735527355373554735557355673557735587355973560735617356273563735647356573566735677356873569735707357173572735737357473575735767357773578735797358073581735827358373584735857358673587735887358973590735917359273593735947359573596735977359873599736007360173602736037360473605736067360773608736097361073611736127361373614736157361673617736187361973620736217362273623736247362573626736277362873629736307363173632736337363473635736367363773638736397364073641736427364373644736457364673647736487364973650736517365273653736547365573656736577365873659736607366173662736637366473665736667366773668736697367073671736727367373674736757367673677736787367973680736817368273683736847368573686736877368873689736907369173692736937369473695736967369773698736997370073701737027370373704737057370673707737087370973710737117371273713737147371573716737177371873719737207372173722737237372473725737267372773728737297373073731737327373373734737357373673737737387373973740737417374273743737447374573746737477374873749737507375173752737537375473755737567375773758737597376073761737627376373764737657376673767737687376973770737717377273773737747377573776737777377873779737807378173782737837378473785737867378773788737897379073791737927379373794737957379673797737987379973800738017380273803738047380573806738077380873809738107381173812738137381473815738167381773818738197382073821738227382373824738257382673827738287382973830738317383273833738347383573836738377383873839738407384173842738437384473845738467384773848738497385073851738527385373854738557385673857738587385973860738617386273863738647386573866738677386873869738707387173872738737387473875738767387773878738797388073881738827388373884738857388673887738887388973890738917389273893738947389573896738977389873899739007390173902739037390473905739067390773908739097391073911739127391373914739157391673917739187391973920739217392273923739247392573926739277392873929739307393173932739337393473935739367393773938739397394073941739427394373944739457394673947739487394973950739517395273953739547395573956739577395873959739607396173962739637396473965739667396773968739697397073971739727397373974739757397673977739787397973980739817398273983739847398573986739877398873989739907399173992739937399473995739967399773998739997400074001740027400374004740057400674007740087400974010740117401274013740147401574016740177401874019740207402174022740237402474025740267402774028740297403074031740327403374034740357403674037740387403974040740417404274043740447404574046740477404874049740507405174052740537405474055740567405774058740597406074061740627406374064740657406674067740687406974070740717407274073740747407574076740777407874079740807408174082740837408474085740867408774088740897409074091740927409374094740957409674097740987409974100741017410274103741047410574106741077410874109741107411174112741137411474115741167411774118741197412074121741227412374124741257412674127741287412974130741317413274133741347413574136741377413874139741407414174142741437414474145741467414774148741497415074151741527415374154741557415674157741587415974160741617416274163741647416574166741677416874169741707417174172741737417474175741767417774178741797418074181741827418374184741857418674187741887418974190741917419274193741947419574196741977419874199742007420174202742037420474205742067420774208742097421074211742127421374214742157421674217742187421974220742217422274223742247422574226742277422874229742307423174232742337423474235742367423774238742397424074241742427424374244742457424674247742487424974250742517425274253742547425574256742577425874259742607426174262742637426474265742667426774268742697427074271742727427374274742757427674277742787427974280742817428274283742847428574286742877428874289742907429174292742937429474295742967429774298742997430074301743027430374304743057430674307743087430974310743117431274313743147431574316743177431874319743207432174322743237432474325743267432774328743297433074331743327433374334743357433674337743387433974340743417434274343743447434574346743477434874349743507435174352743537435474355743567435774358743597436074361743627436374364743657436674367743687436974370743717437274373743747437574376743777437874379743807438174382743837438474385743867438774388743897439074391743927439374394743957439674397743987439974400744017440274403744047440574406744077440874409744107441174412744137441474415744167441774418744197442074421744227442374424744257442674427744287442974430744317443274433744347443574436744377443874439744407444174442744437444474445744467444774448744497445074451744527445374454744557445674457744587445974460744617446274463744647446574466744677446874469744707447174472744737447474475744767447774478744797448074481744827448374484744857448674487744887448974490744917449274493744947449574496744977449874499745007450174502745037450474505745067450774508745097451074511745127451374514745157451674517745187451974520745217452274523745247452574526745277452874529745307453174532745337453474535745367453774538745397454074541745427454374544745457454674547745487454974550745517455274553745547455574556745577455874559745607456174562745637456474565745667456774568745697457074571745727457374574745757457674577745787457974580745817458274583745847458574586745877458874589745907459174592745937459474595745967459774598745997460074601746027460374604746057460674607746087460974610746117461274613746147461574616746177461874619746207462174622746237462474625746267462774628746297463074631746327463374634746357463674637746387463974640746417464274643746447464574646746477464874649746507465174652746537465474655746567465774658746597466074661746627466374664746657466674667746687466974670746717467274673746747467574676746777467874679746807468174682746837468474685746867468774688746897469074691746927469374694746957469674697746987469974700747017470274703747047470574706747077470874709747107471174712747137471474715747167471774718747197472074721747227472374724747257472674727747287472974730747317473274733747347473574736747377473874739747407474174742747437474474745747467474774748747497475074751747527475374754747557475674757747587475974760747617476274763747647476574766747677476874769747707477174772747737477474775747767477774778747797478074781747827478374784747857478674787747887478974790747917479274793747947479574796747977479874799748007480174802748037480474805748067480774808748097481074811748127481374814748157481674817748187481974820748217482274823748247482574826748277482874829748307483174832748337483474835748367483774838748397484074841748427484374844748457484674847748487484974850748517485274853748547485574856748577485874859748607486174862748637486474865748667486774868748697487074871748727487374874748757487674877748787487974880748817488274883748847488574886748877488874889748907489174892748937489474895748967489774898748997490074901749027490374904749057490674907749087490974910749117491274913749147491574916749177491874919749207492174922749237492474925749267492774928749297493074931749327493374934749357493674937749387493974940749417494274943749447494574946749477494874949749507495174952749537495474955749567495774958749597496074961749627496374964749657496674967749687496974970749717497274973749747497574976749777497874979749807498174982749837498474985749867498774988749897499074991749927499374994749957499674997749987499975000750017500275003750047500575006750077500875009750107501175012750137501475015750167501775018750197502075021750227502375024750257502675027750287502975030750317503275033750347503575036750377503875039750407504175042750437504475045750467504775048750497505075051750527505375054750557505675057750587505975060750617506275063750647506575066750677506875069750707507175072750737507475075750767507775078750797508075081750827508375084750857508675087750887508975090750917509275093750947509575096750977509875099751007510175102751037510475105751067510775108751097511075111751127511375114751157511675117751187511975120751217512275123751247512575126751277512875129751307513175132751337513475135751367513775138751397514075141751427514375144751457514675147751487514975150751517515275153751547515575156751577515875159751607516175162751637516475165751667516775168751697517075171751727517375174751757517675177751787517975180751817518275183751847518575186751877518875189751907519175192751937519475195751967519775198751997520075201752027520375204752057520675207752087520975210752117521275213752147521575216752177521875219752207522175222752237522475225752267522775228752297523075231752327523375234752357523675237752387523975240752417524275243752447524575246752477524875249752507525175252752537525475255752567525775258752597526075261752627526375264752657526675267752687526975270752717527275273752747527575276752777527875279752807528175282752837528475285752867528775288752897529075291752927529375294752957529675297752987529975300753017530275303753047530575306753077530875309753107531175312753137531475315753167531775318753197532075321753227532375324753257532675327753287532975330753317533275333753347533575336753377533875339753407534175342753437534475345753467534775348753497535075351753527535375354753557535675357753587535975360753617536275363753647536575366753677536875369753707537175372753737537475375753767537775378753797538075381753827538375384753857538675387753887538975390753917539275393753947539575396753977539875399754007540175402754037540475405754067540775408754097541075411754127541375414754157541675417754187541975420754217542275423754247542575426754277542875429754307543175432754337543475435754367543775438754397544075441754427544375444754457544675447754487544975450754517545275453754547545575456754577545875459754607546175462754637546475465754667546775468754697547075471754727547375474754757547675477754787547975480754817548275483754847548575486754877548875489754907549175492754937549475495754967549775498754997550075501755027550375504755057550675507755087550975510755117551275513755147551575516755177551875519755207552175522755237552475525755267552775528755297553075531755327553375534755357553675537755387553975540755417554275543755447554575546755477554875549755507555175552755537555475555755567555775558755597556075561755627556375564755657556675567755687556975570755717557275573755747557575576755777557875579755807558175582755837558475585755867558775588755897559075591755927559375594755957559675597755987559975600756017560275603756047560575606756077560875609756107561175612756137561475615756167561775618756197562075621756227562375624756257562675627756287562975630756317563275633756347563575636756377563875639756407564175642756437564475645756467564775648756497565075651756527565375654756557565675657756587565975660756617566275663756647566575666756677566875669756707567175672756737567475675756767567775678756797568075681756827568375684756857568675687756887568975690756917569275693756947569575696756977569875699757007570175702757037570475705757067570775708757097571075711757127571375714757157571675717757187571975720757217572275723757247572575726757277572875729757307573175732757337573475735757367573775738757397574075741757427574375744757457574675747757487574975750757517575275753757547575575756757577575875759757607576175762757637576475765757667576775768757697577075771757727577375774757757577675777757787577975780757817578275783757847578575786757877578875789757907579175792757937579475795757967579775798757997580075801758027580375804758057580675807758087580975810758117581275813758147581575816758177581875819758207582175822758237582475825758267582775828758297583075831758327583375834758357583675837758387583975840758417584275843758447584575846758477584875849758507585175852758537585475855758567585775858758597586075861758627586375864758657586675867758687586975870758717587275873758747587575876758777587875879758807588175882758837588475885758867588775888758897589075891758927589375894758957589675897758987589975900759017590275903759047590575906759077590875909759107591175912759137591475915759167591775918759197592075921759227592375924759257592675927759287592975930759317593275933759347593575936759377593875939759407594175942759437594475945759467594775948759497595075951759527595375954759557595675957759587595975960759617596275963759647596575966759677596875969759707597175972759737597475975759767597775978759797598075981759827598375984759857598675987759887598975990759917599275993759947599575996759977599875999760007600176002760037600476005760067600776008760097601076011760127601376014760157601676017760187601976020760217602276023760247602576026760277602876029760307603176032760337603476035760367603776038760397604076041760427604376044760457604676047760487604976050760517605276053760547605576056760577605876059760607606176062760637606476065760667606776068760697607076071760727607376074760757607676077760787607976080760817608276083760847608576086760877608876089760907609176092760937609476095760967609776098760997610076101761027610376104761057610676107761087610976110761117611276113761147611576116761177611876119761207612176122761237612476125761267612776128761297613076131761327613376134761357613676137761387613976140761417614276143761447614576146761477614876149761507615176152761537615476155761567615776158761597616076161761627616376164761657616676167761687616976170761717617276173761747617576176761777617876179761807618176182761837618476185761867618776188761897619076191761927619376194761957619676197761987619976200762017620276203762047620576206762077620876209762107621176212762137621476215762167621776218762197622076221762227622376224762257622676227762287622976230762317623276233762347623576236762377623876239762407624176242762437624476245762467624776248762497625076251762527625376254762557625676257762587625976260762617626276263762647626576266762677626876269762707627176272762737627476275762767627776278762797628076281762827628376284762857628676287762887628976290762917629276293762947629576296762977629876299763007630176302763037630476305763067630776308763097631076311763127631376314763157631676317763187631976320763217632276323763247632576326763277632876329763307633176332763337633476335763367633776338763397634076341763427634376344763457634676347763487634976350763517635276353763547635576356763577635876359763607636176362763637636476365763667636776368763697637076371763727637376374763757637676377763787637976380763817638276383763847638576386763877638876389763907639176392763937639476395763967639776398763997640076401764027640376404764057640676407764087640976410764117641276413764147641576416764177641876419764207642176422764237642476425764267642776428764297643076431764327643376434764357643676437764387643976440764417644276443764447644576446764477644876449764507645176452764537645476455764567645776458764597646076461764627646376464764657646676467764687646976470764717647276473764747647576476764777647876479764807648176482764837648476485764867648776488764897649076491764927649376494764957649676497764987649976500765017650276503765047650576506765077650876509765107651176512765137651476515765167651776518765197652076521765227652376524765257652676527765287652976530765317653276533765347653576536765377653876539765407654176542765437654476545765467654776548765497655076551765527655376554765557655676557765587655976560765617656276563765647656576566765677656876569765707657176572765737657476575765767657776578765797658076581765827658376584765857658676587765887658976590765917659276593765947659576596765977659876599766007660176602766037660476605766067660776608766097661076611766127661376614766157661676617766187661976620766217662276623766247662576626766277662876629766307663176632766337663476635766367663776638766397664076641766427664376644766457664676647766487664976650766517665276653766547665576656766577665876659766607666176662766637666476665766667666776668766697667076671766727667376674766757667676677766787667976680766817668276683766847668576686766877668876689766907669176692766937669476695766967669776698766997670076701767027670376704767057670676707767087670976710767117671276713767147671576716767177671876719767207672176722767237672476725767267672776728767297673076731767327673376734767357673676737767387673976740767417674276743767447674576746767477674876749767507675176752767537675476755767567675776758767597676076761767627676376764767657676676767767687676976770767717677276773767747677576776767777677876779767807678176782767837678476785767867678776788767897679076791767927679376794767957679676797767987679976800768017680276803768047680576806768077680876809768107681176812768137681476815768167681776818768197682076821768227682376824768257682676827768287682976830768317683276833768347683576836768377683876839768407684176842768437684476845768467684776848768497685076851768527685376854768557685676857768587685976860768617686276863768647686576866768677686876869768707687176872768737687476875768767687776878768797688076881768827688376884768857688676887768887688976890768917689276893768947689576896768977689876899769007690176902769037690476905769067690776908769097691076911769127691376914769157691676917769187691976920769217692276923769247692576926769277692876929769307693176932769337693476935769367693776938769397694076941769427694376944769457694676947769487694976950769517695276953769547695576956769577695876959769607696176962769637696476965769667696776968769697697076971769727697376974769757697676977769787697976980769817698276983769847698576986769877698876989769907699176992769937699476995769967699776998769997700077001770027700377004770057700677007770087700977010770117701277013770147701577016770177701877019770207702177022770237702477025770267702777028770297703077031770327703377034770357703677037770387703977040770417704277043770447704577046770477704877049770507705177052770537705477055770567705777058770597706077061770627706377064770657706677067770687706977070770717707277073770747707577076770777707877079770807708177082770837708477085770867708777088770897709077091770927709377094770957709677097770987709977100771017710277103771047710577106771077710877109771107711177112771137711477115771167711777118771197712077121771227712377124771257712677127771287712977130771317713277133771347713577136771377713877139771407714177142771437714477145771467714777148771497715077151771527715377154771557715677157771587715977160771617716277163771647716577166771677716877169771707717177172771737717477175771767717777178771797718077181771827718377184771857718677187771887718977190771917719277193771947719577196771977719877199772007720177202772037720477205772067720777208772097721077211772127721377214772157721677217772187721977220772217722277223772247722577226772277722877229772307723177232772337723477235772367723777238772397724077241772427724377244772457724677247772487724977250772517725277253772547725577256772577725877259772607726177262772637726477265772667726777268772697727077271772727727377274772757727677277772787727977280772817728277283772847728577286772877728877289772907729177292772937729477295772967729777298772997730077301773027730377304773057730677307773087730977310773117731277313773147731577316773177731877319773207732177322773237732477325773267732777328773297733077331773327733377334773357733677337773387733977340773417734277343773447734577346773477734877349773507735177352773537735477355773567735777358773597736077361773627736377364773657736677367773687736977370773717737277373773747737577376773777737877379773807738177382773837738477385773867738777388773897739077391773927739377394773957739677397773987739977400774017740277403774047740577406774077740877409774107741177412774137741477415774167741777418774197742077421774227742377424774257742677427774287742977430774317743277433774347743577436774377743877439774407744177442774437744477445774467744777448774497745077451774527745377454774557745677457774587745977460774617746277463774647746577466774677746877469774707747177472774737747477475774767747777478774797748077481774827748377484774857748677487774887748977490774917749277493774947749577496774977749877499775007750177502775037750477505775067750777508775097751077511775127751377514775157751677517775187751977520775217752277523775247752577526775277752877529775307753177532775337753477535775367753777538775397754077541775427754377544775457754677547775487754977550775517755277553775547755577556775577755877559775607756177562775637756477565775667756777568775697757077571775727757377574775757757677577775787757977580775817758277583775847758577586775877758877589775907759177592775937759477595775967759777598775997760077601776027760377604776057760677607776087760977610776117761277613776147761577616776177761877619776207762177622776237762477625776267762777628776297763077631776327763377634776357763677637776387763977640776417764277643776447764577646776477764877649776507765177652776537765477655776567765777658776597766077661776627766377664776657766677667776687766977670776717767277673776747767577676776777767877679776807768177682776837768477685776867768777688776897769077691776927769377694776957769677697776987769977700777017770277703777047770577706777077770877709777107771177712777137771477715777167771777718777197772077721777227772377724777257772677727777287772977730777317773277733777347773577736777377773877739777407774177742777437774477745777467774777748777497775077751777527775377754777557775677757777587775977760777617776277763777647776577766777677776877769777707777177772777737777477775777767777777778777797778077781777827778377784777857778677787777887778977790777917779277793777947779577796777977779877799778007780177802778037780477805778067780777808778097781077811778127781377814778157781677817778187781977820778217782277823778247782577826778277782877829778307783177832778337783477835778367783777838778397784077841778427784377844778457784677847778487784977850778517785277853778547785577856778577785877859778607786177862778637786477865778667786777868778697787077871778727787377874778757787677877778787787977880778817788277883778847788577886778877788877889778907789177892778937789477895778967789777898778997790077901779027790377904779057790677907779087790977910779117791277913779147791577916779177791877919779207792177922779237792477925779267792777928779297793077931779327793377934779357793677937779387793977940779417794277943779447794577946779477794877949779507795177952779537795477955779567795777958779597796077961779627796377964779657796677967779687796977970779717797277973779747797577976779777797877979779807798177982779837798477985779867798777988779897799077991779927799377994779957799677997779987799978000780017800278003780047800578006780077800878009780107801178012780137801478015780167801778018780197802078021780227802378024780257802678027780287802978030780317803278033780347803578036780377803878039780407804178042780437804478045780467804778048780497805078051780527805378054780557805678057780587805978060780617806278063780647806578066780677806878069780707807178072780737807478075780767807778078780797808078081780827808378084780857808678087780887808978090780917809278093780947809578096780977809878099781007810178102781037810478105781067810778108781097811078111781127811378114781157811678117781187811978120781217812278123781247812578126781277812878129781307813178132781337813478135781367813778138781397814078141781427814378144781457814678147781487814978150781517815278153781547815578156781577815878159781607816178162781637816478165781667816778168781697817078171781727817378174781757817678177781787817978180781817818278183781847818578186781877818878189781907819178192781937819478195781967819778198781997820078201782027820378204782057820678207782087820978210782117821278213782147821578216782177821878219782207822178222782237822478225782267822778228782297823078231782327823378234782357823678237782387823978240782417824278243782447824578246782477824878249782507825178252782537825478255782567825778258782597826078261782627826378264782657826678267782687826978270782717827278273782747827578276782777827878279782807828178282782837828478285782867828778288782897829078291782927829378294782957829678297782987829978300783017830278303783047830578306783077830878309783107831178312783137831478315783167831778318783197832078321783227832378324783257832678327783287832978330783317833278333783347833578336783377833878339783407834178342783437834478345783467834778348783497835078351783527835378354783557835678357783587835978360783617836278363783647836578366783677836878369783707837178372783737837478375783767837778378783797838078381783827838378384783857838678387783887838978390783917839278393783947839578396783977839878399784007840178402784037840478405784067840778408784097841078411784127841378414784157841678417784187841978420784217842278423784247842578426784277842878429784307843178432784337843478435784367843778438784397844078441784427844378444784457844678447784487844978450784517845278453784547845578456784577845878459784607846178462784637846478465784667846778468784697847078471784727847378474784757847678477784787847978480784817848278483784847848578486784877848878489784907849178492784937849478495784967849778498784997850078501785027850378504785057850678507785087850978510785117851278513785147851578516785177851878519785207852178522785237852478525785267852778528785297853078531785327853378534785357853678537785387853978540785417854278543785447854578546785477854878549785507855178552785537855478555785567855778558785597856078561785627856378564785657856678567785687856978570785717857278573785747857578576785777857878579785807858178582785837858478585785867858778588785897859078591785927859378594785957859678597785987859978600786017860278603786047860578606786077860878609786107861178612786137861478615786167861778618786197862078621786227862378624786257862678627786287862978630786317863278633786347863578636786377863878639786407864178642786437864478645786467864778648786497865078651786527865378654786557865678657786587865978660786617866278663786647866578666786677866878669786707867178672786737867478675786767867778678786797868078681786827868378684786857868678687786887868978690786917869278693786947869578696786977869878699787007870178702787037870478705787067870778708787097871078711787127871378714787157871678717787187871978720787217872278723787247872578726787277872878729787307873178732787337873478735787367873778738787397874078741787427874378744787457874678747787487874978750787517875278753787547875578756787577875878759787607876178762787637876478765787667876778768787697877078771787727877378774787757877678777787787877978780787817878278783787847878578786787877878878789787907879178792787937879478795787967879778798787997880078801788027880378804788057880678807788087880978810788117881278813788147881578816788177881878819788207882178822788237882478825788267882778828788297883078831788327883378834788357883678837788387883978840788417884278843788447884578846788477884878849788507885178852788537885478855788567885778858788597886078861788627886378864788657886678867788687886978870788717887278873788747887578876788777887878879788807888178882788837888478885788867888778888788897889078891788927889378894788957889678897788987889978900789017890278903789047890578906789077890878909789107891178912789137891478915789167891778918789197892078921789227892378924789257892678927789287892978930789317893278933789347893578936789377893878939789407894178942789437894478945789467894778948789497895078951789527895378954789557895678957789587895978960789617896278963789647896578966789677896878969789707897178972789737897478975789767897778978789797898078981789827898378984789857898678987789887898978990789917899278993789947899578996789977899878999790007900179002790037900479005790067900779008790097901079011790127901379014790157901679017790187901979020790217902279023790247902579026790277902879029790307903179032790337903479035790367903779038790397904079041790427904379044790457904679047790487904979050790517905279053790547905579056790577905879059790607906179062790637906479065790667906779068790697907079071790727907379074790757907679077790787907979080790817908279083790847908579086790877908879089790907909179092790937909479095790967909779098790997910079101791027910379104791057910679107791087910979110791117911279113791147911579116791177911879119791207912179122791237912479125791267912779128791297913079131791327913379134791357913679137791387913979140791417914279143791447914579146791477914879149791507915179152791537915479155791567915779158791597916079161791627916379164791657916679167791687916979170791717917279173791747917579176791777917879179791807918179182791837918479185791867918779188791897919079191791927919379194791957919679197791987919979200792017920279203792047920579206792077920879209792107921179212792137921479215792167921779218792197922079221792227922379224792257922679227792287922979230792317923279233792347923579236792377923879239792407924179242792437924479245792467924779248792497925079251792527925379254792557925679257792587925979260792617926279263792647926579266792677926879269792707927179272792737927479275792767927779278792797928079281792827928379284792857928679287792887928979290792917929279293792947929579296792977929879299793007930179302793037930479305793067930779308793097931079311793127931379314793157931679317793187931979320793217932279323793247932579326793277932879329793307933179332793337933479335793367933779338793397934079341793427934379344793457934679347793487934979350793517935279353793547935579356793577935879359793607936179362793637936479365793667936779368793697937079371793727937379374793757937679377793787937979380793817938279383793847938579386793877938879389793907939179392793937939479395793967939779398793997940079401794027940379404794057940679407794087940979410794117941279413794147941579416794177941879419794207942179422794237942479425794267942779428794297943079431794327943379434794357943679437794387943979440794417944279443794447944579446794477944879449794507945179452794537945479455794567945779458794597946079461794627946379464794657946679467794687946979470794717947279473794747947579476794777947879479794807948179482794837948479485794867948779488794897949079491794927949379494794957949679497794987949979500795017950279503795047950579506795077950879509795107951179512795137951479515795167951779518795197952079521795227952379524795257952679527795287952979530795317953279533795347953579536795377953879539795407954179542795437954479545795467954779548795497955079551795527955379554795557955679557795587955979560795617956279563795647956579566795677956879569795707957179572795737957479575795767957779578795797958079581795827958379584795857958679587795887958979590795917959279593795947959579596795977959879599796007960179602796037960479605796067960779608796097961079611796127961379614796157961679617796187961979620796217962279623796247962579626796277962879629796307963179632796337963479635796367963779638796397964079641796427964379644796457964679647796487964979650796517965279653796547965579656796577965879659796607966179662796637966479665796667966779668796697967079671796727967379674796757967679677796787967979680796817968279683796847968579686796877968879689796907969179692796937969479695796967969779698796997970079701797027970379704797057970679707797087970979710797117971279713797147971579716797177971879719797207972179722797237972479725797267972779728797297973079731797327973379734797357973679737797387973979740797417974279743797447974579746797477974879749797507975179752797537975479755797567975779758797597976079761797627976379764797657976679767797687976979770797717977279773797747977579776797777977879779797807978179782797837978479785797867978779788797897979079791797927979379794797957979679797797987979979800798017980279803798047980579806798077980879809798107981179812798137981479815798167981779818798197982079821798227982379824798257982679827798287982979830798317983279833798347983579836798377983879839798407984179842798437984479845798467984779848798497985079851798527985379854798557985679857798587985979860798617986279863798647986579866798677986879869798707987179872798737987479875798767987779878798797988079881798827988379884798857988679887798887988979890798917989279893798947989579896798977989879899799007990179902799037990479905799067990779908799097991079911799127991379914799157991679917799187991979920799217992279923799247992579926799277992879929799307993179932799337993479935799367993779938799397994079941799427994379944799457994679947799487994979950799517995279953799547995579956799577995879959799607996179962799637996479965799667996779968799697997079971799727997379974799757997679977799787997979980799817998279983799847998579986799877998879989799907999179992799937999479995799967999779998799998000080001800028000380004800058000680007800088000980010800118001280013800148001580016800178001880019800208002180022800238002480025800268002780028800298003080031800328003380034800358003680037800388003980040800418004280043800448004580046800478004880049800508005180052800538005480055800568005780058800598006080061800628006380064800658006680067800688006980070800718007280073800748007580076800778007880079800808008180082800838008480085800868008780088800898009080091800928009380094800958009680097800988009980100801018010280103801048010580106801078010880109801108011180112801138011480115801168011780118801198012080121801228012380124801258012680127801288012980130801318013280133801348013580136801378013880139801408014180142801438014480145801468014780148801498015080151801528015380154801558015680157801588015980160801618016280163801648016580166801678016880169801708017180172801738017480175801768017780178801798018080181801828018380184801858018680187801888018980190801918019280193801948019580196801978019880199802008020180202802038020480205802068020780208802098021080211802128021380214802158021680217802188021980220802218022280223802248022580226802278022880229802308023180232802338023480235802368023780238802398024080241802428024380244802458024680247802488024980250802518025280253802548025580256802578025880259802608026180262802638026480265802668026780268802698027080271802728027380274802758027680277802788027980280802818028280283802848028580286802878028880289802908029180292802938029480295802968029780298802998030080301803028030380304803058030680307803088030980310803118031280313803148031580316803178031880319803208032180322803238032480325803268032780328803298033080331803328033380334803358033680337803388033980340803418034280343803448034580346803478034880349803508035180352803538035480355803568035780358803598036080361803628036380364803658036680367803688036980370803718037280373803748037580376803778037880379803808038180382803838038480385803868038780388803898039080391803928039380394803958039680397803988039980400804018040280403804048040580406804078040880409804108041180412804138041480415804168041780418804198042080421804228042380424804258042680427804288042980430804318043280433804348043580436804378043880439804408044180442804438044480445804468044780448804498045080451804528045380454804558045680457804588045980460804618046280463804648046580466804678046880469804708047180472804738047480475804768047780478804798048080481804828048380484804858048680487804888048980490804918049280493804948049580496804978049880499805008050180502805038050480505805068050780508805098051080511805128051380514805158051680517805188051980520805218052280523805248052580526805278052880529805308053180532805338053480535805368053780538805398054080541805428054380544805458054680547805488054980550805518055280553805548055580556805578055880559805608056180562805638056480565805668056780568805698057080571805728057380574805758057680577805788057980580805818058280583805848058580586805878058880589805908059180592805938059480595805968059780598805998060080601806028060380604806058060680607806088060980610806118061280613806148061580616806178061880619806208062180622806238062480625806268062780628806298063080631806328063380634806358063680637806388063980640806418064280643806448064580646806478064880649806508065180652806538065480655806568065780658806598066080661806628066380664806658066680667806688066980670806718067280673806748067580676806778067880679806808068180682806838068480685806868068780688806898069080691806928069380694806958069680697806988069980700807018070280703807048070580706807078070880709807108071180712807138071480715807168071780718807198072080721807228072380724807258072680727807288072980730807318073280733807348073580736807378073880739807408074180742807438074480745807468074780748807498075080751807528075380754807558075680757807588075980760807618076280763807648076580766807678076880769807708077180772807738077480775807768077780778807798078080781807828078380784807858078680787807888078980790807918079280793807948079580796807978079880799808008080180802808038080480805808068080780808808098081080811808128081380814808158081680817808188081980820808218082280823808248082580826808278082880829808308083180832808338083480835808368083780838808398084080841808428084380844808458084680847808488084980850808518085280853808548085580856808578085880859808608086180862808638086480865808668086780868808698087080871808728087380874808758087680877808788087980880808818088280883808848088580886808878088880889808908089180892808938089480895808968089780898808998090080901809028090380904809058090680907809088090980910809118091280913809148091580916809178091880919809208092180922809238092480925809268092780928809298093080931809328093380934809358093680937809388093980940809418094280943809448094580946809478094880949809508095180952809538095480955809568095780958809598096080961809628096380964809658096680967809688096980970809718097280973809748097580976809778097880979809808098180982809838098480985809868098780988809898099080991809928099380994809958099680997809988099981000810018100281003810048100581006810078100881009810108101181012810138101481015810168101781018810198102081021810228102381024810258102681027810288102981030810318103281033810348103581036810378103881039810408104181042810438104481045810468104781048810498105081051810528105381054810558105681057810588105981060810618106281063810648106581066810678106881069810708107181072810738107481075810768107781078810798108081081810828108381084810858108681087810888108981090810918109281093810948109581096810978109881099811008110181102811038110481105811068110781108811098111081111811128111381114811158111681117811188111981120811218112281123811248112581126811278112881129811308113181132811338113481135811368113781138811398114081141811428114381144811458114681147811488114981150811518115281153811548115581156811578115881159811608116181162811638116481165811668116781168811698117081171811728117381174811758117681177811788117981180811818118281183811848118581186811878118881189811908119181192811938119481195811968119781198811998120081201812028120381204812058120681207812088120981210812118121281213812148121581216812178121881219812208122181222812238122481225812268122781228812298123081231812328123381234812358123681237812388123981240812418124281243812448124581246812478124881249812508125181252812538125481255812568125781258812598126081261812628126381264812658126681267812688126981270812718127281273812748127581276812778127881279812808128181282812838128481285812868128781288812898129081291812928129381294812958129681297812988129981300813018130281303813048130581306813078130881309813108131181312813138131481315813168131781318813198132081321813228132381324813258132681327813288132981330813318133281333813348133581336813378133881339813408134181342813438134481345813468134781348813498135081351813528135381354813558135681357813588135981360813618136281363813648136581366813678136881369813708137181372813738137481375813768137781378813798138081381813828138381384813858138681387813888138981390813918139281393813948139581396813978139881399814008140181402814038140481405814068140781408814098141081411814128141381414814158141681417814188141981420814218142281423814248142581426814278142881429814308143181432814338143481435814368143781438814398144081441814428144381444814458144681447814488144981450814518145281453814548145581456814578145881459814608146181462814638146481465814668146781468814698147081471814728147381474814758147681477814788147981480814818148281483814848148581486814878148881489814908149181492814938149481495814968149781498814998150081501815028150381504815058150681507815088150981510815118151281513815148151581516815178151881519815208152181522815238152481525815268152781528815298153081531815328153381534815358153681537815388153981540815418154281543815448154581546815478154881549815508155181552815538155481555815568155781558815598156081561815628156381564815658156681567815688156981570815718157281573815748157581576815778157881579815808158181582815838158481585815868158781588815898159081591815928159381594815958159681597815988159981600816018160281603816048160581606816078160881609816108161181612816138161481615816168161781618816198162081621816228162381624816258162681627816288162981630816318163281633816348163581636816378163881639816408164181642816438164481645816468164781648816498165081651816528165381654816558165681657816588165981660816618166281663816648166581666816678166881669816708167181672816738167481675816768167781678816798168081681816828168381684816858168681687816888168981690816918169281693816948169581696816978169881699817008170181702817038170481705817068170781708817098171081711817128171381714817158171681717817188171981720817218172281723817248172581726817278172881729817308173181732817338173481735817368173781738817398174081741817428174381744817458174681747817488174981750817518175281753817548175581756817578175881759817608176181762817638176481765817668176781768817698177081771817728177381774817758177681777817788177981780817818178281783817848178581786817878178881789817908179181792817938179481795817968179781798817998180081801818028180381804818058180681807818088180981810818118181281813818148181581816818178181881819818208182181822818238182481825818268182781828818298183081831818328183381834818358183681837818388183981840818418184281843818448184581846818478184881849818508185181852818538185481855818568185781858818598186081861818628186381864818658186681867818688186981870818718187281873818748187581876818778187881879818808188181882818838188481885818868188781888818898189081891818928189381894818958189681897818988189981900819018190281903819048190581906819078190881909819108191181912819138191481915819168191781918819198192081921819228192381924819258192681927819288192981930819318193281933819348193581936819378193881939819408194181942819438194481945819468194781948819498195081951819528195381954819558195681957819588195981960819618196281963819648196581966819678196881969819708197181972819738197481975819768197781978819798198081981819828198381984819858198681987819888198981990819918199281993819948199581996819978199881999820008200182002820038200482005820068200782008820098201082011820128201382014820158201682017820188201982020820218202282023820248202582026820278202882029820308203182032820338203482035820368203782038820398204082041820428204382044820458204682047820488204982050820518205282053820548205582056820578205882059820608206182062820638206482065820668206782068820698207082071820728207382074820758207682077820788207982080820818208282083820848208582086820878208882089820908209182092820938209482095820968209782098820998210082101821028210382104821058210682107821088210982110821118211282113821148211582116821178211882119821208212182122821238212482125821268212782128821298213082131821328213382134821358213682137821388213982140821418214282143821448214582146821478214882149821508215182152821538215482155821568215782158821598216082161821628216382164821658216682167821688216982170821718217282173821748217582176821778217882179821808218182182821838218482185821868218782188821898219082191821928219382194821958219682197821988219982200822018220282203822048220582206822078220882209822108221182212822138221482215822168221782218822198222082221822228222382224822258222682227822288222982230822318223282233822348223582236822378223882239822408224182242822438224482245822468224782248822498225082251822528225382254822558225682257822588225982260822618226282263822648226582266822678226882269822708227182272822738227482275822768227782278822798228082281822828228382284822858228682287822888228982290822918229282293822948229582296822978229882299823008230182302823038230482305823068230782308823098231082311823128231382314823158231682317823188231982320823218232282323823248232582326823278232882329823308233182332823338233482335823368233782338823398234082341823428234382344823458234682347823488234982350823518235282353823548235582356823578235882359823608236182362823638236482365823668236782368823698237082371823728237382374823758237682377823788237982380823818238282383823848238582386823878238882389823908239182392823938239482395823968239782398823998240082401824028240382404824058240682407824088240982410824118241282413824148241582416824178241882419824208242182422824238242482425824268242782428824298243082431824328243382434824358243682437824388243982440824418244282443824448244582446824478244882449824508245182452824538245482455824568245782458824598246082461824628246382464824658246682467824688246982470824718247282473824748247582476824778247882479824808248182482824838248482485824868248782488824898249082491824928249382494824958249682497824988249982500825018250282503825048250582506825078250882509825108251182512825138251482515825168251782518825198252082521825228252382524825258252682527825288252982530825318253282533825348253582536825378253882539825408254182542825438254482545825468254782548825498255082551825528255382554825558255682557825588255982560825618256282563825648256582566825678256882569825708257182572825738257482575825768257782578825798258082581825828258382584825858258682587825888258982590825918259282593825948259582596825978259882599826008260182602826038260482605826068260782608826098261082611826128261382614826158261682617826188261982620826218262282623826248262582626826278262882629826308263182632826338263482635826368263782638826398264082641826428264382644826458264682647826488264982650826518265282653826548265582656826578265882659826608266182662826638266482665826668266782668826698267082671826728267382674826758267682677826788267982680826818268282683826848268582686826878268882689826908269182692826938269482695826968269782698826998270082701827028270382704827058270682707827088270982710827118271282713827148271582716827178271882719827208272182722827238272482725827268272782728827298273082731827328273382734827358273682737827388273982740827418274282743827448274582746827478274882749827508275182752827538275482755827568275782758827598276082761827628276382764827658276682767827688276982770827718277282773827748277582776827778277882779827808278182782827838278482785827868278782788827898279082791827928279382794827958279682797827988279982800828018280282803828048280582806828078280882809828108281182812828138281482815828168281782818828198282082821828228282382824828258282682827828288282982830828318283282833828348283582836828378283882839828408284182842828438284482845828468284782848828498285082851828528285382854828558285682857828588285982860828618286282863828648286582866828678286882869828708287182872828738287482875828768287782878828798288082881828828288382884828858288682887828888288982890828918289282893828948289582896828978289882899829008290182902829038290482905829068290782908829098291082911829128291382914829158291682917829188291982920829218292282923829248292582926829278292882929829308293182932829338293482935829368293782938829398294082941829428294382944829458294682947829488294982950829518295282953829548295582956829578295882959829608296182962829638296482965829668296782968829698297082971829728297382974829758297682977829788297982980829818298282983829848298582986829878298882989829908299182992829938299482995829968299782998829998300083001830028300383004830058300683007830088300983010830118301283013830148301583016830178301883019830208302183022830238302483025830268302783028830298303083031830328303383034830358303683037830388303983040830418304283043830448304583046830478304883049830508305183052830538305483055830568305783058830598306083061830628306383064830658306683067830688306983070830718307283073830748307583076830778307883079830808308183082830838308483085830868308783088830898309083091830928309383094830958309683097830988309983100831018310283103831048310583106831078310883109831108311183112831138311483115831168311783118831198312083121831228312383124831258312683127831288312983130831318313283133831348313583136831378313883139831408314183142831438314483145831468314783148831498315083151831528315383154831558315683157831588315983160831618316283163831648316583166831678316883169831708317183172831738317483175831768317783178831798318083181831828318383184831858318683187831888318983190831918319283193831948319583196831978319883199832008320183202832038320483205832068320783208832098321083211832128321383214832158321683217832188321983220832218322283223832248322583226832278322883229832308323183232832338323483235832368323783238832398324083241832428324383244832458324683247832488324983250832518325283253832548325583256832578325883259832608326183262832638326483265832668326783268832698327083271832728327383274832758327683277832788327983280832818328283283832848328583286832878328883289832908329183292832938329483295832968329783298832998330083301833028330383304833058330683307833088330983310833118331283313833148331583316833178331883319833208332183322833238332483325833268332783328833298333083331833328333383334833358333683337833388333983340833418334283343833448334583346833478334883349833508335183352833538335483355833568335783358833598336083361833628336383364833658336683367833688336983370833718337283373833748337583376833778337883379833808338183382833838338483385833868338783388833898339083391833928339383394833958339683397833988339983400834018340283403834048340583406834078340883409834108341183412834138341483415834168341783418834198342083421834228342383424834258342683427834288342983430834318343283433834348343583436834378343883439834408344183442834438344483445834468344783448834498345083451834528345383454834558345683457834588345983460834618346283463834648346583466834678346883469834708347183472834738347483475834768347783478834798348083481834828348383484834858348683487834888348983490834918349283493834948349583496834978349883499835008350183502835038350483505835068350783508835098351083511835128351383514835158351683517835188351983520835218352283523835248352583526835278352883529835308353183532835338353483535835368353783538835398354083541835428354383544835458354683547835488354983550835518355283553835548355583556835578355883559835608356183562835638356483565835668356783568835698357083571835728357383574835758357683577835788357983580835818358283583835848358583586835878358883589835908359183592835938359483595835968359783598835998360083601836028360383604836058360683607836088360983610836118361283613836148361583616836178361883619836208362183622836238362483625836268362783628836298363083631836328363383634836358363683637836388363983640836418364283643836448364583646836478364883649836508365183652836538365483655836568365783658836598366083661836628366383664836658366683667836688366983670836718367283673836748367583676836778367883679836808368183682836838368483685836868368783688836898369083691836928369383694836958369683697836988369983700837018370283703837048370583706837078370883709837108371183712837138371483715837168371783718837198372083721837228372383724837258372683727837288372983730837318373283733837348373583736837378373883739837408374183742837438374483745837468374783748837498375083751837528375383754837558375683757837588375983760837618376283763837648376583766837678376883769837708377183772837738377483775837768377783778837798378083781837828378383784837858378683787837888378983790837918379283793837948379583796837978379883799838008380183802838038380483805838068380783808838098381083811838128381383814838158381683817838188381983820838218382283823838248382583826838278382883829838308383183832838338383483835838368383783838838398384083841838428384383844838458384683847838488384983850838518385283853838548385583856838578385883859838608386183862838638386483865838668386783868838698387083871838728387383874838758387683877838788387983880838818388283883838848388583886838878388883889838908389183892838938389483895838968389783898838998390083901839028390383904839058390683907839088390983910839118391283913839148391583916839178391883919839208392183922839238392483925839268392783928839298393083931839328393383934839358393683937839388393983940839418394283943839448394583946839478394883949839508395183952839538395483955839568395783958839598396083961839628396383964839658396683967839688396983970839718397283973839748397583976839778397883979839808398183982839838398483985839868398783988839898399083991839928399383994839958399683997839988399984000840018400284003840048400584006840078400884009840108401184012840138401484015840168401784018840198402084021840228402384024840258402684027840288402984030840318403284033840348403584036840378403884039840408404184042840438404484045840468404784048840498405084051840528405384054840558405684057840588405984060840618406284063840648406584066840678406884069840708407184072840738407484075840768407784078840798408084081840828408384084840858408684087840888408984090840918409284093840948409584096840978409884099841008410184102841038410484105841068410784108841098411084111841128411384114841158411684117841188411984120841218412284123841248412584126841278412884129841308413184132841338413484135841368413784138841398414084141841428414384144841458414684147841488414984150841518415284153841548415584156841578415884159841608416184162841638416484165841668416784168841698417084171841728417384174841758417684177841788417984180841818418284183841848418584186841878418884189841908419184192841938419484195841968419784198841998420084201842028420384204842058420684207842088420984210842118421284213842148421584216842178421884219842208422184222842238422484225842268422784228842298423084231842328423384234842358423684237842388423984240842418424284243842448424584246842478424884249842508425184252842538425484255842568425784258842598426084261842628426384264842658426684267842688426984270842718427284273842748427584276842778427884279842808428184282842838428484285842868428784288842898429084291842928429384294842958429684297842988429984300843018430284303843048430584306843078430884309843108431184312843138431484315843168431784318843198432084321843228432384324843258432684327843288432984330843318433284333843348433584336843378433884339843408434184342843438434484345843468434784348843498435084351843528435384354843558435684357843588435984360843618436284363843648436584366843678436884369843708437184372843738437484375843768437784378843798438084381843828438384384843858438684387843888438984390843918439284393843948439584396843978439884399844008440184402844038440484405844068440784408844098441084411844128441384414844158441684417844188441984420844218442284423844248442584426844278442884429844308443184432844338443484435844368443784438844398444084441844428444384444844458444684447844488444984450844518445284453844548445584456844578445884459844608446184462844638446484465844668446784468844698447084471844728447384474844758447684477844788447984480844818448284483844848448584486844878448884489844908449184492844938449484495844968449784498844998450084501845028450384504845058450684507845088450984510845118451284513845148451584516845178451884519845208452184522845238452484525845268452784528845298453084531845328453384534845358453684537845388453984540845418454284543845448454584546845478454884549845508455184552845538455484555845568455784558845598456084561845628456384564845658456684567845688456984570845718457284573845748457584576845778457884579845808458184582845838458484585845868458784588845898459084591845928459384594845958459684597845988459984600846018460284603846048460584606846078460884609846108461184612846138461484615846168461784618846198462084621846228462384624846258462684627846288462984630846318463284633846348463584636846378463884639846408464184642846438464484645846468464784648846498465084651846528465384654846558465684657846588465984660846618466284663846648466584666846678466884669846708467184672846738467484675846768467784678846798468084681846828468384684846858468684687846888468984690846918469284693846948469584696846978469884699847008470184702847038470484705847068470784708847098471084711847128471384714847158471684717847188471984720847218472284723847248472584726847278472884729847308473184732847338473484735847368473784738847398474084741847428474384744847458474684747847488474984750847518475284753847548475584756847578475884759847608476184762847638476484765847668476784768847698477084771847728477384774847758477684777847788477984780847818478284783847848478584786847878478884789847908479184792847938479484795847968479784798847998480084801848028480384804848058480684807848088480984810848118481284813848148481584816848178481884819848208482184822848238482484825848268482784828848298483084831848328483384834848358483684837848388483984840848418484284843848448484584846848478484884849848508485184852848538485484855848568485784858848598486084861848628486384864848658486684867848688486984870848718487284873848748487584876848778487884879848808488184882848838488484885848868488784888848898489084891848928489384894848958489684897848988489984900849018490284903849048490584906849078490884909849108491184912849138491484915849168491784918849198492084921849228492384924849258492684927849288492984930849318493284933849348493584936849378493884939849408494184942849438494484945849468494784948849498495084951849528495384954849558495684957849588495984960849618496284963849648496584966849678496884969849708497184972849738497484975849768497784978849798498084981849828498384984849858498684987849888498984990849918499284993849948499584996849978499884999850008500185002850038500485005850068500785008850098501085011850128501385014850158501685017850188501985020850218502285023850248502585026850278502885029850308503185032850338503485035850368503785038850398504085041850428504385044850458504685047850488504985050850518505285053850548505585056850578505885059850608506185062850638506485065850668506785068850698507085071850728507385074850758507685077850788507985080850818508285083850848508585086850878508885089850908509185092850938509485095850968509785098850998510085101851028510385104851058510685107851088510985110851118511285113851148511585116851178511885119851208512185122851238512485125851268512785128851298513085131851328513385134851358513685137851388513985140851418514285143851448514585146851478514885149851508515185152851538515485155851568515785158851598516085161851628516385164851658516685167851688516985170851718517285173851748517585176851778517885179851808518185182851838518485185851868518785188851898519085191851928519385194851958519685197851988519985200852018520285203852048520585206852078520885209852108521185212852138521485215852168521785218852198522085221852228522385224852258522685227852288522985230852318523285233852348523585236852378523885239852408524185242852438524485245852468524785248852498525085251852528525385254852558525685257852588525985260852618526285263852648526585266852678526885269852708527185272852738527485275852768527785278852798528085281852828528385284852858528685287852888528985290852918529285293852948529585296852978529885299853008530185302853038530485305853068530785308853098531085311853128531385314853158531685317853188531985320853218532285323853248532585326853278532885329853308533185332853338533485335853368533785338853398534085341853428534385344853458534685347853488534985350853518535285353853548535585356853578535885359853608536185362853638536485365853668536785368853698537085371853728537385374853758537685377853788537985380853818538285383853848538585386853878538885389853908539185392853938539485395853968539785398853998540085401854028540385404854058540685407854088540985410854118541285413854148541585416854178541885419854208542185422854238542485425854268542785428854298543085431854328543385434854358543685437854388543985440854418544285443854448544585446854478544885449854508545185452854538545485455854568545785458854598546085461854628546385464854658546685467854688546985470854718547285473854748547585476854778547885479854808548185482854838548485485854868548785488854898549085491854928549385494854958549685497854988549985500855018550285503855048550585506855078550885509855108551185512855138551485515855168551785518855198552085521855228552385524855258552685527855288552985530855318553285533855348553585536855378553885539855408554185542855438554485545855468554785548855498555085551855528555385554855558555685557855588555985560855618556285563855648556585566855678556885569855708557185572855738557485575855768557785578855798558085581855828558385584855858558685587855888558985590855918559285593855948559585596855978559885599856008560185602856038560485605856068560785608856098561085611856128561385614856158561685617856188561985620856218562285623856248562585626856278562885629856308563185632856338563485635856368563785638856398564085641856428564385644856458564685647856488564985650856518565285653856548565585656856578565885659856608566185662856638566485665856668566785668856698567085671856728567385674856758567685677856788567985680856818568285683856848568585686856878568885689856908569185692856938569485695856968569785698856998570085701857028570385704857058570685707857088570985710857118571285713857148571585716857178571885719857208572185722857238572485725857268572785728857298573085731857328573385734857358573685737857388573985740857418574285743857448574585746857478574885749857508575185752857538575485755857568575785758857598576085761857628576385764857658576685767857688576985770857718577285773857748577585776857778577885779857808578185782857838578485785857868578785788857898579085791857928579385794857958579685797857988579985800858018580285803858048580585806858078580885809858108581185812858138581485815858168581785818858198582085821858228582385824858258582685827858288582985830858318583285833858348583585836858378583885839858408584185842858438584485845858468584785848858498585085851858528585385854858558585685857858588585985860858618586285863858648586585866858678586885869858708587185872858738587485875858768587785878858798588085881858828588385884858858588685887858888588985890858918589285893858948589585896858978589885899859008590185902859038590485905859068590785908859098591085911859128591385914859158591685917859188591985920859218592285923859248592585926859278592885929859308593185932859338593485935859368593785938859398594085941859428594385944859458594685947859488594985950859518595285953859548595585956859578595885959859608596185962859638596485965859668596785968859698597085971859728597385974859758597685977859788597985980859818598285983859848598585986859878598885989859908599185992859938599485995859968599785998859998600086001860028600386004860058600686007860088600986010860118601286013860148601586016860178601886019860208602186022860238602486025860268602786028860298603086031860328603386034860358603686037860388603986040860418604286043860448604586046860478604886049860508605186052860538605486055860568605786058860598606086061860628606386064860658606686067860688606986070860718607286073860748607586076860778607886079860808608186082860838608486085860868608786088860898609086091860928609386094860958609686097860988609986100861018610286103861048610586106861078610886109861108611186112861138611486115861168611786118861198612086121861228612386124861258612686127861288612986130861318613286133861348613586136861378613886139861408614186142861438614486145861468614786148861498615086151861528615386154861558615686157861588615986160861618616286163861648616586166861678616886169861708617186172861738617486175861768617786178861798618086181861828618386184861858618686187861888618986190861918619286193861948619586196861978619886199862008620186202862038620486205862068620786208862098621086211862128621386214862158621686217862188621986220862218622286223862248622586226862278622886229862308623186232862338623486235862368623786238862398624086241862428624386244862458624686247862488624986250862518625286253862548625586256862578625886259862608626186262862638626486265862668626786268862698627086271862728627386274862758627686277862788627986280862818628286283862848628586286862878628886289862908629186292862938629486295862968629786298862998630086301863028630386304863058630686307863088630986310863118631286313863148631586316863178631886319863208632186322863238632486325863268632786328863298633086331863328633386334863358633686337863388633986340863418634286343863448634586346863478634886349863508635186352863538635486355863568635786358863598636086361863628636386364863658636686367863688636986370863718637286373863748637586376863778637886379863808638186382863838638486385863868638786388863898639086391863928639386394863958639686397863988639986400864018640286403864048640586406864078640886409864108641186412864138641486415864168641786418864198642086421864228642386424864258642686427864288642986430864318643286433864348643586436864378643886439864408644186442864438644486445864468644786448864498645086451864528645386454864558645686457864588645986460864618646286463864648646586466864678646886469864708647186472864738647486475864768647786478864798648086481864828648386484864858648686487864888648986490864918649286493864948649586496864978649886499865008650186502865038650486505865068650786508865098651086511865128651386514865158651686517865188651986520865218652286523865248652586526865278652886529865308653186532865338653486535865368653786538865398654086541865428654386544865458654686547865488654986550865518655286553865548655586556865578655886559865608656186562865638656486565865668656786568865698657086571865728657386574865758657686577865788657986580865818658286583865848658586586865878658886589865908659186592865938659486595865968659786598865998660086601866028660386604866058660686607866088660986610866118661286613866148661586616866178661886619866208662186622866238662486625866268662786628866298663086631866328663386634866358663686637866388663986640866418664286643866448664586646866478664886649866508665186652866538665486655866568665786658866598666086661866628666386664866658666686667866688666986670866718667286673866748667586676866778667886679866808668186682866838668486685866868668786688866898669086691866928669386694866958669686697866988669986700867018670286703867048670586706867078670886709867108671186712867138671486715867168671786718867198672086721867228672386724867258672686727867288672986730867318673286733867348673586736867378673886739867408674186742867438674486745867468674786748867498675086751867528675386754867558675686757867588675986760867618676286763867648676586766867678676886769867708677186772867738677486775867768677786778867798678086781867828678386784867858678686787867888678986790867918679286793867948679586796867978679886799868008680186802868038680486805868068680786808868098681086811868128681386814868158681686817868188681986820868218682286823868248682586826868278682886829868308683186832868338683486835868368683786838868398684086841868428684386844868458684686847868488684986850868518685286853868548685586856868578685886859868608686186862868638686486865868668686786868868698687086871868728687386874868758687686877868788687986880868818688286883868848688586886868878688886889868908689186892868938689486895868968689786898868998690086901869028690386904869058690686907869088690986910869118691286913869148691586916869178691886919869208692186922869238692486925869268692786928869298693086931869328693386934869358693686937869388693986940869418694286943869448694586946869478694886949869508695186952869538695486955869568695786958869598696086961869628696386964869658696686967869688696986970869718697286973869748697586976869778697886979869808698186982869838698486985869868698786988869898699086991869928699386994869958699686997869988699987000870018700287003870048700587006870078700887009870108701187012870138701487015870168701787018870198702087021870228702387024870258702687027870288702987030870318703287033870348703587036870378703887039870408704187042870438704487045870468704787048870498705087051870528705387054870558705687057870588705987060870618706287063870648706587066870678706887069870708707187072870738707487075870768707787078870798708087081870828708387084870858708687087870888708987090870918709287093870948709587096870978709887099871008710187102871038710487105871068710787108871098711087111871128711387114871158711687117871188711987120871218712287123871248712587126871278712887129871308713187132871338713487135871368713787138871398714087141871428714387144871458714687147871488714987150871518715287153871548715587156871578715887159871608716187162871638716487165871668716787168871698717087171871728717387174871758717687177871788717987180871818718287183871848718587186871878718887189871908719187192871938719487195871968719787198871998720087201872028720387204872058720687207872088720987210872118721287213872148721587216872178721887219872208722187222872238722487225872268722787228872298723087231872328723387234872358723687237872388723987240872418724287243872448724587246872478724887249872508725187252872538725487255872568725787258872598726087261872628726387264872658726687267872688726987270872718727287273872748727587276872778727887279872808728187282872838728487285872868728787288872898729087291872928729387294872958729687297872988729987300873018730287303873048730587306873078730887309873108731187312873138731487315873168731787318873198732087321873228732387324873258732687327873288732987330873318733287333873348733587336873378733887339873408734187342873438734487345873468734787348873498735087351873528735387354873558735687357873588735987360873618736287363873648736587366873678736887369873708737187372873738737487375873768737787378873798738087381873828738387384873858738687387873888738987390873918739287393873948739587396873978739887399874008740187402874038740487405874068740787408874098741087411874128741387414874158741687417874188741987420874218742287423874248742587426874278742887429874308743187432874338743487435874368743787438874398744087441874428744387444874458744687447874488744987450874518745287453874548745587456874578745887459874608746187462874638746487465874668746787468874698747087471874728747387474874758747687477874788747987480874818748287483874848748587486874878748887489874908749187492874938749487495874968749787498874998750087501875028750387504875058750687507875088750987510875118751287513875148751587516875178751887519875208752187522875238752487525875268752787528875298753087531875328753387534875358753687537875388753987540875418754287543875448754587546875478754887549875508755187552875538755487555875568755787558875598756087561875628756387564875658756687567875688756987570875718757287573875748757587576875778757887579875808758187582875838758487585875868758787588875898759087591875928759387594875958759687597875988759987600876018760287603876048760587606876078760887609876108761187612876138761487615876168761787618876198762087621876228762387624876258762687627876288762987630876318763287633876348763587636876378763887639876408764187642876438764487645876468764787648876498765087651876528765387654876558765687657876588765987660876618766287663876648766587666876678766887669876708767187672876738767487675876768767787678876798768087681876828768387684876858768687687876888768987690876918769287693876948769587696876978769887699877008770187702877038770487705877068770787708877098771087711877128771387714877158771687717877188771987720877218772287723877248772587726877278772887729877308773187732877338773487735877368773787738877398774087741877428774387744877458774687747877488774987750877518775287753877548775587756877578775887759877608776187762877638776487765877668776787768877698777087771877728777387774877758777687777877788777987780877818778287783877848778587786877878778887789877908779187792877938779487795877968779787798877998780087801878028780387804878058780687807878088780987810878118781287813878148781587816878178781887819878208782187822878238782487825878268782787828878298783087831878328783387834878358783687837878388783987840
  1. /*
  2. This file is part of Sencha Touch 2.1
  3. Copyright (c) 2011-2012 Sencha Inc
  4. Contact: http://www.sencha.com/contact
  5. Commercial Usage
  6. Licensees holding valid commercial licenses may use this file in accordance with the Commercial
  7. Software License Agreement provided with the Software or, alternatively, in accordance with the
  8. terms contained in a written agreement between you and Sencha.
  9. If you are unsure which license is appropriate for your use, please contact the sales department
  10. at http://www.sencha.com/contact.
  11. Build date: 2012-11-05 22:31:29 (08c91901ae8449841ff23e5d3fb404d6128d3b0b)
  12. */
  13. //@tag foundation,core
  14. //@define Ext
  15. /**
  16. * @class Ext
  17. * @singleton
  18. */
  19. (function() {
  20. var global = this,
  21. objectPrototype = Object.prototype,
  22. toString = objectPrototype.toString,
  23. enumerables = true,
  24. enumerablesTest = { toString: 1 },
  25. emptyFn = function(){},
  26. i;
  27. if (typeof Ext === 'undefined') {
  28. global.Ext = {};
  29. }
  30. Ext.global = global;
  31. for (i in enumerablesTest) {
  32. enumerables = null;
  33. }
  34. if (enumerables) {
  35. enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable',
  36. 'toLocaleString', 'toString', 'constructor'];
  37. }
  38. /**
  39. * An array containing extra enumerables for old browsers.
  40. * @property {String[]}
  41. */
  42. Ext.enumerables = enumerables;
  43. /**
  44. * Copies all the properties of config to the specified object.
  45. * Note that if recursive merging and cloning without referencing the original objects / arrays is needed, use
  46. * {@link Ext.Object#merge} instead.
  47. * @param {Object} object The receiver of the properties.
  48. * @param {Object} config The source of the properties.
  49. * @param {Object} [defaults] A different object that will also be applied for default values.
  50. * @return {Object} returns obj
  51. */
  52. Ext.apply = function(object, config, defaults) {
  53. if (defaults) {
  54. Ext.apply(object, defaults);
  55. }
  56. if (object && config && typeof config === 'object') {
  57. var i, j, k;
  58. for (i in config) {
  59. object[i] = config[i];
  60. }
  61. if (enumerables) {
  62. for (j = enumerables.length; j--;) {
  63. k = enumerables[j];
  64. if (config.hasOwnProperty(k)) {
  65. object[k] = config[k];
  66. }
  67. }
  68. }
  69. }
  70. return object;
  71. };
  72. Ext.buildSettings = Ext.apply({
  73. baseCSSPrefix: 'x-',
  74. scopeResetCSS: false
  75. }, Ext.buildSettings || {});
  76. Ext.apply(Ext, {
  77. /**
  78. * @property {Function}
  79. * A reusable empty function
  80. */
  81. emptyFn: emptyFn,
  82. baseCSSPrefix: Ext.buildSettings.baseCSSPrefix,
  83. /**
  84. * Copies all the properties of config to object if they don't already exist.
  85. * @param {Object} object The receiver of the properties.
  86. * @param {Object} config The source of the properties.
  87. * @return {Object} returns obj
  88. */
  89. applyIf: function(object, config) {
  90. var property;
  91. if (object) {
  92. for (property in config) {
  93. if (object[property] === undefined) {
  94. object[property] = config[property];
  95. }
  96. }
  97. }
  98. return object;
  99. },
  100. /**
  101. * Iterates either an array or an object. This method delegates to
  102. * {@link Ext.Array#each Ext.Array.each} if the given value is iterable, and {@link Ext.Object#each Ext.Object.each} otherwise.
  103. *
  104. * @param {Object/Array} object The object or array to be iterated.
  105. * @param {Function} fn The function to be called for each iteration. See and {@link Ext.Array#each Ext.Array.each} and
  106. * {@link Ext.Object#each Ext.Object.each} for detailed lists of arguments passed to this function depending on the given object
  107. * type that is being iterated.
  108. * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
  109. * Defaults to the object being iterated itself.
  110. */
  111. iterate: function(object, fn, scope) {
  112. if (Ext.isEmpty(object)) {
  113. return;
  114. }
  115. if (scope === undefined) {
  116. scope = object;
  117. }
  118. if (Ext.isIterable(object)) {
  119. Ext.Array.each.call(Ext.Array, object, fn, scope);
  120. }
  121. else {
  122. Ext.Object.each.call(Ext.Object, object, fn, scope);
  123. }
  124. }
  125. });
  126. Ext.apply(Ext, {
  127. /**
  128. * This method deprecated. Use {@link Ext#define Ext.define} instead.
  129. * @method
  130. * @param {Function} superclass
  131. * @param {Object} overrides
  132. * @return {Function} The subclass constructor from the `overrides` parameter, or a generated one if not provided.
  133. * @deprecated 4.0.0 Please use {@link Ext#define Ext.define} instead
  134. */
  135. extend: function() {
  136. // inline overrides
  137. var objectConstructor = objectPrototype.constructor,
  138. inlineOverrides = function(o) {
  139. for (var m in o) {
  140. if (!o.hasOwnProperty(m)) {
  141. continue;
  142. }
  143. this[m] = o[m];
  144. }
  145. };
  146. return function(subclass, superclass, overrides) {
  147. // First we check if the user passed in just the superClass with overrides
  148. if (Ext.isObject(superclass)) {
  149. overrides = superclass;
  150. superclass = subclass;
  151. subclass = overrides.constructor !== objectConstructor ? overrides.constructor : function() {
  152. superclass.apply(this, arguments);
  153. };
  154. }
  155. //<debug>
  156. if (!superclass) {
  157. Ext.Error.raise({
  158. sourceClass: 'Ext',
  159. sourceMethod: 'extend',
  160. msg: 'Attempting to extend from a class which has not been loaded on the page.'
  161. });
  162. }
  163. //</debug>
  164. // We create a new temporary class
  165. var F = function() {},
  166. subclassProto, superclassProto = superclass.prototype;
  167. F.prototype = superclassProto;
  168. subclassProto = subclass.prototype = new F();
  169. subclassProto.constructor = subclass;
  170. subclass.superclass = superclassProto;
  171. if (superclassProto.constructor === objectConstructor) {
  172. superclassProto.constructor = superclass;
  173. }
  174. subclass.override = function(overrides) {
  175. Ext.override(subclass, overrides);
  176. };
  177. subclassProto.override = inlineOverrides;
  178. subclassProto.proto = subclassProto;
  179. subclass.override(overrides);
  180. subclass.extend = function(o) {
  181. return Ext.extend(subclass, o);
  182. };
  183. return subclass;
  184. };
  185. }(),
  186. /**
  187. * Proxy to {@link Ext.Base#override}. Please refer {@link Ext.Base#override} for further details.
  188. *
  189. * @param {Object} cls The class to override
  190. * @param {Object} overrides The properties to add to `origClass`. This should be specified as an object literal
  191. * containing one or more properties.
  192. * @method override
  193. * @deprecated 4.1.0 Please use {@link Ext#define Ext.define} instead.
  194. */
  195. override: function(cls, overrides) {
  196. if (cls.$isClass) {
  197. return cls.override(overrides);
  198. }
  199. else {
  200. Ext.apply(cls.prototype, overrides);
  201. }
  202. }
  203. });
  204. // A full set of static methods to do type checking
  205. Ext.apply(Ext, {
  206. /**
  207. * Returns the given value itself if it's not empty, as described in {@link Ext#isEmpty}; returns the default
  208. * value (second argument) otherwise.
  209. *
  210. * @param {Object} value The value to test.
  211. * @param {Object} defaultValue The value to return if the original value is empty.
  212. * @param {Boolean} [allowBlank=false] (optional) `true` to allow zero length strings to qualify as non-empty.
  213. * @return {Object} `value`, if non-empty, else `defaultValue`.
  214. */
  215. valueFrom: function(value, defaultValue, allowBlank){
  216. return Ext.isEmpty(value, allowBlank) ? defaultValue : value;
  217. },
  218. /**
  219. * Returns the type of the given variable in string format. List of possible values are:
  220. *
  221. * - `undefined`: If the given value is `undefined`
  222. * - `null`: If the given value is `null`
  223. * - `string`: If the given value is a string
  224. * - `number`: If the given value is a number
  225. * - `boolean`: If the given value is a boolean value
  226. * - `date`: If the given value is a `Date` object
  227. * - `function`: If the given value is a function reference
  228. * - `object`: If the given value is an object
  229. * - `array`: If the given value is an array
  230. * - `regexp`: If the given value is a regular expression
  231. * - `element`: If the given value is a DOM Element
  232. * - `textnode`: If the given value is a DOM text node and contains something other than whitespace
  233. * - `whitespace`: If the given value is a DOM text node and contains only whitespace
  234. *
  235. * @param {Object} value
  236. * @return {String}
  237. */
  238. typeOf: function(value) {
  239. if (value === null) {
  240. return 'null';
  241. }
  242. var type = typeof value;
  243. if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
  244. return type;
  245. }
  246. var typeToString = toString.call(value);
  247. switch(typeToString) {
  248. case '[object Array]':
  249. return 'array';
  250. case '[object Date]':
  251. return 'date';
  252. case '[object Boolean]':
  253. return 'boolean';
  254. case '[object Number]':
  255. return 'number';
  256. case '[object RegExp]':
  257. return 'regexp';
  258. }
  259. if (type === 'function') {
  260. return 'function';
  261. }
  262. if (type === 'object') {
  263. if (value.nodeType !== undefined) {
  264. if (value.nodeType === 3) {
  265. return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
  266. }
  267. else {
  268. return 'element';
  269. }
  270. }
  271. return 'object';
  272. }
  273. //<debug error>
  274. Ext.Error.raise({
  275. sourceClass: 'Ext',
  276. sourceMethod: 'typeOf',
  277. msg: 'Failed to determine the type of the specified value "' + value + '". This is most likely a bug.'
  278. });
  279. //</debug>
  280. },
  281. /**
  282. * Returns `true` if the passed value is empty, `false` otherwise. The value is deemed to be empty if it is either:
  283. *
  284. * - `null`
  285. * - `undefined`
  286. * - a zero-length array.
  287. * - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`).
  288. *
  289. * @param {Object} value The value to test.
  290. * @param {Boolean} [allowEmptyString=false] (optional) `true` to allow empty strings.
  291. * @return {Boolean}
  292. */
  293. isEmpty: function(value, allowEmptyString) {
  294. return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
  295. },
  296. /**
  297. * Returns `true` if the passed value is a JavaScript Array, `false` otherwise.
  298. *
  299. * @param {Object} target The target to test.
  300. * @return {Boolean}
  301. * @method
  302. */
  303. isArray: ('isArray' in Array) ? Array.isArray : function(value) {
  304. return toString.call(value) === '[object Array]';
  305. },
  306. /**
  307. * Returns `true` if the passed value is a JavaScript Date object, `false` otherwise.
  308. * @param {Object} object The object to test.
  309. * @return {Boolean}
  310. */
  311. isDate: function(value) {
  312. return toString.call(value) === '[object Date]';
  313. },
  314. /**
  315. * Returns `true` if the passed value is a JavaScript Object, `false` otherwise.
  316. * @param {Object} value The value to test.
  317. * @return {Boolean}
  318. * @method
  319. */
  320. isObject: (toString.call(null) === '[object Object]') ?
  321. function(value) {
  322. // check ownerDocument here as well to exclude DOM nodes
  323. return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.ownerDocument === undefined;
  324. } :
  325. function(value) {
  326. return toString.call(value) === '[object Object]';
  327. },
  328. /**
  329. * @private
  330. */
  331. isSimpleObject: function(value) {
  332. return value instanceof Object && value.constructor === Object;
  333. },
  334. /**
  335. * Returns `true` if the passed value is a JavaScript 'primitive', a string, number or Boolean.
  336. * @param {Object} value The value to test.
  337. * @return {Boolean}
  338. */
  339. isPrimitive: function(value) {
  340. var type = typeof value;
  341. return type === 'string' || type === 'number' || type === 'boolean';
  342. },
  343. /**
  344. * Returns `true` if the passed value is a JavaScript Function, `false` otherwise.
  345. * @param {Object} value The value to test.
  346. * @return {Boolean}
  347. * @method
  348. */
  349. isFunction:
  350. // Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
  351. // Object.prorotype.toString (slower)
  352. (typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
  353. return toString.call(value) === '[object Function]';
  354. } : function(value) {
  355. return typeof value === 'function';
  356. },
  357. /**
  358. * Returns `true` if the passed value is a number. Returns `false` for non-finite numbers.
  359. * @param {Object} value The value to test.
  360. * @return {Boolean}
  361. */
  362. isNumber: function(value) {
  363. return typeof value === 'number' && isFinite(value);
  364. },
  365. /**
  366. * Validates that a value is numeric.
  367. * @param {Object} value Examples: 1, '1', '2.34'
  368. * @return {Boolean} `true` if numeric, `false` otherwise.
  369. */
  370. isNumeric: function(value) {
  371. return !isNaN(parseFloat(value)) && isFinite(value);
  372. },
  373. /**
  374. * Returns `true` if the passed value is a string.
  375. * @param {Object} value The value to test.
  376. * @return {Boolean}
  377. */
  378. isString: function(value) {
  379. return typeof value === 'string';
  380. },
  381. /**
  382. * Returns `true` if the passed value is a Boolean.
  383. *
  384. * @param {Object} value The value to test.
  385. * @return {Boolean}
  386. */
  387. isBoolean: function(value) {
  388. return typeof value === 'boolean';
  389. },
  390. /**
  391. * Returns `true` if the passed value is an HTMLElement.
  392. * @param {Object} value The value to test.
  393. * @return {Boolean}
  394. */
  395. isElement: function(value) {
  396. return value ? value.nodeType === 1 : false;
  397. },
  398. /**
  399. * Returns `true` if the passed value is a TextNode.
  400. * @param {Object} value The value to test.
  401. * @return {Boolean}
  402. */
  403. isTextNode: function(value) {
  404. return value ? value.nodeName === "#text" : false;
  405. },
  406. /**
  407. * Returns `true` if the passed value is defined.
  408. * @param {Object} value The value to test.
  409. * @return {Boolean}
  410. */
  411. isDefined: function(value) {
  412. return typeof value !== 'undefined';
  413. },
  414. /**
  415. * Returns `true` if the passed value is iterable, `false` otherwise.
  416. * @param {Object} value The value to test.
  417. * @return {Boolean}
  418. */
  419. isIterable: function(value) {
  420. return (value && typeof value !== 'string') ? value.length !== undefined : false;
  421. }
  422. });
  423. Ext.apply(Ext, {
  424. /**
  425. * Clone almost any type of variable including array, object, DOM nodes and Date without keeping the old reference.
  426. * @param {Object} item The variable to clone.
  427. * @return {Object} clone
  428. */
  429. clone: function(item) {
  430. if (item === null || item === undefined) {
  431. return item;
  432. }
  433. // DOM nodes
  434. if (item.nodeType && item.cloneNode) {
  435. return item.cloneNode(true);
  436. }
  437. // Strings
  438. var type = toString.call(item);
  439. // Dates
  440. if (type === '[object Date]') {
  441. return new Date(item.getTime());
  442. }
  443. var i, j, k, clone, key;
  444. // Arrays
  445. if (type === '[object Array]') {
  446. i = item.length;
  447. clone = [];
  448. while (i--) {
  449. clone[i] = Ext.clone(item[i]);
  450. }
  451. }
  452. // Objects
  453. else if (type === '[object Object]' && item.constructor === Object) {
  454. clone = {};
  455. for (key in item) {
  456. clone[key] = Ext.clone(item[key]);
  457. }
  458. if (enumerables) {
  459. for (j = enumerables.length; j--;) {
  460. k = enumerables[j];
  461. clone[k] = item[k];
  462. }
  463. }
  464. }
  465. return clone || item;
  466. },
  467. /**
  468. * @private
  469. * Generate a unique reference of Ext in the global scope, useful for sandboxing.
  470. */
  471. getUniqueGlobalNamespace: function() {
  472. var uniqueGlobalNamespace = this.uniqueGlobalNamespace;
  473. if (uniqueGlobalNamespace === undefined) {
  474. var i = 0;
  475. do {
  476. uniqueGlobalNamespace = 'ExtBox' + (++i);
  477. } while (Ext.global[uniqueGlobalNamespace] !== undefined);
  478. Ext.global[uniqueGlobalNamespace] = Ext;
  479. this.uniqueGlobalNamespace = uniqueGlobalNamespace;
  480. }
  481. return uniqueGlobalNamespace;
  482. },
  483. /**
  484. * @private
  485. */
  486. functionFactory: function() {
  487. var args = Array.prototype.slice.call(arguments),
  488. ln = args.length;
  489. if (ln > 0) {
  490. args[ln - 1] = 'var Ext=window.' + this.getUniqueGlobalNamespace() + ';' + args[ln - 1];
  491. }
  492. return Function.prototype.constructor.apply(Function.prototype, args);
  493. },
  494. /**
  495. * @private
  496. */
  497. globalEval: ('execScript' in global) ? function(code) {
  498. global.execScript(code)
  499. } : function(code) {
  500. (function(){
  501. eval(code);
  502. })();
  503. }
  504. //<feature logger>
  505. /**
  506. * @private
  507. * @property
  508. */
  509. ,Logger: {
  510. log: function(message, priority) {
  511. if ('console' in global) {
  512. if (!priority || !(priority in global.console)) {
  513. priority = 'log';
  514. }
  515. message = '[' + priority.toUpperCase() + '] ' + message;
  516. global.console[priority](message);
  517. }
  518. },
  519. verbose: function(message) {
  520. this.log(message, 'verbose');
  521. },
  522. info: function(message) {
  523. this.log(message, 'info');
  524. },
  525. warn: function(message) {
  526. this.log(message, 'warn');
  527. },
  528. error: function(message) {
  529. throw new Error(message);
  530. },
  531. deprecate: function(message) {
  532. this.log(message, 'warn');
  533. }
  534. }
  535. //</feature>
  536. });
  537. /**
  538. * Old alias to {@link Ext#typeOf}.
  539. * @deprecated 4.0.0 Please use {@link Ext#typeOf} instead.
  540. * @method
  541. * @alias Ext#typeOf
  542. */
  543. Ext.type = Ext.typeOf;
  544. })();
  545. //@tag foundation,core
  546. //@define Ext.Version
  547. //@require Ext
  548. /**
  549. * @author Jacky Nguyen <jacky@sencha.com>
  550. * @docauthor Jacky Nguyen <jacky@sencha.com>
  551. * @class Ext.Version
  552. *
  553. * A utility class that wrap around a string version number and provide convenient
  554. * method to perform comparison. See also: {@link Ext.Version#compare compare}. Example:
  555. *
  556. * var version = new Ext.Version('1.0.2beta');
  557. * console.log("Version is " + version); // Version is 1.0.2beta
  558. *
  559. * console.log(version.getMajor()); // 1
  560. * console.log(version.getMinor()); // 0
  561. * console.log(version.getPatch()); // 2
  562. * console.log(version.getBuild()); // 0
  563. * console.log(version.getRelease()); // beta
  564. *
  565. * console.log(version.isGreaterThan('1.0.1')); // true
  566. * console.log(version.isGreaterThan('1.0.2alpha')); // true
  567. * console.log(version.isGreaterThan('1.0.2RC')); // false
  568. * console.log(version.isGreaterThan('1.0.2')); // false
  569. * console.log(version.isLessThan('1.0.2')); // true
  570. *
  571. * console.log(version.match(1.0)); // true
  572. * console.log(version.match('1.0.2')); // true
  573. */
  574. (function() {
  575. // Current core version
  576. var version = '4.1.0', Version;
  577. Ext.Version = Version = Ext.extend(Object, {
  578. /**
  579. * Creates new Version object.
  580. * @param {String/Number} version The version number in the follow standard format: major[.minor[.patch[.build[release]]]]
  581. * Examples: 1.0 or 1.2.3beta or 1.2.3.4RC
  582. * @return {Ext.Version} this
  583. */
  584. constructor: function(version) {
  585. var toNumber = this.toNumber,
  586. parts, releaseStartIndex;
  587. if (version instanceof Version) {
  588. return version;
  589. }
  590. this.version = this.shortVersion = String(version).toLowerCase().replace(/_/g, '.').replace(/[\-+]/g, '');
  591. releaseStartIndex = this.version.search(/([^\d\.])/);
  592. if (releaseStartIndex !== -1) {
  593. this.release = this.version.substr(releaseStartIndex, version.length);
  594. this.shortVersion = this.version.substr(0, releaseStartIndex);
  595. }
  596. this.shortVersion = this.shortVersion.replace(/[^\d]/g, '');
  597. parts = this.version.split('.');
  598. this.major = toNumber(parts.shift());
  599. this.minor = toNumber(parts.shift());
  600. this.patch = toNumber(parts.shift());
  601. this.build = toNumber(parts.shift());
  602. return this;
  603. },
  604. /**
  605. * @param value
  606. * @return {Number}
  607. */
  608. toNumber: function(value) {
  609. value = parseInt(value || 0, 10);
  610. if (isNaN(value)) {
  611. value = 0;
  612. }
  613. return value;
  614. },
  615. /**
  616. * Override the native `toString()` method.
  617. * @private
  618. * @return {String} version
  619. */
  620. toString: function() {
  621. return this.version;
  622. },
  623. /**
  624. * Override the native `valueOf()` method.
  625. * @private
  626. * @return {String} version
  627. */
  628. valueOf: function() {
  629. return this.version;
  630. },
  631. /**
  632. * Returns the major component value.
  633. * @return {Number} major
  634. */
  635. getMajor: function() {
  636. return this.major || 0;
  637. },
  638. /**
  639. * Returns the minor component value.
  640. * @return {Number} minor
  641. */
  642. getMinor: function() {
  643. return this.minor || 0;
  644. },
  645. /**
  646. * Returns the patch component value.
  647. * @return {Number} patch
  648. */
  649. getPatch: function() {
  650. return this.patch || 0;
  651. },
  652. /**
  653. * Returns the build component value.
  654. * @return {Number} build
  655. */
  656. getBuild: function() {
  657. return this.build || 0;
  658. },
  659. /**
  660. * Returns the release component value.
  661. * @return {Number} release
  662. */
  663. getRelease: function() {
  664. return this.release || '';
  665. },
  666. /**
  667. * Returns whether this version if greater than the supplied argument.
  668. * @param {String/Number} target The version to compare with.
  669. * @return {Boolean} `true` if this version if greater than the target, `false` otherwise.
  670. */
  671. isGreaterThan: function(target) {
  672. return Version.compare(this.version, target) === 1;
  673. },
  674. /**
  675. * Returns whether this version if greater than or equal to the supplied argument.
  676. * @param {String/Number} target The version to compare with.
  677. * @return {Boolean} `true` if this version if greater than or equal to the target, `false` otherwise.
  678. */
  679. isGreaterThanOrEqual: function(target) {
  680. return Version.compare(this.version, target) >= 0;
  681. },
  682. /**
  683. * Returns whether this version if smaller than the supplied argument.
  684. * @param {String/Number} target The version to compare with.
  685. * @return {Boolean} `true` if this version if smaller than the target, `false` otherwise.
  686. */
  687. isLessThan: function(target) {
  688. return Version.compare(this.version, target) === -1;
  689. },
  690. /**
  691. * Returns whether this version if less than or equal to the supplied argument.
  692. * @param {String/Number} target The version to compare with.
  693. * @return {Boolean} `true` if this version if less than or equal to the target, `false` otherwise.
  694. */
  695. isLessThanOrEqual: function(target) {
  696. return Version.compare(this.version, target) <= 0;
  697. },
  698. /**
  699. * Returns whether this version equals to the supplied argument.
  700. * @param {String/Number} target The version to compare with.
  701. * @return {Boolean} `true` if this version equals to the target, `false` otherwise.
  702. */
  703. equals: function(target) {
  704. return Version.compare(this.version, target) === 0;
  705. },
  706. /**
  707. * Returns whether this version matches the supplied argument. Example:
  708. *
  709. * var version = new Ext.Version('1.0.2beta');
  710. * console.log(version.match(1)); // true
  711. * console.log(version.match(1.0)); // true
  712. * console.log(version.match('1.0.2')); // true
  713. * console.log(version.match('1.0.2RC')); // false
  714. *
  715. * @param {String/Number} target The version to compare with.
  716. * @return {Boolean} `true` if this version matches the target, `false` otherwise.
  717. */
  718. match: function(target) {
  719. target = String(target);
  720. return this.version.substr(0, target.length) === target;
  721. },
  722. /**
  723. * Returns this format: [major, minor, patch, build, release]. Useful for comparison.
  724. * @return {Number[]}
  725. */
  726. toArray: function() {
  727. return [this.getMajor(), this.getMinor(), this.getPatch(), this.getBuild(), this.getRelease()];
  728. },
  729. /**
  730. * Returns shortVersion version without dots and release.
  731. * @return {String}
  732. */
  733. getShortVersion: function() {
  734. return this.shortVersion;
  735. },
  736. /**
  737. * Convenient alias to {@link Ext.Version#isGreaterThan isGreaterThan}
  738. * @param {String/Number} target
  739. * @return {Boolean}
  740. */
  741. gt: function() {
  742. return this.isGreaterThan.apply(this, arguments);
  743. },
  744. /**
  745. * Convenient alias to {@link Ext.Version#isLessThan isLessThan}
  746. * @param {String/Number} target
  747. * @return {Boolean}
  748. */
  749. lt: function() {
  750. return this.isLessThan.apply(this, arguments);
  751. },
  752. /**
  753. * Convenient alias to {@link Ext.Version#isGreaterThanOrEqual isGreaterThanOrEqual}
  754. * @param {String/Number} target
  755. * @return {Boolean}
  756. */
  757. gtEq: function() {
  758. return this.isGreaterThanOrEqual.apply(this, arguments);
  759. },
  760. /**
  761. * Convenient alias to {@link Ext.Version#isLessThanOrEqual isLessThanOrEqual}
  762. * @param {String/Number} target
  763. * @return {Boolean}
  764. */
  765. ltEq: function() {
  766. return this.isLessThanOrEqual.apply(this, arguments);
  767. }
  768. });
  769. Ext.apply(Version, {
  770. // @private
  771. releaseValueMap: {
  772. 'dev': -6,
  773. 'alpha': -5,
  774. 'a': -5,
  775. 'beta': -4,
  776. 'b': -4,
  777. 'rc': -3,
  778. '#': -2,
  779. 'p': -1,
  780. 'pl': -1
  781. },
  782. /**
  783. * Converts a version component to a comparable value.
  784. *
  785. * @static
  786. * @param {Object} value The value to convert
  787. * @return {Object}
  788. */
  789. getComponentValue: function(value) {
  790. return !value ? 0 : (isNaN(value) ? this.releaseValueMap[value] || value : parseInt(value, 10));
  791. },
  792. /**
  793. * Compare 2 specified versions, starting from left to right. If a part contains special version strings,
  794. * they are handled in the following order:
  795. * 'dev' < 'alpha' = 'a' < 'beta' = 'b' < 'RC' = 'rc' < '#' < 'pl' = 'p' < 'anything else'
  796. *
  797. * @static
  798. * @param {String} current The current version to compare to.
  799. * @param {String} target The target version to compare to.
  800. * @return {Number} Returns -1 if the current version is smaller than the target version, 1 if greater, and 0 if they're equivalent.
  801. */
  802. compare: function(current, target) {
  803. var currentValue, targetValue, i;
  804. current = new Version(current).toArray();
  805. target = new Version(target).toArray();
  806. for (i = 0; i < Math.max(current.length, target.length); i++) {
  807. currentValue = this.getComponentValue(current[i]);
  808. targetValue = this.getComponentValue(target[i]);
  809. if (currentValue < targetValue) {
  810. return -1;
  811. } else if (currentValue > targetValue) {
  812. return 1;
  813. }
  814. }
  815. return 0;
  816. }
  817. });
  818. Ext.apply(Ext, {
  819. /**
  820. * @private
  821. */
  822. versions: {},
  823. /**
  824. * @private
  825. */
  826. lastRegisteredVersion: null,
  827. /**
  828. * Set version number for the given package name.
  829. *
  830. * @param {String} packageName The package name, for example: 'core', 'touch', 'extjs'.
  831. * @param {String/Ext.Version} version The version, for example: '1.2.3alpha', '2.4.0-dev'.
  832. * @return {Ext}
  833. */
  834. setVersion: function(packageName, version) {
  835. Ext.versions[packageName] = new Version(version);
  836. Ext.lastRegisteredVersion = Ext.versions[packageName];
  837. return this;
  838. },
  839. /**
  840. * Get the version number of the supplied package name; will return the last registered version
  841. * (last `Ext.setVersion()` call) if there's no package name given.
  842. *
  843. * @param {String} packageName (Optional) The package name, for example: 'core', 'touch', 'extjs'.
  844. * @return {Ext.Version} The version.
  845. */
  846. getVersion: function(packageName) {
  847. if (packageName === undefined) {
  848. return Ext.lastRegisteredVersion;
  849. }
  850. return Ext.versions[packageName];
  851. },
  852. /**
  853. * Create a closure for deprecated code.
  854. *
  855. * // This means Ext.oldMethod is only supported in 4.0.0beta and older.
  856. * // If Ext.getVersion('extjs') returns a version that is later than '4.0.0beta', for example '4.0.0RC',
  857. * // the closure will not be invoked
  858. * Ext.deprecate('extjs', '4.0.0beta', function() {
  859. * Ext.oldMethod = Ext.newMethod;
  860. * // ...
  861. * });
  862. *
  863. * @param {String} packageName The package name.
  864. * @param {String} since The last version before it's deprecated.
  865. * @param {Function} closure The callback function to be executed with the specified version is less than the current version.
  866. * @param {Object} scope The execution scope (`this`) if the closure
  867. */
  868. deprecate: function(packageName, since, closure, scope) {
  869. if (Version.compare(Ext.getVersion(packageName), since) < 1) {
  870. closure.call(scope);
  871. }
  872. }
  873. }); // End Versioning
  874. Ext.setVersion('core', version);
  875. })();
  876. //@tag foundation,core
  877. //@define Ext.String
  878. //@require Ext.Version
  879. /**
  880. * @class Ext.String
  881. *
  882. * A collection of useful static methods to deal with strings.
  883. * @singleton
  884. */
  885. Ext.String = {
  886. trimRegex: /^[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+|[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+$/g,
  887. escapeRe: /('|\\)/g,
  888. formatRe: /\{(\d+)\}/g,
  889. escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,
  890. /**
  891. * Convert certain characters (&, <, >, and ") to their HTML character equivalents for literal display in web pages.
  892. * @param {String} value The string to encode.
  893. * @return {String} The encoded text.
  894. * @method
  895. */
  896. htmlEncode: (function() {
  897. var entities = {
  898. '&': '&amp;',
  899. '>': '&gt;',
  900. '<': '&lt;',
  901. '"': '&quot;'
  902. }, keys = [], p, regex;
  903. for (p in entities) {
  904. keys.push(p);
  905. }
  906. regex = new RegExp('(' + keys.join('|') + ')', 'g');
  907. return function(value) {
  908. return (!value) ? value : String(value).replace(regex, function(match, capture) {
  909. return entities[capture];
  910. });
  911. };
  912. })(),
  913. /**
  914. * Convert certain characters (&, <, >, and ") from their HTML character equivalents.
  915. * @param {String} value The string to decode.
  916. * @return {String} The decoded text.
  917. * @method
  918. */
  919. htmlDecode: (function() {
  920. var entities = {
  921. '&amp;': '&',
  922. '&gt;': '>',
  923. '&lt;': '<',
  924. '&quot;': '"'
  925. }, keys = [], p, regex;
  926. for (p in entities) {
  927. keys.push(p);
  928. }
  929. regex = new RegExp('(' + keys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
  930. return function(value) {
  931. return (!value) ? value : String(value).replace(regex, function(match, capture) {
  932. if (capture in entities) {
  933. return entities[capture];
  934. } else {
  935. return String.fromCharCode(parseInt(capture.substr(2), 10));
  936. }
  937. });
  938. };
  939. })(),
  940. /**
  941. * Appends content to the query string of a URL, handling logic for whether to place
  942. * a question mark or ampersand.
  943. * @param {String} url The URL to append to.
  944. * @param {String} string The content to append to the URL.
  945. * @return {String} The resulting URL.
  946. */
  947. urlAppend : function(url, string) {
  948. if (!Ext.isEmpty(string)) {
  949. return url + (url.indexOf('?') === -1 ? '?' : '&') + string;
  950. }
  951. return url;
  952. },
  953. /**
  954. * Trims whitespace from either end of a string, leaving spaces within the string intact. Example:
  955. *
  956. * @example
  957. * var s = ' foo bar ';
  958. * alert('-' + s + '-'); // alerts "- foo bar -"
  959. * alert('-' + Ext.String.trim(s) + '-'); // alerts "-foo bar-"
  960. *
  961. * @param {String} string The string to escape
  962. * @return {String} The trimmed string
  963. */
  964. trim: function(string) {
  965. return string.replace(Ext.String.trimRegex, "");
  966. },
  967. /**
  968. * Capitalize the given string.
  969. * @param {String} string
  970. * @return {String}
  971. */
  972. capitalize: function(string) {
  973. return string.charAt(0).toUpperCase() + string.substr(1);
  974. },
  975. /**
  976. * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length.
  977. * @param {String} value The string to truncate.
  978. * @param {Number} length The maximum length to allow before truncating.
  979. * @param {Boolean} word `true` to try to find a common word break.
  980. * @return {String} The converted text.
  981. */
  982. ellipsis: function(value, len, word) {
  983. if (value && value.length > len) {
  984. if (word) {
  985. var vs = value.substr(0, len - 2),
  986. index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
  987. if (index !== -1 && index >= (len - 15)) {
  988. return vs.substr(0, index) + "...";
  989. }
  990. }
  991. return value.substr(0, len - 3) + "...";
  992. }
  993. return value;
  994. },
  995. /**
  996. * Escapes the passed string for use in a regular expression.
  997. * @param {String} string
  998. * @return {String}
  999. */
  1000. escapeRegex: function(string) {
  1001. return string.replace(Ext.String.escapeRegexRe, "\\$1");
  1002. },
  1003. /**
  1004. * Escapes the passed string for ' and \.
  1005. * @param {String} string The string to escape.
  1006. * @return {String} The escaped string.
  1007. */
  1008. escape: function(string) {
  1009. return string.replace(Ext.String.escapeRe, "\\$1");
  1010. },
  1011. /**
  1012. * Utility function that allows you to easily switch a string between two alternating values. The passed value
  1013. * is compared to the current string, and if they are equal, the other value that was passed in is returned. If
  1014. * they are already different, the first value passed in is returned. Note that this method returns the new value
  1015. * but does not change the current string.
  1016. *
  1017. * // alternate sort directions
  1018. * sort = Ext.String.toggle(sort, 'ASC', 'DESC');
  1019. *
  1020. * // instead of conditional logic:
  1021. * sort = (sort == 'ASC' ? 'DESC' : 'ASC');
  1022. *
  1023. * @param {String} string The current string.
  1024. * @param {String} value The value to compare to the current string.
  1025. * @param {String} other The new value to use if the string already equals the first value passed in.
  1026. * @return {String} The new value.
  1027. */
  1028. toggle: function(string, value, other) {
  1029. return string === value ? other : value;
  1030. },
  1031. /**
  1032. * Pads the left side of a string with a specified character. This is especially useful
  1033. * for normalizing number and date strings. Example usage:
  1034. *
  1035. * var s = Ext.String.leftPad('123', 5, '0');
  1036. * alert(s); // '00123'
  1037. *
  1038. * @param {String} string The original string.
  1039. * @param {Number} size The total length of the output string.
  1040. * @param {String} [character= ] (optional) The character with which to pad the original string (defaults to empty string " ").
  1041. * @return {String} The padded string.
  1042. */
  1043. leftPad: function(string, size, character) {
  1044. var result = String(string);
  1045. character = character || " ";
  1046. while (result.length < size) {
  1047. result = character + result;
  1048. }
  1049. return result;
  1050. },
  1051. /**
  1052. * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens. Each
  1053. * token must be unique, and must increment in the format {0}, {1}, etc. Example usage:
  1054. *
  1055. * var cls = 'my-class',
  1056. * text = 'Some text';
  1057. * var s = Ext.String.format('<div class="{0}">{1}</div>', cls, text);
  1058. * alert(s); // '<div class="my-class">Some text</div>'
  1059. *
  1060. * @param {String} string The tokenized string to be formatted.
  1061. * @param {String} value1 The value to replace token {0}.
  1062. * @param {String} value2 Etc...
  1063. * @return {String} The formatted string.
  1064. */
  1065. format: function(format) {
  1066. var args = Ext.Array.toArray(arguments, 1);
  1067. return format.replace(Ext.String.formatRe, function(m, i) {
  1068. return args[i];
  1069. });
  1070. },
  1071. /**
  1072. * Returns a string with a specified number of repetitions a given string pattern.
  1073. * The pattern be separated by a different string.
  1074. *
  1075. * var s = Ext.String.repeat('---', 4); // '------------'
  1076. * var t = Ext.String.repeat('--', 3, '/'); // '--/--/--'
  1077. *
  1078. * @param {String} pattern The pattern to repeat.
  1079. * @param {Number} count The number of times to repeat the pattern (may be 0).
  1080. * @param {String} sep An option string to separate each pattern.
  1081. */
  1082. repeat: function(pattern, count, sep) {
  1083. for (var buf = [], i = count; i--; ) {
  1084. buf.push(pattern);
  1085. }
  1086. return buf.join(sep || '');
  1087. }
  1088. };
  1089. /**
  1090. * Old alias to {@link Ext.String#htmlEncode}.
  1091. * @deprecated Use {@link Ext.String#htmlEncode} instead.
  1092. * @method
  1093. * @member Ext
  1094. * @alias Ext.String#htmlEncode
  1095. */
  1096. Ext.htmlEncode = Ext.String.htmlEncode;
  1097. /**
  1098. * Old alias to {@link Ext.String#htmlDecode}.
  1099. * @deprecated Use {@link Ext.String#htmlDecode} instead.
  1100. * @method
  1101. * @member Ext
  1102. * @alias Ext.String#htmlDecode
  1103. */
  1104. Ext.htmlDecode = Ext.String.htmlDecode;
  1105. /**
  1106. * Old alias to {@link Ext.String#urlAppend}.
  1107. * @deprecated Use {@link Ext.String#urlAppend} instead.
  1108. * @method
  1109. * @member Ext
  1110. * @alias Ext.String#urlAppend
  1111. */
  1112. Ext.urlAppend = Ext.String.urlAppend;
  1113. //@tag foundation,core
  1114. //@define Ext.Array
  1115. //@require Ext.String
  1116. /**
  1117. * @class Ext.Array
  1118. * @singleton
  1119. * @author Jacky Nguyen <jacky@sencha.com>
  1120. * @docauthor Jacky Nguyen <jacky@sencha.com>
  1121. *
  1122. * A set of useful static methods to deal with arrays; provide missing methods for older browsers.
  1123. */
  1124. (function() {
  1125. var arrayPrototype = Array.prototype,
  1126. slice = arrayPrototype.slice,
  1127. supportsSplice = function () {
  1128. var array = [],
  1129. lengthBefore,
  1130. j = 20;
  1131. if (!array.splice) {
  1132. return false;
  1133. }
  1134. // This detects a bug in IE8 splice method:
  1135. // see http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/6e946d03-e09f-4b22-a4dd-cd5e276bf05a/
  1136. while (j--) {
  1137. array.push("A");
  1138. }
  1139. array.splice(15, 0, "F", "F", "F", "F", "F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F");
  1140. lengthBefore = array.length; //41
  1141. array.splice(13, 0, "XXX"); // add one element
  1142. if (lengthBefore+1 != array.length) {
  1143. return false;
  1144. }
  1145. // end IE8 bug
  1146. return true;
  1147. }(),
  1148. supportsForEach = 'forEach' in arrayPrototype,
  1149. supportsMap = 'map' in arrayPrototype,
  1150. supportsIndexOf = 'indexOf' in arrayPrototype,
  1151. supportsEvery = 'every' in arrayPrototype,
  1152. supportsSome = 'some' in arrayPrototype,
  1153. supportsFilter = 'filter' in arrayPrototype,
  1154. supportsSort = function() {
  1155. var a = [1,2,3,4,5].sort(function(){ return 0; });
  1156. return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
  1157. }(),
  1158. supportsSliceOnNodeList = true,
  1159. ExtArray;
  1160. try {
  1161. // IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList
  1162. if (typeof document !== 'undefined') {
  1163. slice.call(document.getElementsByTagName('body'));
  1164. }
  1165. } catch (e) {
  1166. supportsSliceOnNodeList = false;
  1167. }
  1168. function fixArrayIndex (array, index) {
  1169. return (index < 0) ? Math.max(0, array.length + index)
  1170. : Math.min(array.length, index);
  1171. }
  1172. /*
  1173. Does the same work as splice, but with a slightly more convenient signature. The splice
  1174. method has bugs in IE8, so this is the implementation we use on that platform.
  1175. The rippling of items in the array can be tricky. Consider two use cases:
  1176. index=2
  1177. removeCount=2
  1178. /=====\
  1179. +---+---+---+---+---+---+---+---+
  1180. | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
  1181. +---+---+---+---+---+---+---+---+
  1182. / \/ \/ \/ \
  1183. / /\ /\ /\ \
  1184. / / \/ \/ \ +--------------------------+
  1185. / / /\ /\ +--------------------------+ \
  1186. / / / \/ +--------------------------+ \ \
  1187. / / / /+--------------------------+ \ \ \
  1188. / / / / \ \ \ \
  1189. v v v v v v v v
  1190. +---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+
  1191. | 0 | 1 | 4 | 5 | 6 | 7 | | 0 | 1 | a | b | c | 4 | 5 | 6 | 7 |
  1192. +---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+
  1193. A B \=========/
  1194. insert=[a,b,c]
  1195. In case A, it is obvious that copying of [4,5,6,7] must be left-to-right so
  1196. that we don't end up with [0,1,6,7,6,7]. In case B, we have the opposite; we
  1197. must go right-to-left or else we would end up with [0,1,a,b,c,4,4,4,4].
  1198. */
  1199. function replaceSim (array, index, removeCount, insert) {
  1200. var add = insert ? insert.length : 0,
  1201. length = array.length,
  1202. pos = fixArrayIndex(array, index);
  1203. // we try to use Array.push when we can for efficiency...
  1204. if (pos === length) {
  1205. if (add) {
  1206. array.push.apply(array, insert);
  1207. }
  1208. } else {
  1209. var remove = Math.min(removeCount, length - pos),
  1210. tailOldPos = pos + remove,
  1211. tailNewPos = tailOldPos + add - remove,
  1212. tailCount = length - tailOldPos,
  1213. lengthAfterRemove = length - remove,
  1214. i;
  1215. if (tailNewPos < tailOldPos) { // case A
  1216. for (i = 0; i < tailCount; ++i) {
  1217. array[tailNewPos+i] = array[tailOldPos+i];
  1218. }
  1219. } else if (tailNewPos > tailOldPos) { // case B
  1220. for (i = tailCount; i--; ) {
  1221. array[tailNewPos+i] = array[tailOldPos+i];
  1222. }
  1223. } // else, add == remove (nothing to do)
  1224. if (add && pos === lengthAfterRemove) {
  1225. array.length = lengthAfterRemove; // truncate array
  1226. array.push.apply(array, insert);
  1227. } else {
  1228. array.length = lengthAfterRemove + add; // reserves space
  1229. for (i = 0; i < add; ++i) {
  1230. array[pos+i] = insert[i];
  1231. }
  1232. }
  1233. }
  1234. return array;
  1235. }
  1236. function replaceNative (array, index, removeCount, insert) {
  1237. if (insert && insert.length) {
  1238. if (index < array.length) {
  1239. array.splice.apply(array, [index, removeCount].concat(insert));
  1240. } else {
  1241. array.push.apply(array, insert);
  1242. }
  1243. } else {
  1244. array.splice(index, removeCount);
  1245. }
  1246. return array;
  1247. }
  1248. function eraseSim (array, index, removeCount) {
  1249. return replaceSim(array, index, removeCount);
  1250. }
  1251. function eraseNative (array, index, removeCount) {
  1252. array.splice(index, removeCount);
  1253. return array;
  1254. }
  1255. function spliceSim (array, index, removeCount) {
  1256. var pos = fixArrayIndex(array, index),
  1257. removed = array.slice(index, fixArrayIndex(array, pos+removeCount));
  1258. if (arguments.length < 4) {
  1259. replaceSim(array, pos, removeCount);
  1260. } else {
  1261. replaceSim(array, pos, removeCount, slice.call(arguments, 3));
  1262. }
  1263. return removed;
  1264. }
  1265. function spliceNative (array) {
  1266. return array.splice.apply(array, slice.call(arguments, 1));
  1267. }
  1268. var erase = supportsSplice ? eraseNative : eraseSim,
  1269. replace = supportsSplice ? replaceNative : replaceSim,
  1270. splice = supportsSplice ? spliceNative : spliceSim;
  1271. // NOTE: from here on, use erase, replace or splice (not native methods)...
  1272. ExtArray = Ext.Array = {
  1273. /**
  1274. * Iterates an array or an iterable value and invoke the given callback function for each item.
  1275. *
  1276. * var countries = ['Vietnam', 'Singapore', 'United States', 'Russia'];
  1277. *
  1278. * Ext.Array.each(countries, function(name, index, countriesItSelf) {
  1279. * console.log(name);
  1280. * });
  1281. *
  1282. * var sum = function() {
  1283. * var sum = 0;
  1284. *
  1285. * Ext.Array.each(arguments, function(value) {
  1286. * sum += value;
  1287. * });
  1288. *
  1289. * return sum;
  1290. * };
  1291. *
  1292. * sum(1, 2, 3); // returns 6
  1293. *
  1294. * The iteration can be stopped by returning false in the function callback.
  1295. *
  1296. * Ext.Array.each(countries, function(name, index, countriesItSelf) {
  1297. * if (name === 'Singapore') {
  1298. * return false; // break here
  1299. * }
  1300. * });
  1301. *
  1302. * {@link Ext#each Ext.each} is alias for {@link Ext.Array#each Ext.Array.each}
  1303. *
  1304. * @param {Array/NodeList/Object} iterable The value to be iterated. If this
  1305. * argument is not iterable, the callback function is called once.
  1306. * @param {Function} fn The callback function. If it returns `false`, the iteration stops and this method returns
  1307. * the current `index`.
  1308. * @param {Object} fn.item The item at the current `index` in the passed `array`
  1309. * @param {Number} fn.index The current `index` within the `array`
  1310. * @param {Array} fn.allItems The `array` itself which was passed as the first argument
  1311. * @param {Boolean} fn.return Return false to stop iteration.
  1312. * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
  1313. * @param {Boolean} [reverse=false] (Optional) Reverse the iteration order (loop from the end to the beginning).
  1314. * @return {Boolean} See description for the `fn` parameter.
  1315. */
  1316. each: function(array, fn, scope, reverse) {
  1317. array = ExtArray.from(array);
  1318. var i,
  1319. ln = array.length;
  1320. if (reverse !== true) {
  1321. for (i = 0; i < ln; i++) {
  1322. if (fn.call(scope || array[i], array[i], i, array) === false) {
  1323. return i;
  1324. }
  1325. }
  1326. }
  1327. else {
  1328. for (i = ln - 1; i > -1; i--) {
  1329. if (fn.call(scope || array[i], array[i], i, array) === false) {
  1330. return i;
  1331. }
  1332. }
  1333. }
  1334. return true;
  1335. },
  1336. /**
  1337. * Iterates an array and invoke the given callback function for each item. Note that this will simply
  1338. * delegate to the native `Array.prototype.forEach` method if supported. It doesn't support stopping the
  1339. * iteration by returning `false` in the callback function like {@link Ext.Array#each}. However, performance
  1340. * could be much better in modern browsers comparing with {@link Ext.Array#each}
  1341. *
  1342. * @param {Array} array The array to iterate.
  1343. * @param {Function} fn The callback function.
  1344. * @param {Object} fn.item The item at the current `index` in the passed `array`.
  1345. * @param {Number} fn.index The current `index` within the `array`.
  1346. * @param {Array} fn.allItems The `array` itself which was passed as the first argument.
  1347. * @param {Object} scope (Optional) The execution scope (`this`) in which the specified function is executed.
  1348. */
  1349. forEach: supportsForEach ? function(array, fn, scope) {
  1350. return array.forEach(fn, scope);
  1351. } : function(array, fn, scope) {
  1352. var i = 0,
  1353. ln = array.length;
  1354. for (; i < ln; i++) {
  1355. fn.call(scope, array[i], i, array);
  1356. }
  1357. },
  1358. /**
  1359. * Get the index of the provided `item` in the given `array`, a supplement for the
  1360. * missing arrayPrototype.indexOf in Internet Explorer.
  1361. *
  1362. * @param {Array} array The array to check.
  1363. * @param {Object} item The item to look for.
  1364. * @param {Number} from (Optional) The index at which to begin the search.
  1365. * @return {Number} The index of item in the array (or -1 if it is not found).
  1366. */
  1367. indexOf: (supportsIndexOf) ? function(array, item, from) {
  1368. return array.indexOf(item, from);
  1369. } : function(array, item, from) {
  1370. var i, length = array.length;
  1371. for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
  1372. if (array[i] === item) {
  1373. return i;
  1374. }
  1375. }
  1376. return -1;
  1377. },
  1378. /**
  1379. * Checks whether or not the given `array` contains the specified `item`.
  1380. *
  1381. * @param {Array} array The array to check.
  1382. * @param {Object} item The item to look for.
  1383. * @return {Boolean} `true` if the array contains the item, `false` otherwise.
  1384. */
  1385. contains: supportsIndexOf ? function(array, item) {
  1386. return array.indexOf(item) !== -1;
  1387. } : function(array, item) {
  1388. var i, ln;
  1389. for (i = 0, ln = array.length; i < ln; i++) {
  1390. if (array[i] === item) {
  1391. return true;
  1392. }
  1393. }
  1394. return false;
  1395. },
  1396. /**
  1397. * Converts any iterable (numeric indices and a length property) into a true array.
  1398. *
  1399. * function test() {
  1400. * var args = Ext.Array.toArray(arguments),
  1401. * fromSecondToLastArgs = Ext.Array.toArray(arguments, 1);
  1402. *
  1403. * alert(args.join(' '));
  1404. * alert(fromSecondToLastArgs.join(' '));
  1405. * }
  1406. *
  1407. * test('just', 'testing', 'here'); // alerts 'just testing here';
  1408. * // alerts 'testing here';
  1409. *
  1410. * Ext.Array.toArray(document.getElementsByTagName('div')); // will convert the NodeList into an array
  1411. * Ext.Array.toArray('splitted'); // returns ['s', 'p', 'l', 'i', 't', 't', 'e', 'd']
  1412. * Ext.Array.toArray('splitted', 0, 3); // returns ['s', 'p', 'l', 'i']
  1413. *
  1414. * {@link Ext#toArray Ext.toArray} is alias for {@link Ext.Array#toArray Ext.Array.toArray}
  1415. *
  1416. * @param {Object} iterable the iterable object to be turned into a true Array.
  1417. * @param {Number} [start=0] (Optional) a zero-based index that specifies the start of extraction.
  1418. * @param {Number} [end=-1] (Optional) a zero-based index that specifies the end of extraction.
  1419. * @return {Array}
  1420. */
  1421. toArray: function(iterable, start, end){
  1422. if (!iterable || !iterable.length) {
  1423. return [];
  1424. }
  1425. if (typeof iterable === 'string') {
  1426. iterable = iterable.split('');
  1427. }
  1428. if (supportsSliceOnNodeList) {
  1429. return slice.call(iterable, start || 0, end || iterable.length);
  1430. }
  1431. var array = [],
  1432. i;
  1433. start = start || 0;
  1434. end = end ? ((end < 0) ? iterable.length + end : end) : iterable.length;
  1435. for (i = start; i < end; i++) {
  1436. array.push(iterable[i]);
  1437. }
  1438. return array;
  1439. },
  1440. /**
  1441. * Plucks the value of a property from each item in the Array. Example:
  1442. *
  1443. * Ext.Array.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className]
  1444. *
  1445. * @param {Array/NodeList} array The Array of items to pluck the value from.
  1446. * @param {String} propertyName The property name to pluck from each element.
  1447. * @return {Array} The value from each item in the Array.
  1448. */
  1449. pluck: function(array, propertyName) {
  1450. var ret = [],
  1451. i, ln, item;
  1452. for (i = 0, ln = array.length; i < ln; i++) {
  1453. item = array[i];
  1454. ret.push(item[propertyName]);
  1455. }
  1456. return ret;
  1457. },
  1458. /**
  1459. * Creates a new array with the results of calling a provided function on every element in this array.
  1460. *
  1461. * @param {Array} array
  1462. * @param {Function} fn Callback function for each item.
  1463. * @param {Object} scope Callback function scope.
  1464. * @return {Array} results
  1465. */
  1466. map: supportsMap ? function(array, fn, scope) {
  1467. return array.map(fn, scope);
  1468. } : function(array, fn, scope) {
  1469. var results = [],
  1470. i = 0,
  1471. len = array.length;
  1472. for (; i < len; i++) {
  1473. results[i] = fn.call(scope, array[i], i, array);
  1474. }
  1475. return results;
  1476. },
  1477. /**
  1478. * Executes the specified function for each array element until the function returns a falsy value.
  1479. * If such an item is found, the function will return `false` immediately.
  1480. * Otherwise, it will return `true`.
  1481. *
  1482. * @param {Array} array
  1483. * @param {Function} fn Callback function for each item.
  1484. * @param {Object} scope Callback function scope.
  1485. * @return {Boolean} `true` if no `false` value is returned by the callback function.
  1486. */
  1487. every: function(array, fn, scope) {
  1488. //<debug>
  1489. if (!fn) {
  1490. Ext.Error.raise('Ext.Array.every must have a callback function passed as second argument.');
  1491. }
  1492. //</debug>
  1493. if (supportsEvery) {
  1494. return array.every(fn, scope);
  1495. }
  1496. var i = 0,
  1497. ln = array.length;
  1498. for (; i < ln; ++i) {
  1499. if (!fn.call(scope, array[i], i, array)) {
  1500. return false;
  1501. }
  1502. }
  1503. return true;
  1504. },
  1505. /**
  1506. * Executes the specified function for each array element until the function returns a truthy value.
  1507. * If such an item is found, the function will return `true` immediately. Otherwise, it will return `false`.
  1508. *
  1509. * @param {Array} array
  1510. * @param {Function} fn Callback function for each item.
  1511. * @param {Object} scope Callback function scope.
  1512. * @return {Boolean} `true` if the callback function returns a truthy value.
  1513. */
  1514. some: function(array, fn, scope) {
  1515. //<debug>
  1516. if (!fn) {
  1517. Ext.Error.raise('Ext.Array.some must have a callback function passed as second argument.');
  1518. }
  1519. //</debug>
  1520. if (supportsSome) {
  1521. return array.some(fn, scope);
  1522. }
  1523. var i = 0,
  1524. ln = array.length;
  1525. for (; i < ln; ++i) {
  1526. if (fn.call(scope, array[i], i, array)) {
  1527. return true;
  1528. }
  1529. }
  1530. return false;
  1531. },
  1532. /**
  1533. * Filter through an array and remove empty item as defined in {@link Ext#isEmpty Ext.isEmpty}.
  1534. *
  1535. * See {@link Ext.Array#filter}
  1536. *
  1537. * @param {Array} array
  1538. * @return {Array} results
  1539. */
  1540. clean: function(array) {
  1541. var results = [],
  1542. i = 0,
  1543. ln = array.length,
  1544. item;
  1545. for (; i < ln; i++) {
  1546. item = array[i];
  1547. if (!Ext.isEmpty(item)) {
  1548. results.push(item);
  1549. }
  1550. }
  1551. return results;
  1552. },
  1553. /**
  1554. * Returns a new array with unique items.
  1555. *
  1556. * @param {Array} array
  1557. * @return {Array} results
  1558. */
  1559. unique: function(array) {
  1560. var clone = [],
  1561. i = 0,
  1562. ln = array.length,
  1563. item;
  1564. for (; i < ln; i++) {
  1565. item = array[i];
  1566. if (ExtArray.indexOf(clone, item) === -1) {
  1567. clone.push(item);
  1568. }
  1569. }
  1570. return clone;
  1571. },
  1572. /**
  1573. * Creates a new array with all of the elements of this array for which
  1574. * the provided filtering function returns `true`.
  1575. *
  1576. * @param {Array} array
  1577. * @param {Function} fn Callback function for each item.
  1578. * @param {Object} scope Callback function scope.
  1579. * @return {Array} results
  1580. */
  1581. filter: function(array, fn, scope) {
  1582. if (supportsFilter) {
  1583. return array.filter(fn, scope);
  1584. }
  1585. var results = [],
  1586. i = 0,
  1587. ln = array.length;
  1588. for (; i < ln; i++) {
  1589. if (fn.call(scope, array[i], i, array)) {
  1590. results.push(array[i]);
  1591. }
  1592. }
  1593. return results;
  1594. },
  1595. /**
  1596. * Converts a value to an array if it's not already an array; returns:
  1597. *
  1598. * - An empty array if given value is `undefined` or `null`
  1599. * - Itself if given value is already an array
  1600. * - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike)
  1601. * - An array with one item which is the given value, otherwise
  1602. *
  1603. * @param {Object} value The value to convert to an array if it's not already is an array.
  1604. * @param {Boolean} [newReference=false] (Optional) `true` to clone the given array and return a new reference if necessary.
  1605. * @return {Array} array
  1606. */
  1607. from: function(value, newReference) {
  1608. if (value === undefined || value === null) {
  1609. return [];
  1610. }
  1611. if (Ext.isArray(value)) {
  1612. return (newReference) ? slice.call(value) : value;
  1613. }
  1614. if (value && value.length !== undefined && typeof value !== 'string') {
  1615. return ExtArray.toArray(value);
  1616. }
  1617. return [value];
  1618. },
  1619. /**
  1620. * Removes the specified item from the array if it exists.
  1621. *
  1622. * @param {Array} array The array.
  1623. * @param {Object} item The item to remove.
  1624. * @return {Array} The passed array itself.
  1625. */
  1626. remove: function(array, item) {
  1627. var index = ExtArray.indexOf(array, item);
  1628. if (index !== -1) {
  1629. erase(array, index, 1);
  1630. }
  1631. return array;
  1632. },
  1633. /**
  1634. * Push an item into the array only if the array doesn't contain it yet.
  1635. *
  1636. * @param {Array} array The array.
  1637. * @param {Object} item The item to include.
  1638. */
  1639. include: function(array, item) {
  1640. if (!ExtArray.contains(array, item)) {
  1641. array.push(item);
  1642. }
  1643. },
  1644. /**
  1645. * Clone a flat array without referencing the previous one. Note that this is different
  1646. * from `Ext.clone` since it doesn't handle recursive cloning. It's simply a convenient, easy-to-remember method
  1647. * for `Array.prototype.slice.call(array)`.
  1648. *
  1649. * @param {Array} array The array
  1650. * @return {Array} The clone array
  1651. */
  1652. clone: function(array) {
  1653. return slice.call(array);
  1654. },
  1655. /**
  1656. * Merge multiple arrays into one with unique items.
  1657. *
  1658. * {@link Ext.Array#union} is alias for {@link Ext.Array#merge}
  1659. *
  1660. * @param {Array} array1
  1661. * @param {Array} array2
  1662. * @param {Array} etc
  1663. * @return {Array} merged
  1664. */
  1665. merge: function() {
  1666. var args = slice.call(arguments),
  1667. array = [],
  1668. i, ln;
  1669. for (i = 0, ln = args.length; i < ln; i++) {
  1670. array = array.concat(args[i]);
  1671. }
  1672. return ExtArray.unique(array);
  1673. },
  1674. /**
  1675. * Merge multiple arrays into one with unique items that exist in all of the arrays.
  1676. *
  1677. * @param {Array} array1
  1678. * @param {Array} array2
  1679. * @param {Array} etc
  1680. * @return {Array} intersect
  1681. */
  1682. intersect: function() {
  1683. var intersect = [],
  1684. arrays = slice.call(arguments),
  1685. i, j, k, minArray, array, x, y, ln, arraysLn, arrayLn;
  1686. if (!arrays.length) {
  1687. return intersect;
  1688. }
  1689. // Find the smallest array
  1690. for (i = x = 0,ln = arrays.length; i < ln,array = arrays[i]; i++) {
  1691. if (!minArray || array.length < minArray.length) {
  1692. minArray = array;
  1693. x = i;
  1694. }
  1695. }
  1696. minArray = ExtArray.unique(minArray);
  1697. erase(arrays, x, 1);
  1698. // Use the smallest unique'd array as the anchor loop. If the other array(s) do contain
  1699. // an item in the small array, we're likely to find it before reaching the end
  1700. // of the inner loop and can terminate the search early.
  1701. for (i = 0,ln = minArray.length; i < ln,x = minArray[i]; i++) {
  1702. var count = 0;
  1703. for (j = 0,arraysLn = arrays.length; j < arraysLn,array = arrays[j]; j++) {
  1704. for (k = 0,arrayLn = array.length; k < arrayLn,y = array[k]; k++) {
  1705. if (x === y) {
  1706. count++;
  1707. break;
  1708. }
  1709. }
  1710. }
  1711. if (count === arraysLn) {
  1712. intersect.push(x);
  1713. }
  1714. }
  1715. return intersect;
  1716. },
  1717. /**
  1718. * Perform a set difference A-B by subtracting all items in array B from array A.
  1719. *
  1720. * @param {Array} arrayA
  1721. * @param {Array} arrayB
  1722. * @return {Array} difference
  1723. */
  1724. difference: function(arrayA, arrayB) {
  1725. var clone = slice.call(arrayA),
  1726. ln = clone.length,
  1727. i, j, lnB;
  1728. for (i = 0,lnB = arrayB.length; i < lnB; i++) {
  1729. for (j = 0; j < ln; j++) {
  1730. if (clone[j] === arrayB[i]) {
  1731. erase(clone, j, 1);
  1732. j--;
  1733. ln--;
  1734. }
  1735. }
  1736. }
  1737. return clone;
  1738. },
  1739. /**
  1740. * Returns a shallow copy of a part of an array. This is equivalent to the native
  1741. * call `Array.prototype.slice.call(array, begin, end)`. This is often used when "array"
  1742. * is "arguments" since the arguments object does not supply a slice method but can
  1743. * be the context object to `Array.prototype.slice()`.
  1744. *
  1745. * @param {Array} array The array (or arguments object).
  1746. * @param {Number} begin The index at which to begin. Negative values are offsets from
  1747. * the end of the array.
  1748. * @param {Number} end The index at which to end. The copied items do not include
  1749. * end. Negative values are offsets from the end of the array. If end is omitted,
  1750. * all items up to the end of the array are copied.
  1751. * @return {Array} The copied piece of the array.
  1752. */
  1753. slice: function(array, begin, end) {
  1754. return slice.call(array, begin, end);
  1755. },
  1756. /**
  1757. * Sorts the elements of an Array.
  1758. * By default, this method sorts the elements alphabetically and ascending.
  1759. *
  1760. * @param {Array} array The array to sort.
  1761. * @param {Function} sortFn (optional) The comparison function.
  1762. * @return {Array} The sorted array.
  1763. */
  1764. sort: function(array, sortFn) {
  1765. if (supportsSort) {
  1766. if (sortFn) {
  1767. return array.sort(sortFn);
  1768. } else {
  1769. return array.sort();
  1770. }
  1771. }
  1772. var length = array.length,
  1773. i = 0,
  1774. comparison,
  1775. j, min, tmp;
  1776. for (; i < length; i++) {
  1777. min = i;
  1778. for (j = i + 1; j < length; j++) {
  1779. if (sortFn) {
  1780. comparison = sortFn(array[j], array[min]);
  1781. if (comparison < 0) {
  1782. min = j;
  1783. }
  1784. } else if (array[j] < array[min]) {
  1785. min = j;
  1786. }
  1787. }
  1788. if (min !== i) {
  1789. tmp = array[i];
  1790. array[i] = array[min];
  1791. array[min] = tmp;
  1792. }
  1793. }
  1794. return array;
  1795. },
  1796. /**
  1797. * Recursively flattens into 1-d Array. Injects Arrays inline.
  1798. *
  1799. * @param {Array} array The array to flatten
  1800. * @return {Array} The 1-d array.
  1801. */
  1802. flatten: function(array) {
  1803. var worker = [];
  1804. function rFlatten(a) {
  1805. var i, ln, v;
  1806. for (i = 0, ln = a.length; i < ln; i++) {
  1807. v = a[i];
  1808. if (Ext.isArray(v)) {
  1809. rFlatten(v);
  1810. } else {
  1811. worker.push(v);
  1812. }
  1813. }
  1814. return worker;
  1815. }
  1816. return rFlatten(array);
  1817. },
  1818. /**
  1819. * Returns the minimum value in the Array.
  1820. *
  1821. * @param {Array/NodeList} array The Array from which to select the minimum value.
  1822. * @param {Function} comparisonFn (optional) a function to perform the comparison which determines minimization.
  1823. * If omitted the "<" operator will be used.
  1824. * __Note:__ gt = 1; eq = 0; lt = -1
  1825. * @return {Object} minValue The minimum value.
  1826. */
  1827. min: function(array, comparisonFn) {
  1828. var min = array[0],
  1829. i, ln, item;
  1830. for (i = 0, ln = array.length; i < ln; i++) {
  1831. item = array[i];
  1832. if (comparisonFn) {
  1833. if (comparisonFn(min, item) === 1) {
  1834. min = item;
  1835. }
  1836. }
  1837. else {
  1838. if (item < min) {
  1839. min = item;
  1840. }
  1841. }
  1842. }
  1843. return min;
  1844. },
  1845. /**
  1846. * Returns the maximum value in the Array.
  1847. *
  1848. * @param {Array/NodeList} array The Array from which to select the maximum value.
  1849. * @param {Function} comparisonFn (optional) a function to perform the comparison which determines maximization.
  1850. * If omitted the ">" operator will be used.
  1851. * __Note:__ gt = 1; eq = 0; lt = -1
  1852. * @return {Object} maxValue The maximum value
  1853. */
  1854. max: function(array, comparisonFn) {
  1855. var max = array[0],
  1856. i, ln, item;
  1857. for (i = 0, ln = array.length; i < ln; i++) {
  1858. item = array[i];
  1859. if (comparisonFn) {
  1860. if (comparisonFn(max, item) === -1) {
  1861. max = item;
  1862. }
  1863. }
  1864. else {
  1865. if (item > max) {
  1866. max = item;
  1867. }
  1868. }
  1869. }
  1870. return max;
  1871. },
  1872. /**
  1873. * Calculates the mean of all items in the array.
  1874. *
  1875. * @param {Array} array The Array to calculate the mean value of.
  1876. * @return {Number} The mean.
  1877. */
  1878. mean: function(array) {
  1879. return array.length > 0 ? ExtArray.sum(array) / array.length : undefined;
  1880. },
  1881. /**
  1882. * Calculates the sum of all items in the given array.
  1883. *
  1884. * @param {Array} array The Array to calculate the sum value of.
  1885. * @return {Number} The sum.
  1886. */
  1887. sum: function(array) {
  1888. var sum = 0,
  1889. i, ln, item;
  1890. for (i = 0,ln = array.length; i < ln; i++) {
  1891. item = array[i];
  1892. sum += item;
  1893. }
  1894. return sum;
  1895. },
  1896. //<debug>
  1897. _replaceSim: replaceSim, // for unit testing
  1898. _spliceSim: spliceSim,
  1899. //</debug>
  1900. /**
  1901. * Removes items from an array. This is functionally equivalent to the splice method
  1902. * of Array, but works around bugs in IE8's splice method and does not copy the
  1903. * removed elements in order to return them (because very often they are ignored).
  1904. *
  1905. * @param {Array} array The Array on which to replace.
  1906. * @param {Number} index The index in the array at which to operate.
  1907. * @param {Number} removeCount The number of items to remove at index.
  1908. * @return {Array} The array passed.
  1909. * @method
  1910. */
  1911. erase: erase,
  1912. /**
  1913. * Inserts items in to an array.
  1914. *
  1915. * @param {Array} array The Array on which to replace.
  1916. * @param {Number} index The index in the array at which to operate.
  1917. * @param {Array} items The array of items to insert at index.
  1918. * @return {Array} The array passed.
  1919. */
  1920. insert: function (array, index, items) {
  1921. return replace(array, index, 0, items);
  1922. },
  1923. /**
  1924. * Replaces items in an array. This is functionally equivalent to the splice method
  1925. * of Array, but works around bugs in IE8's splice method and is often more convenient
  1926. * to call because it accepts an array of items to insert rather than use a variadic
  1927. * argument list.
  1928. *
  1929. * @param {Array} array The Array on which to replace.
  1930. * @param {Number} index The index in the array at which to operate.
  1931. * @param {Number} removeCount The number of items to remove at index (can be 0).
  1932. * @param {Array} insert (optional) An array of items to insert at index.
  1933. * @return {Array} The array passed.
  1934. * @method
  1935. */
  1936. replace: replace,
  1937. /**
  1938. * Replaces items in an array. This is equivalent to the splice method of Array, but
  1939. * works around bugs in IE8's splice method. The signature is exactly the same as the
  1940. * splice method except that the array is the first argument. All arguments following
  1941. * removeCount are inserted in the array at index.
  1942. *
  1943. * @param {Array} array The Array on which to replace.
  1944. * @param {Number} index The index in the array at which to operate.
  1945. * @param {Number} removeCount The number of items to remove at index (can be 0).
  1946. * @return {Array} An array containing the removed items.
  1947. * @method
  1948. */
  1949. splice: splice
  1950. };
  1951. /**
  1952. * @method
  1953. * @member Ext
  1954. * @alias Ext.Array#each
  1955. */
  1956. Ext.each = ExtArray.each;
  1957. /**
  1958. * @method
  1959. * @member Ext.Array
  1960. * @alias Ext.Array#merge
  1961. */
  1962. ExtArray.union = ExtArray.merge;
  1963. /**
  1964. * Old alias to {@link Ext.Array#min}
  1965. * @deprecated 4.0.0 Please use {@link Ext.Array#min} instead
  1966. * @method
  1967. * @member Ext
  1968. * @alias Ext.Array#min
  1969. */
  1970. Ext.min = ExtArray.min;
  1971. /**
  1972. * Old alias to {@link Ext.Array#max}
  1973. * @deprecated 4.0.0 Please use {@link Ext.Array#max} instead
  1974. * @method
  1975. * @member Ext
  1976. * @alias Ext.Array#max
  1977. */
  1978. Ext.max = ExtArray.max;
  1979. /**
  1980. * Old alias to {@link Ext.Array#sum}
  1981. * @deprecated 4.0.0 Please use {@link Ext.Array#sum} instead
  1982. * @method
  1983. * @member Ext
  1984. * @alias Ext.Array#sum
  1985. */
  1986. Ext.sum = ExtArray.sum;
  1987. /**
  1988. * Old alias to {@link Ext.Array#mean}
  1989. * @deprecated 4.0.0 Please use {@link Ext.Array#mean} instead
  1990. * @method
  1991. * @member Ext
  1992. * @alias Ext.Array#mean
  1993. */
  1994. Ext.mean = ExtArray.mean;
  1995. /**
  1996. * Old alias to {@link Ext.Array#flatten}
  1997. * @deprecated 4.0.0 Please use {@link Ext.Array#flatten} instead
  1998. * @method
  1999. * @member Ext
  2000. * @alias Ext.Array#flatten
  2001. */
  2002. Ext.flatten = ExtArray.flatten;
  2003. /**
  2004. * Old alias to {@link Ext.Array#clean}
  2005. * @deprecated 4.0.0 Please use {@link Ext.Array#clean} instead
  2006. * @method
  2007. * @member Ext
  2008. * @alias Ext.Array#clean
  2009. */
  2010. Ext.clean = ExtArray.clean;
  2011. /**
  2012. * Old alias to {@link Ext.Array#unique}
  2013. * @deprecated 4.0.0 Please use {@link Ext.Array#unique} instead
  2014. * @method
  2015. * @member Ext
  2016. * @alias Ext.Array#unique
  2017. */
  2018. Ext.unique = ExtArray.unique;
  2019. /**
  2020. * Old alias to {@link Ext.Array#pluck Ext.Array.pluck}
  2021. * @deprecated 4.0.0 Please use {@link Ext.Array#pluck Ext.Array.pluck} instead
  2022. * @method
  2023. * @member Ext
  2024. * @alias Ext.Array#pluck
  2025. */
  2026. Ext.pluck = ExtArray.pluck;
  2027. /**
  2028. * @method
  2029. * @member Ext
  2030. * @alias Ext.Array#toArray
  2031. */
  2032. Ext.toArray = function() {
  2033. return ExtArray.toArray.apply(ExtArray, arguments);
  2034. };
  2035. })();
  2036. //@tag foundation,core
  2037. //@define Ext.Number
  2038. //@require Ext.Array
  2039. /**
  2040. * @class Ext.Number
  2041. *
  2042. * A collection of useful static methods to deal with numbers
  2043. * @singleton
  2044. */
  2045. (function() {
  2046. var isToFixedBroken = (0.9).toFixed() !== '1';
  2047. Ext.Number = {
  2048. /**
  2049. * Checks whether or not the passed number is within a desired range. If the number is already within the
  2050. * range it is returned, otherwise the min or max value is returned depending on which side of the range is
  2051. * exceeded. Note that this method returns the constrained value but does not change the current number.
  2052. * @param {Number} number The number to check
  2053. * @param {Number} min The minimum number in the range
  2054. * @param {Number} max The maximum number in the range
  2055. * @return {Number} The constrained value if outside the range, otherwise the current value
  2056. */
  2057. constrain: function(number, min, max) {
  2058. number = parseFloat(number);
  2059. if (!isNaN(min)) {
  2060. number = Math.max(number, min);
  2061. }
  2062. if (!isNaN(max)) {
  2063. number = Math.min(number, max);
  2064. }
  2065. return number;
  2066. },
  2067. /**
  2068. * Snaps the passed number between stopping points based upon a passed increment value.
  2069. * @param {Number} value The unsnapped value.
  2070. * @param {Number} increment The increment by which the value must move.
  2071. * @param {Number} minValue The minimum value to which the returned value must be constrained. Overrides the increment..
  2072. * @param {Number} maxValue The maximum value to which the returned value must be constrained. Overrides the increment..
  2073. * @return {Number} The value of the nearest snap target.
  2074. */
  2075. snap : function(value, increment, minValue, maxValue) {
  2076. var newValue = value,
  2077. m;
  2078. if (!(increment && value)) {
  2079. return value;
  2080. }
  2081. m = value % increment;
  2082. if (m !== 0) {
  2083. newValue -= m;
  2084. if (m * 2 >= increment) {
  2085. newValue += increment;
  2086. } else if (m * 2 < -increment) {
  2087. newValue -= increment;
  2088. }
  2089. }
  2090. return Ext.Number.constrain(newValue, minValue, maxValue);
  2091. },
  2092. /**
  2093. * Formats a number using fixed-point notation
  2094. * @param {Number} value The number to format
  2095. * @param {Number} precision The number of digits to show after the decimal point
  2096. */
  2097. toFixed: function(value, precision) {
  2098. if (isToFixedBroken) {
  2099. precision = precision || 0;
  2100. var pow = Math.pow(10, precision);
  2101. return (Math.round(value * pow) / pow).toFixed(precision);
  2102. }
  2103. return value.toFixed(precision);
  2104. },
  2105. /**
  2106. * Validate that a value is numeric and convert it to a number if necessary. Returns the specified default value if
  2107. * it is not.
  2108. Ext.Number.from('1.23', 1); // returns 1.23
  2109. Ext.Number.from('abc', 1); // returns 1
  2110. * @param {Object} value
  2111. * @param {Number} defaultValue The value to return if the original value is non-numeric
  2112. * @return {Number} value, if numeric, defaultValue otherwise
  2113. */
  2114. from: function(value, defaultValue) {
  2115. if (isFinite(value)) {
  2116. value = parseFloat(value);
  2117. }
  2118. return !isNaN(value) ? value : defaultValue;
  2119. }
  2120. };
  2121. })();
  2122. /**
  2123. * This method is deprecated, please use {@link Ext.Number#from Ext.Number.from} instead
  2124. *
  2125. * @deprecated 4.0.0 Replaced by Ext.Number.from
  2126. * @member Ext
  2127. * @method num
  2128. */
  2129. Ext.num = function() {
  2130. return Ext.Number.from.apply(this, arguments);
  2131. };
  2132. //@tag foundation,core
  2133. //@define Ext.Object
  2134. //@require Ext.Number
  2135. /**
  2136. * @author Jacky Nguyen <jacky@sencha.com>
  2137. * @docauthor Jacky Nguyen <jacky@sencha.com>
  2138. * @class Ext.Object
  2139. *
  2140. * A collection of useful static methods to deal with objects.
  2141. *
  2142. * @singleton
  2143. */
  2144. (function() {
  2145. // The "constructor" for chain:
  2146. var TemplateClass = function(){};
  2147. var ExtObject = Ext.Object = {
  2148. /**
  2149. * Returns a new object with the given object as the prototype chain.
  2150. * @param {Object} object The prototype chain for the new object.
  2151. */
  2152. chain: ('create' in Object) ? function(object){
  2153. return Object.create(object);
  2154. } : function (object) {
  2155. TemplateClass.prototype = object;
  2156. var result = new TemplateClass();
  2157. TemplateClass.prototype = null;
  2158. return result;
  2159. },
  2160. /**
  2161. * Convert a `name` - `value` pair to an array of objects with support for nested structures; useful to construct
  2162. * query strings. For example:
  2163. *
  2164. * Non-recursive:
  2165. *
  2166. * var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);
  2167. *
  2168. * // objects then equals:
  2169. * [
  2170. * { name: 'hobbies', value: 'reading' },
  2171. * { name: 'hobbies', value: 'cooking' },
  2172. * { name: 'hobbies', value: 'swimming' }
  2173. * ]
  2174. *
  2175. * Recursive:
  2176. *
  2177. * var objects = Ext.Object.toQueryObjects('dateOfBirth', {
  2178. * day: 3,
  2179. * month: 8,
  2180. * year: 1987,
  2181. * extra: {
  2182. * hour: 4,
  2183. * minute: 30
  2184. * }
  2185. * }, true);
  2186. *
  2187. * // objects then equals:
  2188. * [
  2189. * { name: 'dateOfBirth[day]', value: 3 },
  2190. * { name: 'dateOfBirth[month]', value: 8 },
  2191. * { name: 'dateOfBirth[year]', value: 1987 },
  2192. * { name: 'dateOfBirth[extra][hour]', value: 4 },
  2193. * { name: 'dateOfBirth[extra][minute]', value: 30 }
  2194. * ]
  2195. *
  2196. * @param {String} name
  2197. * @param {Object} value
  2198. * @param {Boolean} [recursive=false] `true` to recursively encode any sub-objects.
  2199. * @return {Object[]} Array of objects with `name` and `value` fields.
  2200. */
  2201. toQueryObjects: function(name, value, recursive) {
  2202. var self = ExtObject.toQueryObjects,
  2203. objects = [],
  2204. i, ln;
  2205. if (Ext.isArray(value)) {
  2206. for (i = 0, ln = value.length; i < ln; i++) {
  2207. if (recursive) {
  2208. objects = objects.concat(self(name + '[' + i + ']', value[i], true));
  2209. }
  2210. else {
  2211. objects.push({
  2212. name: name,
  2213. value: value[i]
  2214. });
  2215. }
  2216. }
  2217. }
  2218. else if (Ext.isObject(value)) {
  2219. for (i in value) {
  2220. if (value.hasOwnProperty(i)) {
  2221. if (recursive) {
  2222. objects = objects.concat(self(name + '[' + i + ']', value[i], true));
  2223. }
  2224. else {
  2225. objects.push({
  2226. name: name,
  2227. value: value[i]
  2228. });
  2229. }
  2230. }
  2231. }
  2232. }
  2233. else {
  2234. objects.push({
  2235. name: name,
  2236. value: value
  2237. });
  2238. }
  2239. return objects;
  2240. },
  2241. /**
  2242. * Takes an object and converts it to an encoded query string.
  2243. *
  2244. * Non-recursive:
  2245. *
  2246. * Ext.Object.toQueryString({foo: 1, bar: 2}); // returns "foo=1&bar=2"
  2247. * Ext.Object.toQueryString({foo: null, bar: 2}); // returns "foo=&bar=2"
  2248. * Ext.Object.toQueryString({'some price': '$300'}); // returns "some%20price=%24300"
  2249. * Ext.Object.toQueryString({date: new Date(2011, 0, 1)}); // returns "date=%222011-01-01T00%3A00%3A00%22"
  2250. * Ext.Object.toQueryString({colors: ['red', 'green', 'blue']}); // returns "colors=red&colors=green&colors=blue"
  2251. *
  2252. * Recursive:
  2253. *
  2254. * Ext.Object.toQueryString({
  2255. * username: 'Jacky',
  2256. * dateOfBirth: {
  2257. * day: 1,
  2258. * month: 2,
  2259. * year: 1911
  2260. * },
  2261. * hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
  2262. * }, true);
  2263. *
  2264. * // returns the following string (broken down and url-decoded for ease of reading purpose):
  2265. * // username=Jacky
  2266. * // &dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911
  2267. * // &hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff
  2268. *
  2269. * @param {Object} object The object to encode.
  2270. * @param {Boolean} [recursive=false] Whether or not to interpret the object in recursive format.
  2271. * (PHP / Ruby on Rails servers and similar).
  2272. * @return {String} queryString
  2273. */
  2274. toQueryString: function(object, recursive) {
  2275. var paramObjects = [],
  2276. params = [],
  2277. i, j, ln, paramObject, value;
  2278. for (i in object) {
  2279. if (object.hasOwnProperty(i)) {
  2280. paramObjects = paramObjects.concat(ExtObject.toQueryObjects(i, object[i], recursive));
  2281. }
  2282. }
  2283. for (j = 0, ln = paramObjects.length; j < ln; j++) {
  2284. paramObject = paramObjects[j];
  2285. value = paramObject.value;
  2286. if (Ext.isEmpty(value)) {
  2287. value = '';
  2288. }
  2289. else if (Ext.isDate(value)) {
  2290. value = Ext.Date.toString(value);
  2291. }
  2292. params.push(encodeURIComponent(paramObject.name) + '=' + encodeURIComponent(String(value)));
  2293. }
  2294. return params.join('&');
  2295. },
  2296. /**
  2297. * Converts a query string back into an object.
  2298. *
  2299. * Non-recursive:
  2300. *
  2301. * Ext.Object.fromQueryString("foo=1&bar=2"); // returns {foo: 1, bar: 2}
  2302. * Ext.Object.fromQueryString("foo=&bar=2"); // returns {foo: null, bar: 2}
  2303. * Ext.Object.fromQueryString("some%20price=%24300"); // returns {'some price': '$300'}
  2304. * Ext.Object.fromQueryString("colors=red&colors=green&colors=blue"); // returns {colors: ['red', 'green', 'blue']}
  2305. *
  2306. * Recursive:
  2307. *
  2308. * Ext.Object.fromQueryString("username=Jacky&dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911&hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff", true);
  2309. *
  2310. * // returns
  2311. * {
  2312. * username: 'Jacky',
  2313. * dateOfBirth: {
  2314. * day: '1',
  2315. * month: '2',
  2316. * year: '1911'
  2317. * },
  2318. * hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
  2319. * }
  2320. *
  2321. * @param {String} queryString The query string to decode.
  2322. * @param {Boolean} [recursive=false] Whether or not to recursively decode the string. This format is supported by
  2323. * PHP / Ruby on Rails servers and similar.
  2324. * @return {Object}
  2325. */
  2326. fromQueryString: function(queryString, recursive) {
  2327. var parts = queryString.replace(/^\?/, '').split('&'),
  2328. object = {},
  2329. temp, components, name, value, i, ln,
  2330. part, j, subLn, matchedKeys, matchedName,
  2331. keys, key, nextKey;
  2332. for (i = 0, ln = parts.length; i < ln; i++) {
  2333. part = parts[i];
  2334. if (part.length > 0) {
  2335. components = part.split('=');
  2336. name = decodeURIComponent(components[0]);
  2337. value = (components[1] !== undefined) ? decodeURIComponent(components[1]) : '';
  2338. if (!recursive) {
  2339. if (object.hasOwnProperty(name)) {
  2340. if (!Ext.isArray(object[name])) {
  2341. object[name] = [object[name]];
  2342. }
  2343. object[name].push(value);
  2344. }
  2345. else {
  2346. object[name] = value;
  2347. }
  2348. }
  2349. else {
  2350. matchedKeys = name.match(/(\[):?([^\]]*)\]/g);
  2351. matchedName = name.match(/^([^\[]+)/);
  2352. //<debug error>
  2353. if (!matchedName) {
  2354. throw new Error('[Ext.Object.fromQueryString] Malformed query string given, failed parsing name from "' + part + '"');
  2355. }
  2356. //</debug>
  2357. name = matchedName[0];
  2358. keys = [];
  2359. if (matchedKeys === null) {
  2360. object[name] = value;
  2361. continue;
  2362. }
  2363. for (j = 0, subLn = matchedKeys.length; j < subLn; j++) {
  2364. key = matchedKeys[j];
  2365. key = (key.length === 2) ? '' : key.substring(1, key.length - 1);
  2366. keys.push(key);
  2367. }
  2368. keys.unshift(name);
  2369. temp = object;
  2370. for (j = 0, subLn = keys.length; j < subLn; j++) {
  2371. key = keys[j];
  2372. if (j === subLn - 1) {
  2373. if (Ext.isArray(temp) && key === '') {
  2374. temp.push(value);
  2375. }
  2376. else {
  2377. temp[key] = value;
  2378. }
  2379. }
  2380. else {
  2381. if (temp[key] === undefined || typeof temp[key] === 'string') {
  2382. nextKey = keys[j+1];
  2383. temp[key] = (Ext.isNumeric(nextKey) || nextKey === '') ? [] : {};
  2384. }
  2385. temp = temp[key];
  2386. }
  2387. }
  2388. }
  2389. }
  2390. }
  2391. return object;
  2392. },
  2393. /**
  2394. * Iterate through an object and invoke the given callback function for each iteration. The iteration can be stop
  2395. * by returning `false` in the callback function. For example:
  2396. *
  2397. * var person = {
  2398. * name: 'Jacky',
  2399. * hairColor: 'black',
  2400. * loves: ['food', 'sleeping', 'wife']
  2401. * };
  2402. *
  2403. * Ext.Object.each(person, function(key, value, myself) {
  2404. * console.log(key + ":" + value);
  2405. *
  2406. * if (key === 'hairColor') {
  2407. * return false; // stop the iteration
  2408. * }
  2409. * });
  2410. *
  2411. * @param {Object} object The object to iterate
  2412. * @param {Function} fn The callback function.
  2413. * @param {String} fn.key
  2414. * @param {Mixed} fn.value
  2415. * @param {Object} fn.object The object itself
  2416. * @param {Object} [scope] The execution scope (`this`) of the callback function
  2417. */
  2418. each: function(object, fn, scope) {
  2419. for (var property in object) {
  2420. if (object.hasOwnProperty(property)) {
  2421. if (fn.call(scope || object, property, object[property], object) === false) {
  2422. return;
  2423. }
  2424. }
  2425. }
  2426. },
  2427. /**
  2428. * Merges any number of objects recursively without referencing them or their children.
  2429. *
  2430. * var extjs = {
  2431. * companyName: 'Ext JS',
  2432. * products: ['Ext JS', 'Ext GWT', 'Ext Designer'],
  2433. * isSuperCool: true,
  2434. * office: {
  2435. * size: 2000,
  2436. * location: 'Palo Alto',
  2437. * isFun: true
  2438. * }
  2439. * };
  2440. *
  2441. * var newStuff = {
  2442. * companyName: 'Sencha Inc.',
  2443. * products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
  2444. * office: {
  2445. * size: 40000,
  2446. * location: 'Redwood City'
  2447. * }
  2448. * };
  2449. *
  2450. * var sencha = Ext.Object.merge({}, extjs, newStuff);
  2451. *
  2452. * // sencha then equals to
  2453. * {
  2454. * companyName: 'Sencha Inc.',
  2455. * products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
  2456. * isSuperCool: true
  2457. * office: {
  2458. * size: 40000,
  2459. * location: 'Redwood City'
  2460. * isFun: true
  2461. * }
  2462. * }
  2463. *
  2464. * @param {Object} source The first object into which to merge the others.
  2465. * @param {Object...} objs One or more objects to be merged into the first.
  2466. * @return {Object} The object that is created as a result of merging all the objects passed in.
  2467. */
  2468. merge: function(source) {
  2469. var i = 1,
  2470. ln = arguments.length,
  2471. mergeFn = ExtObject.merge,
  2472. cloneFn = Ext.clone,
  2473. object, key, value, sourceKey;
  2474. for (; i < ln; i++) {
  2475. object = arguments[i];
  2476. for (key in object) {
  2477. value = object[key];
  2478. if (value && value.constructor === Object) {
  2479. sourceKey = source[key];
  2480. if (sourceKey && sourceKey.constructor === Object) {
  2481. mergeFn(sourceKey, value);
  2482. }
  2483. else {
  2484. source[key] = cloneFn(value);
  2485. }
  2486. }
  2487. else {
  2488. source[key] = value;
  2489. }
  2490. }
  2491. }
  2492. return source;
  2493. },
  2494. /**
  2495. * @private
  2496. * @param source
  2497. */
  2498. mergeIf: function(source) {
  2499. var i = 1,
  2500. ln = arguments.length,
  2501. cloneFn = Ext.clone,
  2502. object, key, value;
  2503. for (; i < ln; i++) {
  2504. object = arguments[i];
  2505. for (key in object) {
  2506. if (!(key in source)) {
  2507. value = object[key];
  2508. if (value && value.constructor === Object) {
  2509. source[key] = cloneFn(value);
  2510. }
  2511. else {
  2512. source[key] = value;
  2513. }
  2514. }
  2515. }
  2516. }
  2517. return source;
  2518. },
  2519. /**
  2520. * Returns the first matching key corresponding to the given value.
  2521. * If no matching value is found, `null` is returned.
  2522. *
  2523. * var person = {
  2524. * name: 'Jacky',
  2525. * loves: 'food'
  2526. * };
  2527. *
  2528. * alert(Ext.Object.getKey(sencha, 'food')); // alerts 'loves'
  2529. *
  2530. * @param {Object} object
  2531. * @param {Object} value The value to find
  2532. */
  2533. getKey: function(object, value) {
  2534. for (var property in object) {
  2535. if (object.hasOwnProperty(property) && object[property] === value) {
  2536. return property;
  2537. }
  2538. }
  2539. return null;
  2540. },
  2541. /**
  2542. * Gets all values of the given object as an array.
  2543. *
  2544. * var values = Ext.Object.getValues({
  2545. * name: 'Jacky',
  2546. * loves: 'food'
  2547. * }); // ['Jacky', 'food']
  2548. *
  2549. * @param {Object} object
  2550. * @return {Array} An array of values from the object.
  2551. */
  2552. getValues: function(object) {
  2553. var values = [],
  2554. property;
  2555. for (property in object) {
  2556. if (object.hasOwnProperty(property)) {
  2557. values.push(object[property]);
  2558. }
  2559. }
  2560. return values;
  2561. },
  2562. /**
  2563. * Gets all keys of the given object as an array.
  2564. *
  2565. * var values = Ext.Object.getKeys({
  2566. * name: 'Jacky',
  2567. * loves: 'food'
  2568. * }); // ['name', 'loves']
  2569. *
  2570. * @param {Object} object
  2571. * @return {String[]} An array of keys from the object.
  2572. * @method
  2573. */
  2574. getKeys: ('keys' in Object) ? Object.keys : function(object) {
  2575. var keys = [],
  2576. property;
  2577. for (property in object) {
  2578. if (object.hasOwnProperty(property)) {
  2579. keys.push(property);
  2580. }
  2581. }
  2582. return keys;
  2583. },
  2584. /**
  2585. * Gets the total number of this object's own properties.
  2586. *
  2587. * var size = Ext.Object.getSize({
  2588. * name: 'Jacky',
  2589. * loves: 'food'
  2590. * }); // size equals 2
  2591. *
  2592. * @param {Object} object
  2593. * @return {Number} size
  2594. */
  2595. getSize: function(object) {
  2596. var size = 0,
  2597. property;
  2598. for (property in object) {
  2599. if (object.hasOwnProperty(property)) {
  2600. size++;
  2601. }
  2602. }
  2603. return size;
  2604. },
  2605. /**
  2606. * @private
  2607. */
  2608. classify: function(object) {
  2609. var objectProperties = [],
  2610. arrayProperties = [],
  2611. propertyClassesMap = {},
  2612. objectClass = function() {
  2613. var i = 0,
  2614. ln = objectProperties.length,
  2615. property;
  2616. for (; i < ln; i++) {
  2617. property = objectProperties[i];
  2618. this[property] = new propertyClassesMap[property];
  2619. }
  2620. ln = arrayProperties.length;
  2621. for (i = 0; i < ln; i++) {
  2622. property = arrayProperties[i];
  2623. this[property] = object[property].slice();
  2624. }
  2625. },
  2626. key, value, constructor;
  2627. for (key in object) {
  2628. if (object.hasOwnProperty(key)) {
  2629. value = object[key];
  2630. if (value) {
  2631. constructor = value.constructor;
  2632. if (constructor === Object) {
  2633. objectProperties.push(key);
  2634. propertyClassesMap[key] = ExtObject.classify(value);
  2635. }
  2636. else if (constructor === Array) {
  2637. arrayProperties.push(key);
  2638. }
  2639. }
  2640. }
  2641. }
  2642. objectClass.prototype = object;
  2643. return objectClass;
  2644. },
  2645. defineProperty: ('defineProperty' in Object) ? Object.defineProperty : function(object, name, descriptor) {
  2646. if (descriptor.get) {
  2647. object.__defineGetter__(name, descriptor.get);
  2648. }
  2649. if (descriptor.set) {
  2650. object.__defineSetter__(name, descriptor.set);
  2651. }
  2652. }
  2653. };
  2654. /**
  2655. * A convenient alias method for {@link Ext.Object#merge}.
  2656. *
  2657. * @member Ext
  2658. * @method merge
  2659. */
  2660. Ext.merge = Ext.Object.merge;
  2661. /**
  2662. * @private
  2663. */
  2664. Ext.mergeIf = Ext.Object.mergeIf;
  2665. /**
  2666. * A convenient alias method for {@link Ext.Object#toQueryString}.
  2667. *
  2668. * @member Ext
  2669. * @method urlEncode
  2670. * @deprecated 4.0.0 Please use `{@link Ext.Object#toQueryString Ext.Object.toQueryString}` instead
  2671. */
  2672. Ext.urlEncode = function() {
  2673. var args = Ext.Array.from(arguments),
  2674. prefix = '';
  2675. // Support for the old `pre` argument
  2676. if ((typeof args[1] === 'string')) {
  2677. prefix = args[1] + '&';
  2678. args[1] = false;
  2679. }
  2680. return prefix + ExtObject.toQueryString.apply(ExtObject, args);
  2681. };
  2682. /**
  2683. * A convenient alias method for {@link Ext.Object#fromQueryString}.
  2684. *
  2685. * @member Ext
  2686. * @method urlDecode
  2687. * @deprecated 4.0.0 Please use {@link Ext.Object#fromQueryString Ext.Object.fromQueryString} instead
  2688. */
  2689. Ext.urlDecode = function() {
  2690. return ExtObject.fromQueryString.apply(ExtObject, arguments);
  2691. };
  2692. })();
  2693. //@tag foundation,core
  2694. //@define Ext.Function
  2695. //@require Ext.Object
  2696. /**
  2697. * @class Ext.Function
  2698. *
  2699. * A collection of useful static methods to deal with function callbacks.
  2700. * @singleton
  2701. * @alternateClassName Ext.util.Functions
  2702. */
  2703. Ext.Function = {
  2704. /**
  2705. * A very commonly used method throughout the framework. It acts as a wrapper around another method
  2706. * which originally accepts 2 arguments for `name` and `value`.
  2707. * The wrapped function then allows "flexible" value setting of either:
  2708. *
  2709. * - `name` and `value` as 2 arguments
  2710. * - one single object argument with multiple key - value pairs
  2711. *
  2712. * For example:
  2713. *
  2714. * var setValue = Ext.Function.flexSetter(function(name, value) {
  2715. * this[name] = value;
  2716. * });
  2717. *
  2718. * // Afterwards
  2719. * // Setting a single name - value
  2720. * setValue('name1', 'value1');
  2721. *
  2722. * // Settings multiple name - value pairs
  2723. * setValue({
  2724. * name1: 'value1',
  2725. * name2: 'value2',
  2726. * name3: 'value3'
  2727. * });
  2728. *
  2729. * @param {Function} setter
  2730. * @return {Function} flexSetter
  2731. */
  2732. flexSetter: function(fn) {
  2733. return function(a, b) {
  2734. var k, i;
  2735. if (a === null) {
  2736. return this;
  2737. }
  2738. if (typeof a !== 'string') {
  2739. for (k in a) {
  2740. if (a.hasOwnProperty(k)) {
  2741. fn.call(this, k, a[k]);
  2742. }
  2743. }
  2744. if (Ext.enumerables) {
  2745. for (i = Ext.enumerables.length; i--;) {
  2746. k = Ext.enumerables[i];
  2747. if (a.hasOwnProperty(k)) {
  2748. fn.call(this, k, a[k]);
  2749. }
  2750. }
  2751. }
  2752. } else {
  2753. fn.call(this, a, b);
  2754. }
  2755. return this;
  2756. };
  2757. },
  2758. /**
  2759. * Create a new function from the provided `fn`, change `this` to the provided scope, optionally
  2760. * overrides arguments for the call. Defaults to the arguments passed by the caller.
  2761. *
  2762. * {@link Ext#bind Ext.bind} is alias for {@link Ext.Function#bind Ext.Function.bind}
  2763. *
  2764. * @param {Function} fn The function to delegate.
  2765. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
  2766. * **If omitted, defaults to the browser window.**
  2767. * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
  2768. * @param {Boolean/Number} appendArgs (optional) if `true` args are appended to call args instead of overriding,
  2769. * if a number the args are inserted at the specified position.
  2770. * @return {Function} The new function.
  2771. */
  2772. bind: function(fn, scope, args, appendArgs) {
  2773. if (arguments.length === 2) {
  2774. return function() {
  2775. return fn.apply(scope, arguments);
  2776. }
  2777. }
  2778. var method = fn,
  2779. slice = Array.prototype.slice;
  2780. return function() {
  2781. var callArgs = args || arguments;
  2782. if (appendArgs === true) {
  2783. callArgs = slice.call(arguments, 0);
  2784. callArgs = callArgs.concat(args);
  2785. }
  2786. else if (typeof appendArgs == 'number') {
  2787. callArgs = slice.call(arguments, 0); // copy arguments first
  2788. Ext.Array.insert(callArgs, appendArgs, args);
  2789. }
  2790. return method.apply(scope || window, callArgs);
  2791. };
  2792. },
  2793. /**
  2794. * Create a new function from the provided `fn`, the arguments of which are pre-set to `args`.
  2795. * New arguments passed to the newly created callback when it's invoked are appended after the pre-set ones.
  2796. * This is especially useful when creating callbacks.
  2797. *
  2798. * For example:
  2799. *
  2800. * var originalFunction = function(){
  2801. * alert(Ext.Array.from(arguments).join(' '));
  2802. * };
  2803. *
  2804. * var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']);
  2805. *
  2806. * callback(); // alerts 'Hello World'
  2807. * callback('by Me'); // alerts 'Hello World by Me'
  2808. *
  2809. * {@link Ext#pass Ext.pass} is alias for {@link Ext.Function#pass Ext.Function.pass}
  2810. *
  2811. * @param {Function} fn The original function.
  2812. * @param {Array} args The arguments to pass to new callback.
  2813. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
  2814. * @return {Function} The new callback function.
  2815. */
  2816. pass: function(fn, args, scope) {
  2817. if (!Ext.isArray(args)) {
  2818. args = Ext.Array.clone(args);
  2819. }
  2820. return function() {
  2821. args.push.apply(args, arguments);
  2822. return fn.apply(scope || this, args);
  2823. };
  2824. },
  2825. /**
  2826. * Create an alias to the provided method property with name `methodName` of `object`.
  2827. * Note that the execution scope will still be bound to the provided `object` itself.
  2828. *
  2829. * @param {Object/Function} object
  2830. * @param {String} methodName
  2831. * @return {Function} aliasFn
  2832. */
  2833. alias: function(object, methodName) {
  2834. return function() {
  2835. return object[methodName].apply(object, arguments);
  2836. };
  2837. },
  2838. /**
  2839. * Create a "clone" of the provided method. The returned method will call the given
  2840. * method passing along all arguments and the "this" pointer and return its result.
  2841. *
  2842. * @param {Function} method
  2843. * @return {Function} cloneFn
  2844. */
  2845. clone: function(method) {
  2846. return function() {
  2847. return method.apply(this, arguments);
  2848. };
  2849. },
  2850. /**
  2851. * Creates an interceptor function. The passed function is called before the original one. If it returns false,
  2852. * the original one is not called. The resulting function returns the results of the original function.
  2853. * The passed function is called with the parameters of the original function. Example usage:
  2854. *
  2855. * var sayHi = function(name){
  2856. * alert('Hi, ' + name);
  2857. * };
  2858. *
  2859. * sayHi('Fred'); // alerts "Hi, Fred"
  2860. *
  2861. * // create a new function that validates input without
  2862. * // directly modifying the original function:
  2863. * var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){
  2864. * return name === 'Brian';
  2865. * });
  2866. *
  2867. * sayHiToFriend('Fred'); // no alert
  2868. * sayHiToFriend('Brian'); // alerts "Hi, Brian"
  2869. *
  2870. * @param {Function} origFn The original function.
  2871. * @param {Function} newFn The function to call before the original.
  2872. * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
  2873. * **If omitted, defaults to the scope in which the original function is called or the browser window.**
  2874. * @param {Object} [returnValue=null] (optional) The value to return if the passed function return `false`.
  2875. * @return {Function} The new function.
  2876. */
  2877. createInterceptor: function(origFn, newFn, scope, returnValue) {
  2878. var method = origFn;
  2879. if (!Ext.isFunction(newFn)) {
  2880. return origFn;
  2881. }
  2882. else {
  2883. return function() {
  2884. var me = this,
  2885. args = arguments;
  2886. newFn.target = me;
  2887. newFn.method = origFn;
  2888. return (newFn.apply(scope || me || window, args) !== false) ? origFn.apply(me || window, args) : returnValue || null;
  2889. };
  2890. }
  2891. },
  2892. /**
  2893. * Creates a delegate (callback) which, when called, executes after a specific delay.
  2894. *
  2895. * @param {Function} fn The function which will be called on a delay when the returned function is called.
  2896. * Optionally, a replacement (or additional) argument list may be specified.
  2897. * @param {Number} delay The number of milliseconds to defer execution by whenever called.
  2898. * @param {Object} scope (optional) The scope (`this` reference) used by the function at execution time.
  2899. * @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)
  2900. * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
  2901. * if a number the args are inserted at the specified position.
  2902. * @return {Function} A function which, when called, executes the original function after the specified delay.
  2903. */
  2904. createDelayed: function(fn, delay, scope, args, appendArgs) {
  2905. if (scope || args) {
  2906. fn = Ext.Function.bind(fn, scope, args, appendArgs);
  2907. }
  2908. return function() {
  2909. var me = this,
  2910. args = Array.prototype.slice.call(arguments);
  2911. setTimeout(function() {
  2912. fn.apply(me, args);
  2913. }, delay);
  2914. }
  2915. },
  2916. /**
  2917. * Calls this function after the number of milliseconds specified, optionally in a specific scope. Example usage:
  2918. *
  2919. * var sayHi = function(name){
  2920. * alert('Hi, ' + name);
  2921. * };
  2922. *
  2923. * // executes immediately:
  2924. * sayHi('Fred');
  2925. *
  2926. * // executes after 2 seconds:
  2927. * Ext.Function.defer(sayHi, 2000, this, ['Fred']);
  2928. *
  2929. * // this syntax is sometimes useful for deferring
  2930. * // execution of an anonymous function:
  2931. * Ext.Function.defer(function(){
  2932. * alert('Anonymous');
  2933. * }, 100);
  2934. *
  2935. * {@link Ext#defer Ext.defer} is alias for {@link Ext.Function#defer Ext.Function.defer}
  2936. *
  2937. * @param {Function} fn The function to defer.
  2938. * @param {Number} millis The number of milliseconds for the `setTimeout()` call.
  2939. * If less than or equal to 0 the function is executed immediately.
  2940. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
  2941. * If omitted, defaults to the browser window.
  2942. * @param {Array} args (optional) Overrides arguments for the call. Defaults to the arguments passed by the caller.
  2943. * @param {Boolean/Number} appendArgs (optional) if `true`, args are appended to call args instead of overriding,
  2944. * if a number the args are inserted at the specified position.
  2945. * @return {Number} The timeout id that can be used with `clearTimeout()`.
  2946. */
  2947. defer: function(fn, millis, scope, args, appendArgs) {
  2948. fn = Ext.Function.bind(fn, scope, args, appendArgs);
  2949. if (millis > 0) {
  2950. return setTimeout(fn, millis);
  2951. }
  2952. fn();
  2953. return 0;
  2954. },
  2955. /**
  2956. * Create a combined function call sequence of the original function + the passed function.
  2957. * The resulting function returns the results of the original function.
  2958. * The passed function is called with the parameters of the original function. Example usage:
  2959. *
  2960. * var sayHi = function(name){
  2961. * alert('Hi, ' + name);
  2962. * };
  2963. *
  2964. * sayHi('Fred'); // alerts "Hi, Fred"
  2965. *
  2966. * var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){
  2967. * alert('Bye, ' + name);
  2968. * });
  2969. *
  2970. * sayGoodbye('Fred'); // both alerts show
  2971. *
  2972. * @param {Function} originalFn The original function.
  2973. * @param {Function} newFn The function to sequence.
  2974. * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
  2975. * If omitted, defaults to the scope in which the original function is called or the browser window.
  2976. * @return {Function} The new function.
  2977. */
  2978. createSequence: function(originalFn, newFn, scope) {
  2979. if (!newFn) {
  2980. return originalFn;
  2981. }
  2982. else {
  2983. return function() {
  2984. var result = originalFn.apply(this, arguments);
  2985. newFn.apply(scope || this, arguments);
  2986. return result;
  2987. };
  2988. }
  2989. },
  2990. /**
  2991. * Creates a delegate function, optionally with a bound scope which, when called, buffers
  2992. * the execution of the passed function for the configured number of milliseconds.
  2993. * If called again within that period, the impending invocation will be canceled, and the
  2994. * timeout period will begin again.
  2995. *
  2996. * @param {Function} fn The function to invoke on a buffered timer.
  2997. * @param {Number} buffer The number of milliseconds by which to buffer the invocation of the
  2998. * function.
  2999. * @param {Object} scope (optional) The scope (`this` reference) in which
  3000. * the passed function is executed. If omitted, defaults to the scope specified by the caller.
  3001. * @param {Array} args (optional) Override arguments for the call. Defaults to the arguments
  3002. * passed by the caller.
  3003. * @return {Function} A function which invokes the passed function after buffering for the specified time.
  3004. */
  3005. createBuffered: function(fn, buffer, scope, args) {
  3006. var timerId;
  3007. return function() {
  3008. var callArgs = args || Array.prototype.slice.call(arguments, 0),
  3009. me = scope || this;
  3010. if (timerId) {
  3011. clearTimeout(timerId);
  3012. }
  3013. timerId = setTimeout(function(){
  3014. fn.apply(me, callArgs);
  3015. }, buffer);
  3016. };
  3017. },
  3018. /**
  3019. * Creates a throttled version of the passed function which, when called repeatedly and
  3020. * rapidly, invokes the passed function only after a certain interval has elapsed since the
  3021. * previous invocation.
  3022. *
  3023. * This is useful for wrapping functions which may be called repeatedly, such as
  3024. * a handler of a mouse move event when the processing is expensive.
  3025. *
  3026. * @param {Function} fn The function to execute at a regular time interval.
  3027. * @param {Number} interval The interval, in milliseconds, on which the passed function is executed.
  3028. * @param {Object} scope (optional) The scope (`this` reference) in which
  3029. * the passed function is executed. If omitted, defaults to the scope specified by the caller.
  3030. * @return {Function} A function which invokes the passed function at the specified interval.
  3031. */
  3032. createThrottled: function(fn, interval, scope) {
  3033. var lastCallTime, elapsed, lastArgs, timer, execute = function() {
  3034. fn.apply(scope || this, lastArgs);
  3035. lastCallTime = new Date().getTime();
  3036. };
  3037. return function() {
  3038. elapsed = new Date().getTime() - lastCallTime;
  3039. lastArgs = arguments;
  3040. clearTimeout(timer);
  3041. if (!lastCallTime || (elapsed >= interval)) {
  3042. execute();
  3043. } else {
  3044. timer = setTimeout(execute, interval - elapsed);
  3045. }
  3046. };
  3047. },
  3048. interceptBefore: function(object, methodName, fn) {
  3049. var method = object[methodName] || Ext.emptyFn;
  3050. return object[methodName] = function() {
  3051. var ret = fn.apply(this, arguments);
  3052. method.apply(this, arguments);
  3053. return ret;
  3054. };
  3055. },
  3056. interceptAfter: function(object, methodName, fn) {
  3057. var method = object[methodName] || Ext.emptyFn;
  3058. return object[methodName] = function() {
  3059. method.apply(this, arguments);
  3060. return fn.apply(this, arguments);
  3061. };
  3062. }
  3063. };
  3064. /**
  3065. * @method
  3066. * @member Ext
  3067. * @alias Ext.Function#defer
  3068. */
  3069. Ext.defer = Ext.Function.alias(Ext.Function, 'defer');
  3070. /**
  3071. * @method
  3072. * @member Ext
  3073. * @alias Ext.Function#pass
  3074. */
  3075. Ext.pass = Ext.Function.alias(Ext.Function, 'pass');
  3076. /**
  3077. * @method
  3078. * @member Ext
  3079. * @alias Ext.Function#bind
  3080. */
  3081. Ext.bind = Ext.Function.alias(Ext.Function, 'bind');
  3082. //@tag foundation,core
  3083. //@define Ext.JSON
  3084. //@require Ext.Function
  3085. /**
  3086. * @class Ext.JSON
  3087. * Modified version of Douglas Crockford's json.js that doesn't
  3088. * mess with the Object prototype.
  3089. * [http://www.json.org/js.html](http://www.json.org/js.html)
  3090. * @singleton
  3091. */
  3092. Ext.JSON = new(function() {
  3093. var useHasOwn = !! {}.hasOwnProperty,
  3094. isNative = function() {
  3095. var useNative = null;
  3096. return function() {
  3097. if (useNative === null) {
  3098. useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]';
  3099. }
  3100. return useNative;
  3101. };
  3102. }(),
  3103. pad = function(n) {
  3104. return n < 10 ? "0" + n : n;
  3105. },
  3106. doDecode = function(json) {
  3107. return eval("(" + json + ')');
  3108. },
  3109. doEncode = function(o) {
  3110. if (!Ext.isDefined(o) || o === null) {
  3111. return "null";
  3112. } else if (Ext.isArray(o)) {
  3113. return encodeArray(o);
  3114. } else if (Ext.isDate(o)) {
  3115. return Ext.JSON.encodeDate(o);
  3116. } else if (Ext.isString(o)) {
  3117. return encodeString(o);
  3118. } else if (typeof o == "number") {
  3119. //don't use isNumber here, since finite checks happen inside isNumber
  3120. return isFinite(o) ? String(o) : "null";
  3121. } else if (Ext.isBoolean(o)) {
  3122. return String(o);
  3123. } else if (Ext.isObject(o)) {
  3124. return encodeObject(o);
  3125. } else if (typeof o === "function") {
  3126. return "null";
  3127. }
  3128. return 'undefined';
  3129. },
  3130. m = {
  3131. "\b": '\\b',
  3132. "\t": '\\t',
  3133. "\n": '\\n',
  3134. "\f": '\\f',
  3135. "\r": '\\r',
  3136. '"': '\\"',
  3137. "\\": '\\\\',
  3138. '\x0b': '\\u000b' //ie doesn't handle \v
  3139. },
  3140. charToReplace = /[\\\"\x00-\x1f\x7f-\uffff]/g,
  3141. encodeString = function(s) {
  3142. return '"' + s.replace(charToReplace, function(a) {
  3143. var c = m[a];
  3144. return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
  3145. }) + '"';
  3146. },
  3147. encodeArray = function(o) {
  3148. var a = ["[", ""],
  3149. // Note empty string in case there are no serializable members.
  3150. len = o.length,
  3151. i;
  3152. for (i = 0; i < len; i += 1) {
  3153. a.push(doEncode(o[i]), ',');
  3154. }
  3155. // Overwrite trailing comma (or empty string)
  3156. a[a.length - 1] = ']';
  3157. return a.join("");
  3158. },
  3159. encodeObject = function(o) {
  3160. var a = ["{", ""],
  3161. // Note empty string in case there are no serializable members.
  3162. i;
  3163. for (i in o) {
  3164. if (!useHasOwn || o.hasOwnProperty(i)) {
  3165. a.push(doEncode(i), ":", doEncode(o[i]), ',');
  3166. }
  3167. }
  3168. // Overwrite trailing comma (or empty string)
  3169. a[a.length - 1] = '}';
  3170. return a.join("");
  3171. };
  3172. /**
  3173. * Encodes a Date. This returns the actual string which is inserted into the JSON string as the literal expression.
  3174. * __The returned value includes enclosing double quotation marks.__
  3175. *
  3176. * The default return format is "yyyy-mm-ddThh:mm:ss".
  3177. *
  3178. * To override this:
  3179. *
  3180. * Ext.JSON.encodeDate = function(d) {
  3181. * return Ext.Date.format(d, '"Y-m-d"');
  3182. * };
  3183. *
  3184. * @param {Date} d The Date to encode.
  3185. * @return {String} The string literal to use in a JSON string.
  3186. */
  3187. this.encodeDate = function(o) {
  3188. return '"' + o.getFullYear() + "-"
  3189. + pad(o.getMonth() + 1) + "-"
  3190. + pad(o.getDate()) + "T"
  3191. + pad(o.getHours()) + ":"
  3192. + pad(o.getMinutes()) + ":"
  3193. + pad(o.getSeconds()) + '"';
  3194. };
  3195. /**
  3196. * Encodes an Object, Array or other value.
  3197. * @param {Object} o The variable to encode.
  3198. * @return {String} The JSON string.
  3199. * @method
  3200. */
  3201. this.encode = function() {
  3202. var ec;
  3203. return function(o) {
  3204. if (!ec) {
  3205. // setup encoding function on first access
  3206. ec = isNative() ? JSON.stringify : doEncode;
  3207. }
  3208. return ec(o);
  3209. };
  3210. }();
  3211. /**
  3212. * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws a Error unless the safe option is set.
  3213. * @param {String} json The JSON string.
  3214. * @param {Boolean} safe (optional) Whether to return `null` or throw an exception if the JSON is invalid.
  3215. * @return {Object/null} The resulting object.
  3216. * @method
  3217. */
  3218. this.decode = function() {
  3219. var dc;
  3220. return function(json, safe) {
  3221. if (!dc) {
  3222. // setup decoding function on first access
  3223. dc = isNative() ? JSON.parse : doDecode;
  3224. }
  3225. try {
  3226. return dc(json);
  3227. } catch (e) {
  3228. if (safe === true) {
  3229. return null;
  3230. }
  3231. Ext.Error.raise({
  3232. sourceClass: "Ext.JSON",
  3233. sourceMethod: "decode",
  3234. msg: "You're trying to decode an invalid JSON String: " + json
  3235. });
  3236. }
  3237. };
  3238. }();
  3239. })();
  3240. /**
  3241. * Shorthand for {@link Ext.JSON#encode}.
  3242. * @member Ext
  3243. * @method encode
  3244. * @alias Ext.JSON#encode
  3245. */
  3246. Ext.encode = Ext.JSON.encode;
  3247. /**
  3248. * Shorthand for {@link Ext.JSON#decode}.
  3249. * @member Ext
  3250. * @method decode
  3251. * @alias Ext.JSON#decode
  3252. */
  3253. Ext.decode = Ext.JSON.decode;
  3254. //@tag foundation,core
  3255. //@define Ext.Error
  3256. //@require Ext.JSON
  3257. Ext.Error = {
  3258. raise: function(object) {
  3259. throw new Error(object.msg);
  3260. }
  3261. };
  3262. //@tag foundation,core
  3263. //@define Ext.Date
  3264. //@require Ext.Error
  3265. /**
  3266. *
  3267. */
  3268. Ext.Date = {
  3269. /** @ignore */
  3270. now: Date.now,
  3271. /**
  3272. * @private
  3273. * Private for now
  3274. */
  3275. toString: function(date) {
  3276. if (!date) {
  3277. date = new Date();
  3278. }
  3279. var pad = Ext.String.leftPad;
  3280. return date.getFullYear() + "-"
  3281. + pad(date.getMonth() + 1, 2, '0') + "-"
  3282. + pad(date.getDate(), 2, '0') + "T"
  3283. + pad(date.getHours(), 2, '0') + ":"
  3284. + pad(date.getMinutes(), 2, '0') + ":"
  3285. + pad(date.getSeconds(), 2, '0');
  3286. }
  3287. };
  3288. //@tag foundation,core
  3289. //@define Ext.Base
  3290. //@require Ext.Date
  3291. /**
  3292. * @class Ext.Base
  3293. *
  3294. * @author Jacky Nguyen <jacky@sencha.com>
  3295. * @aside guide class_system
  3296. * @aside video class-system
  3297. *
  3298. * The root of all classes created with {@link Ext#define}.
  3299. *
  3300. * Ext.Base is the building block of all Ext classes. All classes in Ext inherit from Ext.Base. All prototype and static
  3301. * members of this class are inherited by all other classes.
  3302. *
  3303. * See the [Class System Guide](#!/guide/class_system) for more.
  3304. *
  3305. */
  3306. (function(flexSetter) {
  3307. var noArgs = [],
  3308. Base = function(){};
  3309. // These static properties will be copied to every newly created class with {@link Ext#define}
  3310. Ext.apply(Base, {
  3311. $className: 'Ext.Base',
  3312. $isClass: true,
  3313. /**
  3314. * Create a new instance of this Class.
  3315. *
  3316. * Ext.define('My.cool.Class', {
  3317. * // ...
  3318. * });
  3319. *
  3320. * My.cool.Class.create({
  3321. * someConfig: true
  3322. * });
  3323. *
  3324. * All parameters are passed to the constructor of the class.
  3325. *
  3326. * @return {Object} the created instance.
  3327. * @static
  3328. * @inheritable
  3329. */
  3330. create: function() {
  3331. return Ext.create.apply(Ext, [this].concat(Array.prototype.slice.call(arguments, 0)));
  3332. },
  3333. /**
  3334. * @private
  3335. * @static
  3336. * @inheritable
  3337. */
  3338. extend: function(parent) {
  3339. var parentPrototype = parent.prototype,
  3340. prototype, i, ln, name, statics;
  3341. prototype = this.prototype = Ext.Object.chain(parentPrototype);
  3342. prototype.self = this;
  3343. this.superclass = prototype.superclass = parentPrototype;
  3344. if (!parent.$isClass) {
  3345. Ext.apply(prototype, Ext.Base.prototype);
  3346. prototype.constructor = function() {
  3347. parentPrototype.constructor.apply(this, arguments);
  3348. };
  3349. }
  3350. //<feature classSystem.inheritableStatics>
  3351. // Statics inheritance
  3352. statics = parentPrototype.$inheritableStatics;
  3353. if (statics) {
  3354. for (i = 0,ln = statics.length; i < ln; i++) {
  3355. name = statics[i];
  3356. if (!this.hasOwnProperty(name)) {
  3357. this[name] = parent[name];
  3358. }
  3359. }
  3360. }
  3361. //</feature>
  3362. if (parent.$onExtended) {
  3363. this.$onExtended = parent.$onExtended.slice();
  3364. }
  3365. //<feature classSystem.config>
  3366. prototype.config = prototype.defaultConfig = new prototype.configClass;
  3367. prototype.initConfigList = prototype.initConfigList.slice();
  3368. prototype.initConfigMap = Ext.Object.chain(prototype.initConfigMap);
  3369. //</feature>
  3370. },
  3371. /**
  3372. * @private
  3373. * @static
  3374. * @inheritable
  3375. */
  3376. '$onExtended': [],
  3377. /**
  3378. * @private
  3379. * @static
  3380. * @inheritable
  3381. */
  3382. triggerExtended: function() {
  3383. var callbacks = this.$onExtended,
  3384. ln = callbacks.length,
  3385. i, callback;
  3386. if (ln > 0) {
  3387. for (i = 0; i < ln; i++) {
  3388. callback = callbacks[i];
  3389. callback.fn.apply(callback.scope || this, arguments);
  3390. }
  3391. }
  3392. },
  3393. /**
  3394. * @private
  3395. * @static
  3396. * @inheritable
  3397. */
  3398. onExtended: function(fn, scope) {
  3399. this.$onExtended.push({
  3400. fn: fn,
  3401. scope: scope
  3402. });
  3403. return this;
  3404. },
  3405. /**
  3406. * @private
  3407. * @static
  3408. * @inheritable
  3409. */
  3410. addConfig: function(config, fullMerge) {
  3411. var prototype = this.prototype,
  3412. initConfigList = prototype.initConfigList,
  3413. initConfigMap = prototype.initConfigMap,
  3414. defaultConfig = prototype.defaultConfig,
  3415. hasInitConfigItem, name, value;
  3416. fullMerge = Boolean(fullMerge);
  3417. for (name in config) {
  3418. if (config.hasOwnProperty(name) && (fullMerge || !(name in defaultConfig))) {
  3419. value = config[name];
  3420. hasInitConfigItem = initConfigMap[name];
  3421. if (value !== null) {
  3422. if (!hasInitConfigItem) {
  3423. initConfigMap[name] = true;
  3424. initConfigList.push(name);
  3425. }
  3426. }
  3427. else if (hasInitConfigItem) {
  3428. initConfigMap[name] = false;
  3429. Ext.Array.remove(initConfigList, name);
  3430. }
  3431. }
  3432. }
  3433. if (fullMerge) {
  3434. Ext.merge(defaultConfig, config);
  3435. }
  3436. else {
  3437. Ext.mergeIf(defaultConfig, config);
  3438. }
  3439. prototype.configClass = Ext.Object.classify(defaultConfig);
  3440. },
  3441. /**
  3442. * Add / override static properties of this class.
  3443. *
  3444. * Ext.define('My.cool.Class', {
  3445. * // this.se
  3446. * });
  3447. *
  3448. * My.cool.Class.addStatics({
  3449. * someProperty: 'someValue', // My.cool.Class.someProperty = 'someValue'
  3450. * method1: function() { }, // My.cool.Class.method1 = function() { ... };
  3451. * method2: function() { } // My.cool.Class.method2 = function() { ... };
  3452. * });
  3453. *
  3454. * @param {Object} members
  3455. * @return {Ext.Base} this
  3456. * @static
  3457. * @inheritable
  3458. */
  3459. addStatics: function(members) {
  3460. var member, name;
  3461. //<debug>
  3462. var className = Ext.getClassName(this);
  3463. //</debug>
  3464. for (name in members) {
  3465. if (members.hasOwnProperty(name)) {
  3466. member = members[name];
  3467. //<debug>
  3468. if (typeof member == 'function') {
  3469. member.displayName = className + '.' + name;
  3470. }
  3471. //</debug>
  3472. this[name] = member;
  3473. }
  3474. }
  3475. return this;
  3476. },
  3477. /**
  3478. * @private
  3479. * @static
  3480. * @inheritable
  3481. */
  3482. addInheritableStatics: function(members) {
  3483. var inheritableStatics,
  3484. hasInheritableStatics,
  3485. prototype = this.prototype,
  3486. name, member;
  3487. inheritableStatics = prototype.$inheritableStatics;
  3488. hasInheritableStatics = prototype.$hasInheritableStatics;
  3489. if (!inheritableStatics) {
  3490. inheritableStatics = prototype.$inheritableStatics = [];
  3491. hasInheritableStatics = prototype.$hasInheritableStatics = {};
  3492. }
  3493. //<debug>
  3494. var className = Ext.getClassName(this);
  3495. //</debug>
  3496. for (name in members) {
  3497. if (members.hasOwnProperty(name)) {
  3498. member = members[name];
  3499. //<debug>
  3500. if (typeof member == 'function') {
  3501. member.displayName = className + '.' + name;
  3502. }
  3503. //</debug>
  3504. this[name] = member;
  3505. if (!hasInheritableStatics[name]) {
  3506. hasInheritableStatics[name] = true;
  3507. inheritableStatics.push(name);
  3508. }
  3509. }
  3510. }
  3511. return this;
  3512. },
  3513. /**
  3514. * Add methods / properties to the prototype of this class.
  3515. *
  3516. * @example
  3517. * Ext.define('My.awesome.Cat', {
  3518. * constructor: function() {
  3519. * // ...
  3520. * }
  3521. * });
  3522. *
  3523. * My.awesome.Cat.addMembers({
  3524. * meow: function() {
  3525. * alert('Meowww...');
  3526. * }
  3527. * });
  3528. *
  3529. * var kitty = new My.awesome.Cat();
  3530. * kitty.meow();
  3531. *
  3532. * @param {Object} members
  3533. * @static
  3534. * @inheritable
  3535. */
  3536. addMembers: function(members) {
  3537. var prototype = this.prototype,
  3538. names = [],
  3539. name, member;
  3540. //<debug>
  3541. var className = this.$className || '';
  3542. //</debug>
  3543. for (name in members) {
  3544. if (members.hasOwnProperty(name)) {
  3545. member = members[name];
  3546. if (typeof member == 'function' && !member.$isClass && member !== Ext.emptyFn) {
  3547. member.$owner = this;
  3548. member.$name = name;
  3549. //<debug>
  3550. member.displayName = className + '#' + name;
  3551. //</debug>
  3552. }
  3553. prototype[name] = member;
  3554. }
  3555. }
  3556. return this;
  3557. },
  3558. /**
  3559. * @private
  3560. * @static
  3561. * @inheritable
  3562. */
  3563. addMember: function(name, member) {
  3564. if (typeof member == 'function' && !member.$isClass && member !== Ext.emptyFn) {
  3565. member.$owner = this;
  3566. member.$name = name;
  3567. //<debug>
  3568. member.displayName = (this.$className || '') + '#' + name;
  3569. //</debug>
  3570. }
  3571. this.prototype[name] = member;
  3572. return this;
  3573. },
  3574. /**
  3575. * @private
  3576. * @static
  3577. * @inheritable
  3578. */
  3579. implement: function() {
  3580. this.addMembers.apply(this, arguments);
  3581. },
  3582. /**
  3583. * Borrow another class' members to the prototype of this class.
  3584. *
  3585. * Ext.define('Bank', {
  3586. * money: '$$$',
  3587. * printMoney: function() {
  3588. * alert('$$$$$$$');
  3589. * }
  3590. * });
  3591. *
  3592. * Ext.define('Thief', {
  3593. * // ...
  3594. * });
  3595. *
  3596. * Thief.borrow(Bank, ['money', 'printMoney']);
  3597. *
  3598. * var steve = new Thief();
  3599. *
  3600. * alert(steve.money); // alerts '$$$'
  3601. * steve.printMoney(); // alerts '$$$$$$$'
  3602. *
  3603. * @param {Ext.Base} fromClass The class to borrow members from
  3604. * @param {Array/String} members The names of the members to borrow
  3605. * @return {Ext.Base} this
  3606. * @static
  3607. * @inheritable
  3608. * @private
  3609. */
  3610. borrow: function(fromClass, members) {
  3611. var prototype = this.prototype,
  3612. fromPrototype = fromClass.prototype,
  3613. //<debug>
  3614. className = Ext.getClassName(this),
  3615. //</debug>
  3616. i, ln, name, fn, toBorrow;
  3617. members = Ext.Array.from(members);
  3618. for (i = 0,ln = members.length; i < ln; i++) {
  3619. name = members[i];
  3620. toBorrow = fromPrototype[name];
  3621. if (typeof toBorrow == 'function') {
  3622. fn = function() {
  3623. return toBorrow.apply(this, arguments);
  3624. };
  3625. //<debug>
  3626. if (className) {
  3627. fn.displayName = className + '#' + name;
  3628. }
  3629. //</debug>
  3630. fn.$owner = this;
  3631. fn.$name = name;
  3632. prototype[name] = fn;
  3633. }
  3634. else {
  3635. prototype[name] = toBorrow;
  3636. }
  3637. }
  3638. return this;
  3639. },
  3640. /**
  3641. * Override members of this class. Overridden methods can be invoked via
  3642. * {@link Ext.Base#callParent}.
  3643. *
  3644. * Ext.define('My.Cat', {
  3645. * constructor: function() {
  3646. * alert("I'm a cat!");
  3647. * }
  3648. * });
  3649. *
  3650. * My.Cat.override({
  3651. * constructor: function() {
  3652. * alert("I'm going to be a cat!");
  3653. *
  3654. * var instance = this.callParent(arguments);
  3655. *
  3656. * alert("Meeeeoooowwww");
  3657. *
  3658. * return instance;
  3659. * }
  3660. * });
  3661. *
  3662. * var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
  3663. * // alerts "I'm a cat!"
  3664. * // alerts "Meeeeoooowwww"
  3665. *
  3666. * As of 2.1, direct use of this method is deprecated. Use {@link Ext#define Ext.define}
  3667. * instead:
  3668. *
  3669. * Ext.define('My.CatOverride', {
  3670. * override: 'My.Cat',
  3671. *
  3672. * constructor: function() {
  3673. * alert("I'm going to be a cat!");
  3674. *
  3675. * var instance = this.callParent(arguments);
  3676. *
  3677. * alert("Meeeeoooowwww");
  3678. *
  3679. * return instance;
  3680. * }
  3681. * });
  3682. *
  3683. * The above accomplishes the same result but can be managed by the {@link Ext.Loader}
  3684. * which can properly order the override and its target class and the build process
  3685. * can determine whether the override is needed based on the required state of the
  3686. * target class (My.Cat).
  3687. *
  3688. * @param {Object} members The properties to add to this class. This should be
  3689. * specified as an object literal containing one or more properties.
  3690. * @return {Ext.Base} this class
  3691. * @static
  3692. * @inheritable
  3693. * @deprecated 2.1.0 Please use {@link Ext#define Ext.define} instead
  3694. */
  3695. override: function(members) {
  3696. var me = this,
  3697. enumerables = Ext.enumerables,
  3698. target = me.prototype,
  3699. cloneFunction = Ext.Function.clone,
  3700. name, index, member, statics, names, previous;
  3701. if (arguments.length === 2) {
  3702. name = members;
  3703. members = {};
  3704. members[name] = arguments[1];
  3705. enumerables = null;
  3706. }
  3707. do {
  3708. names = []; // clean slate for prototype (1st pass) and static (2nd pass)
  3709. statics = null; // not needed 1st pass, but needs to be cleared for 2nd pass
  3710. for (name in members) { // hasOwnProperty is checked in the next loop...
  3711. if (name == 'statics') {
  3712. statics = members[name];
  3713. }
  3714. else if (name == 'config') {
  3715. me.addConfig(members[name], true);
  3716. }
  3717. else {
  3718. names.push(name);
  3719. }
  3720. }
  3721. if (enumerables) {
  3722. names.push.apply(names, enumerables);
  3723. }
  3724. for (index = names.length; index--; ) {
  3725. name = names[index];
  3726. if (members.hasOwnProperty(name)) {
  3727. member = members[name];
  3728. if (typeof member == 'function' && !member.$className && member !== Ext.emptyFn) {
  3729. if (typeof member.$owner != 'undefined') {
  3730. member = cloneFunction(member);
  3731. }
  3732. //<debug>
  3733. var className = me.$className;
  3734. if (className) {
  3735. member.displayName = className + '#' + name;
  3736. }
  3737. //</debug>
  3738. member.$owner = me;
  3739. member.$name = name;
  3740. previous = target[name];
  3741. if (previous) {
  3742. member.$previous = previous;
  3743. }
  3744. }
  3745. target[name] = member;
  3746. }
  3747. }
  3748. target = me; // 2nd pass is for statics
  3749. members = statics; // statics will be null on 2nd pass
  3750. } while (members);
  3751. return this;
  3752. },
  3753. /**
  3754. * @protected
  3755. * @static
  3756. * @inheritable
  3757. */
  3758. callParent: function(args) {
  3759. var method;
  3760. // This code is intentionally inlined for the least amount of debugger stepping
  3761. return (method = this.callParent.caller) && (method.$previous ||
  3762. ((method = method.$owner ? method : method.caller) &&
  3763. method.$owner.superclass.$class[method.$name])).apply(this, args || noArgs);
  3764. },
  3765. //<feature classSystem.mixins>
  3766. /**
  3767. * Used internally by the mixins pre-processor
  3768. * @private
  3769. * @static
  3770. * @inheritable
  3771. */
  3772. mixin: function(name, mixinClass) {
  3773. var mixin = mixinClass.prototype,
  3774. prototype = this.prototype,
  3775. key;
  3776. if (typeof mixin.onClassMixedIn != 'undefined') {
  3777. mixin.onClassMixedIn.call(mixinClass, this);
  3778. }
  3779. if (!prototype.hasOwnProperty('mixins')) {
  3780. if ('mixins' in prototype) {
  3781. prototype.mixins = Ext.Object.chain(prototype.mixins);
  3782. }
  3783. else {
  3784. prototype.mixins = {};
  3785. }
  3786. }
  3787. for (key in mixin) {
  3788. if (key === 'mixins') {
  3789. Ext.merge(prototype.mixins, mixin[key]);
  3790. }
  3791. else if (typeof prototype[key] == 'undefined' && key != 'mixinId' && key != 'config') {
  3792. prototype[key] = mixin[key];
  3793. }
  3794. }
  3795. //<feature classSystem.config>
  3796. if ('config' in mixin) {
  3797. this.addConfig(mixin.config, false);
  3798. }
  3799. //</feature>
  3800. prototype.mixins[name] = mixin;
  3801. },
  3802. //</feature>
  3803. /**
  3804. * Get the current class' name in string format.
  3805. *
  3806. * Ext.define('My.cool.Class', {
  3807. * constructor: function() {
  3808. * alert(this.self.getName()); // alerts 'My.cool.Class'
  3809. * }
  3810. * });
  3811. *
  3812. * My.cool.Class.getName(); // 'My.cool.Class'
  3813. *
  3814. * @return {String} className
  3815. * @static
  3816. * @inheritable
  3817. */
  3818. getName: function() {
  3819. return Ext.getClassName(this);
  3820. },
  3821. /**
  3822. * Create aliases for existing prototype methods. Example:
  3823. *
  3824. * Ext.define('My.cool.Class', {
  3825. * method1: function() { },
  3826. * method2: function() { }
  3827. * });
  3828. *
  3829. * var test = new My.cool.Class();
  3830. *
  3831. * My.cool.Class.createAlias({
  3832. * method3: 'method1',
  3833. * method4: 'method2'
  3834. * });
  3835. *
  3836. * test.method3(); // test.method1()
  3837. *
  3838. * My.cool.Class.createAlias('method5', 'method3');
  3839. *
  3840. * test.method5(); // test.method3() -> test.method1()
  3841. *
  3842. * @param {String/Object} alias The new method name, or an object to set multiple aliases. See
  3843. * {@link Ext.Function#flexSetter flexSetter}
  3844. * @param {String/Object} origin The original method name
  3845. * @static
  3846. * @inheritable
  3847. * @method
  3848. */
  3849. createAlias: flexSetter(function(alias, origin) {
  3850. this.override(alias, function() {
  3851. return this[origin].apply(this, arguments);
  3852. });
  3853. }),
  3854. /**
  3855. * @private
  3856. * @static
  3857. * @inheritable
  3858. */
  3859. addXtype: function(xtype) {
  3860. var prototype = this.prototype,
  3861. xtypesMap = prototype.xtypesMap,
  3862. xtypes = prototype.xtypes,
  3863. xtypesChain = prototype.xtypesChain;
  3864. if (!prototype.hasOwnProperty('xtypesMap')) {
  3865. xtypesMap = prototype.xtypesMap = Ext.merge({}, prototype.xtypesMap || {});
  3866. xtypes = prototype.xtypes = prototype.xtypes ? [].concat(prototype.xtypes) : [];
  3867. xtypesChain = prototype.xtypesChain = prototype.xtypesChain ? [].concat(prototype.xtypesChain) : [];
  3868. prototype.xtype = xtype;
  3869. }
  3870. if (!xtypesMap[xtype]) {
  3871. xtypesMap[xtype] = true;
  3872. xtypes.push(xtype);
  3873. xtypesChain.push(xtype);
  3874. Ext.ClassManager.setAlias(this, 'widget.' + xtype);
  3875. }
  3876. return this;
  3877. }
  3878. });
  3879. Base.implement({
  3880. isInstance: true,
  3881. $className: 'Ext.Base',
  3882. configClass: Ext.emptyFn,
  3883. initConfigList: [],
  3884. initConfigMap: {},
  3885. /**
  3886. * Get the reference to the class from which this object was instantiated. Note that unlike {@link Ext.Base#self},
  3887. * `this.statics()` is scope-independent and it always returns the class from which it was called, regardless of what
  3888. * `this` points to during run-time
  3889. *
  3890. * Ext.define('My.Cat', {
  3891. * statics: {
  3892. * totalCreated: 0,
  3893. * speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
  3894. * },
  3895. *
  3896. * constructor: function() {
  3897. * var statics = this.statics();
  3898. *
  3899. * alert(statics.speciesName); // always equals to 'Cat' no matter what 'this' refers to
  3900. * // equivalent to: My.Cat.speciesName
  3901. *
  3902. * alert(this.self.speciesName); // dependent on 'this'
  3903. *
  3904. * statics.totalCreated++;
  3905. * },
  3906. *
  3907. * clone: function() {
  3908. * var cloned = new this.self(); // dependent on 'this'
  3909. *
  3910. * cloned.groupName = this.statics().speciesName; // equivalent to: My.Cat.speciesName
  3911. *
  3912. * return cloned;
  3913. * }
  3914. * });
  3915. *
  3916. *
  3917. * Ext.define('My.SnowLeopard', {
  3918. * extend: 'My.Cat',
  3919. *
  3920. * statics: {
  3921. * speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
  3922. * },
  3923. *
  3924. * constructor: function() {
  3925. * this.callParent();
  3926. * }
  3927. * });
  3928. *
  3929. * var cat = new My.Cat(); // alerts 'Cat', then alerts 'Cat'
  3930. *
  3931. * var snowLeopard = new My.SnowLeopard(); // alerts 'Cat', then alerts 'Snow Leopard'
  3932. *
  3933. * var clone = snowLeopard.clone();
  3934. * alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
  3935. * alert(clone.groupName); // alerts 'Cat'
  3936. *
  3937. * alert(My.Cat.totalCreated); // alerts 3
  3938. *
  3939. * @protected
  3940. * @return {Ext.Class}
  3941. */
  3942. statics: function() {
  3943. var method = this.statics.caller,
  3944. self = this.self;
  3945. if (!method) {
  3946. return self;
  3947. }
  3948. return method.$owner;
  3949. },
  3950. /**
  3951. * Call the "parent" method of the current method. That is the method previously
  3952. * overridden by derivation or by an override (see {@link Ext#define}).
  3953. *
  3954. * Ext.define('My.Base', {
  3955. * constructor: function (x) {
  3956. * this.x = x;
  3957. * },
  3958. *
  3959. * statics: {
  3960. * method: function (x) {
  3961. * return x;
  3962. * }
  3963. * }
  3964. * });
  3965. *
  3966. * Ext.define('My.Derived', {
  3967. * extend: 'My.Base',
  3968. *
  3969. * constructor: function () {
  3970. * this.callParent([21]);
  3971. * }
  3972. * });
  3973. *
  3974. * var obj = new My.Derived();
  3975. *
  3976. * alert(obj.x); // alerts 21
  3977. *
  3978. * This can be used with an override as follows:
  3979. *
  3980. * Ext.define('My.DerivedOverride', {
  3981. * override: 'My.Derived',
  3982. *
  3983. * constructor: function (x) {
  3984. * this.callParent([x*2]); // calls original My.Derived constructor
  3985. * }
  3986. * });
  3987. *
  3988. * var obj = new My.Derived();
  3989. *
  3990. * alert(obj.x); // now alerts 42
  3991. *
  3992. * This also works with static methods.
  3993. *
  3994. * Ext.define('My.Derived2', {
  3995. * extend: 'My.Base',
  3996. *
  3997. * statics: {
  3998. * method: function (x) {
  3999. * return this.callParent([x*2]); // calls My.Base.method
  4000. * }
  4001. * }
  4002. * });
  4003. *
  4004. * alert(My.Base.method(10)); // alerts 10
  4005. * alert(My.Derived2.method(10)); // alerts 20
  4006. *
  4007. * Lastly, it also works with overridden static methods.
  4008. *
  4009. * Ext.define('My.Derived2Override', {
  4010. * override: 'My.Derived2',
  4011. *
  4012. * statics: {
  4013. * method: function (x) {
  4014. * return this.callParent([x*2]); // calls My.Derived2.method
  4015. * }
  4016. * }
  4017. * });
  4018. *
  4019. * alert(My.Derived2.method(10)); // now alerts 40
  4020. *
  4021. * To override a method and replace it and also call the superclass method, use
  4022. * {@link #callSuper}. This is often done to patch a method to fix a bug.
  4023. *
  4024. * @protected
  4025. * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
  4026. * from the current method, for example: `this.callParent(arguments)`
  4027. * @return {Object} Returns the result of calling the parent method
  4028. */
  4029. callParent: function(args) {
  4030. // NOTE: this code is deliberately as few expressions (and no function calls)
  4031. // as possible so that a debugger can skip over this noise with the minimum number
  4032. // of steps. Basically, just hit Step Into until you are where you really wanted
  4033. // to be.
  4034. var method,
  4035. superMethod = (method = this.callParent.caller) && (method.$previous ||
  4036. ((method = method.$owner ? method : method.caller) &&
  4037. method.$owner.superclass[method.$name]));
  4038. //<debug error>
  4039. if (!superMethod) {
  4040. method = this.callParent.caller;
  4041. var parentClass, methodName;
  4042. if (!method.$owner) {
  4043. if (!method.caller) {
  4044. throw new Error("Attempting to call a protected method from the public scope, which is not allowed");
  4045. }
  4046. method = method.caller;
  4047. }
  4048. parentClass = method.$owner.superclass;
  4049. methodName = method.$name;
  4050. if (!(methodName in parentClass)) {
  4051. throw new Error("this.callParent() was called but there's no such method (" + methodName +
  4052. ") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")");
  4053. }
  4054. }
  4055. //</debug>
  4056. return superMethod.apply(this, args || noArgs);
  4057. },
  4058. /**
  4059. * This method is used by an override to call the superclass method but bypass any
  4060. * overridden method. This is often done to "patch" a method that contains a bug
  4061. * but for whatever reason cannot be fixed directly.
  4062. *
  4063. * Consider:
  4064. *
  4065. * Ext.define('Ext.some.Class', {
  4066. * method: function () {
  4067. * console.log('Good');
  4068. * }
  4069. * });
  4070. *
  4071. * Ext.define('Ext.some.DerivedClass', {
  4072. * method: function () {
  4073. * console.log('Bad');
  4074. *
  4075. * // ... logic but with a bug ...
  4076. *
  4077. * this.callParent();
  4078. * }
  4079. * });
  4080. *
  4081. * To patch the bug in `DerivedClass.method`, the typical solution is to create an
  4082. * override:
  4083. *
  4084. * Ext.define('App.paches.DerivedClass', {
  4085. * override: 'Ext.some.DerivedClass',
  4086. *
  4087. * method: function () {
  4088. * console.log('Fixed');
  4089. *
  4090. * // ... logic but with bug fixed ...
  4091. *
  4092. * this.callSuper();
  4093. * }
  4094. * });
  4095. *
  4096. * The patch method cannot use `callParent` to call the superclass `method` since
  4097. * that would call the overridden method containing the bug. In other words, the
  4098. * above patch would only produce "Fixed" then "Good" in the console log, whereas,
  4099. * using `callParent` would produce "Fixed" then "Bad" then "Good".
  4100. *
  4101. * @protected
  4102. * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
  4103. * from the current method, for example: `this.callSuper(arguments)`
  4104. * @return {Object} Returns the result of calling the superclass method
  4105. */
  4106. callSuper: function(args) {
  4107. var method,
  4108. superMethod = (method = this.callSuper.caller) && ((method = method.$owner ? method : method.caller) &&
  4109. method.$owner.superclass[method.$name]);
  4110. //<debug error>
  4111. if (!superMethod) {
  4112. method = this.callSuper.caller;
  4113. var parentClass, methodName;
  4114. if (!method.$owner) {
  4115. if (!method.caller) {
  4116. throw new Error("Attempting to call a protected method from the public scope, which is not allowed");
  4117. }
  4118. method = method.caller;
  4119. }
  4120. parentClass = method.$owner.superclass;
  4121. methodName = method.$name;
  4122. if (!(methodName in parentClass)) {
  4123. throw new Error("this.callSuper() was called but there's no such method (" + methodName +
  4124. ") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")");
  4125. }
  4126. }
  4127. //</debug>
  4128. return superMethod.apply(this, args || noArgs);
  4129. },
  4130. /**
  4131. * Call the original method that was previously overridden with {@link Ext.Base#override},
  4132. *
  4133. * This method is deprecated as {@link #callParent} does the same thing.
  4134. *
  4135. * Ext.define('My.Cat', {
  4136. * constructor: function() {
  4137. * alert("I'm a cat!");
  4138. * }
  4139. * });
  4140. *
  4141. * My.Cat.override({
  4142. * constructor: function() {
  4143. * alert("I'm going to be a cat!");
  4144. *
  4145. * var instance = this.callOverridden();
  4146. *
  4147. * alert("Meeeeoooowwww");
  4148. *
  4149. * return instance;
  4150. * }
  4151. * });
  4152. *
  4153. * var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
  4154. * // alerts "I'm a cat!"
  4155. * // alerts "Meeeeoooowwww"
  4156. *
  4157. * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
  4158. * from the current method, for example: `this.callOverridden(arguments)`
  4159. * @return {Object} Returns the result of calling the overridden method
  4160. * @protected
  4161. * @deprecated Use callParent instead
  4162. */
  4163. callOverridden: function(args) {
  4164. var method;
  4165. return (method = this.callOverridden.caller) && method.$previous.apply(this, args || noArgs);
  4166. },
  4167. /**
  4168. * @property {Ext.Class} self
  4169. *
  4170. * Get the reference to the current class from which this object was instantiated. Unlike {@link Ext.Base#statics},
  4171. * `this.self` is scope-dependent and it's meant to be used for dynamic inheritance. See {@link Ext.Base#statics}
  4172. * for a detailed comparison
  4173. *
  4174. * Ext.define('My.Cat', {
  4175. * statics: {
  4176. * speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
  4177. * },
  4178. *
  4179. * constructor: function() {
  4180. * alert(this.self.speciesName); // dependent on 'this'
  4181. * },
  4182. *
  4183. * clone: function() {
  4184. * return new this.self();
  4185. * }
  4186. * });
  4187. *
  4188. *
  4189. * Ext.define('My.SnowLeopard', {
  4190. * extend: 'My.Cat',
  4191. * statics: {
  4192. * speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
  4193. * }
  4194. * });
  4195. *
  4196. * var cat = new My.Cat(); // alerts 'Cat'
  4197. * var snowLeopard = new My.SnowLeopard(); // alerts 'Snow Leopard'
  4198. *
  4199. * var clone = snowLeopard.clone();
  4200. * alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
  4201. *
  4202. * @protected
  4203. */
  4204. self: Base,
  4205. // Default constructor, simply returns `this`
  4206. constructor: function() {
  4207. return this;
  4208. },
  4209. //<feature classSystem.config>
  4210. wasInstantiated: false,
  4211. /**
  4212. * Initialize configuration for this class. a typical example:
  4213. *
  4214. * Ext.define('My.awesome.Class', {
  4215. * // The default config
  4216. * config: {
  4217. * name: 'Awesome',
  4218. * isAwesome: true
  4219. * },
  4220. *
  4221. * constructor: function(config) {
  4222. * this.initConfig(config);
  4223. * }
  4224. * });
  4225. *
  4226. * var awesome = new My.awesome.Class({
  4227. * name: 'Super Awesome'
  4228. * });
  4229. *
  4230. * alert(awesome.getName()); // 'Super Awesome'
  4231. *
  4232. * @protected
  4233. * @param {Object} instanceConfig
  4234. * @return {Object} mixins The mixin prototypes as key - value pairs
  4235. */
  4236. initConfig: function(instanceConfig) {
  4237. //<debug>
  4238. // if (instanceConfig && instanceConfig.breakOnInitConfig) {
  4239. // debugger;
  4240. // }
  4241. //</debug>
  4242. var configNameCache = Ext.Class.configNameCache,
  4243. prototype = this.self.prototype,
  4244. initConfigList = this.initConfigList,
  4245. initConfigMap = this.initConfigMap,
  4246. config = new this.configClass,
  4247. defaultConfig = this.defaultConfig,
  4248. i, ln, name, value, nameMap, getName;
  4249. this.initConfig = Ext.emptyFn;
  4250. this.initialConfig = instanceConfig || {};
  4251. if (instanceConfig) {
  4252. Ext.merge(config, instanceConfig);
  4253. }
  4254. this.config = config;
  4255. // Optimize initConfigList *once* per class based on the existence of apply* and update* methods
  4256. // Happens only once during the first instantiation
  4257. if (!prototype.hasOwnProperty('wasInstantiated')) {
  4258. prototype.wasInstantiated = true;
  4259. for (i = 0,ln = initConfigList.length; i < ln; i++) {
  4260. name = initConfigList[i];
  4261. nameMap = configNameCache[name];
  4262. value = defaultConfig[name];
  4263. if (!(nameMap.apply in prototype)
  4264. && !(nameMap.update in prototype)
  4265. && prototype[nameMap.set].$isDefault
  4266. && typeof value != 'object') {
  4267. prototype[nameMap.internal] = defaultConfig[name];
  4268. initConfigMap[name] = false;
  4269. Ext.Array.remove(initConfigList, name);
  4270. i--;
  4271. ln--;
  4272. }
  4273. }
  4274. }
  4275. if (instanceConfig) {
  4276. initConfigList = initConfigList.slice();
  4277. for (name in instanceConfig) {
  4278. if (name in defaultConfig && !initConfigMap[name]) {
  4279. initConfigList.push(name);
  4280. }
  4281. }
  4282. }
  4283. // Point all getters to the initGetters
  4284. for (i = 0,ln = initConfigList.length; i < ln; i++) {
  4285. name = initConfigList[i];
  4286. nameMap = configNameCache[name];
  4287. this[nameMap.get] = this[nameMap.initGet];
  4288. }
  4289. this.beforeInitConfig(config);
  4290. for (i = 0,ln = initConfigList.length; i < ln; i++) {
  4291. name = initConfigList[i];
  4292. nameMap = configNameCache[name];
  4293. getName = nameMap.get;
  4294. if (this.hasOwnProperty(getName)) {
  4295. this[nameMap.set].call(this, config[name]);
  4296. delete this[getName];
  4297. }
  4298. }
  4299. return this;
  4300. },
  4301. beforeInitConfig: Ext.emptyFn,
  4302. /**
  4303. * @private
  4304. */
  4305. getCurrentConfig: function() {
  4306. var defaultConfig = this.defaultConfig,
  4307. configNameCache = Ext.Class.configNameCache,
  4308. config = {},
  4309. name, nameMap;
  4310. for (name in defaultConfig) {
  4311. nameMap = configNameCache[name];
  4312. config[name] = this[nameMap.get].call(this);
  4313. }
  4314. return config;
  4315. },
  4316. /**
  4317. * @private
  4318. */
  4319. setConfig: function(config, applyIfNotSet) {
  4320. if (!config) {
  4321. return this;
  4322. }
  4323. var configNameCache = Ext.Class.configNameCache,
  4324. currentConfig = this.config,
  4325. defaultConfig = this.defaultConfig,
  4326. initialConfig = this.initialConfig,
  4327. configList = [],
  4328. name, i, ln, nameMap;
  4329. applyIfNotSet = Boolean(applyIfNotSet);
  4330. for (name in config) {
  4331. if ((applyIfNotSet && (name in initialConfig))) {
  4332. continue;
  4333. }
  4334. currentConfig[name] = config[name];
  4335. if (name in defaultConfig) {
  4336. configList.push(name);
  4337. nameMap = configNameCache[name];
  4338. this[nameMap.get] = this[nameMap.initGet];
  4339. }
  4340. }
  4341. for (i = 0,ln = configList.length; i < ln; i++) {
  4342. name = configList[i];
  4343. nameMap = configNameCache[name];
  4344. this[nameMap.set].call(this, config[name]);
  4345. delete this[nameMap.get];
  4346. }
  4347. return this;
  4348. },
  4349. set: function(name, value) {
  4350. return this[Ext.Class.configNameCache[name].set].call(this, value);
  4351. },
  4352. get: function(name) {
  4353. return this[Ext.Class.configNameCache[name].get].call(this);
  4354. },
  4355. /**
  4356. * @private
  4357. */
  4358. getConfig: function(name) {
  4359. return this[Ext.Class.configNameCache[name].get].call(this);
  4360. },
  4361. /**
  4362. * @private
  4363. */
  4364. hasConfig: function(name) {
  4365. return (name in this.defaultConfig);
  4366. },
  4367. /**
  4368. * Returns the initial configuration passed to constructor.
  4369. *
  4370. * @param {String} [name] When supplied, value for particular configuration
  4371. * option is returned, otherwise the full config object is returned.
  4372. * @return {Object/Mixed}
  4373. */
  4374. getInitialConfig: function(name) {
  4375. var config = this.config;
  4376. if (!name) {
  4377. return config;
  4378. }
  4379. else {
  4380. return config[name];
  4381. }
  4382. },
  4383. /**
  4384. * @private
  4385. */
  4386. onConfigUpdate: function(names, callback, scope) {
  4387. var self = this.self,
  4388. //<debug>
  4389. className = self.$className,
  4390. //</debug>
  4391. i, ln, name,
  4392. updaterName, updater, newUpdater;
  4393. names = Ext.Array.from(names);
  4394. scope = scope || this;
  4395. for (i = 0,ln = names.length; i < ln; i++) {
  4396. name = names[i];
  4397. updaterName = 'update' + Ext.String.capitalize(name);
  4398. updater = this[updaterName] || Ext.emptyFn;
  4399. newUpdater = function() {
  4400. updater.apply(this, arguments);
  4401. scope[callback].apply(scope, arguments);
  4402. };
  4403. newUpdater.$name = updaterName;
  4404. newUpdater.$owner = self;
  4405. //<debug>
  4406. newUpdater.displayName = className + '#' + updaterName;
  4407. //</debug>
  4408. this[updaterName] = newUpdater;
  4409. }
  4410. },
  4411. //</feature>
  4412. /**
  4413. * @private
  4414. * @param name
  4415. * @param value
  4416. * @return {Mixed}
  4417. */
  4418. link: function(name, value) {
  4419. this.$links = {};
  4420. this.link = this.doLink;
  4421. return this.link.apply(this, arguments);
  4422. },
  4423. doLink: function(name, value) {
  4424. this.$links[name] = true;
  4425. this[name] = value;
  4426. return value;
  4427. },
  4428. /**
  4429. * @private
  4430. */
  4431. unlink: function() {
  4432. var i, ln, link, value;
  4433. for (i = 0, ln = arguments.length; i < ln; i++) {
  4434. link = arguments[i];
  4435. if (this.hasOwnProperty(link)) {
  4436. value = this[link];
  4437. if (value) {
  4438. if (value.isInstance && !value.isDestroyed) {
  4439. value.destroy();
  4440. }
  4441. else if (value.parentNode && 'nodeType' in value) {
  4442. value.parentNode.removeChild(value);
  4443. }
  4444. }
  4445. delete this[link];
  4446. }
  4447. }
  4448. return this;
  4449. },
  4450. /**
  4451. * @protected
  4452. */
  4453. destroy: function() {
  4454. this.destroy = Ext.emptyFn;
  4455. this.isDestroyed = true;
  4456. if (this.hasOwnProperty('$links')) {
  4457. this.unlink.apply(this, Ext.Object.getKeys(this.$links));
  4458. delete this.$links;
  4459. }
  4460. }
  4461. });
  4462. Ext.Base = Base;
  4463. })(Ext.Function.flexSetter);
  4464. //@tag foundation,core
  4465. //@define Ext.Class
  4466. //@require Ext.Base
  4467. /**
  4468. * @class Ext.Class
  4469. *
  4470. * @author Jacky Nguyen <jacky@sencha.com>
  4471. * @aside guide class_system
  4472. * @aside video class-system
  4473. *
  4474. * Handles class creation throughout the framework. This is a low level factory that is used by Ext.ClassManager and generally
  4475. * should not be used directly. If you choose to use Ext.Class you will lose out on the namespace, aliasing and dependency loading
  4476. * features made available by Ext.ClassManager. The only time you would use Ext.Class directly is to create an anonymous class.
  4477. *
  4478. * If you wish to create a class you should use {@link Ext#define Ext.define} which aliases
  4479. * {@link Ext.ClassManager#create Ext.ClassManager.create} to enable namespacing and dynamic dependency resolution.
  4480. *
  4481. * Ext.Class is the factory and **not** the superclass of everything. For the base class that **all** Ext classes inherit
  4482. * from, see {@link Ext.Base}.
  4483. */
  4484. (function() {
  4485. var ExtClass,
  4486. Base = Ext.Base,
  4487. baseStaticMembers = [],
  4488. baseStaticMember, baseStaticMemberLength;
  4489. for (baseStaticMember in Base) {
  4490. if (Base.hasOwnProperty(baseStaticMember)) {
  4491. baseStaticMembers.push(baseStaticMember);
  4492. }
  4493. }
  4494. baseStaticMemberLength = baseStaticMembers.length;
  4495. /**
  4496. * @method constructor
  4497. * Creates a new anonymous class.
  4498. *
  4499. * @param {Object} data An object represent the properties of this class.
  4500. * @param {Function} onCreated (optional) The callback function to be executed when this class is fully created.
  4501. * Note that the creation process can be asynchronous depending on the pre-processors used.
  4502. *
  4503. * @return {Ext.Base} The newly created class
  4504. */
  4505. Ext.Class = ExtClass = function(Class, data, onCreated) {
  4506. if (typeof Class != 'function') {
  4507. onCreated = data;
  4508. data = Class;
  4509. Class = null;
  4510. }
  4511. if (!data) {
  4512. data = {};
  4513. }
  4514. Class = ExtClass.create(Class);
  4515. ExtClass.process(Class, data, onCreated);
  4516. return Class;
  4517. };
  4518. Ext.apply(ExtClass, {
  4519. /**
  4520. * @private
  4521. * @static
  4522. */
  4523. onBeforeCreated: function(Class, data, hooks) {
  4524. Class.addMembers(data);
  4525. hooks.onCreated.call(Class, Class);
  4526. },
  4527. /**
  4528. * @private
  4529. * @static
  4530. */
  4531. create: function(Class) {
  4532. var name, i;
  4533. if (!Class) {
  4534. Class = function() {
  4535. return this.constructor.apply(this, arguments);
  4536. };
  4537. }
  4538. for (i = 0; i < baseStaticMemberLength; i++) {
  4539. name = baseStaticMembers[i];
  4540. Class[name] = Base[name];
  4541. }
  4542. return Class;
  4543. },
  4544. /**
  4545. * @private
  4546. * @static
  4547. */
  4548. process: function(Class, data, onCreated) {
  4549. var preprocessorStack = data.preprocessors || ExtClass.defaultPreprocessors,
  4550. preprocessors = this.preprocessors,
  4551. hooks = {
  4552. onBeforeCreated: this.onBeforeCreated,
  4553. onCreated: onCreated || Ext.emptyFn
  4554. },
  4555. index = 0,
  4556. name, preprocessor, properties,
  4557. i, ln, fn, property, process;
  4558. delete data.preprocessors;
  4559. process = function(Class, data, hooks) {
  4560. fn = null;
  4561. while (fn === null) {
  4562. name = preprocessorStack[index++];
  4563. if (name) {
  4564. preprocessor = preprocessors[name];
  4565. properties = preprocessor.properties;
  4566. if (properties === true) {
  4567. fn = preprocessor.fn;
  4568. }
  4569. else {
  4570. for (i = 0,ln = properties.length; i < ln; i++) {
  4571. property = properties[i];
  4572. if (data.hasOwnProperty(property)) {
  4573. fn = preprocessor.fn;
  4574. break;
  4575. }
  4576. }
  4577. }
  4578. }
  4579. else {
  4580. hooks.onBeforeCreated.apply(this, arguments);
  4581. return;
  4582. }
  4583. }
  4584. if (fn.call(this, Class, data, hooks, process) !== false) {
  4585. process.apply(this, arguments);
  4586. }
  4587. };
  4588. process.call(this, Class, data, hooks);
  4589. },
  4590. /**
  4591. * @private
  4592. * @static
  4593. */
  4594. preprocessors: {},
  4595. /**
  4596. * Register a new pre-processor to be used during the class creation process.
  4597. *
  4598. * @private
  4599. * @static
  4600. * @param {String} name The pre-processor's name.
  4601. * @param {Function} fn The callback function to be executed. Typical format:
  4602. *
  4603. * function(cls, data, fn) {
  4604. * // Your code here
  4605. *
  4606. * // Execute this when the processing is finished.
  4607. * // Asynchronous processing is perfectly OK
  4608. * if (fn) {
  4609. * fn.call(this, cls, data);
  4610. * }
  4611. * });
  4612. *
  4613. * @param {Function} fn.cls The created class.
  4614. * @param {Object} fn.data The set of properties passed in {@link Ext.Class} constructor.
  4615. * @param {Function} fn.fn The callback function that __must__ to be executed when this pre-processor finishes,
  4616. * regardless of whether the processing is synchronous or asynchronous.
  4617. *
  4618. * @return {Ext.Class} this
  4619. */
  4620. registerPreprocessor: function(name, fn, properties, position, relativeTo) {
  4621. if (!position) {
  4622. position = 'last';
  4623. }
  4624. if (!properties) {
  4625. properties = [name];
  4626. }
  4627. this.preprocessors[name] = {
  4628. name: name,
  4629. properties: properties || false,
  4630. fn: fn
  4631. };
  4632. this.setDefaultPreprocessorPosition(name, position, relativeTo);
  4633. return this;
  4634. },
  4635. /**
  4636. * Retrieve a pre-processor callback function by its name, which has been registered before.
  4637. *
  4638. * @private
  4639. * @static
  4640. * @param {String} name
  4641. * @return {Function} preprocessor
  4642. */
  4643. getPreprocessor: function(name) {
  4644. return this.preprocessors[name];
  4645. },
  4646. /**
  4647. * @private
  4648. * @static
  4649. */
  4650. getPreprocessors: function() {
  4651. return this.preprocessors;
  4652. },
  4653. /**
  4654. * @private
  4655. * @static
  4656. */
  4657. defaultPreprocessors: [],
  4658. /**
  4659. * Retrieve the array stack of default pre-processors.
  4660. * @private
  4661. * @static
  4662. * @return {Function} defaultPreprocessors
  4663. */
  4664. getDefaultPreprocessors: function() {
  4665. return this.defaultPreprocessors;
  4666. },
  4667. /**
  4668. * Set the default array stack of default pre-processors.
  4669. *
  4670. * @private
  4671. * @static
  4672. * @param {Array} preprocessors
  4673. * @return {Ext.Class} this
  4674. */
  4675. setDefaultPreprocessors: function(preprocessors) {
  4676. this.defaultPreprocessors = Ext.Array.from(preprocessors);
  4677. return this;
  4678. },
  4679. /**
  4680. * Insert this pre-processor at a specific position in the stack, optionally relative to
  4681. * any existing pre-processor. For example:
  4682. *
  4683. * Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
  4684. * // Your code here
  4685. *
  4686. * if (fn) {
  4687. * fn.call(this, cls, data);
  4688. * }
  4689. * }).insertDefaultPreprocessor('debug', 'last');
  4690. *
  4691. * @private
  4692. * @static
  4693. * @param {String} name The pre-processor name. Note that it needs to be registered with
  4694. * {@link Ext.Class#registerPreprocessor registerPreprocessor} before this.
  4695. * @param {String} offset The insertion position. Four possible values are:
  4696. * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument).
  4697. * @param {String} relativeName
  4698. * @return {Ext.Class} this
  4699. */
  4700. setDefaultPreprocessorPosition: function(name, offset, relativeName) {
  4701. var defaultPreprocessors = this.defaultPreprocessors,
  4702. index;
  4703. if (typeof offset == 'string') {
  4704. if (offset === 'first') {
  4705. defaultPreprocessors.unshift(name);
  4706. return this;
  4707. }
  4708. else if (offset === 'last') {
  4709. defaultPreprocessors.push(name);
  4710. return this;
  4711. }
  4712. offset = (offset === 'after') ? 1 : -1;
  4713. }
  4714. index = Ext.Array.indexOf(defaultPreprocessors, relativeName);
  4715. if (index !== -1) {
  4716. Ext.Array.splice(defaultPreprocessors, Math.max(0, index + offset), 0, name);
  4717. }
  4718. return this;
  4719. },
  4720. /**
  4721. * @private
  4722. * @static
  4723. */
  4724. configNameCache: {},
  4725. /**
  4726. * @private
  4727. * @static
  4728. */
  4729. getConfigNameMap: function(name) {
  4730. var cache = this.configNameCache,
  4731. map = cache[name],
  4732. capitalizedName;
  4733. if (!map) {
  4734. capitalizedName = name.charAt(0).toUpperCase() + name.substr(1);
  4735. map = cache[name] = {
  4736. name: name,
  4737. internal: '_' + name,
  4738. initializing: 'is' + capitalizedName + 'Initializing',
  4739. apply: 'apply' + capitalizedName,
  4740. update: 'update' + capitalizedName,
  4741. set: 'set' + capitalizedName,
  4742. get: 'get' + capitalizedName,
  4743. initGet: 'initGet' + capitalizedName,
  4744. doSet : 'doSet' + capitalizedName,
  4745. changeEvent: name.toLowerCase() + 'change'
  4746. }
  4747. }
  4748. return map;
  4749. },
  4750. /**
  4751. * @private
  4752. * @static
  4753. */
  4754. generateSetter: function(nameMap) {
  4755. var internalName = nameMap.internal,
  4756. getName = nameMap.get,
  4757. applyName = nameMap.apply,
  4758. updateName = nameMap.update,
  4759. setter;
  4760. setter = function(value) {
  4761. var oldValue = this[internalName],
  4762. applier = this[applyName],
  4763. updater = this[updateName];
  4764. delete this[getName];
  4765. if (applier) {
  4766. value = applier.call(this, value, oldValue);
  4767. }
  4768. if (typeof value != 'undefined') {
  4769. this[internalName] = value;
  4770. if (updater && value !== oldValue) {
  4771. updater.call(this, value, oldValue);
  4772. }
  4773. }
  4774. return this;
  4775. };
  4776. setter.$isDefault = true;
  4777. return setter;
  4778. },
  4779. /**
  4780. * @private
  4781. * @static
  4782. */
  4783. generateInitGetter: function(nameMap) {
  4784. var name = nameMap.name,
  4785. setName = nameMap.set,
  4786. getName = nameMap.get,
  4787. initializingName = nameMap.initializing;
  4788. return function() {
  4789. this[initializingName] = true;
  4790. delete this[getName];
  4791. this[setName].call(this, this.config[name]);
  4792. delete this[initializingName];
  4793. return this[getName].apply(this, arguments);
  4794. }
  4795. },
  4796. /**
  4797. * @private
  4798. * @static
  4799. */
  4800. generateGetter: function(nameMap) {
  4801. var internalName = nameMap.internal;
  4802. return function() {
  4803. return this[internalName];
  4804. }
  4805. }
  4806. });
  4807. /**
  4808. * @cfg {String} extend
  4809. * The parent class that this class extends. For example:
  4810. *
  4811. * @example
  4812. * Ext.define('Person', {
  4813. * say: function(text) {
  4814. * alert(text);
  4815. * }
  4816. * });
  4817. *
  4818. * Ext.define('Developer', {
  4819. * extend: 'Person',
  4820. * say: function(text) {
  4821. * this.callParent(["print " + text]);
  4822. * }
  4823. * });
  4824. *
  4825. * var person1 = Ext.create("Person");
  4826. * person1.say("Bill");
  4827. *
  4828. * var developer1 = Ext.create("Developer");
  4829. * developer1.say("Ted");
  4830. */
  4831. ExtClass.registerPreprocessor('extend', function(Class, data) {
  4832. var Base = Ext.Base,
  4833. extend = data.extend,
  4834. Parent;
  4835. delete data.extend;
  4836. if (extend && extend !== Object) {
  4837. Parent = extend;
  4838. }
  4839. else {
  4840. Parent = Base;
  4841. }
  4842. Class.extend(Parent);
  4843. Class.triggerExtended.apply(Class, arguments);
  4844. if (data.onClassExtended) {
  4845. Class.onExtended(data.onClassExtended, Class);
  4846. delete data.onClassExtended;
  4847. }
  4848. }, true);
  4849. //<feature classSystem.statics>
  4850. /**
  4851. * @cfg {Object} statics
  4852. * List of static methods for this class. For example:
  4853. *
  4854. * Ext.define('Computer', {
  4855. * statics: {
  4856. * factory: function(brand) {
  4857. * // 'this' in static methods refer to the class itself
  4858. * return new this(brand);
  4859. * }
  4860. * },
  4861. *
  4862. * constructor: function() {
  4863. * // ...
  4864. * }
  4865. * });
  4866. *
  4867. * var dellComputer = Computer.factory('Dell');
  4868. */
  4869. ExtClass.registerPreprocessor('statics', function(Class, data) {
  4870. Class.addStatics(data.statics);
  4871. delete data.statics;
  4872. });
  4873. //</feature>
  4874. //<feature classSystem.inheritableStatics>
  4875. /**
  4876. * @cfg {Object} inheritableStatics
  4877. * List of inheritable static methods for this class.
  4878. * Otherwise just like {@link #statics} but subclasses inherit these methods.
  4879. */
  4880. ExtClass.registerPreprocessor('inheritableStatics', function(Class, data) {
  4881. Class.addInheritableStatics(data.inheritableStatics);
  4882. delete data.inheritableStatics;
  4883. });
  4884. //</feature>
  4885. //<feature classSystem.config>
  4886. /**
  4887. * @cfg {Object} config
  4888. *
  4889. * List of configuration options with their default values.
  4890. *
  4891. * __Note:__ You need to make sure {@link Ext.Base#initConfig} is called from your constructor if you are defining
  4892. * your own class or singleton, unless you are extending a Component. Otherwise the generated getter and setter
  4893. * methods will not be initialized.
  4894. *
  4895. * Each config item will have its own setter and getter method automatically generated inside the class prototype
  4896. * during class creation time, if the class does not have those methods explicitly defined.
  4897. *
  4898. * As an example, let's convert the name property of a Person class to be a config item, then add extra age and
  4899. * gender items.
  4900. *
  4901. * Ext.define('My.sample.Person', {
  4902. * config: {
  4903. * name: 'Mr. Unknown',
  4904. * age: 0,
  4905. * gender: 'Male'
  4906. * },
  4907. *
  4908. * constructor: function(config) {
  4909. * this.initConfig(config);
  4910. *
  4911. * return this;
  4912. * }
  4913. *
  4914. * // ...
  4915. * });
  4916. *
  4917. * Within the class, this.name still has the default value of "Mr. Unknown". However, it's now publicly accessible
  4918. * without sacrificing encapsulation, via setter and getter methods.
  4919. *
  4920. * var jacky = new Person({
  4921. * name: "Jacky",
  4922. * age: 35
  4923. * });
  4924. *
  4925. * alert(jacky.getAge()); // alerts 35
  4926. * alert(jacky.getGender()); // alerts "Male"
  4927. *
  4928. * jacky.walk(10); // alerts "Jacky is walking 10 steps"
  4929. *
  4930. * jacky.setName("Mr. Nguyen");
  4931. * alert(jacky.getName()); // alerts "Mr. Nguyen"
  4932. *
  4933. * jacky.walk(10); // alerts "Mr. Nguyen is walking 10 steps"
  4934. *
  4935. * Notice that we changed the class constructor to invoke this.initConfig() and pass in the provided config object.
  4936. * Two key things happened:
  4937. *
  4938. * - The provided config object when the class is instantiated is recursively merged with the default config object.
  4939. * - All corresponding setter methods are called with the merged values.
  4940. *
  4941. * Beside storing the given values, throughout the frameworks, setters generally have two key responsibilities:
  4942. *
  4943. * - Filtering / validation / transformation of the given value before it's actually stored within the instance.
  4944. * - Notification (such as firing events) / post-processing after the value has been set, or changed from a
  4945. * previous value.
  4946. *
  4947. * By standardize this common pattern, the default generated setters provide two extra template methods that you
  4948. * can put your own custom logics into, i.e: an "applyFoo" and "updateFoo" method for a "foo" config item, which are
  4949. * executed before and after the value is actually set, respectively. Back to the example class, let's validate that
  4950. * age must be a valid positive number, and fire an 'agechange' if the value is modified.
  4951. *
  4952. * Ext.define('My.sample.Person', {
  4953. * config: {
  4954. * // ...
  4955. * },
  4956. *
  4957. * constructor: {
  4958. * // ...
  4959. * },
  4960. *
  4961. * applyAge: function(age) {
  4962. * if (typeof age !== 'number' || age < 0) {
  4963. * console.warn("Invalid age, must be a positive number");
  4964. * return;
  4965. * }
  4966. *
  4967. * return age;
  4968. * },
  4969. *
  4970. * updateAge: function(newAge, oldAge) {
  4971. * // age has changed from "oldAge" to "newAge"
  4972. * this.fireEvent('agechange', this, newAge, oldAge);
  4973. * }
  4974. *
  4975. * // ...
  4976. * });
  4977. *
  4978. * var jacky = new Person({
  4979. * name: "Jacky",
  4980. * age: 'invalid'
  4981. * });
  4982. *
  4983. * alert(jacky.getAge()); // alerts 0
  4984. *
  4985. * alert(jacky.setAge(-100)); // alerts 0
  4986. * alert(jacky.getAge()); // alerts 0
  4987. *
  4988. * alert(jacky.setAge(35)); // alerts 0
  4989. * alert(jacky.getAge()); // alerts 35
  4990. *
  4991. * In other words, when leveraging the config feature, you mostly never need to define setter and getter methods
  4992. * explicitly. Instead, "apply*" and "update*" methods should be implemented where necessary. Your code will be
  4993. * consistent throughout and only contain the minimal logic that you actually care about.
  4994. *
  4995. * When it comes to inheritance, the default config of the parent class is automatically, recursively merged with
  4996. * the child's default config. The same applies for mixins.
  4997. */
  4998. ExtClass.registerPreprocessor('config', function(Class, data) {
  4999. var config = data.config,
  5000. prototype = Class.prototype,
  5001. defaultConfig = prototype.config,
  5002. nameMap, name, setName, getName, initGetName, internalName, value;
  5003. delete data.config;
  5004. for (name in config) {
  5005. // Once per config item, per class hierarchy
  5006. if (config.hasOwnProperty(name) && !(name in defaultConfig)) {
  5007. value = config[name];
  5008. nameMap = this.getConfigNameMap(name);
  5009. setName = nameMap.set;
  5010. getName = nameMap.get;
  5011. initGetName = nameMap.initGet;
  5012. internalName = nameMap.internal;
  5013. data[initGetName] = this.generateInitGetter(nameMap);
  5014. if (value === null && !data.hasOwnProperty(internalName)) {
  5015. data[internalName] = null;
  5016. }
  5017. if (!data.hasOwnProperty(getName)) {
  5018. data[getName] = this.generateGetter(nameMap);
  5019. }
  5020. if (!data.hasOwnProperty(setName)) {
  5021. data[setName] = this.generateSetter(nameMap);
  5022. }
  5023. }
  5024. }
  5025. Class.addConfig(config, true);
  5026. });
  5027. //</feature>
  5028. //<feature classSystem.mixins>
  5029. /**
  5030. * @cfg {Object} mixins
  5031. * List of classes to mix into this class. For example:
  5032. *
  5033. * Ext.define('CanSing', {
  5034. * sing: function() {
  5035. * alert("I'm on the highway to hell...");
  5036. * }
  5037. * });
  5038. *
  5039. * Ext.define('Musician', {
  5040. * extend: 'Person',
  5041. *
  5042. * mixins: {
  5043. * canSing: 'CanSing'
  5044. * }
  5045. * });
  5046. */
  5047. ExtClass.registerPreprocessor('mixins', function(Class, data, hooks) {
  5048. var mixins = data.mixins,
  5049. name, mixin, i, ln;
  5050. delete data.mixins;
  5051. Ext.Function.interceptBefore(hooks, 'onCreated', function() {
  5052. if (mixins instanceof Array) {
  5053. for (i = 0,ln = mixins.length; i < ln; i++) {
  5054. mixin = mixins[i];
  5055. name = mixin.prototype.mixinId || mixin.$className;
  5056. Class.mixin(name, mixin);
  5057. }
  5058. }
  5059. else {
  5060. for (name in mixins) {
  5061. if (mixins.hasOwnProperty(name)) {
  5062. Class.mixin(name, mixins[name]);
  5063. }
  5064. }
  5065. }
  5066. });
  5067. });
  5068. //</feature>
  5069. //<feature classSystem.backwardsCompatible>
  5070. // Backwards compatible
  5071. Ext.extend = function(Class, Parent, members) {
  5072. if (arguments.length === 2 && Ext.isObject(Parent)) {
  5073. members = Parent;
  5074. Parent = Class;
  5075. Class = null;
  5076. }
  5077. var cls;
  5078. if (!Parent) {
  5079. throw new Error("[Ext.extend] Attempting to extend from a class which has not been loaded on the page.");
  5080. }
  5081. members.extend = Parent;
  5082. members.preprocessors = [
  5083. 'extend'
  5084. //<feature classSystem.statics>
  5085. ,'statics'
  5086. //</feature>
  5087. //<feature classSystem.inheritableStatics>
  5088. ,'inheritableStatics'
  5089. //</feature>
  5090. //<feature classSystem.mixins>
  5091. ,'mixins'
  5092. //</feature>
  5093. //<feature classSystem.config>
  5094. ,'config'
  5095. //</feature>
  5096. ];
  5097. if (Class) {
  5098. cls = new ExtClass(Class, members);
  5099. }
  5100. else {
  5101. cls = new ExtClass(members);
  5102. }
  5103. cls.prototype.override = function(o) {
  5104. for (var m in o) {
  5105. if (o.hasOwnProperty(m)) {
  5106. this[m] = o[m];
  5107. }
  5108. }
  5109. };
  5110. return cls;
  5111. };
  5112. //</feature>
  5113. })();
  5114. //@tag foundation,core
  5115. //@define Ext.ClassManager
  5116. //@require Ext.Class
  5117. /**
  5118. * @class Ext.ClassManager
  5119. *
  5120. * @author Jacky Nguyen <jacky@sencha.com>
  5121. * @aside guide class_system
  5122. * @aside video class-system
  5123. *
  5124. * Ext.ClassManager manages all classes and handles mapping from string class name to
  5125. * actual class objects throughout the whole framework. It is not generally accessed directly, rather through
  5126. * these convenient shorthands:
  5127. *
  5128. * - {@link Ext#define Ext.define}
  5129. * - {@link Ext.ClassManager#create Ext.create}
  5130. * - {@link Ext#widget Ext.widget}
  5131. * - {@link Ext#getClass Ext.getClass}
  5132. * - {@link Ext#getClassName Ext.getClassName}
  5133. *
  5134. * ## Basic syntax:
  5135. *
  5136. * Ext.define(className, properties);
  5137. *
  5138. * in which `properties` is an object represent a collection of properties that apply to the class. See
  5139. * {@link Ext.ClassManager#create} for more detailed instructions.
  5140. *
  5141. * @example
  5142. * Ext.define('Person', {
  5143. * name: 'Unknown',
  5144. *
  5145. * constructor: function(name) {
  5146. * if (name) {
  5147. * this.name = name;
  5148. * }
  5149. *
  5150. * return this;
  5151. * },
  5152. *
  5153. * eat: function(foodType) {
  5154. * alert("I'm eating: " + foodType);
  5155. *
  5156. * return this;
  5157. * }
  5158. * });
  5159. *
  5160. * var aaron = new Person("Aaron");
  5161. * aaron.eat("Sandwich"); // alert("I'm eating: Sandwich");
  5162. *
  5163. * Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of
  5164. * everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc.
  5165. *
  5166. * ## Inheritance:
  5167. *
  5168. * Ext.define('Developer', {
  5169. * extend: 'Person',
  5170. *
  5171. * constructor: function(name, isGeek) {
  5172. * this.isGeek = isGeek;
  5173. *
  5174. * // Apply a method from the parent class' prototype
  5175. * this.callParent([name]);
  5176. *
  5177. * return this;
  5178. *
  5179. * },
  5180. *
  5181. * code: function(language) {
  5182. * alert("I'm coding in: " + language);
  5183. *
  5184. * this.eat("Bugs");
  5185. *
  5186. * return this;
  5187. * }
  5188. * });
  5189. *
  5190. * var jacky = new Developer("Jacky", true);
  5191. * jacky.code("JavaScript"); // alert("I'm coding in: JavaScript");
  5192. * // alert("I'm eating: Bugs");
  5193. *
  5194. * See {@link Ext.Base#callParent} for more details on calling superclass' methods
  5195. *
  5196. * ## Mixins:
  5197. *
  5198. * Ext.define('CanPlayGuitar', {
  5199. * playGuitar: function() {
  5200. * alert("F#...G...D...A");
  5201. * }
  5202. * });
  5203. *
  5204. * Ext.define('CanComposeSongs', {
  5205. * composeSongs: function() { }
  5206. * });
  5207. *
  5208. * Ext.define('CanSing', {
  5209. * sing: function() {
  5210. * alert("I'm on the highway to hell...");
  5211. * }
  5212. * });
  5213. *
  5214. * Ext.define('Musician', {
  5215. * extend: 'Person',
  5216. *
  5217. * mixins: {
  5218. * canPlayGuitar: 'CanPlayGuitar',
  5219. * canComposeSongs: 'CanComposeSongs',
  5220. * canSing: 'CanSing'
  5221. * }
  5222. * });
  5223. *
  5224. * Ext.define('CoolPerson', {
  5225. * extend: 'Person',
  5226. *
  5227. * mixins: {
  5228. * canPlayGuitar: 'CanPlayGuitar',
  5229. * canSing: 'CanSing'
  5230. * },
  5231. *
  5232. * sing: function() {
  5233. * alert("Ahem...");
  5234. *
  5235. * this.mixins.canSing.sing.call(this);
  5236. *
  5237. * alert("[Playing guitar at the same time...]");
  5238. *
  5239. * this.playGuitar();
  5240. * }
  5241. * });
  5242. *
  5243. * var me = new CoolPerson("Jacky");
  5244. *
  5245. * me.sing(); // alert("Ahem...");
  5246. * // alert("I'm on the highway to hell...");
  5247. * // alert("[Playing guitar at the same time...]");
  5248. * // alert("F#...G...D...A");
  5249. *
  5250. * ## Config:
  5251. *
  5252. * Ext.define('SmartPhone', {
  5253. * config: {
  5254. * hasTouchScreen: false,
  5255. * operatingSystem: 'Other',
  5256. * price: 500
  5257. * },
  5258. *
  5259. * isExpensive: false,
  5260. *
  5261. * constructor: function(config) {
  5262. * this.initConfig(config);
  5263. *
  5264. * return this;
  5265. * },
  5266. *
  5267. * applyPrice: function(price) {
  5268. * this.isExpensive = (price > 500);
  5269. *
  5270. * return price;
  5271. * },
  5272. *
  5273. * applyOperatingSystem: function(operatingSystem) {
  5274. * if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) {
  5275. * return 'Other';
  5276. * }
  5277. *
  5278. * return operatingSystem;
  5279. * }
  5280. * });
  5281. *
  5282. * var iPhone = new SmartPhone({
  5283. * hasTouchScreen: true,
  5284. * operatingSystem: 'iOS'
  5285. * });
  5286. *
  5287. * iPhone.getPrice(); // 500;
  5288. * iPhone.getOperatingSystem(); // 'iOS'
  5289. * iPhone.getHasTouchScreen(); // true;
  5290. *
  5291. * iPhone.isExpensive; // false;
  5292. * iPhone.setPrice(600);
  5293. * iPhone.getPrice(); // 600
  5294. * iPhone.isExpensive; // true;
  5295. *
  5296. * iPhone.setOperatingSystem('AlienOS');
  5297. * iPhone.getOperatingSystem(); // 'Other'
  5298. *
  5299. * ## Statics:
  5300. *
  5301. * Ext.define('Computer', {
  5302. * statics: {
  5303. * factory: function(brand) {
  5304. * // 'this' in static methods refer to the class itself
  5305. * return new this(brand);
  5306. * }
  5307. * },
  5308. *
  5309. * constructor: function() { }
  5310. * });
  5311. *
  5312. * var dellComputer = Computer.factory('Dell');
  5313. *
  5314. * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing
  5315. * static properties within class methods
  5316. *
  5317. * @singleton
  5318. */
  5319. (function(Class, alias, arraySlice, arrayFrom, global) {
  5320. var Manager = Ext.ClassManager = {
  5321. /**
  5322. * @property classes
  5323. * @type Object
  5324. * All classes which were defined through the ClassManager. Keys are the
  5325. * name of the classes and the values are references to the classes.
  5326. * @private
  5327. */
  5328. classes: {},
  5329. /**
  5330. * @private
  5331. */
  5332. existCache: {},
  5333. /**
  5334. * @private
  5335. */
  5336. namespaceRewrites: [{
  5337. from: 'Ext.',
  5338. to: Ext
  5339. }],
  5340. /**
  5341. * @private
  5342. */
  5343. maps: {
  5344. alternateToName: {},
  5345. aliasToName: {},
  5346. nameToAliases: {},
  5347. nameToAlternates: {}
  5348. },
  5349. /** @private */
  5350. enableNamespaceParseCache: true,
  5351. /** @private */
  5352. namespaceParseCache: {},
  5353. /** @private */
  5354. instantiators: [],
  5355. /**
  5356. * Checks if a class has already been created.
  5357. *
  5358. * @param {String} className
  5359. * @return {Boolean} exist
  5360. */
  5361. isCreated: function(className) {
  5362. var existCache = this.existCache,
  5363. i, ln, part, root, parts;
  5364. //<debug error>
  5365. if (typeof className != 'string' || className.length < 1) {
  5366. throw new Error("[Ext.ClassManager] Invalid classname, must be a string and must not be empty");
  5367. }
  5368. //</debug>
  5369. if (this.classes[className] || existCache[className]) {
  5370. return true;
  5371. }
  5372. root = global;
  5373. parts = this.parseNamespace(className);
  5374. for (i = 0, ln = parts.length; i < ln; i++) {
  5375. part = parts[i];
  5376. if (typeof part != 'string') {
  5377. root = part;
  5378. } else {
  5379. if (!root || !root[part]) {
  5380. return false;
  5381. }
  5382. root = root[part];
  5383. }
  5384. }
  5385. existCache[className] = true;
  5386. this.triggerCreated(className);
  5387. return true;
  5388. },
  5389. /**
  5390. * @private
  5391. */
  5392. createdListeners: [],
  5393. /**
  5394. * @private
  5395. */
  5396. nameCreatedListeners: {},
  5397. /**
  5398. * @private
  5399. */
  5400. triggerCreated: function(className) {
  5401. var listeners = this.createdListeners,
  5402. nameListeners = this.nameCreatedListeners,
  5403. alternateNames = this.maps.nameToAlternates[className],
  5404. names = [className],
  5405. i, ln, j, subLn, listener, name;
  5406. for (i = 0,ln = listeners.length; i < ln; i++) {
  5407. listener = listeners[i];
  5408. listener.fn.call(listener.scope, className);
  5409. }
  5410. if (alternateNames) {
  5411. names.push.apply(names, alternateNames);
  5412. }
  5413. for (i = 0,ln = names.length; i < ln; i++) {
  5414. name = names[i];
  5415. listeners = nameListeners[name];
  5416. if (listeners) {
  5417. for (j = 0,subLn = listeners.length; j < subLn; j++) {
  5418. listener = listeners[j];
  5419. listener.fn.call(listener.scope, name);
  5420. }
  5421. delete nameListeners[name];
  5422. }
  5423. }
  5424. },
  5425. /**
  5426. * @private
  5427. */
  5428. onCreated: function(fn, scope, className) {
  5429. var listeners = this.createdListeners,
  5430. nameListeners = this.nameCreatedListeners,
  5431. listener = {
  5432. fn: fn,
  5433. scope: scope
  5434. };
  5435. if (className) {
  5436. if (this.isCreated(className)) {
  5437. fn.call(scope, className);
  5438. return;
  5439. }
  5440. if (!nameListeners[className]) {
  5441. nameListeners[className] = [];
  5442. }
  5443. nameListeners[className].push(listener);
  5444. }
  5445. else {
  5446. listeners.push(listener);
  5447. }
  5448. },
  5449. /**
  5450. * Supports namespace rewriting.
  5451. * @private
  5452. */
  5453. parseNamespace: function(namespace) {
  5454. //<debug error>
  5455. if (typeof namespace != 'string') {
  5456. throw new Error("[Ext.ClassManager] Invalid namespace, must be a string");
  5457. }
  5458. //</debug>
  5459. var cache = this.namespaceParseCache;
  5460. if (this.enableNamespaceParseCache) {
  5461. if (cache.hasOwnProperty(namespace)) {
  5462. return cache[namespace];
  5463. }
  5464. }
  5465. var parts = [],
  5466. rewrites = this.namespaceRewrites,
  5467. root = global,
  5468. name = namespace,
  5469. rewrite, from, to, i, ln;
  5470. for (i = 0, ln = rewrites.length; i < ln; i++) {
  5471. rewrite = rewrites[i];
  5472. from = rewrite.from;
  5473. to = rewrite.to;
  5474. if (name === from || name.substring(0, from.length) === from) {
  5475. name = name.substring(from.length);
  5476. if (typeof to != 'string') {
  5477. root = to;
  5478. } else {
  5479. parts = parts.concat(to.split('.'));
  5480. }
  5481. break;
  5482. }
  5483. }
  5484. parts.push(root);
  5485. parts = parts.concat(name.split('.'));
  5486. if (this.enableNamespaceParseCache) {
  5487. cache[namespace] = parts;
  5488. }
  5489. return parts;
  5490. },
  5491. /**
  5492. * Creates a namespace and assign the `value` to the created object.
  5493. *
  5494. * Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject);
  5495. * alert(MyCompany.pkg.Example === someObject); // alerts true
  5496. *
  5497. * @param {String} name
  5498. * @param {Mixed} value
  5499. */
  5500. setNamespace: function(name, value) {
  5501. var root = global,
  5502. parts = this.parseNamespace(name),
  5503. ln = parts.length - 1,
  5504. leaf = parts[ln],
  5505. i, part;
  5506. for (i = 0; i < ln; i++) {
  5507. part = parts[i];
  5508. if (typeof part != 'string') {
  5509. root = part;
  5510. } else {
  5511. if (!root[part]) {
  5512. root[part] = {};
  5513. }
  5514. root = root[part];
  5515. }
  5516. }
  5517. root[leaf] = value;
  5518. return root[leaf];
  5519. },
  5520. /**
  5521. * The new Ext.ns, supports namespace rewriting.
  5522. * @private
  5523. */
  5524. createNamespaces: function() {
  5525. var root = global,
  5526. parts, part, i, j, ln, subLn;
  5527. for (i = 0, ln = arguments.length; i < ln; i++) {
  5528. parts = this.parseNamespace(arguments[i]);
  5529. for (j = 0, subLn = parts.length; j < subLn; j++) {
  5530. part = parts[j];
  5531. if (typeof part != 'string') {
  5532. root = part;
  5533. } else {
  5534. if (!root[part]) {
  5535. root[part] = {};
  5536. }
  5537. root = root[part];
  5538. }
  5539. }
  5540. }
  5541. return root;
  5542. },
  5543. /**
  5544. * Sets a name reference to a class.
  5545. *
  5546. * @param {String} name
  5547. * @param {Object} value
  5548. * @return {Ext.ClassManager} this
  5549. */
  5550. set: function(name, value) {
  5551. var me = this,
  5552. maps = me.maps,
  5553. nameToAlternates = maps.nameToAlternates,
  5554. targetName = me.getName(value),
  5555. alternates;
  5556. me.classes[name] = me.setNamespace(name, value);
  5557. if (targetName && targetName !== name) {
  5558. maps.alternateToName[name] = targetName;
  5559. alternates = nameToAlternates[targetName] || (nameToAlternates[targetName] = []);
  5560. alternates.push(name);
  5561. }
  5562. return this;
  5563. },
  5564. /**
  5565. * Retrieve a class by its name.
  5566. *
  5567. * @param {String} name
  5568. * @return {Ext.Class} class
  5569. */
  5570. get: function(name) {
  5571. var classes = this.classes;
  5572. if (classes[name]) {
  5573. return classes[name];
  5574. }
  5575. var root = global,
  5576. parts = this.parseNamespace(name),
  5577. part, i, ln;
  5578. for (i = 0, ln = parts.length; i < ln; i++) {
  5579. part = parts[i];
  5580. if (typeof part != 'string') {
  5581. root = part;
  5582. } else {
  5583. if (!root || !root[part]) {
  5584. return null;
  5585. }
  5586. root = root[part];
  5587. }
  5588. }
  5589. return root;
  5590. },
  5591. /**
  5592. * Register the alias for a class.
  5593. *
  5594. * @param {Ext.Class/String} cls a reference to a class or a `className`.
  5595. * @param {String} alias Alias to use when referring to this class.
  5596. */
  5597. setAlias: function(cls, alias) {
  5598. var aliasToNameMap = this.maps.aliasToName,
  5599. nameToAliasesMap = this.maps.nameToAliases,
  5600. className;
  5601. if (typeof cls == 'string') {
  5602. className = cls;
  5603. } else {
  5604. className = this.getName(cls);
  5605. }
  5606. if (alias && aliasToNameMap[alias] !== className) {
  5607. //<debug info>
  5608. if (aliasToNameMap[alias]) {
  5609. Ext.Logger.info("[Ext.ClassManager] Overriding existing alias: '" + alias + "' " +
  5610. "of: '" + aliasToNameMap[alias] + "' with: '" + className + "'. Be sure it's intentional.");
  5611. }
  5612. //</debug>
  5613. aliasToNameMap[alias] = className;
  5614. }
  5615. if (!nameToAliasesMap[className]) {
  5616. nameToAliasesMap[className] = [];
  5617. }
  5618. if (alias) {
  5619. Ext.Array.include(nameToAliasesMap[className], alias);
  5620. }
  5621. return this;
  5622. },
  5623. /**
  5624. * Adds a batch of class name to alias mappings
  5625. * @param {Object} aliases The set of mappings of the form
  5626. * className : [values...]
  5627. */
  5628. addNameAliasMappings: function(aliases){
  5629. var aliasToNameMap = this.maps.aliasToName,
  5630. nameToAliasesMap = this.maps.nameToAliases,
  5631. className, aliasList, alias, i;
  5632. for (className in aliases) {
  5633. aliasList = nameToAliasesMap[className] ||
  5634. (nameToAliasesMap[className] = []);
  5635. for (i = 0; i < aliases[className].length; i++) {
  5636. alias = aliases[className][i];
  5637. if (!aliasToNameMap[alias]) {
  5638. aliasToNameMap[alias] = className;
  5639. aliasList.push(alias);
  5640. }
  5641. }
  5642. }
  5643. return this;
  5644. },
  5645. /**
  5646. *
  5647. * @param {Object} alternates The set of mappings of the form
  5648. * className : [values...]
  5649. */
  5650. addNameAlternateMappings: function(alternates) {
  5651. var alternateToName = this.maps.alternateToName,
  5652. nameToAlternates = this.maps.nameToAlternates,
  5653. className, aliasList, alternate, i;
  5654. for (className in alternates) {
  5655. aliasList = nameToAlternates[className] ||
  5656. (nameToAlternates[className] = []);
  5657. for (i = 0; i < alternates[className].length; i++) {
  5658. alternate = alternates[className];
  5659. if (!alternateToName[alternate]) {
  5660. alternateToName[alternate] = className;
  5661. aliasList.push(alternate);
  5662. }
  5663. }
  5664. }
  5665. return this;
  5666. },
  5667. /**
  5668. * Get a reference to the class by its alias.
  5669. *
  5670. * @param {String} alias
  5671. * @return {Ext.Class} class
  5672. */
  5673. getByAlias: function(alias) {
  5674. return this.get(this.getNameByAlias(alias));
  5675. },
  5676. /**
  5677. * Get the name of a class by its alias.
  5678. *
  5679. * @param {String} alias
  5680. * @return {String} className
  5681. */
  5682. getNameByAlias: function(alias) {
  5683. return this.maps.aliasToName[alias] || '';
  5684. },
  5685. /**
  5686. * Get the name of a class by its alternate name.
  5687. *
  5688. * @param {String} alternate
  5689. * @return {String} className
  5690. */
  5691. getNameByAlternate: function(alternate) {
  5692. return this.maps.alternateToName[alternate] || '';
  5693. },
  5694. /**
  5695. * Get the aliases of a class by the class name
  5696. *
  5697. * @param {String} name
  5698. * @return {Array} aliases
  5699. */
  5700. getAliasesByName: function(name) {
  5701. return this.maps.nameToAliases[name] || [];
  5702. },
  5703. /**
  5704. * Get the name of the class by its reference or its instance;
  5705. * usually invoked by the shorthand {@link Ext#getClassName Ext.getClassName}
  5706. *
  5707. * Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
  5708. *
  5709. * @param {Ext.Class/Object} object
  5710. * @return {String} className
  5711. */
  5712. getName: function(object) {
  5713. return object && object.$className || '';
  5714. },
  5715. /**
  5716. * Get the class of the provided object; returns null if it's not an instance
  5717. * of any class created with Ext.define. This is usually invoked by the shorthand {@link Ext#getClass Ext.getClass}.
  5718. *
  5719. * var component = new Ext.Component();
  5720. *
  5721. * Ext.ClassManager.getClass(component); // returns Ext.Component
  5722. *
  5723. * @param {Object} object
  5724. * @return {Ext.Class} class
  5725. */
  5726. getClass: function(object) {
  5727. return object && object.self || null;
  5728. },
  5729. /**
  5730. * @private
  5731. */
  5732. create: function(className, data, createdFn) {
  5733. //<debug error>
  5734. if (typeof className != 'string') {
  5735. throw new Error("[Ext.define] Invalid class name '" + className + "' specified, must be a non-empty string");
  5736. }
  5737. //</debug>
  5738. data.$className = className;
  5739. return new Class(data, function() {
  5740. var postprocessorStack = data.postprocessors || Manager.defaultPostprocessors,
  5741. registeredPostprocessors = Manager.postprocessors,
  5742. index = 0,
  5743. postprocessors = [],
  5744. postprocessor, process, i, ln, j, subLn, postprocessorProperties, postprocessorProperty;
  5745. delete data.postprocessors;
  5746. for (i = 0,ln = postprocessorStack.length; i < ln; i++) {
  5747. postprocessor = postprocessorStack[i];
  5748. if (typeof postprocessor == 'string') {
  5749. postprocessor = registeredPostprocessors[postprocessor];
  5750. postprocessorProperties = postprocessor.properties;
  5751. if (postprocessorProperties === true) {
  5752. postprocessors.push(postprocessor.fn);
  5753. }
  5754. else if (postprocessorProperties) {
  5755. for (j = 0,subLn = postprocessorProperties.length; j < subLn; j++) {
  5756. postprocessorProperty = postprocessorProperties[j];
  5757. if (data.hasOwnProperty(postprocessorProperty)) {
  5758. postprocessors.push(postprocessor.fn);
  5759. break;
  5760. }
  5761. }
  5762. }
  5763. }
  5764. else {
  5765. postprocessors.push(postprocessor);
  5766. }
  5767. }
  5768. process = function(clsName, cls, clsData) {
  5769. postprocessor = postprocessors[index++];
  5770. if (!postprocessor) {
  5771. Manager.set(className, cls);
  5772. if (createdFn) {
  5773. createdFn.call(cls, cls);
  5774. }
  5775. Manager.triggerCreated(className);
  5776. return;
  5777. }
  5778. if (postprocessor.call(this, clsName, cls, clsData, process) !== false) {
  5779. process.apply(this, arguments);
  5780. }
  5781. };
  5782. process.call(Manager, className, this, data);
  5783. });
  5784. },
  5785. createOverride: function(className, data) {
  5786. var overriddenClassName = data.override,
  5787. requires = Ext.Array.from(data.requires);
  5788. delete data.override;
  5789. delete data.requires;
  5790. this.existCache[className] = true;
  5791. Ext.require(requires, function() {
  5792. // Override the target class right after it's created
  5793. this.onCreated(function() {
  5794. this.get(overriddenClassName).override(data);
  5795. // This push the overridding file itself into Ext.Loader.history
  5796. // Hence if the target class never exists, the overriding file will
  5797. // never be included in the build
  5798. this.triggerCreated(className);
  5799. }, this, overriddenClassName);
  5800. }, this);
  5801. return this;
  5802. },
  5803. /**
  5804. * Instantiate a class by its alias; usually invoked by the convenient shorthand {@link Ext#createByAlias Ext.createByAlias}
  5805. * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
  5806. * attempt to load the class via synchronous loading.
  5807. *
  5808. * var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800 });
  5809. *
  5810. * @param {String} alias
  5811. * @param {Mixed...} args Additional arguments after the alias will be passed to the class constructor.
  5812. * @return {Object} instance
  5813. */
  5814. instantiateByAlias: function() {
  5815. var alias = arguments[0],
  5816. args = arraySlice.call(arguments),
  5817. className = this.getNameByAlias(alias);
  5818. if (!className) {
  5819. className = this.maps.aliasToName[alias];
  5820. //<debug error>
  5821. if (!className) {
  5822. throw new Error("[Ext.createByAlias] Cannot create an instance of unrecognized alias: " + alias);
  5823. }
  5824. //</debug>
  5825. //<debug warn>
  5826. Ext.Logger.warn("[Ext.Loader] Synchronously loading '" + className + "'; consider adding " +
  5827. "Ext.require('" + alias + "') above Ext.onReady");
  5828. //</debug>
  5829. Ext.syncRequire(className);
  5830. }
  5831. args[0] = className;
  5832. return this.instantiate.apply(this, args);
  5833. },
  5834. /**
  5835. * Instantiate a class by either full name, alias or alternate name; usually invoked by the convenient
  5836. * shorthand {@link Ext.ClassManager#create Ext.create}.
  5837. *
  5838. * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
  5839. * attempt to load the class via synchronous loading.
  5840. *
  5841. * For example, all these three lines return the same result:
  5842. *
  5843. * // alias
  5844. * var formPanel = Ext.create('widget.formpanel', { width: 600, height: 800 });
  5845. *
  5846. * // alternate name
  5847. * var formPanel = Ext.create('Ext.form.FormPanel', { width: 600, height: 800 });
  5848. *
  5849. * // full class name
  5850. * var formPanel = Ext.create('Ext.form.Panel', { width: 600, height: 800 });
  5851. *
  5852. * @param {String} name
  5853. * @param {Mixed} args Additional arguments after the name will be passed to the class' constructor.
  5854. * @return {Object} instance
  5855. */
  5856. instantiate: function() {
  5857. var name = arguments[0],
  5858. args = arraySlice.call(arguments, 1),
  5859. alias = name,
  5860. possibleName, cls;
  5861. if (typeof name != 'function') {
  5862. //<debug error>
  5863. if ((typeof name != 'string' || name.length < 1)) {
  5864. throw new Error("[Ext.create] Invalid class name or alias '" + name + "' specified, must be a non-empty string");
  5865. }
  5866. //</debug>
  5867. cls = this.get(name);
  5868. }
  5869. else {
  5870. cls = name;
  5871. }
  5872. // No record of this class name, it's possibly an alias, so look it up
  5873. if (!cls) {
  5874. possibleName = this.getNameByAlias(name);
  5875. if (possibleName) {
  5876. name = possibleName;
  5877. cls = this.get(name);
  5878. }
  5879. }
  5880. // Still no record of this class name, it's possibly an alternate name, so look it up
  5881. if (!cls) {
  5882. possibleName = this.getNameByAlternate(name);
  5883. if (possibleName) {
  5884. name = possibleName;
  5885. cls = this.get(name);
  5886. }
  5887. }
  5888. // Still not existing at this point, try to load it via synchronous mode as the last resort
  5889. if (!cls) {
  5890. //<debug warn>
  5891. Ext.Logger.warn("[Ext.Loader] Synchronously loading '" + name + "'; consider adding '" +
  5892. ((possibleName) ? alias : name) + "' explicitly as a require of the corresponding class");
  5893. //</debug>
  5894. Ext.syncRequire(name);
  5895. cls = this.get(name);
  5896. }
  5897. //<debug error>
  5898. if (!cls) {
  5899. throw new Error("[Ext.create] Cannot create an instance of unrecognized class name / alias: " + alias);
  5900. }
  5901. if (typeof cls != 'function') {
  5902. throw new Error("[Ext.create] '" + name + "' is a singleton and cannot be instantiated");
  5903. }
  5904. //</debug>
  5905. return this.getInstantiator(args.length)(cls, args);
  5906. },
  5907. /**
  5908. * @private
  5909. * @param name
  5910. * @param args
  5911. */
  5912. dynInstantiate: function(name, args) {
  5913. args = arrayFrom(args, true);
  5914. args.unshift(name);
  5915. return this.instantiate.apply(this, args);
  5916. },
  5917. /**
  5918. * @private
  5919. * @param length
  5920. */
  5921. getInstantiator: function(length) {
  5922. var instantiators = this.instantiators,
  5923. instantiator;
  5924. instantiator = instantiators[length];
  5925. if (!instantiator) {
  5926. var i = length,
  5927. args = [];
  5928. for (i = 0; i < length; i++) {
  5929. args.push('a[' + i + ']');
  5930. }
  5931. instantiator = instantiators[length] = new Function('c', 'a', 'return new c(' + args.join(',') + ')');
  5932. //<debug>
  5933. instantiator.displayName = "Ext.ClassManager.instantiate" + length;
  5934. //</debug>
  5935. }
  5936. return instantiator;
  5937. },
  5938. /**
  5939. * @private
  5940. */
  5941. postprocessors: {},
  5942. /**
  5943. * @private
  5944. */
  5945. defaultPostprocessors: [],
  5946. /**
  5947. * Register a post-processor function.
  5948. *
  5949. * @private
  5950. * @param {String} name
  5951. * @param {Function} postprocessor
  5952. */
  5953. registerPostprocessor: function(name, fn, properties, position, relativeTo) {
  5954. if (!position) {
  5955. position = 'last';
  5956. }
  5957. if (!properties) {
  5958. properties = [name];
  5959. }
  5960. this.postprocessors[name] = {
  5961. name: name,
  5962. properties: properties || false,
  5963. fn: fn
  5964. };
  5965. this.setDefaultPostprocessorPosition(name, position, relativeTo);
  5966. return this;
  5967. },
  5968. /**
  5969. * Set the default post processors array stack which are applied to every class.
  5970. *
  5971. * @private
  5972. * @param {String/Array} The name of a registered post processor or an array of registered names.
  5973. * @return {Ext.ClassManager} this
  5974. */
  5975. setDefaultPostprocessors: function(postprocessors) {
  5976. this.defaultPostprocessors = arrayFrom(postprocessors);
  5977. return this;
  5978. },
  5979. /**
  5980. * Insert this post-processor at a specific position in the stack, optionally relative to
  5981. * any existing post-processor
  5982. *
  5983. * @private
  5984. * @param {String} name The post-processor name. Note that it needs to be registered with
  5985. * {@link Ext.ClassManager#registerPostprocessor} before this
  5986. * @param {String} offset The insertion position. Four possible values are:
  5987. * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
  5988. * @param {String} relativeName
  5989. * @return {Ext.ClassManager} this
  5990. */
  5991. setDefaultPostprocessorPosition: function(name, offset, relativeName) {
  5992. var defaultPostprocessors = this.defaultPostprocessors,
  5993. index;
  5994. if (typeof offset == 'string') {
  5995. if (offset === 'first') {
  5996. defaultPostprocessors.unshift(name);
  5997. return this;
  5998. }
  5999. else if (offset === 'last') {
  6000. defaultPostprocessors.push(name);
  6001. return this;
  6002. }
  6003. offset = (offset === 'after') ? 1 : -1;
  6004. }
  6005. index = Ext.Array.indexOf(defaultPostprocessors, relativeName);
  6006. if (index !== -1) {
  6007. Ext.Array.splice(defaultPostprocessors, Math.max(0, index + offset), 0, name);
  6008. }
  6009. return this;
  6010. },
  6011. /**
  6012. * Converts a string expression to an array of matching class names. An expression can either refers to class aliases
  6013. * or class names. Expressions support wildcards:
  6014. *
  6015. * // returns ['Ext.window.Window']
  6016. * var window = Ext.ClassManager.getNamesByExpression('widget.window');
  6017. *
  6018. * // returns ['widget.panel', 'widget.window', ...]
  6019. * var allWidgets = Ext.ClassManager.getNamesByExpression('widget.*');
  6020. *
  6021. * // returns ['Ext.data.Store', 'Ext.data.ArrayProxy', ...]
  6022. * var allData = Ext.ClassManager.getNamesByExpression('Ext.data.*');
  6023. *
  6024. * @param {String} expression
  6025. * @return {Array} classNames
  6026. */
  6027. getNamesByExpression: function(expression) {
  6028. var nameToAliasesMap = this.maps.nameToAliases,
  6029. names = [],
  6030. name, alias, aliases, possibleName, regex, i, ln;
  6031. //<debug error>
  6032. if (typeof expression != 'string' || expression.length < 1) {
  6033. throw new Error("[Ext.ClassManager.getNamesByExpression] Expression " + expression + " is invalid, must be a non-empty string");
  6034. }
  6035. //</debug>
  6036. if (expression.indexOf('*') !== -1) {
  6037. expression = expression.replace(/\*/g, '(.*?)');
  6038. regex = new RegExp('^' + expression + '$');
  6039. for (name in nameToAliasesMap) {
  6040. if (nameToAliasesMap.hasOwnProperty(name)) {
  6041. aliases = nameToAliasesMap[name];
  6042. if (name.search(regex) !== -1) {
  6043. names.push(name);
  6044. }
  6045. else {
  6046. for (i = 0, ln = aliases.length; i < ln; i++) {
  6047. alias = aliases[i];
  6048. if (alias.search(regex) !== -1) {
  6049. names.push(name);
  6050. break;
  6051. }
  6052. }
  6053. }
  6054. }
  6055. }
  6056. } else {
  6057. possibleName = this.getNameByAlias(expression);
  6058. if (possibleName) {
  6059. names.push(possibleName);
  6060. } else {
  6061. possibleName = this.getNameByAlternate(expression);
  6062. if (possibleName) {
  6063. names.push(possibleName);
  6064. } else {
  6065. names.push(expression);
  6066. }
  6067. }
  6068. }
  6069. return names;
  6070. }
  6071. };
  6072. //<feature classSystem.alias>
  6073. /**
  6074. * @cfg {String[]} alias
  6075. * @member Ext.Class
  6076. * List of short aliases for class names. Most useful for defining xtypes for widgets:
  6077. *
  6078. * Ext.define('MyApp.CoolPanel', {
  6079. * extend: 'Ext.panel.Panel',
  6080. * alias: ['widget.coolpanel'],
  6081. * title: 'Yeah!'
  6082. * });
  6083. *
  6084. * // Using Ext.create
  6085. * Ext.create('widget.coolpanel');
  6086. *
  6087. * // Using the shorthand for widgets and in xtypes
  6088. * Ext.widget('panel', {
  6089. * items: [
  6090. * {xtype: 'coolpanel', html: 'Foo'},
  6091. * {xtype: 'coolpanel', html: 'Bar'}
  6092. * ]
  6093. * });
  6094. */
  6095. Manager.registerPostprocessor('alias', function(name, cls, data) {
  6096. var aliases = data.alias,
  6097. i, ln;
  6098. for (i = 0,ln = aliases.length; i < ln; i++) {
  6099. alias = aliases[i];
  6100. this.setAlias(cls, alias);
  6101. }
  6102. }, ['xtype', 'alias']);
  6103. //</feature>
  6104. //<feature classSystem.singleton>
  6105. /**
  6106. * @cfg {Boolean} singleton
  6107. * @member Ext.Class
  6108. * When set to true, the class will be instantiated as singleton. For example:
  6109. *
  6110. * Ext.define('Logger', {
  6111. * singleton: true,
  6112. * log: function(msg) {
  6113. * console.log(msg);
  6114. * }
  6115. * });
  6116. *
  6117. * Logger.log('Hello');
  6118. */
  6119. Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
  6120. fn.call(this, name, new cls(), data);
  6121. return false;
  6122. });
  6123. //</feature>
  6124. //<feature classSystem.alternateClassName>
  6125. /**
  6126. * @cfg {String/String[]} alternateClassName
  6127. * @member Ext.Class
  6128. * Defines alternate names for this class. For example:
  6129. *
  6130. * @example
  6131. * Ext.define('Developer', {
  6132. * alternateClassName: ['Coder', 'Hacker'],
  6133. * code: function(msg) {
  6134. * alert('Typing... ' + msg);
  6135. * }
  6136. * });
  6137. *
  6138. * var joe = Ext.create('Developer');
  6139. * joe.code('stackoverflow');
  6140. *
  6141. * var rms = Ext.create('Hacker');
  6142. * rms.code('hack hack');
  6143. */
  6144. Manager.registerPostprocessor('alternateClassName', function(name, cls, data) {
  6145. var alternates = data.alternateClassName,
  6146. i, ln, alternate;
  6147. if (!(alternates instanceof Array)) {
  6148. alternates = [alternates];
  6149. }
  6150. for (i = 0, ln = alternates.length; i < ln; i++) {
  6151. alternate = alternates[i];
  6152. //<debug error>
  6153. if (typeof alternate != 'string') {
  6154. throw new Error("[Ext.define] Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string");
  6155. }
  6156. //</debug>
  6157. this.set(alternate, cls);
  6158. }
  6159. });
  6160. //</feature>
  6161. Ext.apply(Ext, {
  6162. /**
  6163. * Instantiate a class by either full name, alias or alternate name.
  6164. *
  6165. * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
  6166. * attempt to load the class via synchronous loading.
  6167. *
  6168. * For example, all these three lines return the same result:
  6169. *
  6170. * // alias
  6171. * var formPanel = Ext.create('widget.formpanel', { width: 600, height: 800 });
  6172. *
  6173. * // alternate name
  6174. * var formPanel = Ext.create('Ext.form.FormPanel', { width: 600, height: 800 });
  6175. *
  6176. * // full class name
  6177. * var formPanel = Ext.create('Ext.form.Panel', { width: 600, height: 800 });
  6178. *
  6179. * @param {String} name
  6180. * @param {Mixed} args Additional arguments after the name will be passed to the class' constructor.
  6181. * @return {Object} instance
  6182. * @member Ext
  6183. */
  6184. create: alias(Manager, 'instantiate'),
  6185. /**
  6186. * Convenient shorthand to create a widget by its xtype, also see {@link Ext.ClassManager#instantiateByAlias}
  6187. *
  6188. * var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button')
  6189. * var panel = Ext.widget('panel'); // Equivalent to Ext.create('widget.panel')
  6190. *
  6191. * @member Ext
  6192. * @method widget
  6193. */
  6194. widget: function(name) {
  6195. var args = arraySlice.call(arguments);
  6196. args[0] = 'widget.' + name;
  6197. return Manager.instantiateByAlias.apply(Manager, args);
  6198. },
  6199. /**
  6200. * Convenient shorthand, see {@link Ext.ClassManager#instantiateByAlias}.
  6201. * @member Ext
  6202. * @method createByAlias
  6203. */
  6204. createByAlias: alias(Manager, 'instantiateByAlias'),
  6205. /**
  6206. * Defines a class or override. A basic class is defined like this:
  6207. *
  6208. * Ext.define('My.awesome.Class', {
  6209. * someProperty: 'something',
  6210. *
  6211. * someMethod: function(s) {
  6212. * console.log(s + this.someProperty);
  6213. * }
  6214. * });
  6215. *
  6216. * var obj = new My.awesome.Class();
  6217. *
  6218. * obj.someMethod('Say '); // logs 'Say something' to the console
  6219. *
  6220. * To defines an override, include the `override` property. The content of an
  6221. * override is aggregated with the specified class in order to extend or modify
  6222. * that class. This can be as simple as setting default property values or it can
  6223. * extend and/or replace methods. This can also extend the statics of the class.
  6224. *
  6225. * One use for an override is to break a large class into manageable pieces.
  6226. *
  6227. * // File: /src/app/Panel.js
  6228. * Ext.define('My.app.Panel', {
  6229. * extend: 'Ext.panel.Panel',
  6230. * requires: [
  6231. * 'My.app.PanelPart2',
  6232. * 'My.app.PanelPart3'
  6233. * ],
  6234. *
  6235. * constructor: function (config) {
  6236. * this.callParent(arguments); // calls Ext.panel.Panel's constructor
  6237. * // ...
  6238. * },
  6239. *
  6240. * statics: {
  6241. * method: function () {
  6242. * return 'abc';
  6243. * }
  6244. * }
  6245. * });
  6246. *
  6247. * // File: /src/app/PanelPart2.js
  6248. * Ext.define('My.app.PanelPart2', {
  6249. * override: 'My.app.Panel',
  6250. *
  6251. * constructor: function (config) {
  6252. * this.callParent(arguments); // calls My.app.Panel's constructor
  6253. * // ...
  6254. * }
  6255. * });
  6256. *
  6257. * Another use for an override is to provide optional parts of classes that can be
  6258. * independently required. In this case, the class may even be unaware of the
  6259. * override altogether.
  6260. *
  6261. * Ext.define('My.ux.CoolTip', {
  6262. * override: 'Ext.tip.ToolTip',
  6263. *
  6264. * constructor: function (config) {
  6265. * this.callParent(arguments); // calls Ext.tip.ToolTip's constructor
  6266. * // ...
  6267. * }
  6268. * });
  6269. *
  6270. * The above override can now be required as normal.
  6271. *
  6272. * Ext.define('My.app.App', {
  6273. * requires: [
  6274. * 'My.ux.CoolTip'
  6275. * ]
  6276. * });
  6277. *
  6278. * Overrides can also contain statics:
  6279. *
  6280. * Ext.define('My.app.BarMod', {
  6281. * override: 'Ext.foo.Bar',
  6282. *
  6283. * statics: {
  6284. * method: function (x) {
  6285. * return this.callParent([x * 2]); // call Ext.foo.Bar.method
  6286. * }
  6287. * }
  6288. * });
  6289. *
  6290. * __IMPORTANT:__ An override is only included in a build if the class it overrides is
  6291. * required. Otherwise, the override, like the target class, is not included.
  6292. *
  6293. * @param {String} className The class name to create in string dot-namespaced format, for example:
  6294. * 'My.very.awesome.Class', 'FeedViewer.plugin.CoolPager'
  6295. *
  6296. * It is highly recommended to follow this simple convention:
  6297. * - The root and the class name are 'CamelCased'
  6298. * - Everything else is lower-cased
  6299. *
  6300. * @param {Object} data The key - value pairs of properties to apply to this class. Property names can be of
  6301. * any valid strings, except those in the reserved listed below:
  6302. *
  6303. * - `mixins`
  6304. * - `statics`
  6305. * - `config`
  6306. * - `alias`
  6307. * - `self`
  6308. * - `singleton`
  6309. * - `alternateClassName`
  6310. * - `override`
  6311. *
  6312. * @param {Function} [createdFn] Optional callback to execute after the class (or override)
  6313. * is created. The execution scope (`this`) will be the newly created class itself.
  6314. * @return {Ext.Base}
  6315. *
  6316. * @member Ext
  6317. * @method define
  6318. */
  6319. define: function (className, data, createdFn) {
  6320. if ('override' in data) {
  6321. return Manager.createOverride.apply(Manager, arguments);
  6322. }
  6323. return Manager.create.apply(Manager, arguments);
  6324. },
  6325. /**
  6326. * Convenient shorthand for {@link Ext.ClassManager#getName}.
  6327. * @member Ext
  6328. * @method getClassName
  6329. * @inheritdoc Ext.ClassManager#getName
  6330. */
  6331. getClassName: alias(Manager, 'getName'),
  6332. /**
  6333. * Returns the display name for object. This name is looked for in order from the following places:
  6334. *
  6335. * - `displayName` field of the object.
  6336. * - `$name` and `$class` fields of the object.
  6337. * - '$className` field of the object.
  6338. *
  6339. * This method is used by {@link Ext.Logger#log} to display information about objects.
  6340. *
  6341. * @param {Mixed} [object] The object who's display name to determine.
  6342. * @return {String} The determined display name, or "Anonymous" if none found.
  6343. * @member Ext
  6344. */
  6345. getDisplayName: function(object) {
  6346. if (object) {
  6347. if (object.displayName) {
  6348. return object.displayName;
  6349. }
  6350. if (object.$name && object.$class) {
  6351. return Ext.getClassName(object.$class) + '#' + object.$name;
  6352. }
  6353. if (object.$className) {
  6354. return object.$className;
  6355. }
  6356. }
  6357. return 'Anonymous';
  6358. },
  6359. /**
  6360. * Convenient shorthand, see {@link Ext.ClassManager#getClass}.
  6361. * @member Ext
  6362. * @method getClass
  6363. */
  6364. getClass: alias(Manager, 'getClass'),
  6365. /**
  6366. * Creates namespaces to be used for scoping variables and classes so that they are not global.
  6367. * Specifying the last node of a namespace implicitly creates all other nodes. Usage:
  6368. *
  6369. * Ext.namespace('Company', 'Company.data');
  6370. *
  6371. * // equivalent and preferable to the above syntax
  6372. * Ext.namespace('Company.data');
  6373. *
  6374. * Company.Widget = function() {
  6375. * // ...
  6376. * };
  6377. *
  6378. * Company.data.CustomStore = function(config) {
  6379. * // ...
  6380. * };
  6381. *
  6382. * @param {String} namespace1
  6383. * @param {String} namespace2
  6384. * @param {String} etc
  6385. * @return {Object} The namespace object. If multiple arguments are passed, this will be the last namespace created.
  6386. * @member Ext
  6387. * @method namespace
  6388. */
  6389. namespace: alias(Manager, 'createNamespaces')
  6390. });
  6391. /**
  6392. * Old name for {@link Ext#widget}.
  6393. * @deprecated 4.0.0 Please use {@link Ext#widget} instead.
  6394. * @method createWidget
  6395. * @member Ext
  6396. */
  6397. Ext.createWidget = Ext.widget;
  6398. /**
  6399. * Convenient alias for {@link Ext#namespace Ext.namespace}.
  6400. * @member Ext
  6401. * @method ns
  6402. */
  6403. Ext.ns = Ext.namespace;
  6404. Class.registerPreprocessor('className', function(cls, data) {
  6405. if (data.$className) {
  6406. cls.$className = data.$className;
  6407. //<debug>
  6408. cls.displayName = cls.$className;
  6409. //</debug>
  6410. }
  6411. }, true, 'first');
  6412. Class.registerPreprocessor('alias', function(cls, data) {
  6413. var prototype = cls.prototype,
  6414. xtypes = arrayFrom(data.xtype),
  6415. aliases = arrayFrom(data.alias),
  6416. widgetPrefix = 'widget.',
  6417. widgetPrefixLength = widgetPrefix.length,
  6418. xtypesChain = Array.prototype.slice.call(prototype.xtypesChain || []),
  6419. xtypesMap = Ext.merge({}, prototype.xtypesMap || {}),
  6420. i, ln, alias, xtype;
  6421. for (i = 0,ln = aliases.length; i < ln; i++) {
  6422. alias = aliases[i];
  6423. //<debug error>
  6424. if (typeof alias != 'string' || alias.length < 1) {
  6425. throw new Error("[Ext.define] Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string");
  6426. }
  6427. //</debug>
  6428. if (alias.substring(0, widgetPrefixLength) === widgetPrefix) {
  6429. xtype = alias.substring(widgetPrefixLength);
  6430. Ext.Array.include(xtypes, xtype);
  6431. }
  6432. }
  6433. cls.xtype = data.xtype = xtypes[0];
  6434. data.xtypes = xtypes;
  6435. for (i = 0,ln = xtypes.length; i < ln; i++) {
  6436. xtype = xtypes[i];
  6437. if (!xtypesMap[xtype]) {
  6438. xtypesMap[xtype] = true;
  6439. xtypesChain.push(xtype);
  6440. }
  6441. }
  6442. data.xtypesChain = xtypesChain;
  6443. data.xtypesMap = xtypesMap;
  6444. Ext.Function.interceptAfter(data, 'onClassCreated', function() {
  6445. var mixins = prototype.mixins,
  6446. key, mixin;
  6447. for (key in mixins) {
  6448. if (mixins.hasOwnProperty(key)) {
  6449. mixin = mixins[key];
  6450. xtypes = mixin.xtypes;
  6451. if (xtypes) {
  6452. for (i = 0,ln = xtypes.length; i < ln; i++) {
  6453. xtype = xtypes[i];
  6454. if (!xtypesMap[xtype]) {
  6455. xtypesMap[xtype] = true;
  6456. xtypesChain.push(xtype);
  6457. }
  6458. }
  6459. }
  6460. }
  6461. }
  6462. });
  6463. for (i = 0,ln = xtypes.length; i < ln; i++) {
  6464. xtype = xtypes[i];
  6465. //<debug error>
  6466. if (typeof xtype != 'string' || xtype.length < 1) {
  6467. throw new Error("[Ext.define] Invalid xtype of: '" + xtype + "' for class: '" + name + "'; must be a valid non-empty string");
  6468. }
  6469. //</debug>
  6470. Ext.Array.include(aliases, widgetPrefix + xtype);
  6471. }
  6472. data.alias = aliases;
  6473. }, ['xtype', 'alias']);
  6474. })(Ext.Class, Ext.Function.alias, Array.prototype.slice, Ext.Array.from, Ext.global);
  6475. //@tag foundation,core
  6476. //@define Ext.Loader
  6477. //@require Ext.ClassManager
  6478. /**
  6479. * @class Ext.Loader
  6480. *
  6481. * @author Jacky Nguyen <jacky@sencha.com>
  6482. * @docauthor Jacky Nguyen <jacky@sencha.com>
  6483. * @aside guide mvc_dependencies
  6484. *
  6485. * Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
  6486. * via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
  6487. * approaches, and leverage their advantages for the best development flow.
  6488. * We'll discuss about the pros and cons of each approach.
  6489. *
  6490. * __Note:__ The Loader is only enabled by default in development versions of the library (eg sencha-touch-debug.js). To
  6491. * explicitly enable the loader, use `Ext.Loader.setConfig({ enabled: true });` before the start of your script.
  6492. *
  6493. * ## Asynchronous Loading
  6494. *
  6495. * - Advantages:
  6496. * + Cross-domain
  6497. * + No web server needed: you can run the application via the file system protocol (i.e: `file://path/to/your/index
  6498. * .html`)
  6499. * + Best possible debugging experience: error messages come with the exact file name and line number
  6500. *
  6501. * - Disadvantages:
  6502. * + Dependencies need to be specified before-hand
  6503. *
  6504. * ### Method 1: Explicitly include what you need: ###
  6505. *
  6506. * // Syntax
  6507. * // Ext.require({String/Array} expressions);
  6508. *
  6509. * // Example: Single alias
  6510. * Ext.require('widget.window');
  6511. *
  6512. * // Example: Single class name
  6513. * Ext.require('Ext.window.Window');
  6514. *
  6515. * // Example: Multiple aliases / class names mix
  6516. * Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
  6517. *
  6518. * // Wildcards
  6519. * Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
  6520. *
  6521. * ### Method 2: Explicitly exclude what you don't need: ###
  6522. *
  6523. * // Syntax: Note that it must be in this chaining format.
  6524. * // Ext.exclude({String/Array} expressions)
  6525. * // .require({String/Array} expressions);
  6526. *
  6527. * // Include everything except Ext.data.*
  6528. * Ext.exclude('Ext.data.*').require('*');
  6529. *
  6530. * // Include all widgets except widget.checkbox*,
  6531. * // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
  6532. * Ext.exclude('widget.checkbox*').require('widget.*');
  6533. *
  6534. * # Synchronous Loading on Demand #
  6535. *
  6536. * - *Advantages:*
  6537. * + There's no need to specify dependencies before-hand, which is always the convenience of including ext-all.js
  6538. * before
  6539. *
  6540. * - *Disadvantages:*
  6541. * + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
  6542. * + Must be from the same domain due to XHR restriction
  6543. * + Need a web server, same reason as above
  6544. *
  6545. * There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
  6546. *
  6547. * Ext.create('widget.window', {}); // Instead of new Ext.window.Window({...});
  6548. *
  6549. * Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
  6550. *
  6551. * Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
  6552. *
  6553. * Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
  6554. * existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load the given
  6555. * class and all its dependencies.
  6556. *
  6557. * # Hybrid Loading - The Best of Both Worlds #
  6558. *
  6559. * It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
  6560. *
  6561. * ### Step 1: Start writing your application using synchronous approach. ###
  6562. * Ext.Loader will automatically fetch all dependencies on demand as they're
  6563. * needed during run-time. For example:
  6564. *
  6565. * Ext.onReady(function(){
  6566. * var window = Ext.createWidget('window', {
  6567. * width: 500,
  6568. * height: 300,
  6569. * layout: {
  6570. * type: 'border',
  6571. * padding: 5
  6572. * },
  6573. * title: 'Hello Dialog',
  6574. * items: [{
  6575. * title: 'Navigation',
  6576. * collapsible: true,
  6577. * region: 'west',
  6578. * width: 200,
  6579. * html: 'Hello',
  6580. * split: true
  6581. * }, {
  6582. * title: 'TabPanel',
  6583. * region: 'center'
  6584. * }]
  6585. * });
  6586. *
  6587. * window.show();
  6588. * });
  6589. *
  6590. * ### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these: ###
  6591. *
  6592. * [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code
  6593. * ClassManager.js:432
  6594. * [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
  6595. *
  6596. * Simply copy and paste the suggested code above `Ext.onReady`, i.e:
  6597. *
  6598. * Ext.require('Ext.window.Window');
  6599. * Ext.require('Ext.layout.container.Border');
  6600. *
  6601. * Ext.onReady(function () {
  6602. * // ...
  6603. * });
  6604. *
  6605. * Everything should now load via asynchronous mode.
  6606. *
  6607. * # Deployment #
  6608. *
  6609. * It's important to note that dynamic loading should only be used during development on your local machines.
  6610. * During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
  6611. * the whole process of transitioning from / to between development / maintenance and production as easy as
  6612. * possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies your application
  6613. * needs in the exact loading sequence. It's as simple as concatenating all files in this array into one,
  6614. * then include it on top of your application.
  6615. *
  6616. * This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
  6617. *
  6618. * @singleton
  6619. */
  6620. (function(Manager, Class, flexSetter, alias, pass, arrayFrom, arrayErase, arrayInclude) {
  6621. var
  6622. dependencyProperties = ['extend', 'mixins', 'requires'],
  6623. Loader,
  6624. setPathCount = 0;;
  6625. Loader = Ext.Loader = {
  6626. /**
  6627. * @private
  6628. */
  6629. isInHistory: {},
  6630. /**
  6631. * An array of class names to keep track of the dependency loading order.
  6632. * This is not guaranteed to be the same every time due to the asynchronous
  6633. * nature of the Loader.
  6634. *
  6635. * @property history
  6636. * @type Array
  6637. */
  6638. history: [],
  6639. /**
  6640. * Configuration
  6641. * @private
  6642. */
  6643. config: {
  6644. /**
  6645. * Whether or not to enable the dynamic dependency loading feature.
  6646. * @cfg {Boolean} enabled
  6647. */
  6648. enabled: true,
  6649. /**
  6650. * @cfg {Boolean} disableCaching
  6651. * Appends current timestamp to script files to prevent caching.
  6652. */
  6653. disableCaching: true,
  6654. /**
  6655. * @cfg {String} disableCachingParam
  6656. * The get parameter name for the cache buster's timestamp.
  6657. */
  6658. disableCachingParam: '_dc',
  6659. /**
  6660. * @cfg {Object} paths
  6661. * The mapping from namespaces to file paths.
  6662. *
  6663. * {
  6664. * 'Ext': '.', // This is set by default, Ext.layout.container.Container will be
  6665. * // loaded from ./layout/Container.js
  6666. *
  6667. * 'My': './src/my_own_folder' // My.layout.Container will be loaded from
  6668. * // ./src/my_own_folder/layout/Container.js
  6669. * }
  6670. *
  6671. * Note that all relative paths are relative to the current HTML document.
  6672. * If not being specified, for example, `Other.awesome.Class`
  6673. * will simply be loaded from `./Other/awesome/Class.js`.
  6674. */
  6675. paths: {
  6676. 'Ext': '.'
  6677. }
  6678. },
  6679. /**
  6680. * Set the configuration for the loader. This should be called right after ext-(debug).js
  6681. * is included in the page, and before Ext.onReady. i.e:
  6682. *
  6683. * <script type="text/javascript" src="ext-core-debug.js"></script>
  6684. * <script type="text/javascript">
  6685. * Ext.Loader.setConfig({
  6686. * enabled: true,
  6687. * paths: {
  6688. * 'My': 'my_own_path'
  6689. * }
  6690. * });
  6691. * <script>
  6692. * <script type="text/javascript">
  6693. * Ext.require(...);
  6694. *
  6695. * Ext.onReady(function() {
  6696. * // application code here
  6697. * });
  6698. * </script>
  6699. *
  6700. * Refer to config options of {@link Ext.Loader} for the list of possible properties.
  6701. *
  6702. * @param {Object} config The config object to override the default values.
  6703. * @return {Ext.Loader} this
  6704. */
  6705. setConfig: function(name, value) {
  6706. if (Ext.isObject(name) && arguments.length === 1) {
  6707. Ext.merge(this.config, name);
  6708. }
  6709. else {
  6710. this.config[name] = (Ext.isObject(value)) ? Ext.merge(this.config[name], value) : value;
  6711. }
  6712. setPathCount += 1;
  6713. return this;
  6714. },
  6715. /**
  6716. * Get the config value corresponding to the specified name. If no name is given, will return the config object.
  6717. * @param {String} name The config property name.
  6718. * @return {Object/Mixed}
  6719. */
  6720. getConfig: function(name) {
  6721. if (name) {
  6722. return this.config[name];
  6723. }
  6724. return this.config;
  6725. },
  6726. /**
  6727. * Sets the path of a namespace.
  6728. * For example:
  6729. *
  6730. * Ext.Loader.setPath('Ext', '.');
  6731. *
  6732. * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
  6733. * @param {String} [path] See {@link Ext.Function#flexSetter flexSetter}
  6734. * @return {Ext.Loader} this
  6735. * @method
  6736. */
  6737. setPath: flexSetter(function(name, path) {
  6738. this.config.paths[name] = path;
  6739. setPathCount += 1;
  6740. return this;
  6741. }),
  6742. /**
  6743. * Sets a batch of path entries
  6744. *
  6745. * @param {Object } paths a set of className: path mappings
  6746. * @return {Ext.Loader} this
  6747. */
  6748. addClassPathMappings: function(paths) {
  6749. var name;
  6750. if(setPathCount == 0){
  6751. Loader.config.paths = paths;
  6752. } else {
  6753. for(name in paths){
  6754. Loader.config.paths[name] = paths[name];
  6755. }
  6756. }
  6757. setPathCount++;
  6758. return Loader;
  6759. },
  6760. /**
  6761. * Translates a className to a file path by adding the
  6762. * the proper prefix and converting the .'s to /'s. For example:
  6763. *
  6764. * Ext.Loader.setPath('My', '/path/to/My');
  6765. *
  6766. * alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
  6767. *
  6768. * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
  6769. *
  6770. * Ext.Loader.setPath({
  6771. * 'My': '/path/to/lib',
  6772. * 'My.awesome': '/other/path/for/awesome/stuff',
  6773. * 'My.awesome.more': '/more/awesome/path'
  6774. * });
  6775. *
  6776. * alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
  6777. *
  6778. * alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
  6779. *
  6780. * alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
  6781. *
  6782. * alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
  6783. *
  6784. * @param {String} className
  6785. * @return {String} path
  6786. */
  6787. getPath: function(className) {
  6788. var path = '',
  6789. paths = this.config.paths,
  6790. prefix = this.getPrefix(className);
  6791. if (prefix.length > 0) {
  6792. if (prefix === className) {
  6793. return paths[prefix];
  6794. }
  6795. path = paths[prefix];
  6796. className = className.substring(prefix.length + 1);
  6797. }
  6798. if (path.length > 0) {
  6799. path += '/';
  6800. }
  6801. return path.replace(/\/\.\//g, '/') + className.replace(/\./g, "/") + '.js';
  6802. },
  6803. /**
  6804. * @private
  6805. * @param {String} className
  6806. */
  6807. getPrefix: function(className) {
  6808. var paths = this.config.paths,
  6809. prefix, deepestPrefix = '';
  6810. if (paths.hasOwnProperty(className)) {
  6811. return className;
  6812. }
  6813. for (prefix in paths) {
  6814. if (paths.hasOwnProperty(prefix) && prefix + '.' === className.substring(0, prefix.length + 1)) {
  6815. if (prefix.length > deepestPrefix.length) {
  6816. deepestPrefix = prefix;
  6817. }
  6818. }
  6819. }
  6820. return deepestPrefix;
  6821. },
  6822. /**
  6823. * Loads all classes by the given names and all their direct dependencies; optionally executes the given callback function when
  6824. * finishes, within the optional scope. This method is aliased by {@link Ext#require Ext.require} for convenience.
  6825. * @param {String/Array} expressions Can either be a string or an array of string.
  6826. * @param {Function} fn (optional) The callback function.
  6827. * @param {Object} scope (optional) The execution scope (`this`) of the callback function.
  6828. * @param {String/Array} excludes (optional) Classes to be excluded, useful when being used with expressions.
  6829. */
  6830. require: function(expressions, fn, scope, excludes) {
  6831. if (fn) {
  6832. fn.call(scope);
  6833. }
  6834. },
  6835. /**
  6836. * Synchronously loads all classes by the given names and all their direct dependencies; optionally executes the given callback function when finishes, within the optional scope. This method is aliased by {@link Ext#syncRequire} for convenience
  6837. * @param {String/Array} expressions Can either be a string or an array of string
  6838. * @param {Function} fn (optional) The callback function
  6839. * @param {Object} scope (optional) The execution scope (`this`) of the callback function
  6840. * @param {String/Array} excludes (optional) Classes to be excluded, useful when being used with expressions
  6841. */
  6842. syncRequire: function() {},
  6843. /**
  6844. * Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
  6845. * Can be chained with more `require` and `exclude` methods, eg:
  6846. *
  6847. * Ext.exclude('Ext.data.*').require('*');
  6848. *
  6849. * Ext.exclude('widget.button*').require('widget.*');
  6850. *
  6851. * @param {Array} excludes
  6852. * @return {Object} object contains `require` method for chaining.
  6853. */
  6854. exclude: function(excludes) {
  6855. var me = this;
  6856. return {
  6857. require: function(expressions, fn, scope) {
  6858. return me.require(expressions, fn, scope, excludes);
  6859. },
  6860. syncRequire: function(expressions, fn, scope) {
  6861. return me.syncRequire(expressions, fn, scope, excludes);
  6862. }
  6863. };
  6864. },
  6865. /**
  6866. * Add a new listener to be executed when all required scripts are fully loaded.
  6867. *
  6868. * @param {Function} fn The function callback to be executed.
  6869. * @param {Object} scope The execution scope (`this`) of the callback function.
  6870. * @param {Boolean} withDomReady Whether or not to wait for document DOM ready as well.
  6871. */
  6872. onReady: function(fn, scope, withDomReady, options) {
  6873. var oldFn;
  6874. if (withDomReady !== false && Ext.onDocumentReady) {
  6875. oldFn = fn;
  6876. fn = function() {
  6877. Ext.onDocumentReady(oldFn, scope, options);
  6878. };
  6879. }
  6880. fn.call(scope);
  6881. }
  6882. };
  6883. //<feature classSystem.loader>
  6884. Ext.apply(Loader, {
  6885. /**
  6886. * @private
  6887. */
  6888. documentHead: typeof document != 'undefined' && (document.head || document.getElementsByTagName('head')[0]),
  6889. /**
  6890. * Flag indicating whether there are still files being loaded
  6891. * @private
  6892. */
  6893. isLoading: false,
  6894. /**
  6895. * Maintain the queue for all dependencies. Each item in the array is an object of the format:
  6896. *
  6897. * {
  6898. * requires: [...], // The required classes for this queue item
  6899. * callback: function() { ... } // The function to execute when all classes specified in requires exist
  6900. * }
  6901. * @private
  6902. */
  6903. queue: [],
  6904. /**
  6905. * Maintain the list of files that have already been handled so that they never get double-loaded
  6906. * @private
  6907. */
  6908. isClassFileLoaded: {},
  6909. /**
  6910. * @private
  6911. */
  6912. isFileLoaded: {},
  6913. /**
  6914. * Maintain the list of listeners to execute when all required scripts are fully loaded
  6915. * @private
  6916. */
  6917. readyListeners: [],
  6918. /**
  6919. * Contains optional dependencies to be loaded last
  6920. * @private
  6921. */
  6922. optionalRequires: [],
  6923. /**
  6924. * Map of fully qualified class names to an array of dependent classes.
  6925. * @private
  6926. */
  6927. requiresMap: {},
  6928. /**
  6929. * @private
  6930. */
  6931. numPendingFiles: 0,
  6932. /**
  6933. * @private
  6934. */
  6935. numLoadedFiles: 0,
  6936. /** @private */
  6937. hasFileLoadError: false,
  6938. /**
  6939. * @private
  6940. */
  6941. classNameToFilePathMap: {},
  6942. /**
  6943. * @private
  6944. */
  6945. syncModeEnabled: false,
  6946. scriptElements: {},
  6947. /**
  6948. * Refresh all items in the queue. If all dependencies for an item exist during looping,
  6949. * it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
  6950. * empty
  6951. * @private
  6952. */
  6953. refreshQueue: function() {
  6954. var queue = this.queue,
  6955. ln = queue.length,
  6956. i, item, j, requires, references;
  6957. if (ln === 0) {
  6958. this.triggerReady();
  6959. return;
  6960. }
  6961. for (i = 0; i < ln; i++) {
  6962. item = queue[i];
  6963. if (item) {
  6964. requires = item.requires;
  6965. references = item.references;
  6966. // Don't bother checking when the number of files loaded
  6967. // is still less than the array length
  6968. if (requires.length > this.numLoadedFiles) {
  6969. continue;
  6970. }
  6971. j = 0;
  6972. do {
  6973. if (Manager.isCreated(requires[j])) {
  6974. // Take out from the queue
  6975. arrayErase(requires, j, 1);
  6976. }
  6977. else {
  6978. j++;
  6979. }
  6980. } while (j < requires.length);
  6981. if (item.requires.length === 0) {
  6982. arrayErase(queue, i, 1);
  6983. item.callback.call(item.scope);
  6984. this.refreshQueue();
  6985. break;
  6986. }
  6987. }
  6988. }
  6989. return this;
  6990. },
  6991. /**
  6992. * Inject a script element to document's head, call onLoad and onError accordingly
  6993. * @private
  6994. */
  6995. injectScriptElement: function(url, onLoad, onError, scope) {
  6996. var script = document.createElement('script'),
  6997. me = this,
  6998. onLoadFn = function() {
  6999. me.cleanupScriptElement(script);
  7000. onLoad.call(scope);
  7001. },
  7002. onErrorFn = function() {
  7003. me.cleanupScriptElement(script);
  7004. onError.call(scope);
  7005. };
  7006. script.type = 'text/javascript';
  7007. script.src = url;
  7008. script.onload = onLoadFn;
  7009. script.onerror = onErrorFn;
  7010. script.onreadystatechange = function() {
  7011. if (this.readyState === 'loaded' || this.readyState === 'complete') {
  7012. onLoadFn();
  7013. }
  7014. };
  7015. this.documentHead.appendChild(script);
  7016. return script;
  7017. },
  7018. removeScriptElement: function(url) {
  7019. var scriptElements = this.scriptElements;
  7020. if (scriptElements[url]) {
  7021. this.cleanupScriptElement(scriptElements[url], true);
  7022. delete scriptElements[url];
  7023. }
  7024. return this;
  7025. },
  7026. /**
  7027. * @private
  7028. */
  7029. cleanupScriptElement: function(script, remove) {
  7030. script.onload = null;
  7031. script.onreadystatechange = null;
  7032. script.onerror = null;
  7033. if (remove) {
  7034. this.documentHead.removeChild(script);
  7035. }
  7036. return this;
  7037. },
  7038. /**
  7039. * Load a script file, supports both asynchronous and synchronous approaches
  7040. *
  7041. * @param {String} url
  7042. * @param {Function} onLoad
  7043. * @param {Object} scope
  7044. * @param {Boolean} synchronous
  7045. * @private
  7046. */
  7047. loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
  7048. var me = this,
  7049. isFileLoaded = this.isFileLoaded,
  7050. scriptElements = this.scriptElements,
  7051. noCacheUrl = url + (this.getConfig('disableCaching') ? ('?' + this.getConfig('disableCachingParam') + '=' + Ext.Date.now()) : ''),
  7052. xhr, status, content, onScriptError;
  7053. if (isFileLoaded[url]) {
  7054. return this;
  7055. }
  7056. scope = scope || this;
  7057. this.isLoading = true;
  7058. if (!synchronous) {
  7059. onScriptError = function() {
  7060. //<debug error>
  7061. onError.call(scope, "Failed loading '" + url + "', please verify that the file exists", synchronous);
  7062. //</debug>
  7063. };
  7064. if (!Ext.isReady && Ext.onDocumentReady) {
  7065. Ext.onDocumentReady(function() {
  7066. if (!isFileLoaded[url]) {
  7067. scriptElements[url] = me.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
  7068. }
  7069. });
  7070. }
  7071. else {
  7072. scriptElements[url] = this.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
  7073. }
  7074. }
  7075. else {
  7076. if (typeof XMLHttpRequest != 'undefined') {
  7077. xhr = new XMLHttpRequest();
  7078. } else {
  7079. xhr = new ActiveXObject('Microsoft.XMLHTTP');
  7080. }
  7081. try {
  7082. xhr.open('GET', noCacheUrl, false);
  7083. xhr.send(null);
  7084. }
  7085. catch (e) {
  7086. //<debug error>
  7087. onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; It's likely that the file is either " +
  7088. "being loaded from a different domain or from the local file system whereby cross origin " +
  7089. "requests are not allowed due to security reasons. Use asynchronous loading with " +
  7090. "Ext.require instead.", synchronous);
  7091. //</debug>
  7092. }
  7093. status = (xhr.status == 1223) ? 204 : xhr.status;
  7094. content = xhr.responseText;
  7095. if ((status >= 200 && status < 300) || status == 304 || (status == 0 && content.length > 0)) {
  7096. // Debugger friendly, file names are still shown even though they're eval'ed code
  7097. // Breakpoints work on both Firebug and Chrome's Web Inspector
  7098. Ext.globalEval(content + "\n//@ sourceURL=" + url);
  7099. onLoad.call(scope);
  7100. }
  7101. else {
  7102. //<debug>
  7103. onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; please " +
  7104. "verify that the file exists. " +
  7105. "XHR status code: " + status, synchronous);
  7106. //</debug>
  7107. }
  7108. // Prevent potential IE memory leak
  7109. xhr = null;
  7110. }
  7111. },
  7112. // documented above
  7113. syncRequire: function() {
  7114. var syncModeEnabled = this.syncModeEnabled;
  7115. if (!syncModeEnabled) {
  7116. this.syncModeEnabled = true;
  7117. }
  7118. this.require.apply(this, arguments);
  7119. if (!syncModeEnabled) {
  7120. this.syncModeEnabled = false;
  7121. }
  7122. this.refreshQueue();
  7123. },
  7124. // documented above
  7125. require: function(expressions, fn, scope, excludes) {
  7126. var excluded = {},
  7127. included = {},
  7128. queue = this.queue,
  7129. classNameToFilePathMap = this.classNameToFilePathMap,
  7130. isClassFileLoaded = this.isClassFileLoaded,
  7131. excludedClassNames = [],
  7132. possibleClassNames = [],
  7133. classNames = [],
  7134. references = [],
  7135. callback,
  7136. syncModeEnabled,
  7137. filePath, expression, exclude, className,
  7138. possibleClassName, i, j, ln, subLn;
  7139. if (excludes) {
  7140. excludes = arrayFrom(excludes);
  7141. for (i = 0,ln = excludes.length; i < ln; i++) {
  7142. exclude = excludes[i];
  7143. if (typeof exclude == 'string' && exclude.length > 0) {
  7144. excludedClassNames = Manager.getNamesByExpression(exclude);
  7145. for (j = 0,subLn = excludedClassNames.length; j < subLn; j++) {
  7146. excluded[excludedClassNames[j]] = true;
  7147. }
  7148. }
  7149. }
  7150. }
  7151. expressions = arrayFrom(expressions);
  7152. if (fn) {
  7153. if (fn.length > 0) {
  7154. callback = function() {
  7155. var classes = [],
  7156. i, ln, name;
  7157. for (i = 0,ln = references.length; i < ln; i++) {
  7158. name = references[i];
  7159. classes.push(Manager.get(name));
  7160. }
  7161. return fn.apply(this, classes);
  7162. };
  7163. }
  7164. else {
  7165. callback = fn;
  7166. }
  7167. }
  7168. else {
  7169. callback = Ext.emptyFn;
  7170. }
  7171. scope = scope || Ext.global;
  7172. for (i = 0,ln = expressions.length; i < ln; i++) {
  7173. expression = expressions[i];
  7174. if (typeof expression == 'string' && expression.length > 0) {
  7175. possibleClassNames = Manager.getNamesByExpression(expression);
  7176. subLn = possibleClassNames.length;
  7177. for (j = 0; j < subLn; j++) {
  7178. possibleClassName = possibleClassNames[j];
  7179. if (excluded[possibleClassName] !== true) {
  7180. references.push(possibleClassName);
  7181. if (!Manager.isCreated(possibleClassName) && !included[possibleClassName]) {
  7182. included[possibleClassName] = true;
  7183. classNames.push(possibleClassName);
  7184. }
  7185. }
  7186. }
  7187. }
  7188. }
  7189. // If the dynamic dependency feature is not being used, throw an error
  7190. // if the dependencies are not defined
  7191. if (classNames.length > 0) {
  7192. if (!this.config.enabled) {
  7193. throw new Error("Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " +
  7194. "Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + classNames.join(', '));
  7195. }
  7196. }
  7197. else {
  7198. callback.call(scope);
  7199. return this;
  7200. }
  7201. syncModeEnabled = this.syncModeEnabled;
  7202. if (!syncModeEnabled) {
  7203. queue.push({
  7204. requires: classNames.slice(), // this array will be modified as the queue is processed,
  7205. // so we need a copy of it
  7206. callback: callback,
  7207. scope: scope
  7208. });
  7209. }
  7210. ln = classNames.length;
  7211. for (i = 0; i < ln; i++) {
  7212. className = classNames[i];
  7213. filePath = this.getPath(className);
  7214. // If we are synchronously loading a file that has already been asynchronously loaded before
  7215. // we need to destroy the script tag and revert the count
  7216. // This file will then be forced loaded in synchronous
  7217. if (syncModeEnabled && isClassFileLoaded.hasOwnProperty(className)) {
  7218. this.numPendingFiles--;
  7219. this.removeScriptElement(filePath);
  7220. delete isClassFileLoaded[className];
  7221. }
  7222. if (!isClassFileLoaded.hasOwnProperty(className)) {
  7223. isClassFileLoaded[className] = false;
  7224. classNameToFilePathMap[className] = filePath;
  7225. this.numPendingFiles++;
  7226. this.loadScriptFile(
  7227. filePath,
  7228. pass(this.onFileLoaded, [className, filePath], this),
  7229. pass(this.onFileLoadError, [className, filePath]),
  7230. this,
  7231. syncModeEnabled
  7232. );
  7233. }
  7234. }
  7235. if (syncModeEnabled) {
  7236. callback.call(scope);
  7237. if (ln === 1) {
  7238. return Manager.get(className);
  7239. }
  7240. }
  7241. return this;
  7242. },
  7243. /**
  7244. * @private
  7245. * @param {String} className
  7246. * @param {String} filePath
  7247. */
  7248. onFileLoaded: function(className, filePath) {
  7249. this.numLoadedFiles++;
  7250. this.isClassFileLoaded[className] = true;
  7251. this.isFileLoaded[filePath] = true;
  7252. this.numPendingFiles--;
  7253. if (this.numPendingFiles === 0) {
  7254. this.refreshQueue();
  7255. }
  7256. //<debug>
  7257. if (!this.syncModeEnabled && this.numPendingFiles === 0 && this.isLoading && !this.hasFileLoadError) {
  7258. var queue = this.queue,
  7259. missingClasses = [],
  7260. missingPaths = [],
  7261. requires,
  7262. i, ln, j, subLn;
  7263. for (i = 0,ln = queue.length; i < ln; i++) {
  7264. requires = queue[i].requires;
  7265. for (j = 0,subLn = requires.length; j < subLn; j++) {
  7266. if (this.isClassFileLoaded[requires[j]]) {
  7267. missingClasses.push(requires[j]);
  7268. }
  7269. }
  7270. }
  7271. if (missingClasses.length < 1) {
  7272. return;
  7273. }
  7274. missingClasses = Ext.Array.filter(Ext.Array.unique(missingClasses), function(item) {
  7275. return !this.requiresMap.hasOwnProperty(item);
  7276. }, this);
  7277. for (i = 0,ln = missingClasses.length; i < ln; i++) {
  7278. missingPaths.push(this.classNameToFilePathMap[missingClasses[i]]);
  7279. }
  7280. throw new Error("The following classes are not declared even if their files have been " +
  7281. "loaded: '" + missingClasses.join("', '") + "'. Please check the source code of their " +
  7282. "corresponding files for possible typos: '" + missingPaths.join("', '"));
  7283. }
  7284. //</debug>
  7285. },
  7286. /**
  7287. * @private
  7288. */
  7289. onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
  7290. this.numPendingFiles--;
  7291. this.hasFileLoadError = true;
  7292. //<debug error>
  7293. throw new Error("[Ext.Loader] " + errorMessage);
  7294. //</debug>
  7295. },
  7296. /**
  7297. * @private
  7298. */
  7299. addOptionalRequires: function(requires) {
  7300. var optionalRequires = this.optionalRequires,
  7301. i, ln, require;
  7302. requires = arrayFrom(requires);
  7303. for (i = 0, ln = requires.length; i < ln; i++) {
  7304. require = requires[i];
  7305. arrayInclude(optionalRequires, require);
  7306. }
  7307. return this;
  7308. },
  7309. /**
  7310. * @private
  7311. */
  7312. triggerReady: function(force) {
  7313. var readyListeners = this.readyListeners,
  7314. optionalRequires = this.optionalRequires,
  7315. listener;
  7316. if (this.isLoading || force) {
  7317. this.isLoading = false;
  7318. if (optionalRequires.length !== 0) {
  7319. // Clone then empty the array to eliminate potential recursive loop issue
  7320. optionalRequires = optionalRequires.slice();
  7321. // Empty the original array
  7322. this.optionalRequires.length = 0;
  7323. this.require(optionalRequires, pass(this.triggerReady, [true], this), this);
  7324. return this;
  7325. }
  7326. while (readyListeners.length) {
  7327. listener = readyListeners.shift();
  7328. listener.fn.call(listener.scope);
  7329. if (this.isLoading) {
  7330. return this;
  7331. }
  7332. }
  7333. }
  7334. return this;
  7335. },
  7336. // duplicate definition (documented above)
  7337. onReady: function(fn, scope, withDomReady, options) {
  7338. var oldFn;
  7339. if (withDomReady !== false && Ext.onDocumentReady) {
  7340. oldFn = fn;
  7341. fn = function() {
  7342. Ext.onDocumentReady(oldFn, scope, options);
  7343. };
  7344. }
  7345. if (!this.isLoading) {
  7346. fn.call(scope);
  7347. }
  7348. else {
  7349. this.readyListeners.push({
  7350. fn: fn,
  7351. scope: scope
  7352. });
  7353. }
  7354. },
  7355. /**
  7356. * @private
  7357. * @param {String} className
  7358. */
  7359. historyPush: function(className) {
  7360. var isInHistory = this.isInHistory;
  7361. if (className && this.isClassFileLoaded.hasOwnProperty(className) && !isInHistory[className]) {
  7362. isInHistory[className] = true;
  7363. this.history.push(className);
  7364. }
  7365. return this;
  7366. }
  7367. });
  7368. //</feature>
  7369. /**
  7370. * Convenient alias of {@link Ext.Loader#require}. Please see the introduction documentation of
  7371. * {@link Ext.Loader} for examples.
  7372. * @member Ext
  7373. * @method require
  7374. * @inheritdoc Ext.Loader#require
  7375. */
  7376. Ext.require = alias(Loader, 'require');
  7377. /**
  7378. * Synchronous version of {@link Ext#require}, convenient alias of {@link Ext.Loader#syncRequire}.
  7379. * @member Ext
  7380. * @method syncRequire
  7381. * @inheritdoc Ext.Loader#syncRequire
  7382. */
  7383. Ext.syncRequire = alias(Loader, 'syncRequire');
  7384. /**
  7385. * Convenient shortcut to {@link Ext.Loader#exclude}.
  7386. * @member Ext
  7387. * @method exclude
  7388. * @inheritdoc Ext.Loader#exclude
  7389. */
  7390. Ext.exclude = alias(Loader, 'exclude');
  7391. /**
  7392. * Adds a listener to be notified when the document is ready and all dependencies are loaded.
  7393. *
  7394. * @param {Function} fn The method the event invokes.
  7395. * @param {Object} [scope] The scope in which the handler function executes. Defaults to the browser window.
  7396. * @param {Boolean} [options] Options object as passed to {@link Ext.Element#addListener}. It is recommended
  7397. * that the options `{single: true}` be used so that the handler is removed on first invocation.
  7398. * @member Ext
  7399. * @method onReady
  7400. */
  7401. Ext.onReady = function(fn, scope, options) {
  7402. Loader.onReady(fn, scope, true, options);
  7403. };
  7404. Class.registerPreprocessor('loader', function(cls, data, hooks, continueFn) {
  7405. var me = this,
  7406. dependencies = [],
  7407. className = Manager.getName(cls),
  7408. i, j, ln, subLn, value, propertyName, propertyValue;
  7409. /*
  7410. Loop through the dependencyProperties, look for string class names and push
  7411. them into a stack, regardless of whether the property's value is a string, array or object. For example:
  7412. {
  7413. extend: 'Ext.MyClass',
  7414. requires: ['Ext.some.OtherClass'],
  7415. mixins: {
  7416. observable: 'Ext.mixin.Observable';
  7417. }
  7418. }
  7419. which will later be transformed into:
  7420. {
  7421. extend: Ext.MyClass,
  7422. requires: [Ext.some.OtherClass],
  7423. mixins: {
  7424. observable: Ext.mixin.Observable;
  7425. }
  7426. }
  7427. */
  7428. for (i = 0,ln = dependencyProperties.length; i < ln; i++) {
  7429. propertyName = dependencyProperties[i];
  7430. if (data.hasOwnProperty(propertyName)) {
  7431. propertyValue = data[propertyName];
  7432. if (typeof propertyValue == 'string') {
  7433. dependencies.push(propertyValue);
  7434. }
  7435. else if (propertyValue instanceof Array) {
  7436. for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
  7437. value = propertyValue[j];
  7438. if (typeof value == 'string') {
  7439. dependencies.push(value);
  7440. }
  7441. }
  7442. }
  7443. else if (typeof propertyValue != 'function') {
  7444. for (j in propertyValue) {
  7445. if (propertyValue.hasOwnProperty(j)) {
  7446. value = propertyValue[j];
  7447. if (typeof value == 'string') {
  7448. dependencies.push(value);
  7449. }
  7450. }
  7451. }
  7452. }
  7453. }
  7454. }
  7455. if (dependencies.length === 0) {
  7456. return;
  7457. }
  7458. //<feature classSystem.loader>
  7459. //<debug error>
  7460. var deadlockPath = [],
  7461. requiresMap = Loader.requiresMap,
  7462. detectDeadlock;
  7463. /*
  7464. Automatically detect deadlocks before-hand,
  7465. will throw an error with detailed path for ease of debugging. Examples of deadlock cases:
  7466. - A extends B, then B extends A
  7467. - A requires B, B requires C, then C requires A
  7468. The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks
  7469. no matter how deep the path is.
  7470. */
  7471. if (className) {
  7472. requiresMap[className] = dependencies;
  7473. //<debug>
  7474. if (!Loader.requiredByMap) Loader.requiredByMap = {};
  7475. Ext.Array.each(dependencies, function(dependency){
  7476. if (!Loader.requiredByMap[dependency]) Loader.requiredByMap[dependency] = [];
  7477. Loader.requiredByMap[dependency].push(className);
  7478. });
  7479. //</debug>
  7480. detectDeadlock = function(cls) {
  7481. deadlockPath.push(cls);
  7482. if (requiresMap[cls]) {
  7483. if (Ext.Array.contains(requiresMap[cls], className)) {
  7484. throw new Error("Deadlock detected while loading dependencies! '" + className + "' and '" +
  7485. deadlockPath[1] + "' " + "mutually require each other. Path: " +
  7486. deadlockPath.join(' -> ') + " -> " + deadlockPath[0]);
  7487. }
  7488. for (i = 0,ln = requiresMap[cls].length; i < ln; i++) {
  7489. detectDeadlock(requiresMap[cls][i]);
  7490. }
  7491. }
  7492. };
  7493. detectDeadlock(className);
  7494. }
  7495. //</debug>
  7496. //</feature>
  7497. Loader.require(dependencies, function() {
  7498. for (i = 0,ln = dependencyProperties.length; i < ln; i++) {
  7499. propertyName = dependencyProperties[i];
  7500. if (data.hasOwnProperty(propertyName)) {
  7501. propertyValue = data[propertyName];
  7502. if (typeof propertyValue == 'string') {
  7503. data[propertyName] = Manager.get(propertyValue);
  7504. }
  7505. else if (propertyValue instanceof Array) {
  7506. for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
  7507. value = propertyValue[j];
  7508. if (typeof value == 'string') {
  7509. data[propertyName][j] = Manager.get(value);
  7510. }
  7511. }
  7512. }
  7513. else if (typeof propertyValue != 'function') {
  7514. for (var k in propertyValue) {
  7515. if (propertyValue.hasOwnProperty(k)) {
  7516. value = propertyValue[k];
  7517. if (typeof value == 'string') {
  7518. data[propertyName][k] = Manager.get(value);
  7519. }
  7520. }
  7521. }
  7522. }
  7523. }
  7524. }
  7525. continueFn.call(me, cls, data, hooks);
  7526. });
  7527. return false;
  7528. }, true, 'after', 'className');
  7529. //<feature classSystem.loader>
  7530. /**
  7531. * @cfg {String[]} uses
  7532. * @member Ext.Class
  7533. * List of optional classes to load together with this class. These aren't necessarily loaded before
  7534. * this class is created, but are guaranteed to be available before Ext.onReady listeners are
  7535. * invoked
  7536. */
  7537. Manager.registerPostprocessor('uses', function(name, cls, data) {
  7538. var uses = arrayFrom(data.uses),
  7539. items = [],
  7540. i, ln, item;
  7541. for (i = 0,ln = uses.length; i < ln; i++) {
  7542. item = uses[i];
  7543. if (typeof item == 'string') {
  7544. items.push(item);
  7545. }
  7546. }
  7547. Loader.addOptionalRequires(items);
  7548. });
  7549. Manager.onCreated(function(className) {
  7550. this.historyPush(className);
  7551. }, Loader);
  7552. //</feature>
  7553. })(Ext.ClassManager, Ext.Class, Ext.Function.flexSetter, Ext.Function.alias,
  7554. Ext.Function.pass, Ext.Array.from, Ext.Array.erase, Ext.Array.include);
  7555. // initalize the default path of the framework
  7556. // trimmed down version of sench-touch-debug-suffix.js
  7557. // with alias / alternates removed, as those are handled separately by
  7558. // compiler-generated metadata
  7559. (function() {
  7560. var scripts = document.getElementsByTagName('script'),
  7561. currentScript = scripts[scripts.length - 1],
  7562. src = currentScript.src,
  7563. path = src.substring(0, src.lastIndexOf('/') + 1),
  7564. Loader = Ext.Loader;
  7565. //<debug>
  7566. // if we're running in dev mode out of the repo src tree, then this
  7567. // file will potentially be loaded from the touch/src/core/class folder
  7568. // so we'll need to adjust for that
  7569. if(src.indexOf("src/core/class/") != -1) {
  7570. path = path + "../../../";
  7571. }
  7572. //</debug>
  7573. Loader.setConfig({
  7574. enabled: true,
  7575. disableCaching: !/[?&](cache|breakpoint)/i.test(location.search),
  7576. paths: {
  7577. 'Ext' : path + 'src'
  7578. }
  7579. });
  7580. })();
  7581. //@tag dom,core
  7582. //@define Ext.EventManager
  7583. //@define Ext.core.EventManager
  7584. //@require Ext.Loader
  7585. /**
  7586. * @class Ext.EventManager
  7587. *
  7588. * This object has been deprecated in Sencha Touch 2.0.0. Please refer to the method documentation for specific alternatives.
  7589. *
  7590. * @deprecated 2.0.0
  7591. * @singleton
  7592. * @private
  7593. */
  7594. //@tag dom,core
  7595. //@define Ext-more
  7596. //@require Ext.EventManager
  7597. /**
  7598. * @class Ext
  7599. *
  7600. * Ext is the global namespace for the whole Sencha Touch framework. Every class, function and configuration for the
  7601. * whole framework exists under this single global variable. The Ext singleton itself contains a set of useful helper
  7602. * functions (like {@link #apply}, {@link #min} and others), but most of the framework that you use day to day exists
  7603. * in specialized classes (for example {@link Ext.Panel}, {@link Ext.Carousel} and others).
  7604. *
  7605. * If you are new to Sencha Touch we recommend starting with the [Getting Started Guide][getting_started] to
  7606. * get a feel for how the framework operates. After that, use the more focused guides on subjects like panels, forms and data
  7607. * to broaden your understanding. The MVC guides take you through the process of building full applications using the
  7608. * framework, and detail how to deploy them to production.
  7609. *
  7610. * The functions listed below are mostly utility functions used internally by many of the classes shipped in the
  7611. * framework, but also often useful in your own apps.
  7612. *
  7613. * A method that is crucial to beginning your application is {@link #setup Ext.setup}. Please refer to it's documentation, or the
  7614. * [Getting Started Guide][getting_started] as a reference on beginning your application.
  7615. *
  7616. * Ext.setup({
  7617. * onReady: function() {
  7618. * Ext.Viewport.add({
  7619. * xtype: 'component',
  7620. * html: 'Hello world!'
  7621. * });
  7622. * }
  7623. * });
  7624. *
  7625. * [getting_started]: #!/guide/getting_started
  7626. */
  7627. Ext.setVersion('touch', '2.1.0');
  7628. Ext.apply(Ext, {
  7629. /**
  7630. * The version of the framework
  7631. * @type String
  7632. */
  7633. version: Ext.getVersion('touch'),
  7634. /**
  7635. * @private
  7636. */
  7637. idSeed: 0,
  7638. /**
  7639. * Repaints the whole page. This fixes frequently encountered painting issues in mobile Safari.
  7640. */
  7641. repaint: function() {
  7642. var mask = Ext.getBody().createChild({
  7643. cls: Ext.baseCSSPrefix + 'mask ' + Ext.baseCSSPrefix + 'mask-transparent'
  7644. });
  7645. setTimeout(function() {
  7646. mask.destroy();
  7647. }, 0);
  7648. },
  7649. /**
  7650. * Generates unique ids. If the element already has an `id`, it is unchanged.
  7651. * @param {Mixed} el (optional) The element to generate an id for.
  7652. * @param {String} [prefix=ext-gen] (optional) The `id` prefix.
  7653. * @return {String} The generated `id`.
  7654. */
  7655. id: function(el, prefix) {
  7656. if (el && el.id) {
  7657. return el.id;
  7658. }
  7659. el = Ext.getDom(el) || {};
  7660. if (el === document || el === document.documentElement) {
  7661. el.id = 'ext-application';
  7662. }
  7663. else if (el === document.body) {
  7664. el.id = 'ext-viewport';
  7665. }
  7666. else if (el === window) {
  7667. el.id = 'ext-window';
  7668. }
  7669. el.id = el.id || ((prefix || 'ext-element-') + (++Ext.idSeed));
  7670. return el.id;
  7671. },
  7672. /**
  7673. * Returns the current document body as an {@link Ext.Element}.
  7674. * @return {Ext.Element} The document body.
  7675. */
  7676. getBody: function() {
  7677. if (!Ext.documentBodyElement) {
  7678. if (!document.body) {
  7679. throw new Error("[Ext.getBody] document.body does not exist at this point");
  7680. }
  7681. Ext.documentBodyElement = Ext.get(document.body);
  7682. }
  7683. return Ext.documentBodyElement;
  7684. },
  7685. /**
  7686. * Returns the current document head as an {@link Ext.Element}.
  7687. * @return {Ext.Element} The document head.
  7688. */
  7689. getHead: function() {
  7690. if (!Ext.documentHeadElement) {
  7691. Ext.documentHeadElement = Ext.get(document.head || document.getElementsByTagName('head')[0]);
  7692. }
  7693. return Ext.documentHeadElement;
  7694. },
  7695. /**
  7696. * Returns the current HTML document object as an {@link Ext.Element}.
  7697. * @return {Ext.Element} The document.
  7698. */
  7699. getDoc: function() {
  7700. if (!Ext.documentElement) {
  7701. Ext.documentElement = Ext.get(document);
  7702. }
  7703. return Ext.documentElement;
  7704. },
  7705. /**
  7706. * This is shorthand reference to {@link Ext.ComponentMgr#get}.
  7707. * Looks up an existing {@link Ext.Component Component} by {@link Ext.Component#getId id}
  7708. * @param {String} id The component {@link Ext.Component#getId id}
  7709. * @return {Ext.Component} The Component, `undefined` if not found, or `null` if a
  7710. * Class was found.
  7711. */
  7712. getCmp: function(id) {
  7713. return Ext.ComponentMgr.get(id);
  7714. },
  7715. /**
  7716. * Copies a set of named properties from the source object to the destination object.
  7717. *
  7718. * Example:
  7719. *
  7720. * ImageComponent = Ext.extend(Ext.Component, {
  7721. * initComponent: function() {
  7722. * this.autoEl = { tag: 'img' };
  7723. * MyComponent.superclass.initComponent.apply(this, arguments);
  7724. * this.initialBox = Ext.copyTo({}, this.initialConfig, 'x,y,width,height');
  7725. * }
  7726. * });
  7727. *
  7728. * Important note: To borrow class prototype methods, use {@link Ext.Base#borrow} instead.
  7729. *
  7730. * @param {Object} dest The destination object.
  7731. * @param {Object} source The source object.
  7732. * @param {String/String[]} names Either an Array of property names, or a comma-delimited list
  7733. * of property names to copy.
  7734. * @param {Boolean} [usePrototypeKeys=false] (optional) Pass `true` to copy keys off of the prototype as well as the instance.
  7735. * @return {Object} The modified object.
  7736. */
  7737. copyTo : function(dest, source, names, usePrototypeKeys) {
  7738. if (typeof names == 'string') {
  7739. names = names.split(/[,;\s]/);
  7740. }
  7741. Ext.each (names, function(name) {
  7742. if (usePrototypeKeys || source.hasOwnProperty(name)) {
  7743. dest[name] = source[name];
  7744. }
  7745. }, this);
  7746. return dest;
  7747. },
  7748. /**
  7749. * Attempts to destroy any objects passed to it by removing all event listeners, removing them from the
  7750. * DOM (if applicable) and calling their destroy functions (if available). This method is primarily
  7751. * intended for arguments of type {@link Ext.Element} and {@link Ext.Component}.
  7752. * Any number of elements and/or components can be passed into this function in a single
  7753. * call as separate arguments.
  7754. * @param {Mixed...} args An {@link Ext.Element}, {@link Ext.Component}, or an Array of either of these to destroy.
  7755. */
  7756. destroy: function() {
  7757. var args = arguments,
  7758. ln = args.length,
  7759. i, item;
  7760. for (i = 0; i < ln; i++) {
  7761. item = args[i];
  7762. if (item) {
  7763. if (Ext.isArray(item)) {
  7764. this.destroy.apply(this, item);
  7765. }
  7766. else if (Ext.isFunction(item.destroy)) {
  7767. item.destroy();
  7768. }
  7769. }
  7770. }
  7771. },
  7772. /**
  7773. * Return the dom node for the passed String (id), dom node, or Ext.Element.
  7774. * Here are some examples:
  7775. *
  7776. * // gets dom node based on id
  7777. * var elDom = Ext.getDom('elId');
  7778. *
  7779. * // gets dom node based on the dom node
  7780. * var elDom1 = Ext.getDom(elDom);
  7781. *
  7782. * // If we don't know if we are working with an
  7783. * // Ext.Element or a dom node use Ext.getDom
  7784. * function(el){
  7785. * var dom = Ext.getDom(el);
  7786. * // do something with the dom node
  7787. * }
  7788. *
  7789. * __Note:__ the dom node to be found actually needs to exist (be rendered, etc)
  7790. * when this method is called to be successful.
  7791. * @param {Mixed} el
  7792. * @return {HTMLElement}
  7793. */
  7794. getDom: function(el) {
  7795. if (!el || !document) {
  7796. return null;
  7797. }
  7798. return el.dom ? el.dom : (typeof el == 'string' ? document.getElementById(el) : el);
  7799. },
  7800. /**
  7801. * Removes this element from the document, removes all DOM event listeners, and deletes the cache reference.
  7802. * All DOM event listeners are removed from this element.
  7803. * @param {HTMLElement} node The node to remove.
  7804. */
  7805. removeNode: function(node) {
  7806. if (node && node.parentNode && node.tagName != 'BODY') {
  7807. Ext.get(node).clearListeners();
  7808. node.parentNode.removeChild(node);
  7809. delete Ext.cache[node.id];
  7810. }
  7811. },
  7812. /**
  7813. * @private
  7814. */
  7815. defaultSetupConfig: {
  7816. eventPublishers: {
  7817. dom: {
  7818. xclass: 'Ext.event.publisher.Dom'
  7819. },
  7820. touchGesture: {
  7821. xclass: 'Ext.event.publisher.TouchGesture',
  7822. recognizers: {
  7823. drag: {
  7824. xclass: 'Ext.event.recognizer.Drag'
  7825. },
  7826. tap: {
  7827. xclass: 'Ext.event.recognizer.Tap'
  7828. },
  7829. doubleTap: {
  7830. xclass: 'Ext.event.recognizer.DoubleTap'
  7831. },
  7832. longPress: {
  7833. xclass: 'Ext.event.recognizer.LongPress'
  7834. },
  7835. swipe: {
  7836. xclass: 'Ext.event.recognizer.HorizontalSwipe'
  7837. },
  7838. pinch: {
  7839. xclass: 'Ext.event.recognizer.Pinch'
  7840. },
  7841. rotate: {
  7842. xclass: 'Ext.event.recognizer.Rotate'
  7843. }
  7844. }
  7845. },
  7846. componentDelegation: {
  7847. xclass: 'Ext.event.publisher.ComponentDelegation'
  7848. },
  7849. componentPaint: {
  7850. xclass: 'Ext.event.publisher.ComponentPaint'
  7851. },
  7852. // componentSize: {
  7853. // xclass: 'Ext.event.publisher.ComponentSize'
  7854. // },
  7855. elementPaint: {
  7856. xclass: 'Ext.event.publisher.ElementPaint'
  7857. },
  7858. elementSize: {
  7859. xclass: 'Ext.event.publisher.ElementSize'
  7860. }
  7861. },
  7862. //<feature logger>
  7863. logger: {
  7864. enabled: true,
  7865. xclass: 'Ext.log.Logger',
  7866. minPriority: 'deprecate',
  7867. writers: {
  7868. console: {
  7869. xclass: 'Ext.log.writer.Console',
  7870. throwOnErrors: true,
  7871. formatter: {
  7872. xclass: 'Ext.log.formatter.Default'
  7873. }
  7874. }
  7875. }
  7876. },
  7877. //</feature>
  7878. animator: {
  7879. xclass: 'Ext.fx.Runner'
  7880. },
  7881. viewport: {
  7882. xclass: 'Ext.viewport.Viewport'
  7883. }
  7884. },
  7885. /**
  7886. * @private
  7887. */
  7888. isSetup: false,
  7889. /**
  7890. * This indicate the start timestamp of current cycle.
  7891. * It is only reliable during dom-event-initiated cycles and
  7892. * {@link Ext.draw.Animator} initiated cycles.
  7893. */
  7894. frameStartTime: +new Date(),
  7895. /**
  7896. * @private
  7897. */
  7898. setupListeners: [],
  7899. /**
  7900. * @private
  7901. */
  7902. onSetup: function(fn, scope) {
  7903. if (Ext.isSetup) {
  7904. fn.call(scope);
  7905. }
  7906. else {
  7907. Ext.setupListeners.push({
  7908. fn: fn,
  7909. scope: scope
  7910. });
  7911. }
  7912. },
  7913. /**
  7914. * Ext.setup() is the entry-point to initialize a Sencha Touch application. Note that if your application makes
  7915. * use of MVC architecture, use {@link Ext#application} instead.
  7916. *
  7917. * This method accepts one single argument in object format. The most basic use of Ext.setup() is as follows:
  7918. *
  7919. * Ext.setup({
  7920. * onReady: function() {
  7921. * // ...
  7922. * }
  7923. * });
  7924. *
  7925. * This sets up the viewport, initializes the event system, instantiates a default animation runner, and a default
  7926. * logger (during development). When all of that is ready, it invokes the callback function given to the `onReady` key.
  7927. *
  7928. * The default scope (`this`) of `onReady` is the main viewport. By default the viewport instance is stored in
  7929. * {@link Ext.Viewport}. For example, this snippet adds a 'Hello World' button that is centered on the screen:
  7930. *
  7931. * Ext.setup({
  7932. * onReady: function() {
  7933. * this.add({
  7934. * xtype: 'button',
  7935. * centered: true,
  7936. * text: 'Hello world!'
  7937. * }); // Equivalent to Ext.Viewport.add(...)
  7938. * }
  7939. * });
  7940. *
  7941. * @param {Object} config An object with the following config options:
  7942. *
  7943. * @param {Function} config.onReady
  7944. * A function to be called when the application is ready. Your application logic should be here.
  7945. *
  7946. * @param {Object} config.viewport
  7947. * A custom config object to be used when creating the global {@link Ext.Viewport} instance. Please refer to the
  7948. * {@link Ext.Viewport} documentation for more information.
  7949. *
  7950. * Ext.setup({
  7951. * viewport: {
  7952. * width: 500,
  7953. * height: 500
  7954. * },
  7955. * onReady: function() {
  7956. * // ...
  7957. * }
  7958. * });
  7959. *
  7960. * @param {String/Object} config.icon
  7961. * Specifies a set of URLs to the application icon for different device form factors. This icon is displayed
  7962. * when the application is added to the device's Home Screen.
  7963. *
  7964. * Ext.setup({
  7965. * icon: {
  7966. * 57: 'resources/icons/Icon.png',
  7967. * 72: 'resources/icons/Icon~ipad.png',
  7968. * 114: 'resources/icons/Icon@2x.png',
  7969. * 144: 'resources/icons/Icon~ipad@2x.png'
  7970. * },
  7971. * onReady: function() {
  7972. * // ...
  7973. * }
  7974. * });
  7975. *
  7976. * Each key represents the dimension of the icon as a square shape. For example: '57' is the key for a 57 x 57
  7977. * icon image. Here is the breakdown of each dimension and its device target:
  7978. *
  7979. * - 57: Non-retina iPhone, iPod touch, and all Android devices
  7980. * - 72: Retina iPhone and iPod touch
  7981. * - 114: Non-retina iPad (first and second generation)
  7982. * - 144: Retina iPad (third generation)
  7983. *
  7984. * Note that the dimensions of the icon images must be exactly 57x57, 72x72, 114x114 and 144x144 respectively.
  7985. *
  7986. * It is highly recommended that you provide all these different sizes to accommodate a full range of
  7987. * devices currently available. However if you only have one icon in one size, make it 57x57 in size and
  7988. * specify it as a string value. This same icon will be used on all supported devices.
  7989. *
  7990. * Ext.setup({
  7991. * icon: 'resources/icons/Icon.png',
  7992. * onReady: function() {
  7993. * // ...
  7994. * }
  7995. * });
  7996. *
  7997. * @param {Object} config.startupImage
  7998. * Specifies a set of URLs to the application startup images for different device form factors. This image is
  7999. * displayed when the application is being launched from the Home Screen icon. Note that this currently only applies
  8000. * to iOS devices.
  8001. *
  8002. * Ext.setup({
  8003. * startupImage: {
  8004. * '320x460': 'resources/startup/320x460.jpg',
  8005. * '640x920': 'resources/startup/640x920.png',
  8006. * '640x1096': 'resources/startup/640x1096.png',
  8007. * '768x1004': 'resources/startup/768x1004.png',
  8008. * '748x1024': 'resources/startup/748x1024.png',
  8009. * '1536x2008': 'resources/startup/1536x2008.png',
  8010. * '1496x2048': 'resources/startup/1496x2048.png'
  8011. * },
  8012. * onReady: function() {
  8013. * // ...
  8014. * }
  8015. * });
  8016. *
  8017. * Each key represents the dimension of the image. For example: '320x460' is the key for a 320px x 460px image.
  8018. * Here is the breakdown of each dimension and its device target:
  8019. *
  8020. * - 320x460: Non-retina iPhone, iPod touch, and all Android devices
  8021. * - 640x920: Retina iPhone and iPod touch
  8022. * - 640x1096: iPhone 5 and iPod touch (fifth generation)
  8023. * - 768x1004: Non-retina iPad (first and second generation) in portrait orientation
  8024. * - 748x1024: Non-retina iPad (first and second generation) in landscape orientation
  8025. * - 1536x2008: Retina iPad (third generation) in portrait orientation
  8026. * - 1496x2048: Retina iPad (third generation) in landscape orientation
  8027. *
  8028. * Please note that there's no automatic fallback mechanism for the startup images. In other words, if you don't specify
  8029. * a valid image for a certain device, nothing will be displayed while the application is being launched on that device.
  8030. *
  8031. * @param {Boolean} isIconPrecomposed
  8032. * True to not having a glossy effect added to the icon by the OS, which will preserve its exact look. This currently
  8033. * only applies to iOS devices.
  8034. *
  8035. * @param {String} statusBarStyle
  8036. * The style of status bar to be shown on applications added to the iOS home screen. Valid options are:
  8037. *
  8038. * * `default`
  8039. * * `black`
  8040. * * `black-translucent`
  8041. *
  8042. * @param {String[]} config.requires
  8043. * An array of required classes for your application which will be automatically loaded before `onReady` is invoked.
  8044. * Please refer to {@link Ext.Loader} and {@link Ext.Loader#require} for more information.
  8045. *
  8046. * Ext.setup({
  8047. * requires: ['Ext.Button', 'Ext.tab.Panel'],
  8048. * onReady: function() {
  8049. * // ...
  8050. * }
  8051. * });
  8052. *
  8053. * @param {Object} config.eventPublishers
  8054. * Sencha Touch, by default, includes various {@link Ext.event.recognizer.Recognizer} subclasses to recognize events fired
  8055. * in your application. The list of default recognizers can be found in the documentation for
  8056. * {@link Ext.event.recognizer.Recognizer}.
  8057. *
  8058. * To change the default recognizers, you can use the following syntax:
  8059. *
  8060. * Ext.setup({
  8061. * eventPublishers: {
  8062. * touchGesture: {
  8063. * recognizers: {
  8064. * swipe: {
  8065. * // this will include both vertical and horizontal swipe recognizers
  8066. * xclass: 'Ext.event.recognizer.Swipe'
  8067. * }
  8068. * }
  8069. * }
  8070. * },
  8071. * onReady: function() {
  8072. * // ...
  8073. * }
  8074. * });
  8075. *
  8076. * You can also disable recognizers using this syntax:
  8077. *
  8078. * Ext.setup({
  8079. * eventPublishers: {
  8080. * touchGesture: {
  8081. * recognizers: {
  8082. * swipe: null,
  8083. * pinch: null,
  8084. * rotate: null
  8085. * }
  8086. * }
  8087. * },
  8088. * onReady: function() {
  8089. * // ...
  8090. * }
  8091. * });
  8092. */
  8093. setup: function(config) {
  8094. var defaultSetupConfig = Ext.defaultSetupConfig,
  8095. emptyFn = Ext.emptyFn,
  8096. onReady = config.onReady || emptyFn,
  8097. onUpdated = config.onUpdated || emptyFn,
  8098. scope = config.scope,
  8099. requires = Ext.Array.from(config.requires),
  8100. extOnReady = Ext.onReady,
  8101. head = Ext.getHead(),
  8102. callback, viewport, precomposed;
  8103. Ext.setup = function() {
  8104. throw new Error("Ext.setup has already been called before");
  8105. };
  8106. delete config.requires;
  8107. delete config.onReady;
  8108. delete config.onUpdated;
  8109. delete config.scope;
  8110. Ext.require(['Ext.event.Dispatcher']);
  8111. callback = function() {
  8112. var listeners = Ext.setupListeners,
  8113. ln = listeners.length,
  8114. i, listener;
  8115. delete Ext.setupListeners;
  8116. Ext.isSetup = true;
  8117. for (i = 0; i < ln; i++) {
  8118. listener = listeners[i];
  8119. listener.fn.call(listener.scope);
  8120. }
  8121. Ext.onReady = extOnReady;
  8122. Ext.onReady(onReady, scope);
  8123. };
  8124. Ext.onUpdated = onUpdated;
  8125. Ext.onReady = function(fn, scope) {
  8126. var origin = onReady;
  8127. onReady = function() {
  8128. origin();
  8129. Ext.onReady(fn, scope);
  8130. };
  8131. };
  8132. config = Ext.merge({}, defaultSetupConfig, config);
  8133. Ext.onDocumentReady(function() {
  8134. Ext.factoryConfig(config, function(data) {
  8135. Ext.event.Dispatcher.getInstance().setPublishers(data.eventPublishers);
  8136. if (data.logger) {
  8137. Ext.Logger = data.logger;
  8138. }
  8139. if (data.animator) {
  8140. Ext.Animator = data.animator;
  8141. }
  8142. if (data.viewport) {
  8143. Ext.Viewport = viewport = data.viewport;
  8144. if (!scope) {
  8145. scope = viewport;
  8146. }
  8147. Ext.require(requires, function() {
  8148. Ext.Viewport.on('ready', callback, null, {single: true});
  8149. });
  8150. }
  8151. else {
  8152. Ext.require(requires, callback);
  8153. }
  8154. });
  8155. });
  8156. function addMeta(name, content) {
  8157. var meta = document.createElement('meta');
  8158. meta.setAttribute('name', name);
  8159. meta.setAttribute('content', content);
  8160. head.append(meta);
  8161. }
  8162. function addIcon(href, sizes, precomposed) {
  8163. var link = document.createElement('link');
  8164. link.setAttribute('rel', 'apple-touch-icon' + (precomposed ? '-precomposed' : ''));
  8165. link.setAttribute('href', href);
  8166. if (sizes) {
  8167. link.setAttribute('sizes', sizes);
  8168. }
  8169. head.append(link);
  8170. }
  8171. function addStartupImage(href, media) {
  8172. var link = document.createElement('link');
  8173. link.setAttribute('rel', 'apple-touch-startup-image');
  8174. link.setAttribute('href', href);
  8175. if (media) {
  8176. link.setAttribute('media', media);
  8177. }
  8178. head.append(link);
  8179. }
  8180. var icon = config.icon,
  8181. isIconPrecomposed = Boolean(config.isIconPrecomposed),
  8182. startupImage = config.startupImage || {},
  8183. statusBarStyle = config.statusBarStyle,
  8184. devicePixelRatio = window.devicePixelRatio || 1;
  8185. if (navigator.standalone) {
  8186. addMeta('viewport', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0');
  8187. }
  8188. else {
  8189. addMeta('viewport', 'initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0');
  8190. }
  8191. addMeta('apple-mobile-web-app-capable', 'yes');
  8192. addMeta('apple-touch-fullscreen', 'yes');
  8193. // status bar style
  8194. if (statusBarStyle) {
  8195. addMeta('apple-mobile-web-app-status-bar-style', statusBarStyle);
  8196. }
  8197. if (Ext.isString(icon)) {
  8198. icon = {
  8199. 57: icon,
  8200. 72: icon,
  8201. 114: icon,
  8202. 144: icon
  8203. };
  8204. }
  8205. else if (!icon) {
  8206. icon = {};
  8207. }
  8208. if (Ext.os.is.iPad) {
  8209. if (devicePixelRatio >= 2) {
  8210. // Retina iPad - Landscape
  8211. if ('1496x2048' in startupImage) {
  8212. addStartupImage(startupImage['1496x2048'], '(orientation: landscape)');
  8213. }
  8214. // Retina iPad - Portrait
  8215. if ('1536x2008' in startupImage) {
  8216. addStartupImage(startupImage['1536x2008'], '(orientation: portrait)');
  8217. }
  8218. // Retina iPad
  8219. if ('144' in icon) {
  8220. addIcon(icon['144'], '144x144', isIconPrecomposed);
  8221. }
  8222. }
  8223. else {
  8224. // Non-Retina iPad - Landscape
  8225. if ('748x1024' in startupImage) {
  8226. addStartupImage(startupImage['748x1024'], '(orientation: landscape)');
  8227. }
  8228. // Non-Retina iPad - Portrait
  8229. if ('768x1004' in startupImage) {
  8230. addStartupImage(startupImage['768x1004'], '(orientation: portrait)');
  8231. }
  8232. // Non-Retina iPad
  8233. if ('72' in icon) {
  8234. addIcon(icon['72'], '72x72', isIconPrecomposed);
  8235. }
  8236. }
  8237. }
  8238. else {
  8239. // Retina iPhone, iPod touch with iOS version >= 4.3
  8240. if (devicePixelRatio >= 2 && Ext.os.version.gtEq('4.3')) {
  8241. if (Ext.os.is.iPhone5) {
  8242. addStartupImage(startupImage['640x1096']);
  8243. } else {
  8244. addStartupImage(startupImage['640x920']);
  8245. }
  8246. // Retina iPhone and iPod touch
  8247. if ('114' in icon) {
  8248. addIcon(icon['114'], '114x114', isIconPrecomposed);
  8249. }
  8250. }
  8251. else {
  8252. addStartupImage(startupImage['320x460']);
  8253. // Non-Retina iPhone, iPod touch, and Android devices
  8254. if ('57' in icon) {
  8255. addIcon(icon['57'], null, isIconPrecomposed);
  8256. }
  8257. }
  8258. }
  8259. },
  8260. /**
  8261. * @member Ext
  8262. * @method application
  8263. *
  8264. * Loads Ext.app.Application class and starts it up with given configuration after the page is ready.
  8265. *
  8266. * Ext.application({
  8267. * launch: function() {
  8268. * alert('Application launched!');
  8269. * }
  8270. * });
  8271. *
  8272. * See {@link Ext.app.Application} for details.
  8273. *
  8274. * @param {Object} config An object with the following config options:
  8275. *
  8276. * @param {Function} config.launch
  8277. * A function to be called when the application is ready. Your application logic should be here. Please see {@link Ext.app.Application}
  8278. * for details.
  8279. *
  8280. * @param {Object} config.viewport
  8281. * An object to be used when creating the global {@link Ext.Viewport} instance. Please refer to the {@link Ext.Viewport}
  8282. * documentation for more information.
  8283. *
  8284. * Ext.application({
  8285. * viewport: {
  8286. * layout: 'vbox'
  8287. * },
  8288. * launch: function() {
  8289. * Ext.Viewport.add({
  8290. * flex: 1,
  8291. * html: 'top (flex: 1)'
  8292. * });
  8293. *
  8294. * Ext.Viewport.add({
  8295. * flex: 4,
  8296. * html: 'bottom (flex: 4)'
  8297. * });
  8298. * }
  8299. * });
  8300. *
  8301. * @param {String/Object} config.icon
  8302. * Specifies a set of URLs to the application icon for different device form factors. This icon is displayed
  8303. * when the application is added to the device's Home Screen.
  8304. *
  8305. * Ext.application({
  8306. * icon: {
  8307. * 57: 'resources/icons/Icon.png',
  8308. * 72: 'resources/icons/Icon~ipad.png',
  8309. * 114: 'resources/icons/Icon@2x.png',
  8310. * 144: 'resources/icons/Icon~ipad@2x.png'
  8311. * },
  8312. * launch: function() {
  8313. * // ...
  8314. * }
  8315. * });
  8316. *
  8317. * Each key represents the dimension of the icon as a square shape. For example: '57' is the key for a 57 x 57
  8318. * icon image. Here is the breakdown of each dimension and its device target:
  8319. *
  8320. * - 57: Non-retina iPhone, iPod touch, and all Android devices
  8321. * - 72: Retina iPhone and iPod touch
  8322. * - 114: Non-retina iPad (first and second generation)
  8323. * - 144: Retina iPad (third generation)
  8324. *
  8325. * Note that the dimensions of the icon images must be exactly 57x57, 72x72, 114x114 and 144x144 respectively.
  8326. *
  8327. * It is highly recommended that you provide all these different sizes to accommodate a full range of
  8328. * devices currently available. However if you only have one icon in one size, make it 57x57 in size and
  8329. * specify it as a string value. This same icon will be used on all supported devices.
  8330. *
  8331. * Ext.setup({
  8332. * icon: 'resources/icons/Icon.png',
  8333. * onReady: function() {
  8334. * // ...
  8335. * }
  8336. * });
  8337. *
  8338. * @param {Object} config.startupImage
  8339. * Specifies a set of URLs to the application startup images for different device form factors. This image is
  8340. * displayed when the application is being launched from the Home Screen icon. Note that this currently only applies
  8341. * to iOS devices.
  8342. *
  8343. * Ext.application({
  8344. * startupImage: {
  8345. * '320x460': 'resources/startup/320x460.jpg',
  8346. * '640x920': 'resources/startup/640x920.png',
  8347. * '640x1096': 'resources/startup/640x1096.png',
  8348. * '768x1004': 'resources/startup/768x1004.png',
  8349. * '748x1024': 'resources/startup/748x1024.png',
  8350. * '1536x2008': 'resources/startup/1536x2008.png',
  8351. * '1496x2048': 'resources/startup/1496x2048.png'
  8352. * },
  8353. * launch: function() {
  8354. * // ...
  8355. * }
  8356. * });
  8357. *
  8358. * Each key represents the dimension of the image. For example: '320x460' is the key for a 320px x 460px image.
  8359. * Here is the breakdown of each dimension and its device target:
  8360. *
  8361. * - 320x460: Non-retina iPhone, iPod touch, and all Android devices
  8362. * - 640x920: Retina iPhone and iPod touch
  8363. * - 640x1096: iPhone 5 and iPod touch (fifth generation)
  8364. * - 768x1004: Non-retina iPad (first and second generation) in portrait orientation
  8365. * - 748x1024: Non-retina iPad (first and second generation) in landscape orientation
  8366. * - 1536x2008: Retina iPad (third generation) in portrait orientation
  8367. * - 1496x2048: Retina iPad (third generation) in landscape orientation
  8368. *
  8369. * Please note that there's no automatic fallback mechanism for the startup images. In other words, if you don't specify
  8370. * a valid image for a certain device, nothing will be displayed while the application is being launched on that device.
  8371. *
  8372. * @param {Boolean} config.isIconPrecomposed
  8373. * True to not having a glossy effect added to the icon by the OS, which will preserve its exact look. This currently
  8374. * only applies to iOS devices.
  8375. *
  8376. * @param {String} config.statusBarStyle
  8377. * The style of status bar to be shown on applications added to the iOS home screen. Valid options are:
  8378. *
  8379. * * `default`
  8380. * * `black`
  8381. * * `black-translucent`
  8382. *
  8383. * @param {String[]} config.requires
  8384. * An array of required classes for your application which will be automatically loaded if {@link Ext.Loader#enabled} is set
  8385. * to `true`. Please refer to {@link Ext.Loader} and {@link Ext.Loader#require} for more information.
  8386. *
  8387. * Ext.application({
  8388. * requires: ['Ext.Button', 'Ext.tab.Panel'],
  8389. * launch: function() {
  8390. * // ...
  8391. * }
  8392. * });
  8393. *
  8394. * @param {Object} config.eventPublishers
  8395. * Sencha Touch, by default, includes various {@link Ext.event.recognizer.Recognizer} subclasses to recognize events fired
  8396. * in your application. The list of default recognizers can be found in the documentation for {@link Ext.event.recognizer.Recognizer}.
  8397. *
  8398. * To change the default recognizers, you can use the following syntax:
  8399. *
  8400. * Ext.application({
  8401. * eventPublishers: {
  8402. * touchGesture: {
  8403. * recognizers: {
  8404. * swipe: {
  8405. * // this will include both vertical and horizontal swipe recognizers
  8406. * xclass: 'Ext.event.recognizer.Swipe'
  8407. * }
  8408. * }
  8409. * }
  8410. * },
  8411. * launch: function() {
  8412. * // ...
  8413. * }
  8414. * });
  8415. *
  8416. * You can also disable recognizers using this syntax:
  8417. *
  8418. * Ext.application({
  8419. * eventPublishers: {
  8420. * touchGesture: {
  8421. * recognizers: {
  8422. * swipe: null,
  8423. * pinch: null,
  8424. * rotate: null
  8425. * }
  8426. * }
  8427. * },
  8428. * launch: function() {
  8429. * // ...
  8430. * }
  8431. * });
  8432. */
  8433. application: function(config) {
  8434. var appName = config.name,
  8435. onReady, scope, requires;
  8436. if (!config) {
  8437. config = {};
  8438. }
  8439. if (!Ext.Loader.config.paths[appName]) {
  8440. Ext.Loader.setPath(appName, config.appFolder || 'app');
  8441. }
  8442. requires = Ext.Array.from(config.requires);
  8443. config.requires = ['Ext.app.Application'];
  8444. onReady = config.onReady;
  8445. scope = config.scope;
  8446. config.onReady = function() {
  8447. config.requires = requires;
  8448. new Ext.app.Application(config);
  8449. if (onReady) {
  8450. onReady.call(scope);
  8451. }
  8452. };
  8453. Ext.setup(config);
  8454. },
  8455. /**
  8456. * @private
  8457. * @param config
  8458. * @param callback
  8459. * @member Ext
  8460. */
  8461. factoryConfig: function(config, callback) {
  8462. var isSimpleObject = Ext.isSimpleObject(config);
  8463. if (isSimpleObject && config.xclass) {
  8464. var className = config.xclass;
  8465. delete config.xclass;
  8466. Ext.require(className, function() {
  8467. Ext.factoryConfig(config, function(cfg) {
  8468. callback(Ext.create(className, cfg));
  8469. });
  8470. });
  8471. return;
  8472. }
  8473. var isArray = Ext.isArray(config),
  8474. keys = [],
  8475. key, value, i, ln;
  8476. if (isSimpleObject || isArray) {
  8477. if (isSimpleObject) {
  8478. for (key in config) {
  8479. if (config.hasOwnProperty(key)) {
  8480. value = config[key];
  8481. if (Ext.isSimpleObject(value) || Ext.isArray(value)) {
  8482. keys.push(key);
  8483. }
  8484. }
  8485. }
  8486. }
  8487. else {
  8488. for (i = 0,ln = config.length; i < ln; i++) {
  8489. value = config[i];
  8490. if (Ext.isSimpleObject(value) || Ext.isArray(value)) {
  8491. keys.push(i);
  8492. }
  8493. }
  8494. }
  8495. i = 0;
  8496. ln = keys.length;
  8497. if (ln === 0) {
  8498. callback(config);
  8499. return;
  8500. }
  8501. function fn(value) {
  8502. config[key] = value;
  8503. i++;
  8504. factory();
  8505. }
  8506. function factory() {
  8507. if (i >= ln) {
  8508. callback(config);
  8509. return;
  8510. }
  8511. key = keys[i];
  8512. value = config[key];
  8513. Ext.factoryConfig(value, fn);
  8514. }
  8515. factory();
  8516. return;
  8517. }
  8518. callback(config);
  8519. },
  8520. /**
  8521. * A global factory method to instantiate a class from a config object. For example, these two calls are equivalent:
  8522. *
  8523. * Ext.factory({ text: 'My Button' }, 'Ext.Button');
  8524. * Ext.create('Ext.Button', { text: 'My Button' });
  8525. *
  8526. * If an existing instance is also specified, it will be updated with the supplied config object. This is useful
  8527. * if you need to either create or update an object, depending on if an instance already exists. For example:
  8528. *
  8529. * var button;
  8530. * button = Ext.factory({ text: 'New Button' }, 'Ext.Button', button); // Button created
  8531. * button = Ext.factory({ text: 'Updated Button' }, 'Ext.Button', button); // Button updated
  8532. *
  8533. * @param {Object} config The config object to instantiate or update an instance with.
  8534. * @param {String} classReference The class to instantiate from.
  8535. * @param {Object} [instance] The instance to update.
  8536. * @param [aliasNamespace]
  8537. * @member Ext
  8538. */
  8539. factory: function(config, classReference, instance, aliasNamespace) {
  8540. var manager = Ext.ClassManager,
  8541. newInstance;
  8542. // If config is falsy or a valid instance, destroy the current instance
  8543. // (if it exists) and replace with the new one
  8544. if (!config || config.isInstance) {
  8545. if (instance && instance !== config) {
  8546. instance.destroy();
  8547. }
  8548. return config;
  8549. }
  8550. if (aliasNamespace) {
  8551. // If config is a string value, treat it as an alias
  8552. if (typeof config == 'string') {
  8553. return manager.instantiateByAlias(aliasNamespace + '.' + config);
  8554. }
  8555. // Same if 'type' is given in config
  8556. else if (Ext.isObject(config) && 'type' in config) {
  8557. return manager.instantiateByAlias(aliasNamespace + '.' + config.type, config);
  8558. }
  8559. }
  8560. if (config === true) {
  8561. return instance || manager.instantiate(classReference);
  8562. }
  8563. //<debug error>
  8564. if (!Ext.isObject(config)) {
  8565. Ext.Logger.error("Invalid config, must be a valid config object");
  8566. }
  8567. //</debug>
  8568. if ('xtype' in config) {
  8569. newInstance = manager.instantiateByAlias('widget.' + config.xtype, config);
  8570. }
  8571. else if ('xclass' in config) {
  8572. newInstance = manager.instantiate(config.xclass, config);
  8573. }
  8574. if (newInstance) {
  8575. if (instance) {
  8576. instance.destroy();
  8577. }
  8578. return newInstance;
  8579. }
  8580. if (instance) {
  8581. return instance.setConfig(config);
  8582. }
  8583. return manager.instantiate(classReference, config);
  8584. },
  8585. /**
  8586. * @private
  8587. * @member Ext
  8588. */
  8589. deprecateClassMember: function(cls, oldName, newName, message) {
  8590. return this.deprecateProperty(cls.prototype, oldName, newName, message);
  8591. },
  8592. /**
  8593. * @private
  8594. * @member Ext
  8595. */
  8596. deprecateClassMembers: function(cls, members) {
  8597. var prototype = cls.prototype,
  8598. oldName, newName;
  8599. for (oldName in members) {
  8600. if (members.hasOwnProperty(oldName)) {
  8601. newName = members[oldName];
  8602. this.deprecateProperty(prototype, oldName, newName);
  8603. }
  8604. }
  8605. },
  8606. /**
  8607. * @private
  8608. * @member Ext
  8609. */
  8610. deprecateProperty: function(object, oldName, newName, message) {
  8611. if (!message) {
  8612. message = "'" + oldName + "' is deprecated";
  8613. }
  8614. if (newName) {
  8615. message += ", please use '" + newName + "' instead";
  8616. }
  8617. if (newName) {
  8618. Ext.Object.defineProperty(object, oldName, {
  8619. get: function() {
  8620. //<debug warn>
  8621. Ext.Logger.deprecate(message, 1);
  8622. //</debug>
  8623. return this[newName];
  8624. },
  8625. set: function(value) {
  8626. //<debug warn>
  8627. Ext.Logger.deprecate(message, 1);
  8628. //</debug>
  8629. this[newName] = value;
  8630. },
  8631. configurable: true
  8632. });
  8633. }
  8634. },
  8635. /**
  8636. * @private
  8637. * @member Ext
  8638. */
  8639. deprecatePropertyValue: function(object, name, value, message) {
  8640. Ext.Object.defineProperty(object, name, {
  8641. get: function() {
  8642. //<debug warn>
  8643. Ext.Logger.deprecate(message, 1);
  8644. //</debug>
  8645. return value;
  8646. },
  8647. configurable: true
  8648. });
  8649. },
  8650. /**
  8651. * @private
  8652. * @member Ext
  8653. */
  8654. deprecateMethod: function(object, name, method, message) {
  8655. object[name] = function() {
  8656. //<debug warn>
  8657. Ext.Logger.deprecate(message, 2);
  8658. //</debug>
  8659. if (method) {
  8660. return method.apply(this, arguments);
  8661. }
  8662. };
  8663. },
  8664. /**
  8665. * @private
  8666. * @member Ext
  8667. */
  8668. deprecateClassMethod: function(cls, name, method, message) {
  8669. if (typeof name != 'string') {
  8670. var from, to;
  8671. for (from in name) {
  8672. if (name.hasOwnProperty(from)) {
  8673. to = name[from];
  8674. Ext.deprecateClassMethod(cls, from, to);
  8675. }
  8676. }
  8677. return;
  8678. }
  8679. var isLateBinding = typeof method == 'string',
  8680. member;
  8681. if (!message) {
  8682. message = "'" + name + "()' is deprecated, please use '" + (isLateBinding ? method : method.name) +
  8683. "()' instead";
  8684. }
  8685. if (isLateBinding) {
  8686. member = function() {
  8687. //<debug warn>
  8688. Ext.Logger.deprecate(message, this);
  8689. //</debug>
  8690. return this[method].apply(this, arguments);
  8691. };
  8692. }
  8693. else {
  8694. member = function() {
  8695. //<debug warn>
  8696. Ext.Logger.deprecate(message, this);
  8697. //</debug>
  8698. return method.apply(this, arguments);
  8699. };
  8700. }
  8701. if (name in cls.prototype) {
  8702. Ext.Object.defineProperty(cls.prototype, name, {
  8703. value: null,
  8704. writable: true,
  8705. configurable: true
  8706. });
  8707. }
  8708. cls.addMember(name, member);
  8709. },
  8710. //<debug>
  8711. /**
  8712. * Useful snippet to show an exact, narrowed-down list of top-level Components that are not yet destroyed.
  8713. * @private
  8714. */
  8715. showLeaks: function() {
  8716. var map = Ext.ComponentManager.all.map,
  8717. leaks = [],
  8718. parent;
  8719. Ext.Object.each(map, function(id, component) {
  8720. while ((parent = component.getParent()) && map.hasOwnProperty(parent.getId())) {
  8721. component = parent;
  8722. }
  8723. if (leaks.indexOf(component) === -1) {
  8724. leaks.push(component);
  8725. }
  8726. });
  8727. console.log(leaks);
  8728. },
  8729. //</debug>
  8730. /**
  8731. * True when the document is fully initialized and ready for action
  8732. * @type Boolean
  8733. * @member Ext
  8734. * @private
  8735. */
  8736. isReady : false,
  8737. /**
  8738. * @private
  8739. * @member Ext
  8740. */
  8741. readyListeners: [],
  8742. /**
  8743. * @private
  8744. * @member Ext
  8745. */
  8746. triggerReady: function() {
  8747. var listeners = Ext.readyListeners,
  8748. i, ln, listener;
  8749. if (!Ext.isReady) {
  8750. Ext.isReady = true;
  8751. for (i = 0,ln = listeners.length; i < ln; i++) {
  8752. listener = listeners[i];
  8753. listener.fn.call(listener.scope);
  8754. }
  8755. delete Ext.readyListeners;
  8756. }
  8757. },
  8758. /**
  8759. * @private
  8760. * @member Ext
  8761. */
  8762. onDocumentReady: function(fn, scope) {
  8763. if (Ext.isReady) {
  8764. fn.call(scope);
  8765. }
  8766. else {
  8767. var triggerFn = Ext.triggerReady;
  8768. Ext.readyListeners.push({
  8769. fn: fn,
  8770. scope: scope
  8771. });
  8772. if (Ext.browser.is.PhoneGap && !Ext.os.is.Desktop) {
  8773. if (!Ext.readyListenerAttached) {
  8774. Ext.readyListenerAttached = true;
  8775. document.addEventListener('deviceready', triggerFn, false);
  8776. }
  8777. }
  8778. else {
  8779. if (document.readyState.match(/interactive|complete|loaded/) !== null) {
  8780. triggerFn();
  8781. }
  8782. else if (!Ext.readyListenerAttached) {
  8783. Ext.readyListenerAttached = true;
  8784. window.addEventListener('DOMContentLoaded', triggerFn, false);
  8785. }
  8786. }
  8787. }
  8788. },
  8789. /**
  8790. * Calls function after specified delay, or right away when delay == 0.
  8791. * @param {Function} callback The callback to execute.
  8792. * @param {Object} scope (optional) The scope to execute in.
  8793. * @param {Array} args (optional) The arguments to pass to the function.
  8794. * @param {Number} delay (optional) Pass a number to delay the call by a number of milliseconds.
  8795. * @member Ext
  8796. */
  8797. callback: function(callback, scope, args, delay) {
  8798. if (Ext.isFunction(callback)) {
  8799. args = args || [];
  8800. scope = scope || window;
  8801. if (delay) {
  8802. Ext.defer(callback, delay, scope, args);
  8803. } else {
  8804. callback.apply(scope, args);
  8805. }
  8806. }
  8807. }
  8808. });
  8809. //<debug>
  8810. Ext.Object.defineProperty(Ext, 'Msg', {
  8811. get: function() {
  8812. Ext.Logger.error("Using Ext.Msg without requiring Ext.MessageBox");
  8813. return null;
  8814. },
  8815. set: function(value) {
  8816. Ext.Object.defineProperty(Ext, 'Msg', {
  8817. value: value
  8818. });
  8819. return value;
  8820. },
  8821. configurable: true
  8822. });
  8823. //</debug>
  8824. //@tag dom,core
  8825. //@require Ext-more
  8826. /**
  8827. * Provides information about browser.
  8828. *
  8829. * Should not be manually instantiated unless for unit-testing.
  8830. * Access the global instance stored in {@link Ext.browser} instead.
  8831. * @private
  8832. */
  8833. Ext.define('Ext.env.Browser', {
  8834. requires: ['Ext.Version'],
  8835. statics: {
  8836. browserNames: {
  8837. ie: 'IE',
  8838. firefox: 'Firefox',
  8839. safari: 'Safari',
  8840. chrome: 'Chrome',
  8841. opera: 'Opera',
  8842. dolfin: 'Dolfin',
  8843. webosbrowser: 'webOSBrowser',
  8844. chromeMobile: 'ChromeMobile',
  8845. silk: 'Silk',
  8846. other: 'Other'
  8847. },
  8848. engineNames: {
  8849. webkit: 'WebKit',
  8850. gecko: 'Gecko',
  8851. presto: 'Presto',
  8852. trident: 'Trident',
  8853. other: 'Other'
  8854. },
  8855. enginePrefixes: {
  8856. webkit: 'AppleWebKit/',
  8857. gecko: 'Gecko/',
  8858. presto: 'Presto/',
  8859. trident: 'Trident/'
  8860. },
  8861. browserPrefixes: {
  8862. ie: 'MSIE ',
  8863. firefox: 'Firefox/',
  8864. chrome: 'Chrome/',
  8865. safari: 'Version/',
  8866. opera: 'Opera/',
  8867. dolfin: 'Dolfin/',
  8868. webosbrowser: 'wOSBrowser/',
  8869. chromeMobile: 'CrMo/',
  8870. silk: 'Silk/'
  8871. }
  8872. },
  8873. styleDashPrefixes: {
  8874. WebKit: '-webkit-',
  8875. Gecko: '-moz-',
  8876. Trident: '-ms-',
  8877. Presto: '-o-',
  8878. Other: ''
  8879. },
  8880. stylePrefixes: {
  8881. WebKit: 'Webkit',
  8882. Gecko: 'Moz',
  8883. Trident: 'ms',
  8884. Presto: 'O',
  8885. Other: ''
  8886. },
  8887. propertyPrefixes: {
  8888. WebKit: 'webkit',
  8889. Gecko: 'moz',
  8890. Trident: 'ms',
  8891. Presto: 'o',
  8892. Other: ''
  8893. },
  8894. // scope: Ext.env.Browser.prototype
  8895. /**
  8896. * A "hybrid" property, can be either accessed as a method call, for example:
  8897. *
  8898. * if (Ext.browser.is('IE')) {
  8899. * // ...
  8900. * }
  8901. *
  8902. * Or as an object with Boolean properties, for example:
  8903. *
  8904. * if (Ext.browser.is.IE) {
  8905. * // ...
  8906. * }
  8907. *
  8908. * Versions can be conveniently checked as well. For example:
  8909. *
  8910. * if (Ext.browser.is.IE6) {
  8911. * // Equivalent to (Ext.browser.is.IE && Ext.browser.version.equals(6))
  8912. * }
  8913. *
  8914. * __Note:__ Only {@link Ext.Version#getMajor major component} and {@link Ext.Version#getShortVersion simplified}
  8915. * value of the version are available via direct property checking.
  8916. *
  8917. * Supported values are:
  8918. *
  8919. * - IE
  8920. * - Firefox
  8921. * - Safari
  8922. * - Chrome
  8923. * - Opera
  8924. * - WebKit
  8925. * - Gecko
  8926. * - Presto
  8927. * - Trident
  8928. * - WebView
  8929. * - Other
  8930. *
  8931. * @param {String} value The OS name to check.
  8932. * @return {Boolean}
  8933. */
  8934. is: Ext.emptyFn,
  8935. /**
  8936. * The full name of the current browser.
  8937. * Possible values are:
  8938. *
  8939. * - IE
  8940. * - Firefox
  8941. * - Safari
  8942. * - Chrome
  8943. * - Opera
  8944. * - Other
  8945. * @type String
  8946. * @readonly
  8947. */
  8948. name: null,
  8949. /**
  8950. * Refer to {@link Ext.Version}.
  8951. * @type Ext.Version
  8952. * @readonly
  8953. */
  8954. version: null,
  8955. /**
  8956. * The full name of the current browser's engine.
  8957. * Possible values are:
  8958. *
  8959. * - WebKit
  8960. * - Gecko
  8961. * - Presto
  8962. * - Trident
  8963. * - Other
  8964. * @type String
  8965. * @readonly
  8966. */
  8967. engineName: null,
  8968. /**
  8969. * Refer to {@link Ext.Version}.
  8970. * @type Ext.Version
  8971. * @readonly
  8972. */
  8973. engineVersion: null,
  8974. setFlag: function(name, value) {
  8975. if (typeof value == 'undefined') {
  8976. value = true;
  8977. }
  8978. this.is[name] = value;
  8979. this.is[name.toLowerCase()] = value;
  8980. return this;
  8981. },
  8982. constructor: function(userAgent) {
  8983. /**
  8984. * @property {String}
  8985. * Browser User Agent string.
  8986. */
  8987. this.userAgent = userAgent;
  8988. is = this.is = function(name) {
  8989. return is[name] === true;
  8990. };
  8991. var statics = this.statics(),
  8992. browserMatch = userAgent.match(new RegExp('((?:' + Ext.Object.getValues(statics.browserPrefixes).join(')|(?:') + '))([\\w\\._]+)')),
  8993. engineMatch = userAgent.match(new RegExp('((?:' + Ext.Object.getValues(statics.enginePrefixes).join(')|(?:') + '))([\\w\\._]+)')),
  8994. browserNames = statics.browserNames,
  8995. browserName = browserNames.other,
  8996. engineNames = statics.engineNames,
  8997. engineName = engineNames.other,
  8998. browserVersion = '',
  8999. engineVersion = '',
  9000. isWebView = false,
  9001. is, i, name;
  9002. if (browserMatch) {
  9003. browserName = browserNames[Ext.Object.getKey(statics.browserPrefixes, browserMatch[1])];
  9004. browserVersion = new Ext.Version(browserMatch[2]);
  9005. }
  9006. if (engineMatch) {
  9007. engineName = engineNames[Ext.Object.getKey(statics.enginePrefixes, engineMatch[1])];
  9008. engineVersion = new Ext.Version(engineMatch[2]);
  9009. }
  9010. // Facebook changes the userAgent when you view a website within their iOS app. For some reason, the strip out information
  9011. // about the browser, so we have to detect that and fake it...
  9012. if (userAgent.match(/FB/) && browserName == "Other") {
  9013. browserName = browserNames.safari;
  9014. engineName = engineNames.webkit;
  9015. }
  9016. if (userAgent.match(/Android.*Chrome/g)) {
  9017. browserName = 'ChromeMobile';
  9018. }
  9019. Ext.apply(this, {
  9020. engineName: engineName,
  9021. engineVersion: engineVersion,
  9022. name: browserName,
  9023. version: browserVersion
  9024. });
  9025. this.setFlag(browserName);
  9026. if (browserVersion) {
  9027. this.setFlag(browserName + (browserVersion.getMajor() || ''));
  9028. this.setFlag(browserName + browserVersion.getShortVersion());
  9029. }
  9030. for (i in browserNames) {
  9031. if (browserNames.hasOwnProperty(i)) {
  9032. name = browserNames[i];
  9033. this.setFlag(name, browserName === name);
  9034. }
  9035. }
  9036. this.setFlag(name);
  9037. if (engineVersion) {
  9038. this.setFlag(engineName + (engineVersion.getMajor() || ''));
  9039. this.setFlag(engineName + engineVersion.getShortVersion());
  9040. }
  9041. for (i in engineNames) {
  9042. if (engineNames.hasOwnProperty(i)) {
  9043. name = engineNames[i];
  9044. this.setFlag(name, engineName === name);
  9045. }
  9046. }
  9047. this.setFlag('Standalone', !!navigator.standalone);
  9048. if (typeof window.PhoneGap != 'undefined' || typeof window.Cordova != 'undefined' || typeof window.cordova != 'undefined') {
  9049. isWebView = true;
  9050. this.setFlag('PhoneGap');
  9051. }
  9052. else if (!!window.isNK) {
  9053. isWebView = true;
  9054. this.setFlag('Sencha');
  9055. }
  9056. // Check if running in UIWebView
  9057. if (/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)(?!.*FBAN)/i.test(userAgent)) {
  9058. isWebView = true;
  9059. }
  9060. // Flag to check if it we are in the WebView
  9061. this.setFlag('WebView', isWebView);
  9062. /**
  9063. * @property {Boolean}
  9064. * `true` if browser is using strict mode.
  9065. */
  9066. this.isStrict = document.compatMode == "CSS1Compat";
  9067. /**
  9068. * @property {Boolean}
  9069. * `true` if page is running over SSL.
  9070. */
  9071. this.isSecure = /^https/i.test(window.location.protocol);
  9072. return this;
  9073. },
  9074. getStyleDashPrefix: function() {
  9075. return this.styleDashPrefixes[this.engineName];
  9076. },
  9077. getStylePrefix: function() {
  9078. return this.stylePrefixes[this.engineName];
  9079. },
  9080. getVendorProperyName: function(name) {
  9081. var prefix = this.propertyPrefixes[this.engineName];
  9082. if (prefix.length > 0) {
  9083. return prefix + Ext.String.capitalize(name);
  9084. }
  9085. return name;
  9086. }
  9087. }, function() {
  9088. /**
  9089. * @class Ext.browser
  9090. * @extends Ext.env.Browser
  9091. * @singleton
  9092. * Provides useful information about the current browser.
  9093. *
  9094. * Example:
  9095. *
  9096. * if (Ext.browser.is.IE) {
  9097. * // IE specific code here
  9098. * }
  9099. *
  9100. * if (Ext.browser.is.WebKit) {
  9101. * // WebKit specific code here
  9102. * }
  9103. *
  9104. * console.log("Version " + Ext.browser.version);
  9105. *
  9106. * For a full list of supported values, refer to {@link #is} property/method.
  9107. *
  9108. * @aside guide environment_package
  9109. */
  9110. var browserEnv = Ext.browser = new this(Ext.global.navigator.userAgent);
  9111. });
  9112. //@tag dom,core
  9113. //@require Ext.env.Browser
  9114. /**
  9115. * Provides information about operating system environment.
  9116. *
  9117. * Should not be manually instantiated unless for unit-testing.
  9118. * Access the global instance stored in {@link Ext.os} instead.
  9119. * @private
  9120. */
  9121. Ext.define('Ext.env.OS', {
  9122. requires: ['Ext.Version'],
  9123. statics: {
  9124. names: {
  9125. ios: 'iOS',
  9126. android: 'Android',
  9127. webos: 'webOS',
  9128. blackberry: 'BlackBerry',
  9129. rimTablet: 'RIMTablet',
  9130. mac: 'MacOS',
  9131. win: 'Windows',
  9132. linux: 'Linux',
  9133. bada: 'Bada',
  9134. other: 'Other'
  9135. },
  9136. prefixes: {
  9137. ios: 'i(?:Pad|Phone|Pod)(?:.*)CPU(?: iPhone)? OS ',
  9138. android: '(Android |HTC_|Silk/)', // Some HTC devices ship with an OSX userAgent by default,
  9139. // so we need to add a direct check for HTC_
  9140. blackberry: 'BlackBerry(?:.*)Version\/',
  9141. rimTablet: 'RIM Tablet OS ',
  9142. webos: '(?:webOS|hpwOS)\/',
  9143. bada: 'Bada\/'
  9144. }
  9145. },
  9146. /**
  9147. * A "hybrid" property, can be either accessed as a method call, i.e:
  9148. *
  9149. * if (Ext.os.is('Android')) {
  9150. * // ...
  9151. * }
  9152. *
  9153. * or as an object with boolean properties, i.e:
  9154. *
  9155. * if (Ext.os.is.Android) {
  9156. * // ...
  9157. * }
  9158. *
  9159. * Versions can be conveniently checked as well. For example:
  9160. *
  9161. * if (Ext.os.is.Android2) {
  9162. * // Equivalent to (Ext.os.is.Android && Ext.os.version.equals(2))
  9163. * }
  9164. *
  9165. * if (Ext.os.is.iOS32) {
  9166. * // Equivalent to (Ext.os.is.iOS && Ext.os.version.equals(3.2))
  9167. * }
  9168. *
  9169. * Note that only {@link Ext.Version#getMajor major component} and {@link Ext.Version#getShortVersion simplified}
  9170. * value of the version are available via direct property checking. Supported values are:
  9171. *
  9172. * - iOS
  9173. * - iPad
  9174. * - iPhone
  9175. * - iPhone5 (also true for 4in iPods).
  9176. * - iPod
  9177. * - Android
  9178. * - WebOS
  9179. * - BlackBerry
  9180. * - Bada
  9181. * - MacOS
  9182. * - Windows
  9183. * - Linux
  9184. * - Other
  9185. * @param {String} value The OS name to check.
  9186. * @return {Boolean}
  9187. */
  9188. is: Ext.emptyFn,
  9189. /**
  9190. * @property {String} [name=null]
  9191. * @readonly
  9192. * The full name of the current operating system. Possible values are:
  9193. *
  9194. * - iOS
  9195. * - Android
  9196. * - WebOS
  9197. * - BlackBerry,
  9198. * - MacOS
  9199. * - Windows
  9200. * - Linux
  9201. * - Other
  9202. */
  9203. name: null,
  9204. /**
  9205. * @property {Ext.Version} [version=null]
  9206. * Refer to {@link Ext.Version}
  9207. * @readonly
  9208. */
  9209. version: null,
  9210. setFlag: function(name, value) {
  9211. if (typeof value == 'undefined') {
  9212. value = true;
  9213. }
  9214. this.is[name] = value;
  9215. this.is[name.toLowerCase()] = value;
  9216. return this;
  9217. },
  9218. constructor: function(userAgent, platform) {
  9219. var statics = this.statics(),
  9220. names = statics.names,
  9221. prefixes = statics.prefixes,
  9222. name,
  9223. version = '',
  9224. i, prefix, match, item, is;
  9225. is = this.is = function(name) {
  9226. return this.is[name] === true;
  9227. };
  9228. for (i in prefixes) {
  9229. if (prefixes.hasOwnProperty(i)) {
  9230. prefix = prefixes[i];
  9231. match = userAgent.match(new RegExp('(?:'+prefix+')([^\\s;]+)'));
  9232. if (match) {
  9233. name = names[i];
  9234. // This is here because some HTC android devices show an OSX Snow Leopard userAgent by default.
  9235. // And the Kindle Fire doesn't have any indicator of Android as the OS in its User Agent
  9236. if (match[1] && (match[1] == "HTC_" || match[1] == "Silk/")) {
  9237. version = new Ext.Version("2.3");
  9238. } else {
  9239. version = new Ext.Version(match[match.length - 1]);
  9240. }
  9241. break;
  9242. }
  9243. }
  9244. }
  9245. if (!name) {
  9246. name = names[(userAgent.toLowerCase().match(/mac|win|linux/) || ['other'])[0]];
  9247. version = new Ext.Version('');
  9248. }
  9249. this.name = name;
  9250. this.version = version;
  9251. if (platform) {
  9252. this.setFlag(platform.replace(/ simulator$/i, ''));
  9253. }
  9254. this.setFlag(name);
  9255. if (version) {
  9256. this.setFlag(name + (version.getMajor() || ''));
  9257. this.setFlag(name + version.getShortVersion());
  9258. }
  9259. for (i in names) {
  9260. if (names.hasOwnProperty(i)) {
  9261. item = names[i];
  9262. if (!is.hasOwnProperty(name)) {
  9263. this.setFlag(item, (name === item));
  9264. }
  9265. }
  9266. }
  9267. // Detect if the device is the iPhone 5.
  9268. if (this.name == "iOS" && window.screen.height == 568) {
  9269. this.setFlag('iPhone5');
  9270. }
  9271. return this;
  9272. }
  9273. }, function() {
  9274. var navigation = Ext.global.navigator,
  9275. userAgent = navigation.userAgent,
  9276. osEnv, osName, deviceType;
  9277. /**
  9278. * @class Ext.os
  9279. * @extends Ext.env.OS
  9280. * @singleton
  9281. * Provides useful information about the current operating system environment.
  9282. *
  9283. * Example:
  9284. *
  9285. * if (Ext.os.is.Windows) {
  9286. * // Windows specific code here
  9287. * }
  9288. *
  9289. * if (Ext.os.is.iOS) {
  9290. * // iPad, iPod, iPhone, etc.
  9291. * }
  9292. *
  9293. * console.log("Version " + Ext.os.version);
  9294. *
  9295. * For a full list of supported values, refer to the {@link #is} property/method.
  9296. *
  9297. * @aside guide environment_package
  9298. */
  9299. Ext.os = osEnv = new this(userAgent, navigation.platform);
  9300. osName = osEnv.name;
  9301. var search = window.location.search.match(/deviceType=(Tablet|Phone)/),
  9302. nativeDeviceType = window.deviceType;
  9303. // Override deviceType by adding a get variable of deviceType. NEEDED FOR DOCS APP.
  9304. // E.g: example/kitchen-sink.html?deviceType=Phone
  9305. if (search && search[1]) {
  9306. deviceType = search[1];
  9307. }
  9308. else if (nativeDeviceType === 'iPhone') {
  9309. deviceType = 'Phone';
  9310. }
  9311. else if (nativeDeviceType === 'iPad') {
  9312. deviceType = 'Tablet';
  9313. }
  9314. else {
  9315. if (!osEnv.is.Android && !osEnv.is.iOS && /Windows|Linux|MacOS/.test(osName)) {
  9316. deviceType = 'Desktop';
  9317. // always set it to false when you are on a desktop
  9318. Ext.browser.is.WebView = false;
  9319. }
  9320. else if (osEnv.is.iPad || osEnv.is.Android3 || (osEnv.is.Android4 && userAgent.search(/mobile/i) == -1)) {
  9321. deviceType = 'Tablet';
  9322. }
  9323. else {
  9324. deviceType = 'Phone';
  9325. }
  9326. }
  9327. /**
  9328. * @property {String} deviceType
  9329. * The generic type of the current device.
  9330. *
  9331. * Possible values:
  9332. *
  9333. * - Phone
  9334. * - Tablet
  9335. * - Desktop
  9336. *
  9337. * For testing purposes the deviceType can be overridden by adding
  9338. * a deviceType parameter to the URL of the page, like so:
  9339. *
  9340. * http://localhost/mypage.html?deviceType=Tablet
  9341. *
  9342. */
  9343. osEnv.setFlag(deviceType, true);
  9344. osEnv.deviceType = deviceType;
  9345. /**
  9346. * @class Ext.is
  9347. * Used to detect if the current browser supports a certain feature, and the type of the current browser.
  9348. * @deprecated 2.0.0
  9349. * Please refer to the {@link Ext.browser}, {@link Ext.os} and {@link Ext.feature} classes instead.
  9350. */
  9351. });
  9352. //@tag dom,core
  9353. /**
  9354. * Provides information about browser.
  9355. *
  9356. * Should not be manually instantiated unless for unit-testing.
  9357. * Access the global instance stored in {@link Ext.browser} instead.
  9358. * @private
  9359. */
  9360. Ext.define('Ext.env.Feature', {
  9361. requires: ['Ext.env.Browser', 'Ext.env.OS'],
  9362. constructor: function() {
  9363. this.testElements = {};
  9364. this.has = function(name) {
  9365. return !!this.has[name];
  9366. };
  9367. return this;
  9368. },
  9369. getTestElement: function(tag, createNew) {
  9370. if (tag === undefined) {
  9371. tag = 'div';
  9372. }
  9373. else if (typeof tag !== 'string') {
  9374. return tag;
  9375. }
  9376. if (createNew) {
  9377. return document.createElement(tag);
  9378. }
  9379. if (!this.testElements[tag]) {
  9380. this.testElements[tag] = document.createElement(tag);
  9381. }
  9382. return this.testElements[tag];
  9383. },
  9384. isStyleSupported: function(name, tag) {
  9385. var elementStyle = this.getTestElement(tag).style,
  9386. cName = Ext.String.capitalize(name);
  9387. if (typeof elementStyle[name] !== 'undefined'
  9388. || typeof elementStyle[Ext.browser.getStylePrefix(name) + cName] !== 'undefined') {
  9389. return true;
  9390. }
  9391. return false;
  9392. },
  9393. isEventSupported: function(name, tag) {
  9394. if (tag === undefined) {
  9395. tag = window;
  9396. }
  9397. var element = this.getTestElement(tag),
  9398. eventName = 'on' + name.toLowerCase(),
  9399. isSupported = (eventName in element);
  9400. if (!isSupported) {
  9401. if (element.setAttribute && element.removeAttribute) {
  9402. element.setAttribute(eventName, '');
  9403. isSupported = typeof element[eventName] === 'function';
  9404. if (typeof element[eventName] !== 'undefined') {
  9405. element[eventName] = undefined;
  9406. }
  9407. element.removeAttribute(eventName);
  9408. }
  9409. }
  9410. return isSupported;
  9411. },
  9412. getSupportedPropertyName: function(object, name) {
  9413. var vendorName = Ext.browser.getVendorProperyName(name);
  9414. if (vendorName in object) {
  9415. return vendorName;
  9416. }
  9417. else if (name in object) {
  9418. return name;
  9419. }
  9420. return null;
  9421. },
  9422. registerTest: Ext.Function.flexSetter(function(name, fn) {
  9423. this.has[name] = fn.call(this);
  9424. return this;
  9425. })
  9426. }, function() {
  9427. /**
  9428. * @class Ext.feature
  9429. * @extend Ext.env.Feature
  9430. * @singleton
  9431. *
  9432. * A simple class to verify if a browser feature exists or not on the current device.
  9433. *
  9434. * if (Ext.feature.has.Canvas) {
  9435. * // do some cool things with canvas here
  9436. * }
  9437. *
  9438. * See the {@link #has} property/method for details of the features that can be detected.
  9439. *
  9440. * @aside guide environment_package
  9441. */
  9442. Ext.feature = new this;
  9443. var has = Ext.feature.has;
  9444. /**
  9445. * @method has
  9446. * @member Ext.feature
  9447. * Verifies if a browser feature exists or not on the current device.
  9448. *
  9449. * A "hybrid" property, can be either accessed as a method call, i.e:
  9450. *
  9451. * if (Ext.feature.has('Canvas')) {
  9452. * // ...
  9453. * }
  9454. *
  9455. * or as an object with boolean properties, i.e:
  9456. *
  9457. * if (Ext.feature.has.Canvas) {
  9458. * // ...
  9459. * }
  9460. *
  9461. * Possible properties/parameter values:
  9462. *
  9463. * - Canvas
  9464. * - Svg
  9465. * - Vml
  9466. * - Touch - supports touch events (`touchstart`).
  9467. * - Orientation - supports different orientations.
  9468. * - OrientationChange - supports the `orientationchange` event.
  9469. * - DeviceMotion - supports the `devicemotion` event.
  9470. * - Geolocation
  9471. * - SqlDatabase
  9472. * - WebSockets
  9473. * - Range - supports [DOM document fragments.][1]
  9474. * - CreateContextualFragment - supports HTML fragment parsing using [range.createContextualFragment()][2].
  9475. * - History - supports history management with [history.pushState()][3].
  9476. * - CssTransforms
  9477. * - Css3dTransforms
  9478. * - CssAnimations
  9479. * - CssTransitions
  9480. * - Audio - supports the `<audio>` tag.
  9481. * - Video - supports the `<video>` tag.
  9482. * - ClassList - supports the HTML5 classList API.
  9483. * - LocalStorage - LocalStorage is supported and can be written to.
  9484. *
  9485. * [1]: https://developer.mozilla.org/en/DOM/range
  9486. * [2]: https://developer.mozilla.org/en/DOM/range.createContextualFragment
  9487. * [3]: https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history#The_pushState().C2.A0method
  9488. *
  9489. * @param {String} value The feature name to check.
  9490. * @return {Boolean}
  9491. */
  9492. Ext.feature.registerTest({
  9493. Canvas: function() {
  9494. var element = this.getTestElement('canvas');
  9495. return !!(element && element.getContext && element.getContext('2d'));
  9496. },
  9497. Svg: function() {
  9498. var doc = document;
  9499. return !!(doc.createElementNS && !!doc.createElementNS("http:/" + "/www.w3.org/2000/svg", "svg").createSVGRect);
  9500. },
  9501. Vml: function() {
  9502. var element = this.getTestElement(),
  9503. ret = false;
  9504. element.innerHTML = "<!--[if vml]><br><![endif]-->";
  9505. ret = (element.childNodes.length === 1);
  9506. element.innerHTML = "";
  9507. return ret;
  9508. },
  9509. Touch: function() {
  9510. return this.isEventSupported('touchstart') && !(Ext.os && Ext.os.name.match(/Windows|MacOS|Linux/) && !Ext.os.is.BlackBerry6);
  9511. },
  9512. Orientation: function() {
  9513. return ('orientation' in window) && this.isEventSupported('orientationchange');
  9514. },
  9515. OrientationChange: function() {
  9516. return this.isEventSupported('orientationchange');
  9517. },
  9518. DeviceMotion: function() {
  9519. return this.isEventSupported('devicemotion');
  9520. },
  9521. Geolocation: function() {
  9522. return 'geolocation' in window.navigator;
  9523. },
  9524. SqlDatabase: function() {
  9525. return 'openDatabase' in window;
  9526. },
  9527. WebSockets: function() {
  9528. return 'WebSocket' in window;
  9529. },
  9530. Range: function() {
  9531. return !!document.createRange;
  9532. },
  9533. CreateContextualFragment: function() {
  9534. var range = !!document.createRange ? document.createRange() : false;
  9535. return range && !!range.createContextualFragment;
  9536. },
  9537. History: function() {
  9538. return ('history' in window && 'pushState' in window.history);
  9539. },
  9540. CssTransforms: function() {
  9541. return this.isStyleSupported('transform');
  9542. },
  9543. Css3dTransforms: function() {
  9544. // See https://sencha.jira.com/browse/TOUCH-1544
  9545. return this.has('CssTransforms') && this.isStyleSupported('perspective') && !Ext.os.is.Android2;
  9546. },
  9547. CssAnimations: function() {
  9548. return this.isStyleSupported('animationName');
  9549. },
  9550. CssTransitions: function() {
  9551. return this.isStyleSupported('transitionProperty');
  9552. },
  9553. Audio: function() {
  9554. return !!this.getTestElement('audio').canPlayType;
  9555. },
  9556. Video: function() {
  9557. return !!this.getTestElement('video').canPlayType;
  9558. },
  9559. ClassList: function() {
  9560. return "classList" in this.getTestElement();
  9561. },
  9562. LocalStorage : function() {
  9563. var supported = false;
  9564. try {
  9565. if ('localStorage' in window && window['localStorage'] !== null) {
  9566. //this should throw an error in private browsing mode in iOS
  9567. localStorage.setItem('sencha-localstorage-test', 'test success');
  9568. //clean up if setItem worked
  9569. localStorage.removeItem('sencha-localstorage-test');
  9570. supported = true;
  9571. }
  9572. } catch ( e ) {}
  9573. return supported;
  9574. }
  9575. });
  9576. });
  9577. //@tag dom,core
  9578. //@define Ext.DomQuery
  9579. //@define Ext.core.DomQuery
  9580. //@require Ext.env.Feature
  9581. /**
  9582. * @class Ext.DomQuery
  9583. * @alternateClassName Ext.dom.Query
  9584. *
  9585. * Provides functionality to select elements on the page based on a CSS selector. Delegates to
  9586. * document.querySelectorAll. More information can be found at
  9587. * [http://www.w3.org/TR/css3-selectors/](http://www.w3.org/TR/css3-selectors/)
  9588. *
  9589. * All selectors, attribute filters and pseudos below can be combined infinitely in any order. For example
  9590. * `div.foo:nth-child(odd)[@foo=bar].bar:first` would be a perfectly valid selector.
  9591. *
  9592. * ## Element Selectors:
  9593. *
  9594. * * \* any element
  9595. * * E an element with the tag E
  9596. * * E F All descendant elements of E that have the tag F
  9597. * * E > F or E/F all direct children elements of E that have the tag F
  9598. * * E + F all elements with the tag F that are immediately preceded by an element with the tag E
  9599. * * E ~ F all elements with the tag F that are preceded by a sibling element with the tag E
  9600. *
  9601. * ## Attribute Selectors:
  9602. *
  9603. * The use of @ and quotes are optional. For example, div[@foo='bar'] is also a valid attribute selector.
  9604. *
  9605. * * E[foo] has an attribute "foo"
  9606. * * E[foo=bar] has an attribute "foo" that equals "bar"
  9607. * * E[foo^=bar] has an attribute "foo" that starts with "bar"
  9608. * * E[foo$=bar] has an attribute "foo" that ends with "bar"
  9609. * * E[foo*=bar] has an attribute "foo" that contains the substring "bar"
  9610. * * E[foo%=2] has an attribute "foo" that is evenly divisible by 2
  9611. * * E[foo!=bar] has an attribute "foo" that does not equal "bar"
  9612. *
  9613. * ## Pseudo Classes:
  9614. *
  9615. * * E:first-child E is the first child of its parent
  9616. * * E:last-child E is the last child of its parent
  9617. * * E:nth-child(n) E is the nth child of its parent (1 based as per the spec)
  9618. * * E:nth-child(odd) E is an odd child of its parent
  9619. * * E:nth-child(even) E is an even child of its parent
  9620. * * E:only-child E is the only child of its parent
  9621. * * E:checked E is an element that is has a checked attribute that is true (e.g. a radio or checkbox)
  9622. * * E:first the first E in the resultset
  9623. * * E:last the last E in the resultset
  9624. * * E:nth(n) the nth E in the resultset (1 based)
  9625. * * E:odd shortcut for :nth-child(odd)
  9626. * * E:even shortcut for :nth-child(even)
  9627. * * E:not(S) an E element that does not match simple selector S
  9628. * * E:has(S) an E element that has a descendant that matches simple selector S
  9629. * * E:next(S) an E element whose next sibling matches simple selector S
  9630. * * E:prev(S) an E element whose previous sibling matches simple selector S
  9631. * * E:any(S1|S2|S2) an E element which matches any of the simple selectors S1, S2 or S3//\\
  9632. *
  9633. * ## CSS Value Selectors:
  9634. *
  9635. * * E{display=none} CSS value "display" that equals "none"
  9636. * * E{display^=none} CSS value "display" that starts with "none"
  9637. * * E{display$=none} CSS value "display" that ends with "none"
  9638. * * E{display*=none} CSS value "display" that contains the substring "none"
  9639. * * E{display%=2} CSS value "display" that is evenly divisible by 2
  9640. * * E{display!=none} CSS value "display" that does not equal "none"
  9641. */
  9642. Ext.define('Ext.dom.Query', {
  9643. /**
  9644. * Selects a group of elements.
  9645. * @param {String} selector The selector/xpath query (can be a comma separated list of selectors)
  9646. * @param {HTMLElement/String} [root] The start of the query (defaults to document).
  9647. * @return {HTMLElement[]} An Array of DOM elements which match the selector. If there are
  9648. * no matches, and empty Array is returned.
  9649. */
  9650. select: function(q, root) {
  9651. var results = [],
  9652. nodes,
  9653. i,
  9654. j,
  9655. qlen,
  9656. nlen;
  9657. root = root || document;
  9658. if (typeof root == 'string') {
  9659. root = document.getElementById(root);
  9660. }
  9661. q = q.split(",");
  9662. for (i = 0,qlen = q.length; i < qlen; i++) {
  9663. if (typeof q[i] == 'string') {
  9664. //support for node attribute selection
  9665. if (q[i][0] == '@') {
  9666. nodes = root.getAttributeNode(q[i].substring(1));
  9667. results.push(nodes);
  9668. }
  9669. else {
  9670. nodes = root.querySelectorAll(q[i]);
  9671. for (j = 0,nlen = nodes.length; j < nlen; j++) {
  9672. results.push(nodes[j]);
  9673. }
  9674. }
  9675. }
  9676. }
  9677. return results;
  9678. },
  9679. /**
  9680. * Selects a single element.
  9681. * @param {String} selector The selector/xpath query
  9682. * @param {HTMLElement/String} [root] The start of the query (defaults to document).
  9683. * @return {HTMLElement} The DOM element which matched the selector.
  9684. */
  9685. selectNode: function(q, root) {
  9686. return this.select(q, root)[0];
  9687. },
  9688. /**
  9689. * Returns true if the passed element(s) match the passed simple selector (e.g. div.some-class or span:first-child)
  9690. * @param {String/HTMLElement/Array} el An element id, element or array of elements
  9691. * @param {String} selector The simple selector to test
  9692. * @return {Boolean}
  9693. */
  9694. is: function(el, q) {
  9695. if (typeof el == "string") {
  9696. el = document.getElementById(el);
  9697. }
  9698. return this.select(q).indexOf(el) !== -1;
  9699. },
  9700. isXml: function(el) {
  9701. var docEl = (el ? el.ownerDocument || el : 0).documentElement;
  9702. return docEl ? docEl.nodeName !== "HTML" : false;
  9703. }
  9704. }, function() {
  9705. Ext.ns('Ext.core');
  9706. Ext.core.DomQuery = Ext.DomQuery = new this();
  9707. Ext.query = Ext.Function.alias(Ext.DomQuery, 'select');
  9708. });
  9709. //@tag dom,core
  9710. //@define Ext.DomHelper
  9711. //@require Ext.dom.Query
  9712. /**
  9713. * @class Ext.DomHelper
  9714. * @alternateClassName Ext.dom.Helper
  9715. *
  9716. * The DomHelper class provides a layer of abstraction from DOM and transparently supports creating elements via DOM or
  9717. * using HTML fragments. It also has the ability to create HTML fragment templates from your DOM building code.
  9718. *
  9719. * ## DomHelper element specification object
  9720. *
  9721. * A specification object is used when creating elements. Attributes of this object are assumed to be element
  9722. * attributes, except for 4 special attributes:
  9723. *
  9724. * * **tag**: The tag name of the element
  9725. * * **children (or cn)**: An array of the same kind of element definition objects to be created and appended. These
  9726. * can be nested as deep as you want.
  9727. * * **cls**: The class attribute of the element. This will end up being either the "class" attribute on a HTML
  9728. * fragment or className for a DOM node, depending on whether DomHelper is using fragments or DOM.
  9729. * * **html**: The innerHTML for the element
  9730. *
  9731. * ## Insertion methods
  9732. *
  9733. * Commonly used insertion methods:
  9734. *
  9735. * * {@link #append}
  9736. * * {@link #insertBefore}
  9737. * * {@link #insertAfter}
  9738. * * {@link #overwrite}
  9739. * * {@link #insertHtml}
  9740. *
  9741. * ## Example
  9742. *
  9743. * This is an example, where an unordered list with 3 children items is appended to an existing element with id
  9744. * 'my-div':
  9745. *
  9746. * var dh = Ext.DomHelper; // create shorthand alias
  9747. * // specification object
  9748. * var spec = {
  9749. * id: 'my-ul',
  9750. * tag: 'ul',
  9751. * cls: 'my-list',
  9752. * // append children after creating
  9753. * children: [ // may also specify 'cn' instead of 'children'
  9754. * {tag: 'li', id: 'item0', html: 'List Item 0'},
  9755. * {tag: 'li', id: 'item1', html: 'List Item 1'},
  9756. * {tag: 'li', id: 'item2', html: 'List Item 2'}
  9757. * ]
  9758. * };
  9759. * var list = dh.append(
  9760. * 'my-div', // the context element 'my-div' can either be the id or the actual node
  9761. * spec // the specification object
  9762. * );
  9763. *
  9764. * Element creation specification parameters in this class may also be passed as an Array of specification objects.
  9765. * This can be used to insert multiple sibling nodes into an existing container very efficiently. For example, to add
  9766. * more list items to the example above:
  9767. *
  9768. * dh.append('my-ul', [
  9769. * {tag: 'li', id: 'item3', html: 'List Item 3'},
  9770. * {tag: 'li', id: 'item4', html: 'List Item 4'}
  9771. * ]);
  9772. *
  9773. * ## Templating
  9774. *
  9775. * The real power is in the built-in templating. Instead of creating or appending any elements, createTemplate returns
  9776. * a Template object which can be used over and over to insert new elements. Revisiting the example above, we could
  9777. * utilize templating this time:
  9778. *
  9779. * // create the node
  9780. * var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
  9781. * // get template
  9782. * var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
  9783. *
  9784. * for(var i = 0; i < 5; i++){
  9785. * tpl.append(list, i); // use template to append to the actual node
  9786. * }
  9787. *
  9788. * An example using a template:
  9789. *
  9790. * var html = '"{0}" href="{1}" class="nav">{2}';
  9791. *
  9792. * var tpl = new Ext.DomHelper.createTemplate(html);
  9793. * tpl.append('blog-roll', ['link1', 'http://www.tommymaintz.com/', "Tommy's Site"]);
  9794. * tpl.append('blog-roll', ['link2', 'http://www.avins.org/', "Jamie's Site"]);
  9795. *
  9796. * The same example using named parameters:
  9797. *
  9798. * var html = '"{id}" href="{url}" class="nav">{text}';
  9799. *
  9800. * var tpl = new Ext.DomHelper.createTemplate(html);
  9801. * tpl.append('blog-roll', {
  9802. * id: 'link1',
  9803. * url: 'http://www.tommymaintz.com/',
  9804. * text: "Tommy's Site"
  9805. * });
  9806. * tpl.append('blog-roll', {
  9807. * id: 'link2',
  9808. * url: 'http://www.avins.org/',
  9809. * text: "Jamie's Site"
  9810. * });
  9811. *
  9812. * ## Compiling Templates
  9813. *
  9814. * Templates are applied using regular expressions. The performance is great, but if you are adding a bunch of DOM
  9815. * elements using the same template, you can increase performance even further by "compiling" the template. The way
  9816. * "compile()" works is the template is parsed and broken up at the different variable points and a dynamic function is
  9817. * created and eval'ed. The generated function performs string concatenation of these parts and the passed variables
  9818. * instead of using regular expressions.
  9819. *
  9820. * var html = '"{id}" href="{url}" class="nav">{text}';
  9821. *
  9822. * var tpl = new Ext.DomHelper.createTemplate(html);
  9823. * tpl.compile();
  9824. *
  9825. * // ... use template like normal
  9826. *
  9827. * ## Performance Boost
  9828. *
  9829. * DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead of DOM can
  9830. * significantly boost performance.
  9831. *
  9832. * Element creation specification parameters may also be strings. If useDom is false, then the string is used as
  9833. * innerHTML. If useDom is true, a string specification results in the creation of a text node. Usage:
  9834. *
  9835. * Ext.DomHelper.useDom = true; // force it to use DOM; reduces performance
  9836. *
  9837. */
  9838. Ext.define('Ext.dom.Helper', {
  9839. emptyTags : /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
  9840. confRe : /tag|children|cn|html|tpl|tplData$/i,
  9841. endRe : /end/i,
  9842. attribXlat: { cls : 'class', htmlFor : 'for' },
  9843. closeTags: {},
  9844. decamelizeName : function () {
  9845. var camelCaseRe = /([a-z])([A-Z])/g,
  9846. cache = {};
  9847. function decamel (match, p1, p2) {
  9848. return p1 + '-' + p2.toLowerCase();
  9849. }
  9850. return function (s) {
  9851. return cache[s] || (cache[s] = s.replace(camelCaseRe, decamel));
  9852. };
  9853. }(),
  9854. generateMarkup: function(spec, buffer) {
  9855. var me = this,
  9856. attr, val, tag, i, closeTags;
  9857. if (typeof spec == "string") {
  9858. buffer.push(spec);
  9859. } else if (Ext.isArray(spec)) {
  9860. for (i = 0; i < spec.length; i++) {
  9861. if (spec[i]) {
  9862. me.generateMarkup(spec[i], buffer);
  9863. }
  9864. }
  9865. } else {
  9866. tag = spec.tag || 'div';
  9867. buffer.push('<', tag);
  9868. for (attr in spec) {
  9869. if (spec.hasOwnProperty(attr)) {
  9870. val = spec[attr];
  9871. if (!me.confRe.test(attr)) {
  9872. if (typeof val == "object") {
  9873. buffer.push(' ', attr, '="');
  9874. me.generateStyles(val, buffer).push('"');
  9875. } else {
  9876. buffer.push(' ', me.attribXlat[attr] || attr, '="', val, '"');
  9877. }
  9878. }
  9879. }
  9880. }
  9881. // Now either just close the tag or try to add children and close the tag.
  9882. if (me.emptyTags.test(tag)) {
  9883. buffer.push('/>');
  9884. } else {
  9885. buffer.push('>');
  9886. // Apply the tpl html, and cn specifications
  9887. if ((val = spec.tpl)) {
  9888. val.applyOut(spec.tplData, buffer);
  9889. }
  9890. if ((val = spec.html)) {
  9891. buffer.push(val);
  9892. }
  9893. if ((val = spec.cn || spec.children)) {
  9894. me.generateMarkup(val, buffer);
  9895. }
  9896. // we generate a lot of close tags, so cache them rather than push 3 parts
  9897. closeTags = me.closeTags;
  9898. buffer.push(closeTags[tag] || (closeTags[tag] = '</' + tag + '>'));
  9899. }
  9900. }
  9901. return buffer;
  9902. },
  9903. /**
  9904. * Converts the styles from the given object to text. The styles are CSS style names
  9905. * with their associated value.
  9906. *
  9907. * The basic form of this method returns a string:
  9908. *
  9909. * var s = Ext.DomHelper.generateStyles({
  9910. * backgroundColor: 'red'
  9911. * });
  9912. *
  9913. * // s = 'background-color:red;'
  9914. *
  9915. * Alternatively, this method can append to an output array.
  9916. *
  9917. * var buf = [];
  9918. *
  9919. * // ...
  9920. *
  9921. * Ext.DomHelper.generateStyles({
  9922. * backgroundColor: 'red'
  9923. * }, buf);
  9924. *
  9925. * In this case, the style text is pushed on to the array and the array is returned.
  9926. *
  9927. * @param {Object} styles The object describing the styles.
  9928. * @param {String[]} [buffer] The output buffer.
  9929. * @return {String/String[]} If buffer is passed, it is returned. Otherwise the style
  9930. * string is returned.
  9931. */
  9932. generateStyles: function (styles, buffer) {
  9933. var a = buffer || [],
  9934. name;
  9935. for (name in styles) {
  9936. if (styles.hasOwnProperty(name)) {
  9937. a.push(this.decamelizeName(name), ':', styles[name], ';');
  9938. }
  9939. }
  9940. return buffer || a.join('');
  9941. },
  9942. /**
  9943. * Returns the markup for the passed Element(s) config.
  9944. * @param {Object} spec The DOM object spec (and children).
  9945. * @return {String}
  9946. */
  9947. markup: function(spec) {
  9948. if (typeof spec == "string") {
  9949. return spec;
  9950. }
  9951. var buf = this.generateMarkup(spec, []);
  9952. return buf.join('');
  9953. },
  9954. /**
  9955. * Applies a style specification to an element.
  9956. * @param {String/HTMLElement} el The element to apply styles to
  9957. * @param {String/Object/Function} styles A style specification string e.g. 'width:100px', or object in the form {width:'100px'}, or
  9958. * a function which returns such a specification.
  9959. */
  9960. applyStyles: function(el, styles) {
  9961. Ext.fly(el).applyStyles(styles);
  9962. },
  9963. /**
  9964. * @private
  9965. * Fix for browsers which no longer support createContextualFragment
  9966. */
  9967. createContextualFragment: function(html){
  9968. var div = document.createElement("div"),
  9969. fragment = document.createDocumentFragment(),
  9970. i = 0,
  9971. length, childNodes;
  9972. div.innerHTML = html;
  9973. childNodes = div.childNodes;
  9974. length = childNodes.length;
  9975. for (; i < length; i++) {
  9976. fragment.appendChild(childNodes[i].cloneNode(true));
  9977. }
  9978. return fragment;
  9979. },
  9980. /**
  9981. * Inserts an HTML fragment into the DOM.
  9982. * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
  9983. *
  9984. * For example take the following HTML: `<div>Contents</div>`
  9985. *
  9986. * Using different `where` values inserts element to the following places:
  9987. *
  9988. * - beforeBegin: `<HERE><div>Contents</div>`
  9989. * - afterBegin: `<div><HERE>Contents</div>`
  9990. * - beforeEnd: `<div>Contents<HERE></div>`
  9991. * - afterEnd: `<div>Contents</div><HERE>`
  9992. *
  9993. * @param {HTMLElement/TextNode} el The context element
  9994. * @param {String} html The HTML fragment
  9995. * @return {HTMLElement} The new node
  9996. */
  9997. insertHtml: function(where, el, html) {
  9998. var setStart, range, frag, rangeEl, isBeforeBegin, isAfterBegin;
  9999. where = where.toLowerCase();
  10000. if (Ext.isTextNode(el)) {
  10001. if (where == 'afterbegin' ) {
  10002. where = 'beforebegin';
  10003. }
  10004. else if (where == 'beforeend') {
  10005. where = 'afterend';
  10006. }
  10007. }
  10008. isBeforeBegin = where == 'beforebegin';
  10009. isAfterBegin = where == 'afterbegin';
  10010. range = Ext.feature.has.CreateContextualFragment ? el.ownerDocument.createRange() : undefined;
  10011. setStart = 'setStart' + (this.endRe.test(where) ? 'After' : 'Before');
  10012. if (isBeforeBegin || where == 'afterend') {
  10013. if (range) {
  10014. range[setStart](el);
  10015. frag = range.createContextualFragment(html);
  10016. }
  10017. else {
  10018. frag = this.createContextualFragment(html);
  10019. }
  10020. el.parentNode.insertBefore(frag, isBeforeBegin ? el : el.nextSibling);
  10021. return el[(isBeforeBegin ? 'previous' : 'next') + 'Sibling'];
  10022. }
  10023. else {
  10024. rangeEl = (isAfterBegin ? 'first' : 'last') + 'Child';
  10025. if (el.firstChild) {
  10026. if (range) {
  10027. range[setStart](el[rangeEl]);
  10028. frag = range.createContextualFragment(html);
  10029. } else {
  10030. frag = this.createContextualFragment(html);
  10031. }
  10032. if (isAfterBegin) {
  10033. el.insertBefore(frag, el.firstChild);
  10034. } else {
  10035. el.appendChild(frag);
  10036. }
  10037. } else {
  10038. el.innerHTML = html;
  10039. }
  10040. return el[rangeEl];
  10041. }
  10042. },
  10043. /**
  10044. * Creates new DOM element(s) and inserts them before el.
  10045. * @param {String/HTMLElement/Ext.Element} el The context element
  10046. * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
  10047. * @param {Boolean} [returnElement] true to return a Ext.Element
  10048. * @return {HTMLElement/Ext.Element} The new node
  10049. */
  10050. insertBefore: function(el, o, returnElement) {
  10051. return this.doInsert(el, o, returnElement, 'beforebegin');
  10052. },
  10053. /**
  10054. * Creates new DOM element(s) and inserts them after el.
  10055. * @param {String/HTMLElement/Ext.Element} el The context element
  10056. * @param {Object} o The DOM object spec (and children)
  10057. * @param {Boolean} [returnElement] true to return a Ext.Element
  10058. * @return {HTMLElement/Ext.Element} The new node
  10059. */
  10060. insertAfter: function(el, o, returnElement) {
  10061. return this.doInsert(el, o, returnElement, 'afterend');
  10062. },
  10063. /**
  10064. * Creates new DOM element(s) and inserts them as the first child of el.
  10065. * @param {String/HTMLElement/Ext.Element} el The context element
  10066. * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
  10067. * @param {Boolean} [returnElement] true to return a Ext.Element
  10068. * @return {HTMLElement/Ext.Element} The new node
  10069. */
  10070. insertFirst: function(el, o, returnElement) {
  10071. return this.doInsert(el, o, returnElement, 'afterbegin');
  10072. },
  10073. /**
  10074. * Creates new DOM element(s) and appends them to el.
  10075. * @param {String/HTMLElement/Ext.Element} el The context element
  10076. * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
  10077. * @param {Boolean} [returnElement] true to return a Ext.Element
  10078. * @return {HTMLElement/Ext.Element} The new node
  10079. */
  10080. append: function(el, o, returnElement) {
  10081. return this.doInsert(el, o, returnElement, 'beforeend');
  10082. },
  10083. /**
  10084. * Creates new DOM element(s) and overwrites the contents of el with them.
  10085. * @param {String/HTMLElement/Ext.Element} el The context element
  10086. * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
  10087. * @param {Boolean} [returnElement] true to return a Ext.Element
  10088. * @return {HTMLElement/Ext.Element} The new node
  10089. */
  10090. overwrite: function(el, o, returnElement) {
  10091. el = Ext.getDom(el);
  10092. el.innerHTML = this.markup(o);
  10093. return returnElement ? Ext.get(el.firstChild) : el.firstChild;
  10094. },
  10095. doInsert: function(el, o, returnElement, pos) {
  10096. var newNode = this.insertHtml(pos, Ext.getDom(el), this.markup(o));
  10097. return returnElement ? Ext.get(newNode, true) : newNode;
  10098. },
  10099. /**
  10100. * Creates a new Ext.Template from the DOM object spec.
  10101. * @param {Object} o The DOM object spec (and children)
  10102. * @return {Ext.Template} The new template
  10103. */
  10104. createTemplate: function(o) {
  10105. var html = this.markup(o);
  10106. return new Ext.Template(html);
  10107. }
  10108. }, function() {
  10109. Ext.ns('Ext.core');
  10110. Ext.core.DomHelper = Ext.DomHelper = new this;
  10111. });
  10112. //@tag dom,core
  10113. //@require Ext.dom.Helper
  10114. /**
  10115. * An Identifiable mixin.
  10116. * @private
  10117. */
  10118. Ext.define('Ext.mixin.Identifiable', {
  10119. statics: {
  10120. uniqueIds: {}
  10121. },
  10122. isIdentifiable: true,
  10123. mixinId: 'identifiable',
  10124. idCleanRegex: /\.|[^\w\-]/g,
  10125. defaultIdPrefix: 'ext-',
  10126. defaultIdSeparator: '-',
  10127. getOptimizedId: function() {
  10128. return this.id;
  10129. },
  10130. getUniqueId: function() {
  10131. var id = this.id,
  10132. prototype, separator, xtype, uniqueIds, prefix;
  10133. if (!id) {
  10134. prototype = this.self.prototype;
  10135. separator = this.defaultIdSeparator;
  10136. uniqueIds = Ext.mixin.Identifiable.uniqueIds;
  10137. if (!prototype.hasOwnProperty('identifiablePrefix')) {
  10138. xtype = this.xtype;
  10139. if (xtype) {
  10140. prefix = this.defaultIdPrefix + xtype + separator;
  10141. }
  10142. else {
  10143. prefix = prototype.$className.replace(this.idCleanRegex, separator).toLowerCase() + separator;
  10144. }
  10145. prototype.identifiablePrefix = prefix;
  10146. }
  10147. prefix = this.identifiablePrefix;
  10148. if (!uniqueIds.hasOwnProperty(prefix)) {
  10149. uniqueIds[prefix] = 0;
  10150. }
  10151. id = this.id = prefix + (++uniqueIds[prefix]);
  10152. }
  10153. this.getUniqueId = this.getOptimizedId;
  10154. return id;
  10155. },
  10156. setId: function(id) {
  10157. this.id = id;
  10158. },
  10159. /**
  10160. * Retrieves the id of this component. Will autogenerate an id if one has not already been set.
  10161. * @return {String} id
  10162. */
  10163. getId: function() {
  10164. var id = this.id;
  10165. if (!id) {
  10166. id = this.getUniqueId();
  10167. }
  10168. this.getId = this.getOptimizedId;
  10169. return id;
  10170. }
  10171. });
  10172. //@tag dom,core
  10173. //@define Ext.Element-all
  10174. //@define Ext.Element
  10175. /**
  10176. * Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser differences.
  10177. *
  10178. * All instances of this class inherit the methods of Ext.Fx making visual effects easily available to all DOM elements.
  10179. *
  10180. * Note that the events documented in this class are not Ext events, they encapsulate browser events. To access the
  10181. * underlying browser event, see {@link Ext.EventObject#browserEvent}. Some older browsers may not support the full range of
  10182. * events. Which events are supported is beyond the control of Sencha Touch.
  10183. *
  10184. * ## Usage
  10185. *
  10186. * // by id
  10187. * var el = Ext.get("my-div");
  10188. *
  10189. * // by DOM element reference
  10190. * var el = Ext.get(myDivElement);
  10191. *
  10192. * ## Composite (Collections of) Elements
  10193. *
  10194. * For working with collections of Elements, see {@link Ext.CompositeElement}.
  10195. *
  10196. * @mixins Ext.mixin.Observable
  10197. */
  10198. Ext.define('Ext.dom.Element', {
  10199. alternateClassName: 'Ext.Element',
  10200. mixins: [
  10201. 'Ext.mixin.Identifiable'
  10202. ],
  10203. requires: [
  10204. 'Ext.dom.Query',
  10205. 'Ext.dom.Helper'
  10206. ],
  10207. observableType: 'element',
  10208. xtype: 'element',
  10209. statics: {
  10210. CREATE_ATTRIBUTES: {
  10211. style: 'style',
  10212. className: 'className',
  10213. cls: 'cls',
  10214. classList: 'classList',
  10215. text: 'text',
  10216. hidden: 'hidden',
  10217. html: 'html',
  10218. children: 'children'
  10219. },
  10220. create: function(attributes, domNode) {
  10221. var ATTRIBUTES = this.CREATE_ATTRIBUTES,
  10222. element, elementStyle, tag, value, name, i, ln;
  10223. if (!attributes) {
  10224. attributes = {};
  10225. }
  10226. if (attributes.isElement) {
  10227. return attributes.dom;
  10228. }
  10229. else if ('nodeType' in attributes) {
  10230. return attributes;
  10231. }
  10232. if (typeof attributes == 'string') {
  10233. return document.createTextNode(attributes);
  10234. }
  10235. tag = attributes.tag;
  10236. if (!tag) {
  10237. tag = 'div';
  10238. }
  10239. if (attributes.namespace) {
  10240. element = document.createElementNS(attributes.namespace, tag);
  10241. } else {
  10242. element = document.createElement(tag);
  10243. }
  10244. elementStyle = element.style;
  10245. for (name in attributes) {
  10246. if (name != 'tag') {
  10247. value = attributes[name];
  10248. switch (name) {
  10249. case ATTRIBUTES.style:
  10250. if (typeof value == 'string') {
  10251. element.setAttribute(name, value);
  10252. }
  10253. else {
  10254. for (i in value) {
  10255. if (value.hasOwnProperty(i)) {
  10256. elementStyle[i] = value[i];
  10257. }
  10258. }
  10259. }
  10260. break;
  10261. case ATTRIBUTES.className:
  10262. case ATTRIBUTES.cls:
  10263. element.className = value;
  10264. break;
  10265. case ATTRIBUTES.classList:
  10266. element.className = value.join(' ');
  10267. break;
  10268. case ATTRIBUTES.text:
  10269. element.textContent = value;
  10270. break;
  10271. case ATTRIBUTES.hidden:
  10272. if (value) {
  10273. element.style.display = 'none';
  10274. }
  10275. break;
  10276. case ATTRIBUTES.html:
  10277. element.innerHTML = value;
  10278. break;
  10279. case ATTRIBUTES.children:
  10280. for (i = 0,ln = value.length; i < ln; i++) {
  10281. element.appendChild(this.create(value[i], true));
  10282. }
  10283. break;
  10284. default:
  10285. element.setAttribute(name, value);
  10286. }
  10287. }
  10288. }
  10289. if (domNode) {
  10290. return element;
  10291. }
  10292. else {
  10293. return this.get(element);
  10294. }
  10295. },
  10296. documentElement: null,
  10297. cache: {},
  10298. /**
  10299. * Retrieves Ext.dom.Element objects. {@link Ext#get} is alias for {@link Ext.dom.Element#get}.
  10300. *
  10301. * **This method does not retrieve {@link Ext.Element Element}s.** This method retrieves Ext.dom.Element
  10302. * objects which encapsulate DOM elements. To retrieve a Element by its ID, use {@link Ext.ElementManager#get}.
  10303. *
  10304. * Uses simple caching to consistently return the same object. Automatically fixes if an object was recreated with
  10305. * the same id via AJAX or DOM.
  10306. *
  10307. * @param {String/HTMLElement/Ext.Element} el The `id` of the node, a DOM Node or an existing Element.
  10308. * @return {Ext.dom.Element} The Element object (or `null` if no matching element was found).
  10309. * @static
  10310. * @inheritable
  10311. */
  10312. get: function(element) {
  10313. var cache = this.cache,
  10314. instance, dom, id;
  10315. if (!element) {
  10316. return null;
  10317. }
  10318. if (typeof element == 'string') {
  10319. if (cache.hasOwnProperty(element)) {
  10320. return cache[element];
  10321. }
  10322. if (!(dom = document.getElementById(element))) {
  10323. return null;
  10324. }
  10325. cache[element] = instance = new this(dom);
  10326. return instance;
  10327. }
  10328. if ('tagName' in element) { // dom element
  10329. id = element.id;
  10330. if (cache.hasOwnProperty(id)) {
  10331. return cache[id];
  10332. }
  10333. instance = new this(element);
  10334. cache[instance.getId()] = instance;
  10335. return instance;
  10336. }
  10337. if (element.isElement) {
  10338. return element;
  10339. }
  10340. if (element.isComposite) {
  10341. return element;
  10342. }
  10343. if (Ext.isArray(element)) {
  10344. return this.select(element);
  10345. }
  10346. if (element === document) {
  10347. // create a bogus element object representing the document object
  10348. if (!this.documentElement) {
  10349. this.documentElement = new this(document.documentElement);
  10350. this.documentElement.setId('ext-application');
  10351. }
  10352. return this.documentElement;
  10353. }
  10354. return null;
  10355. },
  10356. data: function(element, key, value) {
  10357. var cache = Ext.cache,
  10358. id, data;
  10359. element = this.get(element);
  10360. if (!element) {
  10361. return null;
  10362. }
  10363. id = element.id;
  10364. data = cache[id].data;
  10365. if (!data) {
  10366. cache[id].data = data = {};
  10367. }
  10368. if (arguments.length == 2) {
  10369. return data[key];
  10370. }
  10371. else {
  10372. return (data[key] = value);
  10373. }
  10374. }
  10375. },
  10376. isElement: true,
  10377. /**
  10378. * @event painted
  10379. * Fires whenever this Element actually becomes visible (painted) on the screen. This is useful when you need to
  10380. * perform 'read' operations on the DOM element, i.e: calculating natural sizes and positioning.
  10381. *
  10382. * __Note:__ This event is not available to be used with event delegation. Instead `painted` only fires if you explicitly
  10383. * add at least one listener to it, for performance reasons.
  10384. *
  10385. * @param {Ext.Element} this The component instance.
  10386. */
  10387. /**
  10388. * @event resize
  10389. * Important note: For the best performance on mobile devices, use this only when you absolutely need to monitor
  10390. * a Element's size.
  10391. *
  10392. * __Note:__ This event is not available to be used with event delegation. Instead `resize` only fires if you explicitly
  10393. * add at least one listener to it, for performance reasons.
  10394. *
  10395. * @param {Ext.Element} this The component instance.
  10396. */
  10397. constructor: function(dom) {
  10398. if (typeof dom == 'string') {
  10399. dom = document.getElementById(dom);
  10400. }
  10401. if (!dom) {
  10402. throw new Error("Invalid domNode reference or an id of an existing domNode: " + dom);
  10403. }
  10404. /**
  10405. * The DOM element
  10406. * @property dom
  10407. * @type HTMLElement
  10408. */
  10409. this.dom = dom;
  10410. this.getUniqueId();
  10411. },
  10412. attach: function (dom) {
  10413. this.dom = dom;
  10414. this.id = dom.id;
  10415. return this;
  10416. },
  10417. getUniqueId: function() {
  10418. var id = this.id,
  10419. dom;
  10420. if (!id) {
  10421. dom = this.dom;
  10422. if (dom.id.length > 0) {
  10423. this.id = id = dom.id;
  10424. }
  10425. else {
  10426. dom.id = id = this.mixins.identifiable.getUniqueId.call(this);
  10427. }
  10428. this.self.cache[id] = this;
  10429. }
  10430. return id;
  10431. },
  10432. setId: function(id) {
  10433. var currentId = this.id,
  10434. cache = this.self.cache;
  10435. if (currentId) {
  10436. delete cache[currentId];
  10437. }
  10438. this.dom.id = id;
  10439. /**
  10440. * The DOM element ID
  10441. * @property id
  10442. * @type String
  10443. */
  10444. this.id = id;
  10445. cache[id] = this;
  10446. return this;
  10447. },
  10448. /**
  10449. * Sets the `innerHTML` of this element.
  10450. * @param {String} html The new HTML.
  10451. */
  10452. setHtml: function(html) {
  10453. this.dom.innerHTML = html;
  10454. },
  10455. /**
  10456. * Returns the `innerHTML` of an element.
  10457. * @return {String}
  10458. */
  10459. getHtml: function() {
  10460. return this.dom.innerHTML;
  10461. },
  10462. setText: function(text) {
  10463. this.dom.textContent = text;
  10464. },
  10465. redraw: function() {
  10466. var dom = this.dom,
  10467. domStyle = dom.style;
  10468. domStyle.display = 'none';
  10469. dom.offsetHeight;
  10470. domStyle.display = '';
  10471. },
  10472. isPainted: function() {
  10473. var dom = this.dom;
  10474. return Boolean(dom && dom.offsetParent);
  10475. },
  10476. /**
  10477. * Sets the passed attributes as attributes of this element (a style attribute can be a string, object or function).
  10478. * @param {Object} attributes The object with the attributes.
  10479. * @param {Boolean} [useSet=true] `false` to override the default `setAttribute` to use expandos.
  10480. * @return {Ext.dom.Element} this
  10481. */
  10482. set: function(attributes, useSet) {
  10483. var dom = this.dom,
  10484. attribute, value;
  10485. for (attribute in attributes) {
  10486. if (attributes.hasOwnProperty(attribute)) {
  10487. value = attributes[attribute];
  10488. if (attribute == 'style') {
  10489. this.applyStyles(value);
  10490. }
  10491. else if (attribute == 'cls') {
  10492. dom.className = value;
  10493. }
  10494. else if (useSet !== false) {
  10495. if (value === undefined) {
  10496. dom.removeAttribute(attribute);
  10497. } else {
  10498. dom.setAttribute(attribute, value);
  10499. }
  10500. }
  10501. else {
  10502. dom[attribute] = value;
  10503. }
  10504. }
  10505. }
  10506. return this;
  10507. },
  10508. /**
  10509. * Returns `true` if this element matches the passed simple selector (e.g. 'div.some-class' or 'span:first-child').
  10510. * @param {String} selector The simple selector to test.
  10511. * @return {Boolean} `true` if this element matches the selector, else `false`.
  10512. */
  10513. is: function(selector) {
  10514. return Ext.DomQuery.is(this.dom, selector);
  10515. },
  10516. /**
  10517. * Returns the value of the `value` attribute.
  10518. * @param {Boolean} asNumber `true` to parse the value as a number.
  10519. * @return {String/Number}
  10520. */
  10521. getValue: function(asNumber) {
  10522. var value = this.dom.value;
  10523. return asNumber ? parseInt(value, 10) : value;
  10524. },
  10525. /**
  10526. * Returns the value of an attribute from the element's underlying DOM node.
  10527. * @param {String} name The attribute name.
  10528. * @param {String} [namespace] The namespace in which to look for the attribute.
  10529. * @return {String} The attribute value.
  10530. */
  10531. getAttribute: function(name, namespace) {
  10532. var dom = this.dom;
  10533. return dom.getAttributeNS(namespace, name) || dom.getAttribute(namespace + ":" + name)
  10534. || dom.getAttribute(name) || dom[name];
  10535. },
  10536. setSizeState: function(state) {
  10537. var classes = ['x-sized', 'x-unsized', 'x-stretched'],
  10538. states = [true, false, null],
  10539. index = states.indexOf(state),
  10540. addedClass;
  10541. if (index !== -1) {
  10542. addedClass = classes[index];
  10543. classes.splice(index, 1);
  10544. this.addCls(addedClass);
  10545. }
  10546. this.removeCls(classes);
  10547. return this;
  10548. },
  10549. /**
  10550. * Removes this element's DOM reference. Note that event and cache removal is handled at {@link Ext#removeNode}
  10551. */
  10552. destroy: function() {
  10553. this.isDestroyed = true;
  10554. var cache = Ext.Element.cache,
  10555. dom = this.dom;
  10556. if (dom && dom.parentNode && dom.tagName != 'BODY') {
  10557. dom.parentNode.removeChild(dom);
  10558. }
  10559. delete cache[this.id];
  10560. delete this.dom;
  10561. }
  10562. }, function(Element) {
  10563. Ext.elements = Ext.cache = Element.cache;
  10564. this.addStatics({
  10565. Fly: new Ext.Class({
  10566. extend: Element,
  10567. constructor: function(dom) {
  10568. this.dom = dom;
  10569. }
  10570. }),
  10571. _flyweights: {},
  10572. /**
  10573. * Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference
  10574. * to this element - the dom node can be overwritten by other code. {@link Ext#fly} is alias for
  10575. * {@link Ext.dom.Element#fly}.
  10576. *
  10577. * Use this to make one-time references to DOM elements which are not going to be accessed again either by
  10578. * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link
  10579. * Ext#get Ext.get} will be more appropriate to take advantage of the caching provided by the {@link Ext.dom.Element}
  10580. * class.
  10581. *
  10582. * @param {String/HTMLElement} element The DOM node or `id`.
  10583. * @param {String} [named] Allows for creation of named reusable flyweights to prevent conflicts (e.g.
  10584. * internally Ext uses "_global").
  10585. * @return {Ext.dom.Element} The shared Element object (or `null` if no matching element was found).
  10586. * @static
  10587. */
  10588. fly: function(element, named) {
  10589. var fly = null,
  10590. flyweights = Element._flyweights,
  10591. cachedElement;
  10592. named = named || '_global';
  10593. element = Ext.getDom(element);
  10594. if (element) {
  10595. fly = flyweights[named] || (flyweights[named] = new Element.Fly());
  10596. fly.dom = element;
  10597. fly.isSynchronized = false;
  10598. cachedElement = Ext.cache[element.id];
  10599. if (cachedElement && cachedElement.isElement) {
  10600. cachedElement.isSynchronized = false;
  10601. }
  10602. }
  10603. return fly;
  10604. }
  10605. });
  10606. /**
  10607. * @member Ext
  10608. * @method get
  10609. * @alias Ext.dom.Element#get
  10610. */
  10611. Ext.get = function(element) {
  10612. return Element.get.call(Element, element);
  10613. };
  10614. /**
  10615. * @member Ext
  10616. * @method fly
  10617. * @alias Ext.dom.Element#fly
  10618. */
  10619. Ext.fly = function() {
  10620. return Element.fly.apply(Element, arguments);
  10621. };
  10622. Ext.ClassManager.onCreated(function() {
  10623. Element.mixin('observable', Ext.mixin.Observable);
  10624. }, null, 'Ext.mixin.Observable');
  10625. });
  10626. //@tag dom,core
  10627. //@define Ext.Element-all
  10628. //@define Ext.Element-static
  10629. //@require Ext.Element
  10630. /**
  10631. * @class Ext.dom.Element
  10632. */
  10633. Ext.dom.Element.addStatics({
  10634. numberRe: /\d+$/,
  10635. unitRe: /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
  10636. camelRe: /(-[a-z])/gi,
  10637. cssRe: /([a-z0-9-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,
  10638. opacityRe: /alpha\(opacity=(.*)\)/i,
  10639. propertyCache: {},
  10640. defaultUnit: "px",
  10641. borders: {l: 'border-left-width', r: 'border-right-width', t: 'border-top-width', b: 'border-bottom-width'},
  10642. paddings: {l: 'padding-left', r: 'padding-right', t: 'padding-top', b: 'padding-bottom'},
  10643. margins: {l: 'margin-left', r: 'margin-right', t: 'margin-top', b: 'margin-bottom'},
  10644. /**
  10645. * Test if size has a unit, otherwise appends the passed unit string, or the default for this Element.
  10646. * @param {Object} size The size to set.
  10647. * @param {String} units The units to append to a numeric size value.
  10648. * @return {String}
  10649. * @private
  10650. * @static
  10651. */
  10652. addUnits: function(size, units) {
  10653. // Size set to a value which means "auto"
  10654. if (size === "" || size == "auto" || size === undefined || size === null) {
  10655. return size || '';
  10656. }
  10657. // Otherwise, warn if it's not a valid CSS measurement
  10658. if (Ext.isNumber(size) || this.numberRe.test(size)) {
  10659. return size + (units || this.defaultUnit || 'px');
  10660. }
  10661. else if (!this.unitRe.test(size)) {
  10662. //<debug>
  10663. Ext.Logger.warn("Warning, size detected (" + size + ") not a valid property value on Element.addUnits.");
  10664. //</debug>
  10665. return size || '';
  10666. }
  10667. return size;
  10668. },
  10669. /**
  10670. * @static
  10671. * @return {Boolean}
  10672. * @private
  10673. */
  10674. isAncestor: function(p, c) {
  10675. var ret = false;
  10676. p = Ext.getDom(p);
  10677. c = Ext.getDom(c);
  10678. if (p && c) {
  10679. if (p.contains) {
  10680. return p.contains(c);
  10681. } else if (p.compareDocumentPosition) {
  10682. return !!(p.compareDocumentPosition(c) & 16);
  10683. } else {
  10684. while ((c = c.parentNode)) {
  10685. ret = c == p || ret;
  10686. }
  10687. }
  10688. }
  10689. return ret;
  10690. },
  10691. /**
  10692. * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
  10693. * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
  10694. * @static
  10695. * @param {Number/String} box The encoded margins
  10696. * @return {Object} An object with margin sizes for top, right, bottom and left containing the unit
  10697. */
  10698. parseBox: function(box) {
  10699. if (typeof box != 'string') {
  10700. box = box.toString();
  10701. }
  10702. var parts = box.split(' '),
  10703. ln = parts.length;
  10704. if (ln == 1) {
  10705. parts[1] = parts[2] = parts[3] = parts[0];
  10706. }
  10707. else if (ln == 2) {
  10708. parts[2] = parts[0];
  10709. parts[3] = parts[1];
  10710. }
  10711. else if (ln == 3) {
  10712. parts[3] = parts[1];
  10713. }
  10714. return {
  10715. top: parts[0] || 0,
  10716. right: parts[1] || 0,
  10717. bottom: parts[2] || 0,
  10718. left: parts[3] || 0
  10719. };
  10720. },
  10721. /**
  10722. * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
  10723. * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
  10724. * @static
  10725. * @param {Number/String} box The encoded margins
  10726. * @param {String} units The type of units to add
  10727. * @return {String} An string with unitized (px if units is not specified) metrics for top, right, bottom and left
  10728. */
  10729. unitizeBox: function(box, units) {
  10730. var me = this;
  10731. box = me.parseBox(box);
  10732. return me.addUnits(box.top, units) + ' ' +
  10733. me.addUnits(box.right, units) + ' ' +
  10734. me.addUnits(box.bottom, units) + ' ' +
  10735. me.addUnits(box.left, units);
  10736. },
  10737. // @private
  10738. camelReplaceFn: function(m, a) {
  10739. return a.charAt(1).toUpperCase();
  10740. },
  10741. /**
  10742. * Normalizes CSS property keys from dash delimited to camel case JavaScript Syntax.
  10743. * For example:
  10744. *
  10745. * - border-width -> borderWidth
  10746. * - padding-top -> paddingTop
  10747. *
  10748. * @static
  10749. * @param {String} prop The property to normalize
  10750. * @return {String} The normalized string
  10751. */
  10752. normalize: function(prop) {
  10753. // TODO: Mobile optimization?
  10754. // if (prop == 'float') {
  10755. // prop = Ext.supports.Float ? 'cssFloat' : 'styleFloat';
  10756. // }
  10757. return this.propertyCache[prop] || (this.propertyCache[prop] = prop.replace(this.camelRe, this.camelReplaceFn));
  10758. },
  10759. /**
  10760. * Returns the top Element that is located at the passed coordinates
  10761. * @static
  10762. * @param {Number} x The x coordinate
  10763. * @param {Number} y The y coordinate
  10764. * @return {String} The found Element
  10765. */
  10766. fromPoint: function(x, y) {
  10767. return Ext.get(document.elementFromPoint(x, y));
  10768. },
  10769. /**
  10770. * Converts a CSS string into an object with a property for each style.
  10771. *
  10772. * The sample code below would return an object with 2 properties, one
  10773. * for background-color and one for color.
  10774. *
  10775. * var css = 'background-color: red;color: blue; ';
  10776. * console.log(Ext.dom.Element.parseStyles(css));
  10777. *
  10778. * @static
  10779. * @param {String} styles A CSS string
  10780. * @return {Object} styles
  10781. */
  10782. parseStyles: function(styles) {
  10783. var out = {},
  10784. cssRe = this.cssRe,
  10785. matches;
  10786. if (styles) {
  10787. // Since we're using the g flag on the regex, we need to set the lastIndex.
  10788. // This automatically happens on some implementations, but not others, see:
  10789. // http://stackoverflow.com/questions/2645273/javascript-regular-expression-literal-persists-between-function-calls
  10790. // http://blog.stevenlevithan.com/archives/fixing-javascript-regexp
  10791. cssRe.lastIndex = 0;
  10792. while ((matches = cssRe.exec(styles))) {
  10793. out[matches[1]] = matches[2];
  10794. }
  10795. }
  10796. return out;
  10797. }
  10798. });
  10799. //@tag dom,core
  10800. //@define Ext.Element-all
  10801. //@define Ext.Element-alignment
  10802. //@require Ext.Element-static
  10803. /**
  10804. * @class Ext.dom.Element
  10805. */
  10806. //@tag dom,core
  10807. //@define Ext.Element-all
  10808. //@define Ext.Element-insertion
  10809. //@require Ext.Element-alignment
  10810. /**
  10811. * @class Ext.dom.Element
  10812. */
  10813. Ext.dom.Element.addMembers({
  10814. /**
  10815. * Appends the passed element(s) to this element.
  10816. * @param {HTMLElement/Ext.dom.Element} element a DOM Node or an existing Element.
  10817. * @return {Ext.dom.Element} This element.
  10818. */
  10819. appendChild: function(element) {
  10820. this.dom.appendChild(Ext.getDom(element));
  10821. return this;
  10822. },
  10823. removeChild: function(element) {
  10824. this.dom.removeChild(Ext.getDom(element));
  10825. return this;
  10826. },
  10827. append: function() {
  10828. this.appendChild.apply(this, arguments);
  10829. },
  10830. /**
  10831. * Appends this element to the passed element.
  10832. * @param {String/HTMLElement/Ext.dom.Element} el The new parent element.
  10833. * The id of the node, a DOM Node or an existing Element.
  10834. * @return {Ext.dom.Element} This element.
  10835. */
  10836. appendTo: function(el) {
  10837. Ext.getDom(el).appendChild(this.dom);
  10838. return this;
  10839. },
  10840. /**
  10841. * Inserts this element before the passed element in the DOM.
  10842. * @param {String/HTMLElement/Ext.dom.Element} el The element before which this element will be inserted.
  10843. * The id of the node, a DOM Node or an existing Element.
  10844. * @return {Ext.dom.Element} This element.
  10845. */
  10846. insertBefore: function(el) {
  10847. el = Ext.getDom(el);
  10848. el.parentNode.insertBefore(this.dom, el);
  10849. return this;
  10850. },
  10851. /**
  10852. * Inserts this element after the passed element in the DOM.
  10853. * @param {String/HTMLElement/Ext.dom.Element} el The element to insert after.
  10854. * The `id` of the node, a DOM Node or an existing Element.
  10855. * @return {Ext.dom.Element} This element.
  10856. */
  10857. insertAfter: function(el) {
  10858. el = Ext.getDom(el);
  10859. el.parentNode.insertBefore(this.dom, el.nextSibling);
  10860. return this;
  10861. },
  10862. /**
  10863. * Inserts an element as the first child of this element.
  10864. * @param {String/HTMLElement/Ext.dom.Element} element The `id` or element to insert.
  10865. * @return {Ext.dom.Element} this
  10866. */
  10867. insertFirst: function(element) {
  10868. var elementDom = Ext.getDom(element),
  10869. dom = this.dom,
  10870. firstChild = dom.firstChild;
  10871. if (!firstChild) {
  10872. dom.appendChild(elementDom);
  10873. }
  10874. else {
  10875. dom.insertBefore(elementDom, firstChild);
  10876. }
  10877. return this;
  10878. },
  10879. /**
  10880. * Inserts (or creates) the passed element (or DomHelper config) as a sibling of this element
  10881. * @param {String/HTMLElement/Ext.dom.Element/Object/Array} el The id, element to insert or a DomHelper config
  10882. * to create and insert *or* an array of any of those.
  10883. * @param {String} [where=before] (optional) 'before' or 'after'.
  10884. * @param {Boolean} returnDom (optional) `true` to return the raw DOM element instead of Ext.dom.Element.
  10885. * @return {Ext.dom.Element} The inserted Element. If an array is passed, the last inserted element is returned.
  10886. */
  10887. insertSibling: function(el, where, returnDom) {
  10888. var me = this, rt,
  10889. isAfter = (where || 'before').toLowerCase() == 'after',
  10890. insertEl;
  10891. if (Ext.isArray(el)) {
  10892. insertEl = me;
  10893. Ext.each(el, function(e) {
  10894. rt = Ext.fly(insertEl, '_internal').insertSibling(e, where, returnDom);
  10895. if (isAfter) {
  10896. insertEl = rt;
  10897. }
  10898. });
  10899. return rt;
  10900. }
  10901. el = el || {};
  10902. if (el.nodeType || el.dom) {
  10903. rt = me.dom.parentNode.insertBefore(Ext.getDom(el), isAfter ? me.dom.nextSibling : me.dom);
  10904. if (!returnDom) {
  10905. rt = Ext.get(rt);
  10906. }
  10907. } else {
  10908. if (isAfter && !me.dom.nextSibling) {
  10909. rt = Ext.core.DomHelper.append(me.dom.parentNode, el, !returnDom);
  10910. } else {
  10911. rt = Ext.core.DomHelper[isAfter ? 'insertAfter' : 'insertBefore'](me.dom, el, !returnDom);
  10912. }
  10913. }
  10914. return rt;
  10915. },
  10916. /**
  10917. * Replaces the passed element with this element.
  10918. * @param {String/HTMLElement/Ext.dom.Element} el The element to replace.
  10919. * The id of the node, a DOM Node or an existing Element.
  10920. * @return {Ext.dom.Element} This element.
  10921. */
  10922. replace: function(element) {
  10923. element = Ext.getDom(element);
  10924. element.parentNode.replaceChild(this.dom, element);
  10925. return this;
  10926. },
  10927. /**
  10928. * Replaces this element with the passed element.
  10929. * @param {String/HTMLElement/Ext.dom.Element/Object} el The new element (id of the node, a DOM Node
  10930. * or an existing Element) or a DomHelper config of an element to create.
  10931. * @return {Ext.dom.Element} This element.
  10932. */
  10933. replaceWith: function(el) {
  10934. var me = this;
  10935. if (el.nodeType || el.dom || typeof el == 'string') {
  10936. el = Ext.get(el);
  10937. me.dom.parentNode.insertBefore(el, me.dom);
  10938. } else {
  10939. el = Ext.core.DomHelper.insertBefore(me.dom, el);
  10940. }
  10941. delete Ext.cache[me.id];
  10942. Ext.removeNode(me.dom);
  10943. me.id = Ext.id(me.dom = el);
  10944. Ext.dom.Element.addToCache(me.isFlyweight ? new Ext.dom.Element(me.dom) : me);
  10945. return me;
  10946. },
  10947. doReplaceWith: function(element) {
  10948. var dom = this.dom;
  10949. dom.parentNode.replaceChild(Ext.getDom(element), dom);
  10950. },
  10951. /**
  10952. * Creates the passed DomHelper config and appends it to this element or optionally inserts it before the passed child element.
  10953. * @param {Object} config DomHelper element config object. If no tag is specified (e.g., `{tag:'input'}`) then a div will be
  10954. * automatically generated with the specified attributes.
  10955. * @param {HTMLElement} insertBefore (optional) a child element of this element.
  10956. * @param {Boolean} returnDom (optional) `true` to return the dom node instead of creating an Element.
  10957. * @return {Ext.dom.Element} The new child element.
  10958. */
  10959. createChild: function(config, insertBefore, returnDom) {
  10960. config = config || {tag: 'div'};
  10961. if (insertBefore) {
  10962. return Ext.core.DomHelper.insertBefore(insertBefore, config, returnDom !== true);
  10963. }
  10964. else {
  10965. return Ext.core.DomHelper[!this.dom.firstChild ? 'insertFirst' : 'append'](this.dom, config, returnDom !== true);
  10966. }
  10967. },
  10968. /**
  10969. * Creates and wraps this element with another element.
  10970. * @param {Object} [config] (optional) DomHelper element config object for the wrapper element or `null` for an empty div
  10971. * @param {Boolean} [domNode] (optional) `true` to return the raw DOM element instead of Ext.dom.Element.
  10972. * @return {HTMLElement/Ext.dom.Element} The newly created wrapper element.
  10973. */
  10974. wrap: function(config, domNode) {
  10975. var dom = this.dom,
  10976. wrapper = this.self.create(config, domNode),
  10977. wrapperDom = (domNode) ? wrapper : wrapper.dom,
  10978. parentNode = dom.parentNode;
  10979. if (parentNode) {
  10980. parentNode.insertBefore(wrapperDom, dom);
  10981. }
  10982. wrapperDom.appendChild(dom);
  10983. return wrapper;
  10984. },
  10985. wrapAllChildren: function(config) {
  10986. var dom = this.dom,
  10987. children = dom.childNodes,
  10988. wrapper = this.self.create(config),
  10989. wrapperDom = wrapper.dom;
  10990. while (children.length > 0) {
  10991. wrapperDom.appendChild(dom.firstChild);
  10992. }
  10993. dom.appendChild(wrapperDom);
  10994. return wrapper;
  10995. },
  10996. unwrapAllChildren: function() {
  10997. var dom = this.dom,
  10998. children = dom.childNodes,
  10999. parentNode = dom.parentNode;
  11000. if (parentNode) {
  11001. while (children.length > 0) {
  11002. parentNode.insertBefore(dom, dom.firstChild);
  11003. }
  11004. this.destroy();
  11005. }
  11006. },
  11007. unwrap: function() {
  11008. var dom = this.dom,
  11009. parentNode = dom.parentNode,
  11010. grandparentNode;
  11011. if (parentNode) {
  11012. grandparentNode = parentNode.parentNode;
  11013. grandparentNode.insertBefore(dom, parentNode);
  11014. grandparentNode.removeChild(parentNode);
  11015. }
  11016. else {
  11017. grandparentNode = document.createDocumentFragment();
  11018. grandparentNode.appendChild(dom);
  11019. }
  11020. return this;
  11021. },
  11022. detach: function() {
  11023. var dom = this.dom;
  11024. if (dom && dom.parentNode && dom.tagName !== 'BODY') {
  11025. dom.parentNode.removeChild(dom);
  11026. }
  11027. return this;
  11028. },
  11029. /**
  11030. * Inserts an HTML fragment into this element.
  11031. * @param {String} where Where to insert the HTML in relation to this element - 'beforeBegin', 'afterBegin', 'beforeEnd', 'afterEnd'.
  11032. * See {@link Ext.DomHelper#insertHtml} for details.
  11033. * @param {String} html The HTML fragment
  11034. * @param {Boolean} [returnEl=false] (optional) `true` to return an Ext.dom.Element.
  11035. * @return {HTMLElement/Ext.dom.Element} The inserted node (or nearest related if more than 1 inserted).
  11036. */
  11037. insertHtml: function(where, html, returnEl) {
  11038. var el = Ext.core.DomHelper.insertHtml(where, this.dom, html);
  11039. return returnEl ? Ext.get(el) : el;
  11040. }
  11041. });
  11042. //@tag dom,core
  11043. //@define Ext.Element-all
  11044. //@define Ext.Element-position
  11045. //@require Ext.Element-insertion
  11046. /**
  11047. * @class Ext.dom.Element
  11048. */
  11049. Ext.dom.Element.override({
  11050. /**
  11051. * Gets the current X position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (`display:none` or elements not appended return `false`).
  11052. * @return {Number} The X position of the element
  11053. */
  11054. getX: function(el) {
  11055. return this.getXY(el)[0];
  11056. },
  11057. /**
  11058. * Gets the current Y position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (`display:none` or elements not appended return `false`).
  11059. * @return {Number} The Y position of the element
  11060. */
  11061. getY: function(el) {
  11062. return this.getXY(el)[1];
  11063. },
  11064. /**
  11065. * Gets the current position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (`display:none` or elements not appended return `false`).
  11066. * @return {Array} The XY position of the element
  11067. */
  11068. getXY: function() {
  11069. var rect = this.dom.getBoundingClientRect(),
  11070. round = Math.round;
  11071. return [round(rect.left + window.pageXOffset), round(rect.top + window.pageYOffset)];
  11072. },
  11073. /**
  11074. * Returns the offsets of this element from the passed element. Both element must be part of the DOM tree
  11075. * and not have `display:none` to have page coordinates.
  11076. * @param {Mixed} element The element to get the offsets from.
  11077. * @return {Array} The XY page offsets (e.g. [100, -200])
  11078. */
  11079. getOffsetsTo: function(el) {
  11080. var o = this.getXY(),
  11081. e = Ext.fly(el, '_internal').getXY();
  11082. return [o[0] - e[0], o[1] - e[1]];
  11083. },
  11084. /**
  11085. * Sets the X position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (`display:none` or elements not appended return `false`).
  11086. * @param {Number} The X position of the element
  11087. * @param {Boolean/Object} animate (optional) `true` for the default animation, or a standard Element animation config object.
  11088. * @return {Ext.dom.Element} this
  11089. */
  11090. setX: function(x) {
  11091. return this.setXY([x, this.getY()]);
  11092. },
  11093. /**
  11094. * Sets the Y position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (`display:none` or elements not appended return `false`).
  11095. * @param {Number} The Y position of the element.
  11096. * @param {Boolean/Object} animate (optional) `true` for the default animation, or a standard Element animation config object.
  11097. * @return {Ext.dom.Element} this
  11098. */
  11099. setY: function(y) {
  11100. return this.setXY([this.getX(), y]);
  11101. },
  11102. /**
  11103. * Sets the position of the element in page coordinates, regardless of how the element is positioned.
  11104. * The element must be part of the DOM tree to have page coordinates (`display:none` or elements not appended return `false`).
  11105. * @param {Array} pos Contains X & Y [x, y] values for new position (coordinates are page-based).
  11106. * @param {Boolean/Object} animate (optional) `true` for the default animation, or a standard Element animation config object.
  11107. * @return {Ext.dom.Element} this
  11108. */
  11109. setXY: function(pos) {
  11110. var me = this;
  11111. if (arguments.length > 1) {
  11112. pos = [pos, arguments[1]];
  11113. }
  11114. // me.position();
  11115. var pts = me.translatePoints(pos),
  11116. style = me.dom.style;
  11117. for (pos in pts) {
  11118. if (!pts.hasOwnProperty(pos)) {
  11119. continue;
  11120. }
  11121. if (!isNaN(pts[pos])) style[pos] = pts[pos] + "px";
  11122. }
  11123. return me;
  11124. },
  11125. /**
  11126. * Gets the left X coordinate.
  11127. * @return {Number}
  11128. */
  11129. getLeft: function() {
  11130. return parseInt(this.getStyle('left'), 10) || 0;
  11131. },
  11132. /**
  11133. * Gets the right X coordinate of the element (element X position + element width).
  11134. * @return {Number}
  11135. */
  11136. getRight: function() {
  11137. return parseInt(this.getStyle('right'), 10) || 0;
  11138. },
  11139. /**
  11140. * Gets the top Y coordinate.
  11141. * @return {Number}
  11142. */
  11143. getTop: function() {
  11144. return parseInt(this.getStyle('top'), 10) || 0;
  11145. },
  11146. /**
  11147. * Gets the bottom Y coordinate of the element (element Y position + element height).
  11148. * @return {Number}
  11149. */
  11150. getBottom: function() {
  11151. return parseInt(this.getStyle('bottom'), 10) || 0;
  11152. },
  11153. /**
  11154. * Translates the passed page coordinates into left/top CSS values for this element.
  11155. * @param {Number/Array} x The page `x` or an array containing [x, y].
  11156. * @param {Number} y (optional) The page `y`, required if `x` is not an array.
  11157. * @return {Object} An object with `left` and `top` properties. e.g. `{left: (value), top: (value)}`.
  11158. */
  11159. translatePoints: function(x, y) {
  11160. y = isNaN(x[1]) ? y : x[1];
  11161. x = isNaN(x[0]) ? x : x[0];
  11162. var me = this,
  11163. relative = me.isStyle('position', 'relative'),
  11164. o = me.getXY(),
  11165. l = parseInt(me.getStyle('left'), 10),
  11166. t = parseInt(me.getStyle('top'), 10);
  11167. l = !isNaN(l) ? l : (relative ? 0 : me.dom.offsetLeft);
  11168. t = !isNaN(t) ? t : (relative ? 0 : me.dom.offsetTop);
  11169. return {left: (x - o[0] + l), top: (y - o[1] + t)};
  11170. },
  11171. /**
  11172. * Sets the element's box. Use {@link #getBox} on another element to get a box object.
  11173. * @param {Object} box The box to fill, for example:
  11174. *
  11175. * {
  11176. * left: ...,
  11177. * top: ...,
  11178. * width: ...,
  11179. * height: ...
  11180. * }
  11181. *
  11182. * @return {Ext.dom.Element} this
  11183. */
  11184. setBox: function(box) {
  11185. var me = this,
  11186. width = box.width,
  11187. height = box.height,
  11188. top = box.top,
  11189. left = box.left;
  11190. if (left !== undefined) {
  11191. me.setLeft(left);
  11192. }
  11193. if (top !== undefined) {
  11194. me.setTop(top);
  11195. }
  11196. if (width !== undefined) {
  11197. me.setWidth(width);
  11198. }
  11199. if (height !== undefined) {
  11200. me.setHeight(height);
  11201. }
  11202. return this;
  11203. },
  11204. /**
  11205. * Return an object defining the area of this Element which can be passed to {@link #setBox} to
  11206. * set another Element's size/location to match this element.
  11207. *
  11208. * The returned object may also be addressed as an Array where index 0 contains the X position
  11209. * and index 1 contains the Y position. So the result may also be used for {@link #setXY}.
  11210. *
  11211. * @param {Boolean} contentBox (optional) If `true` a box for the content of the element is returned.
  11212. * @param {Boolean} local (optional) If `true` the element's left and top are returned instead of page x/y.
  11213. * @return {Object} An object in the format
  11214. * @return {Number} return.x The element's X position.
  11215. * @return {Number} return.y The element's Y position.
  11216. * @return {Number} return.width The element's width.
  11217. * @return {Number} return.height The element's height.
  11218. * @return {Number} return.bottom The element's lower bound.
  11219. * @return {Number} return.right The element's rightmost bound.
  11220. */
  11221. getBox: function(contentBox, local) {
  11222. var me = this,
  11223. dom = me.dom,
  11224. width = dom.offsetWidth,
  11225. height = dom.offsetHeight,
  11226. xy, box, l, r, t, b;
  11227. if (!local) {
  11228. xy = me.getXY();
  11229. }
  11230. else if (contentBox) {
  11231. xy = [0, 0];
  11232. }
  11233. else {
  11234. xy = [parseInt(me.getStyle("left"), 10) || 0, parseInt(me.getStyle("top"), 10) || 0];
  11235. }
  11236. if (!contentBox) {
  11237. box = {
  11238. x: xy[0],
  11239. y: xy[1],
  11240. 0: xy[0],
  11241. 1: xy[1],
  11242. width: width,
  11243. height: height
  11244. };
  11245. }
  11246. else {
  11247. l = me.getBorderWidth.call(me, "l") + me.getPadding.call(me, "l");
  11248. r = me.getBorderWidth.call(me, "r") + me.getPadding.call(me, "r");
  11249. t = me.getBorderWidth.call(me, "t") + me.getPadding.call(me, "t");
  11250. b = me.getBorderWidth.call(me, "b") + me.getPadding.call(me, "b");
  11251. box = {
  11252. x: xy[0] + l,
  11253. y: xy[1] + t,
  11254. 0: xy[0] + l,
  11255. 1: xy[1] + t,
  11256. width: width - (l + r),
  11257. height: height - (t + b)
  11258. };
  11259. }
  11260. box.left = box.x;
  11261. box.top = box.y;
  11262. box.right = box.x + box.width;
  11263. box.bottom = box.y + box.height;
  11264. return box;
  11265. },
  11266. /**
  11267. * Return an object defining the area of this Element which can be passed to {@link #setBox} to
  11268. * set another Element's size/location to match this element.
  11269. * @param {Boolean} asRegion (optional) If `true` an {@link Ext.util.Region} will be returned.
  11270. * @return {Object} box An object in the format:
  11271. *
  11272. * {
  11273. * x: <Element's X position>,
  11274. * y: <Element's Y position>,
  11275. * width: <Element's width>,
  11276. * height: <Element's height>,
  11277. * bottom: <Element's lower bound>,
  11278. * right: <Element's rightmost bound>
  11279. * }
  11280. *
  11281. * The returned object may also be addressed as an Array where index 0 contains the X position
  11282. * and index 1 contains the Y position. So the result may also be used for {@link #setXY}.
  11283. */
  11284. getPageBox: function(getRegion) {
  11285. var me = this,
  11286. el = me.dom,
  11287. w = el.offsetWidth,
  11288. h = el.offsetHeight,
  11289. xy = me.getXY(),
  11290. t = xy[1],
  11291. r = xy[0] + w,
  11292. b = xy[1] + h,
  11293. l = xy[0];
  11294. if (!el) {
  11295. return new Ext.util.Region();
  11296. }
  11297. if (getRegion) {
  11298. return new Ext.util.Region(t, r, b, l);
  11299. }
  11300. else {
  11301. return {
  11302. left: l,
  11303. top: t,
  11304. width: w,
  11305. height: h,
  11306. right: r,
  11307. bottom: b
  11308. };
  11309. }
  11310. }
  11311. });
  11312. //@tag dom,core
  11313. //@define Ext.Element-all
  11314. //@define Ext.Element-style
  11315. //@require Ext.Element-position
  11316. /**
  11317. * @class Ext.dom.Element
  11318. */
  11319. Ext.dom.Element.addMembers({
  11320. WIDTH: 'width',
  11321. HEIGHT: 'height',
  11322. MIN_WIDTH: 'min-width',
  11323. MIN_HEIGHT: 'min-height',
  11324. MAX_WIDTH: 'max-width',
  11325. MAX_HEIGHT: 'max-height',
  11326. TOP: 'top',
  11327. RIGHT: 'right',
  11328. BOTTOM: 'bottom',
  11329. LEFT: 'left',
  11330. /**
  11331. * @property VISIBILITY
  11332. * Visibility mode constant for use with {@link #setVisibilityMode}. Use `visibility` to hide element.
  11333. */
  11334. VISIBILITY: 1,
  11335. /**
  11336. * @property DISPLAY
  11337. * Visibility mode constant for use with {@link #setVisibilityMode}. Use `display` to hide element.
  11338. */
  11339. DISPLAY: 2,
  11340. /**
  11341. * @property OFFSETS
  11342. * Visibility mode constant for use with {@link #setVisibilityMode}. Use offsets to hide element.
  11343. */
  11344. OFFSETS: 3,
  11345. SEPARATOR: '-',
  11346. trimRe: /^\s+|\s+$/g,
  11347. wordsRe: /\w/g,
  11348. spacesRe: /\s+/,
  11349. styleSplitRe: /\s*(?::|;)\s*/,
  11350. transparentRe: /^(?:transparent|(?:rgba[(](?:\s*\d+\s*[,]){3}\s*0\s*[)]))$/i,
  11351. classNameSplitRegex: /[\s]+/,
  11352. borders: {
  11353. t: 'border-top-width',
  11354. r: 'border-right-width',
  11355. b: 'border-bottom-width',
  11356. l: 'border-left-width'
  11357. },
  11358. paddings: {
  11359. t: 'padding-top',
  11360. r: 'padding-right',
  11361. b: 'padding-bottom',
  11362. l: 'padding-left'
  11363. },
  11364. margins: {
  11365. t: 'margin-top',
  11366. r: 'margin-right',
  11367. b: 'margin-bottom',
  11368. l: 'margin-left'
  11369. },
  11370. /**
  11371. * @property {String} defaultUnit
  11372. * The default unit to append to CSS values where a unit isn't provided.
  11373. */
  11374. defaultUnit: "px",
  11375. isSynchronized: false,
  11376. /**
  11377. * @private
  11378. */
  11379. synchronize: function() {
  11380. var dom = this.dom,
  11381. hasClassMap = {},
  11382. className = dom.className,
  11383. classList, i, ln, name;
  11384. if (className.length > 0) {
  11385. classList = dom.className.split(this.classNameSplitRegex);
  11386. for (i = 0, ln = classList.length; i < ln; i++) {
  11387. name = classList[i];
  11388. hasClassMap[name] = true;
  11389. }
  11390. }
  11391. else {
  11392. classList = [];
  11393. }
  11394. this.classList = classList;
  11395. this.hasClassMap = hasClassMap;
  11396. this.isSynchronized = true;
  11397. return this;
  11398. },
  11399. /**
  11400. * Adds the given CSS class(es) to this Element.
  11401. * @param {String} names The CSS class(es) to add to this element.
  11402. * @param {String} [prefix] (optional) Prefix to prepend to each class.
  11403. * @param {String} [suffix] (optional) Suffix to append to each class.
  11404. */
  11405. addCls: function(names, prefix, suffix) {
  11406. if (!names) {
  11407. return this;
  11408. }
  11409. if (!this.isSynchronized) {
  11410. this.synchronize();
  11411. }
  11412. var dom = this.dom,
  11413. map = this.hasClassMap,
  11414. classList = this.classList,
  11415. SEPARATOR = this.SEPARATOR,
  11416. i, ln, name;
  11417. prefix = prefix ? prefix + SEPARATOR : '';
  11418. suffix = suffix ? SEPARATOR + suffix : '';
  11419. if (typeof names == 'string') {
  11420. names = names.split(this.spacesRe);
  11421. }
  11422. for (i = 0, ln = names.length; i < ln; i++) {
  11423. name = prefix + names[i] + suffix;
  11424. if (!map[name]) {
  11425. map[name] = true;
  11426. classList.push(name);
  11427. }
  11428. }
  11429. dom.className = classList.join(' ');
  11430. return this;
  11431. },
  11432. /**
  11433. * Removes the given CSS class(es) from this Element.
  11434. * @param {String} names The CSS class(es) to remove from this element.
  11435. * @param {String} [prefix=''] (optional) Prefix to prepend to each class to be removed.
  11436. * @param {String} [suffix=''] (optional) Suffix to append to each class to be removed.
  11437. */
  11438. removeCls: function(names, prefix, suffix) {
  11439. if (!names) {
  11440. return this;
  11441. }
  11442. if (!this.isSynchronized) {
  11443. this.synchronize();
  11444. }
  11445. if (!suffix) {
  11446. suffix = '';
  11447. }
  11448. var dom = this.dom,
  11449. map = this.hasClassMap,
  11450. classList = this.classList,
  11451. SEPARATOR = this.SEPARATOR,
  11452. i, ln, name;
  11453. prefix = prefix ? prefix + SEPARATOR : '';
  11454. suffix = suffix ? SEPARATOR + suffix : '';
  11455. if (typeof names == 'string') {
  11456. names = names.split(this.spacesRe);
  11457. }
  11458. for (i = 0, ln = names.length; i < ln; i++) {
  11459. name = prefix + names[i] + suffix;
  11460. if (map[name]) {
  11461. delete map[name];
  11462. Ext.Array.remove(classList, name);
  11463. }
  11464. }
  11465. dom.className = classList.join(' ');
  11466. return this;
  11467. },
  11468. /**
  11469. * Replaces a CSS class on the element with another. If the old name does not exist, the new name will simply be added.
  11470. * @param {String} oldClassName The CSS class to replace.
  11471. * @param {String} newClassName The replacement CSS class.
  11472. * @return {Ext.dom.Element} this
  11473. */
  11474. replaceCls: function(oldName, newName, prefix, suffix) {
  11475. return this.removeCls(oldName, prefix, suffix).addCls(newName, prefix, suffix);
  11476. },
  11477. /**
  11478. * Checks if the specified CSS class exists on this element's DOM node.
  11479. * @param {String} className The CSS class to check for.
  11480. * @return {Boolean} `true` if the class exists, else `false`.
  11481. */
  11482. hasCls: function(name) {
  11483. if (!this.isSynchronized) {
  11484. this.synchronize();
  11485. }
  11486. return this.hasClassMap.hasOwnProperty(name);
  11487. },
  11488. /**
  11489. * Toggles the specified CSS class on this element (removes it if it already exists, otherwise adds it).
  11490. * @param {String} className The CSS class to toggle.
  11491. * @return {Ext.dom.Element} this
  11492. */
  11493. toggleCls: function(className, force){
  11494. if (typeof force !== 'boolean') {
  11495. force = !this.hasCls(className);
  11496. }
  11497. return (force) ? this.addCls(className) : this.removeCls(className);
  11498. },
  11499. /**
  11500. * @private
  11501. * @param firstClass
  11502. * @param secondClass
  11503. * @param flag
  11504. * @param prefix
  11505. * @return {Mixed}
  11506. */
  11507. swapCls: function(firstClass, secondClass, flag, prefix) {
  11508. if (flag === undefined) {
  11509. flag = true;
  11510. }
  11511. var addedClass = flag ? firstClass : secondClass,
  11512. removedClass = flag ? secondClass : firstClass;
  11513. if (removedClass) {
  11514. this.removeCls(prefix ? prefix + '-' + removedClass : removedClass);
  11515. }
  11516. if (addedClass) {
  11517. this.addCls(prefix ? prefix + '-' + addedClass : addedClass);
  11518. }
  11519. return this;
  11520. },
  11521. /**
  11522. * Set the width of this Element.
  11523. * @param {Number/String} width The new width.
  11524. * @return {Ext.dom.Element} this
  11525. */
  11526. setWidth: function(width) {
  11527. return this.setLengthValue(this.WIDTH, width);
  11528. },
  11529. /**
  11530. * Set the height of this Element.
  11531. * @param {Number/String} height The new height.
  11532. * @return {Ext.dom.Element} this
  11533. */
  11534. setHeight: function(height) {
  11535. return this.setLengthValue(this.HEIGHT, height);
  11536. },
  11537. /**
  11538. * Set the size of this Element.
  11539. *
  11540. * @param {Number/String} width The new width. This may be one of:
  11541. *
  11542. * - A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).
  11543. * - A String used to set the CSS width style. Animation may **not** be used.
  11544. * - A size object in the format `{width: widthValue, height: heightValue}`.
  11545. *
  11546. * @param {Number/String} height The new height. This may be one of:
  11547. *
  11548. * - A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels).
  11549. * - A String used to set the CSS height style. Animation may **not** be used.
  11550. * @return {Ext.dom.Element} this
  11551. */
  11552. setSize: function(width, height) {
  11553. if (Ext.isObject(width)) {
  11554. // in case of object from getSize()
  11555. height = width.height;
  11556. width = width.width;
  11557. }
  11558. this.setWidth(width);
  11559. this.setHeight(height);
  11560. return this;
  11561. },
  11562. /**
  11563. * Set the minimum width of this Element.
  11564. * @param {Number/String} width The new minimum width.
  11565. * @return {Ext.dom.Element} this
  11566. */
  11567. setMinWidth: function(width) {
  11568. return this.setLengthValue(this.MIN_WIDTH, width);
  11569. },
  11570. /**
  11571. * Set the minimum height of this Element.
  11572. * @param {Number/String} height The new minimum height.
  11573. * @return {Ext.dom.Element} this
  11574. */
  11575. setMinHeight: function(height) {
  11576. return this.setLengthValue(this.MIN_HEIGHT, height);
  11577. },
  11578. /**
  11579. * Set the maximum width of this Element.
  11580. * @param {Number/String} width The new maximum width.
  11581. * @return {Ext.dom.Element} this
  11582. */
  11583. setMaxWidth: function(width) {
  11584. return this.setLengthValue(this.MAX_WIDTH, width);
  11585. },
  11586. /**
  11587. * Set the maximum height of this Element.
  11588. * @param {Number/String} height The new maximum height.
  11589. * @return {Ext.dom.Element} this
  11590. */
  11591. setMaxHeight: function(height) {
  11592. return this.setLengthValue(this.MAX_HEIGHT, height);
  11593. },
  11594. /**
  11595. * Sets the element's top position directly using CSS style (instead of {@link #setY}).
  11596. * @param {String} top The top CSS property value.
  11597. * @return {Ext.dom.Element} this
  11598. */
  11599. setTop: function(top) {
  11600. return this.setLengthValue(this.TOP, top);
  11601. },
  11602. /**
  11603. * Sets the element's CSS right style.
  11604. * @param {String} right The right CSS property value.
  11605. * @return {Ext.dom.Element} this
  11606. */
  11607. setRight: function(right) {
  11608. return this.setLengthValue(this.RIGHT, right);
  11609. },
  11610. /**
  11611. * Sets the element's CSS bottom style.
  11612. * @param {String} bottom The bottom CSS property value.
  11613. * @return {Ext.dom.Element} this
  11614. */
  11615. setBottom: function(bottom) {
  11616. return this.setLengthValue(this.BOTTOM, bottom);
  11617. },
  11618. /**
  11619. * Sets the element's left position directly using CSS style (instead of {@link #setX}).
  11620. * @param {String} left The left CSS property value.
  11621. * @return {Ext.dom.Element} this
  11622. */
  11623. setLeft: function(left) {
  11624. return this.setLengthValue(this.LEFT, left);
  11625. },
  11626. setMargin: function(margin) {
  11627. var domStyle = this.dom.style;
  11628. if (margin || margin === 0) {
  11629. margin = this.self.unitizeBox((margin === true) ? 5 : margin);
  11630. domStyle.setProperty('margin', margin, 'important');
  11631. }
  11632. else {
  11633. domStyle.removeProperty('margin-top');
  11634. domStyle.removeProperty('margin-right');
  11635. domStyle.removeProperty('margin-bottom');
  11636. domStyle.removeProperty('margin-left');
  11637. }
  11638. },
  11639. setPadding: function(padding) {
  11640. var domStyle = this.dom.style;
  11641. if (padding || padding === 0) {
  11642. padding = this.self.unitizeBox((padding === true) ? 5 : padding);
  11643. domStyle.setProperty('padding', padding, 'important');
  11644. }
  11645. else {
  11646. domStyle.removeProperty('padding-top');
  11647. domStyle.removeProperty('padding-right');
  11648. domStyle.removeProperty('padding-bottom');
  11649. domStyle.removeProperty('padding-left');
  11650. }
  11651. },
  11652. setBorder: function(border) {
  11653. var domStyle = this.dom.style;
  11654. if (border || border === 0) {
  11655. border = this.self.unitizeBox((border === true) ? 1 : border);
  11656. domStyle.setProperty('border-width', border, 'important');
  11657. }
  11658. else {
  11659. domStyle.removeProperty('border-top-width');
  11660. domStyle.removeProperty('border-right-width');
  11661. domStyle.removeProperty('border-bottom-width');
  11662. domStyle.removeProperty('border-left-width');
  11663. }
  11664. },
  11665. setLengthValue: function(name, value) {
  11666. var domStyle = this.dom.style;
  11667. if (value === null) {
  11668. domStyle.removeProperty(name);
  11669. return this;
  11670. }
  11671. if (typeof value == 'number') {
  11672. value = value + 'px';
  11673. }
  11674. domStyle.setProperty(name, value, 'important');
  11675. return this;
  11676. },
  11677. /**
  11678. * Sets the visibility of the element (see details). If the `visibilityMode` is set to `Element.DISPLAY`, it will use
  11679. * the display property to hide the element, otherwise it uses visibility. The default is to hide and show using the `visibility` property.
  11680. * @param {Boolean} visible Whether the element is visible.
  11681. * @return {Ext.Element} this
  11682. */
  11683. setVisible: function(visible) {
  11684. var mode = this.getVisibilityMode(),
  11685. method = visible ? 'removeCls' : 'addCls';
  11686. switch (mode) {
  11687. case this.VISIBILITY:
  11688. this.removeCls(['x-hidden-display', 'x-hidden-offsets']);
  11689. this[method]('x-hidden-visibility');
  11690. break;
  11691. case this.DISPLAY:
  11692. this.removeCls(['x-hidden-visibility', 'x-hidden-offsets']);
  11693. this[method]('x-hidden-display');
  11694. break;
  11695. case this.OFFSETS:
  11696. this.removeCls(['x-hidden-visibility', 'x-hidden-display']);
  11697. this[method]('x-hidden-offsets');
  11698. break;
  11699. }
  11700. return this;
  11701. },
  11702. getVisibilityMode: function() {
  11703. var dom = this.dom,
  11704. mode = Ext.dom.Element.data(dom, 'visibilityMode');
  11705. if (mode === undefined) {
  11706. Ext.dom.Element.data(dom, 'visibilityMode', mode = this.DISPLAY);
  11707. }
  11708. return mode;
  11709. },
  11710. /**
  11711. * Use this to change the visibility mode between {@link #VISIBILITY}, {@link #DISPLAY} or {@link #OFFSETS}.
  11712. */
  11713. setVisibilityMode: function(mode) {
  11714. this.self.data(this.dom, 'visibilityMode', mode);
  11715. return this;
  11716. },
  11717. /**
  11718. * Shows this element.
  11719. * Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
  11720. */
  11721. show: function() {
  11722. var dom = this.dom;
  11723. if (dom) {
  11724. dom.style.removeProperty('display');
  11725. }
  11726. },
  11727. /**
  11728. * Hides this element.
  11729. * Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
  11730. */
  11731. hide: function() {
  11732. this.dom.style.setProperty('display', 'none', 'important');
  11733. },
  11734. setVisibility: function(isVisible) {
  11735. var domStyle = this.dom.style;
  11736. if (isVisible) {
  11737. domStyle.removeProperty('visibility');
  11738. }
  11739. else {
  11740. domStyle.setProperty('visibility', 'hidden', 'important');
  11741. }
  11742. },
  11743. /**
  11744. * This shared object is keyed by style name (e.g., 'margin-left' or 'marginLeft'). The
  11745. * values are objects with the following properties:
  11746. *
  11747. * * `name` (String) : The actual name to be presented to the DOM. This is typically the value
  11748. * returned by {@link #normalize}.
  11749. * * `get` (Function) : A hook function that will perform the get on this style. These
  11750. * functions receive "(dom, el)" arguments. The `dom` parameter is the DOM Element
  11751. * from which to get the style. The `el` argument (may be `null`) is the Ext.Element.
  11752. * * `set` (Function) : A hook function that will perform the set on this style. These
  11753. * functions receive "(dom, value, el)" arguments. The `dom` parameter is the DOM Element
  11754. * from which to get this style. The `value` parameter is the new value for the style. The
  11755. * `el` argument (may be `null`) is the Ext.Element.
  11756. *
  11757. * The `this` pointer is the object that contains `get` or `set`, which means that
  11758. * `this.name` can be accessed if needed. The hook functions are both optional.
  11759. * @private
  11760. */
  11761. styleHooks: {},
  11762. // @private
  11763. addStyles: function(sides, styles) {
  11764. var totalSize = 0,
  11765. sidesArr = sides.match(this.wordsRe),
  11766. i = 0,
  11767. len = sidesArr.length,
  11768. side, size;
  11769. for (; i < len; i++) {
  11770. side = sidesArr[i];
  11771. size = side && parseInt(this.getStyle(styles[side]), 10);
  11772. if (size) {
  11773. totalSize += Math.abs(size);
  11774. }
  11775. }
  11776. return totalSize;
  11777. },
  11778. /**
  11779. * Checks if the current value of a style is equal to a given value.
  11780. * @param {String} style property whose value is returned.
  11781. * @param {String} value to check against.
  11782. * @return {Boolean} `true` for when the current value equals the given value.
  11783. */
  11784. isStyle: function(style, val) {
  11785. return this.getStyle(style) == val;
  11786. },
  11787. getStyleValue: function(name) {
  11788. return this.dom.style.getPropertyValue(name);
  11789. },
  11790. /**
  11791. * Normalizes `currentStyle` and `computedStyle`.
  11792. * @param {String} prop The style property whose value is returned.
  11793. * @return {String} The current value of the style property for this element.
  11794. */
  11795. getStyle: function(prop) {
  11796. var me = this,
  11797. dom = me.dom,
  11798. hook = me.styleHooks[prop],
  11799. cs, result;
  11800. if (dom == document) {
  11801. return null;
  11802. }
  11803. if (!hook) {
  11804. me.styleHooks[prop] = hook = { name: Ext.dom.Element.normalize(prop) };
  11805. }
  11806. if (hook.get) {
  11807. return hook.get(dom, me);
  11808. }
  11809. cs = window.getComputedStyle(dom, '');
  11810. // why the dom.style lookup? It is not true that "style == computedStyle" as
  11811. // well as the fact that 0/false are valid answers...
  11812. result = (cs && cs[hook.name]); // || dom.style[hook.name];
  11813. // WebKit returns rgb values for transparent, how does this work n IE9+
  11814. // if (!supportsTransparentColor && result == 'rgba(0, 0, 0, 0)') {
  11815. // result = 'transparent';
  11816. // }
  11817. return result;
  11818. },
  11819. /**
  11820. * Wrapper for setting style properties, also takes single object parameter of multiple styles.
  11821. * @param {String/Object} property The style property to be set, or an object of multiple styles.
  11822. * @param {String} [value] The value to apply to the given property, or `null` if an object was passed.
  11823. * @return {Ext.dom.Element} this
  11824. */
  11825. setStyle: function(prop, value) {
  11826. var me = this,
  11827. dom = me.dom,
  11828. hooks = me.styleHooks,
  11829. style = dom.style,
  11830. valueFrom = Ext.valueFrom,
  11831. name, hook;
  11832. // we don't promote the 2-arg form to object-form to avoid the overhead...
  11833. if (typeof prop == 'string') {
  11834. hook = hooks[prop];
  11835. if (!hook) {
  11836. hooks[prop] = hook = { name: Ext.dom.Element.normalize(prop) };
  11837. }
  11838. value = valueFrom(value, '');
  11839. if (hook.set) {
  11840. hook.set(dom, value, me);
  11841. } else {
  11842. style[hook.name] = value;
  11843. }
  11844. }
  11845. else {
  11846. for (name in prop) {
  11847. if (prop.hasOwnProperty(name)) {
  11848. hook = hooks[name];
  11849. if (!hook) {
  11850. hooks[name] = hook = { name: Ext.dom.Element.normalize(name) };
  11851. }
  11852. value = valueFrom(prop[name], '');
  11853. if (hook.set) {
  11854. hook.set(dom, value, me);
  11855. }
  11856. else {
  11857. style[hook.name] = value;
  11858. }
  11859. }
  11860. }
  11861. }
  11862. return me;
  11863. },
  11864. /**
  11865. * Returns the offset height of the element.
  11866. * @param {Boolean} [contentHeight] `true` to get the height minus borders and padding.
  11867. * @return {Number} The element's height.
  11868. */
  11869. getHeight: function(contentHeight) {
  11870. var dom = this.dom,
  11871. height = contentHeight ? (dom.clientHeight - this.getPadding("tb")) : dom.offsetHeight;
  11872. return height > 0 ? height : 0;
  11873. },
  11874. /**
  11875. * Returns the offset width of the element.
  11876. * @param {Boolean} [contentWidth] `true` to get the width minus borders and padding.
  11877. * @return {Number} The element's width.
  11878. */
  11879. getWidth: function(contentWidth) {
  11880. var dom = this.dom,
  11881. width = contentWidth ? (dom.clientWidth - this.getPadding("lr")) : dom.offsetWidth;
  11882. return width > 0 ? width : 0;
  11883. },
  11884. /**
  11885. * Gets the width of the border(s) for the specified side(s)
  11886. * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
  11887. * passing `'lr'` would get the border **l**eft width + the border **r**ight width.
  11888. * @return {Number} The width of the sides passed added together
  11889. */
  11890. getBorderWidth: function(side) {
  11891. return this.addStyles(side, this.borders);
  11892. },
  11893. /**
  11894. * Gets the width of the padding(s) for the specified side(s).
  11895. * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
  11896. * passing `'lr'` would get the padding **l**eft + the padding **r**ight.
  11897. * @return {Number} The padding of the sides passed added together.
  11898. */
  11899. getPadding: function(side) {
  11900. return this.addStyles(side, this.paddings);
  11901. },
  11902. /**
  11903. * More flexible version of {@link #setStyle} for setting style properties.
  11904. * @param {String/Object/Function} styles A style specification string, e.g. "width:100px", or object in the form `{width:"100px"}`, or
  11905. * a function which returns such a specification.
  11906. * @return {Ext.dom.Element} this
  11907. */
  11908. applyStyles: function(styles) {
  11909. if (styles) {
  11910. var dom = this.dom,
  11911. styleType, i, len;
  11912. if (typeof styles == 'function') {
  11913. styles = styles.call();
  11914. }
  11915. styleType = typeof styles;
  11916. if (styleType == 'string') {
  11917. styles = Ext.util.Format.trim(styles).split(this.styleSplitRe);
  11918. for (i = 0, len = styles.length; i < len;) {
  11919. dom.style[Ext.dom.Element.normalize(styles[i++])] = styles[i++];
  11920. }
  11921. }
  11922. else if (styleType == 'object') {
  11923. this.setStyle(styles);
  11924. }
  11925. }
  11926. },
  11927. /**
  11928. * Returns the size of the element.
  11929. * @param {Boolean} [contentSize] `true` to get the width/size minus borders and padding.
  11930. * @return {Object} An object containing the element's size:
  11931. * @return {Number} return.width
  11932. * @return {Number} return.height
  11933. */
  11934. getSize: function(contentSize) {
  11935. var dom = this.dom;
  11936. return {
  11937. width: Math.max(0, contentSize ? (dom.clientWidth - this.getPadding("lr")) : dom.offsetWidth),
  11938. height: Math.max(0, contentSize ? (dom.clientHeight - this.getPadding("tb")) : dom.offsetHeight)
  11939. };
  11940. },
  11941. /**
  11942. * Forces the browser to repaint this element.
  11943. * @return {Ext.dom.Element} this
  11944. */
  11945. repaint: function() {
  11946. var dom = this.dom;
  11947. this.addCls(Ext.baseCSSPrefix + 'repaint');
  11948. setTimeout(function() {
  11949. Ext.fly(dom).removeCls(Ext.baseCSSPrefix + 'repaint');
  11950. }, 1);
  11951. return this;
  11952. },
  11953. /**
  11954. * Returns an object with properties top, left, right and bottom representing the margins of this element unless sides is passed,
  11955. * then it returns the calculated width of the sides (see {@link #getPadding}).
  11956. * @param {String} [sides] Any combination of 'l', 'r', 't', 'b' to get the sum of those sides.
  11957. * @return {Object/Number}
  11958. */
  11959. getMargin: function(side) {
  11960. var me = this,
  11961. hash = {t: "top", l: "left", r: "right", b: "bottom"},
  11962. o = {},
  11963. key;
  11964. if (!side) {
  11965. for (key in me.margins) {
  11966. o[hash[key]] = parseFloat(me.getStyle(me.margins[key])) || 0;
  11967. }
  11968. return o;
  11969. } else {
  11970. return me.addStyles.call(me, side, me.margins);
  11971. }
  11972. }
  11973. });
  11974. //@tag dom,core
  11975. //@define Ext.Element-all
  11976. //@define Ext.Element-traversal
  11977. //@require Ext.Element-style
  11978. /**
  11979. * @class Ext.dom.Element
  11980. */
  11981. Ext.dom.Element.addMembers({
  11982. getParent: function() {
  11983. return Ext.get(this.dom.parentNode);
  11984. },
  11985. getFirstChild: function() {
  11986. return Ext.get(this.dom.firstElementChild);
  11987. },
  11988. /**
  11989. * Returns `true` if this element is an ancestor of the passed element.
  11990. * @param {HTMLElement/String} element The element to check.
  11991. * @return {Boolean} `true` if this element is an ancestor of `el`, else `false`.
  11992. */
  11993. contains: function(element) {
  11994. if (!element) {
  11995. return false;
  11996. }
  11997. var dom = Ext.getDom(element);
  11998. // we need el-contains-itself logic here because isAncestor does not do that:
  11999. return (dom === this.dom) || this.self.isAncestor(this.dom, dom);
  12000. },
  12001. /**
  12002. * Looks at this node and then at parent nodes for a match of the passed simple selector (e.g. 'div.some-class' or 'span:first-child')
  12003. * @param {String} selector The simple selector to test.
  12004. * @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
  12005. * The max depth to search as a number or element (defaults to `50 || document.body`)
  12006. * @param {Boolean} returnEl (optional) `true` to return a Ext.Element object instead of DOM node.
  12007. * @return {HTMLElement/null} The matching DOM node (or `null` if no match was found).
  12008. */
  12009. findParent: function(simpleSelector, maxDepth, returnEl) {
  12010. var p = this.dom,
  12011. b = document.body,
  12012. depth = 0,
  12013. stopEl;
  12014. maxDepth = maxDepth || 50;
  12015. if (isNaN(maxDepth)) {
  12016. stopEl = Ext.getDom(maxDepth);
  12017. maxDepth = Number.MAX_VALUE;
  12018. }
  12019. while (p && p.nodeType == 1 && depth < maxDepth && p != b && p != stopEl) {
  12020. if (Ext.DomQuery.is(p, simpleSelector)) {
  12021. return returnEl ? Ext.get(p) : p;
  12022. }
  12023. depth++;
  12024. p = p.parentNode;
  12025. }
  12026. return null;
  12027. },
  12028. /**
  12029. * Looks at parent nodes for a match of the passed simple selector (e.g. 'div.some-class' or 'span:first-child').
  12030. * @param {String} selector The simple selector to test.
  12031. * @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
  12032. * The max depth to search as a number or element (defaults to `10 || document.body`).
  12033. * @param {Boolean} returnEl (optional) `true` to return a Ext.Element object instead of DOM node.
  12034. * @return {HTMLElement/null} The matching DOM node (or `null` if no match was found).
  12035. */
  12036. findParentNode: function(simpleSelector, maxDepth, returnEl) {
  12037. var p = Ext.fly(this.dom.parentNode, '_internal');
  12038. return p ? p.findParent(simpleSelector, maxDepth, returnEl) : null;
  12039. },
  12040. /**
  12041. * Walks up the dom looking for a parent node that matches the passed simple selector (e.g. 'div.some-class' or 'span:first-child').
  12042. * This is a shortcut for `findParentNode()` that always returns an Ext.dom.Element.
  12043. * @param {String} selector The simple selector to test
  12044. * @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
  12045. * The max depth to search as a number or element (defaults to `10 || document.body`).
  12046. * @return {Ext.dom.Element/null} The matching DOM node (or `null` if no match was found).
  12047. */
  12048. up: function(simpleSelector, maxDepth) {
  12049. return this.findParentNode(simpleSelector, maxDepth, true);
  12050. },
  12051. select: function(selector, composite) {
  12052. return Ext.dom.Element.select(selector, this.dom, composite);
  12053. },
  12054. /**
  12055. * Selects child nodes based on the passed CSS selector (the selector should not contain an id).
  12056. * @param {String} selector The CSS selector.
  12057. * @return {HTMLElement[]} An array of the matched nodes.
  12058. */
  12059. query: function(selector) {
  12060. return Ext.DomQuery.select(selector, this.dom);
  12061. },
  12062. /**
  12063. * Selects a single child at any depth below this element based on the passed CSS selector (the selector should not contain an id).
  12064. * @param {String} selector The CSS selector.
  12065. * @param {Boolean} [returnDom=false] (optional) `true` to return the DOM node instead of Ext.dom.Element.
  12066. * @return {HTMLElement/Ext.dom.Element} The child Ext.dom.Element (or DOM node if `returnDom` is `true`).
  12067. */
  12068. down: function(selector, returnDom) {
  12069. var n = Ext.DomQuery.selectNode(selector, this.dom);
  12070. return returnDom ? n : Ext.get(n);
  12071. },
  12072. /**
  12073. * Selects a single *direct* child based on the passed CSS selector (the selector should not contain an id).
  12074. * @param {String} selector The CSS selector.
  12075. * @param {Boolean} [returnDom=false] (optional) `true` to return the DOM node instead of Ext.dom.Element.
  12076. * @return {HTMLElement/Ext.dom.Element} The child Ext.dom.Element (or DOM node if `returnDom` is `true`)
  12077. */
  12078. child: function(selector, returnDom) {
  12079. var node,
  12080. me = this,
  12081. id;
  12082. id = Ext.get(me).id;
  12083. // Escape . or :
  12084. id = id.replace(/[\.:]/g, "\\$0");
  12085. node = Ext.DomQuery.selectNode('#' + id + " > " + selector, me.dom);
  12086. return returnDom ? node : Ext.get(node);
  12087. },
  12088. /**
  12089. * Gets the parent node for this element, optionally chaining up trying to match a selector.
  12090. * @param {String} selector (optional) Find a parent node that matches the passed simple selector.
  12091. * @param {Boolean} returnDom (optional) `true` to return a raw DOM node instead of an Ext.dom.Element.
  12092. * @return {Ext.dom.Element/HTMLElement/null} The parent node or `null`.
  12093. */
  12094. parent: function(selector, returnDom) {
  12095. return this.matchNode('parentNode', 'parentNode', selector, returnDom);
  12096. },
  12097. /**
  12098. * Gets the next sibling, skipping text nodes.
  12099. * @param {String} selector (optional) Find the next sibling that matches the passed simple selector.
  12100. * @param {Boolean} returnDom (optional) `true` to return a raw dom node instead of an Ext.dom.Element.
  12101. * @return {Ext.dom.Element/HTMLElement/null} The next sibling or `null`.
  12102. */
  12103. next: function(selector, returnDom) {
  12104. return this.matchNode('nextSibling', 'nextSibling', selector, returnDom);
  12105. },
  12106. /**
  12107. * Gets the previous sibling, skipping text nodes.
  12108. * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector.
  12109. * @param {Boolean} returnDom (optional) `true` to return a raw DOM node instead of an Ext.dom.Element
  12110. * @return {Ext.dom.Element/HTMLElement/null} The previous sibling or `null`.
  12111. */
  12112. prev: function(selector, returnDom) {
  12113. return this.matchNode('previousSibling', 'previousSibling', selector, returnDom);
  12114. },
  12115. /**
  12116. * Gets the first child, skipping text nodes.
  12117. * @param {String} selector (optional) Find the next sibling that matches the passed simple selector.
  12118. * @param {Boolean} returnDom (optional) `true` to return a raw DOM node instead of an Ext.dom.Element.
  12119. * @return {Ext.dom.Element/HTMLElement/null} The first child or `null`.
  12120. */
  12121. first: function(selector, returnDom) {
  12122. return this.matchNode('nextSibling', 'firstChild', selector, returnDom);
  12123. },
  12124. /**
  12125. * Gets the last child, skipping text nodes.
  12126. * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector.
  12127. * @param {Boolean} returnDom (optional) `true` to return a raw DOM node instead of an Ext.dom.Element.
  12128. * @return {Ext.dom.Element/HTMLElement/null} The last child or `null`.
  12129. */
  12130. last: function(selector, returnDom) {
  12131. return this.matchNode('previousSibling', 'lastChild', selector, returnDom);
  12132. },
  12133. matchNode: function(dir, start, selector, returnDom) {
  12134. if (!this.dom) {
  12135. return null;
  12136. }
  12137. var n = this.dom[start];
  12138. while (n) {
  12139. if (n.nodeType == 1 && (!selector || Ext.DomQuery.is(n, selector))) {
  12140. return !returnDom ? Ext.get(n) : n;
  12141. }
  12142. n = n[dir];
  12143. }
  12144. return null;
  12145. },
  12146. isAncestor: function(element) {
  12147. return this.self.isAncestor.call(this.self, this.dom, element);
  12148. }
  12149. });
  12150. //@tag dom,core
  12151. //@require Ext.Element-all
  12152. /**
  12153. * This class encapsulates a *collection* of DOM elements, providing methods to filter members, or to perform collective
  12154. * actions upon the whole set.
  12155. *
  12156. * Although they are not listed, this class supports all of the methods of {@link Ext.dom.Element} and
  12157. * {@link Ext.Anim}. The methods from these classes will be performed on all the elements in this collection.
  12158. *
  12159. * Example:
  12160. *
  12161. * var els = Ext.select("#some-el div.some-class");
  12162. * // or select directly from an existing element
  12163. * var el = Ext.get('some-el');
  12164. * el.select('div.some-class');
  12165. *
  12166. * els.setWidth(100); // all elements become 100 width
  12167. * els.hide(true); // all elements fade out and hide
  12168. * // or
  12169. * els.setWidth(100).hide(true);
  12170. *
  12171. * @mixins Ext.dom.Element
  12172. */
  12173. Ext.define('Ext.dom.CompositeElementLite', {
  12174. alternateClassName: ['Ext.CompositeElementLite', 'Ext.CompositeElement'],
  12175. requires: ['Ext.dom.Element'],
  12176. // We use the @mixins tag above to document that CompositeElement has
  12177. // all the same methods as Element, but the @mixins tag also pulls in
  12178. // configs and properties which we don't want, so hide them explicitly:
  12179. /** @cfg bubbleEvents @hide */
  12180. /** @cfg listeners @hide */
  12181. /** @property DISPLAY @hide */
  12182. /** @property OFFSETS @hide */
  12183. /** @property VISIBILITY @hide */
  12184. /** @property defaultUnit @hide */
  12185. /** @property dom @hide */
  12186. /** @property id @hide */
  12187. // Also hide the static #get method that also gets inherited
  12188. /** @method get @static @hide */
  12189. statics: {
  12190. /**
  12191. * @private
  12192. * @static
  12193. * Copies all of the functions from Ext.dom.Element's prototype onto CompositeElementLite's prototype.
  12194. */
  12195. importElementMethods: function() {
  12196. }
  12197. },
  12198. constructor: function(elements, root) {
  12199. /**
  12200. * @property {HTMLElement[]} elements
  12201. * @readonly
  12202. * The Array of DOM elements which this CompositeElement encapsulates.
  12203. *
  12204. * This will not *usually* be accessed in developers' code, but developers wishing to augment the capabilities
  12205. * of the CompositeElementLite class may use it when adding methods to the class.
  12206. *
  12207. * For example to add the `nextAll` method to the class to **add** all following siblings of selected elements,
  12208. * the code would be
  12209. *
  12210. * Ext.override(Ext.dom.CompositeElementLite, {
  12211. * nextAll: function() {
  12212. * var elements = this.elements, i, l = elements.length, n, r = [], ri = -1;
  12213. *
  12214. * // Loop through all elements in this Composite, accumulating
  12215. * // an Array of all siblings.
  12216. * for (i = 0; i < l; i++) {
  12217. * for (n = elements[i].nextSibling; n; n = n.nextSibling) {
  12218. * r[++ri] = n;
  12219. * }
  12220. * }
  12221. *
  12222. * // Add all found siblings to this Composite
  12223. * return this.add(r);
  12224. * }
  12225. * });
  12226. */
  12227. this.elements = [];
  12228. this.add(elements, root);
  12229. this.el = new Ext.dom.Element.Fly();
  12230. },
  12231. isComposite: true,
  12232. // @private
  12233. getElement: function(el) {
  12234. // Set the shared flyweight dom property to the current element
  12235. return this.el.attach(el).synchronize();
  12236. },
  12237. // @private
  12238. transformElement: function(el) {
  12239. return Ext.getDom(el);
  12240. },
  12241. /**
  12242. * Returns the number of elements in this Composite.
  12243. * @return {Number}
  12244. */
  12245. getCount: function() {
  12246. return this.elements.length;
  12247. },
  12248. /**
  12249. * Adds elements to this Composite object.
  12250. * @param {HTMLElement[]/Ext.dom.CompositeElementLite} els Either an Array of DOM elements to add, or another Composite
  12251. * object who's elements should be added.
  12252. * @param {HTMLElement/String} [root] The root element of the query or id of the root.
  12253. * @return {Ext.dom.CompositeElementLite} This Composite object.
  12254. */
  12255. add: function(els, root) {
  12256. var elements = this.elements,
  12257. i, ln;
  12258. if (!els) {
  12259. return this;
  12260. }
  12261. if (typeof els == "string") {
  12262. els = Ext.dom.Element.selectorFunction(els, root);
  12263. }
  12264. else if (els.isComposite) {
  12265. els = els.elements;
  12266. }
  12267. else if (!Ext.isIterable(els)) {
  12268. els = [els];
  12269. }
  12270. for (i = 0, ln = els.length; i < ln; ++i) {
  12271. elements.push(this.transformElement(els[i]));
  12272. }
  12273. return this;
  12274. },
  12275. invoke: function(fn, args) {
  12276. var elements = this.elements,
  12277. ln = elements.length,
  12278. element,
  12279. i;
  12280. for (i = 0; i < ln; i++) {
  12281. element = elements[i];
  12282. if (element) {
  12283. Ext.dom.Element.prototype[fn].apply(this.getElement(element), args);
  12284. }
  12285. }
  12286. return this;
  12287. },
  12288. /**
  12289. * Returns a flyweight Element of the dom element object at the specified index.
  12290. * @param {Number} index
  12291. * @return {Ext.dom.Element}
  12292. */
  12293. item: function(index) {
  12294. var el = this.elements[index],
  12295. out = null;
  12296. if (el) {
  12297. out = this.getElement(el);
  12298. }
  12299. return out;
  12300. },
  12301. // fixes scope with flyweight.
  12302. addListener: function(eventName, handler, scope, opt) {
  12303. var els = this.elements,
  12304. len = els.length,
  12305. i, e;
  12306. for (i = 0; i < len; i++) {
  12307. e = els[i];
  12308. if (e) {
  12309. e.on(eventName, handler, scope || e, opt);
  12310. }
  12311. }
  12312. return this;
  12313. },
  12314. /**
  12315. * Calls the passed function for each element in this composite.
  12316. * @param {Function} fn The function to call.
  12317. * @param {Ext.dom.Element} fn.el The current Element in the iteration. **This is the flyweight
  12318. * (shared) Ext.dom.Element instance, so if you require a a reference to the dom node, use el.dom.**
  12319. * @param {Ext.dom.CompositeElementLite} fn.c This Composite object.
  12320. * @param {Number} fn.index The zero-based index in the iteration.
  12321. * @param {Object} [scope] The scope (this reference) in which the function is executed.
  12322. * Defaults to the Element.
  12323. * @return {Ext.dom.CompositeElementLite} this
  12324. */
  12325. each: function(fn, scope) {
  12326. var me = this,
  12327. els = me.elements,
  12328. len = els.length,
  12329. i, e;
  12330. for (i = 0; i < len; i++) {
  12331. e = els[i];
  12332. if (e) {
  12333. e = this.getElement(e);
  12334. if (fn.call(scope || e, e, me, i) === false) {
  12335. break;
  12336. }
  12337. }
  12338. }
  12339. return me;
  12340. },
  12341. /**
  12342. * Clears this Composite and adds the elements passed.
  12343. * @param {HTMLElement[]/Ext.dom.CompositeElementLite} els Either an array of DOM elements, or another Composite from which
  12344. * to fill this Composite.
  12345. * @return {Ext.dom.CompositeElementLite} this
  12346. */
  12347. fill: function(els) {
  12348. var me = this;
  12349. me.elements = [];
  12350. me.add(els);
  12351. return me;
  12352. },
  12353. /**
  12354. * Filters this composite to only elements that match the passed selector.
  12355. * @param {String/Function} selector A string CSS selector or a comparison function. The comparison function will be
  12356. * called with the following arguments:
  12357. * @param {Ext.dom.Element} selector.el The current DOM element.
  12358. * @param {Number} selector.index The current index within the collection.
  12359. * @return {Ext.dom.CompositeElementLite} this
  12360. */
  12361. filter: function(selector) {
  12362. var els = [],
  12363. me = this,
  12364. fn = Ext.isFunction(selector) ? selector
  12365. : function(el) {
  12366. return el.is(selector);
  12367. };
  12368. me.each(function(el, self, i) {
  12369. if (fn(el, i) !== false) {
  12370. els[els.length] = me.transformElement(el);
  12371. }
  12372. });
  12373. me.elements = els;
  12374. return me;
  12375. },
  12376. /**
  12377. * Find the index of the passed element within the composite collection.
  12378. * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, or an Ext.dom.Element, or an HtmlElement
  12379. * to find within the composite collection.
  12380. * @return {Number} The index of the passed Ext.dom.Element in the composite collection, or -1 if not found.
  12381. */
  12382. indexOf: function(el) {
  12383. return Ext.Array.indexOf(this.elements, this.transformElement(el));
  12384. },
  12385. /**
  12386. * Replaces the specified element with the passed element.
  12387. * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, the Element itself, the index of the
  12388. * element in this composite to replace.
  12389. * @param {String/Ext.Element} replacement The id of an element or the Element itself.
  12390. * @param {Boolean} [domReplace] `true` to remove and replace the element in the document too.
  12391. * @return {Ext.dom.CompositeElementLite} this
  12392. */
  12393. replaceElement: function(el, replacement, domReplace) {
  12394. var index = !isNaN(el) ? el : this.indexOf(el),
  12395. d;
  12396. if (index > -1) {
  12397. replacement = Ext.getDom(replacement);
  12398. if (domReplace) {
  12399. d = this.elements[index];
  12400. d.parentNode.insertBefore(replacement, d);
  12401. Ext.removeNode(d);
  12402. }
  12403. Ext.Array.splice(this.elements, index, 1, replacement);
  12404. }
  12405. return this;
  12406. },
  12407. /**
  12408. * Removes all elements.
  12409. */
  12410. clear: function() {
  12411. this.elements = [];
  12412. },
  12413. addElements: function(els, root) {
  12414. if (!els) {
  12415. return this;
  12416. }
  12417. if (typeof els == "string") {
  12418. els = Ext.dom.Element.selectorFunction(els, root);
  12419. }
  12420. var yels = this.elements;
  12421. Ext.each(els, function(e) {
  12422. yels.push(Ext.get(e));
  12423. });
  12424. return this;
  12425. },
  12426. /**
  12427. * Returns the first Element
  12428. * @return {Ext.dom.Element}
  12429. */
  12430. first: function() {
  12431. return this.item(0);
  12432. },
  12433. /**
  12434. * Returns the last Element
  12435. * @return {Ext.dom.Element}
  12436. */
  12437. last: function() {
  12438. return this.item(this.getCount() - 1);
  12439. },
  12440. /**
  12441. * Returns `true` if this composite contains the passed element
  12442. * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, or an Ext.Element, or an HtmlElement to
  12443. * find within the composite collection.
  12444. * @return {Boolean}
  12445. */
  12446. contains: function(el) {
  12447. return this.indexOf(el) != -1;
  12448. },
  12449. /**
  12450. * Removes the specified element(s).
  12451. * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, the Element itself, the index of the
  12452. * element in this composite or an array of any of those.
  12453. * @param {Boolean} [removeDom] `true` to also remove the element from the document
  12454. * @return {Ext.dom.CompositeElementLite} this
  12455. */
  12456. removeElement: function(keys, removeDom) {
  12457. var me = this,
  12458. elements = this.elements,
  12459. el;
  12460. Ext.each(keys, function(val) {
  12461. if ((el = (elements[val] || elements[val = me.indexOf(val)]))) {
  12462. if (removeDom) {
  12463. if (el.dom) {
  12464. el.remove();
  12465. }
  12466. else {
  12467. Ext.removeNode(el);
  12468. }
  12469. }
  12470. Ext.Array.erase(elements, val, 1);
  12471. }
  12472. });
  12473. return this;
  12474. }
  12475. }, function() {
  12476. var Element = Ext.dom.Element,
  12477. elementPrototype = Element.prototype,
  12478. prototype = this.prototype,
  12479. name;
  12480. for (name in elementPrototype) {
  12481. if (typeof elementPrototype[name] == 'function'){
  12482. (function(key) {
  12483. prototype[key] = prototype[key] || function() {
  12484. return this.invoke(key, arguments);
  12485. };
  12486. }).call(prototype, name);
  12487. }
  12488. }
  12489. prototype.on = prototype.addListener;
  12490. if (Ext.DomQuery){
  12491. Element.selectorFunction = Ext.DomQuery.select;
  12492. }
  12493. /**
  12494. * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
  12495. * to be applied to many related elements in one statement through the returned
  12496. * {@link Ext.dom.CompositeElementLite CompositeElementLite} object.
  12497. * @param {String/HTMLElement[]} selector The CSS selector or an array of elements
  12498. * @param {HTMLElement/String} [root] The root element of the query or id of the root
  12499. * @return {Ext.dom.CompositeElementLite}
  12500. * @member Ext.dom.Element
  12501. * @method select
  12502. */
  12503. Element.select = function(selector, root) {
  12504. var elements;
  12505. if (typeof selector == "string") {
  12506. elements = Element.selectorFunction(selector, root);
  12507. }
  12508. else if (selector.length !== undefined) {
  12509. elements = selector;
  12510. }
  12511. else {
  12512. //<debug>
  12513. throw new Error("[Ext.select] Invalid selector specified: " + selector);
  12514. //</debug>
  12515. }
  12516. return new Ext.CompositeElementLite(elements);
  12517. };
  12518. /**
  12519. * @member Ext
  12520. * @method select
  12521. * @alias Ext.dom.Element#select
  12522. */
  12523. Ext.select = function() {
  12524. return Element.select.apply(Element, arguments);
  12525. };
  12526. });
  12527. //@require Ext.Class
  12528. //@require Ext.ClassManager
  12529. //@require Ext.Loader
  12530. /**
  12531. * Base class for all mixins.
  12532. * @private
  12533. */
  12534. Ext.define('Ext.mixin.Mixin', {
  12535. onClassExtended: function(cls, data) {
  12536. var mixinConfig = data.mixinConfig,
  12537. parentClassMixinConfig,
  12538. beforeHooks, afterHooks;
  12539. if (mixinConfig) {
  12540. parentClassMixinConfig = cls.superclass.mixinConfig;
  12541. if (parentClassMixinConfig) {
  12542. mixinConfig = data.mixinConfig = Ext.merge({}, parentClassMixinConfig, mixinConfig);
  12543. }
  12544. data.mixinId = mixinConfig.id;
  12545. beforeHooks = mixinConfig.beforeHooks;
  12546. afterHooks = mixinConfig.hooks || mixinConfig.afterHooks;
  12547. if (beforeHooks || afterHooks) {
  12548. Ext.Function.interceptBefore(data, 'onClassMixedIn', function(targetClass) {
  12549. var mixin = this.prototype;
  12550. if (beforeHooks) {
  12551. Ext.Object.each(beforeHooks, function(from, to) {
  12552. targetClass.override(to, function() {
  12553. if (mixin[from].apply(this, arguments) !== false) {
  12554. return this.callOverridden(arguments);
  12555. }
  12556. });
  12557. });
  12558. }
  12559. if (afterHooks) {
  12560. Ext.Object.each(afterHooks, function(from, to) {
  12561. targetClass.override(to, function() {
  12562. var ret = this.callOverridden(arguments);
  12563. mixin[from].apply(this, arguments);
  12564. return ret;
  12565. });
  12566. });
  12567. }
  12568. });
  12569. }
  12570. }
  12571. }
  12572. });
  12573. //@require @core
  12574. /**
  12575. * @private
  12576. */
  12577. Ext.define('Ext.event.ListenerStack', {
  12578. currentOrder: 'current',
  12579. length: 0,
  12580. constructor: function() {
  12581. this.listeners = {
  12582. before: [],
  12583. current: [],
  12584. after: []
  12585. };
  12586. this.lateBindingMap = {};
  12587. return this;
  12588. },
  12589. add: function(fn, scope, options, order) {
  12590. var lateBindingMap = this.lateBindingMap,
  12591. listeners = this.getAll(order),
  12592. i = listeners.length,
  12593. bindingMap, listener, id;
  12594. if (typeof fn == 'string' && scope.isIdentifiable) {
  12595. id = scope.getId();
  12596. bindingMap = lateBindingMap[id];
  12597. if (bindingMap) {
  12598. if (bindingMap[fn]) {
  12599. return false;
  12600. }
  12601. else {
  12602. bindingMap[fn] = true;
  12603. }
  12604. }
  12605. else {
  12606. lateBindingMap[id] = bindingMap = {};
  12607. bindingMap[fn] = true;
  12608. }
  12609. }
  12610. else {
  12611. if (i > 0) {
  12612. while (i--) {
  12613. listener = listeners[i];
  12614. if (listener.fn === fn && listener.scope === scope) {
  12615. listener.options = options;
  12616. return false;
  12617. }
  12618. }
  12619. }
  12620. }
  12621. listener = this.create(fn, scope, options, order);
  12622. if (options && options.prepend) {
  12623. delete options.prepend;
  12624. listeners.unshift(listener);
  12625. }
  12626. else {
  12627. listeners.push(listener);
  12628. }
  12629. this.length++;
  12630. return true;
  12631. },
  12632. getAt: function(index, order) {
  12633. return this.getAll(order)[index];
  12634. },
  12635. getAll: function(order) {
  12636. if (!order) {
  12637. order = this.currentOrder;
  12638. }
  12639. return this.listeners[order];
  12640. },
  12641. count: function(order) {
  12642. return this.getAll(order).length;
  12643. },
  12644. create: function(fn, scope, options, order) {
  12645. return {
  12646. stack: this,
  12647. fn: fn,
  12648. firingFn: false,
  12649. boundFn: false,
  12650. isLateBinding: typeof fn == 'string',
  12651. scope: scope,
  12652. options: options || {},
  12653. order: order
  12654. };
  12655. },
  12656. remove: function(fn, scope, order) {
  12657. var listeners = this.getAll(order),
  12658. i = listeners.length,
  12659. isRemoved = false,
  12660. lateBindingMap = this.lateBindingMap,
  12661. listener, id;
  12662. if (i > 0) {
  12663. // Start from the end index, faster than looping from the
  12664. // beginning for "single" listeners,
  12665. // which are normally LIFO
  12666. while (i--) {
  12667. listener = listeners[i];
  12668. if (listener.fn === fn && listener.scope === scope) {
  12669. listeners.splice(i, 1);
  12670. isRemoved = true;
  12671. this.length--;
  12672. if (typeof fn == 'string' && scope.isIdentifiable) {
  12673. id = scope.getId();
  12674. if (lateBindingMap[id] && lateBindingMap[id][fn]) {
  12675. delete lateBindingMap[id][fn];
  12676. }
  12677. }
  12678. break;
  12679. }
  12680. }
  12681. }
  12682. return isRemoved;
  12683. }
  12684. });
  12685. //@require @core
  12686. /**
  12687. * @private
  12688. */
  12689. Ext.define('Ext.event.Controller', {
  12690. isFiring: false,
  12691. listenerStack: null,
  12692. constructor: function(info) {
  12693. this.firingListeners = [];
  12694. this.firingArguments = [];
  12695. this.setInfo(info);
  12696. return this;
  12697. },
  12698. setInfo: function(info) {
  12699. this.info = info;
  12700. },
  12701. getInfo: function() {
  12702. return this.info;
  12703. },
  12704. setListenerStacks: function(listenerStacks) {
  12705. this.listenerStacks = listenerStacks;
  12706. },
  12707. fire: function(args, action) {
  12708. var listenerStacks = this.listenerStacks,
  12709. firingListeners = this.firingListeners,
  12710. firingArguments = this.firingArguments,
  12711. push = firingListeners.push,
  12712. ln = listenerStacks.length,
  12713. listeners, beforeListeners, currentListeners, afterListeners,
  12714. isActionBefore = false,
  12715. isActionAfter = false,
  12716. i;
  12717. firingListeners.length = 0;
  12718. if (action) {
  12719. if (action.order !== 'after') {
  12720. isActionBefore = true;
  12721. }
  12722. else {
  12723. isActionAfter = true;
  12724. }
  12725. }
  12726. if (ln === 1) {
  12727. listeners = listenerStacks[0].listeners;
  12728. beforeListeners = listeners.before;
  12729. currentListeners = listeners.current;
  12730. afterListeners = listeners.after;
  12731. if (beforeListeners.length > 0) {
  12732. push.apply(firingListeners, beforeListeners);
  12733. }
  12734. if (isActionBefore) {
  12735. push.call(firingListeners, action);
  12736. }
  12737. if (currentListeners.length > 0) {
  12738. push.apply(firingListeners, currentListeners);
  12739. }
  12740. if (isActionAfter) {
  12741. push.call(firingListeners, action);
  12742. }
  12743. if (afterListeners.length > 0) {
  12744. push.apply(firingListeners, afterListeners);
  12745. }
  12746. }
  12747. else {
  12748. for (i = 0; i < ln; i++) {
  12749. beforeListeners = listenerStacks[i].listeners.before;
  12750. if (beforeListeners.length > 0) {
  12751. push.apply(firingListeners, beforeListeners);
  12752. }
  12753. }
  12754. if (isActionBefore) {
  12755. push.call(firingListeners, action);
  12756. }
  12757. for (i = 0; i < ln; i++) {
  12758. currentListeners = listenerStacks[i].listeners.current;
  12759. if (currentListeners.length > 0) {
  12760. push.apply(firingListeners, currentListeners);
  12761. }
  12762. }
  12763. if (isActionAfter) {
  12764. push.call(firingListeners, action);
  12765. }
  12766. for (i = 0; i < ln; i++) {
  12767. afterListeners = listenerStacks[i].listeners.after;
  12768. if (afterListeners.length > 0) {
  12769. push.apply(firingListeners, afterListeners);
  12770. }
  12771. }
  12772. }
  12773. if (firingListeners.length === 0) {
  12774. return this;
  12775. }
  12776. if (!args) {
  12777. args = [];
  12778. }
  12779. firingArguments.length = 0;
  12780. firingArguments.push.apply(firingArguments, args);
  12781. // Backwards compatibility
  12782. firingArguments.push(null, this);
  12783. this.doFire();
  12784. return this;
  12785. },
  12786. doFire: function() {
  12787. var firingListeners = this.firingListeners,
  12788. firingArguments = this.firingArguments,
  12789. optionsArgumentIndex = firingArguments.length - 2,
  12790. i, ln, listener, options, fn, firingFn,
  12791. boundFn, isLateBinding, scope, args, result;
  12792. this.isPausing = false;
  12793. this.isPaused = false;
  12794. this.isStopped = false;
  12795. this.isFiring = true;
  12796. for (i = 0,ln = firingListeners.length; i < ln; i++) {
  12797. listener = firingListeners[i];
  12798. options = listener.options;
  12799. fn = listener.fn;
  12800. firingFn = listener.firingFn;
  12801. boundFn = listener.boundFn;
  12802. isLateBinding = listener.isLateBinding;
  12803. scope = listener.scope;
  12804. // Re-bind the callback if it has changed since the last time it's bound (overridden)
  12805. if (isLateBinding && boundFn && boundFn !== scope[fn]) {
  12806. boundFn = false;
  12807. firingFn = false;
  12808. }
  12809. if (!boundFn) {
  12810. if (isLateBinding) {
  12811. boundFn = scope[fn];
  12812. if (!boundFn) {
  12813. continue;
  12814. }
  12815. }
  12816. else {
  12817. boundFn = fn;
  12818. }
  12819. listener.boundFn = boundFn;
  12820. }
  12821. if (!firingFn) {
  12822. firingFn = boundFn;
  12823. if (options.buffer) {
  12824. firingFn = Ext.Function.createBuffered(firingFn, options.buffer, scope);
  12825. }
  12826. if (options.delay) {
  12827. firingFn = Ext.Function.createDelayed(firingFn, options.delay, scope);
  12828. }
  12829. listener.firingFn = firingFn;
  12830. }
  12831. firingArguments[optionsArgumentIndex] = options;
  12832. args = firingArguments;
  12833. if (options.args) {
  12834. args = options.args.concat(args);
  12835. }
  12836. if (options.single === true) {
  12837. listener.stack.remove(fn, scope, listener.order);
  12838. }
  12839. result = firingFn.apply(scope, args);
  12840. if (result === false) {
  12841. this.stop();
  12842. }
  12843. if (this.isStopped) {
  12844. break;
  12845. }
  12846. if (this.isPausing) {
  12847. this.isPaused = true;
  12848. firingListeners.splice(0, i + 1);
  12849. return;
  12850. }
  12851. }
  12852. this.isFiring = false;
  12853. this.listenerStacks = null;
  12854. firingListeners.length = 0;
  12855. firingArguments.length = 0;
  12856. this.connectingController = null;
  12857. },
  12858. connect: function(controller) {
  12859. this.connectingController = controller;
  12860. },
  12861. resume: function() {
  12862. var connectingController = this.connectingController;
  12863. this.isPausing = false;
  12864. if (this.isPaused && this.firingListeners.length > 0) {
  12865. this.isPaused = false;
  12866. this.doFire();
  12867. }
  12868. if (connectingController) {
  12869. connectingController.resume();
  12870. }
  12871. return this;
  12872. },
  12873. isInterrupted: function() {
  12874. return this.isStopped || this.isPaused;
  12875. },
  12876. stop: function() {
  12877. var connectingController = this.connectingController;
  12878. this.isStopped = true;
  12879. if (connectingController) {
  12880. this.connectingController = null;
  12881. connectingController.stop();
  12882. }
  12883. this.isFiring = false;
  12884. this.listenerStacks = null;
  12885. return this;
  12886. },
  12887. pause: function() {
  12888. var connectingController = this.connectingController;
  12889. this.isPausing = true;
  12890. if (connectingController) {
  12891. connectingController.pause();
  12892. }
  12893. return this;
  12894. }
  12895. });
  12896. //@require @core
  12897. /**
  12898. * @private
  12899. */
  12900. Ext.define('Ext.event.Dispatcher', {
  12901. requires: [
  12902. 'Ext.event.ListenerStack',
  12903. 'Ext.event.Controller'
  12904. ],
  12905. statics: {
  12906. getInstance: function() {
  12907. if (!this.instance) {
  12908. this.instance = new this();
  12909. }
  12910. return this.instance;
  12911. },
  12912. setInstance: function(instance) {
  12913. this.instance = instance;
  12914. return this;
  12915. }
  12916. },
  12917. config: {
  12918. publishers: {}
  12919. },
  12920. wildcard: '*',
  12921. constructor: function(config) {
  12922. this.listenerStacks = {};
  12923. this.activePublishers = {};
  12924. this.publishersCache = {};
  12925. this.noActivePublishers = [];
  12926. this.controller = null;
  12927. this.initConfig(config);
  12928. return this;
  12929. },
  12930. getListenerStack: function(targetType, target, eventName, createIfNotExist) {
  12931. var listenerStacks = this.listenerStacks,
  12932. map = listenerStacks[targetType],
  12933. listenerStack;
  12934. createIfNotExist = Boolean(createIfNotExist);
  12935. if (!map) {
  12936. if (createIfNotExist) {
  12937. listenerStacks[targetType] = map = {};
  12938. }
  12939. else {
  12940. return null;
  12941. }
  12942. }
  12943. map = map[target];
  12944. if (!map) {
  12945. if (createIfNotExist) {
  12946. listenerStacks[targetType][target] = map = {};
  12947. }
  12948. else {
  12949. return null;
  12950. }
  12951. }
  12952. listenerStack = map[eventName];
  12953. if (!listenerStack) {
  12954. if (createIfNotExist) {
  12955. map[eventName] = listenerStack = new Ext.event.ListenerStack();
  12956. }
  12957. else {
  12958. return null;
  12959. }
  12960. }
  12961. return listenerStack;
  12962. },
  12963. getController: function(targetType, target, eventName, connectedController) {
  12964. var controller = this.controller,
  12965. info = {
  12966. targetType: targetType,
  12967. target: target,
  12968. eventName: eventName
  12969. };
  12970. if (!controller) {
  12971. this.controller = controller = new Ext.event.Controller();
  12972. }
  12973. if (controller.isFiring) {
  12974. controller = new Ext.event.Controller();
  12975. }
  12976. controller.setInfo(info);
  12977. if (connectedController && controller !== connectedController) {
  12978. controller.connect(connectedController);
  12979. }
  12980. return controller;
  12981. },
  12982. applyPublishers: function(publishers) {
  12983. var i, publisher;
  12984. this.publishersCache = {};
  12985. for (i in publishers) {
  12986. if (publishers.hasOwnProperty(i)) {
  12987. publisher = publishers[i];
  12988. this.registerPublisher(publisher);
  12989. }
  12990. }
  12991. return publishers;
  12992. },
  12993. registerPublisher: function(publisher) {
  12994. var activePublishers = this.activePublishers,
  12995. targetType = publisher.getTargetType(),
  12996. publishers = activePublishers[targetType];
  12997. if (!publishers) {
  12998. activePublishers[targetType] = publishers = [];
  12999. }
  13000. publishers.push(publisher);
  13001. publisher.setDispatcher(this);
  13002. return this;
  13003. },
  13004. getCachedActivePublishers: function(targetType, eventName) {
  13005. var cache = this.publishersCache,
  13006. publishers;
  13007. if ((publishers = cache[targetType]) && (publishers = publishers[eventName])) {
  13008. return publishers;
  13009. }
  13010. return null;
  13011. },
  13012. cacheActivePublishers: function(targetType, eventName, publishers) {
  13013. var cache = this.publishersCache;
  13014. if (!cache[targetType]) {
  13015. cache[targetType] = {};
  13016. }
  13017. cache[targetType][eventName] = publishers;
  13018. return publishers;
  13019. },
  13020. getActivePublishers: function(targetType, eventName) {
  13021. var publishers, activePublishers,
  13022. i, ln, publisher;
  13023. if ((publishers = this.getCachedActivePublishers(targetType, eventName))) {
  13024. return publishers;
  13025. }
  13026. activePublishers = this.activePublishers[targetType];
  13027. if (activePublishers) {
  13028. publishers = [];
  13029. for (i = 0,ln = activePublishers.length; i < ln; i++) {
  13030. publisher = activePublishers[i];
  13031. if (publisher.handles(eventName)) {
  13032. publishers.push(publisher);
  13033. }
  13034. }
  13035. }
  13036. else {
  13037. publishers = this.noActivePublishers;
  13038. }
  13039. return this.cacheActivePublishers(targetType, eventName, publishers);
  13040. },
  13041. hasListener: function(targetType, target, eventName) {
  13042. var listenerStack = this.getListenerStack(targetType, target, eventName);
  13043. if (listenerStack) {
  13044. return listenerStack.count() > 0;
  13045. }
  13046. return false;
  13047. },
  13048. addListener: function(targetType, target, eventName) {
  13049. var publishers = this.getActivePublishers(targetType, eventName),
  13050. ln = publishers.length,
  13051. i;
  13052. if (ln > 0) {
  13053. for (i = 0; i < ln; i++) {
  13054. publishers[i].subscribe(target, eventName);
  13055. }
  13056. }
  13057. return this.doAddListener.apply(this, arguments);
  13058. },
  13059. doAddListener: function(targetType, target, eventName, fn, scope, options, order) {
  13060. var listenerStack = this.getListenerStack(targetType, target, eventName, true);
  13061. return listenerStack.add(fn, scope, options, order);
  13062. },
  13063. removeListener: function(targetType, target, eventName) {
  13064. var publishers = this.getActivePublishers(targetType, eventName),
  13065. ln = publishers.length,
  13066. i;
  13067. if (ln > 0) {
  13068. for (i = 0; i < ln; i++) {
  13069. publishers[i].unsubscribe(target, eventName);
  13070. }
  13071. }
  13072. return this.doRemoveListener.apply(this, arguments);
  13073. },
  13074. doRemoveListener: function(targetType, target, eventName, fn, scope, order) {
  13075. var listenerStack = this.getListenerStack(targetType, target, eventName);
  13076. if (listenerStack === null) {
  13077. return false;
  13078. }
  13079. return listenerStack.remove(fn, scope, order);
  13080. },
  13081. clearListeners: function(targetType, target, eventName) {
  13082. var listenerStacks = this.listenerStacks,
  13083. ln = arguments.length,
  13084. stacks, publishers, i, publisherGroup;
  13085. if (ln === 3) {
  13086. if (listenerStacks[targetType] && listenerStacks[targetType][target]) {
  13087. this.removeListener(targetType, target, eventName);
  13088. delete listenerStacks[targetType][target][eventName];
  13089. }
  13090. }
  13091. else if (ln === 2) {
  13092. if (listenerStacks[targetType]) {
  13093. stacks = listenerStacks[targetType][target];
  13094. if (stacks) {
  13095. for (eventName in stacks) {
  13096. if (stacks.hasOwnProperty(eventName)) {
  13097. publishers = this.getActivePublishers(targetType, eventName);
  13098. for (i = 0,ln = publishers.length; i < ln; i++) {
  13099. publishers[i].unsubscribe(target, eventName, true);
  13100. }
  13101. }
  13102. }
  13103. delete listenerStacks[targetType][target];
  13104. }
  13105. }
  13106. }
  13107. else if (ln === 1) {
  13108. publishers = this.activePublishers[targetType];
  13109. for (i = 0,ln = publishers.length; i < ln; i++) {
  13110. publishers[i].unsubscribeAll();
  13111. }
  13112. delete listenerStacks[targetType];
  13113. }
  13114. else {
  13115. publishers = this.activePublishers;
  13116. for (targetType in publishers) {
  13117. if (publishers.hasOwnProperty(targetType)) {
  13118. publisherGroup = publishers[targetType];
  13119. for (i = 0,ln = publisherGroup.length; i < ln; i++) {
  13120. publisherGroup[i].unsubscribeAll();
  13121. }
  13122. }
  13123. }
  13124. delete this.listenerStacks;
  13125. this.listenerStacks = {};
  13126. }
  13127. return this;
  13128. },
  13129. dispatchEvent: function(targetType, target, eventName) {
  13130. var publishers = this.getActivePublishers(targetType, eventName),
  13131. ln = publishers.length,
  13132. i;
  13133. if (ln > 0) {
  13134. for (i = 0; i < ln; i++) {
  13135. publishers[i].notify(target, eventName);
  13136. }
  13137. }
  13138. return this.doDispatchEvent.apply(this, arguments);
  13139. },
  13140. doDispatchEvent: function(targetType, target, eventName, args, action, connectedController) {
  13141. var listenerStack = this.getListenerStack(targetType, target, eventName),
  13142. wildcardStacks = this.getWildcardListenerStacks(targetType, target, eventName),
  13143. controller;
  13144. if ((listenerStack === null || listenerStack.length == 0)) {
  13145. if (wildcardStacks.length == 0 && !action) {
  13146. return;
  13147. }
  13148. }
  13149. else {
  13150. wildcardStacks.push(listenerStack);
  13151. }
  13152. controller = this.getController(targetType, target, eventName, connectedController);
  13153. controller.setListenerStacks(wildcardStacks);
  13154. controller.fire(args, action);
  13155. return !controller.isInterrupted();
  13156. },
  13157. getWildcardListenerStacks: function(targetType, target, eventName) {
  13158. var stacks = [],
  13159. wildcard = this.wildcard,
  13160. isEventNameNotWildcard = eventName !== wildcard,
  13161. isTargetNotWildcard = target !== wildcard,
  13162. stack;
  13163. if (isEventNameNotWildcard && (stack = this.getListenerStack(targetType, target, wildcard))) {
  13164. stacks.push(stack);
  13165. }
  13166. if (isTargetNotWildcard && (stack = this.getListenerStack(targetType, wildcard, eventName))) {
  13167. stacks.push(stack);
  13168. }
  13169. return stacks;
  13170. }
  13171. });
  13172. /**
  13173. * Mixin that provides a common interface for publishing events. Classes using this mixin can use the {@link #fireEvent}
  13174. * and {@link #fireAction} methods to notify listeners of events on the class.
  13175. *
  13176. * Classes can also define a {@link #listeners} config to add an event handler to the current object. See
  13177. * {@link #addListener} for more details.
  13178. *
  13179. * ## Example
  13180. *
  13181. * Ext.define('Employee', {
  13182. * mixins: ['Ext.mixin.Observable'],
  13183. *
  13184. * config: {
  13185. * fullName: ''
  13186. * },
  13187. *
  13188. * constructor: function(config) {
  13189. * this.initConfig(config); // We need to initialize the config options when the class is instantiated
  13190. * },
  13191. *
  13192. * quitJob: function() {
  13193. * this.fireEvent('quit');
  13194. * }
  13195. * });
  13196. *
  13197. * var newEmployee = Ext.create('Employee', {
  13198. *
  13199. * fullName: 'Ed Spencer',
  13200. *
  13201. * listeners: {
  13202. * quit: function() { // This function will be called when the 'quit' event is fired
  13203. * // By default, "this" will be the object that fired the event.
  13204. * console.log(this.getFullName() + " has quit!");
  13205. * }
  13206. * }
  13207. * });
  13208. *
  13209. * newEmployee.quitJob(); // Will log 'Ed Spencer has quit!'
  13210. *
  13211. * @aside guide events
  13212. */
  13213. Ext.define('Ext.mixin.Observable', {
  13214. requires: ['Ext.event.Dispatcher'],
  13215. extend: 'Ext.mixin.Mixin',
  13216. mixins: ['Ext.mixin.Identifiable'],
  13217. mixinConfig: {
  13218. id: 'observable',
  13219. hooks: {
  13220. destroy: 'destroy'
  13221. }
  13222. },
  13223. alternateClassName: 'Ext.util.Observable',
  13224. // @private
  13225. isObservable: true,
  13226. observableType: 'observable',
  13227. validIdRegex: /^([\w\-]+)$/,
  13228. observableIdPrefix: '#',
  13229. listenerOptionsRegex: /^(?:delegate|single|delay|buffer|args|prepend)$/,
  13230. config: {
  13231. /**
  13232. * @cfg {Object} listeners
  13233. *
  13234. * A config object containing one or more event handlers to be added to this object during initialization. This
  13235. * should be a valid listeners `config` object as specified in the {@link #addListener} example for attaching
  13236. * multiple handlers at once.
  13237. *
  13238. * See the [Event guide](#!/guide/events) for more
  13239. *
  13240. * __Note:__ It is bad practice to specify a listener's `config` when you are defining a class using `Ext.define()`.
  13241. * Instead, only specify listeners when you are instantiating your class with `Ext.create()`.
  13242. * @accessor
  13243. */
  13244. listeners: null,
  13245. /**
  13246. * @cfg {String/String[]} bubbleEvents The event name to bubble, or an Array of event names.
  13247. * @accessor
  13248. */
  13249. bubbleEvents: null
  13250. },
  13251. constructor: function(config) {
  13252. this.initConfig(config);
  13253. },
  13254. applyListeners: function(listeners) {
  13255. if (listeners) {
  13256. this.addListener(listeners);
  13257. }
  13258. },
  13259. applyBubbleEvents: function(bubbleEvents) {
  13260. if (bubbleEvents) {
  13261. this.enableBubble(bubbleEvents);
  13262. }
  13263. },
  13264. getOptimizedObservableId: function() {
  13265. return this.observableId;
  13266. },
  13267. getObservableId: function() {
  13268. if (!this.observableId) {
  13269. var id = this.getUniqueId();
  13270. //<debug error>
  13271. if (!id.match(this.validIdRegex)) {
  13272. Ext.Logger.error("Invalid unique id of '" + id + "' for this object", this);
  13273. }
  13274. //</debug>
  13275. this.observableId = this.observableIdPrefix + id;
  13276. this.getObservableId = this.getOptimizedObservableId;
  13277. }
  13278. return this.observableId;
  13279. },
  13280. getOptimizedEventDispatcher: function() {
  13281. return this.eventDispatcher;
  13282. },
  13283. getEventDispatcher: function() {
  13284. if (!this.eventDispatcher) {
  13285. this.eventDispatcher = Ext.event.Dispatcher.getInstance();
  13286. this.getEventDispatcher = this.getOptimizedEventDispatcher;
  13287. this.getListeners();
  13288. this.getBubbleEvents();
  13289. }
  13290. return this.eventDispatcher;
  13291. },
  13292. getManagedListeners: function(object, eventName) {
  13293. var id = object.getUniqueId(),
  13294. managedListeners = this.managedListeners;
  13295. if (!managedListeners) {
  13296. this.managedListeners = managedListeners = {};
  13297. }
  13298. if (!managedListeners[id]) {
  13299. managedListeners[id] = {};
  13300. object.doAddListener('destroy', 'clearManagedListeners', this, {
  13301. single: true,
  13302. args: [object]
  13303. });
  13304. }
  13305. if (!managedListeners[id][eventName]) {
  13306. managedListeners[id][eventName] = [];
  13307. }
  13308. return managedListeners[id][eventName];
  13309. },
  13310. getUsedSelectors: function() {
  13311. var selectors = this.usedSelectors;
  13312. if (!selectors) {
  13313. selectors = this.usedSelectors = [];
  13314. selectors.$map = {};
  13315. }
  13316. return selectors;
  13317. },
  13318. /**
  13319. * Fires the specified event with the passed parameters (minus the event name, plus the `options` object passed
  13320. * to {@link #addListener}).
  13321. *
  13322. * The first argument is the name of the event. Every other argument passed will be available when you listen for
  13323. * the event.
  13324. *
  13325. * ## Example
  13326. *
  13327. * Firstly, we set up a listener for our new event.
  13328. *
  13329. * this.on('myevent', function(arg1, arg2, arg3, arg4, options, e) {
  13330. * console.log(arg1); // true
  13331. * console.log(arg2); // 2
  13332. * console.log(arg3); // { test: 'foo' }
  13333. * console.log(arg4); // 14
  13334. * console.log(options); // the options added when adding the listener
  13335. * console.log(e); // the event object with information about the event
  13336. * });
  13337. *
  13338. * And then we can fire off the event.
  13339. *
  13340. * this.fireEvent('myevent', true, 2, { test: 'foo' }, 14);
  13341. *
  13342. * An event may be set to bubble up an Observable parent hierarchy by calling {@link #enableBubble}.
  13343. *
  13344. * @param {String} eventName The name of the event to fire.
  13345. * @param {Object...} args Variable number of parameters are passed to handlers.
  13346. * @return {Boolean} Returns `false` if any of the handlers return `false`, otherwise it returns `true`.
  13347. */
  13348. fireEvent: function(eventName) {
  13349. var args = Array.prototype.slice.call(arguments, 1);
  13350. return this.doFireEvent(eventName, args);
  13351. },
  13352. /**
  13353. * Fires the specified event with the passed parameters and execute a function (action)
  13354. * at the end if there are no listeners that return `false`.
  13355. *
  13356. * @param {String} eventName The name of the event to fire.
  13357. * @param {Array} args Arguments to pass to handers.
  13358. * @param {Function} fn Action.
  13359. * @param {Object} scope Scope of fn.
  13360. * @return {Object}
  13361. */
  13362. fireAction: function(eventName, args, fn, scope, options, order) {
  13363. var fnType = typeof fn,
  13364. action;
  13365. if (args === undefined) {
  13366. args = [];
  13367. }
  13368. if (fnType != 'undefined') {
  13369. action = {
  13370. fn: fn,
  13371. isLateBinding: fnType == 'string',
  13372. scope: scope || this,
  13373. options: options || {},
  13374. order: order
  13375. };
  13376. }
  13377. return this.doFireEvent(eventName, args, action);
  13378. },
  13379. doFireEvent: function(eventName, args, action, connectedController) {
  13380. if (this.eventFiringSuspended) {
  13381. return;
  13382. }
  13383. var id = this.getObservableId(),
  13384. dispatcher = this.getEventDispatcher();
  13385. return dispatcher.dispatchEvent(this.observableType, id, eventName, args, action, connectedController);
  13386. },
  13387. /**
  13388. * @private
  13389. * @param name
  13390. * @param fn
  13391. * @param scope
  13392. * @param options
  13393. * @return {Boolean}
  13394. */
  13395. doAddListener: function(name, fn, scope, options, order) {
  13396. var isManaged = (scope && scope !== this && scope.isIdentifiable),
  13397. usedSelectors = this.getUsedSelectors(),
  13398. usedSelectorsMap = usedSelectors.$map,
  13399. selector = this.getObservableId(),
  13400. isAdded, managedListeners, delegate;
  13401. if (!options) {
  13402. options = {};
  13403. }
  13404. if (!scope) {
  13405. scope = this;
  13406. }
  13407. if (options.delegate) {
  13408. delegate = options.delegate;
  13409. // See https://sencha.jira.com/browse/TOUCH-1579
  13410. selector += ' ' + delegate;
  13411. }
  13412. if (!(selector in usedSelectorsMap)) {
  13413. usedSelectorsMap[selector] = true;
  13414. usedSelectors.push(selector);
  13415. }
  13416. isAdded = this.addDispatcherListener(selector, name, fn, scope, options, order);
  13417. if (isAdded && isManaged) {
  13418. managedListeners = this.getManagedListeners(scope, name);
  13419. managedListeners.push({
  13420. delegate: delegate,
  13421. scope: scope,
  13422. fn: fn,
  13423. order: order
  13424. });
  13425. }
  13426. return isAdded;
  13427. },
  13428. addDispatcherListener: function(selector, name, fn, scope, options, order) {
  13429. return this.getEventDispatcher().addListener(this.observableType, selector, name, fn, scope, options, order);
  13430. },
  13431. doRemoveListener: function(name, fn, scope, options, order) {
  13432. var isManaged = (scope && scope !== this && scope.isIdentifiable),
  13433. selector = this.getObservableId(),
  13434. isRemoved,
  13435. managedListeners, i, ln, listener, delegate;
  13436. if (options && options.delegate) {
  13437. delegate = options.delegate;
  13438. // See https://sencha.jira.com/browse/TOUCH-1579
  13439. selector += ' ' + delegate;
  13440. }
  13441. if (!scope) {
  13442. scope = this;
  13443. }
  13444. isRemoved = this.removeDispatcherListener(selector, name, fn, scope, order);
  13445. if (isRemoved && isManaged) {
  13446. managedListeners = this.getManagedListeners(scope, name);
  13447. for (i = 0,ln = managedListeners.length; i < ln; i++) {
  13448. listener = managedListeners[i];
  13449. if (listener.fn === fn && listener.scope === scope && listener.delegate === delegate && listener.order === order) {
  13450. managedListeners.splice(i, 1);
  13451. break;
  13452. }
  13453. }
  13454. }
  13455. return isRemoved;
  13456. },
  13457. removeDispatcherListener: function(selector, name, fn, scope, order) {
  13458. return this.getEventDispatcher().removeListener(this.observableType, selector, name, fn, scope, order);
  13459. },
  13460. clearManagedListeners: function(object) {
  13461. var managedListeners = this.managedListeners,
  13462. id, namedListeners, listeners, eventName, i, ln, listener, options;
  13463. if (!managedListeners) {
  13464. return this;
  13465. }
  13466. if (object) {
  13467. if (typeof object != 'string') {
  13468. id = object.getUniqueId();
  13469. }
  13470. else {
  13471. id = object;
  13472. }
  13473. namedListeners = managedListeners[id];
  13474. for (eventName in namedListeners) {
  13475. if (namedListeners.hasOwnProperty(eventName)) {
  13476. listeners = namedListeners[eventName];
  13477. for (i = 0,ln = listeners.length; i < ln; i++) {
  13478. listener = listeners[i];
  13479. options = {};
  13480. if (listener.delegate) {
  13481. options.delegate = listener.delegate;
  13482. }
  13483. if (this.doRemoveListener(eventName, listener.fn, listener.scope, options, listener.order)) {
  13484. i--;
  13485. ln--;
  13486. }
  13487. }
  13488. }
  13489. }
  13490. delete managedListeners[id];
  13491. return this;
  13492. }
  13493. for (id in managedListeners) {
  13494. if (managedListeners.hasOwnProperty(id)) {
  13495. this.clearManagedListeners(id);
  13496. }
  13497. }
  13498. },
  13499. /**
  13500. * @private
  13501. * @param operation
  13502. * @param eventName
  13503. * @param fn
  13504. * @param scope
  13505. * @param options
  13506. * @param order
  13507. * @return {Object}
  13508. */
  13509. changeListener: function(actionFn, eventName, fn, scope, options, order) {
  13510. var eventNames,
  13511. listeners,
  13512. listenerOptionsRegex,
  13513. actualOptions,
  13514. name, value, i, ln, listener, valueType;
  13515. if (typeof fn != 'undefined') {
  13516. // Support for array format to add multiple listeners
  13517. if (typeof eventName != 'string') {
  13518. for (i = 0,ln = eventName.length; i < ln; i++) {
  13519. name = eventName[i];
  13520. actionFn.call(this, name, fn, scope, options, order);
  13521. }
  13522. return this;
  13523. }
  13524. actionFn.call(this, eventName, fn, scope, options, order);
  13525. }
  13526. else if (Ext.isArray(eventName)) {
  13527. listeners = eventName;
  13528. for (i = 0,ln = listeners.length; i < ln; i++) {
  13529. listener = listeners[i];
  13530. actionFn.call(this, listener.event, listener.fn, listener.scope, listener, listener.order);
  13531. }
  13532. }
  13533. else {
  13534. listenerOptionsRegex = this.listenerOptionsRegex;
  13535. options = eventName;
  13536. eventNames = [];
  13537. listeners = [];
  13538. actualOptions = {};
  13539. for (name in options) {
  13540. value = options[name];
  13541. if (name === 'scope') {
  13542. scope = value;
  13543. continue;
  13544. }
  13545. else if (name === 'order') {
  13546. order = value;
  13547. continue;
  13548. }
  13549. if (!listenerOptionsRegex.test(name)) {
  13550. valueType = typeof value;
  13551. if (valueType != 'string' && valueType != 'function') {
  13552. actionFn.call(this, name, value.fn, value.scope || scope, value, value.order || order);
  13553. continue;
  13554. }
  13555. eventNames.push(name);
  13556. listeners.push(value);
  13557. }
  13558. else {
  13559. actualOptions[name] = value;
  13560. }
  13561. }
  13562. for (i = 0,ln = eventNames.length; i < ln; i++) {
  13563. actionFn.call(this, eventNames[i], listeners[i], scope, actualOptions, order);
  13564. }
  13565. }
  13566. return this;
  13567. },
  13568. /**
  13569. * Appends an event handler to this object. You can review the available handlers by looking at the 'events'
  13570. * section of the documentation for the component you are working with.
  13571. *
  13572. * ## Combining Options
  13573. *
  13574. * Using the options argument, it is possible to combine different types of listeners:
  13575. *
  13576. * A delayed, one-time listener:
  13577. *
  13578. * container.on('tap', this.handleTap, this, {
  13579. * single: true,
  13580. * delay: 100
  13581. * });
  13582. *
  13583. * ## Attaching multiple handlers in 1 call
  13584. *
  13585. * The method also allows for a single argument to be passed which is a config object containing properties which
  13586. * specify multiple events. For example:
  13587. *
  13588. * container.on({
  13589. * tap : this.onTap,
  13590. * swipe: this.onSwipe,
  13591. *
  13592. * scope: this // Important. Ensure "this" is correct during handler execution
  13593. * });
  13594. *
  13595. * One can also specify options for each event handler separately:
  13596. *
  13597. * container.on({
  13598. * tap : { fn: this.onTap, scope: this, single: true },
  13599. * swipe: { fn: button.onSwipe, scope: button }
  13600. * });
  13601. *
  13602. * See the [Events Guide](#!/guide/events) for more.
  13603. *
  13604. * @param {String/String[]/Object} eventName The name of the event to listen for. May also be an object who's property names are
  13605. * event names.
  13606. * @param {Function} fn The method the event invokes. Will be called with arguments given to
  13607. * {@link #fireEvent} plus the `options` parameter described below.
  13608. * @param {Object} [scope] The scope (`this` reference) in which the handler function is executed. **If
  13609. * omitted, defaults to the object which fired the event.**
  13610. * @param {Object} [options] An object containing handler configuration.
  13611. *
  13612. * This object may contain any of the following properties:
  13613. * @param {Object} [options.scope] The scope (`this` reference) in which the handler function is executed. If omitted, defaults to the object
  13614. * which fired the event.
  13615. * @param {Number} [options.delay] The number of milliseconds to delay the invocation of the handler after the event fires.
  13616. * @param {Boolean} [options.single] `true` to add a handler to handle just the next firing of the event, and then remove itself.
  13617. * @param {String} [options.order=current] The order of when the listener should be added into the listener queue.
  13618. *
  13619. * If you set an order of `before` and the event you are listening to is preventable, you can return `false` and it will stop the event.
  13620. *
  13621. * Available options are `before`, `current` and `after`.
  13622. *
  13623. * @param {Number} [options.buffer] Causes the handler to be delayed by the specified number of milliseconds. If the event fires again within that
  13624. * time, the original handler is _not_ invoked, but the new handler is scheduled in its place.
  13625. * @param {String} [options.element] Allows you to add a listener onto a element of this component using the elements reference.
  13626. *
  13627. * Ext.create('Ext.Component', {
  13628. * listeners: {
  13629. * element: 'element',
  13630. * tap: function() {
  13631. * alert('element tap!');
  13632. * }
  13633. * }
  13634. * });
  13635. *
  13636. * All components have the `element` reference, which is the outer most element of the component. {@link Ext.Container} also has the
  13637. * `innerElement` element which contains all children. In most cases `element` is adequate.
  13638. *
  13639. * @param {String} [options.delegate] Uses {@link Ext.ComponentQuery} to delegate events to a specified query selector within this item.
  13640. *
  13641. * // Create a container with a two children; a button and a toolbar
  13642. * var container = Ext.create('Ext.Container', {
  13643. * items: [
  13644. * {
  13645. * xtype: 'toolbar',
  13646. * docked: 'top',
  13647. * title: 'My Toolbar'
  13648. * },
  13649. * {
  13650. * xtype: 'button',
  13651. * text: 'My Button'
  13652. * }
  13653. * ]
  13654. * });
  13655. *
  13656. * container.on({
  13657. * // Ext.Buttons have an xtype of 'button', so we use that are a selector for our delegate
  13658. * delegate: 'button',
  13659. *
  13660. * tap: function() {
  13661. * alert('Button tapped!');
  13662. * }
  13663. * });
  13664. *
  13665. * @param {String} [order='current'] The order of when the listener should be added into the listener queue.
  13666. * Possible values are `before`, `current` and `after`.
  13667. */
  13668. addListener: function(eventName, fn, scope, options, order) {
  13669. return this.changeListener(this.doAddListener, eventName, fn, scope, options, order);
  13670. },
  13671. toggleListener: function(toggle, eventName, fn, scope, options, order) {
  13672. return this.changeListener(toggle ? this.doAddListener : this.doRemoveListener, eventName, fn, scope, options, order);
  13673. },
  13674. /**
  13675. * Appends a before-event handler. Returning `false` from the handler will stop the event.
  13676. *
  13677. * Same as {@link #addListener} with `order` set to `'before'`.
  13678. *
  13679. * @param {String/String[]/Object} eventName The name of the event to listen for.
  13680. * @param {Function} fn The method the event invokes.
  13681. * @param {Object} [scope] The scope for `fn`.
  13682. * @param {Object} [options] An object containing handler configuration.
  13683. */
  13684. addBeforeListener: function(eventName, fn, scope, options) {
  13685. return this.addListener(eventName, fn, scope, options, 'before');
  13686. },
  13687. /**
  13688. * Appends an after-event handler.
  13689. *
  13690. * Same as {@link #addListener} with `order` set to `'after'`.
  13691. *
  13692. * @param {String/String[]/Object} eventName The name of the event to listen for.
  13693. * @param {Function} fn The method the event invokes.
  13694. * @param {Object} [scope] The scope for `fn`.
  13695. * @param {Object} [options] An object containing handler configuration.
  13696. */
  13697. addAfterListener: function(eventName, fn, scope, options) {
  13698. return this.addListener(eventName, fn, scope, options, 'after');
  13699. },
  13700. /**
  13701. * Removes an event handler.
  13702. *
  13703. * @param {String/String[]/Object} eventName The type of event the handler was associated with.
  13704. * @param {Function} fn The handler to remove. **This must be a reference to the function passed into the
  13705. * {@link #addListener} call.**
  13706. * @param {Object} [scope] The scope originally specified for the handler. It must be the same as the
  13707. * scope argument specified in the original call to {@link #addListener} or the listener will not be removed.
  13708. * @param {Object} [options] Extra options object. See {@link #addListener} for details.
  13709. * @param {String} [order='current'] The order of the listener to remove.
  13710. * Possible values are `before`, `current` and `after`.
  13711. */
  13712. removeListener: function(eventName, fn, scope, options, order) {
  13713. return this.changeListener(this.doRemoveListener, eventName, fn, scope, options, order);
  13714. },
  13715. /**
  13716. * Removes a before-event handler.
  13717. *
  13718. * Same as {@link #removeListener} with `order` set to `'before'`.
  13719. *
  13720. * @param {String/String[]/Object} eventName The name of the event the handler was associated with.
  13721. * @param {Function} fn The handler to remove.
  13722. * @param {Object} [scope] The scope originally specified for `fn`.
  13723. * @param {Object} [options] Extra options object.
  13724. */
  13725. removeBeforeListener: function(eventName, fn, scope, options) {
  13726. return this.removeListener(eventName, fn, scope, options, 'before');
  13727. },
  13728. /**
  13729. * Removes a before-event handler.
  13730. *
  13731. * Same as {@link #removeListener} with `order` set to `'after'`.
  13732. *
  13733. * @param {String/String[]/Object} eventName The name of the event the handler was associated with.
  13734. * @param {Function} fn The handler to remove.
  13735. * @param {Object} [scope] The scope originally specified for `fn`.
  13736. * @param {Object} [options] Extra options object.
  13737. */
  13738. removeAfterListener: function(eventName, fn, scope, options) {
  13739. return this.removeListener(eventName, fn, scope, options, 'after');
  13740. },
  13741. /**
  13742. * Removes all listeners for this object.
  13743. */
  13744. clearListeners: function() {
  13745. var usedSelectors = this.getUsedSelectors(),
  13746. dispatcher = this.getEventDispatcher(),
  13747. i, ln, selector;
  13748. for (i = 0,ln = usedSelectors.length; i < ln; i++) {
  13749. selector = usedSelectors[i];
  13750. dispatcher.clearListeners(this.observableType, selector);
  13751. }
  13752. },
  13753. /**
  13754. * Checks to see if this object has any listeners for a specified event
  13755. *
  13756. * @param {String} eventName The name of the event to check for
  13757. * @return {Boolean} True if the event is being listened for, else false
  13758. */
  13759. hasListener: function(eventName) {
  13760. return this.getEventDispatcher().hasListener(this.observableType, this.getObservableId(), eventName);
  13761. },
  13762. /**
  13763. * Suspends the firing of all events. (see {@link #resumeEvents})
  13764. *
  13765. * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
  13766. * after the {@link #resumeEvents} call instead of discarding all suspended events.
  13767. */
  13768. suspendEvents: function(queueSuspended) {
  13769. this.eventFiringSuspended = true;
  13770. },
  13771. /**
  13772. * Resumes firing events (see {@link #suspendEvents}).
  13773. *
  13774. * If events were suspended using the `queueSuspended` parameter, then all events fired
  13775. * during event suspension will be sent to any listeners now.
  13776. */
  13777. resumeEvents: function() {
  13778. this.eventFiringSuspended = false;
  13779. },
  13780. /**
  13781. * Relays selected events from the specified Observable as if the events were fired by `this`.
  13782. * @param {Object} object The Observable whose events this object is to relay.
  13783. * @param {String/Array/Object} events Array of event names to relay.
  13784. */
  13785. relayEvents: function(object, events, prefix) {
  13786. var i, ln, oldName, newName;
  13787. if (typeof prefix == 'undefined') {
  13788. prefix = '';
  13789. }
  13790. if (typeof events == 'string') {
  13791. events = [events];
  13792. }
  13793. if (Ext.isArray(events)) {
  13794. for (i = 0,ln = events.length; i < ln; i++) {
  13795. oldName = events[i];
  13796. newName = prefix + oldName;
  13797. object.addListener(oldName, this.createEventRelayer(newName), this);
  13798. }
  13799. }
  13800. else {
  13801. for (oldName in events) {
  13802. if (events.hasOwnProperty(oldName)) {
  13803. newName = prefix + events[oldName];
  13804. object.addListener(oldName, this.createEventRelayer(newName), this);
  13805. }
  13806. }
  13807. }
  13808. return this;
  13809. },
  13810. /**
  13811. * @private
  13812. * @param args
  13813. * @param fn
  13814. */
  13815. relayEvent: function(args, fn, scope, options, order) {
  13816. var fnType = typeof fn,
  13817. controller = args[args.length - 1],
  13818. eventName = controller.getInfo().eventName,
  13819. action;
  13820. args = Array.prototype.slice.call(args, 0, -2);
  13821. args[0] = this;
  13822. if (fnType != 'undefined') {
  13823. action = {
  13824. fn: fn,
  13825. scope: scope || this,
  13826. options: options || {},
  13827. order: order,
  13828. isLateBinding: fnType == 'string'
  13829. };
  13830. }
  13831. return this.doFireEvent(eventName, args, action, controller);
  13832. },
  13833. /**
  13834. * @private
  13835. * Creates an event handling function which re-fires the event from this object as the passed event name.
  13836. * @param newName
  13837. * @return {Function}
  13838. */
  13839. createEventRelayer: function(newName){
  13840. return function() {
  13841. return this.doFireEvent(newName, Array.prototype.slice.call(arguments, 0, -2));
  13842. }
  13843. },
  13844. /**
  13845. * Enables events fired by this Observable to bubble up an owner hierarchy by calling `this.getBubbleTarget()` if
  13846. * present. There is no implementation in the Observable base class.
  13847. *
  13848. * @param {String/String[]} events The event name to bubble, or an Array of event names.
  13849. */
  13850. enableBubble: function(events) {
  13851. var isBubblingEnabled = this.isBubblingEnabled,
  13852. i, ln, name;
  13853. if (!isBubblingEnabled) {
  13854. isBubblingEnabled = this.isBubblingEnabled = {};
  13855. }
  13856. if (typeof events == 'string') {
  13857. events = Ext.Array.clone(arguments);
  13858. }
  13859. for (i = 0,ln = events.length; i < ln; i++) {
  13860. name = events[i];
  13861. if (!isBubblingEnabled[name]) {
  13862. isBubblingEnabled[name] = true;
  13863. this.addListener(name, this.createEventBubbler(name), this);
  13864. }
  13865. }
  13866. },
  13867. createEventBubbler: function(name) {
  13868. return function doBubbleEvent() {
  13869. var bubbleTarget = ('getBubbleTarget' in this) ? this.getBubbleTarget() : null;
  13870. if (bubbleTarget && bubbleTarget !== this && bubbleTarget.isObservable) {
  13871. bubbleTarget.fireAction(name, Array.prototype.slice.call(arguments, 0, -2), doBubbleEvent, bubbleTarget, null, 'after');
  13872. }
  13873. }
  13874. },
  13875. getBubbleTarget: function() {
  13876. return false;
  13877. },
  13878. destroy: function() {
  13879. if (this.observableId) {
  13880. this.fireEvent('destroy', this);
  13881. this.clearListeners();
  13882. this.clearManagedListeners();
  13883. }
  13884. },
  13885. /**
  13886. * @ignore
  13887. */
  13888. addEvents: Ext.emptyFn
  13889. }, function() {
  13890. this.createAlias({
  13891. /**
  13892. * @method
  13893. * Alias for {@link #addListener}.
  13894. * @inheritdoc Ext.mixin.Observable#addListener
  13895. */
  13896. on: 'addListener',
  13897. /**
  13898. * @method
  13899. * Alias for {@link #removeListener}.
  13900. * @inheritdoc Ext.mixin.Observable#removeListener
  13901. */
  13902. un: 'removeListener',
  13903. /**
  13904. * @method
  13905. * Alias for {@link #addBeforeListener}.
  13906. * @inheritdoc Ext.mixin.Observable#addBeforeListener
  13907. */
  13908. onBefore: 'addBeforeListener',
  13909. /**
  13910. * @method
  13911. * Alias for {@link #addAfterListener}.
  13912. * @inheritdoc Ext.mixin.Observable#addAfterListener
  13913. */
  13914. onAfter: 'addAfterListener',
  13915. /**
  13916. * @method
  13917. * Alias for {@link #removeBeforeListener}.
  13918. * @inheritdoc Ext.mixin.Observable#removeBeforeListener
  13919. */
  13920. unBefore: 'removeBeforeListener',
  13921. /**
  13922. * @method
  13923. * Alias for {@link #removeAfterListener}.
  13924. * @inheritdoc Ext.mixin.Observable#removeAfterListener
  13925. */
  13926. unAfter: 'removeAfterListener'
  13927. });
  13928. });
  13929. /**
  13930. * @private
  13931. */
  13932. Ext.define('Ext.Evented', {
  13933. alternateClassName: 'Ext.EventedBase',
  13934. mixins: ['Ext.mixin.Observable'],
  13935. statics: {
  13936. generateSetter: function(nameMap) {
  13937. var internalName = nameMap.internal,
  13938. applyName = nameMap.apply,
  13939. changeEventName = nameMap.changeEvent,
  13940. doSetName = nameMap.doSet;
  13941. return function(value) {
  13942. var initialized = this.initialized,
  13943. oldValue = this[internalName],
  13944. applier = this[applyName];
  13945. if (applier) {
  13946. value = applier.call(this, value, oldValue);
  13947. if (typeof value == 'undefined') {
  13948. return this;
  13949. }
  13950. }
  13951. // The old value might have been changed at this point
  13952. // (after the apply call chain) so it should be read again
  13953. oldValue = this[internalName];
  13954. if (value !== oldValue) {
  13955. if (initialized) {
  13956. this.fireAction(changeEventName, [this, value, oldValue], this.doSet, this, {
  13957. nameMap: nameMap
  13958. });
  13959. }
  13960. else {
  13961. this[internalName] = value;
  13962. if (this[doSetName]) {
  13963. this[doSetName].call(this, value, oldValue);
  13964. }
  13965. }
  13966. }
  13967. return this;
  13968. }
  13969. }
  13970. },
  13971. initialized: false,
  13972. constructor: function(config) {
  13973. this.initialConfig = config;
  13974. this.initialize();
  13975. },
  13976. initialize: function() {
  13977. this.initConfig(this.initialConfig);
  13978. this.initialized = true;
  13979. },
  13980. doSet: function(me, value, oldValue, options) {
  13981. var nameMap = options.nameMap;
  13982. me[nameMap.internal] = value;
  13983. if (me[nameMap.doSet]) {
  13984. me[nameMap.doSet].call(this, value, oldValue);
  13985. }
  13986. },
  13987. onClassExtended: function(Class, data) {
  13988. if (!data.hasOwnProperty('eventedConfig')) {
  13989. return;
  13990. }
  13991. var ExtClass = Ext.Class,
  13992. config = data.config,
  13993. eventedConfig = data.eventedConfig,
  13994. name, nameMap;
  13995. data.config = (config) ? Ext.applyIf(config, eventedConfig) : eventedConfig;
  13996. /*
  13997. * These are generated setters for eventedConfig
  13998. *
  13999. * If the component is initialized, it invokes fireAction to fire the event as well,
  14000. * which indicate something has changed. Otherwise, it just executes the action
  14001. * (happens during initialization)
  14002. *
  14003. * This is helpful when we only want the event to be fired for subsequent changes.
  14004. * Also it's a major performance improvement for instantiation when fired events
  14005. * are mostly useless since there's no listeners
  14006. */
  14007. for (name in eventedConfig) {
  14008. if (eventedConfig.hasOwnProperty(name)) {
  14009. nameMap = ExtClass.getConfigNameMap(name);
  14010. data[nameMap.set] = this.generateSetter(nameMap);
  14011. }
  14012. }
  14013. }
  14014. });
  14015. /**
  14016. * @private
  14017. * This is the abstract class for {@link Ext.Component}.
  14018. *
  14019. * This should never be overridden.
  14020. */
  14021. Ext.define('Ext.AbstractComponent', {
  14022. extend: 'Ext.Evented',
  14023. onClassExtended: function(Class, members) {
  14024. if (!members.hasOwnProperty('cachedConfig')) {
  14025. return;
  14026. }
  14027. var prototype = Class.prototype,
  14028. config = members.config,
  14029. cachedConfig = members.cachedConfig,
  14030. cachedConfigList = prototype.cachedConfigList,
  14031. hasCachedConfig = prototype.hasCachedConfig,
  14032. name, value;
  14033. delete members.cachedConfig;
  14034. prototype.cachedConfigList = cachedConfigList = (cachedConfigList) ? cachedConfigList.slice() : [];
  14035. prototype.hasCachedConfig = hasCachedConfig = (hasCachedConfig) ? Ext.Object.chain(hasCachedConfig) : {};
  14036. if (!config) {
  14037. members.config = config = {};
  14038. }
  14039. for (name in cachedConfig) {
  14040. if (cachedConfig.hasOwnProperty(name)) {
  14041. value = cachedConfig[name];
  14042. if (!hasCachedConfig[name]) {
  14043. hasCachedConfig[name] = true;
  14044. cachedConfigList.push(name);
  14045. }
  14046. config[name] = value;
  14047. }
  14048. }
  14049. },
  14050. getElementConfig: Ext.emptyFn,
  14051. referenceAttributeName: 'reference',
  14052. referenceSelector: '[reference]',
  14053. /**
  14054. * @private
  14055. * Significantly improve instantiation time for Component with multiple references
  14056. * Ext.Element instance of the reference domNode is only created the very first time
  14057. * it's ever used.
  14058. */
  14059. addReferenceNode: function(name, domNode) {
  14060. Ext.Object.defineProperty(this, name, {
  14061. get: function() {
  14062. var reference;
  14063. delete this[name];
  14064. this[name] = reference = new Ext.Element(domNode);
  14065. return reference;
  14066. },
  14067. configurable: true
  14068. });
  14069. },
  14070. initElement: function() {
  14071. var prototype = this.self.prototype,
  14072. id = this.getId(),
  14073. referenceList = [],
  14074. cleanAttributes = true,
  14075. referenceAttributeName = this.referenceAttributeName,
  14076. needsOptimization = false,
  14077. renderTemplate, renderElement, element,
  14078. referenceNodes, i, ln, referenceNode, reference,
  14079. configNameCache, defaultConfig, cachedConfigList, initConfigList, initConfigMap, configList,
  14080. elements, name, nameMap, internalName;
  14081. if (prototype.hasOwnProperty('renderTemplate')) {
  14082. renderTemplate = this.renderTemplate.cloneNode(true);
  14083. renderElement = renderTemplate.firstChild;
  14084. }
  14085. else {
  14086. cleanAttributes = false;
  14087. needsOptimization = true;
  14088. renderTemplate = document.createDocumentFragment();
  14089. renderElement = Ext.Element.create(this.getElementConfig(), true);
  14090. renderTemplate.appendChild(renderElement);
  14091. }
  14092. referenceNodes = renderTemplate.querySelectorAll(this.referenceSelector);
  14093. for (i = 0,ln = referenceNodes.length; i < ln; i++) {
  14094. referenceNode = referenceNodes[i];
  14095. reference = referenceNode.getAttribute(referenceAttributeName);
  14096. if (cleanAttributes) {
  14097. referenceNode.removeAttribute(referenceAttributeName);
  14098. }
  14099. if (reference == 'element') {
  14100. referenceNode.id = id;
  14101. this.element = element = new Ext.Element(referenceNode);
  14102. }
  14103. else {
  14104. this.addReferenceNode(reference, referenceNode);
  14105. }
  14106. referenceList.push(reference);
  14107. }
  14108. this.referenceList = referenceList;
  14109. if (!this.innerElement) {
  14110. this.innerElement = element;
  14111. }
  14112. if (!this.bodyElement) {
  14113. this.bodyElement = this.innerElement;
  14114. }
  14115. if (renderElement === element.dom) {
  14116. this.renderElement = element;
  14117. }
  14118. else {
  14119. this.addReferenceNode('renderElement', renderElement);
  14120. }
  14121. // This happens only *once* per class, during the very first instantiation
  14122. // to optimize renderTemplate based on cachedConfig
  14123. if (needsOptimization) {
  14124. configNameCache = Ext.Class.configNameCache;
  14125. defaultConfig = this.config;
  14126. cachedConfigList = this.cachedConfigList;
  14127. initConfigList = this.initConfigList;
  14128. initConfigMap = this.initConfigMap;
  14129. configList = [];
  14130. for (i = 0,ln = cachedConfigList.length; i < ln; i++) {
  14131. name = cachedConfigList[i];
  14132. nameMap = configNameCache[name];
  14133. if (initConfigMap[name]) {
  14134. initConfigMap[name] = false;
  14135. Ext.Array.remove(initConfigList, name);
  14136. }
  14137. if (defaultConfig[name] !== null) {
  14138. configList.push(name);
  14139. this[nameMap.get] = this[nameMap.initGet];
  14140. }
  14141. }
  14142. for (i = 0,ln = configList.length; i < ln; i++) {
  14143. name = configList[i];
  14144. nameMap = configNameCache[name];
  14145. internalName = nameMap.internal;
  14146. this[internalName] = null;
  14147. this[nameMap.set].call(this, defaultConfig[name]);
  14148. delete this[nameMap.get];
  14149. prototype[internalName] = this[internalName];
  14150. }
  14151. renderElement = this.renderElement.dom;
  14152. prototype.renderTemplate = renderTemplate = document.createDocumentFragment();
  14153. renderTemplate.appendChild(renderElement.cloneNode(true));
  14154. elements = renderTemplate.querySelectorAll('[id]');
  14155. for (i = 0,ln = elements.length; i < ln; i++) {
  14156. element = elements[i];
  14157. element.removeAttribute('id');
  14158. }
  14159. for (i = 0,ln = referenceList.length; i < ln; i++) {
  14160. reference = referenceList[i];
  14161. this[reference].dom.removeAttribute('reference');
  14162. }
  14163. }
  14164. return this;
  14165. }
  14166. });
  14167. /**
  14168. * Represents a collection of a set of key and value pairs. Each key in the HashMap must be unique, the same
  14169. * key cannot exist twice. Access to items is provided via the key only. Sample usage:
  14170. *
  14171. * var map = Ext.create('Ext.util.HashMap');
  14172. * map.add('key1', 1);
  14173. * map.add('key2', 2);
  14174. * map.add('key3', 3);
  14175. *
  14176. * map.each(function(key, value, length){
  14177. * console.log(key, value, length);
  14178. * });
  14179. *
  14180. * The HashMap is an unordered class, there is no guarantee when iterating over the items that they will be in
  14181. * any particular order. If this is required, then use a {@link Ext.util.MixedCollection}.
  14182. */
  14183. Ext.define('Ext.util.HashMap', {
  14184. mixins: {
  14185. observable: 'Ext.mixin.Observable'
  14186. },
  14187. /**
  14188. * @cfg {Function} keyFn
  14189. * A function that is used to retrieve a default key for a passed object.
  14190. * A default is provided that returns the **id** property on the object.
  14191. * This function is only used if the add method is called with a single argument.
  14192. */
  14193. /**
  14194. * Creates new HashMap.
  14195. * @param {Object} config The configuration options
  14196. */
  14197. constructor: function(config) {
  14198. /**
  14199. * @event add
  14200. * Fires when a new item is added to the hash.
  14201. * @param {Ext.util.HashMap} this
  14202. * @param {String} key The key of the added item.
  14203. * @param {Object} value The value of the added item.
  14204. */
  14205. /**
  14206. * @event clear
  14207. * Fires when the hash is cleared.
  14208. * @param {Ext.util.HashMap} this
  14209. */
  14210. /**
  14211. * @event remove
  14212. * Fires when an item is removed from the hash.
  14213. * @param {Ext.util.HashMap} this
  14214. * @param {String} key The key of the removed item.
  14215. * @param {Object} value The value of the removed item.
  14216. */
  14217. /**
  14218. * @event replace
  14219. * Fires when an item is replaced in the hash.
  14220. * @param {Ext.util.HashMap} this
  14221. * @param {String} key The key of the replaced item.
  14222. * @param {Object} value The new value for the item.
  14223. * @param {Object} old The old value for the item.
  14224. */
  14225. this.callParent();
  14226. this.mixins.observable.constructor.call(this);
  14227. this.clear(true);
  14228. },
  14229. /**
  14230. * Gets the number of items in the hash.
  14231. * @return {Number} The number of items in the hash.
  14232. */
  14233. getCount: function() {
  14234. return this.length;
  14235. },
  14236. /**
  14237. * Implementation for being able to extract the key from an object if only
  14238. * a single argument is passed.
  14239. * @private
  14240. * @param {String} key The key
  14241. * @param {Object} value The value
  14242. * @return {Array} [key, value]
  14243. */
  14244. getData: function(key, value) {
  14245. // if we have no value, it means we need to get the key from the object
  14246. if (value === undefined) {
  14247. value = key;
  14248. key = this.getKey(value);
  14249. }
  14250. return [key, value];
  14251. },
  14252. /**
  14253. * Extracts the key from an object. This is a default implementation, it may be overridden.
  14254. * @private
  14255. * @param {Object} o The object to get the key from.
  14256. * @return {String} The key to use.
  14257. */
  14258. getKey: function(o) {
  14259. return o.id;
  14260. },
  14261. /**
  14262. * Add a new item to the hash. An exception will be thrown if the key already exists.
  14263. * @param {String} key The key of the new item.
  14264. * @param {Object} value The value of the new item.
  14265. * @return {Object} The value of the new item added.
  14266. */
  14267. add: function(key, value) {
  14268. var me = this,
  14269. data;
  14270. if (me.containsKey(key)) {
  14271. throw new Error('This key already exists in the HashMap');
  14272. }
  14273. data = this.getData(key, value);
  14274. key = data[0];
  14275. value = data[1];
  14276. me.map[key] = value;
  14277. ++me.length;
  14278. me.fireEvent('add', me, key, value);
  14279. return value;
  14280. },
  14281. /**
  14282. * Replaces an item in the hash. If the key doesn't exist, the
  14283. * `{@link #method-add}` method will be used.
  14284. * @param {String} key The key of the item.
  14285. * @param {Object} value The new value for the item.
  14286. * @return {Object} The new value of the item.
  14287. */
  14288. replace: function(key, value) {
  14289. var me = this,
  14290. map = me.map,
  14291. old;
  14292. if (!me.containsKey(key)) {
  14293. me.add(key, value);
  14294. }
  14295. old = map[key];
  14296. map[key] = value;
  14297. me.fireEvent('replace', me, key, value, old);
  14298. return value;
  14299. },
  14300. /**
  14301. * Remove an item from the hash.
  14302. * @param {Object} o The value of the item to remove.
  14303. * @return {Boolean} `true` if the item was successfully removed.
  14304. */
  14305. remove: function(o) {
  14306. var key = this.findKey(o);
  14307. if (key !== undefined) {
  14308. return this.removeByKey(key);
  14309. }
  14310. return false;
  14311. },
  14312. /**
  14313. * Remove an item from the hash.
  14314. * @param {String} key The key to remove.
  14315. * @return {Boolean} `true` if the item was successfully removed.
  14316. */
  14317. removeByKey: function(key) {
  14318. var me = this,
  14319. value;
  14320. if (me.containsKey(key)) {
  14321. value = me.map[key];
  14322. delete me.map[key];
  14323. --me.length;
  14324. me.fireEvent('remove', me, key, value);
  14325. return true;
  14326. }
  14327. return false;
  14328. },
  14329. /**
  14330. * Retrieves an item with a particular key.
  14331. * @param {String} key The key to lookup.
  14332. * @return {Object} The value at that key. If it doesn't exist, `undefined` is returned.
  14333. */
  14334. get: function(key) {
  14335. return this.map[key];
  14336. },
  14337. /**
  14338. * Removes all items from the hash.
  14339. * @return {Ext.util.HashMap} this
  14340. */
  14341. clear: function(/* private */ initial) {
  14342. var me = this;
  14343. me.map = {};
  14344. me.length = 0;
  14345. if (initial !== true) {
  14346. me.fireEvent('clear', me);
  14347. }
  14348. return me;
  14349. },
  14350. /**
  14351. * Checks whether a key exists in the hash.
  14352. * @param {String} key The key to check for.
  14353. * @return {Boolean} `true` if they key exists in the hash.
  14354. */
  14355. containsKey: function(key) {
  14356. return this.map[key] !== undefined;
  14357. },
  14358. /**
  14359. * Checks whether a value exists in the hash.
  14360. * @param {Object} value The value to check for.
  14361. * @return {Boolean} `true` if the value exists in the dictionary.
  14362. */
  14363. contains: function(value) {
  14364. return this.containsKey(this.findKey(value));
  14365. },
  14366. /**
  14367. * Return all of the keys in the hash.
  14368. * @return {Array} An array of keys.
  14369. */
  14370. getKeys: function() {
  14371. return this.getArray(true);
  14372. },
  14373. /**
  14374. * Return all of the values in the hash.
  14375. * @return {Array} An array of values.
  14376. */
  14377. getValues: function() {
  14378. return this.getArray(false);
  14379. },
  14380. /**
  14381. * Gets either the keys/values in an array from the hash.
  14382. * @private
  14383. * @param {Boolean} isKey `true` to extract the keys, otherwise, the value.
  14384. * @return {Array} An array of either keys/values from the hash.
  14385. */
  14386. getArray: function(isKey) {
  14387. var arr = [],
  14388. key,
  14389. map = this.map;
  14390. for (key in map) {
  14391. if (map.hasOwnProperty(key)) {
  14392. arr.push(isKey ? key : map[key]);
  14393. }
  14394. }
  14395. return arr;
  14396. },
  14397. /**
  14398. * Executes the specified function once for each item in the hash.
  14399. *
  14400. * @param {Function} fn The function to execute.
  14401. * @param {String} fn.key The key of the item.
  14402. * @param {Number} fn.value The value of the item.
  14403. * @param {Number} fn.length The total number of items in the hash.
  14404. * @param {Boolean} fn.return Returning `false` from the function will cease the iteration.
  14405. * @param {Object} [scope=this] The scope to execute in.
  14406. * @return {Ext.util.HashMap} this
  14407. */
  14408. each: function(fn, scope) {
  14409. // copy items so they may be removed during iteration.
  14410. var items = Ext.apply({}, this.map),
  14411. key,
  14412. length = this.length;
  14413. scope = scope || this;
  14414. for (key in items) {
  14415. if (items.hasOwnProperty(key)) {
  14416. if (fn.call(scope, key, items[key], length) === false) {
  14417. break;
  14418. }
  14419. }
  14420. }
  14421. return this;
  14422. },
  14423. /**
  14424. * Performs a shallow copy on this hash.
  14425. * @return {Ext.util.HashMap} The new hash object.
  14426. */
  14427. clone: function() {
  14428. var hash = new Ext.util.HashMap(),
  14429. map = this.map,
  14430. key;
  14431. hash.suspendEvents();
  14432. for (key in map) {
  14433. if (map.hasOwnProperty(key)) {
  14434. hash.add(key, map[key]);
  14435. }
  14436. }
  14437. hash.resumeEvents();
  14438. return hash;
  14439. },
  14440. /**
  14441. * @private
  14442. * Find the key for a value.
  14443. * @param {Object} value The value to find.
  14444. * @return {Object} The value of the item. Returns `undefined` if not found.
  14445. */
  14446. findKey: function(value) {
  14447. var key,
  14448. map = this.map;
  14449. for (key in map) {
  14450. if (map.hasOwnProperty(key) && map[key] === value) {
  14451. return key;
  14452. }
  14453. }
  14454. return undefined;
  14455. }
  14456. });
  14457. /**
  14458. * @private
  14459. */
  14460. Ext.define('Ext.AbstractManager', {
  14461. /* Begin Definitions */
  14462. requires: ['Ext.util.HashMap'],
  14463. /* End Definitions */
  14464. typeName: 'type',
  14465. constructor: function(config) {
  14466. Ext.apply(this, config || {});
  14467. /**
  14468. * @property {Ext.util.HashMap} all
  14469. * Contains all of the items currently managed
  14470. */
  14471. this.all = Ext.create('Ext.util.HashMap');
  14472. this.types = {};
  14473. },
  14474. /**
  14475. * Returns an item by id.
  14476. * For additional details see {@link Ext.util.HashMap#get}.
  14477. * @param {String} id The `id` of the item.
  14478. * @return {Object} The item, `undefined` if not found.
  14479. */
  14480. get : function(id) {
  14481. return this.all.get(id);
  14482. },
  14483. /**
  14484. * Registers an item to be managed.
  14485. * @param {Object} item The item to register.
  14486. */
  14487. register: function(item) {
  14488. this.all.add(item);
  14489. },
  14490. /**
  14491. * Unregisters an item by removing it from this manager.
  14492. * @param {Object} item The item to unregister.
  14493. */
  14494. unregister: function(item) {
  14495. this.all.remove(item);
  14496. },
  14497. /**
  14498. * Registers a new item constructor, keyed by a type key.
  14499. * @param {String} type The mnemonic string by which the class may be looked up.
  14500. * @param {Function} cls The new instance class.
  14501. */
  14502. registerType : function(type, cls) {
  14503. this.types[type] = cls;
  14504. cls[this.typeName] = type;
  14505. },
  14506. /**
  14507. * Checks if an item type is registered.
  14508. * @param {String} type The mnemonic string by which the class may be looked up.
  14509. * @return {Boolean} Whether the type is registered.
  14510. */
  14511. isRegistered : function(type){
  14512. return this.types[type] !== undefined;
  14513. },
  14514. /**
  14515. * Creates and returns an instance of whatever this manager manages, based on the supplied type and
  14516. * config object.
  14517. * @param {Object} config The config object.
  14518. * @param {String} defaultType If no type is discovered in the config object, we fall back to this type.
  14519. * @return {Object} The instance of whatever this manager is managing.
  14520. */
  14521. create: function(config, defaultType) {
  14522. var type = config[this.typeName] || config.type || defaultType,
  14523. Constructor = this.types[type];
  14524. //<debug>
  14525. if (Constructor == undefined) {
  14526. Ext.Error.raise("The '" + type + "' type has not been registered with this manager");
  14527. }
  14528. //</debug>
  14529. return new Constructor(config);
  14530. },
  14531. /**
  14532. * Registers a function that will be called when an item with the specified id is added to the manager.
  14533. * This will happen on instantiation.
  14534. * @param {String} id The item `id`.
  14535. * @param {Function} fn The callback function. Called with a single parameter, the item.
  14536. * @param {Object} scope The scope (`this` reference) in which the callback is executed.
  14537. * Defaults to the item.
  14538. */
  14539. onAvailable : function(id, fn, scope){
  14540. var all = this.all,
  14541. item;
  14542. if (all.containsKey(id)) {
  14543. item = all.get(id);
  14544. fn.call(scope || item, item);
  14545. } else {
  14546. all.on('add', function(map, key, item){
  14547. if (key == id) {
  14548. fn.call(scope || item, item);
  14549. all.un('add', fn, scope);
  14550. }
  14551. });
  14552. }
  14553. },
  14554. /**
  14555. * Executes the specified function once for each item in the collection.
  14556. * @param {Function} fn The function to execute.
  14557. * @param {String} fn.key The key of the item
  14558. * @param {Number} fn.value The value of the item
  14559. * @param {Number} fn.length The total number of items in the collection
  14560. * @param {Boolean} fn.return False to cease iteration.
  14561. * @param {Object} [scope=this] The scope to execute in.
  14562. */
  14563. each: function(fn, scope){
  14564. this.all.each(fn, scope || this);
  14565. },
  14566. /**
  14567. * Gets the number of items in the collection.
  14568. * @return {Number} The number of items in the collection.
  14569. */
  14570. getCount: function(){
  14571. return this.all.getCount();
  14572. }
  14573. });
  14574. /**
  14575. * @private
  14576. *
  14577. * Provides a registry of all Components (instances of {@link Ext.Component} or any subclass
  14578. * thereof) on a page so that they can be easily accessed by {@link Ext.Component component}
  14579. * {@link Ext.Component#getId id} (see {@link #get}, or the convenience method {@link Ext#getCmp Ext.getCmp}).
  14580. *
  14581. * This object also provides a registry of available Component _classes_
  14582. * indexed by a mnemonic code known as the Component's `xtype`.
  14583. * The `xtype` provides a way to avoid instantiating child Components
  14584. * when creating a full, nested config object for a complete Ext page.
  14585. *
  14586. * A child Component may be specified simply as a _config object_
  14587. * as long as the correct `xtype` is specified so that if and when the Component
  14588. * needs rendering, the correct type can be looked up for lazy instantiation.
  14589. *
  14590. * For a list of all available `xtype`, see {@link Ext.Component}.
  14591. */
  14592. Ext.define('Ext.ComponentManager', {
  14593. alternateClassName: 'Ext.ComponentMgr',
  14594. singleton: true,
  14595. constructor: function() {
  14596. var map = {};
  14597. // The sole reason for this is just to support the old code of ComponentQuery
  14598. this.all = {
  14599. map: map,
  14600. getArray: function() {
  14601. var list = [],
  14602. id;
  14603. for (id in map) {
  14604. list.push(map[id]);
  14605. }
  14606. return list;
  14607. }
  14608. };
  14609. this.map = map;
  14610. },
  14611. /**
  14612. * Registers an item to be managed.
  14613. * @param {Object} component The item to register.
  14614. */
  14615. register: function(component) {
  14616. var id = component.getId();
  14617. // <debug>
  14618. if (this.map[id]) {
  14619. Ext.Logger.warn('Registering a component with a id (`' + id + '`) which has already been used. Please ensure the existing component has been destroyed (`Ext.Component#destroy()`.');
  14620. }
  14621. // </debug>
  14622. this.map[component.getId()] = component;
  14623. },
  14624. /**
  14625. * Unregisters an item by removing it from this manager.
  14626. * @param {Object} component The item to unregister.
  14627. */
  14628. unregister: function(component) {
  14629. delete this.map[component.getId()];
  14630. },
  14631. /**
  14632. * Checks if an item type is registered.
  14633. * @param {String} component The mnemonic string by which the class may be looked up.
  14634. * @return {Boolean} Whether the type is registered.
  14635. */
  14636. isRegistered : function(component){
  14637. return this.map[component] !== undefined;
  14638. },
  14639. /**
  14640. * Returns an item by id.
  14641. * For additional details see {@link Ext.util.HashMap#get}.
  14642. * @param {String} id The `id` of the item.
  14643. * @return {Object} The item, or `undefined` if not found.
  14644. */
  14645. get: function(id) {
  14646. return this.map[id];
  14647. },
  14648. /**
  14649. * Creates a new Component from the specified config object using the
  14650. * config object's `xtype` to determine the class to instantiate.
  14651. * @param {Object} config A configuration object for the Component you wish to create.
  14652. * @param {Function} defaultType (optional) The constructor to provide the default Component type if
  14653. * the config object does not contain a `xtype`. (Optional if the config contains an `xtype`).
  14654. * @return {Ext.Component} The newly instantiated Component.
  14655. */
  14656. create: function(component, defaultType) {
  14657. if (component.isComponent) {
  14658. return component;
  14659. }
  14660. else if (Ext.isString(component)) {
  14661. return Ext.createByAlias('widget.' + component);
  14662. }
  14663. else {
  14664. var type = component.xtype || defaultType;
  14665. return Ext.createByAlias('widget.' + type, component);
  14666. }
  14667. },
  14668. registerType: Ext.emptyFn
  14669. });
  14670. //@define Ext.DateExtras
  14671. /**
  14672. * @class Ext.Date
  14673. * @mixins Ext.DateExtras
  14674. * A set of useful static methods to deal with date.
  14675. *
  14676. * __Note:__ Unless you require `Ext.DateExtras`, only the {@link #now} method will be available. You **MUST**
  14677. * require `Ext.DateExtras` to use the other methods available below.
  14678. *
  14679. * Usage with {@link Ext#setup}:
  14680. *
  14681. * @example
  14682. * Ext.setup({
  14683. * requires: 'Ext.DateExtras',
  14684. * onReady: function() {
  14685. * var date = new Date();
  14686. * alert(Ext.Date.format(date, 'n/j/Y'));
  14687. * }
  14688. * });
  14689. *
  14690. * The date parsing and formatting syntax contains a subset of
  14691. * [PHP's `date()` function](http://www.php.net/date), and the formats that are
  14692. * supported will provide results equivalent to their PHP versions.
  14693. *
  14694. * The following is a list of all currently supported formats:
  14695. * <pre>
  14696. Format Description Example returned values
  14697. ------ ----------------------------------------------------------------------- -----------------------
  14698. d Day of the month, 2 digits with leading zeros 01 to 31
  14699. D A short textual representation of the day of the week Mon to Sun
  14700. j Day of the month without leading zeros 1 to 31
  14701. l A full textual representation of the day of the week Sunday to Saturday
  14702. N ISO-8601 numeric representation of the day of the week 1 (for Monday) through 7 (for Sunday)
  14703. S English ordinal suffix for the day of the month, 2 characters st, nd, rd or th. Works well with j
  14704. w Numeric representation of the day of the week 0 (for Sunday) to 6 (for Saturday)
  14705. z The day of the year (starting from 0) 0 to 364 (365 in leap years)
  14706. W ISO-8601 week number of year, weeks starting on Monday 01 to 53
  14707. F A full textual representation of a month, such as January or March January to December
  14708. m Numeric representation of a month, with leading zeros 01 to 12
  14709. M A short textual representation of a month Jan to Dec
  14710. n Numeric representation of a month, without leading zeros 1 to 12
  14711. t Number of days in the given month 28 to 31
  14712. L Whether it&#39;s a leap year 1 if it is a leap year, 0 otherwise.
  14713. o ISO-8601 year number (identical to (Y), but if the ISO week number (W) Examples: 1998 or 2004
  14714. belongs to the previous or next year, that year is used instead)
  14715. Y A full numeric representation of a year, 4 digits Examples: 1999 or 2003
  14716. y A two digit representation of a year Examples: 99 or 03
  14717. a Lowercase Ante meridiem and Post meridiem am or pm
  14718. A Uppercase Ante meridiem and Post meridiem AM or PM
  14719. g 12-hour format of an hour without leading zeros 1 to 12
  14720. G 24-hour format of an hour without leading zeros 0 to 23
  14721. h 12-hour format of an hour with leading zeros 01 to 12
  14722. H 24-hour format of an hour with leading zeros 00 to 23
  14723. i Minutes, with leading zeros 00 to 59
  14724. s Seconds, with leading zeros 00 to 59
  14725. u Decimal fraction of a second Examples:
  14726. (minimum 1 digit, arbitrary number of digits allowed) 001 (i.e. 0.001s) or
  14727. 100 (i.e. 0.100s) or
  14728. 999 (i.e. 0.999s) or
  14729. 999876543210 (i.e. 0.999876543210s)
  14730. O Difference to Greenwich time (GMT) in hours and minutes Example: +1030
  14731. P Difference to Greenwich time (GMT) with colon between hours and minutes Example: -08:00
  14732. T Timezone abbreviation of the machine running the code Examples: EST, MDT, PDT ...
  14733. Z Timezone offset in seconds (negative if west of UTC, positive if east) -43200 to 50400
  14734. c ISO 8601 date
  14735. Notes: Examples:
  14736. 1) If unspecified, the month / day defaults to the current month / day, 1991 or
  14737. the time defaults to midnight, while the timezone defaults to the 1992-10 or
  14738. browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
  14739. and minutes. The "T" delimiter, seconds, milliseconds and timezone 1994-08-19T16:20+01:00 or
  14740. are optional. 1995-07-18T17:21:28-02:00 or
  14741. 2) The decimal fraction of a second, if specified, must contain at 1996-06-17T18:22:29.98765+03:00 or
  14742. least 1 digit (there is no limit to the maximum number 1997-05-16T19:23:30,12345-0400 or
  14743. of digits allowed), and may be delimited by either a '.' or a ',' 1998-04-15T20:24:31.2468Z or
  14744. Refer to the examples on the right for the various levels of 1999-03-14T20:24:32Z or
  14745. date-time granularity which are supported, or see 2000-02-13T21:25:33
  14746. http://www.w3.org/TR/NOTE-datetime for more info. 2001-01-12 22:26:34
  14747. U Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) 1193432466 or -2138434463
  14748. MS Microsoft AJAX serialized dates \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
  14749. \/Date(1238606590509+0800)\/
  14750. </pre>
  14751. *
  14752. * For more information on the ISO 8601 date/time format, see [http://www.w3.org/TR/NOTE-datetime](http://www.w3.org/TR/NOTE-datetime).
  14753. *
  14754. * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
  14755. *
  14756. * // Sample date:
  14757. * // 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
  14758. *
  14759. * var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
  14760. * console.log(Ext.Date.format(dt, 'Y-m-d')); // 2007-01-10
  14761. * console.log(Ext.Date.format(dt, 'F j, Y, g:i a')); // January 10, 2007, 3:05 pm
  14762. * console.log(Ext.Date.format(dt, 'l, \\t\\he jS \\of F Y h:i:s A')); // Wednesday, the 10th of January 2007 03:05:01 PM
  14763. *
  14764. * Here are some standard date/time patterns that you might find helpful. They
  14765. * are not part of the source of Ext.Date, but to use them you can simply copy this
  14766. * block of code into any script that is included after Ext.Date and they will also become
  14767. * globally available on the Date object. Feel free to add or remove patterns as needed in your code.
  14768. *
  14769. * Ext.Date.patterns = {
  14770. * ISO8601Long: "Y-m-d H:i:s",
  14771. * ISO8601Short: "Y-m-d",
  14772. * ShortDate: "n/j/Y",
  14773. * LongDate: "l, F d, Y",
  14774. * FullDateTime: "l, F d, Y g:i:s A",
  14775. * MonthDay: "F d",
  14776. * ShortTime: "g:i A",
  14777. * LongTime: "g:i:s A",
  14778. * SortableDateTime: "Y-m-d\\TH:i:s",
  14779. * UniversalSortableDateTime: "Y-m-d H:i:sO",
  14780. * YearMonth: "F, Y"
  14781. * };
  14782. *
  14783. * Example usage:
  14784. *
  14785. * @example
  14786. * var dt = new Date();
  14787. * Ext.Date.patterns = {
  14788. * ShortDate: "n/j/Y"
  14789. * };
  14790. * alert(Ext.Date.format(dt, Ext.Date.patterns.ShortDate));
  14791. *
  14792. * Developer-written, custom formats may be used by supplying both a formatting and a parsing function
  14793. * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.
  14794. * @singleton
  14795. */
  14796. /*
  14797. * Most of the date-formatting functions below are the excellent work of Baron Schwartz.
  14798. * see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
  14799. * They generate precompiled functions from format patterns instead of parsing and
  14800. * processing each pattern every time a date is formatted. These functions are available
  14801. * on every Date object.
  14802. */
  14803. (function() {
  14804. // create private copy of Ext's Ext.util.Format.format() method
  14805. // - to remove unnecessary dependency
  14806. // - to resolve namespace conflict with MS-Ajax's implementation
  14807. function xf(format) {
  14808. var args = Array.prototype.slice.call(arguments, 1);
  14809. return format.replace(/\{(\d+)\}/g, function(m, i) {
  14810. return args[i];
  14811. });
  14812. }
  14813. /**
  14814. * Extra methods to be mixed into Ext.Date.
  14815. *
  14816. * Require this class to get Ext.Date with all the methods listed below.
  14817. *
  14818. * Using Ext.setup:
  14819. *
  14820. * @example
  14821. * Ext.setup({
  14822. * requires: 'Ext.DateExtras',
  14823. * onReady: function() {
  14824. * var date = new Date();
  14825. * alert(Ext.Date.format(date, 'n/j/Y'));
  14826. * }
  14827. * });
  14828. *
  14829. * Using Ext.application:
  14830. *
  14831. * @example
  14832. * Ext.application({
  14833. * requires: 'Ext.DateExtras',
  14834. * launch: function() {
  14835. * var date = new Date();
  14836. * alert(Ext.Date.format(date, 'n/j/Y'));
  14837. * }
  14838. * });
  14839. *
  14840. * @singleton
  14841. */
  14842. Ext.DateExtras = {
  14843. /**
  14844. * Returns the current timestamp.
  14845. * @return {Number} The current timestamp.
  14846. * @method
  14847. */
  14848. now: Date.now || function() {
  14849. return +new Date();
  14850. },
  14851. /**
  14852. * Returns the number of milliseconds between two dates.
  14853. * @param {Date} dateA The first date.
  14854. * @param {Date} [dateB=new Date()] (optional) The second date, defaults to now.
  14855. * @return {Number} The difference in milliseconds.
  14856. */
  14857. getElapsed: function(dateA, dateB) {
  14858. return Math.abs(dateA - (dateB || new Date()));
  14859. },
  14860. /**
  14861. * Global flag which determines if strict date parsing should be used.
  14862. * Strict date parsing will not roll-over invalid dates, which is the
  14863. * default behavior of JavaScript Date objects.
  14864. * (see {@link #parse} for more information)
  14865. * @type Boolean
  14866. */
  14867. useStrict: false,
  14868. // @private
  14869. formatCodeToRegex: function(character, currentGroup) {
  14870. // Note: currentGroup - position in regex result array (see notes for Ext.Date.parseCodes below)
  14871. var p = utilDate.parseCodes[character];
  14872. if (p) {
  14873. p = typeof p == 'function'? p() : p;
  14874. utilDate.parseCodes[character] = p; // reassign function result to prevent repeated execution
  14875. }
  14876. return p ? Ext.applyIf({
  14877. c: p.c ? xf(p.c, currentGroup || "{0}") : p.c
  14878. }, p) : {
  14879. g: 0,
  14880. c: null,
  14881. s: Ext.String.escapeRegex(character) // treat unrecognized characters as literals
  14882. };
  14883. },
  14884. /**
  14885. * An object hash in which each property is a date parsing function. The property name is the
  14886. * format string which that function parses.
  14887. *
  14888. * This object is automatically populated with date parsing functions as
  14889. * date formats are requested for Ext standard formatting strings.
  14890. *
  14891. * Custom parsing functions may be inserted into this object, keyed by a name which from then on
  14892. * may be used as a format string to {@link #parse}.
  14893. *
  14894. * Example:
  14895. *
  14896. * Ext.Date.parseFunctions['x-date-format'] = myDateParser;
  14897. *
  14898. * A parsing function should return a Date object, and is passed the following parameters:
  14899. *
  14900. * - `date`: {@link String} - The date string to parse.
  14901. * - `strict`: {@link Boolean} - `true` to validate date strings while parsing
  14902. * (i.e. prevent JavaScript Date "rollover"). __The default must be `false`.__
  14903. * Invalid date strings should return `null` when parsed.
  14904. *
  14905. * To enable Dates to also be _formatted_ according to that format, a corresponding
  14906. * formatting function must be placed into the {@link #formatFunctions} property.
  14907. * @property parseFunctions
  14908. * @type Object
  14909. */
  14910. parseFunctions: {
  14911. "MS": function(input, strict) {
  14912. // note: the timezone offset is ignored since the MS Ajax server sends
  14913. // a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
  14914. var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');
  14915. var r = (input || '').match(re);
  14916. return r? new Date(((r[1] || '') + r[2]) * 1) : null;
  14917. }
  14918. },
  14919. parseRegexes: [],
  14920. /**
  14921. * An object hash in which each property is a date formatting function. The property name is the
  14922. * format string which corresponds to the produced formatted date string.
  14923. *
  14924. * This object is automatically populated with date formatting functions as
  14925. * date formats are requested for Ext standard formatting strings.
  14926. *
  14927. * Custom formatting functions may be inserted into this object, keyed by a name which from then on
  14928. * may be used as a format string to {@link #format}.
  14929. *
  14930. * Example:
  14931. *
  14932. * Ext.Date.formatFunctions['x-date-format'] = myDateFormatter;
  14933. *
  14934. * A formatting function should return a string representation of the Date object which is the scope (this) of the function.
  14935. *
  14936. * To enable date strings to also be _parsed_ according to that format, a corresponding
  14937. * parsing function must be placed into the {@link #parseFunctions} property.
  14938. * @property formatFunctions
  14939. * @type Object
  14940. */
  14941. formatFunctions: {
  14942. "MS": function() {
  14943. // UTC milliseconds since Unix epoch (MS-AJAX serialized date format (MRSF))
  14944. return '\\/Date(' + this.getTime() + ')\\/';
  14945. }
  14946. },
  14947. y2kYear : 50,
  14948. /**
  14949. * Date interval constant.
  14950. * @type String
  14951. * @readonly
  14952. */
  14953. MILLI : "ms",
  14954. /**
  14955. * Date interval constant.
  14956. * @type String
  14957. * @readonly
  14958. */
  14959. SECOND : "s",
  14960. /**
  14961. * Date interval constant.
  14962. * @type String
  14963. * @readonly
  14964. */
  14965. MINUTE : "mi",
  14966. /**
  14967. * Date interval constant.
  14968. * @type String
  14969. * @readonly
  14970. */
  14971. HOUR : "h",
  14972. /**
  14973. * Date interval constant.
  14974. * @type String
  14975. * @readonly
  14976. */
  14977. DAY : "d",
  14978. /**
  14979. * Date interval constant.
  14980. * @type String
  14981. * @readonly
  14982. */
  14983. MONTH : "mo",
  14984. /**
  14985. * Date interval constant.
  14986. * @type String
  14987. * @readonly
  14988. */
  14989. YEAR : "y",
  14990. /**
  14991. * An object hash containing default date values used during date parsing.
  14992. *
  14993. * The following properties are available:
  14994. *
  14995. * - `y`: {@link Number} - The default year value. Defaults to `undefined`.
  14996. * - `m`: {@link Number} - The default 1-based month value. Defaults to `undefined`.
  14997. * - `d`: {@link Number} - The default day value. Defaults to `undefined`.
  14998. * - `h`: {@link Number} - The default hour value. Defaults to `undefined`.
  14999. * - `i`: {@link Number} - The default minute value. Defaults to `undefined`.
  15000. * - `s`: {@link Number} - The default second value. Defaults to `undefined`.
  15001. * - `ms`: {@link Number} - The default millisecond value. Defaults to `undefined`.
  15002. *
  15003. * Override these properties to customize the default date values used by the {@link #parse} method.
  15004. *
  15005. * __Note:__ In countries which experience Daylight Saving Time (i.e. DST), the `h`, `i`, `s`
  15006. * and `ms` properties may coincide with the exact time in which DST takes effect.
  15007. * It is the responsibility of the developer to account for this.
  15008. *
  15009. * Example Usage:
  15010. *
  15011. * @example
  15012. * // set default day value to the first day of the month
  15013. * Ext.Date.defaults.d = 1;
  15014. *
  15015. * // parse a February date string containing only year and month values.
  15016. * // setting the default day value to 1 prevents weird date rollover issues.
  15017. * // when attempting to parse the following date string on, for example, March 31st 2009.
  15018. * alert(Ext.Date.parse('2009-02', 'Y-m')); // returns a Date object representing February 1st 2009.
  15019. *
  15020. * @property defaults
  15021. * @type Object
  15022. */
  15023. defaults: {},
  15024. /**
  15025. * An array of textual day names.
  15026. * Override these values for international dates.
  15027. * Example:
  15028. *
  15029. * Ext.Date.dayNames = [
  15030. * 'SundayInYourLang',
  15031. * 'MondayInYourLang'
  15032. * // ...
  15033. * ];
  15034. *
  15035. * @type Array
  15036. */
  15037. dayNames : [
  15038. "Sunday",
  15039. "Monday",
  15040. "Tuesday",
  15041. "Wednesday",
  15042. "Thursday",
  15043. "Friday",
  15044. "Saturday"
  15045. ],
  15046. /**
  15047. * An array of textual month names.
  15048. * Override these values for international dates.
  15049. * Example:
  15050. *
  15051. * Ext.Date.monthNames = [
  15052. * 'JanInYourLang',
  15053. * 'FebInYourLang'
  15054. * // ...
  15055. * ];
  15056. *
  15057. * @type Array
  15058. */
  15059. monthNames : [
  15060. "January",
  15061. "February",
  15062. "March",
  15063. "April",
  15064. "May",
  15065. "June",
  15066. "July",
  15067. "August",
  15068. "September",
  15069. "October",
  15070. "November",
  15071. "December"
  15072. ],
  15073. /**
  15074. * An object hash of zero-based JavaScript month numbers (with short month names as keys).
  15075. *
  15076. * __Note:__ keys are case-sensitive.
  15077. *
  15078. * Override these values for international dates.
  15079. * Example:
  15080. *
  15081. * Ext.Date.monthNumbers = {
  15082. * 'ShortJanNameInYourLang': 0,
  15083. * 'ShortFebNameInYourLang': 1
  15084. * // ...
  15085. * };
  15086. *
  15087. * @type Object
  15088. */
  15089. monthNumbers : {
  15090. Jan:0,
  15091. Feb:1,
  15092. Mar:2,
  15093. Apr:3,
  15094. May:4,
  15095. Jun:5,
  15096. Jul:6,
  15097. Aug:7,
  15098. Sep:8,
  15099. Oct:9,
  15100. Nov:10,
  15101. Dec:11
  15102. },
  15103. /**
  15104. * The date format string that the {@link Ext.util.Format#date} function uses.
  15105. * See {@link Ext.Date} for details.
  15106. *
  15107. * This defaults to `m/d/Y`, but may be overridden in a locale file.
  15108. * @property defaultFormat
  15109. * @type String
  15110. */
  15111. defaultFormat : "m/d/Y",
  15112. /**
  15113. * Get the short month name for the given month number.
  15114. * Override this function for international dates.
  15115. * @param {Number} month A zero-based JavaScript month number.
  15116. * @return {String} The short month name.
  15117. */
  15118. getShortMonthName : function(month) {
  15119. return utilDate.monthNames[month].substring(0, 3);
  15120. },
  15121. /**
  15122. * Get the short day name for the given day number.
  15123. * Override this function for international dates.
  15124. * @param {Number} day A zero-based JavaScript day number.
  15125. * @return {String} The short day name.
  15126. */
  15127. getShortDayName : function(day) {
  15128. return utilDate.dayNames[day].substring(0, 3);
  15129. },
  15130. /**
  15131. * Get the zero-based JavaScript month number for the given short/full month name.
  15132. * Override this function for international dates.
  15133. * @param {String} name The short/full month name.
  15134. * @return {Number} The zero-based JavaScript month number.
  15135. */
  15136. getMonthNumber : function(name) {
  15137. // handle camel casing for English month names (since the keys for the Ext.Date.monthNumbers hash are case sensitive)
  15138. return utilDate.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
  15139. },
  15140. /**
  15141. * The base format-code to formatting-function hashmap used by the {@link #format} method.
  15142. * Formatting functions are strings (or functions which return strings) which
  15143. * will return the appropriate value when evaluated in the context of the Date object
  15144. * from which the {@link #format} method is called.
  15145. * Add to / override these mappings for custom date formatting.
  15146. *
  15147. * __Note:__ `Ext.Date.format()` treats characters as literals if an appropriate mapping cannot be found.
  15148. *
  15149. * Example:
  15150. *
  15151. * @example
  15152. * Ext.Date.formatCodes.x = "Ext.util.Format.leftPad(this.getDate(), 2, '0')";
  15153. * alert(Ext.Date.format(new Date(), 'x')); // returns the current day of the month
  15154. *
  15155. * @type Object
  15156. */
  15157. formatCodes : {
  15158. d: "Ext.String.leftPad(this.getDate(), 2, '0')",
  15159. D: "Ext.Date.getShortDayName(this.getDay())", // get localized short day name
  15160. j: "this.getDate()",
  15161. l: "Ext.Date.dayNames[this.getDay()]",
  15162. N: "(this.getDay() ? this.getDay() : 7)",
  15163. S: "Ext.Date.getSuffix(this)",
  15164. w: "this.getDay()",
  15165. z: "Ext.Date.getDayOfYear(this)",
  15166. W: "Ext.String.leftPad(Ext.Date.getWeekOfYear(this), 2, '0')",
  15167. F: "Ext.Date.monthNames[this.getMonth()]",
  15168. m: "Ext.String.leftPad(this.getMonth() + 1, 2, '0')",
  15169. M: "Ext.Date.getShortMonthName(this.getMonth())", // get localized short month name
  15170. n: "(this.getMonth() + 1)",
  15171. t: "Ext.Date.getDaysInMonth(this)",
  15172. L: "(Ext.Date.isLeapYear(this) ? 1 : 0)",
  15173. o: "(this.getFullYear() + (Ext.Date.getWeekOfYear(this) == 1 && this.getMonth() > 0 ? +1 : (Ext.Date.getWeekOfYear(this) >= 52 && this.getMonth() < 11 ? -1 : 0)))",
  15174. Y: "Ext.String.leftPad(this.getFullYear(), 4, '0')",
  15175. y: "('' + this.getFullYear()).substring(2, 4)",
  15176. a: "(this.getHours() < 12 ? 'am' : 'pm')",
  15177. A: "(this.getHours() < 12 ? 'AM' : 'PM')",
  15178. g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
  15179. G: "this.getHours()",
  15180. h: "Ext.String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
  15181. H: "Ext.String.leftPad(this.getHours(), 2, '0')",
  15182. i: "Ext.String.leftPad(this.getMinutes(), 2, '0')",
  15183. s: "Ext.String.leftPad(this.getSeconds(), 2, '0')",
  15184. u: "Ext.String.leftPad(this.getMilliseconds(), 3, '0')",
  15185. O: "Ext.Date.getGMTOffset(this)",
  15186. P: "Ext.Date.getGMTOffset(this, true)",
  15187. T: "Ext.Date.getTimezone(this)",
  15188. Z: "(this.getTimezoneOffset() * -60)",
  15189. c: function() { // ISO-8601 -- GMT format
  15190. for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
  15191. var e = c.charAt(i);
  15192. code.push(e == "T" ? "'T'" : utilDate.getFormatCode(e)); // treat T as a character literal
  15193. }
  15194. return code.join(" + ");
  15195. },
  15196. /*
  15197. c: function() { // ISO-8601 -- UTC format
  15198. return [
  15199. "this.getUTCFullYear()", "'-'",
  15200. "Ext.util.Format.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
  15201. "Ext.util.Format.leftPad(this.getUTCDate(), 2, '0')",
  15202. "'T'",
  15203. "Ext.util.Format.leftPad(this.getUTCHours(), 2, '0')", "':'",
  15204. "Ext.util.Format.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
  15205. "Ext.util.Format.leftPad(this.getUTCSeconds(), 2, '0')",
  15206. "'Z'"
  15207. ].join(" + ");
  15208. },
  15209. */
  15210. U: "Math.round(this.getTime() / 1000)"
  15211. },
  15212. /**
  15213. * Checks if the passed Date parameters will cause a JavaScript Date "rollover".
  15214. * @param {Number} year 4-digit year.
  15215. * @param {Number} month 1-based month-of-year.
  15216. * @param {Number} day Day of month.
  15217. * @param {Number} hour (optional) Hour.
  15218. * @param {Number} minute (optional) Minute.
  15219. * @param {Number} second (optional) Second.
  15220. * @param {Number} millisecond (optional) Millisecond.
  15221. * @return {Boolean} `true` if the passed parameters do not cause a Date "rollover", `false` otherwise.
  15222. */
  15223. isValid : function(y, m, d, h, i, s, ms) {
  15224. // setup defaults
  15225. h = h || 0;
  15226. i = i || 0;
  15227. s = s || 0;
  15228. ms = ms || 0;
  15229. // Special handling for year < 100
  15230. var dt = utilDate.add(new Date(y < 100 ? 100 : y, m - 1, d, h, i, s, ms), utilDate.YEAR, y < 100 ? y - 100 : 0);
  15231. return y == dt.getFullYear() &&
  15232. m == dt.getMonth() + 1 &&
  15233. d == dt.getDate() &&
  15234. h == dt.getHours() &&
  15235. i == dt.getMinutes() &&
  15236. s == dt.getSeconds() &&
  15237. ms == dt.getMilliseconds();
  15238. },
  15239. /**
  15240. * Parses the passed string using the specified date format.
  15241. * Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
  15242. * The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
  15243. * which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
  15244. * the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
  15245. * Keep in mind that the input date string must precisely match the specified format string
  15246. * in order for the parse operation to be successful (failed parse operations return a `null` value).
  15247. *
  15248. * Example:
  15249. *
  15250. * // dt = Fri May 25 2007 (current date)
  15251. * var dt = new Date();
  15252. *
  15253. * // dt = Thu May 25 2006 (today's month/day in 2006)
  15254. * dt = Ext.Date.parse("2006", "Y");
  15255. *
  15256. * // dt = Sun Jan 15 2006 (all date parts specified)
  15257. * dt = Ext.Date.parse("2006-01-15", "Y-m-d");
  15258. *
  15259. * // dt = Sun Jan 15 2006 15:20:01
  15260. * dt = Ext.Date.parse("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
  15261. *
  15262. * // attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
  15263. * dt = Ext.Date.parse("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // null
  15264. *
  15265. * @param {String} input The raw date string.
  15266. * @param {String} format The expected date string format.
  15267. * @param {Boolean} [strict=false] (optional) `true` to validate date strings while parsing (i.e. prevents JavaScript Date "rollover").
  15268. * Invalid date strings will return `null` when parsed.
  15269. * @return {Date/null} The parsed Date, or `null` if an invalid date string.
  15270. */
  15271. parse : function(input, format, strict) {
  15272. var p = utilDate.parseFunctions;
  15273. if (p[format] == null) {
  15274. utilDate.createParser(format);
  15275. }
  15276. return p[format](input, Ext.isDefined(strict) ? strict : utilDate.useStrict);
  15277. },
  15278. // Backwards compat
  15279. parseDate: function(input, format, strict){
  15280. return utilDate.parse(input, format, strict);
  15281. },
  15282. // @private
  15283. getFormatCode : function(character) {
  15284. var f = utilDate.formatCodes[character];
  15285. if (f) {
  15286. f = typeof f == 'function'? f() : f;
  15287. utilDate.formatCodes[character] = f; // reassign function result to prevent repeated execution
  15288. }
  15289. // note: unknown characters are treated as literals
  15290. return f || ("'" + Ext.String.escape(character) + "'");
  15291. },
  15292. // @private
  15293. createFormat : function(format) {
  15294. var code = [],
  15295. special = false,
  15296. ch = '';
  15297. for (var i = 0; i < format.length; ++i) {
  15298. ch = format.charAt(i);
  15299. if (!special && ch == "\\") {
  15300. special = true;
  15301. } else if (special) {
  15302. special = false;
  15303. code.push("'" + Ext.String.escape(ch) + "'");
  15304. } else if (ch == '\n') {
  15305. code.push(Ext.JSON.encode(ch));
  15306. } else {
  15307. code.push(utilDate.getFormatCode(ch));
  15308. }
  15309. }
  15310. utilDate.formatFunctions[format] = Ext.functionFactory("return " + code.join('+'));
  15311. },
  15312. // @private
  15313. createParser : (function() {
  15314. var code = [
  15315. "var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
  15316. "def = Ext.Date.defaults,",
  15317. "results = String(input).match(Ext.Date.parseRegexes[{0}]);", // either null, or an array of matched strings
  15318. "if(results){",
  15319. "{1}",
  15320. "if(u != null){", // i.e. unix time is defined
  15321. "v = new Date(u * 1000);", // give top priority to UNIX time
  15322. "}else{",
  15323. // create Date object representing midnight of the current day;
  15324. // this will provide us with our date defaults
  15325. // (note: clearTime() handles Daylight Saving Time automatically)
  15326. "dt = Ext.Date.clearTime(new Date);",
  15327. // date calculations (note: these calculations create a dependency on Ext.Number.from())
  15328. "y = Ext.Number.from(y, Ext.Number.from(def.y, dt.getFullYear()));",
  15329. "m = Ext.Number.from(m, Ext.Number.from(def.m - 1, dt.getMonth()));",
  15330. "d = Ext.Number.from(d, Ext.Number.from(def.d, dt.getDate()));",
  15331. // time calculations (note: these calculations create a dependency on Ext.Number.from())
  15332. "h = Ext.Number.from(h, Ext.Number.from(def.h, dt.getHours()));",
  15333. "i = Ext.Number.from(i, Ext.Number.from(def.i, dt.getMinutes()));",
  15334. "s = Ext.Number.from(s, Ext.Number.from(def.s, dt.getSeconds()));",
  15335. "ms = Ext.Number.from(ms, Ext.Number.from(def.ms, dt.getMilliseconds()));",
  15336. "if(z >= 0 && y >= 0){",
  15337. // both the year and zero-based day of year are defined and >= 0.
  15338. // these 2 values alone provide sufficient info to create a full date object
  15339. // create Date object representing January 1st for the given year
  15340. // handle years < 100 appropriately
  15341. "v = Ext.Date.add(new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
  15342. // then add day of year, checking for Date "rollover" if necessary
  15343. "v = !strict? v : (strict === true && (z <= 364 || (Ext.Date.isLeapYear(v) && z <= 365))? Ext.Date.add(v, Ext.Date.DAY, z) : null);",
  15344. "}else if(strict === true && !Ext.Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
  15345. "v = null;", // invalid date, so return null
  15346. "}else{",
  15347. // plain old Date object
  15348. // handle years < 100 properly
  15349. "v = Ext.Date.add(new Date(y < 100 ? 100 : y, m, d, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
  15350. "}",
  15351. "}",
  15352. "}",
  15353. "if(v){",
  15354. // favor UTC offset over GMT offset
  15355. "if(zz != null){",
  15356. // reset to UTC, then add offset
  15357. "v = Ext.Date.add(v, Ext.Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
  15358. "}else if(o){",
  15359. // reset to GMT, then add offset
  15360. "v = Ext.Date.add(v, Ext.Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
  15361. "}",
  15362. "}",
  15363. "return v;"
  15364. ].join('\n');
  15365. return function(format) {
  15366. var regexNum = utilDate.parseRegexes.length,
  15367. currentGroup = 1,
  15368. calc = [],
  15369. regex = [],
  15370. special = false,
  15371. ch = "";
  15372. for (var i = 0; i < format.length; ++i) {
  15373. ch = format.charAt(i);
  15374. if (!special && ch == "\\") {
  15375. special = true;
  15376. } else if (special) {
  15377. special = false;
  15378. regex.push(Ext.String.escape(ch));
  15379. } else {
  15380. var obj = utilDate.formatCodeToRegex(ch, currentGroup);
  15381. currentGroup += obj.g;
  15382. regex.push(obj.s);
  15383. if (obj.g && obj.c) {
  15384. calc.push(obj.c);
  15385. }
  15386. }
  15387. }
  15388. utilDate.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
  15389. utilDate.parseFunctions[format] = Ext.functionFactory("input", "strict", xf(code, regexNum, calc.join('')));
  15390. };
  15391. })(),
  15392. // @private
  15393. parseCodes : {
  15394. /*
  15395. * Notes:
  15396. * g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
  15397. * c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
  15398. * s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
  15399. */
  15400. d: {
  15401. g:1,
  15402. c:"d = parseInt(results[{0}], 10);\n",
  15403. s:"(\\d{2})" // day of month with leading zeros (01 - 31)
  15404. },
  15405. j: {
  15406. g:1,
  15407. c:"d = parseInt(results[{0}], 10);\n",
  15408. s:"(\\d{1,2})" // day of month without leading zeros (1 - 31)
  15409. },
  15410. D: function() {
  15411. for (var a = [], i = 0; i < 7; a.push(utilDate.getShortDayName(i)), ++i); // get localized short day names
  15412. return {
  15413. g:0,
  15414. c:null,
  15415. s:"(?:" + a.join("|") +")"
  15416. };
  15417. },
  15418. l: function() {
  15419. return {
  15420. g:0,
  15421. c:null,
  15422. s:"(?:" + utilDate.dayNames.join("|") + ")"
  15423. };
  15424. },
  15425. N: {
  15426. g:0,
  15427. c:null,
  15428. s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
  15429. },
  15430. S: {
  15431. g:0,
  15432. c:null,
  15433. s:"(?:st|nd|rd|th)"
  15434. },
  15435. w: {
  15436. g:0,
  15437. c:null,
  15438. s:"[0-6]" // JavaScript day number (0 (sunday) - 6 (saturday))
  15439. },
  15440. z: {
  15441. g:1,
  15442. c:"z = parseInt(results[{0}], 10);\n",
  15443. s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
  15444. },
  15445. W: {
  15446. g:0,
  15447. c:null,
  15448. s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
  15449. },
  15450. F: function() {
  15451. return {
  15452. g:1,
  15453. c:"m = parseInt(Ext.Date.getMonthNumber(results[{0}]), 10);\n", // get localized month number
  15454. s:"(" + utilDate.monthNames.join("|") + ")"
  15455. };
  15456. },
  15457. M: function() {
  15458. for (var a = [], i = 0; i < 12; a.push(utilDate.getShortMonthName(i)), ++i); // get localized short month names
  15459. return Ext.applyIf({
  15460. s:"(" + a.join("|") + ")"
  15461. }, utilDate.formatCodeToRegex("F"));
  15462. },
  15463. m: {
  15464. g:1,
  15465. c:"m = parseInt(results[{0}], 10) - 1;\n",
  15466. s:"(\\d{2})" // month number with leading zeros (01 - 12)
  15467. },
  15468. n: {
  15469. g:1,
  15470. c:"m = parseInt(results[{0}], 10) - 1;\n",
  15471. s:"(\\d{1,2})" // month number without leading zeros (1 - 12)
  15472. },
  15473. t: {
  15474. g:0,
  15475. c:null,
  15476. s:"(?:\\d{2})" // no. of days in the month (28 - 31)
  15477. },
  15478. L: {
  15479. g:0,
  15480. c:null,
  15481. s:"(?:1|0)"
  15482. },
  15483. o: function() {
  15484. return utilDate.formatCodeToRegex("Y");
  15485. },
  15486. Y: {
  15487. g:1,
  15488. c:"y = parseInt(results[{0}], 10);\n",
  15489. s:"(\\d{4})" // 4-digit year
  15490. },
  15491. y: {
  15492. g:1,
  15493. c:"var ty = parseInt(results[{0}], 10);\n"
  15494. + "y = ty > Ext.Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
  15495. s:"(\\d{1,2})"
  15496. },
  15497. /*
  15498. * In the am/pm parsing routines, we allow both upper and lower case
  15499. * even though it doesn't exactly match the spec. It gives much more flexibility
  15500. * in being able to specify case insensitive regexes.
  15501. */
  15502. a: {
  15503. g:1,
  15504. c:"if (/(am)/i.test(results[{0}])) {\n"
  15505. + "if (!h || h == 12) { h = 0; }\n"
  15506. + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
  15507. s:"(am|pm|AM|PM)"
  15508. },
  15509. A: {
  15510. g:1,
  15511. c:"if (/(am)/i.test(results[{0}])) {\n"
  15512. + "if (!h || h == 12) { h = 0; }\n"
  15513. + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
  15514. s:"(AM|PM|am|pm)"
  15515. },
  15516. g: function() {
  15517. return utilDate.formatCodeToRegex("G");
  15518. },
  15519. G: {
  15520. g:1,
  15521. c:"h = parseInt(results[{0}], 10);\n",
  15522. s:"(\\d{1,2})" // 24-hr format of an hour without leading zeros (0 - 23)
  15523. },
  15524. h: function() {
  15525. return utilDate.formatCodeToRegex("H");
  15526. },
  15527. H: {
  15528. g:1,
  15529. c:"h = parseInt(results[{0}], 10);\n",
  15530. s:"(\\d{2})" // 24-hr format of an hour with leading zeros (00 - 23)
  15531. },
  15532. i: {
  15533. g:1,
  15534. c:"i = parseInt(results[{0}], 10);\n",
  15535. s:"(\\d{2})" // minutes with leading zeros (00 - 59)
  15536. },
  15537. s: {
  15538. g:1,
  15539. c:"s = parseInt(results[{0}], 10);\n",
  15540. s:"(\\d{2})" // seconds with leading zeros (00 - 59)
  15541. },
  15542. u: {
  15543. g:1,
  15544. c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
  15545. s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
  15546. },
  15547. O: {
  15548. g:1,
  15549. c:[
  15550. "o = results[{0}];",
  15551. "var sn = o.substring(0,1),", // get + / - sign
  15552. "hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
  15553. "mn = o.substring(3,5) % 60;", // get minutes
  15554. "o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + Ext.String.leftPad(hr, 2, '0') + Ext.String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
  15555. ].join("\n"),
  15556. s: "([+\-]\\d{4})" // GMT offset in hrs and mins
  15557. },
  15558. P: {
  15559. g:1,
  15560. c:[
  15561. "o = results[{0}];",
  15562. "var sn = o.substring(0,1),", // get + / - sign
  15563. "hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
  15564. "mn = o.substring(4,6) % 60;", // get minutes
  15565. "o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + Ext.String.leftPad(hr, 2, '0') + Ext.String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
  15566. ].join("\n"),
  15567. s: "([+\-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
  15568. },
  15569. T: {
  15570. g:0,
  15571. c:null,
  15572. s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
  15573. },
  15574. Z: {
  15575. g:1,
  15576. c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
  15577. + "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
  15578. s:"([+\-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
  15579. },
  15580. c: function() {
  15581. var calc = [],
  15582. arr = [
  15583. utilDate.formatCodeToRegex("Y", 1), // year
  15584. utilDate.formatCodeToRegex("m", 2), // month
  15585. utilDate.formatCodeToRegex("d", 3), // day
  15586. utilDate.formatCodeToRegex("h", 4), // hour
  15587. utilDate.formatCodeToRegex("i", 5), // minute
  15588. utilDate.formatCodeToRegex("s", 6), // second
  15589. {c:"ms = results[7] || '0'; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n"}, // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
  15590. {c:[ // allow either "Z" (i.e. UTC) or "-0530" or "+08:00" (i.e. UTC offset) timezone delimiters. assumes local timezone if no timezone is specified
  15591. "if(results[8]) {", // timezone specified
  15592. "if(results[8] == 'Z'){",
  15593. "zz = 0;", // UTC
  15594. "}else if (results[8].indexOf(':') > -1){",
  15595. utilDate.formatCodeToRegex("P", 8).c, // timezone offset with colon separator
  15596. "}else{",
  15597. utilDate.formatCodeToRegex("O", 8).c, // timezone offset without colon separator
  15598. "}",
  15599. "}"
  15600. ].join('\n')}
  15601. ];
  15602. for (var i = 0, l = arr.length; i < l; ++i) {
  15603. calc.push(arr[i].c);
  15604. }
  15605. return {
  15606. g:1,
  15607. c:calc.join(""),
  15608. s:[
  15609. arr[0].s, // year (required)
  15610. "(?:", "-", arr[1].s, // month (optional)
  15611. "(?:", "-", arr[2].s, // day (optional)
  15612. "(?:",
  15613. "(?:T| )?", // time delimiter -- either a "T" or a single blank space
  15614. arr[3].s, ":", arr[4].s, // hour AND minute, delimited by a single colon (optional). MUST be preceded by either a "T" or a single blank space
  15615. "(?::", arr[5].s, ")?", // seconds (optional)
  15616. "(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
  15617. "(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
  15618. ")?",
  15619. ")?",
  15620. ")?"
  15621. ].join("")
  15622. };
  15623. },
  15624. U: {
  15625. g:1,
  15626. c:"u = parseInt(results[{0}], 10);\n",
  15627. s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
  15628. }
  15629. },
  15630. // Old Ext.Date prototype methods.
  15631. // @private
  15632. dateFormat: function(date, format) {
  15633. return utilDate.format(date, format);
  15634. },
  15635. /**
  15636. * Formats a date given the supplied format string.
  15637. * @param {Date} date The date to format.
  15638. * @param {String} format The format string.
  15639. * @return {String} The formatted date.
  15640. */
  15641. format: function(date, format) {
  15642. if (utilDate.formatFunctions[format] == null) {
  15643. utilDate.createFormat(format);
  15644. }
  15645. var result = utilDate.formatFunctions[format].call(date);
  15646. return result + '';
  15647. },
  15648. /**
  15649. * Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
  15650. *
  15651. * __Note:__ The date string returned by the JavaScript Date object's `toString()` method varies
  15652. * between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
  15653. * For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
  15654. * `getTimezone()` first tries to get the timezone abbreviation from between a pair of parentheses
  15655. * (which may or may not be present), failing which it proceeds to get the timezone abbreviation
  15656. * from the GMT offset portion of the date string.
  15657. *
  15658. * @example
  15659. * var dt = new Date('9/17/2011');
  15660. * alert(Ext.Date.getTimezone(dt));
  15661. *
  15662. * @param {Date} date The date.
  15663. * @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
  15664. */
  15665. getTimezone : function(date) {
  15666. // the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
  15667. //
  15668. // Opera : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
  15669. // Safari : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone (same as FF)
  15670. // FF : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
  15671. // IE : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
  15672. // IE : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
  15673. //
  15674. // this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
  15675. // step 1: (?:\((.*)\) -- find timezone in parentheses
  15676. // step 2: ([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?) -- if nothing was found in step 1, find timezone from timezone offset portion of date string
  15677. // step 3: remove all non uppercase characters found in step 1 and 2
  15678. return date.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
  15679. },
  15680. /**
  15681. * Get the offset from GMT of the current date (equivalent to the format specifier 'O').
  15682. *
  15683. * @example
  15684. * var dt = new Date('9/17/2011');
  15685. * alert(Ext.Date.getGMTOffset(dt));
  15686. *
  15687. * @param {Date} date The date.
  15688. * @param {Boolean} [colon=false] (optional) `true` to separate the hours and minutes with a colon.
  15689. * @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
  15690. */
  15691. getGMTOffset : function(date, colon) {
  15692. var offset = date.getTimezoneOffset();
  15693. return (offset > 0 ? "-" : "+")
  15694. + Ext.String.leftPad(Math.floor(Math.abs(offset) / 60), 2, "0")
  15695. + (colon ? ":" : "")
  15696. + Ext.String.leftPad(Math.abs(offset % 60), 2, "0");
  15697. },
  15698. /**
  15699. * Get the numeric day number of the year, adjusted for leap year.
  15700. *
  15701. * @example
  15702. * var dt = new Date('9/17/2011');
  15703. * alert(Ext.Date.getDayOfYear(dt)); // 259
  15704. *
  15705. * @param {Date} date The date.
  15706. * @return {Number} 0 to 364 (365 in leap years).
  15707. */
  15708. getDayOfYear: function(date) {
  15709. var num = 0,
  15710. d = Ext.Date.clone(date),
  15711. m = date.getMonth(),
  15712. i;
  15713. for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
  15714. num += utilDate.getDaysInMonth(d);
  15715. }
  15716. return num + date.getDate() - 1;
  15717. },
  15718. /**
  15719. * Get the numeric ISO-8601 week number of the year
  15720. * (equivalent to the format specifier 'W', but without a leading zero).
  15721. *
  15722. * @example
  15723. * var dt = new Date('9/17/2011');
  15724. * alert(Ext.Date.getWeekOfYear(dt)); // 37
  15725. *
  15726. * @param {Date} date The date.
  15727. * @return {Number} 1 to 53.
  15728. * @method
  15729. */
  15730. getWeekOfYear : (function() {
  15731. // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
  15732. var ms1d = 864e5, // milliseconds in a day
  15733. ms7d = 7 * ms1d; // milliseconds in a week
  15734. return function(date) { // return a closure so constants get calculated only once
  15735. var DC3 = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate() + 3) / ms1d, // an Absolute Day Number
  15736. AWN = Math.floor(DC3 / 7), // an Absolute Week Number
  15737. Wyr = new Date(AWN * ms7d).getUTCFullYear();
  15738. return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
  15739. };
  15740. })(),
  15741. /**
  15742. * Checks if the current date falls within a leap year.
  15743. *
  15744. * @example
  15745. * var dt = new Date('1/10/2011');
  15746. * alert(Ext.Date.isLeapYear(dt)); // false
  15747. *
  15748. * @param {Date} date The date.
  15749. * @return {Boolean} `true` if the current date falls within a leap year, `false` otherwise.
  15750. */
  15751. isLeapYear : function(date) {
  15752. var year = date.getFullYear();
  15753. return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
  15754. },
  15755. /**
  15756. * Get the first day of the current month, adjusted for leap year. The returned value
  15757. * is the numeric day index within the week (0-6) which can be used in conjunction with
  15758. * the {@link #monthNames} array to retrieve the textual day name.
  15759. *
  15760. * @example
  15761. * var dt = new Date('1/10/2007'),
  15762. * firstDay = Ext.Date.getFirstDayOfMonth(dt);
  15763. * alert(Ext.Date.dayNames[firstDay]); // 'Monday'
  15764. *
  15765. * @param {Date} date The date
  15766. * @return {Number} The day number (0-6).
  15767. */
  15768. getFirstDayOfMonth : function(date) {
  15769. var day = (date.getDay() - (date.getDate() - 1)) % 7;
  15770. return (day < 0) ? (day + 7) : day;
  15771. },
  15772. /**
  15773. * Get the last day of the current month, adjusted for leap year. The returned value
  15774. * is the numeric day index within the week (0-6) which can be used in conjunction with
  15775. * the {@link #monthNames} array to retrieve the textual day name.
  15776. *
  15777. * @example
  15778. * var dt = new Date('1/10/2007'),
  15779. * lastDay = Ext.Date.getLastDayOfMonth(dt);
  15780. * alert(Ext.Date.dayNames[lastDay]); // 'Wednesday'
  15781. *
  15782. * @param {Date} date The date.
  15783. * @return {Number} The day number (0-6).
  15784. */
  15785. getLastDayOfMonth : function(date) {
  15786. return utilDate.getLastDateOfMonth(date).getDay();
  15787. },
  15788. /**
  15789. * Get the date of the first day of the month in which this date resides.
  15790. *
  15791. * @example
  15792. * var dt = new Date('1/10/2007'),
  15793. * lastDate = Ext.Date.getFirstDateOfMonth(dt);
  15794. * alert(lastDate); // Mon Jan 01 2007 00:00:00 GMT-0800 (PST)
  15795. *
  15796. * @param {Date} date The date.
  15797. * @return {Date}
  15798. */
  15799. getFirstDateOfMonth : function(date) {
  15800. return new Date(date.getFullYear(), date.getMonth(), 1);
  15801. },
  15802. /**
  15803. * Get the date of the last day of the month in which this date resides.
  15804. *
  15805. * @example
  15806. * var dt = new Date('1/10/2007'),
  15807. * lastDate = Ext.Date.getLastDateOfMonth(dt);
  15808. * alert(lastDate); // Wed Jan 31 2007 00:00:00 GMT-0800 (PST)
  15809. *
  15810. * @param {Date} date The date.
  15811. * @return {Date}
  15812. */
  15813. getLastDateOfMonth : function(date) {
  15814. return new Date(date.getFullYear(), date.getMonth(), utilDate.getDaysInMonth(date));
  15815. },
  15816. /**
  15817. * Get the number of days in the current month, adjusted for leap year.
  15818. *
  15819. * @example
  15820. * var dt = new Date('1/10/2007');
  15821. * alert(Ext.Date.getDaysInMonth(dt)); // 31
  15822. *
  15823. * @param {Date} date The date.
  15824. * @return {Number} The number of days in the month.
  15825. * @method
  15826. */
  15827. getDaysInMonth: (function() {
  15828. var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  15829. return function(date) { // return a closure for efficiency
  15830. var m = date.getMonth();
  15831. return m == 1 && utilDate.isLeapYear(date) ? 29 : daysInMonth[m];
  15832. };
  15833. })(),
  15834. /**
  15835. * Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
  15836. *
  15837. * @example
  15838. * var dt = new Date('9/17/2011');
  15839. * alert(Ext.Date.getSuffix(dt)); // 'th'
  15840. *
  15841. * @param {Date} date The date.
  15842. * @return {String} 'st', 'nd', 'rd' or 'th'.
  15843. */
  15844. getSuffix : function(date) {
  15845. switch (date.getDate()) {
  15846. case 1:
  15847. case 21:
  15848. case 31:
  15849. return "st";
  15850. case 2:
  15851. case 22:
  15852. return "nd";
  15853. case 3:
  15854. case 23:
  15855. return "rd";
  15856. default:
  15857. return "th";
  15858. }
  15859. },
  15860. /**
  15861. * Creates and returns a new Date instance with the exact same date value as the called instance.
  15862. * Dates are copied and passed by reference, so if a copied date variable is modified later, the original
  15863. * variable will also be changed. When the intention is to create a new variable that will not
  15864. * modify the original instance, you should create a clone.
  15865. *
  15866. * Example of correctly cloning a date:
  15867. *
  15868. * // wrong way:
  15869. * var orig = new Date('10/1/2006');
  15870. * var copy = orig;
  15871. * copy.setDate(5);
  15872. * console.log(orig); // returns 'Thu Oct 05 2006'!
  15873. *
  15874. * // correct way:
  15875. * var orig = new Date('10/1/2006'),
  15876. * copy = Ext.Date.clone(orig);
  15877. * copy.setDate(5);
  15878. * console.log(orig); // returns 'Thu Oct 01 2006'
  15879. *
  15880. * @param {Date} date The date.
  15881. * @return {Date} The new Date instance.
  15882. */
  15883. clone : function(date) {
  15884. return new Date(date.getTime());
  15885. },
  15886. /**
  15887. * Checks if the current date is affected by Daylight Saving Time (DST).
  15888. *
  15889. * @example
  15890. * var dt = new Date('9/17/2011');
  15891. * alert(Ext.Date.isDST(dt));
  15892. *
  15893. * @param {Date} date The date.
  15894. * @return {Boolean} `true` if the current date is affected by DST.
  15895. */
  15896. isDST : function(date) {
  15897. // adapted from http://sencha.com/forum/showthread.php?p=247172#post247172
  15898. // courtesy of @geoffrey.mcgill
  15899. return new Date(date.getFullYear(), 0, 1).getTimezoneOffset() != date.getTimezoneOffset();
  15900. },
  15901. /**
  15902. * Attempts to clear all time information from this Date by setting the time to midnight of the same day,
  15903. * automatically adjusting for Daylight Saving Time (DST) where applicable.
  15904. *
  15905. * __Note:__ DST timezone information for the browser's host operating system is assumed to be up-to-date.
  15906. *
  15907. * @param {Date} date The date.
  15908. * @param {Boolean} [clone=false] `true` to create a clone of this date, clear the time and return it.
  15909. * @return {Date} this or the clone.
  15910. */
  15911. clearTime : function(date, clone) {
  15912. if (clone) {
  15913. return Ext.Date.clearTime(Ext.Date.clone(date));
  15914. }
  15915. // get current date before clearing time
  15916. var d = date.getDate();
  15917. // clear time
  15918. date.setHours(0);
  15919. date.setMinutes(0);
  15920. date.setSeconds(0);
  15921. date.setMilliseconds(0);
  15922. if (date.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
  15923. // note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
  15924. // refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
  15925. // increment hour until cloned date == current date
  15926. for (var hr = 1, c = utilDate.add(date, Ext.Date.HOUR, hr); c.getDate() != d; hr++, c = utilDate.add(date, Ext.Date.HOUR, hr));
  15927. date.setDate(d);
  15928. date.setHours(c.getHours());
  15929. }
  15930. return date;
  15931. },
  15932. /**
  15933. * Provides a convenient method for performing basic date arithmetic. This method
  15934. * does not modify the Date instance being called - it creates and returns
  15935. * a new Date instance containing the resulting date value.
  15936. *
  15937. * @example
  15938. * // Basic usage:
  15939. * var dt = Ext.Date.add(new Date('10/29/2006'), Ext.Date.DAY, 5);
  15940. * alert(dt); // 'Fri Nov 03 2006 00:00:00'
  15941. *
  15942. * You can also subtract date values by passing a negative value:
  15943. *
  15944. * @example
  15945. * // Negative values will be subtracted:
  15946. * var dt2 = Ext.Date.add(new Date('10/1/2006'), Ext.Date.DAY, -5);
  15947. * alert(dt2); // 'Tue Sep 26 2006 00:00:00'
  15948. *
  15949. * @param {Date} date The date to modify.
  15950. * @param {String} interval A valid date interval enum value.
  15951. * @param {Number} value The amount to add to the current date.
  15952. * @return {Date} The new Date instance.
  15953. */
  15954. add : function(date, interval, value) {
  15955. var d = Ext.Date.clone(date);
  15956. if (!interval || value === 0) return d;
  15957. switch(interval.toLowerCase()) {
  15958. case Ext.Date.MILLI:
  15959. d= new Date(d.valueOf() + value);
  15960. break;
  15961. case Ext.Date.SECOND:
  15962. d= new Date(d.valueOf() + value * 1000);
  15963. break;
  15964. case Ext.Date.MINUTE:
  15965. d= new Date(d.valueOf() + value * 60000);
  15966. break;
  15967. case Ext.Date.HOUR:
  15968. d= new Date(d.valueOf() + value * 3600000);
  15969. break;
  15970. case Ext.Date.DAY:
  15971. d= new Date(d.valueOf() + value * 86400000);
  15972. break;
  15973. case Ext.Date.MONTH:
  15974. var day = date.getDate();
  15975. if (day > 28) {
  15976. day = Math.min(day, Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(date), 'mo', value)).getDate());
  15977. }
  15978. d.setDate(day);
  15979. d.setMonth(date.getMonth() + value);
  15980. break;
  15981. case Ext.Date.YEAR:
  15982. d.setFullYear(date.getFullYear() + value);
  15983. break;
  15984. }
  15985. return d;
  15986. },
  15987. /**
  15988. * Checks if a date falls on or between the given start and end dates.
  15989. * @param {Date} date The date to check.
  15990. * @param {Date} start Start date.
  15991. * @param {Date} end End date.
  15992. * @return {Boolean} `true` if this date falls on or between the given start and end dates.
  15993. */
  15994. between : function(date, start, end) {
  15995. var t = date.getTime();
  15996. return start.getTime() <= t && t <= end.getTime();
  15997. },
  15998. /**
  15999. * Calculate how many units are there between two time.
  16000. * @param {Date} min The first time.
  16001. * @param {Date} max The second time.
  16002. * @param {String} unit The unit. This unit is compatible with the date interval constants.
  16003. * @return {Number} The maximum number n of units that min + n * unit <= max.
  16004. */
  16005. diff: function (min, max, unit) {
  16006. var ExtDate = Ext.Date, est, diff = +max - min;
  16007. switch (unit) {
  16008. case ExtDate.MILLI:
  16009. return diff;
  16010. case ExtDate.SECOND:
  16011. return Math.floor(diff / 1000);
  16012. case ExtDate.MINUTE:
  16013. return Math.floor(diff / 60000);
  16014. case ExtDate.HOUR:
  16015. return Math.floor(diff / 3600000);
  16016. case ExtDate.DAY:
  16017. return Math.floor(diff / 86400000);
  16018. case 'w':
  16019. return Math.floor(diff / 604800000);
  16020. case ExtDate.MONTH:
  16021. est = (max.getFullYear() * 12 + max.getMonth()) - (min.getFullYear() * 12 + min.getMonth());
  16022. if (Ext.Date.add(min, unit, est) > max) {
  16023. return est - 1;
  16024. } else {
  16025. return est;
  16026. }
  16027. case ExtDate.YEAR:
  16028. est = max.getFullYear() - min.getFullYear();
  16029. if (Ext.Date.add(min, unit, est) > max) {
  16030. return est - 1;
  16031. } else {
  16032. return est;
  16033. }
  16034. }
  16035. },
  16036. /**
  16037. * Align the date to `unit`.
  16038. * @param {Date} date The date to be aligned.
  16039. * @param {String} unit The unit. This unit is compatible with the date interval constants.
  16040. * @return {Date} The aligned date.
  16041. */
  16042. align: function (date, unit, step) {
  16043. var num = new Date(+date);
  16044. switch (unit.toLowerCase()) {
  16045. case Ext.Date.MILLI:
  16046. return num;
  16047. break;
  16048. case Ext.Date.SECOND:
  16049. num.setUTCSeconds(num.getUTCSeconds() - num.getUTCSeconds() % step);
  16050. num.setUTCMilliseconds(0);
  16051. return num;
  16052. break;
  16053. case Ext.Date.MINUTE:
  16054. num.setUTCMinutes(num.getUTCMinutes() - num.getUTCMinutes() % step);
  16055. num.setUTCSeconds(0);
  16056. num.setUTCMilliseconds(0);
  16057. return num;
  16058. break;
  16059. case Ext.Date.HOUR:
  16060. num.setUTCHours(num.getUTCHours() - num.getUTCHours() % step);
  16061. num.setUTCMinutes(0);
  16062. num.setUTCSeconds(0);
  16063. num.setUTCMilliseconds(0);
  16064. return num;
  16065. break;
  16066. case Ext.Date.DAY:
  16067. if (step == 7 || step == 14){
  16068. num.setUTCDate(num.getUTCDate() - num.getUTCDay() + 1);
  16069. }
  16070. num.setUTCHours(0);
  16071. num.setUTCMinutes(0);
  16072. num.setUTCSeconds(0);
  16073. num.setUTCMilliseconds(0);
  16074. return num;
  16075. break;
  16076. case Ext.Date.MONTH:
  16077. num.setUTCMonth(num.getUTCMonth() - (num.getUTCMonth() - 1) % step,1);
  16078. num.setUTCHours(0);
  16079. num.setUTCMinutes(0);
  16080. num.setUTCSeconds(0);
  16081. num.setUTCMilliseconds(0);
  16082. return num;
  16083. break;
  16084. case Ext.Date.YEAR:
  16085. num.setUTCFullYear(num.getUTCFullYear() - num.getUTCFullYear() % step, 1, 1);
  16086. num.setUTCHours(0);
  16087. num.setUTCMinutes(0);
  16088. num.setUTCSeconds(0);
  16089. num.setUTCMilliseconds(0);
  16090. return date;
  16091. break;
  16092. }
  16093. }
  16094. };
  16095. var utilDate = Ext.DateExtras;
  16096. Ext.apply(Ext.Date, utilDate);
  16097. })();
  16098. /**
  16099. * Reusable data formatting functions
  16100. */
  16101. Ext.define('Ext.util.Format', {
  16102. requires: [
  16103. 'Ext.DateExtras'
  16104. ],
  16105. singleton: true,
  16106. /**
  16107. * The global default date format.
  16108. */
  16109. defaultDateFormat: 'm/d/Y',
  16110. escapeRe: /('|\\)/g,
  16111. trimRe: /^[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+|[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+$/g,
  16112. formatRe: /\{(\d+)\}/g,
  16113. escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,
  16114. dashesRe: /-/g,
  16115. iso8601TestRe: /\d\dT\d\d/,
  16116. iso8601SplitRe: /[- :T\.Z\+]/,
  16117. /**
  16118. * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length.
  16119. * @param {String} value The string to truncate.
  16120. * @param {Number} length The maximum length to allow before truncating.
  16121. * @param {Boolean} word True to try to find a common word break.
  16122. * @return {String} The converted text.
  16123. */
  16124. ellipsis: function(value, len, word) {
  16125. if (value && value.length > len) {
  16126. if (word) {
  16127. var vs = value.substr(0, len - 2),
  16128. index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
  16129. if (index != -1 && index >= (len - 15)) {
  16130. return vs.substr(0, index) + "...";
  16131. }
  16132. }
  16133. return value.substr(0, len - 3) + "...";
  16134. }
  16135. return value;
  16136. },
  16137. /**
  16138. * Escapes the passed string for use in a regular expression.
  16139. * @param {String} str
  16140. * @return {String}
  16141. */
  16142. escapeRegex: function(s) {
  16143. return s.replace(Ext.util.Format.escapeRegexRe, "\\$1");
  16144. },
  16145. /**
  16146. * Escapes the passed string for ' and \.
  16147. * @param {String} string The string to escape.
  16148. * @return {String} The escaped string.
  16149. */
  16150. escape: function(string) {
  16151. return string.replace(Ext.util.Format.escapeRe, "\\$1");
  16152. },
  16153. /**
  16154. * Utility function that allows you to easily switch a string between two alternating values. The passed value
  16155. * is compared to the current string, and if they are equal, the other value that was passed in is returned. If
  16156. * they are already different, the first value passed in is returned.
  16157. *
  16158. * __Note:__ This method returns the new value but does not change the current string.
  16159. *
  16160. * // alternate sort directions
  16161. * sort = Ext.util.Format.toggle(sort, 'ASC', 'DESC');
  16162. *
  16163. * // instead of conditional logic:
  16164. * sort = (sort === 'ASC' ? 'DESC' : 'ASC');
  16165. *
  16166. * @param {String} string The current string
  16167. * @param {String} value The value to compare to the current string
  16168. * @param {String} other The new value to use if the string already equals the first value passed in
  16169. * @return {String} The new value
  16170. */
  16171. toggle: function(string, value, other) {
  16172. return string == value ? other : value;
  16173. },
  16174. /**
  16175. * Trims whitespace from either end of a string, leaving spaces within the string intact. Example:
  16176. *
  16177. * var s = ' foo bar ';
  16178. * alert('-' + s + '-'); // alerts "- foo bar -"
  16179. * alert('-' + Ext.util.Format.trim(s) + '-'); // alerts "-foo bar-"
  16180. *
  16181. * @param {String} string The string to escape
  16182. * @return {String} The trimmed string
  16183. */
  16184. trim: function(string) {
  16185. return string.replace(Ext.util.Format.trimRe, "");
  16186. },
  16187. /**
  16188. * Pads the left side of a string with a specified character. This is especially useful
  16189. * for normalizing number and date strings. Example usage:
  16190. *
  16191. * var s = Ext.util.Format.leftPad('123', 5, '0');
  16192. * // s now contains the string: '00123'
  16193. *
  16194. * @param {String} string The original string.
  16195. * @param {Number} size The total length of the output string.
  16196. * @param {String} [char=' '] (optional) The character with which to pad the original string.
  16197. * @return {String} The padded string.
  16198. */
  16199. leftPad: function (val, size, ch) {
  16200. var result = String(val);
  16201. ch = ch || " ";
  16202. while (result.length < size) {
  16203. result = ch + result;
  16204. }
  16205. return result;
  16206. },
  16207. /**
  16208. * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens. Each
  16209. * token must be unique, and must increment in the format {0}, {1}, etc. Example usage:
  16210. *
  16211. * var cls = 'my-class', text = 'Some text';
  16212. * var s = Ext.util.Format.format('<div class="{0}">{1}</div>', cls, text);
  16213. * // s now contains the string: '<div class="my-class">Some text</div>'
  16214. *
  16215. * @param {String} string The tokenized string to be formatted.
  16216. * @param {String...} values The values to replace token {0}, {1}, etc.
  16217. * @return {String} The formatted string.
  16218. */
  16219. format: function (format) {
  16220. var args = Ext.toArray(arguments, 1);
  16221. return format.replace(Ext.util.Format.formatRe, function(m, i) {
  16222. return args[i];
  16223. });
  16224. },
  16225. /**
  16226. * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
  16227. * @param {String} value The string to encode.
  16228. * @return {String} The encoded text.
  16229. */
  16230. htmlEncode: function(value) {
  16231. return ! value ? value: String(value).replace(/&/g, "&amp;").replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
  16232. },
  16233. /**
  16234. * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
  16235. * @param {String} value The string to decode.
  16236. * @return {String} The decoded text.
  16237. */
  16238. htmlDecode: function(value) {
  16239. return ! value ? value: String(value).replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/&quot;/g, '"').replace(/&amp;/g, "&");
  16240. },
  16241. /**
  16242. * Parse a value into a formatted date using the specified format pattern.
  16243. * @param {String/Date} value The value to format. Strings must conform to the format expected by the JavaScript
  16244. * Date object's [parse() method](http://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/parse).
  16245. * @param {String} [format='m/d/Y'] (optional) Any valid date format string.
  16246. * @return {String} The formatted date string.
  16247. */
  16248. date: function(value, format) {
  16249. var date = value;
  16250. if (!value) {
  16251. return "";
  16252. }
  16253. if (!Ext.isDate(value)) {
  16254. date = new Date(Date.parse(value));
  16255. if (isNaN(date)) {
  16256. // Dates with ISO 8601 format are not well supported by mobile devices, this can work around the issue.
  16257. if (this.iso8601TestRe.test(value)) {
  16258. date = value.split(this.iso8601SplitRe);
  16259. date = new Date(date[0], date[1]-1, date[2], date[3], date[4], date[5]);
  16260. }
  16261. if (isNaN(date)) {
  16262. // Dates with the format "2012-01-20" fail, but "2012/01/20" work in some browsers. We'll try and
  16263. // get around that.
  16264. date = new Date(Date.parse(value.replace(this.dashesRe, "/")));
  16265. //<debug>
  16266. if (isNaN(date)) {
  16267. Ext.Logger.error("Cannot parse the passed value " + value + " into a valid date");
  16268. }
  16269. //</debug>
  16270. }
  16271. }
  16272. value = date;
  16273. }
  16274. return Ext.Date.format(value, format || Ext.util.Format.defaultDateFormat);
  16275. }
  16276. });
  16277. /**
  16278. * Represents an HTML fragment template. Templates may be {@link #compile precompiled} for greater performance.
  16279. *
  16280. * An instance of this class may be created by passing to the constructor either a single argument, or multiple
  16281. * arguments:
  16282. *
  16283. * # Single argument: String/Array
  16284. *
  16285. * The single argument may be either a String or an Array:
  16286. *
  16287. * - String:
  16288. *
  16289. * var t = new Ext.Template("<div>Hello {0}.</div>");
  16290. * t.{@link #append}('some-element', ['foo']);
  16291. *
  16292. * - Array: An Array will be combined with `join('')`.
  16293. *
  16294. * var t = new Ext.Template([
  16295. * '<div name="{id}">',
  16296. * '<span class="{cls}">{name:trim} {value:ellipsis(10)}</span>',
  16297. * '</div>'
  16298. * ]);
  16299. * t.{@link #compile}();
  16300. * t.{@link #append}('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
  16301. *
  16302. * # Multiple arguments: String, Object, Array, ...
  16303. *
  16304. * Multiple arguments will be combined with `join('')`.
  16305. *
  16306. * var t = new Ext.Template(
  16307. * '<div name="{id}">',
  16308. * '<span class="{cls}">{name} {value}</span>',
  16309. * '</div>',
  16310. * // a configuration object:
  16311. * {
  16312. * compiled: true // {@link #compile} immediately
  16313. * }
  16314. * );
  16315. *
  16316. * # Notes
  16317. *
  16318. * - For a list of available format functions, see {@link Ext.util.Format}.
  16319. * - `disableFormats` reduces `{@link #apply}` time when no formatting is required.
  16320. */
  16321. Ext.define('Ext.Template', {
  16322. /* Begin Definitions */
  16323. requires: ['Ext.dom.Helper', 'Ext.util.Format'],
  16324. inheritableStatics: {
  16325. /**
  16326. * Creates a template from the passed element's value (_display:none_ textarea, preferred) or `innerHTML`.
  16327. * @param {String/HTMLElement} el A DOM element or its `id`.
  16328. * @param {Object} config (optional) Config object.
  16329. * @return {Ext.Template} The created template.
  16330. * @static
  16331. * @inheritable
  16332. */
  16333. from: function(el, config) {
  16334. el = Ext.getDom(el);
  16335. return new this(el.value || el.innerHTML, config || '');
  16336. }
  16337. },
  16338. /* End Definitions */
  16339. /**
  16340. * Creates new template.
  16341. *
  16342. * @param {String...} html List of strings to be concatenated into template.
  16343. * Alternatively an array of strings can be given, but then no config object may be passed.
  16344. * @param {Object} config (optional) Config object.
  16345. */
  16346. constructor: function(html) {
  16347. var me = this,
  16348. args = arguments,
  16349. buffer = [],
  16350. i = 0,
  16351. length = args.length,
  16352. value;
  16353. me.initialConfig = {};
  16354. // Allow an array to be passed here so we can
  16355. // pass an array of strings and an object
  16356. // at the end
  16357. if (length === 1 && Ext.isArray(html)) {
  16358. args = html;
  16359. length = args.length;
  16360. }
  16361. if (length > 1) {
  16362. for (; i < length; i++) {
  16363. value = args[i];
  16364. if (typeof value == 'object') {
  16365. Ext.apply(me.initialConfig, value);
  16366. Ext.apply(me, value);
  16367. } else {
  16368. buffer.push(value);
  16369. }
  16370. }
  16371. } else {
  16372. buffer.push(html);
  16373. }
  16374. // @private
  16375. me.html = buffer.join('');
  16376. if (me.compiled) {
  16377. me.compile();
  16378. }
  16379. },
  16380. /**
  16381. * @property {Boolean} isTemplate
  16382. * `true` in this class to identify an object as an instantiated Template, or subclass thereof.
  16383. */
  16384. isTemplate: true,
  16385. /**
  16386. * @cfg {Boolean} [compiled=false]
  16387. * `true` to immediately compile the template.
  16388. */
  16389. /**
  16390. * @cfg {Boolean} [disableFormats=false]
  16391. * `true` to disable format functions in the template. If the template doesn't contain
  16392. * format functions, setting `disableFormats` to `true` will reduce apply time.
  16393. */
  16394. disableFormats: false,
  16395. re: /\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
  16396. /**
  16397. * Returns an HTML fragment of this template with the specified values applied.
  16398. *
  16399. * @param {Object/Array} values The template values. Can be an array if your params are numeric:
  16400. *
  16401. * var tpl = new Ext.Template('Name: {0}, Age: {1}');
  16402. * tpl.apply(['John', 25]);
  16403. *
  16404. * or an object:
  16405. *
  16406. * var tpl = new Ext.Template('Name: {name}, Age: {age}');
  16407. * tpl.apply({name: 'John', age: 25});
  16408. *
  16409. * @return {String} The HTML fragment.
  16410. */
  16411. apply: function(values) {
  16412. var me = this,
  16413. useFormat = me.disableFormats !== true,
  16414. fm = Ext.util.Format,
  16415. tpl = me,
  16416. ret;
  16417. if (me.compiled) {
  16418. return me.compiled(values).join('');
  16419. }
  16420. function fn(m, name, format, args) {
  16421. if (format && useFormat) {
  16422. if (args) {
  16423. args = [values[name]].concat(Ext.functionFactory('return ['+ args +'];')());
  16424. } else {
  16425. args = [values[name]];
  16426. }
  16427. if (format.substr(0, 5) == "this.") {
  16428. return tpl[format.substr(5)].apply(tpl, args);
  16429. }
  16430. else {
  16431. return fm[format].apply(fm, args);
  16432. }
  16433. }
  16434. else {
  16435. return values[name] !== undefined ? values[name] : "";
  16436. }
  16437. }
  16438. ret = me.html.replace(me.re, fn);
  16439. return ret;
  16440. },
  16441. /**
  16442. * Appends the result of this template to the provided output array.
  16443. * @param {Object/Array} values The template values. See {@link #apply}.
  16444. * @param {Array} out The array to which output is pushed.
  16445. * @return {Array} The given out array.
  16446. */
  16447. applyOut: function(values, out) {
  16448. var me = this;
  16449. if (me.compiled) {
  16450. out.push.apply(out, me.compiled(values));
  16451. } else {
  16452. out.push(me.apply(values));
  16453. }
  16454. return out;
  16455. },
  16456. /**
  16457. * @method applyTemplate
  16458. * @member Ext.Template
  16459. * Alias for {@link #apply}.
  16460. * @inheritdoc Ext.Template#apply
  16461. */
  16462. applyTemplate: function () {
  16463. return this.apply.apply(this, arguments);
  16464. },
  16465. /**
  16466. * Sets the HTML used as the template and optionally compiles it.
  16467. * @param {String} html
  16468. * @param {Boolean} compile (optional) `true` to compile the template.
  16469. * @return {Ext.Template} this
  16470. */
  16471. set: function(html, compile) {
  16472. var me = this;
  16473. me.html = html;
  16474. me.compiled = null;
  16475. return compile ? me.compile() : me;
  16476. },
  16477. compileARe: /\\/g,
  16478. compileBRe: /(\r\n|\n)/g,
  16479. compileCRe: /'/g,
  16480. /**
  16481. * Compiles the template into an internal function, eliminating the RegEx overhead.
  16482. * @return {Ext.Template} this
  16483. */
  16484. compile: function() {
  16485. var me = this,
  16486. fm = Ext.util.Format,
  16487. useFormat = me.disableFormats !== true,
  16488. body, bodyReturn;
  16489. function fn(m, name, format, args) {
  16490. if (format && useFormat) {
  16491. args = args ? ',' + args: "";
  16492. if (format.substr(0, 5) != "this.") {
  16493. format = "fm." + format + '(';
  16494. }
  16495. else {
  16496. format = 'this.' + format.substr(5) + '(';
  16497. }
  16498. }
  16499. else {
  16500. args = '';
  16501. format = "(values['" + name + "'] == undefined ? '' : ";
  16502. }
  16503. return "'," + format + "values['" + name + "']" + args + ") ,'";
  16504. }
  16505. bodyReturn = me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn);
  16506. body = "this.compiled = function(values){ return ['" + bodyReturn + "'];};";
  16507. eval(body);
  16508. return me;
  16509. },
  16510. /**
  16511. * Applies the supplied values to the template and inserts the new node(s) as the first child of el.
  16512. *
  16513. * @param {String/HTMLElement/Ext.Element} el The context element.
  16514. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  16515. * @param {Boolean} returnElement (optional) `true` to return a Ext.Element.
  16516. * @return {HTMLElement/Ext.Element} The new node or Element.
  16517. */
  16518. insertFirst: function(el, values, returnElement) {
  16519. return this.doInsert('afterBegin', el, values, returnElement);
  16520. },
  16521. /**
  16522. * Applies the supplied values to the template and inserts the new node(s) before el.
  16523. *
  16524. * @param {String/HTMLElement/Ext.Element} el The context element.
  16525. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  16526. * @param {Boolean} returnElement (optional) `true` to return an Ext.Element.
  16527. * @return {HTMLElement/Ext.Element} The new node or Element
  16528. */
  16529. insertBefore: function(el, values, returnElement) {
  16530. return this.doInsert('beforeBegin', el, values, returnElement);
  16531. },
  16532. /**
  16533. * Applies the supplied values to the template and inserts the new node(s) after el.
  16534. *
  16535. * @param {String/HTMLElement/Ext.Element} el The context element.
  16536. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  16537. * @param {Boolean} returnElement (optional) `true` to return a Ext.Element.
  16538. * @return {HTMLElement/Ext.Element} The new node or Element.
  16539. */
  16540. insertAfter: function(el, values, returnElement) {
  16541. return this.doInsert('afterEnd', el, values, returnElement);
  16542. },
  16543. /**
  16544. * Applies the supplied `values` to the template and appends the new node(s) to the specified `el`.
  16545. *
  16546. * For example usage see {@link Ext.Template Ext.Template class docs}.
  16547. *
  16548. * @param {String/HTMLElement/Ext.Element} el The context element.
  16549. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  16550. * @param {Boolean} returnElement (optional) true to return an Ext.Element.
  16551. * @return {HTMLElement/Ext.Element} The new node or Element.
  16552. */
  16553. append: function(el, values, returnElement) {
  16554. return this.doInsert('beforeEnd', el, values, returnElement);
  16555. },
  16556. doInsert: function(where, el, values, returnElement) {
  16557. var newNode = Ext.DomHelper.insertHtml(where, Ext.getDom(el), this.apply(values));
  16558. return returnElement ? Ext.get(newNode) : newNode;
  16559. },
  16560. /**
  16561. * Applies the supplied values to the template and overwrites the content of el with the new node(s).
  16562. *
  16563. * @param {String/HTMLElement/Ext.Element} el The context element.
  16564. * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
  16565. * @param {Boolean} returnElement (optional) true to return a Ext.Element.
  16566. * @return {HTMLElement/Ext.Element} The new node or Element.
  16567. */
  16568. overwrite: function(el, values, returnElement) {
  16569. var newNode = Ext.DomHelper.overwrite(Ext.getDom(el), this.apply(values));
  16570. return returnElement ? Ext.get(newNode) : newNode;
  16571. }
  16572. });
  16573. /**
  16574. * This class parses the XTemplate syntax and calls abstract methods to process the parts.
  16575. * @private
  16576. */
  16577. Ext.define('Ext.XTemplateParser', {
  16578. constructor: function (config) {
  16579. Ext.apply(this, config);
  16580. },
  16581. /**
  16582. * @property {Number} level The 'for' loop context level. This is adjusted up by one
  16583. * prior to calling {@link #doFor} and down by one after calling the corresponding
  16584. * {@link #doEnd} that closes the loop. This will be 1 on the first {@link #doFor}
  16585. * call.
  16586. */
  16587. /**
  16588. * This method is called to process a piece of raw text from the tpl.
  16589. * @param {String} text
  16590. * @method doText
  16591. */
  16592. // doText: function (text)
  16593. /**
  16594. * This method is called to process expressions (like `{[expr]}`).
  16595. * @param {String} expr The body of the expression (inside "{[" and "]}").
  16596. * @method doExpr
  16597. */
  16598. // doExpr: function (expr)
  16599. /**
  16600. * This method is called to process simple tags (like `{tag}`).
  16601. * @param {String} tag
  16602. * @method doTag
  16603. */
  16604. // doTag: function (tag)
  16605. /**
  16606. * This method is called to process `<tpl else>`.
  16607. * @method doElse
  16608. */
  16609. // doElse: function ()
  16610. /**
  16611. * This method is called to process `{% text %}`.
  16612. * @param {String} text
  16613. * @method doEval
  16614. */
  16615. // doEval: function (text)
  16616. /**
  16617. * This method is called to process `<tpl if="action">`. If there are other attributes,
  16618. * these are passed in the actions object.
  16619. * @param {String} action
  16620. * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
  16621. * @method doIf
  16622. */
  16623. // doIf: function (action, actions)
  16624. /**
  16625. * This method is called to process `<tpl elseif="action">`. If there are other attributes,
  16626. * these are passed in the actions object.
  16627. * @param {String} action
  16628. * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
  16629. * @method doElseIf
  16630. */
  16631. // doElseIf: function (action, actions)
  16632. /**
  16633. * This method is called to process `<tpl switch="action">`. If there are other attributes,
  16634. * these are passed in the actions object.
  16635. * @param {String} action
  16636. * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
  16637. * @method doSwitch
  16638. */
  16639. // doSwitch: function (action, actions)
  16640. /**
  16641. * This method is called to process `<tpl case="action">`. If there are other attributes,
  16642. * these are passed in the actions object.
  16643. * @param {String} action
  16644. * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
  16645. * @method doCase
  16646. */
  16647. // doCase: function (action, actions)
  16648. /**
  16649. * This method is called to process `<tpl default>`.
  16650. * @method doDefault
  16651. */
  16652. // doDefault: function ()
  16653. /**
  16654. * This method is called to process `</tpl>`. It is given the action type that started
  16655. * the tpl and the set of additional actions.
  16656. * @param {String} type The type of action that is being ended.
  16657. * @param {Object} actions The other actions keyed by the attribute name (such as 'exec').
  16658. * @method doEnd
  16659. */
  16660. // doEnd: function (type, actions)
  16661. /**
  16662. * This method is called to process `<tpl for="action">`. If there are other attributes,
  16663. * these are passed in the actions object.
  16664. * @param {String} action
  16665. * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
  16666. * @method doFor
  16667. */
  16668. // doFor: function (action, actions)
  16669. /**
  16670. * This method is called to process `<tpl exec="action">`. If there are other attributes,
  16671. * these are passed in the actions object.
  16672. * @param {String} action
  16673. * @param {Object} actions Other actions keyed by the attribute name.
  16674. * @method doExec
  16675. */
  16676. // doExec: function (action, actions)
  16677. /**
  16678. * This method is called to process an empty `<tpl>`. This is unlikely to need to be
  16679. * implemented, so a default (do nothing) version is provided.
  16680. * @method
  16681. */
  16682. doTpl: Ext.emptyFn,
  16683. parse: function (str) {
  16684. var me = this,
  16685. len = str.length,
  16686. aliases = { elseif: 'elif' },
  16687. topRe = me.topRe,
  16688. actionsRe = me.actionsRe,
  16689. index, stack, s, m, t, prev, frame, subMatch, begin, end, actions,
  16690. prop;
  16691. me.level = 0;
  16692. me.stack = stack = [];
  16693. for (index = 0; index < len; index = end) {
  16694. topRe.lastIndex = index;
  16695. m = topRe.exec(str);
  16696. if (!m) {
  16697. me.doText(str.substring(index, len));
  16698. break;
  16699. }
  16700. begin = m.index;
  16701. end = topRe.lastIndex;
  16702. if (index < begin) {
  16703. me.doText(str.substring(index, begin));
  16704. }
  16705. if (m[1]) {
  16706. end = str.indexOf('%}', begin+2);
  16707. me.doEval(str.substring(begin+2, end));
  16708. end += 2;
  16709. } else if (m[2]) {
  16710. end = str.indexOf(']}', begin+2);
  16711. me.doExpr(str.substring(begin+2, end));
  16712. end += 2;
  16713. } else if (m[3]) { // if ('{' token)
  16714. me.doTag(m[3]);
  16715. } else if (m[4]) { // content of a <tpl xxxxxx xxx> tag
  16716. actions = null;
  16717. while ((subMatch = actionsRe.exec(m[4])) !== null) {
  16718. s = subMatch[2] || subMatch[3];
  16719. if (s) {
  16720. s = Ext.String.htmlDecode(s); // decode attr value
  16721. t = subMatch[1];
  16722. t = aliases[t] || t;
  16723. actions = actions || {};
  16724. prev = actions[t];
  16725. if (typeof prev == 'string') {
  16726. actions[t] = [prev, s];
  16727. } else if (prev) {
  16728. actions[t].push(s);
  16729. } else {
  16730. actions[t] = s;
  16731. }
  16732. }
  16733. }
  16734. if (!actions) {
  16735. if (me.elseRe.test(m[4])) {
  16736. me.doElse();
  16737. } else if (me.defaultRe.test(m[4])) {
  16738. me.doDefault();
  16739. } else {
  16740. me.doTpl();
  16741. stack.push({ type: 'tpl' });
  16742. }
  16743. }
  16744. else if (actions['if']) {
  16745. me.doIf(actions['if'], actions);
  16746. stack.push({ type: 'if' });
  16747. }
  16748. else if (actions['switch']) {
  16749. me.doSwitch(actions['switch'], actions);
  16750. stack.push({ type: 'switch' });
  16751. }
  16752. else if (actions['case']) {
  16753. me.doCase(actions['case'], actions);
  16754. }
  16755. else if (actions['elif']) {
  16756. me.doElseIf(actions['elif'], actions);
  16757. }
  16758. else if (actions['for']) {
  16759. ++me.level;
  16760. // Extract property name to use from indexed item
  16761. if (prop = me.propRe.exec(m[4])) {
  16762. actions.propName = prop[1] || prop[2];
  16763. }
  16764. me.doFor(actions['for'], actions);
  16765. stack.push({ type: 'for', actions: actions });
  16766. }
  16767. else if (actions.exec) {
  16768. me.doExec(actions.exec, actions);
  16769. stack.push({ type: 'exec', actions: actions });
  16770. }
  16771. /*
  16772. else {
  16773. // todo - error
  16774. }
  16775. */
  16776. } else if (m[0].length === 5) {
  16777. // if the length of m[0] is 5, assume that we're dealing with an opening tpl tag with no attributes (e.g. <tpl>...</tpl>)
  16778. // in this case no action is needed other than pushing it on to the stack
  16779. stack.push({ type: 'tpl' });
  16780. } else {
  16781. frame = stack.pop();
  16782. me.doEnd(frame.type, frame.actions);
  16783. if (frame.type == 'for') {
  16784. --me.level;
  16785. }
  16786. }
  16787. }
  16788. },
  16789. // Internal regexes
  16790. topRe: /(?:(\{\%)|(\{\[)|\{([^{}]*)\})|(?:<tpl([^>]*)\>)|(?:<\/tpl>)/g,
  16791. actionsRe: /\s*(elif|elseif|if|for|exec|switch|case|eval)\s*\=\s*(?:(?:"([^"]*)")|(?:'([^']*)'))\s*/g,
  16792. propRe: /prop=(?:(?:"([^"]*)")|(?:'([^']*)'))/,
  16793. defaultRe: /^\s*default\s*$/,
  16794. elseRe: /^\s*else\s*$/
  16795. });
  16796. /**
  16797. * This class compiles the XTemplate syntax into a function object. The function is used
  16798. * like so:
  16799. *
  16800. * function (out, values, parent, xindex, xcount) {
  16801. * // out is the output array to store results
  16802. * // values, parent, xindex and xcount have their historical meaning
  16803. * }
  16804. *
  16805. * @private
  16806. */
  16807. Ext.define('Ext.XTemplateCompiler', {
  16808. extend: 'Ext.XTemplateParser',
  16809. // Chrome really likes "new Function" to realize the code block (as in it is
  16810. // 2x-3x faster to call it than using eval), but Firefox chokes on it badly.
  16811. // IE and Opera are also fine with the "new Function" technique.
  16812. useEval: Ext.isGecko,
  16813. // See [http://jsperf.com/nige-array-append](http://jsperf.com/nige-array-append) for quickest way to append to an array of unknown length
  16814. // (Due to arbitrary code execution inside a template, we cannot easily track the length in var)
  16815. // On IE6 and 7 `myArray[myArray.length]='foo'` is better. On other browsers `myArray.push('foo')` is better.
  16816. useIndex: Ext.isIE6 || Ext.isIE7,
  16817. useFormat: true,
  16818. propNameRe: /^[\w\d\$]*$/,
  16819. compile: function (tpl) {
  16820. var me = this,
  16821. code = me.generate(tpl);
  16822. // When using "new Function", we have to pass our "Ext" variable to it in order to
  16823. // support sandboxing. If we did not, the generated function would use the global
  16824. // "Ext", not the "Ext" from our sandbox (scope chain).
  16825. //
  16826. return me.useEval ? me.evalTpl(code) : (new Function('Ext', code))(Ext);
  16827. },
  16828. generate: function (tpl) {
  16829. var me = this,
  16830. // note: Ext here is properly sandboxed
  16831. definitions = 'var fm=Ext.util.Format,ts=Object.prototype.toString;',
  16832. code;
  16833. // Track how many levels we use, so that we only "var" each level's variables once
  16834. me.maxLevel = 0;
  16835. me.body = [
  16836. 'var c0=values, a0=' + me.createArrayTest(0) + ', p0=parent, n0=xcount, i0=xindex, v;\n'
  16837. ];
  16838. if (me.definitions) {
  16839. if (typeof me.definitions === 'string') {
  16840. me.definitions = [me.definitions, definitions ];
  16841. } else {
  16842. me.definitions.push(definitions);
  16843. }
  16844. } else {
  16845. me.definitions = [ definitions ];
  16846. }
  16847. me.switches = [];
  16848. me.parse(tpl);
  16849. me.definitions.push(
  16850. (me.useEval ? '$=' : 'return') + ' function (' + me.fnArgs + ') {',
  16851. me.body.join(''),
  16852. '}'
  16853. );
  16854. code = me.definitions.join('\n');
  16855. // Free up the arrays.
  16856. me.definitions.length = me.body.length = me.switches.length = 0;
  16857. delete me.definitions;
  16858. delete me.body;
  16859. delete me.switches;
  16860. return code;
  16861. },
  16862. //-----------------------------------
  16863. // XTemplateParser callouts
  16864. //
  16865. doText: function (text) {
  16866. var me = this,
  16867. out = me.body;
  16868. text = text.replace(me.aposRe, "\\'").replace(me.newLineRe, '\\n');
  16869. if (me.useIndex) {
  16870. out.push('out[out.length]=\'', text, '\'\n');
  16871. } else {
  16872. out.push('out.push(\'', text, '\')\n');
  16873. }
  16874. },
  16875. doExpr: function (expr) {
  16876. var out = this.body;
  16877. out.push('if ((v=' + expr + ')!==undefined && (v=' + expr + ')!==null) out');
  16878. // Coerce value to string using concatenation of an empty string literal.
  16879. // See http://jsperf.com/tostringvscoercion/5
  16880. if (this.useIndex) {
  16881. out.push('[out.length]=v+\'\'\n');
  16882. } else {
  16883. out.push('.push(v+\'\')\n');
  16884. }
  16885. },
  16886. doTag: function (tag) {
  16887. this.doExpr(this.parseTag(tag));
  16888. },
  16889. doElse: function () {
  16890. this.body.push('} else {\n');
  16891. },
  16892. doEval: function (text) {
  16893. this.body.push(text, '\n');
  16894. },
  16895. doIf: function (action, actions) {
  16896. var me = this;
  16897. // If it's just a propName, use it directly in the if
  16898. if (action === '.') {
  16899. me.body.push('if (values) {\n');
  16900. } else if (me.propNameRe.test(action)) {
  16901. me.body.push('if (', me.parseTag(action), ') {\n');
  16902. }
  16903. // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values)
  16904. else {
  16905. me.body.push('if (', me.addFn(action), me.callFn, ') {\n');
  16906. }
  16907. if (actions.exec) {
  16908. me.doExec(actions.exec);
  16909. }
  16910. },
  16911. doElseIf: function (action, actions) {
  16912. var me = this;
  16913. // If it's just a propName, use it directly in the else if
  16914. if (action === '.') {
  16915. me.body.push('else if (values) {\n');
  16916. } else if (me.propNameRe.test(action)) {
  16917. me.body.push('} else if (', me.parseTag(action), ') {\n');
  16918. }
  16919. // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values)
  16920. else {
  16921. me.body.push('} else if (', me.addFn(action), me.callFn, ') {\n');
  16922. }
  16923. if (actions.exec) {
  16924. me.doExec(actions.exec);
  16925. }
  16926. },
  16927. doSwitch: function (action) {
  16928. var me = this;
  16929. // If it's just a propName, use it directly in the switch
  16930. if (action === '.') {
  16931. me.body.push('switch (values) {\n');
  16932. } else if (me.propNameRe.test(action)) {
  16933. me.body.push('switch (', me.parseTag(action), ') {\n');
  16934. }
  16935. // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values)
  16936. else {
  16937. me.body.push('switch (', me.addFn(action), me.callFn, ') {\n');
  16938. }
  16939. me.switches.push(0);
  16940. },
  16941. doCase: function (action) {
  16942. var me = this,
  16943. cases = Ext.isArray(action) ? action : [action],
  16944. n = me.switches.length - 1,
  16945. match, i;
  16946. if (me.switches[n]) {
  16947. me.body.push('break;\n');
  16948. } else {
  16949. me.switches[n]++;
  16950. }
  16951. for (i = 0, n = cases.length; i < n; ++i) {
  16952. match = me.intRe.exec(cases[i]);
  16953. cases[i] = match ? match[1] : ("'" + cases[i].replace(me.aposRe,"\\'") + "'");
  16954. }
  16955. me.body.push('case ', cases.join(': case '), ':\n');
  16956. },
  16957. doDefault: function () {
  16958. var me = this,
  16959. n = me.switches.length - 1;
  16960. if (me.switches[n]) {
  16961. me.body.push('break;\n');
  16962. } else {
  16963. me.switches[n]++;
  16964. }
  16965. me.body.push('default:\n');
  16966. },
  16967. doEnd: function (type, actions) {
  16968. var me = this,
  16969. L = me.level-1;
  16970. if (type == 'for') {
  16971. /*
  16972. To exit a for loop we must restore the outer loop's context. The code looks
  16973. like this (which goes with that produced by doFor:
  16974. for (...) { // the part generated by doFor
  16975. ... // the body of the for loop
  16976. // ... any tpl for exec statement goes here...
  16977. }
  16978. parent = p1;
  16979. values = r2;
  16980. xcount = n1;
  16981. xindex = i1
  16982. */
  16983. if (actions.exec) {
  16984. me.doExec(actions.exec);
  16985. }
  16986. me.body.push('}\n');
  16987. me.body.push('parent=p',L,';values=r',L+1,';xcount=n',L,';xindex=i',L,'\n');
  16988. } else if (type == 'if' || type == 'switch') {
  16989. me.body.push('}\n');
  16990. }
  16991. },
  16992. doFor: function (action, actions) {
  16993. var me = this,
  16994. s,
  16995. L = me.level,
  16996. up = L-1,
  16997. pL = 'p' + L,
  16998. parentAssignment;
  16999. // If it's just a propName, use it directly in the switch
  17000. if (action === '.') {
  17001. s = 'values';
  17002. } else if (me.propNameRe.test(action)) {
  17003. s = me.parseTag(action);
  17004. }
  17005. // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values)
  17006. else {
  17007. s = me.addFn(action) + me.callFn;
  17008. }
  17009. /*
  17010. We are trying to produce a block of code that looks like below. We use the nesting
  17011. level to uniquely name the control variables.
  17012. // Omit "var " if we have already been through level 2
  17013. var i2 = 0,
  17014. n2 = 0,
  17015. c2 = values['propName'],
  17016. // c2 is the context object for the for loop
  17017. a2 = Array.isArray(c2);
  17018. p2 = c1,
  17019. // p2 is the parent context (of the outer for loop)
  17020. r2 = values
  17021. // r2 is the values object to
  17022. // If iterating over the current data, the parent is always set to c2
  17023. parent = c2;
  17024. // If iterating over a property in an object, set the parent to the object
  17025. parent = a1 ? c1[i1] : p2 // set parent
  17026. if (c2) {
  17027. if (a2) {
  17028. n2 = c2.length;
  17029. } else if (c2.isMixedCollection) {
  17030. c2 = c2.items;
  17031. n2 = c2.length;
  17032. } else if (c2.isStore) {
  17033. c2 = c2.data.items;
  17034. n2 = c2.length;
  17035. } else {
  17036. c2 = [ c2 ];
  17037. n2 = 1;
  17038. }
  17039. }
  17040. // i2 is the loop index and n2 is the number (xcount) of this for loop
  17041. for (xcount = n2; i2 < n2; ++i2) {
  17042. values = c2[i2] // adjust special vars to inner scope
  17043. xindex = i2 + 1 // xindex is 1-based
  17044. The body of the loop is whatever comes between the tpl and /tpl statements (which
  17045. is handled by doEnd).
  17046. */
  17047. // Declare the vars for a particular level only if we have not already declared them.
  17048. if (me.maxLevel < L) {
  17049. me.maxLevel = L;
  17050. me.body.push('var ');
  17051. }
  17052. if (action == '.') {
  17053. parentAssignment = 'c' + L;
  17054. } else {
  17055. parentAssignment = 'a' + up + '?c' + up + '[i' + up + ']:p' + L;
  17056. }
  17057. me.body.push('i',L,'=0,n', L, '=0,c',L,'=',s,',a',L,'=', me.createArrayTest(L), ',p',L,'=c',up,',r',L,'=values;\n',
  17058. 'parent=',parentAssignment,'\n',
  17059. 'if (c',L,'){if(a',L,'){n', L,'=c', L, '.length;}else if (c', L, '.isMixedCollection){c',L,'=c',L,'.items;n',L,'=c',L,'.length;}else if(c',L,'.isStore){c',L,'=c',L,'.data.items;n',L,'=c',L,'.length;}else{c',L,'=[c',L,'];n',L,'=1;}}\n',
  17060. 'for (xcount=n',L,';i',L,'<n'+L+';++i',L,'){\n',
  17061. 'values=c',L,'[i',L,']');
  17062. if (actions.propName) {
  17063. me.body.push('.', actions.propName);
  17064. }
  17065. me.body.push('\n',
  17066. 'xindex=i',L,'+1\n');
  17067. },
  17068. createArrayTest: ('isArray' in Array) ? function(L) {
  17069. return 'Array.isArray(c' + L + ')';
  17070. } : function(L) {
  17071. return 'ts.call(c' + L + ')==="[object Array]"';
  17072. },
  17073. doExec: function (action, actions) {
  17074. var me = this,
  17075. name = 'f' + me.definitions.length;
  17076. me.definitions.push('function ' + name + '(' + me.fnArgs + ') {',
  17077. ' try { with(values) {',
  17078. ' ' + action,
  17079. ' }} catch(e) {',
  17080. //<debug>
  17081. 'Ext.Logger.log("XTemplate Error: " + e.message);',
  17082. //</debug>
  17083. '}',
  17084. '}');
  17085. me.body.push(name + me.callFn + '\n');
  17086. },
  17087. //-----------------------------------
  17088. // Internal
  17089. //
  17090. addFn: function (body) {
  17091. var me = this,
  17092. name = 'f' + me.definitions.length;
  17093. if (body === '.') {
  17094. me.definitions.push('function ' + name + '(' + me.fnArgs + ') {',
  17095. ' return values',
  17096. '}');
  17097. } else if (body === '..') {
  17098. me.definitions.push('function ' + name + '(' + me.fnArgs + ') {',
  17099. ' return parent',
  17100. '}');
  17101. } else {
  17102. me.definitions.push('function ' + name + '(' + me.fnArgs + ') {',
  17103. ' try { with(values) {',
  17104. ' return(' + body + ')',
  17105. ' }} catch(e) {',
  17106. //<debug>
  17107. 'Ext.Logger.log("XTemplate Error: " + e.message);',
  17108. //</debug>
  17109. '}',
  17110. '}');
  17111. }
  17112. return name;
  17113. },
  17114. parseTag: function (tag) {
  17115. var me = this,
  17116. m = me.tagRe.exec(tag),
  17117. name = m[1],
  17118. format = m[2],
  17119. args = m[3],
  17120. math = m[4],
  17121. v;
  17122. // name = "." - Just use the values object.
  17123. if (name == '.') {
  17124. // filter to not include arrays/objects/nulls
  17125. if (!me.validTypes) {
  17126. me.definitions.push('var validTypes={string:1,number:1,boolean:1};');
  17127. me.validTypes = true;
  17128. }
  17129. v = 'validTypes[typeof values] || ts.call(values) === "[object Date]" ? values : ""';
  17130. }
  17131. // name = "#" - Use the xindex
  17132. else if (name == '#') {
  17133. v = 'xindex';
  17134. }
  17135. else if (name.substr(0, 7) == "parent.") {
  17136. v = name;
  17137. }
  17138. // compound JavaScript property name (e.g., "foo.bar")
  17139. else if (isNaN(name) && name.indexOf('-') == -1 && name.indexOf('.') != -1) {
  17140. v = "values." + name;
  17141. }
  17142. // number or a '-' in it or a single word (maybe a keyword): use array notation
  17143. // (http://jsperf.com/string-property-access/4)
  17144. else {
  17145. v = "values['" + name + "']";
  17146. }
  17147. if (math) {
  17148. v = '(' + v + math + ')';
  17149. }
  17150. if (format && me.useFormat) {
  17151. args = args ? ',' + args : "";
  17152. if (format.substr(0, 5) != "this.") {
  17153. format = "fm." + format + '(';
  17154. } else {
  17155. format += '(';
  17156. }
  17157. } else {
  17158. return v;
  17159. }
  17160. return format + v + args + ')';
  17161. },
  17162. // @private
  17163. evalTpl: function ($) {
  17164. // We have to use eval to realize the code block and capture the inner func we also
  17165. // don't want a deep scope chain. We only do this in Firefox and it is also unhappy
  17166. // with eval containing a return statement, so instead we assign to "$" and return
  17167. // that. Because we use "eval", we are automatically sandboxed properly.
  17168. eval($);
  17169. return $;
  17170. },
  17171. newLineRe: /\r\n|\r|\n/g,
  17172. aposRe: /[']/g,
  17173. intRe: /^\s*(\d+)\s*$/,
  17174. tagRe: /([\w-\.\#\$]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?/
  17175. }, function () {
  17176. var proto = this.prototype;
  17177. proto.fnArgs = 'out,values,parent,xindex,xcount';
  17178. proto.callFn = '.call(this,' + proto.fnArgs + ')';
  17179. });
  17180. /**
  17181. * A template class that supports advanced functionality like:
  17182. *
  17183. * - Autofilling arrays using templates and sub-templates
  17184. * - Conditional processing with basic comparison operators
  17185. * - Basic math function support
  17186. * - Execute arbitrary inline code with special built-in template variables
  17187. * - Custom member functions
  17188. * - Many special tags and built-in operators that aren't defined as part of the API, but are supported in the templates that can be created
  17189. *
  17190. * XTemplate provides the templating mechanism built into {@link Ext.DataView}.
  17191. *
  17192. * The {@link Ext.Template} describes the acceptable parameters to pass to the constructor. The following examples
  17193. * demonstrate all of the supported features.
  17194. *
  17195. * # Sample Data
  17196. *
  17197. * This is the data object used for reference in each code example:
  17198. *
  17199. * var data = {
  17200. * name: 'Don Griffin',
  17201. * title: 'Senior Technomage',
  17202. * company: 'Sencha Inc.',
  17203. * drinks: ['Coffee', 'Water', 'More Coffee'],
  17204. * kids: [
  17205. * { name: 'Aubrey', age: 17 },
  17206. * { name: 'Joshua', age: 13 },
  17207. * { name: 'Cale', age: 10 },
  17208. * { name: 'Nikol', age: 5 },
  17209. * { name: 'Solomon', age: 0 }
  17210. * ]
  17211. * };
  17212. *
  17213. * # Auto filling of arrays
  17214. *
  17215. * The **tpl** tag and the **for** operator are used to process the provided data object:
  17216. *
  17217. * - If the value specified in for is an array, it will auto-fill, repeating the template block inside the tpl
  17218. * tag for each item in the array.
  17219. * - If for="." is specified, the data object provided is examined.
  17220. * - While processing an array, the special variable {#} will provide the current array index + 1 (starts at 1, not 0).
  17221. *
  17222. * Examples:
  17223. *
  17224. * <tpl for=".">...</tpl> // loop through array at root node
  17225. * <tpl for="foo">...</tpl> // loop through array at foo node
  17226. * <tpl for="foo.bar">...</tpl> // loop through array at foo.bar node
  17227. *
  17228. * Using the sample data above:
  17229. *
  17230. * var tpl = new Ext.XTemplate(
  17231. * '<p>Kids: ',
  17232. * '<tpl for=".">', // process the data.kids node
  17233. * '<p>{#}. {name}</p>', // use current array index to autonumber
  17234. * '</tpl></p>'
  17235. * );
  17236. * tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
  17237. *
  17238. * An example illustrating how the **for** property can be leveraged to access specified members of the provided data
  17239. * object to populate the template:
  17240. *
  17241. * var tpl = new Ext.XTemplate(
  17242. * '<p>Name: {name}</p>',
  17243. * '<p>Title: {title}</p>',
  17244. * '<p>Company: {company}</p>',
  17245. * '<p>Kids: ',
  17246. * '<tpl for="kids">', // interrogate the kids property within the data
  17247. * '<p>{name}</p>',
  17248. * '</tpl></p>'
  17249. * );
  17250. * tpl.overwrite(panel.body, data); // pass the root node of the data object
  17251. *
  17252. * Flat arrays that contain values (and not objects) can be auto-rendered using the special **`{.}`** variable inside a
  17253. * loop. This variable will represent the value of the array at the current index:
  17254. *
  17255. * var tpl = new Ext.XTemplate(
  17256. * '<p>{name}\'s favorite beverages:</p>',
  17257. * '<tpl for="drinks">',
  17258. * '<div> - {.}</div>',
  17259. * '</tpl>'
  17260. * );
  17261. * tpl.overwrite(panel.body, data);
  17262. *
  17263. * When processing a sub-template, for example while looping through a child array, you can access the parent object's
  17264. * members via the **parent** object:
  17265. *
  17266. * var tpl = new Ext.XTemplate(
  17267. * '<p>Name: {name}</p>',
  17268. * '<p>Kids: ',
  17269. * '<tpl for="kids">',
  17270. * '<tpl if="age &gt; 1">',
  17271. * '<p>{name}</p>',
  17272. * '<p>Dad: {parent.name}</p>',
  17273. * '</tpl>',
  17274. * '</tpl></p>'
  17275. * );
  17276. * tpl.overwrite(panel.body, data);
  17277. *
  17278. * # Conditional processing with basic comparison operators
  17279. *
  17280. * The **tpl** tag and the **if** operator are used to provide conditional checks for deciding whether or not to render
  17281. * specific parts of the template.
  17282. *
  17283. * Using the sample data above:
  17284. *
  17285. * var tpl = new Ext.XTemplate(
  17286. * '<p>Name: {name}</p>',
  17287. * '<p>Kids: ',
  17288. * '<tpl for="kids">',
  17289. * '<tpl if="age &gt; 1">',
  17290. * '<p>{name}</p>',
  17291. * '</tpl>',
  17292. * '</tpl></p>'
  17293. * );
  17294. * tpl.overwrite(panel.body, data);
  17295. *
  17296. * More advanced conditionals are also supported:
  17297. *
  17298. * var tpl = new Ext.XTemplate(
  17299. * '<p>Name: {name}</p>',
  17300. * '<p>Kids: ',
  17301. * '<tpl for="kids">',
  17302. * '<p>{name} is a ',
  17303. * '<tpl if="age &gt;= 13">',
  17304. * '<p>teenager</p>',
  17305. * '<tpl elseif="age &gt;= 2">',
  17306. * '<p>kid</p>',
  17307. * '<tpl else>',
  17308. * '<p>baby</p>',
  17309. * '</tpl>',
  17310. * '</tpl></p>'
  17311. * );
  17312. *
  17313. * var tpl = new Ext.XTemplate(
  17314. * '<p>Name: {name}</p>',
  17315. * '<p>Kids: ',
  17316. * '<tpl for="kids">',
  17317. * '<p>{name} is a ',
  17318. * '<tpl switch="name">',
  17319. * '<tpl case="Aubrey" case="Nikol">',
  17320. * '<p>girl</p>',
  17321. * '<tpl default">',
  17322. * '<p>boy</p>',
  17323. * '</tpl>',
  17324. * '</tpl></p>'
  17325. * );
  17326. *
  17327. * A `break` is implied between each case and default, however, multiple cases can be listed
  17328. * in a single &lt;tpl&gt; tag.
  17329. *
  17330. * # Using double quotes
  17331. *
  17332. * Examples:
  17333. *
  17334. * var tpl = new Ext.XTemplate(
  17335. * "<tpl if='age &gt; 1 && age &lt; 10'>Child</tpl>",
  17336. * "<tpl if='age &gt;= 10 && age &lt; 18'>Teenager</tpl>",
  17337. * "<tpl if='this.isGirl(name)'>...</tpl>",
  17338. * '<tpl if="id == \'download\'">...</tpl>',
  17339. * "<tpl if='needsIcon'><img src='{icon}' class='{iconCls}'/></tpl>",
  17340. * "<tpl if='name == \"Don\"'>Hello</tpl>"
  17341. * );
  17342. *
  17343. * # Basic math support
  17344. *
  17345. * The following basic math operators may be applied directly on numeric data values:
  17346. *
  17347. * + - * /
  17348. *
  17349. * For example:
  17350. *
  17351. * var tpl = new Ext.XTemplate(
  17352. * '<p>Name: {name}</p>',
  17353. * '<p>Kids: ',
  17354. * '<tpl for="kids">',
  17355. * '<tpl if="age &gt; 1">', // <-- Note that the > is encoded
  17356. * '<p>{#}: {name}</p>', // <-- Auto-number each item
  17357. * '<p>In 5 Years: {age+5}</p>', // <-- Basic math
  17358. * '<p>Dad: {parent.name}</p>',
  17359. * '</tpl>',
  17360. * '</tpl></p>'
  17361. * );
  17362. * tpl.overwrite(panel.body, data);
  17363. *
  17364. * # Execute arbitrary inline code with special built-in template variables
  17365. *
  17366. * Anything between `{[ ... ]}` is considered code to be executed in the scope of the template.
  17367. * The expression is evaluated and the result is included in the generated result. There are
  17368. * some special variables available in that code:
  17369. *
  17370. * - **out**: The output array into which the template is being appended (using `push` to later
  17371. * `join`).
  17372. * - **values**: The values in the current scope. If you are using scope changing sub-templates,
  17373. * you can change what values is.
  17374. * - **parent**: The scope (values) of the ancestor template.
  17375. * - **xindex**: If you are in a looping template, the index of the loop you are in (1-based).
  17376. * - **xcount**: If you are in a looping template, the total length of the array you are looping.
  17377. *
  17378. * This example demonstrates basic row striping using an inline code block and the xindex variable:
  17379. *
  17380. * var tpl = new Ext.XTemplate(
  17381. * '<p>Name: {name}</p>',
  17382. * '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
  17383. * '<p>Kids: ',
  17384. * '<tpl for="kids">',
  17385. * '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
  17386. * '{name}',
  17387. * '</div>',
  17388. * '</tpl></p>'
  17389. * );
  17390. *
  17391. * Any code contained in "verbatim" blocks (using "{% ... %}") will be inserted directly in
  17392. * the generated code for the template. These blocks are not included in the output. This
  17393. * can be used for simple things like break/continue in a loop, or control structures or
  17394. * method calls (when they don't produce output). The `this` references the template instance.
  17395. *
  17396. * var tpl = new Ext.XTemplate(
  17397. * '<p>Name: {name}</p>',
  17398. * '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
  17399. * '<p>Kids: ',
  17400. * '<tpl for="kids">',
  17401. * '{% if (xindex % 2 === 0) continue; %}',
  17402. * '{name}',
  17403. * '{% if (xindex > 100) break; %}',
  17404. * '</div>',
  17405. * '</tpl></p>'
  17406. * );
  17407. *
  17408. * # Template member functions
  17409. *
  17410. * One or more member functions can be specified in a configuration object passed into the XTemplate constructor for
  17411. * more complex processing:
  17412. *
  17413. * var tpl = new Ext.XTemplate(
  17414. * '<p>Name: {name}</p>',
  17415. * '<p>Kids: ',
  17416. * '<tpl for="kids">',
  17417. * '<tpl if="this.isGirl(name)">',
  17418. * '<p>Girl: {name} - {age}</p>',
  17419. * '<tpl else>',
  17420. * '<p>Boy: {name} - {age}</p>',
  17421. * '</tpl>',
  17422. * '<tpl if="this.isBaby(age)">',
  17423. * '<p>{name} is a baby!</p>',
  17424. * '</tpl>',
  17425. * '</tpl></p>',
  17426. * {
  17427. * // XTemplate configuration:
  17428. * disableFormats: true,
  17429. * // member functions:
  17430. * isGirl: function(name){
  17431. * return name == 'Sara Grace';
  17432. * },
  17433. * isBaby: function(age){
  17434. * return age < 1;
  17435. * }
  17436. * }
  17437. * );
  17438. * tpl.overwrite(panel.body, data);
  17439. */
  17440. Ext.define('Ext.XTemplate', {
  17441. extend: 'Ext.Template',
  17442. requires: 'Ext.XTemplateCompiler',
  17443. /**
  17444. * @private
  17445. */
  17446. emptyObj: {},
  17447. /**
  17448. * @cfg {Boolean} compiled
  17449. * Only applies to {@link Ext.Template}, XTemplates are compiled automatically on the
  17450. * first call to {@link #apply} or {@link #applyOut}.
  17451. * @hide
  17452. */
  17453. apply: function(values) {
  17454. return this.applyOut(values, []).join('');
  17455. },
  17456. /**
  17457. * Appends the result of this template to the provided output array.
  17458. * @param {Object/Array} values The template values. See {@link #apply}.
  17459. * @param {Array} out The array to which output is pushed.
  17460. * @param {Object} parent
  17461. * @return {Array} The given out array.
  17462. */
  17463. applyOut: function(values, out, parent) {
  17464. var me = this,
  17465. xindex = values.xindex,
  17466. xcount = values.xcount,
  17467. compiler;
  17468. if (!me.fn) {
  17469. compiler = new Ext.XTemplateCompiler({
  17470. useFormat : me.disableFormats !== true,
  17471. definitions : me.definitions
  17472. });
  17473. me.fn = compiler.compile(me.html);
  17474. }
  17475. try {
  17476. xindex = typeof xindex === 'number' ? xindex : 1;
  17477. xcount = typeof xcount === 'number' ? xcount : 1;
  17478. me.fn.call(me, out, values, parent || me.emptyObj, xindex, xcount);
  17479. } catch (e) {
  17480. //<debug>
  17481. Ext.Logger.log('Error: ' + e.message);
  17482. //</debug>
  17483. }
  17484. return out;
  17485. },
  17486. /**
  17487. * Does nothing. XTemplates are compiled automatically, so this function simply returns this.
  17488. * @return {Ext.XTemplate} this
  17489. */
  17490. compile: function() {
  17491. return this;
  17492. },
  17493. statics: {
  17494. /**
  17495. * Gets an `XTemplate` from an object (an instance of an {@link Ext#define}'d class).
  17496. * Many times, templates are configured high in the class hierarchy and are to be
  17497. * shared by all classes that derive from that base. To further complicate matters,
  17498. * these templates are seldom actual instances but are rather configurations. For
  17499. * example:
  17500. *
  17501. * Ext.define('MyApp.Class', {
  17502. * someTpl: [
  17503. * 'tpl text here'
  17504. * ]
  17505. * });
  17506. *
  17507. * The goal being to share that template definition with all instances and even
  17508. * instances of derived classes, until `someTpl` is overridden. This method will
  17509. * "upgrade" these configurations to be real `XTemplate` instances *in place* (to
  17510. * avoid creating one instance per object).
  17511. *
  17512. * @param {Object} instance The object from which to get the `XTemplate` (must be
  17513. * an instance of an {@link Ext#define}'d class).
  17514. * @param {String} name The name of the property by which to get the `XTemplate`.
  17515. * @return {Ext.XTemplate} The `XTemplate` instance or null if not found.
  17516. * @protected
  17517. */
  17518. getTpl: function (instance, name) {
  17519. var tpl = instance[name], // go for it! 99% of the time we will get it!
  17520. proto;
  17521. if (tpl && !tpl.isTemplate) { // tpl is just a configuration (not an instance)
  17522. // create the template instance from the configuration:
  17523. tpl = Ext.ClassManager.dynInstantiate('Ext.XTemplate', tpl);
  17524. // and replace the reference with the new instance:
  17525. if (instance.hasOwnProperty(name)) { // the tpl is on the instance
  17526. instance[name] = tpl;
  17527. } else { // must be somewhere in the prototype chain
  17528. for (proto = instance.self.prototype; proto; proto = proto.superclass) {
  17529. if (proto.hasOwnProperty(name)) {
  17530. proto[name] = tpl;
  17531. break;
  17532. }
  17533. }
  17534. }
  17535. }
  17536. // else !tpl (no such tpl) or the tpl is an instance already... either way, tpl
  17537. // is ready to return
  17538. return tpl || null;
  17539. }
  17540. }
  17541. });
  17542. /**
  17543. * @private
  17544. */
  17545. Ext.define('Ext.behavior.Behavior', {
  17546. constructor: function(component) {
  17547. this.component = component;
  17548. component.on('destroy', 'onComponentDestroy', this);
  17549. },
  17550. onComponentDestroy: Ext.emptyFn
  17551. });
  17552. /**
  17553. * @private
  17554. */
  17555. Ext.define('Ext.fx.easing.Abstract', {
  17556. config: {
  17557. startTime: 0,
  17558. startValue: 0
  17559. },
  17560. isEasing: true,
  17561. isEnded: false,
  17562. constructor: function(config) {
  17563. this.initConfig(config);
  17564. return this;
  17565. },
  17566. applyStartTime: function(startTime) {
  17567. if (!startTime) {
  17568. startTime = Ext.Date.now();
  17569. }
  17570. return startTime;
  17571. },
  17572. updateStartTime: function(startTime) {
  17573. this.reset();
  17574. },
  17575. reset: function() {
  17576. this.isEnded = false;
  17577. },
  17578. getValue: Ext.emptyFn
  17579. });
  17580. /**
  17581. * @private
  17582. */
  17583. Ext.define('Ext.fx.easing.Linear', {
  17584. extend: 'Ext.fx.easing.Abstract',
  17585. alias: 'easing.linear',
  17586. config: {
  17587. duration: 0,
  17588. endValue: 0
  17589. },
  17590. updateStartValue: function(startValue) {
  17591. this.distance = this.getEndValue() - startValue;
  17592. },
  17593. updateEndValue: function(endValue) {
  17594. this.distance = endValue - this.getStartValue();
  17595. },
  17596. getValue: function() {
  17597. var deltaTime = Ext.Date.now() - this.getStartTime(),
  17598. duration = this.getDuration();
  17599. if (deltaTime > duration) {
  17600. this.isEnded = true;
  17601. return this.getEndValue();
  17602. }
  17603. else {
  17604. return this.getStartValue() + ((deltaTime / duration) * this.distance);
  17605. }
  17606. }
  17607. });
  17608. /**
  17609. * @private
  17610. *
  17611. * The abstract class. Sub-classes are expected, at the very least, to implement translation logics inside
  17612. * the 'translate' method
  17613. */
  17614. Ext.define('Ext.util.translatable.Abstract', {
  17615. extend: 'Ext.Evented',
  17616. requires: ['Ext.fx.easing.Linear'],
  17617. config: {
  17618. easing: null,
  17619. easingX: null,
  17620. easingY: null,
  17621. fps: Ext.os.is.Android4 ? 50 : 60
  17622. },
  17623. /**
  17624. * @event animationstart
  17625. * Fires whenever the animation is started
  17626. * @param {Ext.util.translatable.Abstract} this
  17627. * @param {Number} x The current translation on the x axis
  17628. * @param {Number} y The current translation on the y axis
  17629. */
  17630. /**
  17631. * @event animationframe
  17632. * Fires for each animation frame
  17633. * @param {Ext.util.translatable.Abstract} this
  17634. * @param {Number} x The new translation on the x axis
  17635. * @param {Number} y The new translation on the y axis
  17636. */
  17637. /**
  17638. * @event animationend
  17639. * Fires whenever the animation is ended
  17640. * @param {Ext.util.translatable.Abstract} this
  17641. * @param {Number} x The current translation on the x axis
  17642. * @param {Number} y The current translation on the y axis
  17643. */
  17644. x: 0,
  17645. y: 0,
  17646. activeEasingX: null,
  17647. activeEasingY: null,
  17648. isAnimating: false,
  17649. isTranslatable: true,
  17650. constructor: function(config) {
  17651. this.doAnimationFrame = Ext.Function.bind(this.doAnimationFrame, this);
  17652. this.initConfig(config);
  17653. },
  17654. factoryEasing: function(easing) {
  17655. return Ext.factory(easing, Ext.fx.easing.Linear, null, 'easing');
  17656. },
  17657. applyEasing: function(easing) {
  17658. if (!this.getEasingX()) {
  17659. this.setEasingX(this.factoryEasing(easing));
  17660. }
  17661. if (!this.getEasingY()) {
  17662. this.setEasingY(this.factoryEasing(easing));
  17663. }
  17664. },
  17665. applyEasingX: function(easing) {
  17666. return this.factoryEasing(easing);
  17667. },
  17668. applyEasingY: function(easing) {
  17669. return this.factoryEasing(easing);
  17670. },
  17671. updateFps: function(fps) {
  17672. this.animationInterval = 1000 / fps;
  17673. },
  17674. doTranslate: Ext.emptyFn,
  17675. translate: function(x, y, animation) {
  17676. if (animation) {
  17677. return this.translateAnimated(x, y, animation);
  17678. }
  17679. if (this.isAnimating) {
  17680. this.stopAnimation();
  17681. }
  17682. if (!isNaN(x) && typeof x == 'number') {
  17683. this.x = x;
  17684. }
  17685. if (!isNaN(y) && typeof y == 'number') {
  17686. this.y = y;
  17687. }
  17688. this.doTranslate(x, y);
  17689. },
  17690. translateAxis: function(axis, value, animation) {
  17691. var x, y;
  17692. if (axis == 'x') {
  17693. x = value;
  17694. }
  17695. else {
  17696. y = value;
  17697. }
  17698. return this.translate(x, y, animation);
  17699. },
  17700. animate: function(easingX, easingY) {
  17701. this.activeEasingX = easingX;
  17702. this.activeEasingY = easingY;
  17703. this.isAnimating = true;
  17704. this.lastX = null;
  17705. this.lastY = null;
  17706. this.animationFrameId = requestAnimationFrame(this.doAnimationFrame);
  17707. this.fireEvent('animationstart', this, this.x, this.y);
  17708. return this;
  17709. },
  17710. translateAnimated: function(x, y, animation) {
  17711. if (!Ext.isObject(animation)) {
  17712. animation = {};
  17713. }
  17714. if (this.isAnimating) {
  17715. this.stopAnimation();
  17716. }
  17717. var now = Ext.Date.now(),
  17718. easing = animation.easing,
  17719. easingX = (typeof x == 'number') ? (animation.easingX || easing || this.getEasingX() || true) : null,
  17720. easingY = (typeof y == 'number') ? (animation.easingY || easing || this.getEasingY() || true) : null;
  17721. if (easingX) {
  17722. easingX = this.factoryEasing(easingX);
  17723. easingX.setStartTime(now);
  17724. easingX.setStartValue(this.x);
  17725. easingX.setEndValue(x);
  17726. if ('duration' in animation) {
  17727. easingX.setDuration(animation.duration);
  17728. }
  17729. }
  17730. if (easingY) {
  17731. easingY = this.factoryEasing(easingY);
  17732. easingY.setStartTime(now);
  17733. easingY.setStartValue(this.y);
  17734. easingY.setEndValue(y);
  17735. if ('duration' in animation) {
  17736. easingY.setDuration(animation.duration);
  17737. }
  17738. }
  17739. return this.animate(easingX, easingY);
  17740. },
  17741. doAnimationFrame: function() {
  17742. var me = this,
  17743. easingX = me.activeEasingX,
  17744. easingY = me.activeEasingY,
  17745. now = Date.now(),
  17746. x, y;
  17747. this.animationFrameId = requestAnimationFrame(this.doAnimationFrame);
  17748. if (!me.isAnimating) {
  17749. return;
  17750. }
  17751. me.lastRun = now;
  17752. if (easingX === null && easingY === null) {
  17753. me.stopAnimation();
  17754. return;
  17755. }
  17756. if (easingX !== null) {
  17757. me.x = x = Math.round(easingX.getValue());
  17758. if (easingX.isEnded) {
  17759. me.activeEasingX = null;
  17760. me.fireEvent('axisanimationend', me, 'x', x);
  17761. }
  17762. }
  17763. else {
  17764. x = me.x;
  17765. }
  17766. if (easingY !== null) {
  17767. me.y = y = Math.round(easingY.getValue());
  17768. if (easingY.isEnded) {
  17769. me.activeEasingY = null;
  17770. me.fireEvent('axisanimationend', me, 'y', y);
  17771. }
  17772. }
  17773. else {
  17774. y = me.y;
  17775. }
  17776. if (me.lastX !== x || me.lastY !== y) {
  17777. me.doTranslate(x, y);
  17778. me.lastX = x;
  17779. me.lastY = y;
  17780. }
  17781. me.fireEvent('animationframe', me, x, y);
  17782. },
  17783. stopAnimation: function() {
  17784. if (!this.isAnimating) {
  17785. return;
  17786. }
  17787. this.activeEasingX = null;
  17788. this.activeEasingY = null;
  17789. this.isAnimating = false;
  17790. cancelAnimationFrame(this.animationFrameId);
  17791. this.fireEvent('animationend', this, this.x, this.y);
  17792. },
  17793. refresh: function() {
  17794. this.translate(this.x, this.y);
  17795. },
  17796. destroy: function() {
  17797. if (this.isAnimating) {
  17798. this.stopAnimation();
  17799. }
  17800. this.callParent(arguments);
  17801. }
  17802. });
  17803. /**
  17804. * @private
  17805. */
  17806. Ext.define('Ext.util.translatable.Dom', {
  17807. extend: 'Ext.util.translatable.Abstract',
  17808. config: {
  17809. element: null
  17810. },
  17811. applyElement: function(element) {
  17812. if (!element) {
  17813. return;
  17814. }
  17815. return Ext.get(element);
  17816. },
  17817. updateElement: function() {
  17818. this.refresh();
  17819. }
  17820. });
  17821. /**
  17822. * @private
  17823. *
  17824. * CSS Transform implementation
  17825. */
  17826. Ext.define('Ext.util.translatable.CssTransform', {
  17827. extend: 'Ext.util.translatable.Dom',
  17828. doTranslate: function() {
  17829. this.getElement().dom.style.webkitTransform = 'translate3d(' + this.x + 'px, ' + this.y + 'px, 0px)';
  17830. },
  17831. destroy: function() {
  17832. var element = this.getElement();
  17833. if (element && !element.isDestroyed) {
  17834. element.dom.style.webkitTransform = null;
  17835. }
  17836. this.callSuper();
  17837. }
  17838. });
  17839. /**
  17840. * @private
  17841. *
  17842. * Scroll position implementation
  17843. */
  17844. Ext.define('Ext.util.translatable.ScrollPosition', {
  17845. extend: 'Ext.util.translatable.Dom',
  17846. wrapperWidth: 0,
  17847. wrapperHeight: 0,
  17848. config: {
  17849. useWrapper: true
  17850. },
  17851. getWrapper: function() {
  17852. var wrapper = this.wrapper,
  17853. element = this.getElement(),
  17854. container;
  17855. if (!wrapper) {
  17856. container = element.getParent();
  17857. if (!container) {
  17858. return null;
  17859. }
  17860. if (this.getUseWrapper()) {
  17861. wrapper = element.wrap();
  17862. }
  17863. else {
  17864. wrapper = container;
  17865. }
  17866. element.addCls('x-translatable');
  17867. wrapper.addCls('x-translatable-container');
  17868. this.wrapper = wrapper;
  17869. wrapper.on('resize', 'onWrapperResize', this);
  17870. wrapper.on('painted', 'refresh', this);
  17871. this.refresh();
  17872. }
  17873. return wrapper;
  17874. },
  17875. doTranslate: function(x, y) {
  17876. var wrapper = this.getWrapper(),
  17877. dom;
  17878. if (wrapper) {
  17879. dom = wrapper.dom;
  17880. if (typeof x == 'number') {
  17881. dom.scrollLeft = this.wrapperWidth - x;
  17882. }
  17883. if (typeof y == 'number') {
  17884. dom.scrollTop = this.wrapperHeight - y;
  17885. }
  17886. }
  17887. },
  17888. onWrapperResize: function(wrapper, info) {
  17889. this.wrapperWidth = info.width;
  17890. this.wrapperHeight = info.height;
  17891. this.refresh();
  17892. },
  17893. destroy: function() {
  17894. var element = this.getElement(),
  17895. wrapper = this.wrapper;
  17896. if (wrapper) {
  17897. if (!element.isDestroyed) {
  17898. if (this.getUseWrapper()) {
  17899. wrapper.doReplaceWith(element);
  17900. }
  17901. element.removeCls('x-translatable');
  17902. }
  17903. wrapper.removeCls('x-translatable-container');
  17904. wrapper.un('resize', 'onWrapperResize', this);
  17905. wrapper.un('painted', 'refresh', this);
  17906. delete this.wrapper;
  17907. delete this._element;
  17908. }
  17909. this.callSuper();
  17910. }
  17911. });
  17912. /**
  17913. * The utility class to abstract different implementations to have the best performance when applying 2D translation
  17914. * on any DOM element.
  17915. *
  17916. * @private
  17917. */
  17918. Ext.define('Ext.util.Translatable', {
  17919. requires: [
  17920. 'Ext.util.translatable.CssTransform',
  17921. 'Ext.util.translatable.ScrollPosition'
  17922. ],
  17923. constructor: function(config) {
  17924. var namespace = Ext.util.translatable,
  17925. CssTransform = namespace.CssTransform,
  17926. ScrollPosition = namespace.ScrollPosition,
  17927. classReference;
  17928. if (typeof config == 'object' && 'translationMethod' in config) {
  17929. if (config.translationMethod === 'scrollposition') {
  17930. classReference = ScrollPosition;
  17931. }
  17932. else if (config.translationMethod === 'csstransform') {
  17933. classReference = CssTransform;
  17934. }
  17935. }
  17936. if (!classReference) {
  17937. if (Ext.os.is.Android2 || Ext.browser.is.ChromeMobile) {
  17938. classReference = ScrollPosition;
  17939. }
  17940. else {
  17941. classReference = CssTransform;
  17942. }
  17943. }
  17944. return new classReference(config);
  17945. }
  17946. });
  17947. /**
  17948. * @private
  17949. */
  17950. Ext.define('Ext.behavior.Translatable', {
  17951. extend: 'Ext.behavior.Behavior',
  17952. requires: [
  17953. 'Ext.util.Translatable'
  17954. ],
  17955. setConfig: function(config) {
  17956. var translatable = this.translatable,
  17957. component = this.component;
  17958. if (config) {
  17959. if (!translatable) {
  17960. this.translatable = translatable = new Ext.util.Translatable(config);
  17961. translatable.setElement(component.renderElement);
  17962. translatable.on('destroy', 'onTranslatableDestroy', this);
  17963. }
  17964. else if (Ext.isObject(config)) {
  17965. translatable.setConfig(config);
  17966. }
  17967. }
  17968. else if (translatable) {
  17969. translatable.destroy();
  17970. }
  17971. return this;
  17972. },
  17973. getTranslatable: function() {
  17974. return this.translatable;
  17975. },
  17976. onTranslatableDestroy: function() {
  17977. delete this.translatable;
  17978. },
  17979. onComponentDestroy: function() {
  17980. var translatable = this.translatable;
  17981. if (translatable) {
  17982. translatable.destroy();
  17983. }
  17984. }
  17985. });
  17986. /**
  17987. * A core util class to bring Draggable behavior to any DOM element
  17988. */
  17989. Ext.define('Ext.util.Draggable', {
  17990. isDraggable: true,
  17991. mixins: [
  17992. 'Ext.mixin.Observable'
  17993. ],
  17994. requires: [
  17995. 'Ext.util.Translatable'
  17996. ],
  17997. /**
  17998. * @event dragstart
  17999. * @preventable initDragStart
  18000. * Fires whenever the component starts to be dragged
  18001. * @param {Ext.util.Draggable} this
  18002. * @param {Ext.event.Event} e the event object
  18003. * @param {Number} offsetX The current offset value on the x axis
  18004. * @param {Number} offsetY The current offset value on the y axis
  18005. */
  18006. /**
  18007. * @event drag
  18008. * Fires whenever the component is dragged
  18009. * @param {Ext.util.Draggable} this
  18010. * @param {Ext.event.Event} e the event object
  18011. * @param {Number} offsetX The new offset value on the x axis
  18012. * @param {Number} offsetY The new offset value on the y axis
  18013. */
  18014. /**
  18015. * @event dragend
  18016. * Fires whenever the component is dragged
  18017. * @param {Ext.util.Draggable} this
  18018. * @param {Ext.event.Event} e the event object
  18019. * @param {Number} offsetX The current offset value on the x axis
  18020. * @param {Number} offsetY The current offset value on the y axis
  18021. */
  18022. config: {
  18023. cls: Ext.baseCSSPrefix + 'draggable',
  18024. draggingCls: Ext.baseCSSPrefix + 'dragging',
  18025. element: null,
  18026. constraint: 'container',
  18027. disabled: null,
  18028. /**
  18029. * @cfg {String} direction
  18030. * Possible values: 'vertical', 'horizontal', or 'both'
  18031. * @accessor
  18032. */
  18033. direction: 'both',
  18034. /**
  18035. * @cfg {Object/Number} initialOffset
  18036. * The initial draggable offset. When specified as Number,
  18037. * both x and y will be set to that value.
  18038. */
  18039. initialOffset: {
  18040. x: 0,
  18041. y: 0
  18042. },
  18043. translatable: {}
  18044. },
  18045. DIRECTION_BOTH: 'both',
  18046. DIRECTION_VERTICAL: 'vertical',
  18047. DIRECTION_HORIZONTAL: 'horizontal',
  18048. defaultConstraint: {
  18049. min: { x: -Infinity, y: -Infinity },
  18050. max: { x: Infinity, y: Infinity }
  18051. },
  18052. containerWidth: 0,
  18053. containerHeight: 0,
  18054. width: 0,
  18055. height: 0,
  18056. /**
  18057. * Creates new Draggable.
  18058. * @param {Object} config The configuration object for this Draggable.
  18059. */
  18060. constructor: function(config) {
  18061. var element;
  18062. this.extraConstraint = {};
  18063. this.initialConfig = config;
  18064. this.offset = {
  18065. x: 0,
  18066. y: 0
  18067. };
  18068. this.listeners = {
  18069. dragstart: 'onDragStart',
  18070. drag : 'onDrag',
  18071. dragend : 'onDragEnd',
  18072. resize : 'onElementResize',
  18073. scope: this
  18074. };
  18075. if (config && config.element) {
  18076. element = config.element;
  18077. delete config.element;
  18078. this.setElement(element);
  18079. }
  18080. return this;
  18081. },
  18082. applyElement: function(element) {
  18083. if (!element) {
  18084. return;
  18085. }
  18086. return Ext.get(element);
  18087. },
  18088. updateElement: function(element) {
  18089. element.on(this.listeners);
  18090. this.initConfig(this.initialConfig);
  18091. },
  18092. updateInitialOffset: function(initialOffset) {
  18093. if (typeof initialOffset == 'number') {
  18094. initialOffset = {
  18095. x: initialOffset,
  18096. y: initialOffset
  18097. };
  18098. }
  18099. var offset = this.offset,
  18100. x, y;
  18101. offset.x = x = initialOffset.x;
  18102. offset.y = y = initialOffset.y;
  18103. this.getTranslatable().translate(x, y);
  18104. },
  18105. updateCls: function(cls) {
  18106. this.getElement().addCls(cls);
  18107. },
  18108. applyTranslatable: function(translatable, currentInstance) {
  18109. translatable = Ext.factory(translatable, Ext.util.Translatable, currentInstance);
  18110. translatable.setElement(this.getElement());
  18111. return translatable;
  18112. },
  18113. setExtraConstraint: function(constraint) {
  18114. this.extraConstraint = constraint || {};
  18115. this.refreshConstraint();
  18116. return this;
  18117. },
  18118. addExtraConstraint: function(constraint) {
  18119. Ext.merge(this.extraConstraint, constraint);
  18120. this.refreshConstraint();
  18121. return this;
  18122. },
  18123. applyConstraint: function(newConstraint) {
  18124. this.currentConstraint = newConstraint;
  18125. if (!newConstraint) {
  18126. newConstraint = this.defaultConstraint;
  18127. }
  18128. if (newConstraint === 'container') {
  18129. return Ext.merge(this.getContainerConstraint(), this.extraConstraint);
  18130. }
  18131. return Ext.merge({}, this.extraConstraint, newConstraint);
  18132. },
  18133. updateConstraint: function() {
  18134. this.refreshOffset();
  18135. },
  18136. getContainerConstraint: function() {
  18137. var container = this.getContainer(),
  18138. element = this.getElement();
  18139. if (!container || !element.dom) {
  18140. return this.defaultConstraint;
  18141. }
  18142. return {
  18143. min: { x: 0, y: 0 },
  18144. max: { x: this.containerWidth - this.width, y: this.containerHeight - this.height }
  18145. };
  18146. },
  18147. getContainer: function() {
  18148. var container = this.container;
  18149. if (!container) {
  18150. container = this.getElement().getParent();
  18151. if (container) {
  18152. this.container = container;
  18153. container.on({
  18154. resize: 'onContainerResize',
  18155. destroy: 'onContainerDestroy',
  18156. scope: this
  18157. });
  18158. }
  18159. }
  18160. return container;
  18161. },
  18162. onElementResize: function(element, info) {
  18163. this.width = info.width;
  18164. this.height = info.height;
  18165. this.refresh();
  18166. },
  18167. onContainerResize: function(container, info) {
  18168. this.containerWidth = info.width;
  18169. this.containerHeight = info.height;
  18170. this.refresh();
  18171. },
  18172. onContainerDestroy: function() {
  18173. delete this.container;
  18174. delete this.containerSizeMonitor;
  18175. },
  18176. detachListeners: function() {
  18177. this.getElement().un(this.listeners);
  18178. },
  18179. isAxisEnabled: function(axis) {
  18180. var direction = this.getDirection();
  18181. if (axis === 'x') {
  18182. return (direction === this.DIRECTION_BOTH || direction === this.DIRECTION_HORIZONTAL);
  18183. }
  18184. return (direction === this.DIRECTION_BOTH || direction === this.DIRECTION_VERTICAL);
  18185. },
  18186. onDragStart: function(e) {
  18187. if (this.getDisabled()) {
  18188. return false;
  18189. }
  18190. var offset = this.offset;
  18191. this.fireAction('dragstart', [this, e, offset.x, offset.y], this.initDragStart);
  18192. },
  18193. initDragStart: function(me, e, offsetX, offsetY) {
  18194. this.dragStartOffset = {
  18195. x: offsetX,
  18196. y: offsetY
  18197. };
  18198. this.isDragging = true;
  18199. this.getElement().addCls(this.getDraggingCls());
  18200. },
  18201. onDrag: function(e) {
  18202. if (!this.isDragging) {
  18203. return;
  18204. }
  18205. var startOffset = this.dragStartOffset;
  18206. this.fireAction('drag', [this, e, startOffset.x + e.deltaX, startOffset.y + e.deltaY], this.doDrag);
  18207. },
  18208. doDrag: function(me, e, offsetX, offsetY) {
  18209. me.setOffset(offsetX, offsetY);
  18210. },
  18211. onDragEnd: function(e) {
  18212. if (!this.isDragging) {
  18213. return;
  18214. }
  18215. this.onDrag(e);
  18216. this.isDragging = false;
  18217. this.getElement().removeCls(this.getDraggingCls());
  18218. this.fireEvent('dragend', this, e, this.offset.x, this.offset.y);
  18219. },
  18220. setOffset: function(x, y, animation) {
  18221. var currentOffset = this.offset,
  18222. constraint = this.getConstraint(),
  18223. minOffset = constraint.min,
  18224. maxOffset = constraint.max,
  18225. min = Math.min,
  18226. max = Math.max;
  18227. if (this.isAxisEnabled('x') && typeof x == 'number') {
  18228. x = min(max(x, minOffset.x), maxOffset.x);
  18229. }
  18230. else {
  18231. x = currentOffset.x;
  18232. }
  18233. if (this.isAxisEnabled('y') && typeof y == 'number') {
  18234. y = min(max(y, minOffset.y), maxOffset.y);
  18235. }
  18236. else {
  18237. y = currentOffset.y;
  18238. }
  18239. currentOffset.x = x;
  18240. currentOffset.y = y;
  18241. this.getTranslatable().translate(x, y, animation);
  18242. },
  18243. getOffset: function() {
  18244. return this.offset;
  18245. },
  18246. refreshConstraint: function() {
  18247. this.setConstraint(this.currentConstraint);
  18248. },
  18249. refreshOffset: function() {
  18250. var offset = this.offset;
  18251. this.setOffset(offset.x, offset.y);
  18252. },
  18253. refresh: function() {
  18254. this.refreshConstraint();
  18255. this.getTranslatable().refresh();
  18256. this.refreshOffset();
  18257. },
  18258. /**
  18259. * Enable the Draggable.
  18260. * @return {Ext.util.Draggable} This Draggable instance
  18261. */
  18262. enable: function() {
  18263. return this.setDisabled(false);
  18264. },
  18265. /**
  18266. * Disable the Draggable.
  18267. * @return {Ext.util.Draggable} This Draggable instance
  18268. */
  18269. disable: function() {
  18270. return this.setDisabled(true);
  18271. },
  18272. destroy: function() {
  18273. var translatable = this.getTranslatable();
  18274. var element = this.getElement();
  18275. if (element && !element.isDestroyed) {
  18276. element.removeCls(this.getCls());
  18277. }
  18278. this.detachListeners();
  18279. if (translatable) {
  18280. translatable.destroy();
  18281. }
  18282. }
  18283. }, function() {
  18284. });
  18285. /**
  18286. * @private
  18287. */
  18288. Ext.define('Ext.behavior.Draggable', {
  18289. extend: 'Ext.behavior.Behavior',
  18290. requires: [
  18291. 'Ext.util.Draggable'
  18292. ],
  18293. setConfig: function(config) {
  18294. var draggable = this.draggable,
  18295. component = this.component;
  18296. if (config) {
  18297. if (!draggable) {
  18298. component.setTranslatable(true);
  18299. this.draggable = draggable = new Ext.util.Draggable(config);
  18300. draggable.setTranslatable(component.getTranslatable());
  18301. draggable.setElement(component.renderElement);
  18302. draggable.on('destroy', 'onDraggableDestroy', this);
  18303. component.on(this.listeners);
  18304. }
  18305. else if (Ext.isObject(config)) {
  18306. draggable.setConfig(config);
  18307. }
  18308. }
  18309. else if (draggable) {
  18310. draggable.destroy();
  18311. }
  18312. return this;
  18313. },
  18314. getDraggable: function() {
  18315. return this.draggable;
  18316. },
  18317. onDraggableDestroy: function() {
  18318. delete this.draggable;
  18319. },
  18320. onComponentDestroy: function() {
  18321. var draggable = this.draggable;
  18322. if (draggable) {
  18323. draggable.destroy();
  18324. }
  18325. }
  18326. });
  18327. /**
  18328. * A Traversable mixin.
  18329. * @private
  18330. */
  18331. Ext.define('Ext.mixin.Traversable', {
  18332. extend: 'Ext.mixin.Mixin',
  18333. mixinConfig: {
  18334. id: 'traversable'
  18335. },
  18336. setParent: function(parent) {
  18337. this.parent = parent;
  18338. return this;
  18339. },
  18340. /**
  18341. * @member Ext.Component
  18342. * Returns `true` if this component has a parent.
  18343. * @return {Boolean} `true` if this component has a parent.
  18344. */
  18345. hasParent: function() {
  18346. return Boolean(this.parent);
  18347. },
  18348. /**
  18349. * @member Ext.Component
  18350. * Returns the parent of this component, if it has one.
  18351. * @return {Ext.Component} The parent of this component.
  18352. */
  18353. getParent: function() {
  18354. return this.parent;
  18355. },
  18356. getAncestors: function() {
  18357. var ancestors = [],
  18358. parent = this.getParent();
  18359. while (parent) {
  18360. ancestors.push(parent);
  18361. parent = parent.getParent();
  18362. }
  18363. return ancestors;
  18364. },
  18365. getAncestorIds: function() {
  18366. var ancestorIds = [],
  18367. parent = this.getParent();
  18368. while (parent) {
  18369. ancestorIds.push(parent.getId());
  18370. parent = parent.getParent();
  18371. }
  18372. return ancestorIds;
  18373. }
  18374. });
  18375. (function(clsPrefix) {
  18376. /**
  18377. * Most of the visual classes you interact with in Sencha Touch are Components. Every Component in Sencha Touch is a
  18378. * subclass of Ext.Component, which means they can all:
  18379. *
  18380. * * Render themselves onto the page using a template
  18381. * * Show and hide themselves at any time
  18382. * * Center themselves on the screen
  18383. * * Enable and disable themselves
  18384. *
  18385. * They can also do a few more advanced things:
  18386. *
  18387. * * Float above other components (windows, message boxes and overlays)
  18388. * * Change size and position on the screen with animation
  18389. * * Dock other Components inside themselves (useful for toolbars)
  18390. * * Align to other components, allow themselves to be dragged around, make their content scrollable & more
  18391. *
  18392. * ## Available Components
  18393. *
  18394. * There are many components available in Sencha Touch, separated into 4 main groups:
  18395. *
  18396. * ### Navigation components
  18397. * * {@link Ext.Toolbar}
  18398. * * {@link Ext.Button}
  18399. * * {@link Ext.TitleBar}
  18400. * * {@link Ext.SegmentedButton}
  18401. * * {@link Ext.Title}
  18402. * * {@link Ext.Spacer}
  18403. *
  18404. * ### Store-bound components
  18405. * * {@link Ext.dataview.DataView}
  18406. * * {@link Ext.Carousel}
  18407. * * {@link Ext.List}
  18408. * * {@link Ext.NestedList}
  18409. *
  18410. * ### Form components
  18411. * * {@link Ext.form.Panel}
  18412. * * {@link Ext.form.FieldSet}
  18413. * * {@link Ext.field.Checkbox}
  18414. * * {@link Ext.field.Hidden}
  18415. * * {@link Ext.field.Slider}
  18416. * * {@link Ext.field.Text}
  18417. * * {@link Ext.picker.Picker}
  18418. * * {@link Ext.picker.Date}
  18419. *
  18420. * ### General components
  18421. * * {@link Ext.Panel}
  18422. * * {@link Ext.tab.Panel}
  18423. * * {@link Ext.Viewport Ext.Viewport}
  18424. * * {@link Ext.Img}
  18425. * * {@link Ext.Map}
  18426. * * {@link Ext.Audio}
  18427. * * {@link Ext.Video}
  18428. * * {@link Ext.Sheet}
  18429. * * {@link Ext.ActionSheet}
  18430. * * {@link Ext.MessageBox}
  18431. *
  18432. *
  18433. * ## Instantiating Components
  18434. *
  18435. * Components are created the same way as all other classes in Sencha Touch - using Ext.create. Here's how we can
  18436. * create a Text field:
  18437. *
  18438. * var panel = Ext.create('Ext.Panel', {
  18439. * html: 'This is my panel'
  18440. * });
  18441. *
  18442. * This will create a {@link Ext.Panel Panel} instance, configured with some basic HTML content. A Panel is just a
  18443. * simple Component that can render HTML and also contain other items. In this case we've created a Panel instance but
  18444. * it won't show up on the screen yet because items are not rendered immediately after being instantiated. This allows
  18445. * us to create some components and move them around before rendering and laying them out, which is a good deal faster
  18446. * than moving them after rendering.
  18447. *
  18448. * To show this panel on the screen now we can simply add it to the global Viewport:
  18449. *
  18450. * Ext.Viewport.add(panel);
  18451. *
  18452. * Panels are also Containers, which means they can contain other Components, arranged by a layout. Let's revisit the
  18453. * above example now, this time creating a panel with two child Components and a hbox layout:
  18454. *
  18455. * @example
  18456. * var panel = Ext.create('Ext.Panel', {
  18457. * layout: 'hbox',
  18458. *
  18459. * items: [
  18460. * {
  18461. * xtype: 'panel',
  18462. * flex: 1,
  18463. * html: 'Left Panel, 1/3rd of total size',
  18464. * style: 'background-color: #5E99CC;'
  18465. * },
  18466. * {
  18467. * xtype: 'panel',
  18468. * flex: 2,
  18469. * html: 'Right Panel, 2/3rds of total size',
  18470. * style: 'background-color: #759E60;'
  18471. * }
  18472. * ]
  18473. * });
  18474. *
  18475. * Ext.Viewport.add(panel);
  18476. *
  18477. * This time we created 3 Panels - the first one is created just as before but the inner two are declared inline using
  18478. * an xtype. Xtype is a convenient way of creating Components without having to go through the process of using
  18479. * Ext.create and specifying the full class name, instead you can just provide the xtype for the class inside an object
  18480. * and the framework will create the components for you.
  18481. *
  18482. * We also specified a layout for the top level panel - in this case hbox, which splits the horizontal width of the
  18483. * parent panel based on the 'flex' of each child. For example, if the parent Panel above is 300px wide then the first
  18484. * child will be flexed to 100px wide and the second to 200px because the first one was given `flex: 1` and the second
  18485. * `flex: 2`.
  18486. *
  18487. * ## Using xtype
  18488. *
  18489. * xtype is an easy way to create Components without using the full class name. This is especially useful when creating
  18490. * a {@link Ext.Container Container} that contains child Components. An xtype is simply a shorthand way of specifying a
  18491. * Component - for example you can use `xtype: 'panel'` instead of typing out Ext.panel.Panel.
  18492. *
  18493. * Sample usage:
  18494. *
  18495. * @example miniphone
  18496. * Ext.create('Ext.Container', {
  18497. * fullscreen: true,
  18498. * layout: 'fit',
  18499. *
  18500. * items: [
  18501. * {
  18502. * xtype: 'panel',
  18503. * html: 'This panel is created by xtype'
  18504. * },
  18505. * {
  18506. * xtype: 'toolbar',
  18507. * title: 'So is the toolbar',
  18508. * docked: 'top'
  18509. * }
  18510. * ]
  18511. * });
  18512. *
  18513. *
  18514. * ### Common xtypes
  18515. *
  18516. * These are the xtypes that are most commonly used. For an exhaustive list please see the
  18517. * [Components Guide](#!/guide/components).
  18518. *
  18519. * <pre>
  18520. xtype Class
  18521. ----------------- ---------------------
  18522. actionsheet Ext.ActionSheet
  18523. audio Ext.Audio
  18524. button Ext.Button
  18525. image Ext.Img
  18526. label Ext.Label
  18527. loadmask Ext.LoadMask
  18528. map Ext.Map
  18529. panel Ext.Panel
  18530. segmentedbutton Ext.SegmentedButton
  18531. sheet Ext.Sheet
  18532. spacer Ext.Spacer
  18533. titlebar Ext.TitleBar
  18534. toolbar Ext.Toolbar
  18535. video Ext.Video
  18536. carousel Ext.carousel.Carousel
  18537. navigationview Ext.navigation.View
  18538. datepicker Ext.picker.Date
  18539. picker Ext.picker.Picker
  18540. slider Ext.slider.Slider
  18541. thumb Ext.slider.Thumb
  18542. tabpanel Ext.tab.Panel
  18543. viewport Ext.viewport.Default
  18544. DataView Components
  18545. ---------------------------------------------
  18546. dataview Ext.dataview.DataView
  18547. list Ext.dataview.List
  18548. nestedlist Ext.dataview.NestedList
  18549. Form Components
  18550. ---------------------------------------------
  18551. checkboxfield Ext.field.Checkbox
  18552. datepickerfield Ext.field.DatePicker
  18553. emailfield Ext.field.Email
  18554. hiddenfield Ext.field.Hidden
  18555. numberfield Ext.field.Number
  18556. passwordfield Ext.field.Password
  18557. radiofield Ext.field.Radio
  18558. searchfield Ext.field.Search
  18559. selectfield Ext.field.Select
  18560. sliderfield Ext.field.Slider
  18561. spinnerfield Ext.field.Spinner
  18562. textfield Ext.field.Text
  18563. textareafield Ext.field.TextArea
  18564. togglefield Ext.field.Toggle
  18565. urlfield Ext.field.Url
  18566. fieldset Ext.form.FieldSet
  18567. formpanel Ext.form.Panel
  18568. * </pre>
  18569. *
  18570. * ## Configuring Components
  18571. *
  18572. * Whenever you create a new Component you can pass in configuration options. All of the configurations for a given
  18573. * Component are listed in the "Config options" section of its class docs page. You can pass in any number of
  18574. * configuration options when you instantiate the Component, and modify any of them at any point later. For example, we
  18575. * can easily modify the {@link Ext.Panel#html html content} of a Panel after creating it:
  18576. *
  18577. * @example miniphone
  18578. * // we can configure the HTML when we instantiate the Component
  18579. * var panel = Ext.create('Ext.Panel', {
  18580. * fullscreen: true,
  18581. * html: 'This is a Panel'
  18582. * });
  18583. *
  18584. * // we can update the HTML later using the setHtml method:
  18585. * panel.setHtml('Some new HTML');
  18586. *
  18587. * // we can retrieve the current HTML using the getHtml method:
  18588. * Ext.Msg.alert(panel.getHtml()); // displays "Some new HTML"
  18589. *
  18590. * Every config has a getter method and a setter method - these are automatically generated and always follow the same
  18591. * pattern. For example, a config called `html` will receive `getHtml` and `setHtml` methods, a config called `defaultType`
  18592. * will receive `getDefaultType` and `setDefaultType` methods, and so on.
  18593. *
  18594. * ## Further Reading
  18595. *
  18596. * See the [Component & Container Guide](#!/guide/components) for more information, and check out the
  18597. * {@link Ext.Container} class docs also.
  18598. *
  18599. * @aside guide components
  18600. * @aside guide events
  18601. *
  18602. */
  18603. Ext.define('Ext.Component', {
  18604. extend: 'Ext.AbstractComponent',
  18605. alternateClassName: 'Ext.lib.Component',
  18606. mixins: ['Ext.mixin.Traversable'],
  18607. requires: [
  18608. 'Ext.ComponentManager',
  18609. 'Ext.XTemplate',
  18610. 'Ext.dom.Element',
  18611. 'Ext.behavior.Translatable',
  18612. 'Ext.behavior.Draggable'
  18613. ],
  18614. /**
  18615. * @cfg {String} xtype
  18616. * The `xtype` configuration option can be used to optimize Component creation and rendering. It serves as a
  18617. * shortcut to the full component name. For example, the component `Ext.button.Button` has an xtype of `button`.
  18618. *
  18619. * You can define your own xtype on a custom {@link Ext.Component component} by specifying the
  18620. * {@link Ext.Class#alias alias} config option with a prefix of `widget`. For example:
  18621. *
  18622. * Ext.define('PressMeButton', {
  18623. * extend: 'Ext.button.Button',
  18624. * alias: 'widget.pressmebutton',
  18625. * text: 'Press Me'
  18626. * });
  18627. *
  18628. * Any Component can be created implicitly as an object config with an xtype specified, allowing it to be
  18629. * declared and passed into the rendering pipeline without actually being instantiated as an object. Not only is
  18630. * rendering deferred, but the actual creation of the object itself is also deferred, saving memory and resources
  18631. * until they are actually needed. In complex, nested layouts containing many Components, this can make a
  18632. * noticeable improvement in performance.
  18633. *
  18634. * // Explicit creation of contained Components:
  18635. * var panel = new Ext.Panel({
  18636. * // ...
  18637. * items: [
  18638. * Ext.create('Ext.button.Button', {
  18639. * text: 'OK'
  18640. * })
  18641. * ]
  18642. * });
  18643. *
  18644. * // Implicit creation using xtype:
  18645. * var panel = new Ext.Panel({
  18646. * // ...
  18647. * items: [{
  18648. * xtype: 'button',
  18649. * text: 'OK'
  18650. * }]
  18651. * });
  18652. *
  18653. * In the first example, the button will always be created immediately during the panel's initialization. With
  18654. * many added Components, this approach could potentially slow the rendering of the page. In the second example,
  18655. * the button will not be created or rendered until the panel is actually displayed in the browser. If the panel
  18656. * is never displayed (for example, if it is a tab that remains hidden) then the button will never be created and
  18657. * will never consume any resources whatsoever.
  18658. */
  18659. xtype: 'component',
  18660. observableType: 'component',
  18661. cachedConfig: {
  18662. /**
  18663. * @cfg {String} baseCls
  18664. * The base CSS class to apply to this component's element. This will also be prepended to
  18665. * other elements within this component. To add specific styling for sub-classes, use the {@link #cls} config.
  18666. * @accessor
  18667. */
  18668. baseCls: null,
  18669. /**
  18670. * @cfg {String/String[]} cls The CSS class to add to this component's element, in addition to the {@link #baseCls}
  18671. * @accessor
  18672. */
  18673. cls: null,
  18674. /**
  18675. * @cfg {String} [floatingCls="x-floating"] The CSS class to add to this component when it is floatable.
  18676. * @accessor
  18677. */
  18678. floatingCls: clsPrefix + 'floating',
  18679. /**
  18680. * @cfg {String} [hiddenCls="x-item-hidden"] The CSS class to add to the component when it is hidden
  18681. * @accessor
  18682. */
  18683. hiddenCls: clsPrefix + 'item-hidden',
  18684. /**
  18685. * @cfg {String} ui The ui to be used on this Component
  18686. */
  18687. ui: null,
  18688. /**
  18689. * @cfg {Number/String} margin The margin to use on this Component. Can be specified as a number (in which case
  18690. * all edges get the same margin) or a CSS string like '5 10 10 10'
  18691. * @accessor
  18692. */
  18693. margin: null,
  18694. /**
  18695. * @cfg {Number/String} padding The padding to use on this Component. Can be specified as a number (in which
  18696. * case all edges get the same padding) or a CSS string like '5 10 10 10'
  18697. * @accessor
  18698. */
  18699. padding: null,
  18700. /**
  18701. * @cfg {Number/String} border The border width to use on this Component. Can be specified as a number (in which
  18702. * case all edges get the same border width) or a CSS string like '5 10 10 10'.
  18703. *
  18704. * Please note that this will not add
  18705. * a `border-color` or `border-style` CSS property to the component; you must do that manually using either CSS or
  18706. * the {@link #style} configuration.
  18707. *
  18708. * ## Using {@link #style}:
  18709. *
  18710. * Ext.Viewport.add({
  18711. * centered: true,
  18712. * width: 100,
  18713. * height: 100,
  18714. *
  18715. * border: 3,
  18716. * style: 'border-color: blue; border-style: solid;'
  18717. * // ...
  18718. * });
  18719. *
  18720. * ## Using CSS:
  18721. *
  18722. * Ext.Viewport.add({
  18723. * centered: true,
  18724. * width: 100,
  18725. * height: 100,
  18726. *
  18727. * border: 3,
  18728. * cls: 'my-component'
  18729. * // ...
  18730. * });
  18731. *
  18732. * And your CSS file:
  18733. *
  18734. * .my-component {
  18735. * border-color: red;
  18736. * border-style: solid;
  18737. * }
  18738. *
  18739. * @accessor
  18740. */
  18741. border: null,
  18742. /**
  18743. * @cfg {String} [styleHtmlCls="x-html"]
  18744. * The class that is added to the content target when you set `styleHtmlContent` to `true`.
  18745. * @accessor
  18746. */
  18747. styleHtmlCls: clsPrefix + 'html',
  18748. /**
  18749. * @cfg {Boolean} [styleHtmlContent=false]
  18750. * `true` to automatically style the HTML inside the content target of this component (body for panels).
  18751. * @accessor
  18752. */
  18753. styleHtmlContent: null
  18754. },
  18755. eventedConfig: {
  18756. /**
  18757. * @cfg {Number} flex
  18758. * The flex of this item *if* this item item is inside a {@link Ext.layout.HBox} or {@link Ext.layout.VBox}
  18759. * layout.
  18760. *
  18761. * You can also update the flex of a component dynamically using the {@link Ext.layout.FlexBox#setItemFlex}
  18762. * method.
  18763. */
  18764. flex: null,
  18765. /**
  18766. * @cfg {Number/String} left
  18767. * The absolute left position of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
  18768. * Explicitly setting this value will make this Component become 'floating', which means its layout will no
  18769. * longer be affected by the Container that it resides in.
  18770. * @accessor
  18771. * @evented
  18772. */
  18773. left: null,
  18774. /**
  18775. * @cfg {Number/String} top
  18776. * The absolute top position of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
  18777. * Explicitly setting this value will make this Component become 'floating', which means its layout will no
  18778. * longer be affected by the Container that it resides in.
  18779. * @accessor
  18780. * @evented
  18781. */
  18782. top: null,
  18783. /**
  18784. * @cfg {Number/String} right
  18785. * The absolute right position of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
  18786. * Explicitly setting this value will make this Component become 'floating', which means its layout will no
  18787. * longer be affected by the Container that it resides in.
  18788. * @accessor
  18789. * @evented
  18790. */
  18791. right: null,
  18792. /**
  18793. * @cfg {Number/String} bottom
  18794. * The absolute bottom position of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
  18795. * Explicitly setting this value will make this Component become 'floating', which means its layout will no
  18796. * longer be affected by the Container that it resides in.
  18797. * @accessor
  18798. * @evented
  18799. */
  18800. bottom: null,
  18801. /**
  18802. * @cfg {Number/String} width
  18803. * The width of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
  18804. * By default, if this is not explicitly set, this Component's element will simply have its own natural size.
  18805. * @accessor
  18806. * @evented
  18807. */
  18808. width: null,
  18809. /**
  18810. * @cfg {Number/String} height
  18811. * The height of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
  18812. * By default, if this is not explicitly set, this Component's element will simply have its own natural size.
  18813. * @accessor
  18814. * @evented
  18815. */
  18816. height: null,
  18817. /**
  18818. * @cfg {Number/String} minWidth
  18819. * The minimum width of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
  18820. * @accessor
  18821. * @evented
  18822. */
  18823. minWidth: null,
  18824. /**
  18825. * @cfg {Number/String} minHeight
  18826. * The minimum height of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
  18827. * @accessor
  18828. * @evented
  18829. */
  18830. minHeight: null,
  18831. /**
  18832. * @cfg {Number/String} maxWidth
  18833. * The maximum width of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
  18834. * Note that this config will not apply if the Component is 'floating' (absolutely positioned or centered)
  18835. * @accessor
  18836. * @evented
  18837. */
  18838. maxWidth: null,
  18839. /**
  18840. * @cfg {Number/String} maxHeight
  18841. * The maximum height of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
  18842. * Note that this config will not apply if the Component is 'floating' (absolutely positioned or centered)
  18843. * @accessor
  18844. * @evented
  18845. */
  18846. maxHeight: null,
  18847. /**
  18848. * @cfg {String} docked
  18849. * The dock position of this component in its container. Can be `left`, `top`, `right` or `bottom`.
  18850. *
  18851. * __Notes__
  18852. *
  18853. * You must use a HTML5 doctype for {@link #docked} `bottom` to work. To do this, simply add the following code to the HTML file:
  18854. *
  18855. * <!doctype html>
  18856. *
  18857. * So your index.html file should look a little like this:
  18858. *
  18859. * <!doctype html>
  18860. * <html>
  18861. * <head>
  18862. * <title>MY application title</title>
  18863. * ...
  18864. *
  18865. * @accessor
  18866. * @evented
  18867. */
  18868. docked: null,
  18869. /**
  18870. * @cfg {Boolean} centered
  18871. * Whether or not this Component is absolutely centered inside its Container
  18872. * @accessor
  18873. * @evented
  18874. */
  18875. centered: null,
  18876. /**
  18877. * @cfg {Boolean} hidden
  18878. * Whether or not this Component is hidden (its CSS `display` property is set to `none`)
  18879. * @accessor
  18880. * @evented
  18881. */
  18882. hidden: null,
  18883. /**
  18884. * @cfg {Boolean} disabled
  18885. * Whether or not this component is disabled
  18886. * @accessor
  18887. * @evented
  18888. */
  18889. disabled: null
  18890. },
  18891. config: {
  18892. /**
  18893. * @cfg {String/Object} style Optional CSS styles that will be rendered into an inline style attribute when the
  18894. * Component is rendered.
  18895. *
  18896. * You can pass either a string syntax:
  18897. *
  18898. * style: 'background:red'
  18899. *
  18900. * Or by using an object:
  18901. *
  18902. * style: {
  18903. * background: 'red'
  18904. * }
  18905. *
  18906. * When using the object syntax, you can define CSS Properties by using a string:
  18907. *
  18908. * style: {
  18909. * 'border-left': '1px solid red'
  18910. * }
  18911. *
  18912. * Although the object syntax is much easier to read, we suggest you to use the string syntax for better performance.
  18913. *
  18914. * @accessor
  18915. */
  18916. style: null,
  18917. /**
  18918. * @cfg {String/Ext.Element/HTMLElement} html Optional HTML content to render inside this Component, or a reference
  18919. * to an existing element on the page.
  18920. * @accessor
  18921. */
  18922. html: null,
  18923. /**
  18924. * @cfg {Object} draggable Configuration options to make this Component draggable
  18925. * @accessor
  18926. */
  18927. draggable: null,
  18928. /**
  18929. * @cfg {Object} translatable
  18930. * @private
  18931. * @accessor
  18932. */
  18933. translatable: null,
  18934. /**
  18935. * @cfg {Ext.Element} renderTo Optional element to render this Component to. Usually this is not needed because
  18936. * a Component is normally full screen or automatically rendered inside another {@link Ext.Container Container}
  18937. * @accessor
  18938. */
  18939. renderTo: null,
  18940. /**
  18941. * @cfg {Number} zIndex The z-index to give this Component when it is rendered
  18942. * @accessor
  18943. */
  18944. zIndex: null,
  18945. /**
  18946. * @cfg {String/String[]/Ext.Template[]/Ext.XTemplate[]} tpl
  18947. * A {@link String}, {@link Ext.Template}, {@link Ext.XTemplate} or an {@link Array} of strings to form an {@link Ext.XTemplate}.
  18948. * Used in conjunction with the {@link #data} and {@link #tplWriteMode} configurations.
  18949. *
  18950. * __Note__
  18951. * The {@link #data} configuration _must_ be set for any content to be shown in the component when using this configuration.
  18952. * @accessor
  18953. */
  18954. tpl: null,
  18955. /**
  18956. * @cfg {String/Mixed} enterAnimation
  18957. * Animation effect to apply when the Component is being shown. Typically you want to use an
  18958. * inbound animation type such as 'fadeIn' or 'slideIn'.
  18959. * @deprecated 2.0.0 Please use {@link #showAnimation} instead.
  18960. * @accessor
  18961. */
  18962. enterAnimation: null,
  18963. /**
  18964. * @cfg {String/Mixed} exitAnimation
  18965. * Animation effect to apply when the Component is being hidden.
  18966. * @deprecated 2.0.0 Please use {@link #hideAnimation} instead. Typically you want to use an
  18967. * outbound animation type such as 'fadeOut' or 'slideOut'.
  18968. * @accessor
  18969. */
  18970. exitAnimation: null,
  18971. /**
  18972. * @cfg {String/Mixed} showAnimation
  18973. * Animation effect to apply when the Component is being shown. Typically you want to use an
  18974. * inbound animation type such as 'fadeIn' or 'slideIn'.
  18975. * @accessor
  18976. */
  18977. showAnimation: null,
  18978. /**
  18979. * @cfg {String/Mixed} hideAnimation
  18980. * Animation effect to apply when the Component is being hidden. Typically you want to use an
  18981. * outbound animation type such as 'fadeOut' or 'slideOut'.
  18982. * @accessor
  18983. */
  18984. hideAnimation: null,
  18985. /**
  18986. * @cfg {String} tplWriteMode The Ext.(X)Template method to use when
  18987. * updating the content area of the Component.
  18988. * Valid modes are:
  18989. *
  18990. * - append
  18991. * - insertAfter
  18992. * - insertBefore
  18993. * - insertFirst
  18994. * - overwrite
  18995. * @accessor
  18996. */
  18997. tplWriteMode: 'overwrite',
  18998. /**
  18999. * @cfg {Mixed} data
  19000. * The initial set of data to apply to the `{@link #tpl}` to
  19001. * update the content area of the Component.
  19002. * @accessor
  19003. */
  19004. data: null,
  19005. /**
  19006. * @cfg {String} [disabledCls="x-item-disabled"] The CSS class to add to the component when it is disabled
  19007. * @accessor
  19008. */
  19009. disabledCls: clsPrefix + 'item-disabled',
  19010. /**
  19011. * @cfg {Ext.Element/HTMLElement/String} contentEl The configured element will automatically be
  19012. * added as the content of this component. When you pass a string, we expect it to be an element id.
  19013. * If the content element is hidden, we will automatically show it.
  19014. * @accessor
  19015. */
  19016. contentEl: null,
  19017. /**
  19018. * @cfg {String} id
  19019. * The **unique id of this component instance.**
  19020. *
  19021. * It should not be necessary to use this configuration except for singleton objects in your application. Components
  19022. * created with an id may be accessed globally using {@link Ext#getCmp Ext.getCmp}.
  19023. *
  19024. * Instead of using assigned ids, use the {@link #itemId} config, and {@link Ext.ComponentQuery ComponentQuery}
  19025. * which provides selector-based searching for Sencha Components analogous to DOM querying. The
  19026. * {@link Ext.Container} class contains {@link Ext.Container#down shortcut methods} to query
  19027. * its descendant Components by selector.
  19028. *
  19029. * Note that this id will also be used as the element id for the containing HTML element that is rendered to the
  19030. * page for this component. This allows you to write id-based CSS rules to style the specific instance of this
  19031. * component uniquely, and also to select sub-elements using this component's id as the parent.
  19032. *
  19033. * **Note**: to avoid complications imposed by a unique id also see `{@link #itemId}`.
  19034. *
  19035. * Defaults to an auto-assigned id.
  19036. */
  19037. /**
  19038. * @cfg {String} itemId
  19039. * An itemId can be used as an alternative way to get a reference to a component when no object reference is
  19040. * available. Instead of using an `{@link #id}` with {@link Ext#getCmp}, use `itemId` with
  19041. * {@link Ext.Container#getComponent} which will retrieve `itemId`'s or {@link #id}'s. Since `itemId`'s are an
  19042. * index to the container's internal MixedCollection, the `itemId` is scoped locally to the container - avoiding
  19043. * potential conflicts with {@link Ext.ComponentManager} which requires a **unique** `{@link #id}`.
  19044. *
  19045. * Also see {@link #id}, {@link Ext.Container#query}, {@link Ext.Container#down} and {@link Ext.Container#child}.
  19046. *
  19047. * @accessor
  19048. */
  19049. itemId: undefined,
  19050. /**
  19051. * @cfg {Ext.data.Model} record A model instance which updates the Component's html based on it's tpl. Similar to the data
  19052. * configuration, but tied to to a record to make allow dynamic updates. This must be a model
  19053. * instance and not a configuration of one.
  19054. * @accessor
  19055. */
  19056. record: null,
  19057. /**
  19058. * @cfg {Object/Array} plugins
  19059. * @accessor
  19060. * An object or array of objects that will provide custom functionality for this component. The only
  19061. * requirement for a valid plugin is that it contain an init method that accepts a reference of type Ext.Component.
  19062. *
  19063. * When a component is created, if any plugins are available, the component will call the init method on each
  19064. * plugin, passing a reference to itself. Each plugin can then call methods or respond to events on the
  19065. * component as needed to provide its functionality.
  19066. *
  19067. * For examples of plugins, see Ext.plugin.PullRefresh and Ext.plugin.ListPaging
  19068. *
  19069. * ## Example code
  19070. *
  19071. * A plugin by alias:
  19072. *
  19073. * Ext.create('Ext.dataview.List', {
  19074. * config: {
  19075. * plugins: 'listpaging',
  19076. * itemTpl: '<div class="item">{title}</div>',
  19077. * store: 'Items'
  19078. * }
  19079. * });
  19080. *
  19081. * Multiple plugins by alias:
  19082. *
  19083. * Ext.create('Ext.dataview.List', {
  19084. * config: {
  19085. * plugins: ['listpaging', 'pullrefresh'],
  19086. * itemTpl: '<div class="item">{title}</div>',
  19087. * store: 'Items'
  19088. * }
  19089. * });
  19090. *
  19091. * Single plugin by class name with config options:
  19092. *
  19093. * Ext.create('Ext.dataview.List', {
  19094. * config: {
  19095. * plugins: {
  19096. * xclass: 'Ext.plugin.ListPaging', // Reference plugin by class
  19097. * autoPaging: true
  19098. * },
  19099. *
  19100. * itemTpl: '<div class="item">{title}</div>',
  19101. * store: 'Items'
  19102. * }
  19103. * });
  19104. *
  19105. * Multiple plugins by class name with config options:
  19106. *
  19107. * Ext.create('Ext.dataview.List', {
  19108. * config: {
  19109. * plugins: [
  19110. * {
  19111. * xclass: 'Ext.plugin.PullRefresh',
  19112. * pullRefreshText: 'Pull to refresh...'
  19113. * },
  19114. * {
  19115. * xclass: 'Ext.plugin.ListPaging',
  19116. * autoPaging: true
  19117. * }
  19118. * ],
  19119. *
  19120. * itemTpl: '<div class="item">{title}</div>',
  19121. * store: 'Items'
  19122. * }
  19123. * });
  19124. *
  19125. */
  19126. plugins: null
  19127. },
  19128. /**
  19129. * @event show
  19130. * Fires whenever the Component is shown
  19131. * @param {Ext.Component} this The component instance
  19132. */
  19133. /**
  19134. * @event hide
  19135. * Fires whenever the Component is hidden
  19136. * @param {Ext.Component} this The component instance
  19137. */
  19138. /**
  19139. * @event fullscreen
  19140. * Fires whenever a Component with the fullscreen config is instantiated
  19141. * @param {Ext.Component} this The component instance
  19142. */
  19143. /**
  19144. * @event floatingchange
  19145. * Fires whenever there is a change in the floating status of a component
  19146. * @param {Ext.Component} this The component instance
  19147. * @param {Boolean} floating The component's new floating state
  19148. */
  19149. /**
  19150. * @event beforeorientationchange
  19151. * Fires before orientation changes.
  19152. * @removed 2.0.0 This event is now only available `onBefore` the Viewport's {@link Ext.Viewport#orientationchange}
  19153. */
  19154. /**
  19155. * @event orientationchange
  19156. * Fires when orientation changes.
  19157. * @removed 2.0.0 This event is now only available on the Viewport's {@link Ext.Viewport#orientationchange}
  19158. */
  19159. /**
  19160. * @event initialize
  19161. * Fires when the component has been initialized
  19162. * @param {Ext.Component} this The component instance
  19163. */
  19164. /**
  19165. * @event painted
  19166. * @inheritdoc Ext.dom.Element#painted
  19167. */
  19168. /**
  19169. * @event erased
  19170. * Fires when the component is no longer displayed in the DOM. Listening to this event will
  19171. * degrade performance not recommend for general use.
  19172. * @param {Ext.Component} this The component instance
  19173. */
  19174. /**
  19175. * @event resize
  19176. * @inheritdoc Ext.dom.Element#resize
  19177. */
  19178. /**
  19179. * @private
  19180. */
  19181. listenerOptionsRegex: /^(?:delegate|single|delay|buffer|args|prepend|element)$/,
  19182. /**
  19183. * @private
  19184. */
  19185. alignmentRegex: /^([a-z]+)-([a-z]+)(\?)?$/,
  19186. /**
  19187. * @private
  19188. */
  19189. isComponent: true,
  19190. /**
  19191. * @private
  19192. */
  19193. floating: false,
  19194. /**
  19195. * @private
  19196. */
  19197. rendered: false,
  19198. /**
  19199. * @private
  19200. */
  19201. isInner: true,
  19202. /**
  19203. * @readonly
  19204. * @private
  19205. */
  19206. dockPositions: {
  19207. top: true,
  19208. right: true,
  19209. bottom: true,
  19210. left: true
  19211. },
  19212. innerElement: null,
  19213. element: null,
  19214. template: [],
  19215. widthLayoutSized: false,
  19216. heightLayoutSized: false,
  19217. layoutStretched: false,
  19218. sizeState: false,
  19219. sizeFlags: 0x0,
  19220. LAYOUT_WIDTH: 0x1,
  19221. LAYOUT_HEIGHT: 0x2,
  19222. LAYOUT_BOTH: 0x3,
  19223. LAYOUT_STRETCHED: 0x4,
  19224. /**
  19225. * Creates new Component.
  19226. * @param {Object} config The standard configuration object.
  19227. */
  19228. constructor: function(config) {
  19229. var me = this,
  19230. currentConfig = me.config,
  19231. id;
  19232. me.onInitializedListeners = [];
  19233. me.initialConfig = config;
  19234. if (config !== undefined && 'id' in config) {
  19235. id = config.id;
  19236. }
  19237. else if ('id' in currentConfig) {
  19238. id = currentConfig.id;
  19239. }
  19240. else {
  19241. id = me.getId();
  19242. }
  19243. me.id = id;
  19244. me.setId(id);
  19245. Ext.ComponentManager.register(me);
  19246. me.initElement();
  19247. me.initConfig(me.initialConfig);
  19248. me.refreshSizeState = me.doRefreshSizeState;
  19249. me.refreshFloating = me.doRefreshFloating;
  19250. if (me.refreshSizeStateOnInitialized) {
  19251. me.refreshSizeState();
  19252. }
  19253. if (me.refreshFloatingOnInitialized) {
  19254. me.refreshFloating();
  19255. }
  19256. me.initialize();
  19257. me.triggerInitialized();
  19258. /**
  19259. * Force the component to take up 100% width and height available, by adding it to {@link Ext.Viewport}.
  19260. * @cfg {Boolean} fullscreen
  19261. */
  19262. if (me.config.fullscreen) {
  19263. me.fireEvent('fullscreen', me);
  19264. }
  19265. me.fireEvent('initialize', me);
  19266. },
  19267. beforeInitConfig: function(config) {
  19268. this.beforeInitialize.apply(this, arguments);
  19269. },
  19270. /**
  19271. * @private
  19272. */
  19273. beforeInitialize: Ext.emptyFn,
  19274. /**
  19275. * Allows addition of behavior to the rendering phase.
  19276. * @protected
  19277. * @template
  19278. */
  19279. initialize: Ext.emptyFn,
  19280. getTemplate: function() {
  19281. return this.template;
  19282. },
  19283. /**
  19284. * @private
  19285. * @return {Object}
  19286. * @return {String} return.reference
  19287. * @return {Array} return.classList
  19288. * @return {Object} return.children
  19289. */
  19290. getElementConfig: function() {
  19291. return {
  19292. reference: 'element',
  19293. classList: ['x-unsized'],
  19294. children: this.getTemplate()
  19295. };
  19296. },
  19297. /**
  19298. * @private
  19299. */
  19300. triggerInitialized: function() {
  19301. var listeners = this.onInitializedListeners,
  19302. ln = listeners.length,
  19303. listener, fn, scope, args, i;
  19304. if (!this.initialized) {
  19305. this.initialized = true;
  19306. if (ln > 0) {
  19307. for (i = 0; i < ln; i++) {
  19308. listener = listeners[i];
  19309. fn = listener.fn;
  19310. scope = listener.scope;
  19311. args = listener.args;
  19312. if (typeof fn == 'string') {
  19313. scope[fn].apply(scope, args);
  19314. }
  19315. else {
  19316. fn.apply(scope, args);
  19317. }
  19318. }
  19319. listeners.length = 0;
  19320. }
  19321. }
  19322. },
  19323. /**
  19324. * @private
  19325. * @param fn
  19326. * @param scope
  19327. */
  19328. onInitialized: function(fn, scope, args) {
  19329. var listeners = this.onInitializedListeners;
  19330. if (!scope) {
  19331. scope = this;
  19332. }
  19333. if (this.initialized) {
  19334. if (typeof fn == 'string') {
  19335. scope[fn].apply(scope, args);
  19336. }
  19337. else {
  19338. fn.apply(scope, args);
  19339. }
  19340. }
  19341. else {
  19342. listeners.push({
  19343. fn: fn,
  19344. scope: scope,
  19345. args: args
  19346. });
  19347. }
  19348. },
  19349. renderTo: function(container, insertBeforeElement) {
  19350. var dom = this.renderElement.dom,
  19351. containerDom = Ext.getDom(container),
  19352. insertBeforeChildDom = Ext.getDom(insertBeforeElement);
  19353. if (containerDom) {
  19354. if (insertBeforeChildDom) {
  19355. containerDom.insertBefore(dom, insertBeforeChildDom);
  19356. }
  19357. else {
  19358. containerDom.appendChild(dom);
  19359. }
  19360. this.setRendered(Boolean(dom.offsetParent));
  19361. }
  19362. },
  19363. /**
  19364. * @private
  19365. * @chainable
  19366. */
  19367. setParent: function(parent) {
  19368. var currentParent = this.parent;
  19369. if (parent && currentParent && currentParent !== parent) {
  19370. currentParent.remove(this, false);
  19371. }
  19372. this.parent = parent;
  19373. return this;
  19374. },
  19375. applyPlugins: function(config) {
  19376. var ln, i, configObj;
  19377. if (!config) {
  19378. return config;
  19379. }
  19380. config = [].concat(config);
  19381. for (i = 0, ln = config.length; i < ln; i++) {
  19382. configObj = config[i];
  19383. config[i] = Ext.factory(configObj, 'Ext.plugin.Plugin', null, 'plugin');
  19384. }
  19385. return config;
  19386. },
  19387. updatePlugins: function(newPlugins, oldPlugins) {
  19388. var ln, i;
  19389. if (newPlugins) {
  19390. for (i = 0, ln = newPlugins.length; i < ln; i++) {
  19391. newPlugins[i].init(this);
  19392. }
  19393. }
  19394. if (oldPlugins) {
  19395. for (i = 0, ln = oldPlugins.length; i < ln; i++) {
  19396. Ext.destroy(oldPlugins[i]);
  19397. }
  19398. }
  19399. },
  19400. updateRenderTo: function(newContainer) {
  19401. this.renderTo(newContainer);
  19402. },
  19403. updateStyle: function(style) {
  19404. this.element.applyStyles(style);
  19405. },
  19406. updateBorder: function(border) {
  19407. this.element.setBorder(border);
  19408. },
  19409. updatePadding: function(padding) {
  19410. this.innerElement.setPadding(padding);
  19411. },
  19412. updateMargin: function(margin) {
  19413. this.element.setMargin(margin);
  19414. },
  19415. updateUi: function(newUi, oldUi) {
  19416. var baseCls = this.getBaseCls();
  19417. if (baseCls) {
  19418. if (oldUi) {
  19419. this.element.removeCls(oldUi, baseCls);
  19420. }
  19421. if (newUi) {
  19422. this.element.addCls(newUi, baseCls);
  19423. }
  19424. }
  19425. },
  19426. applyBaseCls: function(baseCls) {
  19427. return baseCls || clsPrefix + this.xtype;
  19428. },
  19429. updateBaseCls: function(newBaseCls, oldBaseCls) {
  19430. var me = this,
  19431. ui = me.getUi();
  19432. if (newBaseCls) {
  19433. this.element.addCls(newBaseCls);
  19434. if (ui) {
  19435. this.element.addCls(newBaseCls, null, ui);
  19436. }
  19437. }
  19438. if (oldBaseCls) {
  19439. this.element.removeCls(oldBaseCls);
  19440. if (ui) {
  19441. this.element.removeCls(oldBaseCls, null, ui);
  19442. }
  19443. }
  19444. },
  19445. /**
  19446. * Adds a CSS class (or classes) to this Component's rendered element.
  19447. * @param {String} cls The CSS class to add.
  19448. * @param {String} [prefix=""] Optional prefix to add to each class.
  19449. * @param {String} [suffix=""] Optional suffix to add to each class.
  19450. */
  19451. addCls: function(cls, prefix, suffix) {
  19452. var oldCls = this.getCls(),
  19453. newCls = (oldCls) ? oldCls.slice() : [],
  19454. ln, i, cachedCls;
  19455. prefix = prefix || '';
  19456. suffix = suffix || '';
  19457. if (typeof cls == "string") {
  19458. cls = [cls];
  19459. }
  19460. ln = cls.length;
  19461. //check if there is currently nothing in the array and we don't need to add a prefix or a suffix.
  19462. //if true, we can just set the newCls value to the cls property, because that is what the value will be
  19463. //if false, we need to loop through each and add them to the newCls array
  19464. if (!newCls.length && prefix === '' && suffix === '') {
  19465. newCls = cls;
  19466. } else {
  19467. for (i = 0; i < ln; i++) {
  19468. cachedCls = prefix + cls[i] + suffix;
  19469. if (newCls.indexOf(cachedCls) == -1) {
  19470. newCls.push(cachedCls);
  19471. }
  19472. }
  19473. }
  19474. this.setCls(newCls);
  19475. },
  19476. /**
  19477. * Removes the given CSS class(es) from this Component's rendered element.
  19478. * @param {String} cls The class(es) to remove.
  19479. * @param {String} [prefix=""] Optional prefix to prepend before each class.
  19480. * @param {String} [suffix=""] Optional suffix to append to each class.
  19481. */
  19482. removeCls: function(cls, prefix, suffix) {
  19483. var oldCls = this.getCls(),
  19484. newCls = (oldCls) ? oldCls.slice() : [],
  19485. ln, i;
  19486. prefix = prefix || '';
  19487. suffix = suffix || '';
  19488. if (typeof cls == "string") {
  19489. newCls = Ext.Array.remove(newCls, prefix + cls + suffix);
  19490. } else {
  19491. ln = cls.length;
  19492. for (i = 0; i < ln; i++) {
  19493. newCls = Ext.Array.remove(newCls, prefix + cls[i] + suffix);
  19494. }
  19495. }
  19496. this.setCls(newCls);
  19497. },
  19498. /**
  19499. * Replaces specified classes with the newly specified classes.
  19500. * It uses the {@link #addCls} and {@link #removeCls} methods, so if the class(es) you are removing don't exist, it will
  19501. * still add the new classes.
  19502. * @param {String} oldCls The class(es) to remove.
  19503. * @param {String} newCls The class(es) to add.
  19504. * @param {String} [prefix=""] Optional prefix to prepend before each class.
  19505. * @param {String} [suffix=""] Optional suffix to append to each class.
  19506. */
  19507. replaceCls: function(oldCls, newCls, prefix, suffix) {
  19508. // We could have just called {@link #removeCls} and {@link #addCls}, but that would mean {@link #updateCls}
  19509. // would get called twice, which would have performance implications because it will update the dom.
  19510. var cls = this.getCls(),
  19511. array = (cls) ? cls.slice() : [],
  19512. ln, i, cachedCls;
  19513. prefix = prefix || '';
  19514. suffix = suffix || '';
  19515. //remove all oldCls
  19516. if (typeof oldCls == "string") {
  19517. array = Ext.Array.remove(array, prefix + oldCls + suffix);
  19518. } else if (oldCls) {
  19519. ln = oldCls.length;
  19520. for (i = 0; i < ln; i++) {
  19521. array = Ext.Array.remove(array, prefix + oldCls[i] + suffix);
  19522. }
  19523. }
  19524. //add all newCls
  19525. if (typeof newCls == "string") {
  19526. array.push(prefix + newCls + suffix);
  19527. } else if (newCls) {
  19528. ln = newCls.length;
  19529. //check if there is currently nothing in the array and we don't need to add a prefix or a suffix.
  19530. //if true, we can just set the array value to the newCls property, because that is what the value will be
  19531. //if false, we need to loop through each and add them to the array
  19532. if (!array.length && prefix === '' && suffix === '') {
  19533. array = newCls;
  19534. } else {
  19535. for (i = 0; i < ln; i++) {
  19536. cachedCls = prefix + newCls[i] + suffix;
  19537. if (array.indexOf(cachedCls) == -1) {
  19538. array.push(cachedCls);
  19539. }
  19540. }
  19541. }
  19542. }
  19543. this.setCls(array);
  19544. },
  19545. /**
  19546. * @private
  19547. * @chainable
  19548. */
  19549. toggleCls: function(className, force) {
  19550. this.element.toggleCls(className, force);
  19551. return this;
  19552. },
  19553. /**
  19554. * @private
  19555. * Checks if the `cls` is a string. If it is, changed it into an array.
  19556. * @param {String/Array} cls
  19557. * @return {Array/null}
  19558. */
  19559. applyCls: function(cls) {
  19560. if (typeof cls == "string") {
  19561. cls = [cls];
  19562. }
  19563. //reset it back to null if there is nothing.
  19564. if (!cls || !cls.length) {
  19565. cls = null;
  19566. }
  19567. return cls;
  19568. },
  19569. /**
  19570. * @private
  19571. * All cls methods directly report to the {@link #cls} configuration, so anytime it changes, {@link #updateCls} will be called
  19572. */
  19573. updateCls: function(newCls, oldCls) {
  19574. if (oldCls != newCls && this.element) {
  19575. this.element.replaceCls(oldCls, newCls);
  19576. }
  19577. },
  19578. /**
  19579. * Updates the {@link #styleHtmlCls} configuration
  19580. */
  19581. updateStyleHtmlCls: function(newHtmlCls, oldHtmlCls) {
  19582. var innerHtmlElement = this.innerHtmlElement,
  19583. innerElement = this.innerElement;
  19584. if (this.getStyleHtmlContent() && oldHtmlCls) {
  19585. if (innerHtmlElement) {
  19586. innerHtmlElement.replaceCls(oldHtmlCls, newHtmlCls);
  19587. } else {
  19588. innerElement.replaceCls(oldHtmlCls, newHtmlCls);
  19589. }
  19590. }
  19591. },
  19592. applyStyleHtmlContent: function(config) {
  19593. return Boolean(config);
  19594. },
  19595. updateStyleHtmlContent: function(styleHtmlContent) {
  19596. var htmlCls = this.getStyleHtmlCls(),
  19597. innerElement = this.innerElement,
  19598. innerHtmlElement = this.innerHtmlElement;
  19599. if (styleHtmlContent) {
  19600. if (innerHtmlElement) {
  19601. innerHtmlElement.addCls(htmlCls);
  19602. } else {
  19603. innerElement.addCls(htmlCls);
  19604. }
  19605. } else {
  19606. if (innerHtmlElement) {
  19607. innerHtmlElement.removeCls(htmlCls);
  19608. } else {
  19609. innerElement.addCls(htmlCls);
  19610. }
  19611. }
  19612. },
  19613. applyContentEl: function(contentEl) {
  19614. if (contentEl) {
  19615. return Ext.get(contentEl);
  19616. }
  19617. },
  19618. updateContentEl: function(newContentEl, oldContentEl) {
  19619. if (oldContentEl) {
  19620. oldContentEl.hide();
  19621. Ext.getBody().append(oldContentEl);
  19622. }
  19623. if (newContentEl) {
  19624. this.setHtml(newContentEl.dom);
  19625. newContentEl.show();
  19626. }
  19627. },
  19628. /**
  19629. * Returns the height and width of the Component.
  19630. * @return {Object} The current `height` and `width` of the Component.
  19631. * @return {Number} return.width
  19632. * @return {Number} return.height
  19633. */
  19634. getSize: function() {
  19635. return {
  19636. width: this.getWidth(),
  19637. height: this.getHeight()
  19638. };
  19639. },
  19640. /**
  19641. * @private
  19642. * @return {Boolean}
  19643. */
  19644. isCentered: function() {
  19645. return Boolean(this.getCentered());
  19646. },
  19647. isFloating: function() {
  19648. return this.floating;
  19649. },
  19650. isDocked: function() {
  19651. return Boolean(this.getDocked());
  19652. },
  19653. isInnerItem: function() {
  19654. return this.isInner;
  19655. },
  19656. setIsInner: function(isInner) {
  19657. if (isInner !== this.isInner) {
  19658. this.isInner = isInner;
  19659. if (this.initialized) {
  19660. this.fireEvent('innerstatechange', this, isInner);
  19661. }
  19662. }
  19663. },
  19664. filterPositionValue: function(value) {
  19665. if (value === '' || value === 'auto') {
  19666. value = null;
  19667. }
  19668. return value;
  19669. },
  19670. filterLengthValue: function(value) {
  19671. if (value === 'auto' || (!value && value !== 0)) {
  19672. return null;
  19673. }
  19674. return value;
  19675. },
  19676. applyTop: function(top) {
  19677. return this.filterPositionValue(top);
  19678. },
  19679. applyRight: function(right) {
  19680. return this.filterPositionValue(right);
  19681. },
  19682. applyBottom: function(bottom) {
  19683. return this.filterPositionValue(bottom);
  19684. },
  19685. applyLeft: function(left) {
  19686. return this.filterPositionValue(left);
  19687. },
  19688. applyWidth: function(width) {
  19689. return this.filterLengthValue(width);
  19690. },
  19691. applyHeight: function(height) {
  19692. return this.filterLengthValue(height);
  19693. },
  19694. applyMinWidth: function(width) {
  19695. return this.filterLengthValue(width);
  19696. },
  19697. applyMinHeight: function(height) {
  19698. return this.filterLengthValue(height);
  19699. },
  19700. applyMaxWidth: function(width) {
  19701. return this.filterLengthValue(width);
  19702. },
  19703. applyMaxHeight: function(height) {
  19704. return this.filterLengthValue(height);
  19705. },
  19706. doSetTop: function(top) {
  19707. this.element.setTop(top);
  19708. this.refreshFloating();
  19709. },
  19710. doSetRight: function(right) {
  19711. this.element.setRight(right);
  19712. this.refreshFloating();
  19713. },
  19714. doSetBottom: function(bottom) {
  19715. this.element.setBottom(bottom);
  19716. this.refreshFloating();
  19717. },
  19718. doSetLeft: function(left) {
  19719. this.element.setLeft(left);
  19720. this.refreshFloating();
  19721. },
  19722. doSetWidth: function(width) {
  19723. this.element.setWidth(width);
  19724. this.refreshSizeState();
  19725. },
  19726. doSetHeight: function(height) {
  19727. this.element.setHeight(height);
  19728. this.refreshSizeState();
  19729. },
  19730. applyFlex: function(flex) {
  19731. if (flex) {
  19732. flex = Number(flex);
  19733. if (isNaN(flex)) {
  19734. flex = null;
  19735. }
  19736. }
  19737. else {
  19738. flex = null
  19739. }
  19740. return flex;
  19741. },
  19742. doSetFlex: Ext.emptyFn,
  19743. refreshSizeState: function() {
  19744. this.refreshSizeStateOnInitialized = true;
  19745. },
  19746. doRefreshSizeState: function() {
  19747. var hasWidth = this.getWidth() !== null || this.widthLayoutSized || (this.getLeft() !== null && this.getRight() !== null),
  19748. hasHeight = this.getHeight() !== null || this.heightLayoutSized || (this.getTop() !== null && this.getBottom() !== null),
  19749. stretched = this.layoutStretched || (!hasHeight && this.getMinHeight() !== null),
  19750. state = hasWidth && hasHeight,
  19751. flags = (hasWidth && this.LAYOUT_WIDTH) | (hasHeight && this.LAYOUT_HEIGHT) | (stretched && this.LAYOUT_STRETCHED);
  19752. if (!state && stretched) {
  19753. state = null;
  19754. }
  19755. this.setSizeState(state);
  19756. this.setSizeFlags(flags);
  19757. },
  19758. setLayoutSizeFlags: function(flags) {
  19759. this.layoutStretched = !!(flags & this.LAYOUT_STRETCHED);
  19760. this.widthLayoutSized = !!(flags & this.LAYOUT_WIDTH);
  19761. this.heightLayoutSized = !!(flags & this.LAYOUT_HEIGHT);
  19762. this.refreshSizeState();
  19763. },
  19764. setSizeFlags: function(flags) {
  19765. if (flags !== this.sizeFlags) {
  19766. this.sizeFlags = flags;
  19767. if (this.initialized) {
  19768. this.fireEvent('sizeflagschange', this, flags);
  19769. }
  19770. }
  19771. },
  19772. getSizeFlags: function() {
  19773. if (!this.initialized) {
  19774. this.doRefreshSizeState();
  19775. }
  19776. return this.sizeFlags;
  19777. },
  19778. setSizeState: function(state) {
  19779. if (state !== this.sizeState) {
  19780. this.sizeState = state;
  19781. this.element.setSizeState(state);
  19782. if (this.initialized) {
  19783. this.fireEvent('sizestatechange', this, state);
  19784. }
  19785. }
  19786. },
  19787. getSizeState: function() {
  19788. if (!this.initialized) {
  19789. this.doRefreshSizeState();
  19790. }
  19791. return this.sizeState;
  19792. },
  19793. doSetMinWidth: function(width) {
  19794. this.element.setMinWidth(width);
  19795. },
  19796. doSetMinHeight: function(height) {
  19797. this.element.setMinHeight(height);
  19798. this.refreshSizeState();
  19799. },
  19800. doSetMaxWidth: function(width) {
  19801. this.element.setMaxWidth(width);
  19802. },
  19803. doSetMaxHeight: function(height) {
  19804. this.element.setMaxHeight(height);
  19805. },
  19806. /**
  19807. * @private
  19808. * @param {Boolean} centered
  19809. * @return {Boolean}
  19810. */
  19811. applyCentered: function(centered) {
  19812. centered = Boolean(centered);
  19813. if (centered) {
  19814. this.refreshInnerState = Ext.emptyFn;
  19815. if (this.isFloating()) {
  19816. this.resetFloating();
  19817. }
  19818. if (this.isDocked()) {
  19819. this.setDocked(false);
  19820. }
  19821. this.setIsInner(false);
  19822. delete this.refreshInnerState;
  19823. }
  19824. return centered;
  19825. },
  19826. doSetCentered: function(centered) {
  19827. this.toggleCls(this.getFloatingCls(), centered);
  19828. if (!centered) {
  19829. this.refreshInnerState();
  19830. }
  19831. },
  19832. applyDocked: function(docked) {
  19833. if (!docked) {
  19834. return null;
  19835. }
  19836. //<debug error>
  19837. if (!/^(top|right|bottom|left)$/.test(docked)) {
  19838. Ext.Logger.error("Invalid docking position of '" + docked.position + "', must be either 'top', 'right', 'bottom', " +
  19839. "'left' or `null` (for no docking)", this);
  19840. return;
  19841. }
  19842. //</debug>
  19843. this.refreshInnerState = Ext.emptyFn;
  19844. if (this.isFloating()) {
  19845. this.resetFloating();
  19846. }
  19847. if (this.isCentered()) {
  19848. this.setCentered(false);
  19849. }
  19850. this.setIsInner(false);
  19851. delete this.refreshInnerState;
  19852. return docked;
  19853. },
  19854. doSetDocked: function(docked) {
  19855. if (!docked) {
  19856. this.refreshInnerState();
  19857. }
  19858. },
  19859. /**
  19860. * Resets {@link #top}, {@link #right}, {@link #bottom} and {@link #left} configurations to `null`, which
  19861. * will un-float this component.
  19862. */
  19863. resetFloating: function() {
  19864. this.setTop(null);
  19865. this.setRight(null);
  19866. this.setBottom(null);
  19867. this.setLeft(null);
  19868. },
  19869. refreshInnerState: function() {
  19870. this.setIsInner(!this.isCentered() && !this.isFloating() && !this.isDocked());
  19871. },
  19872. refreshFloating: function() {
  19873. this.refreshFloatingOnInitialized = true;
  19874. },
  19875. doRefreshFloating: function() {
  19876. var floating = true,
  19877. floatingCls = this.getFloatingCls();
  19878. if (this.getTop() === null && this.getBottom() === null &&
  19879. this.getRight() === null && this.getLeft() === null) {
  19880. floating = false;
  19881. }
  19882. else {
  19883. this.refreshSizeState();
  19884. }
  19885. if (floating !== this.floating) {
  19886. this.floating = floating;
  19887. this.element.toggleCls(floatingCls, floating);
  19888. if (floating) {
  19889. this.refreshInnerState = Ext.emptyFn;
  19890. if (this.isCentered()) {
  19891. this.setCentered(false);
  19892. }
  19893. if (this.isDocked()) {
  19894. this.setDocked(false);
  19895. }
  19896. this.setIsInner(false);
  19897. delete this.refreshInnerState;
  19898. }
  19899. if (this.initialized) {
  19900. this.fireEvent('floatingchange', this, floating);
  19901. }
  19902. if (!floating) {
  19903. this.refreshInnerState();
  19904. }
  19905. }
  19906. },
  19907. /**
  19908. * Updates the floatingCls if the component is currently floating
  19909. * @private
  19910. */
  19911. updateFloatingCls: function(newFloatingCls, oldFloatingCls) {
  19912. if (this.isFloating()) {
  19913. this.replaceCls(oldFloatingCls, newFloatingCls);
  19914. }
  19915. },
  19916. applyDisabled: function(disabled) {
  19917. return Boolean(disabled);
  19918. },
  19919. doSetDisabled: function(disabled) {
  19920. this.element[disabled ? 'addCls' : 'removeCls'](this.getDisabledCls());
  19921. },
  19922. updateDisabledCls: function(newDisabledCls, oldDisabledCls) {
  19923. if (this.isDisabled()) {
  19924. this.element.replaceCls(oldDisabledCls, newDisabledCls);
  19925. }
  19926. },
  19927. /**
  19928. * Disables this Component
  19929. */
  19930. disable: function() {
  19931. this.setDisabled(true);
  19932. },
  19933. /**
  19934. * Enables this Component
  19935. */
  19936. enable: function() {
  19937. this.setDisabled(false);
  19938. },
  19939. /**
  19940. * Returns `true` if this Component is currently disabled.
  19941. * @return {Boolean} `true` if currently disabled.
  19942. */
  19943. isDisabled: function() {
  19944. return this.getDisabled();
  19945. },
  19946. applyZIndex: function(zIndex) {
  19947. if (!zIndex && zIndex !== 0) {
  19948. zIndex = null;
  19949. }
  19950. if (zIndex !== null) {
  19951. zIndex = Number(zIndex);
  19952. if (isNaN(zIndex)) {
  19953. zIndex = null;
  19954. }
  19955. }
  19956. return zIndex;
  19957. },
  19958. updateZIndex: function(zIndex) {
  19959. var element = this.element,
  19960. domStyle;
  19961. if (element && !element.isDestroyed) {
  19962. domStyle = element.dom.style;
  19963. if (zIndex !== null) {
  19964. domStyle.setProperty('z-index', zIndex, 'important');
  19965. }
  19966. else {
  19967. domStyle.removeProperty('z-index');
  19968. }
  19969. }
  19970. },
  19971. getInnerHtmlElement: function() {
  19972. var innerHtmlElement = this.innerHtmlElement,
  19973. styleHtmlCls = this.getStyleHtmlCls();
  19974. if (!innerHtmlElement || !innerHtmlElement.dom || !innerHtmlElement.dom.parentNode) {
  19975. this.innerHtmlElement = innerHtmlElement = this.innerElement.createChild({ cls: 'x-innerhtml ' });
  19976. if (this.getStyleHtmlContent()) {
  19977. this.innerHtmlElement.addCls(styleHtmlCls);
  19978. this.innerElement.removeCls(styleHtmlCls);
  19979. }
  19980. }
  19981. return innerHtmlElement;
  19982. },
  19983. updateHtml: function(html) {
  19984. var innerHtmlElement = this.getInnerHtmlElement();
  19985. if (Ext.isElement(html)){
  19986. innerHtmlElement.setHtml('');
  19987. innerHtmlElement.append(html);
  19988. }
  19989. else {
  19990. innerHtmlElement.setHtml(html);
  19991. }
  19992. },
  19993. applyHidden: function(hidden) {
  19994. return Boolean(hidden);
  19995. },
  19996. doSetHidden: function(hidden) {
  19997. var element = this.renderElement;
  19998. if (element.isDestroyed) {
  19999. return;
  20000. }
  20001. if (hidden) {
  20002. element.hide();
  20003. }
  20004. else {
  20005. element.show();
  20006. }
  20007. if (this.element) {
  20008. this.element[hidden ? 'addCls' : 'removeCls'](this.getHiddenCls());
  20009. }
  20010. this.fireEvent(hidden ? 'hide' : 'show', this);
  20011. },
  20012. updateHiddenCls: function(newHiddenCls, oldHiddenCls) {
  20013. if (this.isHidden()) {
  20014. this.element.replaceCls(oldHiddenCls, newHiddenCls);
  20015. }
  20016. },
  20017. /**
  20018. * Returns `true` if this Component is currently hidden.
  20019. * @return {Boolean} `true` if currently hidden.
  20020. */
  20021. isHidden: function() {
  20022. return this.getHidden();
  20023. },
  20024. /**
  20025. * Hides this Component
  20026. * @param {Object/Boolean} animation (optional)
  20027. * @return {Ext.Component}
  20028. * @chainable
  20029. */
  20030. hide: function(animation) {
  20031. if (!this.getHidden()) {
  20032. if (animation === undefined || (animation && animation.isComponent)) {
  20033. animation = this.getHideAnimation();
  20034. }
  20035. if (animation) {
  20036. if (animation === true) {
  20037. animation = 'fadeOut';
  20038. }
  20039. this.onBefore({
  20040. hiddenchange: 'animateFn',
  20041. scope: this,
  20042. single: true,
  20043. args: [animation]
  20044. });
  20045. }
  20046. this.setHidden(true);
  20047. }
  20048. return this;
  20049. },
  20050. /**
  20051. * Shows this component.
  20052. * @param {Object/Boolean} animation (optional)
  20053. * @return {Ext.Component}
  20054. * @chainable
  20055. */
  20056. show: function(animation) {
  20057. var hidden = this.getHidden();
  20058. if (hidden || hidden === null) {
  20059. if (animation === true) {
  20060. animation = 'fadeIn';
  20061. }
  20062. else if (animation === undefined || (animation && animation.isComponent)) {
  20063. animation = this.getShowAnimation();
  20064. }
  20065. if (animation) {
  20066. this.onBefore({
  20067. hiddenchange: 'animateFn',
  20068. scope: this,
  20069. single: true,
  20070. args: [animation]
  20071. });
  20072. }
  20073. this.setHidden(false);
  20074. }
  20075. return this;
  20076. },
  20077. animateFn: function(animation, component, newState, oldState, options, controller) {
  20078. if (animation && (!newState || (newState && this.isPainted()))) {
  20079. var anim = new Ext.fx.Animation(animation);
  20080. anim.setElement(component.element);
  20081. if (newState) {
  20082. anim.setOnEnd(function() {
  20083. controller.resume();
  20084. });
  20085. controller.pause();
  20086. }
  20087. Ext.Animator.run(anim);
  20088. }
  20089. },
  20090. /**
  20091. * @private
  20092. */
  20093. setVisibility: function(isVisible) {
  20094. this.renderElement.setVisibility(isVisible);
  20095. },
  20096. /**
  20097. * @private
  20098. */
  20099. isRendered: function() {
  20100. return this.rendered;
  20101. },
  20102. /**
  20103. * @private
  20104. */
  20105. isPainted: function() {
  20106. return this.renderElement.isPainted();
  20107. },
  20108. /**
  20109. * @private
  20110. */
  20111. applyTpl: function(config) {
  20112. return (Ext.isObject(config) && config.isTemplate) ? config : new Ext.XTemplate(config);
  20113. },
  20114. applyData: function(data) {
  20115. if (Ext.isObject(data)) {
  20116. return Ext.apply({}, data);
  20117. } else if (!data) {
  20118. data = {};
  20119. }
  20120. return data;
  20121. },
  20122. /**
  20123. * @private
  20124. */
  20125. updateData: function(newData) {
  20126. var me = this;
  20127. if (newData) {
  20128. var tpl = me.getTpl(),
  20129. tplWriteMode = me.getTplWriteMode();
  20130. if (tpl) {
  20131. tpl[tplWriteMode](me.getInnerHtmlElement(), newData);
  20132. }
  20133. /**
  20134. * @event updatedata
  20135. * Fires whenever the data of the component is updated
  20136. * @param {Ext.Component} this The component instance
  20137. * @param {Object} newData The new data
  20138. */
  20139. this.fireEvent('updatedata', me, newData);
  20140. }
  20141. },
  20142. applyRecord: function(config) {
  20143. if (config && Ext.isObject(config) && config.isModel) {
  20144. return config;
  20145. }
  20146. return null;
  20147. },
  20148. updateRecord: function(newRecord, oldRecord) {
  20149. var me = this;
  20150. if (oldRecord) {
  20151. oldRecord.unjoin(me);
  20152. }
  20153. if (!newRecord) {
  20154. me.updateData('');
  20155. }
  20156. else {
  20157. newRecord.join(me);
  20158. me.updateData(newRecord.getData(true));
  20159. }
  20160. },
  20161. // @private Used to handle joining of a record to a tpl
  20162. afterEdit: function() {
  20163. this.updateRecord(this.getRecord());
  20164. },
  20165. // @private Used to handle joining of a record to a tpl
  20166. afterErase: function() {
  20167. this.setRecord(null);
  20168. },
  20169. applyItemId: function(itemId) {
  20170. return itemId || this.getId();
  20171. },
  20172. /**
  20173. * Tests whether or not this Component is of a specific xtype. This can test whether this Component is descended
  20174. * from the xtype (default) or whether it is directly of the xtype specified (`shallow = true`).
  20175. * __If using your own subclasses, be aware that a Component must register its own xtype
  20176. * to participate in determination of inherited xtypes.__
  20177. *
  20178. * For a list of all available xtypes, see the {@link Ext.Component} header.
  20179. *
  20180. * Example usage:
  20181. *
  20182. * var t = new Ext.field.Text();
  20183. * var isText = t.isXType('textfield'); // true
  20184. * var isBoxSubclass = t.isXType('field'); // true, descended from Ext.field.Field
  20185. * var isBoxInstance = t.isXType('field', true); // false, not a direct Ext.field.Field instance
  20186. *
  20187. * @param {String} xtype The xtype to check for this Component.
  20188. * @param {Boolean} shallow (optional) `false` to check whether this Component is descended from the xtype (this is
  20189. * the default), or `true` to check whether this Component is directly of the specified xtype.
  20190. * @return {Boolean} `true` if this component descends from the specified xtype, `false` otherwise.
  20191. */
  20192. isXType: function(xtype, shallow) {
  20193. if (shallow) {
  20194. return this.xtypes.indexOf(xtype) != -1;
  20195. }
  20196. return Boolean(this.xtypesMap[xtype]);
  20197. },
  20198. /**
  20199. * Returns this Component's xtype hierarchy as a slash-delimited string. For a list of all
  20200. * available xtypes, see the {@link Ext.Component} header.
  20201. *
  20202. * __Note:__ If using your own subclasses, be aware that a Component must register its own xtype
  20203. * to participate in determination of inherited xtypes.
  20204. *
  20205. * Example usage:
  20206. *
  20207. * var t = new Ext.field.Text();
  20208. * alert(t.getXTypes()); // alerts 'component/field/textfield'
  20209. *
  20210. * @return {String} The xtype hierarchy string.
  20211. */
  20212. getXTypes: function() {
  20213. return this.xtypesChain.join('/');
  20214. },
  20215. getDraggableBehavior: function() {
  20216. var behavior = this.draggableBehavior;
  20217. if (!behavior) {
  20218. behavior = this.draggableBehavior = new Ext.behavior.Draggable(this);
  20219. }
  20220. return behavior;
  20221. },
  20222. applyDraggable: function(config) {
  20223. this.getDraggableBehavior().setConfig(config);
  20224. },
  20225. getDraggable: function() {
  20226. return this.getDraggableBehavior().getDraggable();
  20227. },
  20228. getTranslatableBehavior: function() {
  20229. var behavior = this.translatableBehavior;
  20230. if (!behavior) {
  20231. behavior = this.translatableBehavior = new Ext.behavior.Translatable(this);
  20232. }
  20233. return behavior;
  20234. },
  20235. applyTranslatable: function(config) {
  20236. this.getTranslatableBehavior().setConfig(config);
  20237. },
  20238. getTranslatable: function() {
  20239. return this.getTranslatableBehavior().getTranslatable();
  20240. },
  20241. translateAxis: function(axis, value, animation) {
  20242. var x, y;
  20243. if (axis === 'x') {
  20244. x = value;
  20245. }
  20246. else {
  20247. y = value;
  20248. }
  20249. return this.translate(x, y, animation);
  20250. },
  20251. translate: function() {
  20252. var translatable = this.getTranslatable();
  20253. if (!translatable) {
  20254. this.setTranslatable(true);
  20255. translatable = this.getTranslatable();
  20256. }
  20257. translatable.translate.apply(translatable, arguments);
  20258. },
  20259. /**
  20260. * @private
  20261. * @param rendered
  20262. */
  20263. setRendered: function(rendered) {
  20264. var wasRendered = this.rendered;
  20265. if (rendered !== wasRendered) {
  20266. this.rendered = rendered;
  20267. return true;
  20268. }
  20269. return false;
  20270. },
  20271. /**
  20272. * Sets the size of the Component.
  20273. * @param {Number} width The new width for the Component.
  20274. * @param {Number} height The new height for the Component.
  20275. */
  20276. setSize: function(width, height) {
  20277. if (width != undefined) {
  20278. this.setWidth(width);
  20279. }
  20280. if (height != undefined) {
  20281. this.setHeight(height);
  20282. }
  20283. },
  20284. //@private
  20285. doAddListener: function(name, fn, scope, options, order) {
  20286. if (options && 'element' in options) {
  20287. //<debug error>
  20288. if (this.referenceList.indexOf(options.element) === -1) {
  20289. Ext.Logger.error("Adding event listener with an invalid element reference of '" + options.element +
  20290. "' for this component. Available values are: '" + this.referenceList.join("', '") + "'", this);
  20291. }
  20292. //</debug>
  20293. // The default scope is this component
  20294. return this[options.element].doAddListener(name, fn, scope || this, options, order);
  20295. }
  20296. if (name == 'painted' || name == 'resize') {
  20297. return this.element.doAddListener(name, fn, scope || this, options, order);
  20298. }
  20299. return this.callParent(arguments);
  20300. },
  20301. //@private
  20302. doRemoveListener: function(name, fn, scope, options, order) {
  20303. if (options && 'element' in options) {
  20304. //<debug error>
  20305. if (this.referenceList.indexOf(options.element) === -1) {
  20306. Ext.Logger.error("Removing event listener with an invalid element reference of '" + options.element +
  20307. "' for this component. Available values are: '" + this.referenceList.join('", "') + "'", this);
  20308. }
  20309. //</debug>
  20310. // The default scope is this component
  20311. this[options.element].doRemoveListener(name, fn, scope || this, options, order);
  20312. }
  20313. return this.callParent(arguments);
  20314. },
  20315. /**
  20316. * Shows this component by another component. If you specify no alignment, it will automatically
  20317. * position this component relative to the reference component.
  20318. *
  20319. * For example, say we are aligning a Panel next to a Button, the alignment string would look like this:
  20320. *
  20321. * [panel-vertical (t/b/c)][panel-horizontal (l/r/c)]-[button-vertical (t/b/c)][button-horizontal (l/r/c)]
  20322. *
  20323. * where t = top, b = bottom, c = center, l = left, r = right.
  20324. *
  20325. * ## Examples
  20326. *
  20327. * - `tl-tr` means top-left corner of the Panel to the top-right corner of the Button
  20328. * - `tc-bc` means top-center of the Panel to the bottom-center of the Button
  20329. *
  20330. * You can put a '?' at the end of the alignment string to constrain the floating element to the
  20331. * {@link Ext.Viewport Viewport}
  20332. *
  20333. * // show `panel` by `button` using the default positioning (auto fit)
  20334. * panel.showBy(button);
  20335. *
  20336. * // align the top left corner of `panel` with the top right corner of `button` (constrained to viewport)
  20337. * panel.showBy(button, "tl-tr?");
  20338. *
  20339. * // align the bottom right corner of `panel` with the center left edge of `button` (not constrained by viewport)
  20340. * panel.showBy(button, "br-cl");
  20341. *
  20342. * @param {Ext.Component} component The target component to show this component by.
  20343. * @param {String} alignment (optional) The specific alignment.
  20344. */
  20345. showBy: function(component, alignment) {
  20346. var me = this,
  20347. viewport = Ext.Viewport,
  20348. parent = me.getParent();
  20349. me.setVisibility(false);
  20350. if (parent !== viewport) {
  20351. viewport.add(me);
  20352. }
  20353. me.show();
  20354. me.on({
  20355. hide: 'onShowByErased',
  20356. destroy: 'onShowByErased',
  20357. single: true,
  20358. scope: me
  20359. });
  20360. viewport.on('resize', 'alignTo', me, { args: [component, alignment] });
  20361. me.alignTo(component, alignment);
  20362. me.setVisibility(true);
  20363. },
  20364. /**
  20365. * @private
  20366. * @param component
  20367. */
  20368. onShowByErased: function() {
  20369. Ext.Viewport.un('resize', 'alignTo', this);
  20370. },
  20371. /**
  20372. * @private
  20373. */
  20374. alignTo: function(component, alignment) {
  20375. var alignToElement = component.isComponent ? component.renderElement : component,
  20376. element = this.renderElement,
  20377. alignToBox = alignToElement.getPageBox(),
  20378. constrainBox = this.getParent().element.getPageBox(),
  20379. box = element.getPageBox(),
  20380. alignToHeight = alignToBox.height,
  20381. alignToWidth = alignToBox.width,
  20382. height = box.height,
  20383. width = box.width;
  20384. // Keep off the sides...
  20385. constrainBox.bottom -= 5;
  20386. constrainBox.height -= 10;
  20387. constrainBox.left += 5;
  20388. constrainBox.right -= 5;
  20389. constrainBox.top += 5;
  20390. constrainBox.width -= 10;
  20391. if (!alignment || alignment === 'auto') {
  20392. if (constrainBox.bottom - alignToBox.bottom < height) {
  20393. if (alignToBox.top - constrainBox.top < height) {
  20394. if (alignToBox.left - constrainBox.left < width) {
  20395. alignment = 'cl-cr?';
  20396. }
  20397. else {
  20398. alignment = 'cr-cl?';
  20399. }
  20400. }
  20401. else {
  20402. alignment = 'bc-tc?';
  20403. }
  20404. }
  20405. else {
  20406. alignment = 'tc-bc?';
  20407. }
  20408. }
  20409. var matches = alignment.match(this.alignmentRegex);
  20410. //<debug error>
  20411. if (!matches) {
  20412. Ext.Logger.error("Invalid alignment value of '" + alignment + "'");
  20413. }
  20414. //</debug>
  20415. var from = matches[1].split(''),
  20416. to = matches[2].split(''),
  20417. constrained = (matches[3] === '?'),
  20418. fromVertical = from[0],
  20419. fromHorizontal = from[1] || fromVertical,
  20420. toVertical = to[0],
  20421. toHorizontal = to[1] || toVertical,
  20422. top = alignToBox.top,
  20423. left = alignToBox.left,
  20424. halfAlignHeight = alignToHeight / 2,
  20425. halfAlignWidth = alignToWidth / 2,
  20426. halfWidth = width / 2,
  20427. halfHeight = height / 2,
  20428. maxLeft, maxTop;
  20429. switch (fromVertical) {
  20430. case 't':
  20431. switch (toVertical) {
  20432. case 'c':
  20433. top += halfAlignHeight;
  20434. break;
  20435. case 'b':
  20436. top += alignToHeight;
  20437. }
  20438. break;
  20439. case 'b':
  20440. switch (toVertical) {
  20441. case 'c':
  20442. top -= (height - halfAlignHeight);
  20443. break;
  20444. case 't':
  20445. top -= height;
  20446. break;
  20447. case 'b':
  20448. top -= height - alignToHeight;
  20449. }
  20450. break;
  20451. case 'c':
  20452. switch (toVertical) {
  20453. case 't':
  20454. top -= halfHeight;
  20455. break;
  20456. case 'c':
  20457. top -= (halfHeight - halfAlignHeight);
  20458. break;
  20459. case 'b':
  20460. top -= (halfHeight - alignToHeight);
  20461. }
  20462. break;
  20463. }
  20464. switch (fromHorizontal) {
  20465. case 'l':
  20466. switch (toHorizontal) {
  20467. case 'c':
  20468. left += halfAlignHeight;
  20469. break;
  20470. case 'r':
  20471. left += alignToWidth;
  20472. }
  20473. break;
  20474. case 'r':
  20475. switch (toHorizontal) {
  20476. case 'r':
  20477. left -= (width - alignToWidth);
  20478. break;
  20479. case 'c':
  20480. left -= (width - halfWidth);
  20481. break;
  20482. case 'l':
  20483. left -= width;
  20484. }
  20485. break;
  20486. case 'c':
  20487. switch (toHorizontal) {
  20488. case 'l':
  20489. left -= halfWidth;
  20490. break;
  20491. case 'c':
  20492. left -= (halfWidth - halfAlignWidth);
  20493. break;
  20494. case 'r':
  20495. left -= (halfWidth - alignToWidth);
  20496. }
  20497. break;
  20498. }
  20499. if (constrained) {
  20500. maxLeft = (constrainBox.left + constrainBox.width) - width;
  20501. maxTop = (constrainBox.top + constrainBox.height) - height;
  20502. left = Math.max(constrainBox.left, Math.min(maxLeft, left));
  20503. top = Math.max(constrainBox.top, Math.min(maxTop, top));
  20504. }
  20505. this.setLeft(left);
  20506. this.setTop(top);
  20507. },
  20508. /**
  20509. * Walks up the `ownerCt` axis looking for an ancestor Container which matches
  20510. * the passed simple selector.
  20511. *
  20512. * Example:
  20513. *
  20514. * var owningTabPanel = grid.up('tabpanel');
  20515. *
  20516. * @param {String} selector (optional) The simple selector to test.
  20517. * @return {Ext.Container} The matching ancestor Container (or `undefined` if no match was found).
  20518. */
  20519. up: function(selector) {
  20520. var result = this.parent;
  20521. if (selector) {
  20522. for (; result; result = result.parent) {
  20523. if (Ext.ComponentQuery.is(result, selector)) {
  20524. return result;
  20525. }
  20526. }
  20527. }
  20528. return result;
  20529. },
  20530. getBubbleTarget: function() {
  20531. return this.getParent();
  20532. },
  20533. /**
  20534. * Destroys this Component. If it is currently added to a Container it will first be removed from that Container.
  20535. * All Ext.Element references are also deleted and the Component is de-registered from Ext.ComponentManager
  20536. */
  20537. destroy: function() {
  20538. this.destroy = Ext.emptyFn;
  20539. var parent = this.getParent(),
  20540. referenceList = this.referenceList,
  20541. i, ln, reference;
  20542. this.isDestroying = true;
  20543. Ext.destroy(this.getTranslatable(), this.getPlugins());
  20544. // Remove this component itself from the container if it's currently contained
  20545. if (parent) {
  20546. parent.remove(this, false);
  20547. }
  20548. // Destroy all element references
  20549. for (i = 0, ln = referenceList.length; i < ln; i++) {
  20550. reference = referenceList[i];
  20551. this[reference].destroy();
  20552. delete this[reference];
  20553. }
  20554. Ext.destroy(this.innerHtmlElement);
  20555. this.setRecord(null);
  20556. this.callSuper();
  20557. Ext.ComponentManager.unregister(this);
  20558. }
  20559. // Convert old properties in data into a config object
  20560. }, function() {
  20561. });
  20562. })(Ext.baseCSSPrefix);
  20563. /**
  20564. *
  20565. */
  20566. Ext.define('Ext.layout.wrapper.Inner', {
  20567. config: {
  20568. sizeState: null,
  20569. container: null
  20570. },
  20571. constructor: function(config) {
  20572. this.initConfig(config);
  20573. },
  20574. getElement: function() {
  20575. return this.getContainer().bodyElement;
  20576. },
  20577. setInnerWrapper: Ext.emptyFn,
  20578. getInnerWrapper: Ext.emptyFn
  20579. });
  20580. /**
  20581. *
  20582. */
  20583. Ext.define('Ext.layout.Abstract', {
  20584. mixins: ['Ext.mixin.Observable'],
  20585. isLayout: true,
  20586. constructor: function(config) {
  20587. this.initialConfig = config;
  20588. },
  20589. setContainer: function(container) {
  20590. this.container = container;
  20591. this.initConfig(this.initialConfig);
  20592. return this;
  20593. },
  20594. onItemAdd: function() {},
  20595. onItemRemove: function() {},
  20596. onItemMove: function() {},
  20597. onItemCenteredChange: function() {},
  20598. onItemFloatingChange: function() {},
  20599. onItemDockedChange: function() {},
  20600. onItemInnerStateChange: function() {}
  20601. });
  20602. /**
  20603. *
  20604. */
  20605. Ext.define('Ext.mixin.Bindable', {
  20606. extend: 'Ext.mixin.Mixin',
  20607. mixinConfig: {
  20608. id: 'bindable'
  20609. },
  20610. bind: function(instance, boundMethod, bindingMethod, preventDefault) {
  20611. if (!bindingMethod) {
  20612. bindingMethod = boundMethod;
  20613. }
  20614. var boundFn = instance[boundMethod],
  20615. fn;
  20616. instance[boundMethod] = fn = function() {
  20617. var binding = fn.$binding,
  20618. scope = binding.bindingScope,
  20619. args = Array.prototype.slice.call(arguments);
  20620. args.push(arguments);
  20621. if (!binding.preventDefault && scope[binding.bindingMethod].apply(scope, args) !== false) {
  20622. return binding.boundFn.apply(this, arguments);
  20623. }
  20624. };
  20625. fn.$binding = {
  20626. preventDefault: !!preventDefault,
  20627. boundFn: boundFn,
  20628. bindingMethod: bindingMethod,
  20629. bindingScope: this
  20630. };
  20631. return this;
  20632. },
  20633. unbind: function(instance, boundMethod, bindingMethod) {
  20634. if (!bindingMethod) {
  20635. bindingMethod = boundMethod;
  20636. }
  20637. var fn = instance[boundMethod],
  20638. binding = fn.$binding,
  20639. boundFn, currentBinding;
  20640. while (binding) {
  20641. boundFn = binding.boundFn;
  20642. if (binding.bindingMethod === bindingMethod && binding.bindingScope === this) {
  20643. if (currentBinding) {
  20644. currentBinding.boundFn = boundFn;
  20645. }
  20646. else {
  20647. instance[boundMethod] = boundFn;
  20648. }
  20649. return this;
  20650. }
  20651. currentBinding = binding;
  20652. binding = binding.boundFn;
  20653. }
  20654. return this;
  20655. }
  20656. });
  20657. /**
  20658. *
  20659. */
  20660. Ext.define('Ext.util.Wrapper', {
  20661. mixins: ['Ext.mixin.Bindable'],
  20662. constructor: function(elementConfig, wrappedElement) {
  20663. var element = this.link('element', Ext.Element.create(elementConfig));
  20664. if (wrappedElement) {
  20665. element.insertBefore(wrappedElement);
  20666. this.wrap(wrappedElement);
  20667. }
  20668. },
  20669. bindSize: function(sizeName) {
  20670. var wrappedElement = this.wrappedElement,
  20671. boundMethodName;
  20672. this.boundSizeName = sizeName;
  20673. this.boundMethodName = boundMethodName = sizeName === 'width' ? 'setWidth' : 'setHeight';
  20674. this.bind(wrappedElement, boundMethodName, 'onBoundSizeChange');
  20675. wrappedElement[boundMethodName].call(wrappedElement, wrappedElement.getStyleValue(sizeName));
  20676. },
  20677. onBoundSizeChange: function(size, args) {
  20678. var element = this.element;
  20679. if (typeof size === 'string' && size.substr(-1) === '%') {
  20680. args[0] = '100%';
  20681. }
  20682. else {
  20683. size = '';
  20684. }
  20685. element[this.boundMethodName].call(element, size);
  20686. },
  20687. wrap: function(wrappedElement) {
  20688. var element = this.element,
  20689. innerDom;
  20690. this.wrappedElement = wrappedElement;
  20691. innerDom = element.dom;
  20692. while (innerDom.firstElementChild !== null) {
  20693. innerDom = innerDom.firstElementChild;
  20694. }
  20695. innerDom.appendChild(wrappedElement.dom);
  20696. },
  20697. destroy: function() {
  20698. var element = this.element,
  20699. dom = element.dom,
  20700. wrappedElement = this.wrappedElement,
  20701. boundMethodName = this.boundMethodName,
  20702. parentNode = dom.parentNode,
  20703. size;
  20704. if (boundMethodName) {
  20705. this.unbind(wrappedElement, boundMethodName, 'onBoundSizeChange');
  20706. size = element.getStyle(this.boundSizeName);
  20707. if (size) {
  20708. wrappedElement[boundMethodName].call(wrappedElement, size);
  20709. }
  20710. }
  20711. if (parentNode) {
  20712. if (!wrappedElement.isDestroyed) {
  20713. parentNode.replaceChild(dom.firstElementChild, dom);
  20714. }
  20715. delete this.wrappedElement;
  20716. }
  20717. this.callSuper();
  20718. }
  20719. });
  20720. /**
  20721. *
  20722. */
  20723. Ext.define('Ext.layout.wrapper.BoxDock', {
  20724. config: {
  20725. direction: 'horizontal',
  20726. element: {
  20727. className: 'x-dock'
  20728. },
  20729. bodyElement: {
  20730. className: 'x-dock-body'
  20731. },
  20732. innerWrapper: null,
  20733. sizeState: false,
  20734. container: null
  20735. },
  20736. positionMap: {
  20737. top: 'start',
  20738. left: 'start',
  20739. bottom: 'end',
  20740. right: 'end'
  20741. },
  20742. constructor: function(config) {
  20743. this.items = {
  20744. start: [],
  20745. end: []
  20746. };
  20747. this.itemsCount = 0;
  20748. this.initConfig(config);
  20749. },
  20750. addItems: function(items) {
  20751. var i, ln, item;
  20752. for (i = 0, ln = items.length; i < ln; i++) {
  20753. item = items[i];
  20754. this.addItem(item);
  20755. }
  20756. },
  20757. addItem: function(item) {
  20758. var docked = item.getDocked(),
  20759. position = this.positionMap[docked],
  20760. wrapper = item.$dockWrapper,
  20761. container = this.getContainer(),
  20762. index = container.indexOf(item),
  20763. element = item.element,
  20764. items = this.items,
  20765. sideItems = items[position],
  20766. i, ln, sibling, referenceElement, siblingIndex;
  20767. if (wrapper) {
  20768. wrapper.removeItem(item);
  20769. }
  20770. item.$dockWrapper = this;
  20771. item.addCls('x-dock-item');
  20772. item.addCls('x-docked-' + docked);
  20773. for (i = 0, ln = sideItems.length; i < ln; i++) {
  20774. sibling = sideItems[i];
  20775. siblingIndex = container.indexOf(sibling);
  20776. if (siblingIndex > index) {
  20777. referenceElement = sibling.element;
  20778. sideItems.splice(i, 0, item);
  20779. break;
  20780. }
  20781. }
  20782. if (!referenceElement) {
  20783. sideItems.push(item);
  20784. referenceElement = this.getBodyElement();
  20785. }
  20786. this.itemsCount++;
  20787. if (position === 'start') {
  20788. element.insertBefore(referenceElement);
  20789. }
  20790. else {
  20791. element.insertAfter(referenceElement);
  20792. }
  20793. },
  20794. removeItem: function(item) {
  20795. var position = item.getDocked(),
  20796. items = this.items[this.positionMap[position]];
  20797. Ext.Array.remove(items, item);
  20798. item.element.detach();
  20799. delete item.$dockWrapper;
  20800. item.removeCls('x-dock-item');
  20801. item.removeCls('x-docked-' + position);
  20802. if (--this.itemsCount === 0) {
  20803. this.destroy();
  20804. }
  20805. },
  20806. getItemsSlice: function(index) {
  20807. var container = this.getContainer(),
  20808. items = this.items,
  20809. slice = [],
  20810. sideItems, i, ln, item;
  20811. for (sideItems = items.start, i = 0, ln = sideItems.length; i < ln; i++) {
  20812. item = sideItems[i];
  20813. if (container.indexOf(item) > index) {
  20814. slice.push(item);
  20815. }
  20816. }
  20817. for (sideItems = items.end, i = 0, ln = sideItems.length; i < ln; i++) {
  20818. item = sideItems[i];
  20819. if (container.indexOf(item) > index) {
  20820. slice.push(item);
  20821. }
  20822. }
  20823. return slice;
  20824. },
  20825. applyElement: function(element) {
  20826. return Ext.Element.create(element);
  20827. },
  20828. updateElement: function(element) {
  20829. element.addCls('x-dock-' + this.getDirection());
  20830. },
  20831. applyBodyElement: function(bodyElement) {
  20832. return Ext.Element.create(bodyElement);
  20833. },
  20834. updateBodyElement: function(bodyElement) {
  20835. this.getElement().append(bodyElement);
  20836. },
  20837. updateInnerWrapper: function(innerWrapper, oldInnerWrapper) {
  20838. var bodyElement = this.getBodyElement();
  20839. if (oldInnerWrapper && oldInnerWrapper.$outerWrapper === this) {
  20840. oldInnerWrapper.getElement().detach();
  20841. delete oldInnerWrapper.$outerWrapper;
  20842. }
  20843. if (innerWrapper) {
  20844. innerWrapper.setSizeState(this.getSizeState());
  20845. innerWrapper.$outerWrapper = this;
  20846. bodyElement.append(innerWrapper.getElement());
  20847. }
  20848. },
  20849. updateSizeState: function(state) {
  20850. var innerWrapper = this.getInnerWrapper();
  20851. this.getElement().setSizeState(state);
  20852. if (innerWrapper) {
  20853. innerWrapper.setSizeState(state);
  20854. }
  20855. },
  20856. destroy: function() {
  20857. var innerWrapper = this.getInnerWrapper(),
  20858. outerWrapper = this.$outerWrapper,
  20859. innerWrapperElement;
  20860. if (innerWrapper) {
  20861. if (outerWrapper) {
  20862. outerWrapper.setInnerWrapper(innerWrapper);
  20863. }
  20864. else {
  20865. innerWrapperElement = innerWrapper.getElement();
  20866. if (!innerWrapperElement.isDestroyed) {
  20867. innerWrapperElement.replace(this.getElement());
  20868. }
  20869. delete innerWrapper.$outerWrapper;
  20870. }
  20871. }
  20872. delete this.$outerWrapper;
  20873. this.setInnerWrapper(null);
  20874. this.unlink('_bodyElement', '_element');
  20875. this.callSuper();
  20876. }
  20877. });
  20878. /**
  20879. *
  20880. */
  20881. Ext.define('Ext.layout.Default', {
  20882. extend: 'Ext.layout.Abstract',
  20883. isAuto: true,
  20884. alias: ['layout.default', 'layout.auto'],
  20885. requires: [
  20886. 'Ext.util.Wrapper',
  20887. 'Ext.layout.wrapper.BoxDock',
  20888. 'Ext.layout.wrapper.Inner'
  20889. ],
  20890. config: {
  20891. /**
  20892. * @cfg {Ext.fx.layout.Card} animation Layout animation configuration
  20893. * Controls how layout transitions are animated. Currently only available for
  20894. * Card Layouts.
  20895. *
  20896. * Possible values are:
  20897. *
  20898. * - cover
  20899. * - cube
  20900. * - fade
  20901. * - flip
  20902. * - pop
  20903. * - reveal
  20904. * - scroll
  20905. * - slide
  20906. * @accessor
  20907. */
  20908. animation: null
  20909. },
  20910. centerWrapperClass: 'x-center',
  20911. dockWrapperClass: 'x-dock',
  20912. positionMap: {
  20913. top: 'start',
  20914. left: 'start',
  20915. middle: 'center',
  20916. bottom: 'end',
  20917. right: 'end'
  20918. },
  20919. positionDirectionMap: {
  20920. top: 'vertical',
  20921. bottom: 'vertical',
  20922. left: 'horizontal',
  20923. right: 'horizontal'
  20924. },
  20925. setContainer: function(container) {
  20926. var options = {
  20927. delegate: '> component'
  20928. };
  20929. this.dockedItems = [];
  20930. this.callSuper(arguments);
  20931. container.on('centeredchange', 'onItemCenteredChange', this, options, 'before')
  20932. .on('floatingchange', 'onItemFloatingChange', this, options, 'before')
  20933. .on('dockedchange', 'onBeforeItemDockedChange', this, options, 'before')
  20934. .on('dockedchange', 'onAfterItemDockedChange', this, options);
  20935. },
  20936. monitorSizeStateChange: function() {
  20937. this.monitorSizeStateChange = Ext.emptyFn;
  20938. this.container.on('sizestatechange', 'onContainerSizeStateChange', this);
  20939. },
  20940. monitorSizeFlagsChange: function() {
  20941. this.monitorSizeFlagsChange = Ext.emptyFn;
  20942. this.container.on('sizeflagschange', 'onContainerSizeFlagsChange', this);
  20943. },
  20944. onItemAdd: function(item) {
  20945. var docked = item.getDocked();
  20946. if (docked !== null) {
  20947. this.dockItem(item);
  20948. }
  20949. else if (item.isCentered()) {
  20950. this.onItemCenteredChange(item, true);
  20951. }
  20952. else if (item.isFloating()) {
  20953. this.onItemFloatingChange(item, true);
  20954. }
  20955. else {
  20956. this.onItemInnerStateChange(item, true);
  20957. }
  20958. },
  20959. /**
  20960. *
  20961. * @param item
  20962. * @param isInner
  20963. * @param [destroying]
  20964. */
  20965. onItemInnerStateChange: function(item, isInner, destroying) {
  20966. if (isInner) {
  20967. this.insertInnerItem(item, this.container.innerIndexOf(item));
  20968. }
  20969. else {
  20970. this.removeInnerItem(item);
  20971. }
  20972. },
  20973. insertInnerItem: function(item, index) {
  20974. var container = this.container,
  20975. containerDom = container.innerElement.dom,
  20976. itemDom = item.element.dom,
  20977. nextSibling = container.getInnerAt(index + 1),
  20978. nextSiblingDom = nextSibling ? nextSibling.element.dom : null;
  20979. containerDom.insertBefore(itemDom, nextSiblingDom);
  20980. return this;
  20981. },
  20982. insertBodyItem: function(item) {
  20983. var container = this.container.setUseBodyElement(true),
  20984. bodyDom = container.bodyElement.dom;
  20985. if (item.getZIndex() === null) {
  20986. item.setZIndex((container.indexOf(item) + 1) * 2);
  20987. }
  20988. bodyDom.insertBefore(item.element.dom, bodyDom.firstChild);
  20989. return this;
  20990. },
  20991. removeInnerItem: function(item) {
  20992. item.element.detach();
  20993. },
  20994. removeBodyItem: function(item) {
  20995. item.setZIndex(null);
  20996. item.element.detach();
  20997. },
  20998. onItemRemove: function(item, index, destroying) {
  20999. var docked = item.getDocked();
  21000. if (docked) {
  21001. this.undockItem(item);
  21002. }
  21003. else if (item.isCentered()) {
  21004. this.onItemCenteredChange(item, false);
  21005. }
  21006. else if (item.isFloating()) {
  21007. this.onItemFloatingChange(item, false);
  21008. }
  21009. else {
  21010. this.onItemInnerStateChange(item, false, destroying);
  21011. }
  21012. },
  21013. onItemMove: function(item, toIndex, fromIndex) {
  21014. if (item.isCentered() || item.isFloating()) {
  21015. item.setZIndex((toIndex + 1) * 2);
  21016. }
  21017. else if (item.isInnerItem()) {
  21018. this.insertInnerItem(item, this.container.innerIndexOf(item));
  21019. }
  21020. else {
  21021. this.undockItem(item);
  21022. this.dockItem(item);
  21023. }
  21024. },
  21025. onItemCenteredChange: function(item, centered) {
  21026. var wrapperName = '$centerWrapper';
  21027. if (centered) {
  21028. this.insertBodyItem(item);
  21029. item.link(wrapperName, new Ext.util.Wrapper({
  21030. className: this.centerWrapperClass
  21031. }, item.element));
  21032. }
  21033. else {
  21034. item.unlink(wrapperName);
  21035. this.removeBodyItem(item);
  21036. }
  21037. },
  21038. onItemFloatingChange: function(item, floating) {
  21039. if (floating) {
  21040. this.insertBodyItem(item);
  21041. }
  21042. else {
  21043. this.removeBodyItem(item);
  21044. }
  21045. },
  21046. onBeforeItemDockedChange: function(item, docked, oldDocked) {
  21047. if (oldDocked) {
  21048. this.undockItem(item);
  21049. }
  21050. },
  21051. onAfterItemDockedChange: function(item, docked, oldDocked) {
  21052. if (docked) {
  21053. this.dockItem(item);
  21054. }
  21055. },
  21056. onContainerSizeStateChange: function() {
  21057. var dockWrapper = this.getDockWrapper();
  21058. if (dockWrapper) {
  21059. dockWrapper.setSizeState(this.container.getSizeState());
  21060. }
  21061. },
  21062. onContainerSizeFlagsChange: function() {
  21063. var items = this.dockedItems,
  21064. i, ln, item;
  21065. for (i = 0, ln = items.length; i < ln; i++) {
  21066. item = items[i];
  21067. this.refreshDockedItemLayoutSizeFlags(item);
  21068. }
  21069. },
  21070. refreshDockedItemLayoutSizeFlags: function(item) {
  21071. var container = this.container,
  21072. dockedDirection = this.positionDirectionMap[item.getDocked()],
  21073. binaryMask = (dockedDirection === 'horizontal') ? container.LAYOUT_HEIGHT : container.LAYOUT_WIDTH,
  21074. flags = (container.getSizeFlags() & binaryMask);
  21075. item.setLayoutSizeFlags(flags);
  21076. },
  21077. dockItem: function(item) {
  21078. var DockClass = Ext.layout.wrapper.BoxDock,
  21079. dockedItems = this.dockedItems,
  21080. ln = dockedItems.length,
  21081. container = this.container,
  21082. itemIndex = container.indexOf(item),
  21083. positionDirectionMap = this.positionDirectionMap,
  21084. direction = positionDirectionMap[item.getDocked()],
  21085. dockInnerWrapper = this.dockInnerWrapper,
  21086. referenceDirection, i, dockedItem, index, previousItem, slice,
  21087. referenceItem, referenceDocked, referenceWrapper, newWrapper, nestedWrapper;
  21088. this.monitorSizeStateChange();
  21089. this.monitorSizeFlagsChange();
  21090. if (!dockInnerWrapper) {
  21091. dockInnerWrapper = this.link('dockInnerWrapper', new Ext.layout.wrapper.Inner({
  21092. container: this.container
  21093. }));
  21094. }
  21095. if (ln === 0) {
  21096. dockedItems.push(item);
  21097. newWrapper = new DockClass({
  21098. container: this.container,
  21099. direction: direction
  21100. });
  21101. newWrapper.addItem(item);
  21102. newWrapper.getElement().replace(dockInnerWrapper.getElement());
  21103. newWrapper.setInnerWrapper(dockInnerWrapper);
  21104. container.onInitialized('onContainerSizeStateChange', this);
  21105. }
  21106. else {
  21107. for (i = 0; i < ln; i++) {
  21108. dockedItem = dockedItems[i];
  21109. index = container.indexOf(dockedItem);
  21110. if (index > itemIndex) {
  21111. referenceItem = previousItem || dockedItems[0];
  21112. dockedItems.splice(i, 0, item);
  21113. break;
  21114. }
  21115. previousItem = dockedItem;
  21116. }
  21117. if (!referenceItem) {
  21118. referenceItem = dockedItems[ln - 1];
  21119. dockedItems.push(item);
  21120. }
  21121. referenceDocked = referenceItem.getDocked();
  21122. referenceWrapper = referenceItem.$dockWrapper;
  21123. referenceDirection = positionDirectionMap[referenceDocked];
  21124. if (direction === referenceDirection) {
  21125. referenceWrapper.addItem(item);
  21126. }
  21127. else {
  21128. slice = referenceWrapper.getItemsSlice(itemIndex);
  21129. newWrapper = new DockClass({
  21130. container: this.container,
  21131. direction: direction
  21132. });
  21133. if (slice.length > 0) {
  21134. if (slice.length === referenceWrapper.itemsCount) {
  21135. nestedWrapper = referenceWrapper;
  21136. newWrapper.setSizeState(nestedWrapper.getSizeState());
  21137. newWrapper.getElement().replace(nestedWrapper.getElement());
  21138. }
  21139. else {
  21140. nestedWrapper = new DockClass({
  21141. container: this.container,
  21142. direction: referenceDirection
  21143. });
  21144. nestedWrapper.setInnerWrapper(referenceWrapper.getInnerWrapper());
  21145. nestedWrapper.addItems(slice);
  21146. referenceWrapper.setInnerWrapper(newWrapper);
  21147. }
  21148. newWrapper.setInnerWrapper(nestedWrapper);
  21149. }
  21150. else {
  21151. newWrapper.setInnerWrapper(referenceWrapper.getInnerWrapper());
  21152. referenceWrapper.setInnerWrapper(newWrapper);
  21153. }
  21154. newWrapper.addItem(item);
  21155. }
  21156. }
  21157. container.onInitialized('refreshDockedItemLayoutSizeFlags', this, [item]);
  21158. },
  21159. getDockWrapper: function() {
  21160. var dockedItems = this.dockedItems;
  21161. if (dockedItems.length > 0) {
  21162. return dockedItems[0].$dockWrapper;
  21163. }
  21164. return null;
  21165. },
  21166. undockItem: function(item) {
  21167. var dockedItems = this.dockedItems;
  21168. if (item.$dockWrapper) {
  21169. item.$dockWrapper.removeItem(item);
  21170. }
  21171. Ext.Array.remove(dockedItems, item);
  21172. item.setLayoutSizeFlags(0);
  21173. },
  21174. destroy: function() {
  21175. this.dockedItems.length = 0;
  21176. delete this.dockedItems;
  21177. this.callSuper();
  21178. }
  21179. });
  21180. /**
  21181. *
  21182. */
  21183. Ext.define('Ext.layout.Box', {
  21184. extend: 'Ext.layout.Default',
  21185. config: {
  21186. orient: 'horizontal',
  21187. align: 'start',
  21188. pack: 'start'
  21189. },
  21190. alias: 'layout.tablebox',
  21191. layoutBaseClass: 'x-layout-tablebox',
  21192. itemClass: 'x-layout-tablebox-item',
  21193. setContainer: function(container) {
  21194. this.callSuper(arguments);
  21195. container.innerElement.addCls(this.layoutBaseClass);
  21196. container.on('flexchange', 'onItemFlexChange', this, {
  21197. delegate: '> component'
  21198. });
  21199. },
  21200. onItemInnerStateChange: function(item, isInner) {
  21201. this.callSuper(arguments);
  21202. item.toggleCls(this.itemClass, isInner);
  21203. },
  21204. onItemFlexChange: function() {
  21205. }
  21206. });
  21207. /**
  21208. *
  21209. */
  21210. Ext.define('Ext.layout.FlexBox', {
  21211. extend: 'Ext.layout.Box',
  21212. alias: 'layout.box',
  21213. config: {
  21214. align: 'stretch'
  21215. },
  21216. layoutBaseClass: 'x-layout-box',
  21217. itemClass: 'x-layout-box-item',
  21218. setContainer: function(container) {
  21219. this.callSuper(arguments);
  21220. this.monitorSizeFlagsChange();
  21221. },
  21222. applyOrient: function(orient) {
  21223. //<debug error>
  21224. if (orient !== 'horizontal' && orient !== 'vertical') {
  21225. Ext.Logger.error("Invalid box orient of: '" + orient + "', must be either 'horizontal' or 'vertical'");
  21226. }
  21227. //</debug>
  21228. return orient;
  21229. },
  21230. updateOrient: function(orient, oldOrient) {
  21231. var container = this.container,
  21232. delegation = {
  21233. delegate: '> component'
  21234. };
  21235. if (orient === 'horizontal') {
  21236. this.sizePropertyName = 'width';
  21237. }
  21238. else {
  21239. this.sizePropertyName = 'height';
  21240. }
  21241. container.innerElement.swapCls('x-' + orient, 'x-' + oldOrient);
  21242. if (oldOrient) {
  21243. container.un(oldOrient === 'horizontal' ? 'widthchange' : 'heightchange', 'onItemSizeChange', this, delegation);
  21244. this.redrawContainer();
  21245. }
  21246. container.on(orient === 'horizontal' ? 'widthchange' : 'heightchange', 'onItemSizeChange', this, delegation);
  21247. },
  21248. onItemInnerStateChange: function(item, isInner) {
  21249. this.callSuper(arguments);
  21250. var flex, size;
  21251. item.toggleCls(this.itemClass, isInner);
  21252. if (isInner) {
  21253. flex = item.getFlex();
  21254. size = item.get(this.sizePropertyName);
  21255. if (flex) {
  21256. this.doItemFlexChange(item, flex);
  21257. }
  21258. else if (size) {
  21259. this.doItemSizeChange(item, size);
  21260. }
  21261. }
  21262. this.refreshItemSizeState(item);
  21263. },
  21264. refreshItemSizeState: function(item) {
  21265. var isInner = item.isInnerItem(),
  21266. container = this.container,
  21267. LAYOUT_HEIGHT = container.LAYOUT_HEIGHT,
  21268. LAYOUT_WIDTH = container.LAYOUT_WIDTH,
  21269. dimension = this.sizePropertyName,
  21270. layoutSizeFlags = 0,
  21271. containerSizeFlags = container.getSizeFlags();
  21272. if (isInner) {
  21273. layoutSizeFlags |= container.LAYOUT_STRETCHED;
  21274. if (this.getAlign() === 'stretch') {
  21275. layoutSizeFlags |= containerSizeFlags & (dimension === 'width' ? LAYOUT_HEIGHT : LAYOUT_WIDTH);
  21276. }
  21277. if (item.getFlex()) {
  21278. layoutSizeFlags |= containerSizeFlags & (dimension === 'width' ? LAYOUT_WIDTH : LAYOUT_HEIGHT);
  21279. }
  21280. }
  21281. item.setLayoutSizeFlags(layoutSizeFlags);
  21282. },
  21283. refreshAllItemSizedStates: function() {
  21284. var innerItems = this.container.innerItems,
  21285. i, ln, item;
  21286. for (i = 0,ln = innerItems.length; i < ln; i++) {
  21287. item = innerItems[i];
  21288. this.refreshItemSizeState(item);
  21289. }
  21290. },
  21291. onContainerSizeFlagsChange: function() {
  21292. this.refreshAllItemSizedStates();
  21293. this.callSuper(arguments);
  21294. },
  21295. onItemSizeChange: function(item, size) {
  21296. if (item.isInnerItem()) {
  21297. this.doItemSizeChange(item, size);
  21298. }
  21299. },
  21300. doItemSizeChange: function(item, size) {
  21301. if (size) {
  21302. item.setFlex(null);
  21303. this.redrawContainer();
  21304. }
  21305. },
  21306. onItemFlexChange: function(item, flex) {
  21307. if (item.isInnerItem()) {
  21308. this.doItemFlexChange(item, flex);
  21309. this.refreshItemSizeState(item);
  21310. }
  21311. },
  21312. doItemFlexChange: function(item, flex) {
  21313. this.setItemFlex(item, flex);
  21314. if (flex) {
  21315. item.set(this.sizePropertyName, null);
  21316. }
  21317. else {
  21318. this.redrawContainer();
  21319. }
  21320. },
  21321. redrawContainer: function() {
  21322. var container = this.container,
  21323. renderedTo = container.element.dom.parentNode;
  21324. if (renderedTo && renderedTo.nodeType !== 11) {
  21325. container.innerElement.redraw();
  21326. }
  21327. },
  21328. /**
  21329. * Sets the flex of an item in this box layout.
  21330. * @param {Ext.Component} item The item of this layout which you want to update the flex of.
  21331. * @param {Number} flex The flex to set on this method
  21332. */
  21333. setItemFlex: function(item, flex) {
  21334. var element = item.element;
  21335. element.toggleCls('x-flexed', !!flex);
  21336. element.setStyle('-webkit-box-flex', flex);
  21337. },
  21338. convertPosition: function(position) {
  21339. var positionMap = this.positionMap;
  21340. if (positionMap.hasOwnProperty(position)) {
  21341. return positionMap[position];
  21342. }
  21343. return position;
  21344. },
  21345. applyAlign: function(align) {
  21346. return this.convertPosition(align);
  21347. },
  21348. updateAlign: function(align, oldAlign) {
  21349. var container = this.container;
  21350. container.innerElement.swapCls(align, oldAlign, true, 'x-align');
  21351. if (oldAlign !== undefined) {
  21352. this.refreshAllItemSizedStates();
  21353. }
  21354. },
  21355. applyPack: function(pack) {
  21356. return this.convertPosition(pack);
  21357. },
  21358. updatePack: function(pack, oldPack) {
  21359. this.container.innerElement.swapCls(pack, oldPack, true, 'x-pack');
  21360. }
  21361. });
  21362. /**
  21363. *
  21364. */
  21365. Ext.define('Ext.layout.HBox', {
  21366. extend: 'Ext.layout.FlexBox',
  21367. alias: 'layout.hbox'
  21368. });
  21369. /**
  21370. *
  21371. */
  21372. Ext.define('Ext.layout.Fit', {
  21373. extend: 'Ext.layout.Default',
  21374. isFit: true,
  21375. alias: 'layout.fit',
  21376. layoutClass: 'x-layout-fit',
  21377. itemClass: 'x-layout-fit-item',
  21378. setContainer: function(container) {
  21379. this.callSuper(arguments);
  21380. container.innerElement.addCls(this.layoutClass);
  21381. this.onContainerSizeFlagsChange();
  21382. this.monitorSizeFlagsChange();
  21383. },
  21384. onContainerSizeFlagsChange: function() {
  21385. var container = this.container,
  21386. sizeFlags = container.getSizeFlags(),
  21387. stretched = Boolean(sizeFlags & container.LAYOUT_STRETCHED),
  21388. innerItems = container.innerItems,
  21389. i, ln, item;
  21390. this.callSuper();
  21391. for (i = 0,ln = innerItems.length; i < ln; i++) {
  21392. item = innerItems[i];
  21393. item.setLayoutSizeFlags(sizeFlags);
  21394. }
  21395. container.innerElement.toggleCls('x-stretched', stretched);
  21396. },
  21397. onItemInnerStateChange: function(item, isInner) {
  21398. this.callSuper(arguments);
  21399. item.toggleCls(this.itemClass, isInner);
  21400. item.setLayoutSizeFlags(isInner ? this.container.getSizeFlags() : 0);
  21401. }
  21402. });
  21403. /**
  21404. *
  21405. */
  21406. Ext.define('Ext.layout.Float', {
  21407. extend: 'Ext.layout.Default',
  21408. alias: 'layout.float',
  21409. config: {
  21410. direction: 'left'
  21411. },
  21412. layoutClass: 'layout-float',
  21413. itemClass: 'layout-float-item',
  21414. setContainer: function(container) {
  21415. this.callSuper(arguments);
  21416. container.innerElement.addCls(this.layoutClass);
  21417. },
  21418. onItemInnerStateChange: function(item, isInner) {
  21419. this.callSuper(arguments);
  21420. item.toggleCls(this.itemClass, isInner);
  21421. },
  21422. updateDirection: function(direction, oldDirection) {
  21423. var prefix = 'direction-';
  21424. this.container.innerElement.swapCls(prefix + direction, prefix + oldDirection);
  21425. }
  21426. });
  21427. /**
  21428. *
  21429. */
  21430. Ext.define('Ext.layout.wrapper.Dock', {
  21431. requires: [
  21432. 'Ext.util.Wrapper'
  21433. ],
  21434. config: {
  21435. direction: 'horizontal',
  21436. element: {
  21437. className: 'x-dock'
  21438. },
  21439. bodyElement: {
  21440. className: 'x-dock-body'
  21441. },
  21442. innerWrapper: null,
  21443. sizeState: false,
  21444. container: null
  21445. },
  21446. positionMap: {
  21447. top: 'start',
  21448. left: 'start',
  21449. bottom: 'end',
  21450. right: 'end'
  21451. },
  21452. constructor: function(config) {
  21453. this.items = {
  21454. start: [],
  21455. end: []
  21456. };
  21457. this.itemsCount = 0;
  21458. this.initConfig(config);
  21459. },
  21460. addItems: function(items) {
  21461. var i, ln, item;
  21462. for (i = 0, ln = items.length; i < ln; i++) {
  21463. item = items[i];
  21464. this.addItem(item);
  21465. }
  21466. },
  21467. addItem: function(item) {
  21468. var docked = item.getDocked(),
  21469. position = this.positionMap[docked],
  21470. wrapper = item.$dockWrapper,
  21471. container = this.getContainer(),
  21472. index = container.indexOf(item),
  21473. items = this.items,
  21474. sideItems = items[position],
  21475. itemWrapper, element, i, ln, sibling, referenceElement, siblingIndex;
  21476. if (wrapper) {
  21477. wrapper.removeItem(item);
  21478. }
  21479. item.$dockWrapper = this;
  21480. itemWrapper = item.link('$dockItemWrapper', new Ext.util.Wrapper({
  21481. className: 'x-dock-item'
  21482. }));
  21483. item.addCls('x-docked-' + docked);
  21484. element = itemWrapper.element;
  21485. for (i = 0, ln = sideItems.length; i < ln; i++) {
  21486. sibling = sideItems[i];
  21487. siblingIndex = container.indexOf(sibling);
  21488. if (siblingIndex > index) {
  21489. referenceElement = sibling.element;
  21490. sideItems.splice(i, 0, item);
  21491. break;
  21492. }
  21493. }
  21494. if (!referenceElement) {
  21495. sideItems.push(item);
  21496. referenceElement = this.getBodyElement();
  21497. }
  21498. this.itemsCount++;
  21499. if (position === 'start') {
  21500. element.insertBefore(referenceElement);
  21501. }
  21502. else {
  21503. element.insertAfter(referenceElement);
  21504. }
  21505. itemWrapper.wrap(item.element);
  21506. itemWrapper.bindSize(this.getDirection() === 'horizontal' ? 'width' : 'height');
  21507. },
  21508. removeItem: function(item) {
  21509. var position = item.getDocked(),
  21510. items = this.items[this.positionMap[position]];
  21511. item.removeCls('x-docked-' + position);
  21512. Ext.Array.remove(items, item);
  21513. item.unlink('$dockItemWrapper');
  21514. item.element.detach();
  21515. delete item.$dockWrapper;
  21516. if (--this.itemsCount === 0) {
  21517. this.destroy();
  21518. }
  21519. },
  21520. getItemsSlice: function(index) {
  21521. var container = this.getContainer(),
  21522. items = this.items,
  21523. slice = [],
  21524. sideItems, i, ln, item;
  21525. for (sideItems = items.start, i = 0, ln = sideItems.length; i < ln; i++) {
  21526. item = sideItems[i];
  21527. if (container.indexOf(item) > index) {
  21528. slice.push(item);
  21529. }
  21530. }
  21531. for (sideItems = items.end, i = 0, ln = sideItems.length; i < ln; i++) {
  21532. item = sideItems[i];
  21533. if (container.indexOf(item) > index) {
  21534. slice.push(item);
  21535. }
  21536. }
  21537. return slice;
  21538. },
  21539. applyElement: function(element) {
  21540. return Ext.Element.create(element);
  21541. },
  21542. updateElement: function(element) {
  21543. element.addCls('x-dock-' + this.getDirection());
  21544. },
  21545. applyBodyElement: function(bodyElement) {
  21546. return Ext.Element.create(bodyElement);
  21547. },
  21548. updateBodyElement: function(bodyElement) {
  21549. this.getElement().append(bodyElement);
  21550. },
  21551. updateInnerWrapper: function(innerWrapper, oldInnerWrapper) {
  21552. var innerElement = this.getBodyElement();
  21553. if (oldInnerWrapper && oldInnerWrapper.$outerWrapper === this) {
  21554. innerElement.remove(oldInnerWrapper.getElement());
  21555. delete oldInnerWrapper.$outerWrapper;
  21556. }
  21557. if (innerWrapper) {
  21558. innerWrapper.setSizeState(this.getSizeState());
  21559. innerWrapper.$outerWrapper = this;
  21560. innerElement.append(innerWrapper.getElement());
  21561. }
  21562. },
  21563. updateSizeState: function(state) {
  21564. var innerWrapper = this.getInnerWrapper();
  21565. this.getElement().setSizeState(state);
  21566. if (innerWrapper) {
  21567. innerWrapper.setSizeState(state);
  21568. }
  21569. },
  21570. destroy: function() {
  21571. var innerWrapper = this.getInnerWrapper(),
  21572. outerWrapper = this.$outerWrapper;
  21573. if (innerWrapper) {
  21574. if (outerWrapper) {
  21575. outerWrapper.setInnerWrapper(innerWrapper);
  21576. }
  21577. else {
  21578. innerWrapper.getElement().replace(this.getElement());
  21579. delete innerWrapper.$outerWrapper;
  21580. }
  21581. }
  21582. delete this.$outerWrapper;
  21583. this.setInnerWrapper(null);
  21584. this.unlink('_bodyElement', '_element');
  21585. this.callSuper();
  21586. }
  21587. });
  21588. /**
  21589. *
  21590. */
  21591. Ext.define('Ext.layout.VBox', {
  21592. extend: 'Ext.layout.FlexBox',
  21593. alias: 'layout.vbox',
  21594. config: {
  21595. orient: 'vertical'
  21596. }
  21597. });
  21598. /**
  21599. * @private
  21600. */
  21601. Ext.define('Ext.fx.layout.card.Abstract', {
  21602. extend: 'Ext.Evented',
  21603. isAnimation: true,
  21604. config: {
  21605. direction: 'left',
  21606. duration: null,
  21607. reverse: null,
  21608. layout: null
  21609. },
  21610. updateLayout: function() {
  21611. this.enable();
  21612. },
  21613. enable: function() {
  21614. var layout = this.getLayout();
  21615. if (layout) {
  21616. layout.onBefore('activeitemchange', 'onActiveItemChange', this);
  21617. }
  21618. },
  21619. disable: function() {
  21620. var layout = this.getLayout();
  21621. if (this.isAnimating) {
  21622. this.stopAnimation();
  21623. }
  21624. if (layout) {
  21625. layout.unBefore('activeitemchange', 'onActiveItemChange', this);
  21626. }
  21627. },
  21628. onActiveItemChange: Ext.emptyFn,
  21629. destroy: function() {
  21630. var layout = this.getLayout();
  21631. if (this.isAnimating) {
  21632. this.stopAnimation();
  21633. }
  21634. if (layout) {
  21635. layout.unBefore('activeitemchange', 'onActiveItemChange', this);
  21636. }
  21637. this.setLayout(null);
  21638. }
  21639. });
  21640. /**
  21641. * @private
  21642. */
  21643. Ext.define('Ext.fx.State', {
  21644. isAnimatable: {
  21645. 'background-color' : true,
  21646. 'background-image' : true,
  21647. 'background-position': true,
  21648. 'border-bottom-color': true,
  21649. 'border-bottom-width': true,
  21650. 'border-color' : true,
  21651. 'border-left-color' : true,
  21652. 'border-left-width' : true,
  21653. 'border-right-color' : true,
  21654. 'border-right-width' : true,
  21655. 'border-spacing' : true,
  21656. 'border-top-color' : true,
  21657. 'border-top-width' : true,
  21658. 'border-width' : true,
  21659. 'bottom' : true,
  21660. 'color' : true,
  21661. 'crop' : true,
  21662. 'font-size' : true,
  21663. 'font-weight' : true,
  21664. 'height' : true,
  21665. 'left' : true,
  21666. 'letter-spacing' : true,
  21667. 'line-height' : true,
  21668. 'margin-bottom' : true,
  21669. 'margin-left' : true,
  21670. 'margin-right' : true,
  21671. 'margin-top' : true,
  21672. 'max-height' : true,
  21673. 'max-width' : true,
  21674. 'min-height' : true,
  21675. 'min-width' : true,
  21676. 'opacity' : true,
  21677. 'outline-color' : true,
  21678. 'outline-offset' : true,
  21679. 'outline-width' : true,
  21680. 'padding-bottom' : true,
  21681. 'padding-left' : true,
  21682. 'padding-right' : true,
  21683. 'padding-top' : true,
  21684. 'right' : true,
  21685. 'text-indent' : true,
  21686. 'text-shadow' : true,
  21687. 'top' : true,
  21688. 'vertical-align' : true,
  21689. 'visibility' : true,
  21690. 'width' : true,
  21691. 'word-spacing' : true,
  21692. 'z-index' : true,
  21693. 'zoom' : true,
  21694. 'transform' : true
  21695. },
  21696. constructor: function(data) {
  21697. this.data = {};
  21698. this.set(data);
  21699. },
  21700. setConfig: function(data) {
  21701. this.set(data);
  21702. return this;
  21703. },
  21704. setRaw: function(data) {
  21705. this.data = data;
  21706. return this;
  21707. },
  21708. clear: function() {
  21709. return this.setRaw({});
  21710. },
  21711. setTransform: function(name, value) {
  21712. var data = this.data,
  21713. isArray = Ext.isArray(value),
  21714. transform = data.transform,
  21715. ln, key;
  21716. if (!transform) {
  21717. transform = data.transform = {
  21718. translateX: 0,
  21719. translateY: 0,
  21720. translateZ: 0,
  21721. scaleX: 1,
  21722. scaleY: 1,
  21723. scaleZ: 1,
  21724. rotate: 0,
  21725. rotateX: 0,
  21726. rotateY: 0,
  21727. rotateZ: 0,
  21728. skewX: 0,
  21729. skewY: 0
  21730. };
  21731. }
  21732. if (typeof name == 'string') {
  21733. switch (name) {
  21734. case 'translate':
  21735. if (isArray) {
  21736. ln = value.length;
  21737. if (ln == 0) { break; }
  21738. transform.translateX = value[0];
  21739. if (ln == 1) { break; }
  21740. transform.translateY = value[1];
  21741. if (ln == 2) { break; }
  21742. transform.translateZ = value[2];
  21743. }
  21744. else {
  21745. transform.translateX = value;
  21746. }
  21747. break;
  21748. case 'rotate':
  21749. if (isArray) {
  21750. ln = value.length;
  21751. if (ln == 0) { break; }
  21752. transform.rotateX = value[0];
  21753. if (ln == 1) { break; }
  21754. transform.rotateY = value[1];
  21755. if (ln == 2) { break; }
  21756. transform.rotateZ = value[2];
  21757. }
  21758. else {
  21759. transform.rotate = value;
  21760. }
  21761. break;
  21762. case 'scale':
  21763. if (isArray) {
  21764. ln = value.length;
  21765. if (ln == 0) { break; }
  21766. transform.scaleX = value[0];
  21767. if (ln == 1) { break; }
  21768. transform.scaleY = value[1];
  21769. if (ln == 2) { break; }
  21770. transform.scaleZ = value[2];
  21771. }
  21772. else {
  21773. transform.scaleX = value;
  21774. transform.scaleY = value;
  21775. }
  21776. break;
  21777. case 'skew':
  21778. if (isArray) {
  21779. ln = value.length;
  21780. if (ln == 0) { break; }
  21781. transform.skewX = value[0];
  21782. if (ln == 1) { break; }
  21783. transform.skewY = value[1];
  21784. }
  21785. else {
  21786. transform.skewX = value;
  21787. }
  21788. break;
  21789. default:
  21790. transform[name] = value;
  21791. }
  21792. }
  21793. else {
  21794. for (key in name) {
  21795. if (name.hasOwnProperty(key)) {
  21796. value = name[key];
  21797. this.setTransform(key, value);
  21798. }
  21799. }
  21800. }
  21801. },
  21802. set: function(name, value) {
  21803. var data = this.data,
  21804. key;
  21805. if (typeof name != 'string') {
  21806. for (key in name) {
  21807. value = name[key];
  21808. if (key === 'transform') {
  21809. this.setTransform(value);
  21810. }
  21811. else {
  21812. data[key] = value;
  21813. }
  21814. }
  21815. }
  21816. else {
  21817. if (name === 'transform') {
  21818. this.setTransform(value);
  21819. }
  21820. else {
  21821. data[name] = value;
  21822. }
  21823. }
  21824. return this;
  21825. },
  21826. unset: function(name) {
  21827. var data = this.data;
  21828. if (data.hasOwnProperty(name)) {
  21829. delete data[name];
  21830. }
  21831. return this;
  21832. },
  21833. getData: function() {
  21834. return this.data;
  21835. }
  21836. });
  21837. /**
  21838. * @private
  21839. */
  21840. Ext.define('Ext.fx.animation.Abstract', {
  21841. extend: 'Ext.Evented',
  21842. isAnimation: true,
  21843. requires: [
  21844. 'Ext.fx.State'
  21845. ],
  21846. config: {
  21847. name: '',
  21848. element: null,
  21849. /**
  21850. * @cfg
  21851. * Before configuration.
  21852. */
  21853. before: null,
  21854. from: {},
  21855. to: {},
  21856. after: null,
  21857. states: {},
  21858. duration: 300,
  21859. /**
  21860. * @cfg
  21861. * Easing type.
  21862. */
  21863. easing: 'linear',
  21864. iteration: 1,
  21865. direction: 'normal',
  21866. delay: 0,
  21867. onBeforeStart: null,
  21868. onEnd: null,
  21869. onBeforeEnd: null,
  21870. scope: null,
  21871. reverse: null,
  21872. preserveEndState: false,
  21873. replacePrevious: true
  21874. },
  21875. STATE_FROM: '0%',
  21876. STATE_TO: '100%',
  21877. DIRECTION_UP: 'up',
  21878. DIRECTION_DOWN: 'down',
  21879. DIRECTION_LEFT: 'left',
  21880. DIRECTION_RIGHT: 'right',
  21881. stateNameRegex: /^(?:[\d\.]+)%$/,
  21882. constructor: function() {
  21883. this.states = {};
  21884. this.callParent(arguments);
  21885. return this;
  21886. },
  21887. applyElement: function(element) {
  21888. return Ext.get(element);
  21889. },
  21890. applyBefore: function(before, current) {
  21891. if (before) {
  21892. return Ext.factory(before, Ext.fx.State, current);
  21893. }
  21894. },
  21895. applyAfter: function(after, current) {
  21896. if (after) {
  21897. return Ext.factory(after, Ext.fx.State, current);
  21898. }
  21899. },
  21900. setFrom: function(from) {
  21901. return this.setState(this.STATE_FROM, from);
  21902. },
  21903. setTo: function(to) {
  21904. return this.setState(this.STATE_TO, to);
  21905. },
  21906. getFrom: function() {
  21907. return this.getState(this.STATE_FROM);
  21908. },
  21909. getTo: function() {
  21910. return this.getState(this.STATE_TO);
  21911. },
  21912. setStates: function(states) {
  21913. var validNameRegex = this.stateNameRegex,
  21914. name;
  21915. for (name in states) {
  21916. if (validNameRegex.test(name)) {
  21917. this.setState(name, states[name]);
  21918. }
  21919. }
  21920. return this;
  21921. },
  21922. getStates: function() {
  21923. return this.states;
  21924. },
  21925. stop: function() {
  21926. this.fireEvent('stop', this);
  21927. },
  21928. destroy: function() {
  21929. this.stop();
  21930. this.callParent();
  21931. },
  21932. setState: function(name, state) {
  21933. var states = this.getStates(),
  21934. stateInstance;
  21935. stateInstance = Ext.factory(state, Ext.fx.State, states[name]);
  21936. if (stateInstance) {
  21937. states[name] = stateInstance;
  21938. }
  21939. //<debug error>
  21940. else if (name === this.STATE_TO) {
  21941. Ext.Logger.error("Setting and invalid '100%' / 'to' state of: " + state);
  21942. }
  21943. //</debug>
  21944. return this;
  21945. },
  21946. getState: function(name) {
  21947. return this.getStates()[name];
  21948. },
  21949. getData: function() {
  21950. var states = this.getStates(),
  21951. statesData = {},
  21952. before = this.getBefore(),
  21953. after = this.getAfter(),
  21954. from = states[this.STATE_FROM],
  21955. to = states[this.STATE_TO],
  21956. fromData = from.getData(),
  21957. toData = to.getData(),
  21958. data, name, state;
  21959. for (name in states) {
  21960. if (states.hasOwnProperty(name)) {
  21961. state = states[name];
  21962. data = state.getData();
  21963. statesData[name] = data;
  21964. }
  21965. }
  21966. if (Ext.os.is.Android2) {
  21967. statesData['0.0001%'] = fromData;
  21968. }
  21969. return {
  21970. before: before ? before.getData() : {},
  21971. after: after ? after.getData() : {},
  21972. states: statesData,
  21973. from: fromData,
  21974. to: toData,
  21975. duration: this.getDuration(),
  21976. iteration: this.getIteration(),
  21977. direction: this.getDirection(),
  21978. easing: this.getEasing(),
  21979. delay: this.getDelay(),
  21980. onEnd: this.getOnEnd(),
  21981. onBeforeEnd: this.getOnBeforeEnd(),
  21982. onBeforeStart: this.getOnBeforeStart(),
  21983. scope: this.getScope(),
  21984. preserveEndState: this.getPreserveEndState(),
  21985. replacePrevious: this.getReplacePrevious()
  21986. };
  21987. }
  21988. });
  21989. /**
  21990. * @private
  21991. */
  21992. Ext.define('Ext.fx.animation.Slide', {
  21993. extend: 'Ext.fx.animation.Abstract',
  21994. alternateClassName: 'Ext.fx.animation.SlideIn',
  21995. alias: ['animation.slide', 'animation.slideIn'],
  21996. config: {
  21997. /**
  21998. * @cfg {String} direction The direction of which the slide animates
  21999. * @accessor
  22000. */
  22001. direction: 'left',
  22002. /**
  22003. * @cfg {Boolean} out True if you want to make this animation slide out, instead of slide in.
  22004. * @accessor
  22005. */
  22006. out: false,
  22007. /**
  22008. * @cfg {Number} offset The offset that the animation should go offscreen before entering (or when exiting)
  22009. * @accessor
  22010. */
  22011. offset: 0,
  22012. /**
  22013. * @cfg
  22014. * @inheritdoc
  22015. */
  22016. easing: 'auto',
  22017. containerBox: 'auto',
  22018. elementBox: 'auto',
  22019. isElementBoxFit: true,
  22020. useCssTransform: true
  22021. },
  22022. reverseDirectionMap: {
  22023. up: 'down',
  22024. down: 'up',
  22025. left: 'right',
  22026. right: 'left'
  22027. },
  22028. applyEasing: function(easing) {
  22029. if (easing === 'auto') {
  22030. return 'ease-' + ((this.getOut()) ? 'in' : 'out');
  22031. }
  22032. return easing;
  22033. },
  22034. getContainerBox: function() {
  22035. var box = this._containerBox;
  22036. if (box === 'auto') {
  22037. box = this.getElement().getParent().getPageBox();
  22038. }
  22039. return box;
  22040. },
  22041. getElementBox: function() {
  22042. var box = this._elementBox;
  22043. if (this.getIsElementBoxFit()) {
  22044. return this.getContainerBox();
  22045. }
  22046. if (box === 'auto') {
  22047. box = this.getElement().getPageBox();
  22048. }
  22049. return box;
  22050. },
  22051. getData: function() {
  22052. var elementBox = this.getElementBox(),
  22053. containerBox = this.getContainerBox(),
  22054. box = elementBox ? elementBox : containerBox,
  22055. from = this.getFrom(),
  22056. to = this.getTo(),
  22057. out = this.getOut(),
  22058. offset = this.getOffset(),
  22059. direction = this.getDirection(),
  22060. useCssTransform = this.getUseCssTransform(),
  22061. reverse = this.getReverse(),
  22062. translateX = 0,
  22063. translateY = 0,
  22064. fromX, fromY, toX, toY;
  22065. if (reverse) {
  22066. direction = this.reverseDirectionMap[direction];
  22067. }
  22068. switch (direction) {
  22069. case this.DIRECTION_UP:
  22070. if (out) {
  22071. translateY = containerBox.top - box.top - box.height - offset;
  22072. }
  22073. else {
  22074. translateY = containerBox.bottom - box.bottom + box.height + offset;
  22075. }
  22076. break;
  22077. case this.DIRECTION_DOWN:
  22078. if (out) {
  22079. translateY = containerBox.bottom - box.bottom + box.height + offset;
  22080. }
  22081. else {
  22082. translateY = containerBox.top - box.height - box.top - offset;
  22083. }
  22084. break;
  22085. case this.DIRECTION_RIGHT:
  22086. if (out) {
  22087. translateX = containerBox.right - box.right + box.width + offset;
  22088. }
  22089. else {
  22090. translateX = containerBox.left - box.left - box.width - offset;
  22091. }
  22092. break;
  22093. case this.DIRECTION_LEFT:
  22094. if (out) {
  22095. translateX = containerBox.left - box.left - box.width - offset;
  22096. }
  22097. else {
  22098. translateX = containerBox.right - box.right + box.width + offset;
  22099. }
  22100. break;
  22101. }
  22102. fromX = (out) ? 0 : translateX;
  22103. fromY = (out) ? 0 : translateY;
  22104. if (useCssTransform) {
  22105. from.setTransform({
  22106. translateX: fromX,
  22107. translateY: fromY
  22108. });
  22109. }
  22110. else {
  22111. from.set('left', fromX);
  22112. from.set('top', fromY);
  22113. }
  22114. toX = (out) ? translateX : 0;
  22115. toY = (out) ? translateY : 0;
  22116. if (useCssTransform) {
  22117. to.setTransform({
  22118. translateX: toX,
  22119. translateY: toY
  22120. });
  22121. }
  22122. else {
  22123. to.set('left', toX);
  22124. to.set('top', toY);
  22125. }
  22126. return this.callParent(arguments);
  22127. }
  22128. });
  22129. /**
  22130. * @private
  22131. */
  22132. Ext.define('Ext.fx.animation.SlideOut', {
  22133. extend: 'Ext.fx.animation.Slide',
  22134. alias: ['animation.slideOut'],
  22135. config: {
  22136. // @hide
  22137. out: true
  22138. }
  22139. });
  22140. /**
  22141. * @private
  22142. */
  22143. Ext.define('Ext.fx.animation.Fade', {
  22144. extend: 'Ext.fx.animation.Abstract',
  22145. alternateClassName: 'Ext.fx.animation.FadeIn',
  22146. alias: ['animation.fade', 'animation.fadeIn'],
  22147. config: {
  22148. /**
  22149. * @cfg {Boolean} out True if you want to make this animation fade out, instead of fade in.
  22150. * @accessor
  22151. */
  22152. out: false,
  22153. before: {
  22154. display: null,
  22155. opacity: 0
  22156. },
  22157. after: {
  22158. opacity: null
  22159. },
  22160. reverse: null
  22161. },
  22162. updateOut: function(newOut) {
  22163. var to = this.getTo(),
  22164. from = this.getFrom();
  22165. if (newOut) {
  22166. from.set('opacity', 1);
  22167. to.set('opacity', 0);
  22168. } else {
  22169. from.set('opacity', 0);
  22170. to.set('opacity', 1);
  22171. }
  22172. }
  22173. });
  22174. /**
  22175. * @private
  22176. */
  22177. Ext.define('Ext.fx.animation.FadeOut', {
  22178. extend: 'Ext.fx.animation.Fade',
  22179. alias: 'animation.fadeOut',
  22180. config: {
  22181. // @hide
  22182. out: true,
  22183. before: {}
  22184. }
  22185. });
  22186. /**
  22187. * @private
  22188. */
  22189. Ext.define('Ext.fx.animation.Flip', {
  22190. extend: 'Ext.fx.animation.Abstract',
  22191. alias: 'animation.flip',
  22192. config: {
  22193. easing: 'ease-in',
  22194. /**
  22195. * @cfg {String} direction The direction of which the slide animates
  22196. * @accessor
  22197. */
  22198. direction: 'right',
  22199. half: false,
  22200. out: null
  22201. },
  22202. getData: function() {
  22203. var from = this.getFrom(),
  22204. to = this.getTo(),
  22205. direction = this.getDirection(),
  22206. out = this.getOut(),
  22207. half = this.getHalf(),
  22208. rotate = (half) ? 90 : 180,
  22209. fromScale = 1,
  22210. toScale = 1,
  22211. fromRotateX = 0,
  22212. fromRotateY = 0,
  22213. toRotateX = 0,
  22214. toRotateY = 0;
  22215. if (out) {
  22216. toScale = 0.8;
  22217. }
  22218. else {
  22219. fromScale = 0.8;
  22220. }
  22221. switch (direction) {
  22222. case this.DIRECTION_UP:
  22223. if (out) {
  22224. toRotateX = rotate;
  22225. }
  22226. else {
  22227. fromRotateX = -rotate;
  22228. }
  22229. break;
  22230. case this.DIRECTION_DOWN:
  22231. if (out) {
  22232. toRotateX = -rotate;
  22233. }
  22234. else {
  22235. fromRotateX = rotate;
  22236. }
  22237. break;
  22238. case this.DIRECTION_RIGHT:
  22239. if (out) {
  22240. toRotateY = -rotate;
  22241. }
  22242. else {
  22243. fromRotateY = rotate;
  22244. }
  22245. break;
  22246. case this.DIRECTION_LEFT:
  22247. if (out) {
  22248. toRotateY = -rotate;
  22249. }
  22250. else {
  22251. fromRotateY = rotate;
  22252. }
  22253. break;
  22254. }
  22255. from.setTransform({
  22256. rotateX: fromRotateX,
  22257. rotateY: fromRotateY,
  22258. scale: fromScale
  22259. });
  22260. to.setTransform({
  22261. rotateX: toRotateX,
  22262. rotateY: toRotateY,
  22263. scale: toScale
  22264. });
  22265. return this.callParent(arguments);
  22266. }
  22267. });
  22268. /**
  22269. * @private
  22270. */
  22271. Ext.define('Ext.fx.animation.Pop', {
  22272. extend: 'Ext.fx.animation.Abstract',
  22273. alias: ['animation.pop', 'animation.popIn'],
  22274. alternateClassName: 'Ext.fx.animation.PopIn',
  22275. config: {
  22276. /**
  22277. * @cfg {Boolean} out True if you want to make this animation pop out, instead of pop in.
  22278. * @accessor
  22279. */
  22280. out: false,
  22281. before: {
  22282. display: null,
  22283. opacity: 0
  22284. },
  22285. after: {
  22286. opacity: null
  22287. }
  22288. },
  22289. getData: function() {
  22290. var to = this.getTo(),
  22291. from = this.getFrom(),
  22292. out = this.getOut();
  22293. if (out) {
  22294. from.set('opacity', 1);
  22295. from.setTransform({
  22296. scale: 1
  22297. });
  22298. to.set('opacity', 0);
  22299. to.setTransform({
  22300. scale: 0
  22301. });
  22302. }
  22303. else {
  22304. from.set('opacity', 0);
  22305. from.setTransform({
  22306. scale: 0
  22307. });
  22308. to.set('opacity', 1);
  22309. to.setTransform({
  22310. scale: 1
  22311. });
  22312. }
  22313. return this.callParent(arguments);
  22314. }
  22315. });
  22316. /**
  22317. * @private
  22318. */
  22319. Ext.define('Ext.fx.animation.PopOut', {
  22320. extend: 'Ext.fx.animation.Pop',
  22321. alias: 'animation.popOut',
  22322. config: {
  22323. // @hide
  22324. out: true,
  22325. before: {}
  22326. }
  22327. });
  22328. /**
  22329. * @private
  22330. * @author Jacky Nguyen <jacky@sencha.com>
  22331. */
  22332. Ext.define('Ext.fx.Animation', {
  22333. requires: [
  22334. 'Ext.fx.animation.Slide',
  22335. 'Ext.fx.animation.SlideOut',
  22336. 'Ext.fx.animation.Fade',
  22337. 'Ext.fx.animation.FadeOut',
  22338. 'Ext.fx.animation.Flip',
  22339. 'Ext.fx.animation.Pop',
  22340. 'Ext.fx.animation.PopOut'
  22341. // 'Ext.fx.animation.Cube'
  22342. ],
  22343. constructor: function(config) {
  22344. var defaultClass = Ext.fx.animation.Abstract,
  22345. type;
  22346. if (typeof config == 'string') {
  22347. type = config;
  22348. config = {};
  22349. }
  22350. else if (config && config.type) {
  22351. type = config.type;
  22352. }
  22353. if (type) {
  22354. if (Ext.os.is.Android2) {
  22355. if (type == 'pop') {
  22356. type = 'fade';
  22357. }
  22358. if (type == 'popIn') {
  22359. type = 'fadeIn';
  22360. }
  22361. if (type == 'popOut') {
  22362. type = 'fadeOut';
  22363. }
  22364. }
  22365. defaultClass = Ext.ClassManager.getByAlias('animation.' + type);
  22366. //<debug error>
  22367. if (!defaultClass) {
  22368. Ext.Logger.error("Invalid animation type of: '" + type + "'");
  22369. }
  22370. //</debug>
  22371. }
  22372. return Ext.factory(config, defaultClass);
  22373. }
  22374. });
  22375. /**
  22376. * @private
  22377. */
  22378. Ext.define('Ext.fx.layout.card.Style', {
  22379. extend: 'Ext.fx.layout.card.Abstract',
  22380. requires: [
  22381. 'Ext.fx.Animation'
  22382. ],
  22383. config: {
  22384. inAnimation: {
  22385. before: {
  22386. visibility: null
  22387. },
  22388. preserveEndState: false,
  22389. replacePrevious: true
  22390. },
  22391. outAnimation: {
  22392. preserveEndState: false,
  22393. replacePrevious: true
  22394. }
  22395. },
  22396. constructor: function(config) {
  22397. var inAnimation, outAnimation;
  22398. this.initConfig(config);
  22399. this.endAnimationCounter = 0;
  22400. inAnimation = this.getInAnimation();
  22401. outAnimation = this.getOutAnimation();
  22402. inAnimation.on('animationend', 'incrementEnd', this);
  22403. outAnimation.on('animationend', 'incrementEnd', this);
  22404. },
  22405. updateDirection: function(direction) {
  22406. this.getInAnimation().setDirection(direction);
  22407. this.getOutAnimation().setDirection(direction);
  22408. },
  22409. updateDuration: function(duration) {
  22410. this.getInAnimation().setDuration(duration);
  22411. this.getOutAnimation().setDuration(duration);
  22412. },
  22413. updateReverse: function(reverse) {
  22414. this.getInAnimation().setReverse(reverse);
  22415. this.getOutAnimation().setReverse(reverse);
  22416. },
  22417. incrementEnd: function() {
  22418. this.endAnimationCounter++;
  22419. if (this.endAnimationCounter > 1) {
  22420. this.endAnimationCounter = 0;
  22421. this.fireEvent('animationend', this);
  22422. }
  22423. },
  22424. applyInAnimation: function(animation, inAnimation) {
  22425. return Ext.factory(animation, Ext.fx.Animation, inAnimation);
  22426. },
  22427. applyOutAnimation: function(animation, outAnimation) {
  22428. return Ext.factory(animation, Ext.fx.Animation, outAnimation);
  22429. },
  22430. updateInAnimation: function(animation) {
  22431. animation.setScope(this);
  22432. },
  22433. updateOutAnimation: function(animation) {
  22434. animation.setScope(this);
  22435. },
  22436. onActiveItemChange: function(cardLayout, newItem, oldItem, options, controller) {
  22437. var inAnimation = this.getInAnimation(),
  22438. outAnimation = this.getOutAnimation(),
  22439. inElement, outElement;
  22440. if (newItem && oldItem && oldItem.isPainted()) {
  22441. inElement = newItem.renderElement;
  22442. outElement = oldItem.renderElement;
  22443. inAnimation.setElement(inElement);
  22444. outAnimation.setElement(outElement);
  22445. outAnimation.setOnBeforeEnd(function(element, interrupted) {
  22446. if (interrupted || Ext.Animator.hasRunningAnimations(element)) {
  22447. controller.firingArguments[1] = null;
  22448. controller.firingArguments[2] = null;
  22449. }
  22450. });
  22451. outAnimation.setOnEnd(function() {
  22452. controller.resume();
  22453. });
  22454. inElement.dom.style.setProperty('visibility', 'hidden', '!important');
  22455. newItem.show();
  22456. Ext.Animator.run([outAnimation, inAnimation]);
  22457. controller.pause();
  22458. }
  22459. },
  22460. destroy: function () {
  22461. Ext.destroy(this.getInAnimation(), this.getOutAnimation());
  22462. this.callParent(arguments);
  22463. }
  22464. });
  22465. /**
  22466. * @private
  22467. */
  22468. Ext.define('Ext.fx.layout.card.Slide', {
  22469. extend: 'Ext.fx.layout.card.Style',
  22470. alias: 'fx.layout.card.slide',
  22471. config: {
  22472. inAnimation: {
  22473. type: 'slide',
  22474. easing: 'ease-out'
  22475. },
  22476. outAnimation: {
  22477. type: 'slide',
  22478. easing: 'ease-out',
  22479. out: true
  22480. }
  22481. },
  22482. updateReverse: function(reverse) {
  22483. this.getInAnimation().setReverse(reverse);
  22484. this.getOutAnimation().setReverse(reverse);
  22485. }
  22486. });
  22487. /**
  22488. * @private
  22489. */
  22490. Ext.define('Ext.fx.layout.card.Cover', {
  22491. extend: 'Ext.fx.layout.card.Style',
  22492. alias: 'fx.layout.card.cover',
  22493. config: {
  22494. reverse: null,
  22495. inAnimation: {
  22496. before: {
  22497. 'z-index': 100
  22498. },
  22499. after: {
  22500. 'z-index': 0
  22501. },
  22502. type: 'slide',
  22503. easing: 'ease-out'
  22504. },
  22505. outAnimation: {
  22506. easing: 'ease-out',
  22507. from: {
  22508. opacity: 0.99
  22509. },
  22510. to: {
  22511. opacity: 1
  22512. },
  22513. out: true
  22514. }
  22515. },
  22516. updateReverse: function(reverse) {
  22517. this.getInAnimation().setReverse(reverse);
  22518. this.getOutAnimation().setReverse(reverse);
  22519. }
  22520. });
  22521. /**
  22522. * @private
  22523. */
  22524. Ext.define('Ext.fx.layout.card.Reveal', {
  22525. extend: 'Ext.fx.layout.card.Style',
  22526. alias: 'fx.layout.card.reveal',
  22527. config: {
  22528. inAnimation: {
  22529. easing: 'ease-out',
  22530. from: {
  22531. opacity: 0.99
  22532. },
  22533. to: {
  22534. opacity: 1
  22535. }
  22536. },
  22537. outAnimation: {
  22538. before: {
  22539. 'z-index': 100
  22540. },
  22541. after: {
  22542. 'z-index': 0
  22543. },
  22544. type: 'slide',
  22545. easing: 'ease-out',
  22546. out: true
  22547. }
  22548. },
  22549. updateReverse: function(reverse) {
  22550. this.getInAnimation().setReverse(reverse);
  22551. this.getOutAnimation().setReverse(reverse);
  22552. }
  22553. });
  22554. /**
  22555. * @private
  22556. */
  22557. Ext.define('Ext.fx.layout.card.Fade', {
  22558. extend: 'Ext.fx.layout.card.Style',
  22559. alias: 'fx.layout.card.fade',
  22560. config: {
  22561. reverse: null,
  22562. inAnimation: {
  22563. type: 'fade',
  22564. easing: 'ease-out'
  22565. },
  22566. outAnimation: {
  22567. type: 'fade',
  22568. easing: 'ease-out',
  22569. out: true
  22570. }
  22571. }
  22572. });
  22573. /**
  22574. * @private
  22575. */
  22576. Ext.define('Ext.fx.layout.card.Flip', {
  22577. extend: 'Ext.fx.layout.card.Style',
  22578. alias: 'fx.layout.card.flip',
  22579. config: {
  22580. duration: 500,
  22581. inAnimation: {
  22582. type: 'flip',
  22583. half: true,
  22584. easing: 'ease-out',
  22585. before: {
  22586. 'backface-visibility': 'hidden'
  22587. },
  22588. after: {
  22589. 'backface-visibility': null
  22590. }
  22591. },
  22592. outAnimation: {
  22593. type: 'flip',
  22594. half: true,
  22595. easing: 'ease-in',
  22596. before: {
  22597. 'backface-visibility': 'hidden'
  22598. },
  22599. after: {
  22600. 'backface-visibility': null
  22601. },
  22602. out: true
  22603. }
  22604. },
  22605. updateDuration: function(duration) {
  22606. var halfDuration = duration / 2,
  22607. inAnimation = this.getInAnimation(),
  22608. outAnimation = this.getOutAnimation();
  22609. inAnimation.setDelay(halfDuration);
  22610. inAnimation.setDuration(halfDuration);
  22611. outAnimation.setDuration(halfDuration);
  22612. }
  22613. });
  22614. /**
  22615. * @private
  22616. */
  22617. Ext.define('Ext.fx.layout.card.Pop', {
  22618. extend: 'Ext.fx.layout.card.Style',
  22619. alias: 'fx.layout.card.pop',
  22620. config: {
  22621. duration: 500,
  22622. inAnimation: {
  22623. type: 'pop',
  22624. easing: 'ease-out'
  22625. },
  22626. outAnimation: {
  22627. type: 'pop',
  22628. easing: 'ease-in',
  22629. out: true
  22630. }
  22631. },
  22632. updateDuration: function(duration) {
  22633. var halfDuration = duration / 2,
  22634. inAnimation = this.getInAnimation(),
  22635. outAnimation = this.getOutAnimation();
  22636. inAnimation.setDelay(halfDuration);
  22637. inAnimation.setDuration(halfDuration);
  22638. outAnimation.setDuration(halfDuration);
  22639. }
  22640. });
  22641. /**
  22642. * @private
  22643. */
  22644. Ext.define('Ext.fx.layout.card.Scroll', {
  22645. extend: 'Ext.fx.layout.card.Abstract',
  22646. requires: [
  22647. 'Ext.fx.easing.Linear'
  22648. ],
  22649. alias: 'fx.layout.card.scroll',
  22650. config: {
  22651. duration: 150
  22652. },
  22653. constructor: function(config) {
  22654. this.initConfig(config);
  22655. this.doAnimationFrame = Ext.Function.bind(this.doAnimationFrame, this);
  22656. },
  22657. getEasing: function() {
  22658. var easing = this.easing;
  22659. if (!easing) {
  22660. this.easing = easing = new Ext.fx.easing.Linear();
  22661. }
  22662. return easing;
  22663. },
  22664. updateDuration: function(duration) {
  22665. this.getEasing().setDuration(duration);
  22666. },
  22667. onActiveItemChange: function(cardLayout, newItem, oldItem, options, controller) {
  22668. var direction = this.getDirection(),
  22669. easing = this.getEasing(),
  22670. containerElement, inElement, outElement, containerWidth, containerHeight, reverse;
  22671. if (newItem && oldItem) {
  22672. if (this.isAnimating) {
  22673. this.stopAnimation();
  22674. }
  22675. newItem.setWidth('100%');
  22676. newItem.setHeight('100%');
  22677. containerElement = this.getLayout().container.innerElement;
  22678. containerWidth = containerElement.getWidth();
  22679. containerHeight = containerElement.getHeight();
  22680. inElement = newItem.renderElement;
  22681. outElement = oldItem.renderElement;
  22682. this.oldItem = oldItem;
  22683. this.newItem = newItem;
  22684. this.currentEventController = controller;
  22685. this.containerElement = containerElement;
  22686. this.isReverse = reverse = this.getReverse();
  22687. newItem.show();
  22688. if (direction == 'right') {
  22689. direction = 'left';
  22690. this.isReverse = reverse = !reverse;
  22691. }
  22692. else if (direction == 'down') {
  22693. direction = 'up';
  22694. this.isReverse = reverse = !reverse;
  22695. }
  22696. if (direction == 'left') {
  22697. if (reverse) {
  22698. easing.setConfig({
  22699. startValue: containerWidth,
  22700. endValue: 0
  22701. });
  22702. containerElement.dom.scrollLeft = containerWidth;
  22703. outElement.setLeft(containerWidth);
  22704. }
  22705. else {
  22706. easing.setConfig({
  22707. startValue: 0,
  22708. endValue: containerWidth
  22709. });
  22710. inElement.setLeft(containerWidth);
  22711. }
  22712. }
  22713. else {
  22714. if (reverse) {
  22715. easing.setConfig({
  22716. startValue: containerHeight,
  22717. endValue: 0
  22718. });
  22719. containerElement.dom.scrollTop = containerHeight;
  22720. outElement.setTop(containerHeight);
  22721. }
  22722. else {
  22723. easing.setConfig({
  22724. startValue: 0,
  22725. endValue: containerHeight
  22726. });
  22727. inElement.setTop(containerHeight);
  22728. }
  22729. }
  22730. this.startAnimation();
  22731. controller.pause();
  22732. }
  22733. },
  22734. startAnimation: function() {
  22735. this.isAnimating = true;
  22736. this.getEasing().setStartTime(Date.now());
  22737. this.timer = setInterval(this.doAnimationFrame, 20);
  22738. this.doAnimationFrame();
  22739. },
  22740. doAnimationFrame: function() {
  22741. var easing = this.getEasing(),
  22742. direction = this.getDirection(),
  22743. scroll = 'scrollTop',
  22744. value;
  22745. if (direction == 'left' || direction == 'right') {
  22746. scroll = 'scrollLeft';
  22747. }
  22748. if (easing.isEnded) {
  22749. this.stopAnimation();
  22750. }
  22751. else {
  22752. value = easing.getValue();
  22753. this.containerElement.dom[scroll] = value;
  22754. }
  22755. },
  22756. stopAnimation: function() {
  22757. var me = this,
  22758. direction = me.getDirection(),
  22759. scroll = 'setTop',
  22760. oldItem = me.oldItem,
  22761. newItem = me.newItem;
  22762. if (direction == 'left' || direction == 'right') {
  22763. scroll = 'setLeft';
  22764. }
  22765. me.currentEventController.resume();
  22766. if (me.isReverse && oldItem && oldItem.renderElement && oldItem.renderElement.dom) {
  22767. oldItem.renderElement[scroll](null);
  22768. }
  22769. else if (newItem && newItem.renderElement && newItem.renderElement.dom) {
  22770. newItem.renderElement[scroll](null);
  22771. }
  22772. clearInterval(me.timer);
  22773. me.isAnimating = false;
  22774. me.fireEvent('animationend', me);
  22775. }
  22776. });
  22777. /**
  22778. * @private
  22779. */
  22780. Ext.define('Ext.fx.layout.Card', {
  22781. requires: [
  22782. 'Ext.fx.layout.card.Slide',
  22783. 'Ext.fx.layout.card.Cover',
  22784. 'Ext.fx.layout.card.Reveal',
  22785. 'Ext.fx.layout.card.Fade',
  22786. 'Ext.fx.layout.card.Flip',
  22787. 'Ext.fx.layout.card.Pop',
  22788. // 'Ext.fx.layout.card.Cube',
  22789. 'Ext.fx.layout.card.Scroll'
  22790. ],
  22791. constructor: function(config) {
  22792. var defaultClass = Ext.fx.layout.card.Abstract,
  22793. type;
  22794. if (!config) {
  22795. return null;
  22796. }
  22797. if (typeof config == 'string') {
  22798. type = config;
  22799. config = {};
  22800. }
  22801. else if (config.type) {
  22802. type = config.type;
  22803. }
  22804. config.elementBox = false;
  22805. if (type) {
  22806. if (Ext.os.is.Android2) {
  22807. // In Android 2 we only support scroll and fade. Otherwise force it to slide.
  22808. if (type != 'fade') {
  22809. type = 'scroll';
  22810. }
  22811. }
  22812. else if (type === 'slide' && Ext.browser.is.ChromeMobile) {
  22813. type = 'scroll';
  22814. }
  22815. defaultClass = Ext.ClassManager.getByAlias('fx.layout.card.' + type);
  22816. //<debug error>
  22817. if (!defaultClass) {
  22818. Ext.Logger.error("Unknown card animation type: '" + type + "'");
  22819. }
  22820. //</debug>
  22821. }
  22822. return Ext.factory(config, defaultClass);
  22823. }
  22824. });
  22825. /**
  22826. * @aside guide layouts
  22827. * @aside video layouts
  22828. *
  22829. * Sometimes you want to show several screens worth of information but you've only got a small screen to work with.
  22830. * TabPanels and Carousels both enable you to see one screen of many at a time, and underneath they both use a Card
  22831. * Layout.
  22832. *
  22833. * Card Layout takes the size of the Container it is applied to and sizes the currently active item to fill the
  22834. * Container completely. It then hides the rest of the items, allowing you to change which one is currently visible but
  22835. * only showing one at once:
  22836. *
  22837. * {@img ../guides/layouts/card.jpg}
  22838. *
  22839. *
  22840. * Here the gray box is our Container, and the blue box inside it is the currently active card. The three other cards
  22841. * are hidden from view, but can be swapped in later. While it's not too common to create Card layouts directly, you
  22842. * can do so like this:
  22843. *
  22844. * var panel = Ext.create('Ext.Panel', {
  22845. * layout: 'card',
  22846. * items: [
  22847. * {
  22848. * html: "First Item"
  22849. * },
  22850. * {
  22851. * html: "Second Item"
  22852. * },
  22853. * {
  22854. * html: "Third Item"
  22855. * },
  22856. * {
  22857. * html: "Fourth Item"
  22858. * }
  22859. * ]
  22860. * });
  22861. *
  22862. * panel.{@link Ext.Container#setActiveItem setActiveItem}(1);
  22863. *
  22864. * Here we create a Panel with a Card Layout and later set the second item active (the active item index is zero-based,
  22865. * so 1 corresponds to the second item). Normally you're better off using a {@link Ext.tab.Panel tab panel} or a
  22866. * {@link Ext.carousel.Carousel carousel}.
  22867. *
  22868. * For a more detailed overview of what layouts are and the types of layouts shipped with Sencha Touch 2, check out the
  22869. * [Layout Guide](#!/guide/layouts).
  22870. */
  22871. Ext.define('Ext.layout.Card', {
  22872. extend: 'Ext.layout.Default',
  22873. alias: 'layout.card',
  22874. isCard: true,
  22875. /**
  22876. * @event activeitemchange
  22877. * @preventable doActiveItemChange
  22878. * Fires when an card is made active
  22879. * @param {Ext.layout.Card} this The layout instance
  22880. * @param {Mixed} newActiveItem The new active item
  22881. * @param {Mixed} oldActiveItem The old active item
  22882. */
  22883. layoutClass: 'x-layout-card',
  22884. itemClass: 'x-layout-card-item',
  22885. requires: [
  22886. 'Ext.fx.layout.Card'
  22887. ],
  22888. /**
  22889. * @private
  22890. */
  22891. applyAnimation: function(animation) {
  22892. return new Ext.fx.layout.Card(animation);
  22893. },
  22894. /**
  22895. * @private
  22896. */
  22897. updateAnimation: function(animation, oldAnimation) {
  22898. if (animation && animation.isAnimation) {
  22899. animation.setLayout(this);
  22900. }
  22901. if (oldAnimation) {
  22902. oldAnimation.destroy();
  22903. }
  22904. },
  22905. setContainer: function(container) {
  22906. this.callSuper(arguments);
  22907. container.innerElement.addCls(this.layoutClass);
  22908. container.onInitialized('onContainerInitialized', this);
  22909. },
  22910. onContainerInitialized: function() {
  22911. var container = this.container,
  22912. activeItem = container.getActiveItem();
  22913. if (activeItem) {
  22914. activeItem.show();
  22915. }
  22916. container.on('activeitemchange', 'onContainerActiveItemChange', this);
  22917. },
  22918. /**
  22919. * @private
  22920. */
  22921. onContainerActiveItemChange: function(container) {
  22922. this.relayEvent(arguments, 'doActiveItemChange');
  22923. },
  22924. onItemInnerStateChange: function(item, isInner, destroying) {
  22925. this.callSuper(arguments);
  22926. var container = this.container,
  22927. activeItem = container.getActiveItem();
  22928. item.toggleCls(this.itemClass, isInner);
  22929. item.setLayoutSizeFlags(isInner ? container.LAYOUT_BOTH : 0);
  22930. if (isInner) {
  22931. if (activeItem !== container.innerIndexOf(item) && activeItem !== item && item !== container.pendingActiveItem) {
  22932. item.hide();
  22933. }
  22934. }
  22935. else {
  22936. if (!destroying && !item.isDestroyed && item.isDestroying !== true) {
  22937. item.show();
  22938. }
  22939. }
  22940. },
  22941. /**
  22942. * @private
  22943. */
  22944. doActiveItemChange: function(me, newActiveItem, oldActiveItem) {
  22945. if (oldActiveItem) {
  22946. oldActiveItem.hide();
  22947. }
  22948. if (newActiveItem) {
  22949. newActiveItem.show();
  22950. }
  22951. },
  22952. destroy: function () {
  22953. this.callParent(arguments);
  22954. Ext.destroy(this.getAnimation());
  22955. }
  22956. });
  22957. /**
  22958. * Represents a filter that can be applied to a {@link Ext.util.MixedCollection MixedCollection}. Can either simply
  22959. * filter on a property/value pair or pass in a filter function with custom logic. Filters are always used in the
  22960. * context of MixedCollections, though {@link Ext.data.Store Store}s frequently create them when filtering and searching
  22961. * on their records. Example usage:
  22962. *
  22963. * // Set up a fictional MixedCollection containing a few people to filter on
  22964. * var allNames = new Ext.util.MixedCollection();
  22965. * allNames.addAll([
  22966. * { id: 1, name: 'Ed', age: 25 },
  22967. * { id: 2, name: 'Jamie', age: 37 },
  22968. * { id: 3, name: 'Abe', age: 32 },
  22969. * { id: 4, name: 'Aaron', age: 26 },
  22970. * { id: 5, name: 'David', age: 32 }
  22971. * ]);
  22972. *
  22973. * var ageFilter = new Ext.util.Filter({
  22974. * property: 'age',
  22975. * value : 32
  22976. * });
  22977. *
  22978. * var longNameFilter = new Ext.util.Filter({
  22979. * filterFn: function(item) {
  22980. * return item.name.length > 4;
  22981. * }
  22982. * });
  22983. *
  22984. * // a new MixedCollection with the 3 names longer than 4 characters
  22985. * var longNames = allNames.filter(longNameFilter);
  22986. *
  22987. * // a new MixedCollection with the 2 people of age 32:
  22988. * var youngFolk = allNames.filter(ageFilter);
  22989. */
  22990. Ext.define('Ext.util.Filter', {
  22991. isFilter: true,
  22992. config: {
  22993. /**
  22994. * @cfg {String} [property=null]
  22995. * The property to filter on. Required unless a `filter` is passed
  22996. */
  22997. property: null,
  22998. /**
  22999. * @cfg {RegExp/Mixed} [value=null]
  23000. * The value you want to match against. Can be a regular expression which will be used as matcher or any other
  23001. * value.
  23002. */
  23003. value: null,
  23004. /**
  23005. * @cfg {Function} filterFn
  23006. * A custom filter function which is passed each item in the {@link Ext.util.MixedCollection} in turn. Should
  23007. * return true to accept each item or false to reject it
  23008. */
  23009. filterFn: Ext.emptyFn,
  23010. /**
  23011. * @cfg {Boolean} [anyMatch=false]
  23012. * True to allow any match - no regex start/end line anchors will be added.
  23013. */
  23014. anyMatch: false,
  23015. /**
  23016. * @cfg {Boolean} [exactMatch=false]
  23017. * True to force exact match (^ and $ characters added to the regex). Ignored if anyMatch is true.
  23018. */
  23019. exactMatch: false,
  23020. /**
  23021. * @cfg {Boolean} [caseSensitive=false]
  23022. * True to make the regex case sensitive (adds 'i' switch to regex).
  23023. */
  23024. caseSensitive: false,
  23025. /**
  23026. * @cfg {String} [root=null]
  23027. * Optional root property. This is mostly useful when filtering a Store, in which case we set the root to 'data'
  23028. * to make the filter pull the {@link #property} out of the data object of each item
  23029. */
  23030. root: null,
  23031. /**
  23032. * @cfg {String} id
  23033. * An optional id this filter can be keyed by in Collections. If no id is specified it will generate an id by
  23034. * first trying a combination of property-value, and if none if these were specified (like when having a
  23035. * filterFn) it will generate a random id.
  23036. */
  23037. id: undefined,
  23038. /**
  23039. * @cfg {Object} [scope=null]
  23040. * The scope in which to run the filterFn
  23041. */
  23042. scope: null
  23043. },
  23044. applyId: function(id) {
  23045. if (!id) {
  23046. if (this.getProperty()) {
  23047. id = this.getProperty() + '-' + String(this.getValue());
  23048. }
  23049. if (!id) {
  23050. id = Ext.id(null, 'ext-filter-');
  23051. }
  23052. }
  23053. return id;
  23054. },
  23055. /**
  23056. * Creates new Filter.
  23057. * @param {Object} config Config object
  23058. */
  23059. constructor: function(config) {
  23060. this.initConfig(config);
  23061. },
  23062. applyFilterFn: function(filterFn) {
  23063. if (filterFn === Ext.emptyFn) {
  23064. filterFn = this.getInitialConfig('filter');
  23065. if (filterFn) {
  23066. return filterFn;
  23067. }
  23068. var value = this.getValue();
  23069. if (!this.getProperty() && !value && value !== 0) {
  23070. // <debug>
  23071. Ext.Logger.error('A Filter requires either a property and value, or a filterFn to be set');
  23072. // </debug>
  23073. return Ext.emptyFn;
  23074. }
  23075. else {
  23076. return this.createFilterFn();
  23077. }
  23078. }
  23079. return filterFn;
  23080. },
  23081. /**
  23082. * @private
  23083. * Creates a filter function for the configured property/value/anyMatch/caseSensitive options for this Filter
  23084. */
  23085. createFilterFn: function() {
  23086. var me = this,
  23087. matcher = me.createValueMatcher();
  23088. return function(item) {
  23089. var root = me.getRoot(),
  23090. property = me.getProperty();
  23091. if (root) {
  23092. item = item[root];
  23093. }
  23094. return matcher.test(item[property]);
  23095. };
  23096. },
  23097. /**
  23098. * @private
  23099. * Returns a regular expression based on the given value and matching options
  23100. */
  23101. createValueMatcher: function() {
  23102. var me = this,
  23103. value = me.getValue(),
  23104. anyMatch = me.getAnyMatch(),
  23105. exactMatch = me.getExactMatch(),
  23106. caseSensitive = me.getCaseSensitive(),
  23107. escapeRe = Ext.String.escapeRegex;
  23108. if (value === null || value === undefined || !value.exec) { // not a regex
  23109. value = String(value);
  23110. if (anyMatch === true) {
  23111. value = escapeRe(value);
  23112. } else {
  23113. value = '^' + escapeRe(value);
  23114. if (exactMatch === true) {
  23115. value += '$';
  23116. }
  23117. }
  23118. value = new RegExp(value, caseSensitive ? '' : 'i');
  23119. }
  23120. return value;
  23121. }
  23122. });
  23123. /**
  23124. * @private
  23125. */
  23126. Ext.define('Ext.util.AbstractMixedCollection', {
  23127. requires: ['Ext.util.Filter'],
  23128. mixins: {
  23129. observable: 'Ext.mixin.Observable'
  23130. },
  23131. /**
  23132. * @event clear
  23133. * Fires when the collection is cleared.
  23134. */
  23135. /**
  23136. * @event add
  23137. * Fires when an item is added to the collection.
  23138. * @param {Number} index The index at which the item was added.
  23139. * @param {Object} o The item added.
  23140. * @param {String} key The key associated with the added item.
  23141. */
  23142. /**
  23143. * @event replace
  23144. * Fires when an item is replaced in the collection.
  23145. * @param {String} key he key associated with the new added.
  23146. * @param {Object} old The item being replaced.
  23147. * @param {Object} new The new item.
  23148. */
  23149. /**
  23150. * @event remove
  23151. * Fires when an item is removed from the collection.
  23152. * @param {Object} o The item being removed.
  23153. * @param {String} key (optional) The key associated with the removed item.
  23154. */
  23155. /**
  23156. * Creates new MixedCollection.
  23157. * @param {Boolean} [allowFunctions=false] Specify `true` if the {@link #addAll}
  23158. * function should add function references to the collection.
  23159. * @param {Function} [keyFn] A function that can accept an item of the type(s) stored in this MixedCollection
  23160. * and return the key value for that item. This is used when available to look up the key on items that
  23161. * were passed without an explicit key parameter to a MixedCollection method. Passing this parameter is
  23162. * equivalent to providing an implementation for the {@link #getKey} method.
  23163. */
  23164. constructor: function(allowFunctions, keyFn) {
  23165. var me = this;
  23166. me.items = [];
  23167. me.map = {};
  23168. me.keys = [];
  23169. me.length = 0;
  23170. me.allowFunctions = allowFunctions === true;
  23171. if (keyFn) {
  23172. me.getKey = keyFn;
  23173. }
  23174. me.mixins.observable.constructor.call(me);
  23175. },
  23176. /**
  23177. * @cfg {Boolean} allowFunctions Specify `true` if the {@link #addAll}
  23178. * function should add function references to the collection.
  23179. */
  23180. allowFunctions : false,
  23181. /**
  23182. * Adds an item to the collection. Fires the {@link #event-add} event when complete.
  23183. * @param {String} key The key to associate with the item, or the new item.
  23184. *
  23185. * If a {@link #getKey} implementation was specified for this MixedCollection,
  23186. * or if the key of the stored items is in a property called `id`,
  23187. * the MixedCollection will be able to _derive_ the key for the new item.
  23188. * In this case just pass the new item in this parameter.
  23189. * @param {Object} o The item to add.
  23190. * @return {Object} The item added.
  23191. */
  23192. add: function(key, obj){
  23193. var me = this,
  23194. myObj = obj,
  23195. myKey = key,
  23196. old;
  23197. if (arguments.length == 1) {
  23198. myObj = myKey;
  23199. myKey = me.getKey(myObj);
  23200. }
  23201. if (typeof myKey != 'undefined' && myKey !== null) {
  23202. old = me.map[myKey];
  23203. if (typeof old != 'undefined') {
  23204. return me.replace(myKey, myObj);
  23205. }
  23206. me.map[myKey] = myObj;
  23207. }
  23208. me.length++;
  23209. me.items.push(myObj);
  23210. me.keys.push(myKey);
  23211. me.fireEvent('add', me.length - 1, myObj, myKey);
  23212. return myObj;
  23213. },
  23214. /**
  23215. * MixedCollection has a generic way to fetch keys if you implement `getKey`. The default implementation
  23216. * simply returns `item.id` but you can provide your own implementation
  23217. * to return a different value as in the following examples:
  23218. *
  23219. * // normal way
  23220. * var mc = new Ext.util.MixedCollection();
  23221. * mc.add(someEl.dom.id, someEl);
  23222. * mc.add(otherEl.dom.id, otherEl);
  23223. * //and so on
  23224. *
  23225. * // using getKey
  23226. * var mc = new Ext.util.MixedCollection();
  23227. * mc.getKey = function(el) {
  23228. * return el.dom.id;
  23229. * };
  23230. * mc.add(someEl);
  23231. * mc.add(otherEl);
  23232. *
  23233. * // or via the constructor
  23234. * var mc = new Ext.util.MixedCollection(false, function(el) {
  23235. * return el.dom.id;
  23236. * });
  23237. * mc.add(someEl);
  23238. * mc.add(otherEl);
  23239. *
  23240. * @param {Object} item The item for which to find the key.
  23241. * @return {Object} The key for the passed item.
  23242. */
  23243. getKey: function(o){
  23244. return o.id;
  23245. },
  23246. /**
  23247. * Replaces an item in the collection. Fires the {@link #event-replace} event when complete.
  23248. * @param {String} key The key associated with the item to replace, or the replacement item.
  23249. *
  23250. * If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
  23251. * of your stored items is in a property called `id`, then the MixedCollection
  23252. * will be able to _derive_ the key of the replacement item. If you want to replace an item
  23253. * with one having the same key value, then just pass the replacement item in this parameter.
  23254. * @param {Object} o (optional) If the first parameter passed was a key, the item to associate
  23255. * with that key.
  23256. * @return {Object} The new item.
  23257. */
  23258. replace: function(key, o){
  23259. var me = this,
  23260. old,
  23261. index;
  23262. if (arguments.length == 1) {
  23263. o = arguments[0];
  23264. key = me.getKey(o);
  23265. }
  23266. old = me.map[key];
  23267. if (typeof key == 'undefined' || key === null || typeof old == 'undefined') {
  23268. return me.add(key, o);
  23269. }
  23270. index = me.indexOfKey(key);
  23271. me.items[index] = o;
  23272. me.map[key] = o;
  23273. me.fireEvent('replace', key, old, o);
  23274. return o;
  23275. },
  23276. /**
  23277. * Adds all elements of an Array or an Object to the collection.
  23278. * @param {Object/Array} objs An Object containing properties which will be added
  23279. * to the collection, or an Array of values, each of which are added to the collection.
  23280. * Functions references will be added to the collection if `{@link #allowFunctions}`
  23281. * has been set to `true`.
  23282. */
  23283. addAll: function(objs){
  23284. var me = this,
  23285. i = 0,
  23286. args,
  23287. len,
  23288. key;
  23289. if (arguments.length > 1 || Ext.isArray(objs)) {
  23290. args = arguments.length > 1 ? arguments : objs;
  23291. for (len = args.length; i < len; i++) {
  23292. me.add(args[i]);
  23293. }
  23294. } else {
  23295. for (key in objs) {
  23296. if (objs.hasOwnProperty(key)) {
  23297. if (me.allowFunctions || typeof objs[key] != 'function') {
  23298. me.add(key, objs[key]);
  23299. }
  23300. }
  23301. }
  23302. }
  23303. },
  23304. /**
  23305. * Executes the specified function once for every item in the collection.
  23306. *
  23307. * @param {Function} fn The function to execute for each item.
  23308. * @param {Mixed} fn.item The collection item.
  23309. * @param {Number} fn.index The item's index.
  23310. * @param {Number} fn.length The total number of items in the collection.
  23311. * @param {Boolean} fn.return Returning `false` will stop the iteration.
  23312. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
  23313. * Defaults to the current item in the iteration.
  23314. */
  23315. each: function(fn, scope){
  23316. var items = [].concat(this.items), // each safe for removal
  23317. i = 0,
  23318. len = items.length,
  23319. item;
  23320. for (; i < len; i++) {
  23321. item = items[i];
  23322. if (fn.call(scope || item, item, i, len) === false) {
  23323. break;
  23324. }
  23325. }
  23326. },
  23327. /**
  23328. * Executes the specified function once for every key in the collection, passing each
  23329. * key, and its associated item as the first two parameters.
  23330. * @param {Function} fn The function to execute for each item.
  23331. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to the browser window.
  23332. */
  23333. eachKey: function(fn, scope){
  23334. var keys = this.keys,
  23335. items = this.items,
  23336. i = 0,
  23337. len = keys.length;
  23338. for (; i < len; i++) {
  23339. fn.call(scope || window, keys[i], items[i], i, len);
  23340. }
  23341. },
  23342. /**
  23343. * Returns the first item in the collection which elicits a `true` return value from the
  23344. * passed selection function.
  23345. * @param {Function} fn The selection function to execute for each item.
  23346. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to the browser window.
  23347. * @return {Object} The first item in the collection which returned `true` from the selection function.
  23348. */
  23349. findBy: function(fn, scope) {
  23350. var keys = this.keys,
  23351. items = this.items,
  23352. i = 0,
  23353. len = items.length;
  23354. for (; i < len; i++) {
  23355. if (fn.call(scope || window, items[i], keys[i])) {
  23356. return items[i];
  23357. }
  23358. }
  23359. return null;
  23360. },
  23361. /**
  23362. * Inserts an item at the specified index in the collection. Fires the `{@link #event-add}` event when complete.
  23363. * @param {Number} index The index to insert the item at.
  23364. * @param {String} key The key to associate with the new item, or the item itself.
  23365. * @param {Object} o (optional) If the second parameter was a key, the new item.
  23366. * @return {Object} The item inserted.
  23367. */
  23368. insert: function(index, key, obj){
  23369. var me = this,
  23370. myKey = key,
  23371. myObj = obj;
  23372. if (arguments.length == 2) {
  23373. myObj = myKey;
  23374. myKey = me.getKey(myObj);
  23375. }
  23376. if (me.containsKey(myKey)) {
  23377. me.suspendEvents();
  23378. me.removeAtKey(myKey);
  23379. me.resumeEvents();
  23380. }
  23381. if (index >= me.length) {
  23382. return me.add(myKey, myObj);
  23383. }
  23384. me.length++;
  23385. Ext.Array.splice(me.items, index, 0, myObj);
  23386. if (typeof myKey != 'undefined' && myKey !== null) {
  23387. me.map[myKey] = myObj;
  23388. }
  23389. Ext.Array.splice(me.keys, index, 0, myKey);
  23390. me.fireEvent('add', index, myObj, myKey);
  23391. return myObj;
  23392. },
  23393. /**
  23394. * Remove an item from the collection.
  23395. * @param {Object} o The item to remove.
  23396. * @return {Object} The item removed or `false` if no item was removed.
  23397. */
  23398. remove: function(o){
  23399. return this.removeAt(this.indexOf(o));
  23400. },
  23401. /**
  23402. * Remove all items in the passed array from the collection.
  23403. * @param {Array} items An array of items to be removed.
  23404. * @return {Ext.util.MixedCollection} this object
  23405. */
  23406. removeAll: function(items){
  23407. Ext.each(items || [], function(item) {
  23408. this.remove(item);
  23409. }, this);
  23410. return this;
  23411. },
  23412. /**
  23413. * Remove an item from a specified index in the collection. Fires the `{@link #event-remove}` event when complete.
  23414. * @param {Number} index The index within the collection of the item to remove.
  23415. * @return {Object/Boolean} The item removed or `false` if no item was removed.
  23416. */
  23417. removeAt: function(index){
  23418. var me = this,
  23419. o,
  23420. key;
  23421. if (index < me.length && index >= 0) {
  23422. me.length--;
  23423. o = me.items[index];
  23424. Ext.Array.erase(me.items, index, 1);
  23425. key = me.keys[index];
  23426. if (typeof key != 'undefined') {
  23427. delete me.map[key];
  23428. }
  23429. Ext.Array.erase(me.keys, index, 1);
  23430. me.fireEvent('remove', o, key);
  23431. return o;
  23432. }
  23433. return false;
  23434. },
  23435. /**
  23436. * Removed an item associated with the passed key from the collection.
  23437. * @param {String} key The key of the item to remove.
  23438. * @return {Object/Boolean} The item removed or `false` if no item was removed.
  23439. */
  23440. removeAtKey: function(key){
  23441. return this.removeAt(this.indexOfKey(key));
  23442. },
  23443. /**
  23444. * Returns the number of items in the collection.
  23445. * @return {Number} the number of items in the collection.
  23446. */
  23447. getCount: function(){
  23448. return this.length;
  23449. },
  23450. /**
  23451. * Returns index within the collection of the passed Object.
  23452. * @param {Object} o The item to find the index of.
  23453. * @return {Number} index of the item. Returns -1 if not found.
  23454. */
  23455. indexOf: function(o){
  23456. return Ext.Array.indexOf(this.items, o);
  23457. },
  23458. /**
  23459. * Returns index within the collection of the passed key.
  23460. * @param {String} key The key to find the index of.
  23461. * @return {Number} The index of the key.
  23462. */
  23463. indexOfKey: function(key){
  23464. return Ext.Array.indexOf(this.keys, key);
  23465. },
  23466. /**
  23467. * Returns the item associated with the passed key OR index.
  23468. * Key has priority over index. This is the equivalent
  23469. * of calling {@link #getByKey} first, then if nothing matched calling {@link #getAt}.
  23470. * @param {String/Number} key The key or index of the item.
  23471. * @return {Object} If the item is found, returns the item. If the item was not found, returns `undefined`.
  23472. * If an item was found, but is a Class, returns `null`.
  23473. */
  23474. get: function(key) {
  23475. var me = this,
  23476. mk = me.map[key],
  23477. item = mk !== undefined ? mk : (typeof key == 'number') ? me.items[key] : undefined;
  23478. return typeof item != 'function' || me.allowFunctions ? item : null; // for prototype!
  23479. },
  23480. /**
  23481. * Returns the item at the specified index.
  23482. * @param {Number} index The index of the item.
  23483. * @return {Object} The item at the specified index.
  23484. */
  23485. getAt: function(index) {
  23486. return this.items[index];
  23487. },
  23488. /**
  23489. * Returns the item associated with the passed key.
  23490. * @param {String/Number} key The key of the item.
  23491. * @return {Object} The item associated with the passed key.
  23492. */
  23493. getByKey: function(key) {
  23494. return this.map[key];
  23495. },
  23496. /**
  23497. * Returns `true` if the collection contains the passed Object as an item.
  23498. * @param {Object} o The Object to look for in the collection.
  23499. * @return {Boolean} `true` if the collection contains the Object as an item.
  23500. */
  23501. contains: function(o){
  23502. return Ext.Array.contains(this.items, o);
  23503. },
  23504. /**
  23505. * Returns `true` if the collection contains the passed Object as a key.
  23506. * @param {String} key The key to look for in the collection.
  23507. * @return {Boolean} `true` if the collection contains the Object as a key.
  23508. */
  23509. containsKey: function(key){
  23510. return typeof this.map[key] != 'undefined';
  23511. },
  23512. /**
  23513. * Removes all items from the collection. Fires the `{@link #event-clear}` event when complete.
  23514. */
  23515. clear: function(){
  23516. var me = this;
  23517. me.length = 0;
  23518. me.items = [];
  23519. me.keys = [];
  23520. me.map = {};
  23521. me.fireEvent('clear');
  23522. },
  23523. /**
  23524. * Returns the first item in the collection.
  23525. * @return {Object} the first item in the collection..
  23526. */
  23527. first: function() {
  23528. return this.items[0];
  23529. },
  23530. /**
  23531. * Returns the last item in the collection.
  23532. * @return {Object} the last item in the collection..
  23533. */
  23534. last: function() {
  23535. return this.items[this.length - 1];
  23536. },
  23537. /**
  23538. * Collects all of the values of the given property and returns their sum.
  23539. * @param {String} property The property to sum by.
  23540. * @param {String} [root] Optional 'root' property to extract the first argument from. This is used mainly when
  23541. * summing fields in records, where the fields are all stored inside the `data` object
  23542. * @param {Number} [start=0] (optional) The record index to start at.
  23543. * @param {Number} [end=-1] (optional) The record index to end at.
  23544. * @return {Number} The total
  23545. */
  23546. sum: function(property, root, start, end) {
  23547. var values = this.extractValues(property, root),
  23548. length = values.length,
  23549. sum = 0,
  23550. i;
  23551. start = start || 0;
  23552. end = (end || end === 0) ? end : length - 1;
  23553. for (i = start; i <= end; i++) {
  23554. sum += values[i];
  23555. }
  23556. return sum;
  23557. },
  23558. /**
  23559. * Collects unique values of a particular property in this MixedCollection.
  23560. * @param {String} property The property to collect on.
  23561. * @param {String} [root] Optional 'root' property to extract the first argument from. This is used mainly when
  23562. * summing fields in records, where the fields are all stored inside the `data` object.
  23563. * @param {Boolean} allowBlank (optional) Pass `true` to allow `null`, `undefined`, or empty string values.
  23564. * @return {Array} The unique values.
  23565. */
  23566. collect: function(property, root, allowNull) {
  23567. var values = this.extractValues(property, root),
  23568. length = values.length,
  23569. hits = {},
  23570. unique = [],
  23571. value, strValue, i;
  23572. for (i = 0; i < length; i++) {
  23573. value = values[i];
  23574. strValue = String(value);
  23575. if ((allowNull || !Ext.isEmpty(value)) && !hits[strValue]) {
  23576. hits[strValue] = true;
  23577. unique.push(value);
  23578. }
  23579. }
  23580. return unique;
  23581. },
  23582. /**
  23583. * @private
  23584. * Extracts all of the given property values from the items in the MixedCollection. Mainly used as a supporting method for
  23585. * functions like `sum()` and `collect()`.
  23586. * @param {String} property The property to extract.
  23587. * @param {String} [root] Optional 'root' property to extract the first argument from. This is used mainly when
  23588. * extracting field data from Model instances, where the fields are stored inside the `data` object.
  23589. * @return {Array} The extracted values.
  23590. */
  23591. extractValues: function(property, root) {
  23592. var values = this.items;
  23593. if (root) {
  23594. values = Ext.Array.pluck(values, root);
  23595. }
  23596. return Ext.Array.pluck(values, property);
  23597. },
  23598. /**
  23599. * Returns a range of items in this collection.
  23600. * @param {Number} [startIndex=0] (optional) The starting index.
  23601. * @param {Number} [endIndex=-1] (optional) The ending index.
  23602. * @return {Array} An array of items
  23603. */
  23604. getRange: function(start, end){
  23605. var me = this,
  23606. items = me.items,
  23607. range = [],
  23608. i;
  23609. if (items.length < 1) {
  23610. return range;
  23611. }
  23612. start = start || 0;
  23613. end = Math.min(typeof end == 'undefined' ? me.length - 1 : end, me.length - 1);
  23614. if (start <= end) {
  23615. for (i = start; i <= end; i++) {
  23616. range[range.length] = items[i];
  23617. }
  23618. } else {
  23619. for (i = start; i >= end; i--) {
  23620. range[range.length] = items[i];
  23621. }
  23622. }
  23623. return range;
  23624. },
  23625. /**
  23626. * Filters the objects in this collection by a set of {@link Ext.util.Filter Filter}s, or by a single
  23627. * property/value pair with optional parameters for substring matching and case sensitivity. See
  23628. * {@link Ext.util.Filter Filter} for an example of using Filter objects (preferred). Alternatively,
  23629. * MixedCollection can be easily filtered by property like this:
  23630. *
  23631. * // create a simple store with a few people defined
  23632. * var people = new Ext.util.MixedCollection();
  23633. * people.addAll([
  23634. * {id: 1, age: 25, name: 'Ed'},
  23635. * {id: 2, age: 24, name: 'Tommy'},
  23636. * {id: 3, age: 24, name: 'Arne'},
  23637. * {id: 4, age: 26, name: 'Aaron'}
  23638. * ]);
  23639. *
  23640. * // a new MixedCollection containing only the items where age == 24
  23641. * var middleAged = people.filter('age', 24);
  23642. *
  23643. * @param {Ext.util.Filter[]/String} property A property on your objects, or an array of {@link Ext.util.Filter Filter} objects
  23644. * @param {String/RegExp} value Either string that the property values
  23645. * should start with or a RegExp to test against the property.
  23646. * @param {Boolean} anyMatch (optional) `true` to match any part of the string, not just the beginning
  23647. * @param {Boolean} [caseSensitive=false] (optional) `true` for case sensitive comparison.
  23648. * @return {Ext.util.MixedCollection} The new filtered collection
  23649. */
  23650. filter: function(property, value, anyMatch, caseSensitive) {
  23651. var filters = [],
  23652. filterFn;
  23653. //support for the simple case of filtering by property/value
  23654. if (Ext.isString(property)) {
  23655. filters.push(Ext.create('Ext.util.Filter', {
  23656. property : property,
  23657. value : value,
  23658. anyMatch : anyMatch,
  23659. caseSensitive: caseSensitive
  23660. }));
  23661. } else if (Ext.isArray(property) || property instanceof Ext.util.Filter) {
  23662. filters = filters.concat(property);
  23663. }
  23664. //at this point we have an array of zero or more Ext.util.Filter objects to filter with,
  23665. //so here we construct a function that combines these filters by ANDing them together
  23666. filterFn = function(record) {
  23667. var isMatch = true,
  23668. length = filters.length,
  23669. i;
  23670. for (i = 0; i < length; i++) {
  23671. var filter = filters[i],
  23672. fn = filter.getFilterFn(),
  23673. scope = filter.getScope();
  23674. isMatch = isMatch && fn.call(scope, record);
  23675. }
  23676. return isMatch;
  23677. };
  23678. return this.filterBy(filterFn);
  23679. },
  23680. /**
  23681. * Filter by a function. Returns a _new_ collection that has been filtered.
  23682. * The passed function will be called with each object in the collection.
  23683. * If the function returns `true`, the value is included otherwise it is filtered.
  23684. * @param {Function} fn The function to be called, it will receive the args `o` (the object), `k` (the key)
  23685. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to this MixedCollection.
  23686. * @return {Ext.util.MixedCollection} The new filtered collection.
  23687. */
  23688. filterBy: function(fn, scope) {
  23689. var me = this,
  23690. newMC = new this.self(),
  23691. keys = me.keys,
  23692. items = me.items,
  23693. length = items.length,
  23694. i;
  23695. newMC.getKey = me.getKey;
  23696. for (i = 0; i < length; i++) {
  23697. if (fn.call(scope || me, items[i], keys[i])) {
  23698. newMC.add(keys[i], items[i]);
  23699. }
  23700. }
  23701. return newMC;
  23702. },
  23703. /**
  23704. * Finds the index of the first matching object in this collection by a specific property/value.
  23705. * @param {String} property The name of a property on your objects.
  23706. * @param {String/RegExp} value A string that the property values.
  23707. * should start with or a RegExp to test against the property.
  23708. * @param {Number} [start=0] (optional) The index to start searching at.
  23709. * @param {Boolean} anyMatch (optional) `true` to match any part of the string, not just the beginning.
  23710. * @param {Boolean} caseSensitive (optional) `true` for case sensitive comparison.
  23711. * @return {Number} The matched index or -1.
  23712. */
  23713. findIndex: function(property, value, start, anyMatch, caseSensitive){
  23714. if(Ext.isEmpty(value, false)){
  23715. return -1;
  23716. }
  23717. value = this.createValueMatcher(value, anyMatch, caseSensitive);
  23718. return this.findIndexBy(function(o){
  23719. return o && value.test(o[property]);
  23720. }, null, start);
  23721. },
  23722. /**
  23723. * Find the index of the first matching object in this collection by a function.
  23724. * If the function returns `true` it is considered a match.
  23725. * @param {Function} fn The function to be called, it will receive the args `o` (the object), `k` (the key).
  23726. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to this MixedCollection.
  23727. * @param {Number} [start=0] (optional) The index to start searching at.
  23728. * @return {Number} The matched index or -1.
  23729. */
  23730. findIndexBy: function(fn, scope, start){
  23731. var me = this,
  23732. keys = me.keys,
  23733. items = me.items,
  23734. i = start || 0,
  23735. len = items.length;
  23736. for (; i < len; i++) {
  23737. if (fn.call(scope || me, items[i], keys[i])) {
  23738. return i;
  23739. }
  23740. }
  23741. return -1;
  23742. },
  23743. /**
  23744. * Returns a regular expression based on the given value and matching options. This is used internally for finding and filtering,
  23745. * and by Ext.data.Store#filter
  23746. * @private
  23747. * @param {String} value The value to create the regex for. This is escaped using Ext.escapeRe
  23748. * @param {Boolean} [anyMatch=false] `true` to allow any match - no regex start/end line anchors will be added.
  23749. * @param {Boolean} [caseSensitive=false] `true` to make the regex case sensitive (adds 'i' switch to regex).
  23750. * @param {Boolean} [exactMatch=false] `true` to force exact match (^ and $ characters added to the regex). Ignored if `anyMatch` is `true`.
  23751. */
  23752. createValueMatcher: function(value, anyMatch, caseSensitive, exactMatch) {
  23753. if (!value.exec) { // not a regex
  23754. var er = Ext.String.escapeRegex;
  23755. value = String(value);
  23756. if (anyMatch === true) {
  23757. value = er(value);
  23758. } else {
  23759. value = '^' + er(value);
  23760. if (exactMatch === true) {
  23761. value += '$';
  23762. }
  23763. }
  23764. value = new RegExp(value, caseSensitive ? '' : 'i');
  23765. }
  23766. return value;
  23767. },
  23768. /**
  23769. * Creates a shallow copy of this collection.
  23770. * @return {Ext.util.MixedCollection}
  23771. */
  23772. clone: function() {
  23773. var me = this,
  23774. copy = new this.self(),
  23775. keys = me.keys,
  23776. items = me.items,
  23777. i = 0,
  23778. len = items.length;
  23779. for(; i < len; i++){
  23780. copy.add(keys[i], items[i]);
  23781. }
  23782. copy.getKey = me.getKey;
  23783. return copy;
  23784. }
  23785. });
  23786. /**
  23787. * Represents a single sorter that can be used as part of the sorters configuration in Ext.mixin.Sortable.
  23788. *
  23789. * A common place for Sorters to be used are {@link Ext.data.Store Stores}. For example:
  23790. *
  23791. * @example miniphone
  23792. * var store = Ext.create('Ext.data.Store', {
  23793. * fields: ['firstName', 'lastName'],
  23794. * sorters: 'lastName',
  23795. *
  23796. * data: [
  23797. * { firstName: 'Tommy', lastName: 'Maintz' },
  23798. * { firstName: 'Rob', lastName: 'Dougan' },
  23799. * { firstName: 'Ed', lastName: 'Spencer'},
  23800. * { firstName: 'Jamie', lastName: 'Avins' },
  23801. * { firstName: 'Nick', lastName: 'Poulden'}
  23802. * ]
  23803. * });
  23804. *
  23805. * Ext.create('Ext.List', {
  23806. * fullscreen: true,
  23807. * itemTpl: '<div class="contact">{firstName} <strong>{lastName}</strong></div>',
  23808. * store: store
  23809. * });
  23810. *
  23811. * In the next example, we specify a custom sorter function:
  23812. *
  23813. * @example miniphone
  23814. * var store = Ext.create('Ext.data.Store', {
  23815. * fields: ['person'],
  23816. * sorters: [
  23817. * {
  23818. * // Sort by first letter of last name, in descending order
  23819. * sorterFn: function(record1, record2) {
  23820. * var name1 = record1.data.person.name.split('-')[1].substr(0, 1),
  23821. * name2 = record2.data.person.name.split('-')[1].substr(0, 1);
  23822. *
  23823. * return name1 > name2 ? 1 : (name1 === name2 ? 0 : -1);
  23824. * },
  23825. * direction: 'DESC'
  23826. * }
  23827. * ],
  23828. * data: [
  23829. * { person: { name: 'Tommy-Maintz' } },
  23830. * { person: { name: 'Rob-Dougan' } },
  23831. * { person: { name: 'Ed-Spencer' } },
  23832. * { person: { name: 'Nick-Poulden' } },
  23833. * { person: { name: 'Jamie-Avins' } }
  23834. * ]
  23835. * });
  23836. *
  23837. * Ext.create('Ext.List', {
  23838. * fullscreen: true,
  23839. * itemTpl: '{person.name}',
  23840. * store: store
  23841. * });
  23842. */
  23843. Ext.define('Ext.util.Sorter', {
  23844. isSorter: true,
  23845. config: {
  23846. /**
  23847. * @cfg {String} property The property to sort by. Required unless `sorterFn` is provided
  23848. */
  23849. property: null,
  23850. /**
  23851. * @cfg {Function} sorterFn A specific sorter function to execute. Can be passed instead of {@link #property}.
  23852. * This function should compare the two passed arguments, returning -1, 0 or 1 depending on if item 1 should be
  23853. * sorted before, at the same level, or after item 2.
  23854. *
  23855. * sorterFn: function(person1, person2) {
  23856. * return (person1.age > person2.age) ? 1 : (person1.age === person2.age ? 0 : -1);
  23857. * }
  23858. */
  23859. sorterFn: null,
  23860. /**
  23861. * @cfg {String} root Optional root property. This is mostly useful when sorting a Store, in which case we set the
  23862. * root to 'data' to make the filter pull the {@link #property} out of the data object of each item
  23863. */
  23864. root: null,
  23865. /**
  23866. * @cfg {Function} transform A function that will be run on each value before
  23867. * it is compared in the sorter. The function will receive a single argument,
  23868. * the value.
  23869. */
  23870. transform: null,
  23871. /**
  23872. * @cfg {String} direction The direction to sort by. Valid values are "ASC", and "DESC".
  23873. */
  23874. direction: "ASC",
  23875. /**
  23876. * @cfg {Mixed} id An optional id this sorter can be keyed by in Collections. If
  23877. * no id is specified it will use the property name used in this Sorter. If no
  23878. * property is specified, e.g. when adding a custom sorter function we will generate
  23879. * a random id.
  23880. */
  23881. id: undefined
  23882. },
  23883. constructor: function(config) {
  23884. this.initConfig(config);
  23885. },
  23886. // <debug>
  23887. applySorterFn: function(sorterFn) {
  23888. if (!sorterFn && !this.getProperty()) {
  23889. Ext.Logger.error("A Sorter requires either a property or a sorterFn.");
  23890. }
  23891. return sorterFn;
  23892. },
  23893. applyProperty: function(property) {
  23894. if (!property && !this.getSorterFn()) {
  23895. Ext.Logger.error("A Sorter requires either a property or a sorterFn.");
  23896. }
  23897. return property;
  23898. },
  23899. // </debug>
  23900. applyId: function(id) {
  23901. if (!id) {
  23902. id = this.getProperty();
  23903. if (!id) {
  23904. id = Ext.id(null, 'ext-sorter-');
  23905. }
  23906. }
  23907. return id;
  23908. },
  23909. /**
  23910. * @private
  23911. * Creates and returns a function which sorts an array by the given property and direction
  23912. * @return {Function} A function which sorts by the property/direction combination provided
  23913. */
  23914. createSortFunction: function(sorterFn) {
  23915. var me = this,
  23916. modifier = me.getDirection().toUpperCase() == "DESC" ? -1 : 1;
  23917. //create a comparison function. Takes 2 objects, returns 1 if object 1 is greater,
  23918. //-1 if object 2 is greater or 0 if they are equal
  23919. return function(o1, o2) {
  23920. return modifier * sorterFn.call(me, o1, o2);
  23921. };
  23922. },
  23923. /**
  23924. * @private
  23925. * Basic default sorter function that just compares the defined property of each object
  23926. */
  23927. defaultSortFn: function(item1, item2) {
  23928. var me = this,
  23929. transform = me._transform,
  23930. root = me._root,
  23931. value1, value2,
  23932. property = me._property;
  23933. if (root !== null) {
  23934. item1 = item1[root];
  23935. item2 = item2[root];
  23936. }
  23937. value1 = item1[property];
  23938. value2 = item2[property];
  23939. if (transform) {
  23940. value1 = transform(value1);
  23941. value2 = transform(value2);
  23942. }
  23943. return value1 > value2 ? 1 : (value1 < value2 ? -1 : 0);
  23944. },
  23945. updateDirection: function() {
  23946. this.updateSortFn();
  23947. },
  23948. updateSortFn: function() {
  23949. this.sort = this.createSortFunction(this.getSorterFn() || this.defaultSortFn);
  23950. },
  23951. /**
  23952. * Toggles the direction of this Sorter. Note that when you call this function,
  23953. * the Collection this Sorter is part of does not get refreshed automatically.
  23954. */
  23955. toggle: function() {
  23956. this.setDirection(Ext.String.toggle(this.getDirection(), "ASC", "DESC"));
  23957. }
  23958. });
  23959. /**
  23960. * @docauthor Tommy Maintz <tommy@sencha.com>
  23961. *
  23962. * A mixin which allows a data component to be sorted. This is used by e.g. {@link Ext.data.Store} and {@link Ext.data.TreeStore}.
  23963. *
  23964. * __Note:__ This mixin is mainly for internal library use and most users should not need to use it directly. It
  23965. * is more likely you will want to use one of the component classes that import this mixin, such as
  23966. * {@link Ext.data.Store} or {@link Ext.data.TreeStore}.
  23967. */
  23968. Ext.define("Ext.util.Sortable", {
  23969. extend: 'Ext.mixin.Mixin',
  23970. /**
  23971. * @property {Boolean} isSortable
  23972. * Flag denoting that this object is sortable. Always `true`.
  23973. * @readonly
  23974. */
  23975. isSortable: true,
  23976. mixinConfig: {
  23977. hooks: {
  23978. destroy: 'destroy'
  23979. }
  23980. },
  23981. /**
  23982. * @property {String} defaultSortDirection
  23983. * The default sort direction to use if one is not specified.
  23984. */
  23985. defaultSortDirection: "ASC",
  23986. requires: [
  23987. 'Ext.util.Sorter'
  23988. ],
  23989. /**
  23990. * @property {String} sortRoot
  23991. * The property in each item that contains the data to sort.
  23992. */
  23993. /**
  23994. * Performs initialization of this mixin. Component classes using this mixin should call this method during their
  23995. * own initialization.
  23996. */
  23997. initSortable: function() {
  23998. var me = this,
  23999. sorters = me.sorters;
  24000. /**
  24001. * @property {Ext.util.MixedCollection} sorters
  24002. * The collection of {@link Ext.util.Sorter Sorters} currently applied to this Store
  24003. */
  24004. me.sorters = Ext.create('Ext.util.AbstractMixedCollection', false, function(item) {
  24005. return item.id || item.property;
  24006. });
  24007. if (sorters) {
  24008. me.sorters.addAll(me.decodeSorters(sorters));
  24009. }
  24010. },
  24011. /**
  24012. * Sorts the data in the Store by one or more of its properties. Example usage:
  24013. *
  24014. * //sort by a single field
  24015. * myStore.sort('myField', 'DESC');
  24016. *
  24017. * //sorting by multiple fields
  24018. * myStore.sort([
  24019. * {
  24020. * property : 'age',
  24021. * direction: 'ASC'
  24022. * },
  24023. * {
  24024. * property : 'name',
  24025. * direction: 'DESC'
  24026. * }
  24027. * ]);
  24028. *
  24029. * Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates
  24030. * the actual sorting to its internal {@link Ext.util.MixedCollection}.
  24031. *
  24032. * When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:
  24033. *
  24034. * store.sort('myField');
  24035. * store.sort('myField');
  24036. *
  24037. * Is equivalent to this code, because Store handles the toggling automatically:
  24038. *
  24039. * store.sort('myField', 'ASC');
  24040. * store.sort('myField', 'DESC');
  24041. *
  24042. * @param {String/Ext.util.Sorter[]} sorters Either a string name of one of the fields in this Store's configured
  24043. * {@link Ext.data.Model Model}, or an array of sorter configurations.
  24044. * @param {String} [direction="ASC"] The overall direction to sort the data by.
  24045. * @param {String} [where]
  24046. * @param {Boolean} [doSort]
  24047. * @return {Ext.util.Sorter[]}
  24048. */
  24049. sort: function(sorters, direction, where, doSort) {
  24050. var me = this,
  24051. sorter, sorterFn,
  24052. newSorters;
  24053. if (Ext.isArray(sorters)) {
  24054. doSort = where;
  24055. where = direction;
  24056. newSorters = sorters;
  24057. }
  24058. else if (Ext.isObject(sorters)) {
  24059. doSort = where;
  24060. where = direction;
  24061. newSorters = [sorters];
  24062. }
  24063. else if (Ext.isString(sorters)) {
  24064. sorter = me.sorters.get(sorters);
  24065. if (!sorter) {
  24066. sorter = {
  24067. property : sorters,
  24068. direction: direction
  24069. };
  24070. newSorters = [sorter];
  24071. }
  24072. else if (direction === undefined) {
  24073. sorter.toggle();
  24074. }
  24075. else {
  24076. sorter.setDirection(direction);
  24077. }
  24078. }
  24079. if (newSorters && newSorters.length) {
  24080. newSorters = me.decodeSorters(newSorters);
  24081. if (Ext.isString(where)) {
  24082. if (where === 'prepend') {
  24083. sorters = me.sorters.clone().items;
  24084. me.sorters.clear();
  24085. me.sorters.addAll(newSorters);
  24086. me.sorters.addAll(sorters);
  24087. }
  24088. else {
  24089. me.sorters.addAll(newSorters);
  24090. }
  24091. }
  24092. else {
  24093. me.sorters.clear();
  24094. me.sorters.addAll(newSorters);
  24095. }
  24096. if (doSort !== false) {
  24097. me.onBeforeSort(newSorters);
  24098. }
  24099. }
  24100. if (doSort !== false) {
  24101. sorters = me.sorters.items;
  24102. if (sorters.length) {
  24103. //construct an amalgamated sorter function which combines all of the Sorters passed
  24104. sorterFn = function(r1, r2) {
  24105. var result = sorters[0].sort(r1, r2),
  24106. length = sorters.length,
  24107. i;
  24108. //if we have more than one sorter, OR any additional sorter functions together
  24109. for (i = 1; i < length; i++) {
  24110. result = result || sorters[i].sort.call(this, r1, r2);
  24111. }
  24112. return result;
  24113. };
  24114. me.doSort(sorterFn);
  24115. }
  24116. }
  24117. return sorters;
  24118. },
  24119. onBeforeSort: Ext.emptyFn,
  24120. /**
  24121. * @private
  24122. * Normalizes an array of sorter objects, ensuring that they are all {@link Ext.util.Sorter} instances.
  24123. * @param {Array} sorters The sorters array.
  24124. * @return {Array} Array of {@link Ext.util.Sorter} objects.
  24125. */
  24126. decodeSorters: function(sorters) {
  24127. if (!Ext.isArray(sorters)) {
  24128. if (sorters === undefined) {
  24129. sorters = [];
  24130. } else {
  24131. sorters = [sorters];
  24132. }
  24133. }
  24134. var length = sorters.length,
  24135. Sorter = Ext.util.Sorter,
  24136. fields = this.model ? this.model.prototype.fields : null,
  24137. field,
  24138. config, i;
  24139. for (i = 0; i < length; i++) {
  24140. config = sorters[i];
  24141. if (!(config instanceof Sorter)) {
  24142. if (Ext.isString(config)) {
  24143. config = {
  24144. property: config
  24145. };
  24146. }
  24147. Ext.applyIf(config, {
  24148. root : this.sortRoot,
  24149. direction: "ASC"
  24150. });
  24151. if (config.fn) {
  24152. config.sorterFn = config.fn;
  24153. }
  24154. //support a function to be passed as a sorter definition
  24155. if (typeof config == 'function') {
  24156. config = {
  24157. sorterFn: config
  24158. };
  24159. }
  24160. // ensure sortType gets pushed on if necessary
  24161. if (fields && !config.transform) {
  24162. field = fields.get(config.property);
  24163. config.transform = field ? field.sortType : undefined;
  24164. }
  24165. sorters[i] = Ext.create('Ext.util.Sorter', config);
  24166. }
  24167. }
  24168. return sorters;
  24169. },
  24170. getSorters: function() {
  24171. return this.sorters.items;
  24172. },
  24173. destroy: function () {
  24174. this.callSuper();
  24175. Ext.destroy(this.sorters);
  24176. }
  24177. });
  24178. /**
  24179. * Represents a collection of a set of key and value pairs. Each key in the MixedCollection must be unique, the same key
  24180. * cannot exist twice. This collection is ordered, items in the collection can be accessed by index or via the key.
  24181. * Newly added items are added to the end of the collection. This class is similar to {@link Ext.util.HashMap} however
  24182. * it is heavier and provides more functionality. Sample usage:
  24183. *
  24184. * @example
  24185. * var coll = new Ext.util.MixedCollection();
  24186. * coll.add('key1', 'val1');
  24187. * coll.add('key2', 'val2');
  24188. * coll.add('key3', 'val3');
  24189. *
  24190. * alert(coll.get('key1')); // 'val1'
  24191. * alert(coll.indexOfKey('key3')); // 2
  24192. *
  24193. * The MixedCollection also has support for sorting and filtering of the values in the collection.
  24194. *
  24195. * @example
  24196. * var coll = new Ext.util.MixedCollection();
  24197. * coll.add('key1', 100);
  24198. * coll.add('key2', -100);
  24199. * coll.add('key3', 17);
  24200. * coll.add('key4', 0);
  24201. * var biggerThanZero = coll.filterBy(function(value){
  24202. * return value > 0;
  24203. * });
  24204. * alert(biggerThanZero.getCount()); // 2
  24205. */
  24206. Ext.define('Ext.util.MixedCollection', {
  24207. extend: 'Ext.util.AbstractMixedCollection',
  24208. mixins: {
  24209. sortable: 'Ext.util.Sortable'
  24210. },
  24211. /**
  24212. * @event sort
  24213. * Fires whenever MixedCollection is sorted.
  24214. * @param {Ext.util.MixedCollection} this
  24215. */
  24216. constructor: function() {
  24217. var me = this;
  24218. me.callParent(arguments);
  24219. me.mixins.sortable.initSortable.call(me);
  24220. },
  24221. doSort: function(sorterFn) {
  24222. this.sortBy(sorterFn);
  24223. },
  24224. /**
  24225. * @private
  24226. * Performs the actual sorting based on a direction and a sorting function. Internally,
  24227. * this creates a temporary array of all items in the MixedCollection, sorts it and then writes
  24228. * the sorted array data back into `this.items` and `this.keys`.
  24229. * @param {String} property Property to sort by ('key', 'value', or 'index')
  24230. * @param {String} [dir=ASC] (optional) Direction to sort 'ASC' or 'DESC'.
  24231. * @param {Function} fn (optional) Comparison function that defines the sort order.
  24232. * Defaults to sorting by numeric value.
  24233. */
  24234. _sort: function(property, dir, fn){
  24235. var me = this,
  24236. i, len,
  24237. dsc = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
  24238. //this is a temporary array used to apply the sorting function
  24239. c = [],
  24240. keys = me.keys,
  24241. items = me.items;
  24242. //default to a simple sorter function if one is not provided
  24243. fn = fn || function(a, b) {
  24244. return a - b;
  24245. };
  24246. //copy all the items into a temporary array, which we will sort
  24247. for(i = 0, len = items.length; i < len; i++){
  24248. c[c.length] = {
  24249. key : keys[i],
  24250. value: items[i],
  24251. index: i
  24252. };
  24253. }
  24254. //sort the temporary array
  24255. Ext.Array.sort(c, function(a, b){
  24256. var v = fn(a[property], b[property]) * dsc;
  24257. if(v === 0){
  24258. v = (a.index < b.index ? -1 : 1);
  24259. }
  24260. return v;
  24261. });
  24262. //copy the temporary array back into the main this.items and this.keys objects
  24263. for(i = 0, len = c.length; i < len; i++){
  24264. items[i] = c[i].value;
  24265. keys[i] = c[i].key;
  24266. }
  24267. me.fireEvent('sort', me);
  24268. },
  24269. /**
  24270. * Sorts the collection by a single sorter function.
  24271. * @param {Function} sorterFn The function to sort by.
  24272. */
  24273. sortBy: function(sorterFn) {
  24274. var me = this,
  24275. items = me.items,
  24276. keys = me.keys,
  24277. length = items.length,
  24278. temp = [],
  24279. i;
  24280. //first we create a copy of the items array so that we can sort it
  24281. for (i = 0; i < length; i++) {
  24282. temp[i] = {
  24283. key : keys[i],
  24284. value: items[i],
  24285. index: i
  24286. };
  24287. }
  24288. Ext.Array.sort(temp, function(a, b) {
  24289. var v = sorterFn(a.value, b.value);
  24290. if (v === 0) {
  24291. v = (a.index < b.index ? -1 : 1);
  24292. }
  24293. return v;
  24294. });
  24295. //copy the temporary array back into the main this.items and this.keys objects
  24296. for (i = 0; i < length; i++) {
  24297. items[i] = temp[i].value;
  24298. keys[i] = temp[i].key;
  24299. }
  24300. me.fireEvent('sort', me, items, keys);
  24301. },
  24302. /**
  24303. * Reorders each of the items based on a mapping from old index to new index. Internally this just translates into a
  24304. * sort. The `sort` event is fired whenever reordering has occured.
  24305. * @param {Object} mapping Mapping from old item index to new item index.
  24306. */
  24307. reorder: function(mapping) {
  24308. var me = this,
  24309. items = me.items,
  24310. index = 0,
  24311. length = items.length,
  24312. order = [],
  24313. remaining = [],
  24314. oldIndex;
  24315. me.suspendEvents();
  24316. //object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
  24317. for (oldIndex in mapping) {
  24318. order[mapping[oldIndex]] = items[oldIndex];
  24319. }
  24320. for (index = 0; index < length; index++) {
  24321. if (mapping[index] == undefined) {
  24322. remaining.push(items[index]);
  24323. }
  24324. }
  24325. for (index = 0; index < length; index++) {
  24326. if (order[index] == undefined) {
  24327. order[index] = remaining.shift();
  24328. }
  24329. }
  24330. me.clear();
  24331. me.addAll(order);
  24332. me.resumeEvents();
  24333. me.fireEvent('sort', me);
  24334. },
  24335. /**
  24336. * Sorts this collection by **key**s.
  24337. * @param {String} [direction=ASC] 'ASC' or 'DESC'.
  24338. * @param {Function} [fn] Comparison function that defines the sort order. Defaults to sorting by case insensitive
  24339. * string.
  24340. */
  24341. sortByKey: function(dir, fn){
  24342. this._sort('key', dir, fn || function(a, b){
  24343. var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
  24344. return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
  24345. });
  24346. }
  24347. });
  24348. /**
  24349. * @private
  24350. */
  24351. Ext.define('Ext.ItemCollection', {
  24352. extend: 'Ext.util.MixedCollection',
  24353. getKey: function(item) {
  24354. return item.getItemId();
  24355. },
  24356. has: function(item) {
  24357. return this.map.hasOwnProperty(item.getId());
  24358. }
  24359. });
  24360. /**
  24361. * @private
  24362. */
  24363. Ext.define('Ext.fx.easing.Momentum', {
  24364. extend: 'Ext.fx.easing.Abstract',
  24365. config: {
  24366. acceleration: 30,
  24367. friction: 0,
  24368. startVelocity: 0
  24369. },
  24370. alpha: 0,
  24371. updateFriction: function(friction) {
  24372. var theta = Math.log(1 - (friction / 10));
  24373. this.theta = theta;
  24374. this.alpha = theta / this.getAcceleration();
  24375. },
  24376. updateStartVelocity: function(velocity) {
  24377. this.velocity = velocity * this.getAcceleration();
  24378. },
  24379. updateAcceleration: function(acceleration) {
  24380. this.velocity = this.getStartVelocity() * acceleration;
  24381. this.alpha = this.theta / acceleration;
  24382. },
  24383. getValue: function() {
  24384. return this.getStartValue() - this.velocity * (1 - this.getFrictionFactor()) / this.theta;
  24385. },
  24386. getFrictionFactor: function() {
  24387. var deltaTime = Ext.Date.now() - this.getStartTime();
  24388. return Math.exp(deltaTime * this.alpha);
  24389. },
  24390. getVelocity: function() {
  24391. return this.getFrictionFactor() * this.velocity;
  24392. }
  24393. });
  24394. /**
  24395. * @private
  24396. */
  24397. Ext.define('Ext.fx.easing.Bounce', {
  24398. extend: 'Ext.fx.easing.Abstract',
  24399. config: {
  24400. springTension: 0.3,
  24401. acceleration: 30,
  24402. startVelocity: 0
  24403. },
  24404. getValue: function() {
  24405. var deltaTime = Ext.Date.now() - this.getStartTime(),
  24406. theta = (deltaTime / this.getAcceleration()),
  24407. powTime = theta * Math.pow(Math.E, -this.getSpringTension() * theta);
  24408. return this.getStartValue() + (this.getStartVelocity() * powTime);
  24409. }
  24410. });
  24411. /**
  24412. * @private
  24413. *
  24414. * This easing is typically used for {@link Ext.scroll.Scroller}. It's a combination of
  24415. * {@link Ext.fx.easing.Momentum} and {@link Ext.fx.easing.Bounce}, which emulates deceleration when the animated element
  24416. * is still within its boundary, then bouncing back (snapping) when it's out-of-bound.
  24417. */
  24418. Ext.define('Ext.fx.easing.BoundMomentum', {
  24419. extend: 'Ext.fx.easing.Abstract',
  24420. requires: [
  24421. 'Ext.fx.easing.Momentum',
  24422. 'Ext.fx.easing.Bounce'
  24423. ],
  24424. config: {
  24425. /**
  24426. * @cfg {Object} momentum
  24427. * A valid config object for {@link Ext.fx.easing.Momentum}
  24428. * @accessor
  24429. */
  24430. momentum: null,
  24431. /**
  24432. * @cfg {Object} bounce
  24433. * A valid config object for {@link Ext.fx.easing.Bounce}
  24434. * @accessor
  24435. */
  24436. bounce: null,
  24437. minMomentumValue: 0,
  24438. maxMomentumValue: 0,
  24439. /**
  24440. * @cfg {Number} minVelocity
  24441. * The minimum velocity to end this easing
  24442. * @accessor
  24443. */
  24444. minVelocity: 0.01,
  24445. /**
  24446. * @cfg {Number} startVelocity
  24447. * The start velocity
  24448. * @accessor
  24449. */
  24450. startVelocity: 0
  24451. },
  24452. applyMomentum: function(config, currentEasing) {
  24453. return Ext.factory(config, Ext.fx.easing.Momentum, currentEasing);
  24454. },
  24455. applyBounce: function(config, currentEasing) {
  24456. return Ext.factory(config, Ext.fx.easing.Bounce, currentEasing);
  24457. },
  24458. updateStartTime: function(startTime) {
  24459. this.getMomentum().setStartTime(startTime);
  24460. this.callParent(arguments);
  24461. },
  24462. updateStartVelocity: function(startVelocity) {
  24463. this.getMomentum().setStartVelocity(startVelocity);
  24464. },
  24465. updateStartValue: function(startValue) {
  24466. this.getMomentum().setStartValue(startValue);
  24467. },
  24468. reset: function() {
  24469. this.lastValue = null;
  24470. this.isBouncingBack = false;
  24471. this.isOutOfBound = false;
  24472. return this.callParent(arguments);
  24473. },
  24474. getValue: function() {
  24475. var momentum = this.getMomentum(),
  24476. bounce = this.getBounce(),
  24477. startVelocity = momentum.getStartVelocity(),
  24478. direction = startVelocity > 0 ? 1 : -1,
  24479. minValue = this.getMinMomentumValue(),
  24480. maxValue = this.getMaxMomentumValue(),
  24481. boundedValue = (direction == 1) ? maxValue : minValue,
  24482. lastValue = this.lastValue,
  24483. value, velocity;
  24484. if (startVelocity === 0) {
  24485. return this.getStartValue();
  24486. }
  24487. if (!this.isOutOfBound) {
  24488. value = momentum.getValue();
  24489. velocity = momentum.getVelocity();
  24490. if (Math.abs(velocity) < this.getMinVelocity()) {
  24491. this.isEnded = true;
  24492. }
  24493. if (value >= minValue && value <= maxValue) {
  24494. return value;
  24495. }
  24496. this.isOutOfBound = true;
  24497. bounce.setStartTime(Ext.Date.now())
  24498. .setStartVelocity(velocity)
  24499. .setStartValue(boundedValue);
  24500. }
  24501. value = bounce.getValue();
  24502. if (!this.isEnded) {
  24503. if (!this.isBouncingBack) {
  24504. if (lastValue !== null) {
  24505. if ((direction == 1 && value < lastValue) || (direction == -1 && value > lastValue)) {
  24506. this.isBouncingBack = true;
  24507. }
  24508. }
  24509. }
  24510. else {
  24511. if (Math.round(value) == boundedValue) {
  24512. this.isEnded = true;
  24513. }
  24514. }
  24515. }
  24516. this.lastValue = value;
  24517. return value;
  24518. }
  24519. });
  24520. /**
  24521. * @private
  24522. */
  24523. Ext.define('Ext.fx.easing.EaseOut', {
  24524. extend: 'Ext.fx.easing.Linear',
  24525. alias: 'easing.ease-out',
  24526. config: {
  24527. exponent: 4,
  24528. duration: 1500
  24529. },
  24530. getValue: function() {
  24531. var deltaTime = Ext.Date.now() - this.getStartTime(),
  24532. duration = this.getDuration(),
  24533. startValue = this.getStartValue(),
  24534. endValue = this.getEndValue(),
  24535. distance = this.distance,
  24536. theta = deltaTime / duration,
  24537. thetaC = 1 - theta,
  24538. thetaEnd = 1 - Math.pow(thetaC, this.getExponent()),
  24539. currentValue = startValue + (thetaEnd * distance);
  24540. if (deltaTime >= duration) {
  24541. this.isEnded = true;
  24542. return endValue;
  24543. }
  24544. return currentValue;
  24545. }
  24546. });
  24547. /**
  24548. * @class Ext.scroll.Scroller
  24549. * @author Jacky Nguyen <jacky@sencha.com>
  24550. *
  24551. * Momentum scrolling is one of the most important part of the framework's UI layer. In Sencha Touch there are
  24552. * several scroller implementations so we can have the best performance on all mobile devices and browsers.
  24553. *
  24554. * Scroller settings can be changed using the {@link Ext.Container#scrollable scrollable} configuration in
  24555. * {@link Ext.Container}. Anything you pass to that method will be passed to the scroller when it is
  24556. * instantiated in your container.
  24557. *
  24558. * Please note that the {@link Ext.Container#getScrollable} method returns an instance of {@link Ext.scroll.View}.
  24559. * So if you need to get access to the scroller after your container has been instantiated, you must used the
  24560. * {@link Ext.scroll.View#getScroller} method.
  24561. *
  24562. * // lets assume container is a container you have
  24563. * // created which is scrollable
  24564. * container.getScrollable().getScroller().setFps(10);
  24565. *
  24566. * ## Example
  24567. *
  24568. * Here is a simple example of how to adjust the scroller settings when using a {@link Ext.Container} (or anything
  24569. * that extends it).
  24570. *
  24571. * @example
  24572. * var container = Ext.create('Ext.Container', {
  24573. * fullscreen: true,
  24574. * html: 'This container is scrollable!',
  24575. * scrollable: {
  24576. * direction: 'vertical'
  24577. * }
  24578. * });
  24579. *
  24580. * As you can see, we are passing the {@link #direction} configuration into the scroller instance in our container.
  24581. *
  24582. * You can pass any of the configs below in that {@link Ext.Container#scrollable scrollable} configuration and it will
  24583. * just work.
  24584. *
  24585. * Go ahead and try it in the live code editor above!
  24586. */
  24587. Ext.define('Ext.scroll.Scroller', {
  24588. extend: 'Ext.Evented',
  24589. requires: [
  24590. 'Ext.fx.easing.BoundMomentum',
  24591. 'Ext.fx.easing.EaseOut',
  24592. 'Ext.util.Translatable'
  24593. ],
  24594. /**
  24595. * @event maxpositionchange
  24596. * Fires whenever the maximum position has changed.
  24597. * @param {Ext.scroll.Scroller} this
  24598. * @param {Number} maxPosition The new maximum position.
  24599. */
  24600. /**
  24601. * @event refresh
  24602. * Fires whenever the Scroller is refreshed.
  24603. * @param {Ext.scroll.Scroller} this
  24604. */
  24605. /**
  24606. * @event scrollstart
  24607. * Fires whenever the scrolling is started.
  24608. * @param {Ext.scroll.Scroller} this
  24609. * @param {Number} x The current x position.
  24610. * @param {Number} y The current y position.
  24611. */
  24612. /**
  24613. * @event scrollend
  24614. * Fires whenever the scrolling is ended.
  24615. * @param {Ext.scroll.Scroller} this
  24616. * @param {Number} x The current x position.
  24617. * @param {Number} y The current y position.
  24618. */
  24619. /**
  24620. * @event scroll
  24621. * Fires whenever the Scroller is scrolled.
  24622. * @param {Ext.scroll.Scroller} this
  24623. * @param {Number} x The new x position.
  24624. * @param {Number} y The new y position.
  24625. */
  24626. config: {
  24627. /**
  24628. * @cfg element
  24629. * @private
  24630. */
  24631. element: null,
  24632. /**
  24633. * @cfg {String} direction
  24634. * Possible values: 'auto', 'vertical', 'horizontal', or 'both'.
  24635. * @accessor
  24636. */
  24637. direction: 'auto',
  24638. /**
  24639. * @cfg fps
  24640. * @private
  24641. */
  24642. fps: 'auto',
  24643. /**
  24644. * @cfg {Boolean} disabled
  24645. * Whether or not this component is disabled.
  24646. * @accessor
  24647. */
  24648. disabled: null,
  24649. /**
  24650. * @cfg {Boolean} directionLock
  24651. * `true` to lock the direction of the scroller when the user starts scrolling.
  24652. * This is useful when putting a scroller inside a scroller or a {@link Ext.Carousel}.
  24653. * @accessor
  24654. */
  24655. directionLock: false,
  24656. /**
  24657. * @cfg {Object} momentumEasing
  24658. * A valid config for {@link Ext.fx.easing.BoundMomentum}. The default value is:
  24659. *
  24660. * {
  24661. * momentum: {
  24662. * acceleration: 30,
  24663. * friction: 0.5
  24664. * },
  24665. * bounce: {
  24666. * acceleration: 30,
  24667. * springTension: 0.3
  24668. * }
  24669. * }
  24670. *
  24671. * Note that supplied object will be recursively merged with the default object. For example, you can simply
  24672. * pass this to change the momentum acceleration only:
  24673. *
  24674. * {
  24675. * momentum: {
  24676. * acceleration: 10
  24677. * }
  24678. * }
  24679. *
  24680. * @accessor
  24681. */
  24682. momentumEasing: {
  24683. momentum: {
  24684. acceleration: 30,
  24685. friction: 0.5
  24686. },
  24687. bounce: {
  24688. acceleration: 30,
  24689. springTension: 0.3
  24690. },
  24691. minVelocity: 1
  24692. },
  24693. /**
  24694. * @cfg bounceEasing
  24695. * @private
  24696. */
  24697. bounceEasing: {
  24698. duration: 400
  24699. },
  24700. /**
  24701. * @cfg outOfBoundRestrictFactor
  24702. * @private
  24703. */
  24704. outOfBoundRestrictFactor: 0.5,
  24705. /**
  24706. * @cfg startMomentumResetTime
  24707. * @private
  24708. */
  24709. startMomentumResetTime: 300,
  24710. /**
  24711. * @cfg maxAbsoluteVelocity
  24712. * @private
  24713. */
  24714. maxAbsoluteVelocity: 6,
  24715. /**
  24716. * @cfg containerSize
  24717. * @private
  24718. */
  24719. containerSize: 'auto',
  24720. /**
  24721. * @cfg size
  24722. * @private
  24723. */
  24724. size: 'auto',
  24725. /**
  24726. * @cfg autoRefresh
  24727. * @private
  24728. */
  24729. autoRefresh: true,
  24730. /**
  24731. * @cfg {Object/Number} initialOffset
  24732. * The initial scroller position. When specified as Number,
  24733. * both x and y will be set to that value.
  24734. */
  24735. initialOffset: {
  24736. x: 0,
  24737. y: 0
  24738. },
  24739. /**
  24740. * @cfg {Number/Object} slotSnapSize
  24741. * The size of each slot to snap to in 'px', can be either an object with `x` and `y` values, i.e:
  24742. *
  24743. * {
  24744. * x: 50,
  24745. * y: 100
  24746. * }
  24747. *
  24748. * or a number value to be used for both directions. For example, a value of `50` will be treated as:
  24749. *
  24750. * {
  24751. * x: 50,
  24752. * y: 50
  24753. * }
  24754. *
  24755. * @accessor
  24756. */
  24757. slotSnapSize: {
  24758. x: 0,
  24759. y: 0
  24760. },
  24761. /**
  24762. * @cfg slotSnapOffset
  24763. * @private
  24764. */
  24765. slotSnapOffset: {
  24766. x: 0,
  24767. y: 0
  24768. },
  24769. slotSnapEasing: {
  24770. duration: 150
  24771. },
  24772. translatable: {
  24773. translationMethod: 'auto',
  24774. useWrapper: false
  24775. }
  24776. },
  24777. cls: Ext.baseCSSPrefix + 'scroll-scroller',
  24778. containerCls: Ext.baseCSSPrefix + 'scroll-container',
  24779. dragStartTime: 0,
  24780. dragEndTime: 0,
  24781. isDragging: false,
  24782. isAnimating: false,
  24783. /**
  24784. * @private
  24785. * @constructor
  24786. * @chainable
  24787. */
  24788. constructor: function(config) {
  24789. var element = config && config.element;
  24790. if (Ext.os.is.Android4 && !Ext.browser.is.Chrome) {
  24791. this.onDrag = Ext.Function.createThrottled(this.onDrag, 20, this);
  24792. }
  24793. this.listeners = {
  24794. scope: this,
  24795. touchstart: 'onTouchStart',
  24796. touchend: 'onTouchEnd',
  24797. dragstart: 'onDragStart',
  24798. drag: 'onDrag',
  24799. dragend: 'onDragEnd'
  24800. };
  24801. this.minPosition = { x: 0, y: 0 };
  24802. this.startPosition = { x: 0, y: 0 };
  24803. this.position = { x: 0, y: 0 };
  24804. this.velocity = { x: 0, y: 0 };
  24805. this.isAxisEnabledFlags = { x: false, y: false };
  24806. this.flickStartPosition = { x: 0, y: 0 };
  24807. this.flickStartTime = { x: 0, y: 0 };
  24808. this.lastDragPosition = { x: 0, y: 0 };
  24809. this.dragDirection = { x: 0, y: 0};
  24810. this.initialConfig = config;
  24811. if (element) {
  24812. this.setElement(element);
  24813. }
  24814. return this;
  24815. },
  24816. /**
  24817. * @private
  24818. */
  24819. applyElement: function(element) {
  24820. if (!element) {
  24821. return;
  24822. }
  24823. return Ext.get(element);
  24824. },
  24825. /**
  24826. * @private
  24827. * @chainable
  24828. */
  24829. updateElement: function(element) {
  24830. this.initialize();
  24831. element.addCls(this.cls);
  24832. if (!this.getDisabled()) {
  24833. this.attachListeneners();
  24834. }
  24835. this.onConfigUpdate(['containerSize', 'size'], 'refreshMaxPosition');
  24836. this.on('maxpositionchange', 'snapToBoundary');
  24837. this.on('minpositionchange', 'snapToBoundary');
  24838. return this;
  24839. },
  24840. applyTranslatable: function(config, translatable) {
  24841. return Ext.factory(config, Ext.util.Translatable, translatable);
  24842. },
  24843. updateTranslatable: function(translatable) {
  24844. translatable.setConfig({
  24845. element: this.getElement(),
  24846. listeners: {
  24847. animationframe: 'onAnimationFrame',
  24848. animationend: 'onAnimationEnd',
  24849. scope: this
  24850. }
  24851. });
  24852. },
  24853. updateFps: function(fps) {
  24854. if (fps !== 'auto') {
  24855. this.getTranslatable().setFps(fps);
  24856. }
  24857. },
  24858. /**
  24859. * @private
  24860. */
  24861. attachListeneners: function() {
  24862. this.getContainer().on(this.listeners);
  24863. },
  24864. /**
  24865. * @private
  24866. */
  24867. detachListeners: function() {
  24868. this.getContainer().un(this.listeners);
  24869. },
  24870. /**
  24871. * @private
  24872. */
  24873. updateDisabled: function(disabled) {
  24874. if (disabled) {
  24875. this.detachListeners();
  24876. }
  24877. else {
  24878. this.attachListeneners();
  24879. }
  24880. },
  24881. updateInitialOffset: function(initialOffset) {
  24882. if (typeof initialOffset == 'number') {
  24883. initialOffset = {
  24884. x: initialOffset,
  24885. y: initialOffset
  24886. };
  24887. }
  24888. var position = this.position,
  24889. x, y;
  24890. position.x = x = initialOffset.x;
  24891. position.y = y = initialOffset.y;
  24892. this.getTranslatable().translate(-x, -y);
  24893. },
  24894. /**
  24895. * @private
  24896. * @return {String}
  24897. */
  24898. applyDirection: function(direction) {
  24899. var minPosition = this.getMinPosition(),
  24900. maxPosition = this.getMaxPosition(),
  24901. isHorizontal, isVertical;
  24902. this.givenDirection = direction;
  24903. if (direction === 'auto') {
  24904. isHorizontal = maxPosition.x > minPosition.x;
  24905. isVertical = maxPosition.y > minPosition.y;
  24906. if (isHorizontal && isVertical) {
  24907. direction = 'both';
  24908. }
  24909. else if (isHorizontal) {
  24910. direction = 'horizontal';
  24911. }
  24912. else {
  24913. direction = 'vertical';
  24914. }
  24915. }
  24916. return direction;
  24917. },
  24918. /**
  24919. * @private
  24920. */
  24921. updateDirection: function(direction) {
  24922. var isAxisEnabled = this.isAxisEnabledFlags;
  24923. isAxisEnabled.x = (direction === 'both' || direction === 'horizontal');
  24924. isAxisEnabled.y = (direction === 'both' || direction === 'vertical');
  24925. },
  24926. /**
  24927. * Returns `true` if a specified axis is enabled.
  24928. * @param {String} axis The axis to check (`x` or `y`).
  24929. * @return {Boolean} `true` if the axis is enabled.
  24930. */
  24931. isAxisEnabled: function(axis) {
  24932. this.getDirection();
  24933. return this.isAxisEnabledFlags[axis];
  24934. },
  24935. /**
  24936. * @private
  24937. * @return {Object}
  24938. */
  24939. applyMomentumEasing: function(easing) {
  24940. var defaultClass = Ext.fx.easing.BoundMomentum;
  24941. return {
  24942. x: Ext.factory(easing, defaultClass),
  24943. y: Ext.factory(easing, defaultClass)
  24944. };
  24945. },
  24946. /**
  24947. * @private
  24948. * @return {Object}
  24949. */
  24950. applyBounceEasing: function(easing) {
  24951. var defaultClass = Ext.fx.easing.EaseOut;
  24952. return {
  24953. x: Ext.factory(easing, defaultClass),
  24954. y: Ext.factory(easing, defaultClass)
  24955. };
  24956. },
  24957. updateBounceEasing: function(easing) {
  24958. this.getTranslatable().setEasingX(easing.x).setEasingY(easing.y);
  24959. },
  24960. /**
  24961. * @private
  24962. * @return {Object}
  24963. */
  24964. applySlotSnapEasing: function(easing) {
  24965. var defaultClass = Ext.fx.easing.EaseOut;
  24966. return {
  24967. x: Ext.factory(easing, defaultClass),
  24968. y: Ext.factory(easing, defaultClass)
  24969. };
  24970. },
  24971. /**
  24972. * @private
  24973. * @return {Object}
  24974. */
  24975. getMinPosition: function() {
  24976. var minPosition = this.minPosition;
  24977. if (!minPosition) {
  24978. this.minPosition = minPosition = {
  24979. x: 0,
  24980. y: 0
  24981. };
  24982. this.fireEvent('minpositionchange', this, minPosition);
  24983. }
  24984. return minPosition;
  24985. },
  24986. /**
  24987. * @private
  24988. * @return {Object}
  24989. */
  24990. getMaxPosition: function() {
  24991. var maxPosition = this.maxPosition,
  24992. size, containerSize;
  24993. if (!maxPosition) {
  24994. size = this.getSize();
  24995. containerSize = this.getContainerSize();
  24996. this.maxPosition = maxPosition = {
  24997. x: Math.max(0, size.x - containerSize.x),
  24998. y: Math.max(0, size.y - containerSize.y)
  24999. };
  25000. this.fireEvent('maxpositionchange', this, maxPosition);
  25001. }
  25002. return maxPosition;
  25003. },
  25004. /**
  25005. * @private
  25006. */
  25007. refreshMaxPosition: function() {
  25008. this.maxPosition = null;
  25009. this.getMaxPosition();
  25010. },
  25011. /**
  25012. * @private
  25013. * @return {Object}
  25014. */
  25015. applyContainerSize: function(size) {
  25016. var containerDom = this.getContainer().dom,
  25017. x, y;
  25018. if (!containerDom) {
  25019. return;
  25020. }
  25021. this.givenContainerSize = size;
  25022. if (size === 'auto') {
  25023. x = containerDom.offsetWidth;
  25024. y = containerDom.offsetHeight;
  25025. }
  25026. else {
  25027. x = size.x;
  25028. y = size.y;
  25029. }
  25030. return {
  25031. x: x,
  25032. y: y
  25033. };
  25034. },
  25035. /**
  25036. * @private
  25037. * @param {String/Object} size
  25038. * @return {Object}
  25039. */
  25040. applySize: function(size) {
  25041. var dom = this.getElement().dom,
  25042. x, y;
  25043. if (!dom) {
  25044. return;
  25045. }
  25046. this.givenSize = size;
  25047. if (size === 'auto') {
  25048. x = dom.offsetWidth;
  25049. y = dom.offsetHeight;
  25050. }
  25051. else if (typeof size == 'number') {
  25052. x = size;
  25053. y = size;
  25054. }
  25055. else {
  25056. x = size.x;
  25057. y = size.y;
  25058. }
  25059. return {
  25060. x: x,
  25061. y: y
  25062. };
  25063. },
  25064. /**
  25065. * @private
  25066. */
  25067. updateAutoRefresh: function(autoRefresh) {
  25068. this.getElement().toggleListener(autoRefresh, 'resize', 'onElementResize', this);
  25069. this.getContainer().toggleListener(autoRefresh, 'resize', 'onContainerResize', this);
  25070. },
  25071. applySlotSnapSize: function(snapSize) {
  25072. if (typeof snapSize == 'number') {
  25073. return {
  25074. x: snapSize,
  25075. y: snapSize
  25076. };
  25077. }
  25078. return snapSize;
  25079. },
  25080. applySlotSnapOffset: function(snapOffset) {
  25081. if (typeof snapOffset == 'number') {
  25082. return {
  25083. x: snapOffset,
  25084. y: snapOffset
  25085. };
  25086. }
  25087. return snapOffset;
  25088. },
  25089. /**
  25090. * @private
  25091. * Returns the container for this scroller
  25092. */
  25093. getContainer: function() {
  25094. var container = this.container;
  25095. if (!container) {
  25096. this.container = container = this.getElement().getParent();
  25097. //<debug error>
  25098. if (!container) {
  25099. Ext.Logger.error("Making an element scrollable that doesn't have any container");
  25100. }
  25101. //</debug>
  25102. container.addCls(this.containerCls);
  25103. }
  25104. return container;
  25105. },
  25106. /**
  25107. * @private
  25108. * @return {Ext.scroll.Scroller} this
  25109. * @chainable
  25110. */
  25111. refresh: function() {
  25112. this.stopAnimation();
  25113. this.getTranslatable().refresh();
  25114. this.setSize(this.givenSize);
  25115. this.setContainerSize(this.givenContainerSize);
  25116. this.setDirection(this.givenDirection);
  25117. this.fireEvent('refresh', this);
  25118. return this;
  25119. },
  25120. onElementResize: function(element, info) {
  25121. this.setSize({
  25122. x: info.width,
  25123. y: info.height
  25124. });
  25125. this.refresh();
  25126. },
  25127. onContainerResize: function(container, info) {
  25128. this.setContainerSize({
  25129. x: info.width,
  25130. y: info.height
  25131. });
  25132. this.refresh();
  25133. },
  25134. /**
  25135. * Scrolls to the given location.
  25136. *
  25137. * @param {Number} x The scroll position on the x axis.
  25138. * @param {Number} y The scroll position on the y axis.
  25139. * @param {Boolean/Object} animation (optional) Whether or not to animate the scrolling to the new position.
  25140. *
  25141. * @return {Ext.scroll.Scroller} this
  25142. * @chainable
  25143. */
  25144. scrollTo: function(x, y, animation) {
  25145. var translatable = this.getTranslatable(),
  25146. position = this.position,
  25147. positionChanged = false,
  25148. translationX, translationY;
  25149. if (this.isAxisEnabled('x')) {
  25150. if (typeof x != 'number') {
  25151. x = position.x;
  25152. }
  25153. else {
  25154. if (position.x !== x) {
  25155. position.x = x;
  25156. positionChanged = true;
  25157. }
  25158. }
  25159. translationX = -x;
  25160. }
  25161. if (this.isAxisEnabled('y')) {
  25162. if (typeof y != 'number') {
  25163. y = position.y;
  25164. }
  25165. else {
  25166. if (position.y !== y) {
  25167. position.y = y;
  25168. positionChanged = true;
  25169. }
  25170. }
  25171. translationY = -y;
  25172. }
  25173. if (positionChanged) {
  25174. if (animation !== undefined) {
  25175. translatable.translateAnimated(translationX, translationY, animation);
  25176. }
  25177. else {
  25178. this.fireEvent('scroll', this, position.x, position.y);
  25179. translatable.translate(translationX, translationY);
  25180. }
  25181. }
  25182. return this;
  25183. },
  25184. /**
  25185. * @private
  25186. * @return {Ext.scroll.Scroller} this
  25187. * @chainable
  25188. */
  25189. scrollToTop: function(animation) {
  25190. var initialOffset = this.getInitialOffset();
  25191. return this.scrollTo(initialOffset.x, initialOffset.y, animation);
  25192. },
  25193. /**
  25194. * Scrolls to the end of the scrollable view.
  25195. * @return {Ext.scroll.Scroller} this
  25196. * @chainable
  25197. */
  25198. scrollToEnd: function(animation) {
  25199. var size = this.getSize(),
  25200. cntSize = this.getContainerSize();
  25201. return this.scrollTo(size.x - cntSize.x, size.y - cntSize.y, animation);
  25202. },
  25203. /**
  25204. * Change the scroll offset by the given amount.
  25205. * @param {Number} x The offset to scroll by on the x axis.
  25206. * @param {Number} y The offset to scroll by on the y axis.
  25207. * @param {Boolean/Object} animation (optional) Whether or not to animate the scrolling to the new position.
  25208. * @return {Ext.scroll.Scroller} this
  25209. * @chainable
  25210. */
  25211. scrollBy: function(x, y, animation) {
  25212. var position = this.position;
  25213. x = (typeof x == 'number') ? x + position.x : null;
  25214. y = (typeof y == 'number') ? y + position.y : null;
  25215. return this.scrollTo(x, y, animation);
  25216. },
  25217. /**
  25218. * @private
  25219. */
  25220. onTouchStart: function() {
  25221. this.isTouching = true;
  25222. this.stopAnimation();
  25223. },
  25224. /**
  25225. * @private
  25226. */
  25227. onTouchEnd: function() {
  25228. var position = this.position;
  25229. this.isTouching = false;
  25230. if (!this.isDragging && this.snapToSlot()) {
  25231. this.fireEvent('scrollstart', this, position.x, position.y);
  25232. }
  25233. },
  25234. /**
  25235. * @private
  25236. */
  25237. onDragStart: function(e) {
  25238. var direction = this.getDirection(),
  25239. absDeltaX = e.absDeltaX,
  25240. absDeltaY = e.absDeltaY,
  25241. directionLock = this.getDirectionLock(),
  25242. startPosition = this.startPosition,
  25243. flickStartPosition = this.flickStartPosition,
  25244. flickStartTime = this.flickStartTime,
  25245. lastDragPosition = this.lastDragPosition,
  25246. currentPosition = this.position,
  25247. dragDirection = this.dragDirection,
  25248. x = currentPosition.x,
  25249. y = currentPosition.y,
  25250. now = Ext.Date.now();
  25251. this.isDragging = true;
  25252. if (directionLock && direction !== 'both') {
  25253. if ((direction === 'horizontal' && absDeltaX > absDeltaY)
  25254. || (direction === 'vertical' && absDeltaY > absDeltaX)) {
  25255. e.stopPropagation();
  25256. }
  25257. else {
  25258. this.isDragging = false;
  25259. return;
  25260. }
  25261. }
  25262. lastDragPosition.x = x;
  25263. lastDragPosition.y = y;
  25264. flickStartPosition.x = x;
  25265. flickStartPosition.y = y;
  25266. startPosition.x = x;
  25267. startPosition.y = y;
  25268. flickStartTime.x = now;
  25269. flickStartTime.y = now;
  25270. dragDirection.x = 0;
  25271. dragDirection.y = 0;
  25272. this.dragStartTime = now;
  25273. this.isDragging = true;
  25274. this.fireEvent('scrollstart', this, x, y);
  25275. },
  25276. /**
  25277. * @private
  25278. */
  25279. onAxisDrag: function(axis, delta) {
  25280. if (!this.isAxisEnabled(axis)) {
  25281. return;
  25282. }
  25283. var flickStartPosition = this.flickStartPosition,
  25284. flickStartTime = this.flickStartTime,
  25285. lastDragPosition = this.lastDragPosition,
  25286. dragDirection = this.dragDirection,
  25287. old = this.position[axis],
  25288. min = this.getMinPosition()[axis],
  25289. max = this.getMaxPosition()[axis],
  25290. start = this.startPosition[axis],
  25291. last = lastDragPosition[axis],
  25292. current = start - delta,
  25293. lastDirection = dragDirection[axis],
  25294. restrictFactor = this.getOutOfBoundRestrictFactor(),
  25295. startMomentumResetTime = this.getStartMomentumResetTime(),
  25296. now = Ext.Date.now(),
  25297. distance;
  25298. if (current < min) {
  25299. current *= restrictFactor;
  25300. }
  25301. else if (current > max) {
  25302. distance = current - max;
  25303. current = max + distance * restrictFactor;
  25304. }
  25305. if (current > last) {
  25306. dragDirection[axis] = 1;
  25307. }
  25308. else if (current < last) {
  25309. dragDirection[axis] = -1;
  25310. }
  25311. if ((lastDirection !== 0 && (dragDirection[axis] !== lastDirection))
  25312. || (now - flickStartTime[axis]) > startMomentumResetTime) {
  25313. flickStartPosition[axis] = old;
  25314. flickStartTime[axis] = now;
  25315. }
  25316. lastDragPosition[axis] = current;
  25317. },
  25318. /**
  25319. * @private
  25320. */
  25321. onDrag: function(e) {
  25322. if (!this.isDragging) {
  25323. return;
  25324. }
  25325. var lastDragPosition = this.lastDragPosition;
  25326. this.onAxisDrag('x', e.deltaX);
  25327. this.onAxisDrag('y', e.deltaY);
  25328. this.scrollTo(lastDragPosition.x, lastDragPosition.y);
  25329. },
  25330. /**
  25331. * @private
  25332. */
  25333. onDragEnd: function(e) {
  25334. var easingX, easingY;
  25335. if (!this.isDragging) {
  25336. return;
  25337. }
  25338. this.dragEndTime = Ext.Date.now();
  25339. this.onDrag(e);
  25340. this.isDragging = false;
  25341. easingX = this.getAnimationEasing('x');
  25342. easingY = this.getAnimationEasing('y');
  25343. if (easingX || easingY) {
  25344. this.getTranslatable().animate(easingX, easingY);
  25345. }
  25346. else {
  25347. this.onScrollEnd();
  25348. }
  25349. },
  25350. /**
  25351. * @private
  25352. */
  25353. getAnimationEasing: function(axis) {
  25354. if (!this.isAxisEnabled(axis)) {
  25355. return null;
  25356. }
  25357. var currentPosition = this.position[axis],
  25358. flickStartPosition = this.flickStartPosition[axis],
  25359. flickStartTime = this.flickStartTime[axis],
  25360. minPosition = this.getMinPosition()[axis],
  25361. maxPosition = this.getMaxPosition()[axis],
  25362. maxAbsVelocity = this.getMaxAbsoluteVelocity(),
  25363. boundValue = null,
  25364. dragEndTime = this.dragEndTime,
  25365. easing, velocity, duration;
  25366. if (currentPosition < minPosition) {
  25367. boundValue = minPosition;
  25368. }
  25369. else if (currentPosition > maxPosition) {
  25370. boundValue = maxPosition;
  25371. }
  25372. // Out of bound, to be pulled back
  25373. if (boundValue !== null) {
  25374. easing = this.getBounceEasing()[axis];
  25375. easing.setConfig({
  25376. startTime: dragEndTime,
  25377. startValue: -currentPosition,
  25378. endValue: -boundValue
  25379. });
  25380. return easing;
  25381. }
  25382. // Still within boundary, start deceleration
  25383. duration = dragEndTime - flickStartTime;
  25384. if (duration === 0) {
  25385. return null;
  25386. }
  25387. velocity = (currentPosition - flickStartPosition) / (dragEndTime - flickStartTime);
  25388. if (velocity === 0) {
  25389. return null;
  25390. }
  25391. if (velocity < -maxAbsVelocity) {
  25392. velocity = -maxAbsVelocity;
  25393. }
  25394. else if (velocity > maxAbsVelocity) {
  25395. velocity = maxAbsVelocity;
  25396. }
  25397. easing = this.getMomentumEasing()[axis];
  25398. easing.setConfig({
  25399. startTime: dragEndTime,
  25400. startValue: -currentPosition,
  25401. startVelocity: -velocity,
  25402. minMomentumValue: -maxPosition,
  25403. maxMomentumValue: 0
  25404. });
  25405. return easing;
  25406. },
  25407. /**
  25408. * @private
  25409. */
  25410. onAnimationFrame: function(translatable, x, y) {
  25411. var position = this.position;
  25412. position.x = -x;
  25413. position.y = -y;
  25414. this.fireEvent('scroll', this, position.x, position.y);
  25415. },
  25416. /**
  25417. * @private
  25418. */
  25419. onAnimationEnd: function() {
  25420. this.snapToBoundary();
  25421. this.onScrollEnd();
  25422. },
  25423. /**
  25424. * @private
  25425. * Stops the animation of the scroller at any time.
  25426. */
  25427. stopAnimation: function() {
  25428. this.getTranslatable().stopAnimation();
  25429. },
  25430. /**
  25431. * @private
  25432. */
  25433. onScrollEnd: function() {
  25434. var position = this.position;
  25435. if (this.isTouching || !this.snapToSlot()) {
  25436. this.fireEvent('scrollend', this, position.x, position.y);
  25437. }
  25438. },
  25439. /**
  25440. * @private
  25441. * @return {Boolean}
  25442. */
  25443. snapToSlot: function() {
  25444. var snapX = this.getSnapPosition('x'),
  25445. snapY = this.getSnapPosition('y'),
  25446. easing = this.getSlotSnapEasing();
  25447. if (snapX !== null || snapY !== null) {
  25448. this.scrollTo(snapX, snapY, {
  25449. easingX: easing.x,
  25450. easingY: easing.y
  25451. });
  25452. return true;
  25453. }
  25454. return false;
  25455. },
  25456. /**
  25457. * @private
  25458. * @return {Number/null}
  25459. */
  25460. getSnapPosition: function(axis) {
  25461. var snapSize = this.getSlotSnapSize()[axis],
  25462. snapPosition = null,
  25463. position, snapOffset, maxPosition, mod;
  25464. if (snapSize !== 0 && this.isAxisEnabled(axis)) {
  25465. position = this.position[axis];
  25466. snapOffset = this.getSlotSnapOffset()[axis];
  25467. maxPosition = this.getMaxPosition()[axis];
  25468. mod = (position - snapOffset) % snapSize;
  25469. if (mod !== 0) {
  25470. if (Math.abs(mod) > snapSize / 2) {
  25471. snapPosition = position + ((mod > 0) ? snapSize - mod : mod - snapSize);
  25472. if (snapPosition > maxPosition) {
  25473. snapPosition = position - mod;
  25474. }
  25475. }
  25476. else {
  25477. snapPosition = position - mod;
  25478. }
  25479. }
  25480. }
  25481. return snapPosition;
  25482. },
  25483. /**
  25484. * @private
  25485. */
  25486. snapToBoundary: function() {
  25487. var position = this.position,
  25488. minPosition = this.getMinPosition(),
  25489. maxPosition = this.getMaxPosition(),
  25490. minX = minPosition.x,
  25491. minY = minPosition.y,
  25492. maxX = maxPosition.x,
  25493. maxY = maxPosition.y,
  25494. x = Math.round(position.x),
  25495. y = Math.round(position.y);
  25496. if (x < minX) {
  25497. x = minX;
  25498. }
  25499. else if (x > maxX) {
  25500. x = maxX;
  25501. }
  25502. if (y < minY) {
  25503. y = minY;
  25504. }
  25505. else if (y > maxY) {
  25506. y = maxY;
  25507. }
  25508. this.scrollTo(x, y);
  25509. },
  25510. destroy: function() {
  25511. var element = this.getElement(),
  25512. sizeMonitors = this.sizeMonitors;
  25513. if (sizeMonitors) {
  25514. sizeMonitors.element.destroy();
  25515. sizeMonitors.container.destroy();
  25516. }
  25517. if (element && !element.isDestroyed) {
  25518. element.removeCls(this.cls);
  25519. this.getContainer().removeCls(this.containerCls);
  25520. }
  25521. Ext.destroy(this.getTranslatable());
  25522. this.callParent(arguments);
  25523. }
  25524. }, function() {
  25525. });
  25526. /**
  25527. * @private
  25528. */
  25529. Ext.define('Ext.scroll.indicator.Abstract', {
  25530. extend: 'Ext.Component',
  25531. config: {
  25532. baseCls: 'x-scroll-indicator',
  25533. axis: 'x',
  25534. value: 0,
  25535. length: null,
  25536. minLength: 6,
  25537. hidden: true,
  25538. ui: 'dark'
  25539. },
  25540. cachedConfig: {
  25541. ratio: 1,
  25542. barCls: 'x-scroll-bar',
  25543. active: true
  25544. },
  25545. barElement: null,
  25546. barLength: 0,
  25547. gapLength: 0,
  25548. getElementConfig: function() {
  25549. return {
  25550. reference: 'barElement',
  25551. children: [this.callParent()]
  25552. };
  25553. },
  25554. applyRatio: function(ratio) {
  25555. if (isNaN(ratio)) {
  25556. ratio = 1;
  25557. }
  25558. return ratio;
  25559. },
  25560. refresh: function() {
  25561. var bar = this.barElement,
  25562. barDom = bar.dom,
  25563. ratio = this.getRatio(),
  25564. axis = this.getAxis(),
  25565. barLength = (axis === 'x') ? barDom.offsetWidth : barDom.offsetHeight,
  25566. length = barLength * ratio;
  25567. this.barLength = barLength;
  25568. this.gapLength = barLength - length;
  25569. this.setLength(length);
  25570. this.updateValue(this.getValue());
  25571. },
  25572. updateBarCls: function(barCls) {
  25573. this.barElement.addCls(barCls);
  25574. },
  25575. updateAxis: function(axis) {
  25576. this.element.addCls(this.getBaseCls(), null, axis);
  25577. this.barElement.addCls(this.getBarCls(), null, axis);
  25578. },
  25579. updateValue: function(value) {
  25580. this.setOffset(this.gapLength * value);
  25581. },
  25582. updateActive: function(active) {
  25583. this.barElement[active ? 'addCls' : 'removeCls']('active');
  25584. },
  25585. doSetHidden: function(hidden) {
  25586. var elementDomStyle = this.element.dom.style;
  25587. if (hidden) {
  25588. elementDomStyle.opacity = '0';
  25589. }
  25590. else {
  25591. elementDomStyle.opacity = '';
  25592. }
  25593. },
  25594. applyLength: function(length) {
  25595. return Math.max(this.getMinLength(), length);
  25596. },
  25597. updateLength: function(length) {
  25598. if (!this.isDestroyed) {
  25599. var axis = this.getAxis(),
  25600. element = this.element;
  25601. if (axis === 'x') {
  25602. element.setWidth(length);
  25603. }
  25604. else {
  25605. element.setHeight(length);
  25606. }
  25607. }
  25608. },
  25609. setOffset: function(offset) {
  25610. var axis = this.getAxis(),
  25611. element = this.element;
  25612. if (axis === 'x') {
  25613. element.setLeft(offset);
  25614. }
  25615. else {
  25616. element.setTop(offset);
  25617. }
  25618. }
  25619. });
  25620. /**
  25621. * @private
  25622. */
  25623. Ext.define('Ext.scroll.indicator.Default', {
  25624. extend: 'Ext.scroll.indicator.Abstract',
  25625. config: {
  25626. cls: 'default'
  25627. },
  25628. setOffset: function(offset) {
  25629. var axis = this.getAxis(),
  25630. domStyle = this.element.dom.style;
  25631. if (axis === 'x') {
  25632. domStyle.webkitTransform = 'translate3d(' + offset + 'px, 0, 0)';
  25633. }
  25634. else {
  25635. domStyle.webkitTransform = 'translate3d(0, ' + offset + 'px, 0)';
  25636. }
  25637. },
  25638. updateValue: function(value) {
  25639. var barLength = this.barLength,
  25640. gapLength = this.gapLength,
  25641. length = this.getLength(),
  25642. newLength, offset, extra;
  25643. if (value <= 0) {
  25644. offset = 0;
  25645. this.updateLength(this.applyLength(length + value * barLength));
  25646. }
  25647. else if (value >= 1) {
  25648. extra = Math.round((value - 1) * barLength);
  25649. newLength = this.applyLength(length - extra);
  25650. extra = length - newLength;
  25651. this.updateLength(newLength);
  25652. offset = gapLength + extra;
  25653. }
  25654. else {
  25655. offset = gapLength * value;
  25656. }
  25657. this.setOffset(offset);
  25658. }
  25659. });
  25660. /**
  25661. * @private
  25662. */
  25663. Ext.define('Ext.scroll.indicator.ScrollPosition', {
  25664. extend: 'Ext.scroll.indicator.Abstract',
  25665. config: {
  25666. cls: 'scrollposition'
  25667. },
  25668. getElementConfig: function() {
  25669. var config = this.callParent(arguments);
  25670. config.children.unshift({
  25671. className: 'x-scroll-bar-stretcher'
  25672. });
  25673. return config;
  25674. },
  25675. updateValue: function(value) {
  25676. if (this.gapLength === 0) {
  25677. if (value > 1) {
  25678. value = value - 1;
  25679. }
  25680. this.setOffset(this.barLength * value);
  25681. }
  25682. else {
  25683. this.setOffset(this.gapLength * value);
  25684. }
  25685. },
  25686. updateLength: function() {
  25687. var scrollOffset = this.barLength,
  25688. barDom = this.barElement.dom,
  25689. element = this.element;
  25690. this.callParent(arguments);
  25691. if (this.getAxis() === 'x') {
  25692. barDom.scrollLeft = scrollOffset;
  25693. element.setLeft(scrollOffset);
  25694. }
  25695. else {
  25696. barDom.scrollTop = scrollOffset;
  25697. element.setTop(scrollOffset);
  25698. }
  25699. },
  25700. setOffset: function(offset) {
  25701. var barLength = this.barLength,
  25702. minLength = this.getMinLength(),
  25703. barDom = this.barElement.dom;
  25704. offset = Math.min(barLength - minLength, Math.max(offset, minLength - this.getLength()));
  25705. offset = barLength - offset;
  25706. if (this.getAxis() === 'x') {
  25707. barDom.scrollLeft = offset;
  25708. }
  25709. else {
  25710. barDom.scrollTop = offset;
  25711. }
  25712. }
  25713. });
  25714. /**
  25715. * @private
  25716. */
  25717. Ext.define('Ext.scroll.indicator.CssTransform', {
  25718. extend: 'Ext.scroll.indicator.Abstract',
  25719. config: {
  25720. cls: 'csstransform'
  25721. },
  25722. getElementConfig: function() {
  25723. var config = this.callParent();
  25724. config.children[0].children = [
  25725. {
  25726. reference: 'startElement'
  25727. },
  25728. {
  25729. reference: 'middleElement'
  25730. },
  25731. {
  25732. reference: 'endElement'
  25733. }
  25734. ];
  25735. return config;
  25736. },
  25737. refresh: function() {
  25738. var axis = this.getAxis(),
  25739. startElementDom = this.startElement.dom,
  25740. endElementDom = this.endElement.dom,
  25741. middleElement = this.middleElement,
  25742. startElementLength, endElementLength;
  25743. if (axis === 'x') {
  25744. startElementLength = startElementDom.offsetWidth;
  25745. endElementLength = endElementDom.offsetWidth;
  25746. middleElement.setLeft(startElementLength);
  25747. }
  25748. else {
  25749. startElementLength = startElementDom.offsetHeight;
  25750. endElementLength = endElementDom.offsetHeight;
  25751. middleElement.setTop(startElementLength);
  25752. }
  25753. this.startElementLength = startElementLength;
  25754. this.endElementLength = endElementLength;
  25755. this.callParent();
  25756. },
  25757. updateLength: function(length) {
  25758. var axis = this.getAxis(),
  25759. endElementStyle = this.endElement.dom.style,
  25760. middleElementStyle = this.middleElement.dom.style,
  25761. endElementLength = this.endElementLength,
  25762. endElementOffset = length - endElementLength,
  25763. middleElementLength = endElementOffset - this.startElementLength;
  25764. if (axis === 'x') {
  25765. endElementStyle.webkitTransform = 'translate3d(' + endElementOffset + 'px, 0, 0)';
  25766. middleElementStyle.webkitTransform = 'translate3d(0, 0, 0) scaleX(' + middleElementLength + ')';
  25767. }
  25768. else {
  25769. endElementStyle.webkitTransform = 'translate3d(0, ' + endElementOffset + 'px, 0)';
  25770. middleElementStyle.webkitTransform = 'translate3d(0, 0, 0) scaleY(' + middleElementLength + ')';
  25771. }
  25772. },
  25773. updateValue: function(value) {
  25774. var barLength = this.barLength,
  25775. gapLength = this.gapLength,
  25776. length = this.getLength(),
  25777. newLength, offset, extra;
  25778. if (value <= 0) {
  25779. offset = 0;
  25780. this.updateLength(this.applyLength(length + value * barLength));
  25781. }
  25782. else if (value >= 1) {
  25783. extra = Math.round((value - 1) * barLength);
  25784. newLength = this.applyLength(length - extra);
  25785. extra = length - newLength;
  25786. this.updateLength(newLength);
  25787. offset = gapLength + extra;
  25788. }
  25789. else {
  25790. offset = gapLength * value;
  25791. }
  25792. this.setOffset(offset);
  25793. },
  25794. setOffset: function(offset) {
  25795. var axis = this.getAxis(),
  25796. elementStyle = this.element.dom.style;
  25797. offset = Math.round(offset);
  25798. if (axis === 'x') {
  25799. elementStyle.webkitTransform = 'translate3d(' + offset + 'px, 0, 0)';
  25800. }
  25801. else {
  25802. elementStyle.webkitTransform = 'translate3d(0, ' + offset + 'px, 0)';
  25803. }
  25804. }
  25805. });
  25806. /**
  25807. * @private
  25808. */
  25809. Ext.define('Ext.scroll.indicator.Throttled', {
  25810. extend:'Ext.scroll.indicator.Default',
  25811. config: {
  25812. cls: 'throttled'
  25813. },
  25814. constructor: function() {
  25815. this.callParent(arguments);
  25816. this.updateLength = Ext.Function.createThrottled(this.updateLength, 75, this);
  25817. this.setOffset = Ext.Function.createThrottled(this.setOffset, 50, this);
  25818. },
  25819. doSetHidden: function(hidden) {
  25820. if (hidden) {
  25821. this.setOffset(-10000);
  25822. } else {
  25823. delete this.lastLength;
  25824. delete this.lastOffset;
  25825. this.updateValue(this.getValue());
  25826. }
  25827. },
  25828. updateLength: function(length) {
  25829. length = Math.round(length);
  25830. if (this.lastLength === length || this.lastOffset === -10000) {
  25831. return;
  25832. }
  25833. this.lastLength = length;
  25834. Ext.TaskQueue.requestWrite('doUpdateLength', this,[length]);
  25835. },
  25836. doUpdateLength: function(length){
  25837. if (!this.isDestroyed) {
  25838. var axis = this.getAxis(),
  25839. element = this.element;
  25840. if (axis === 'x') {
  25841. element.setWidth(length);
  25842. }
  25843. else {
  25844. element.setHeight(length);
  25845. }
  25846. }
  25847. },
  25848. setOffset: function(offset) {
  25849. offset = Math.round(offset);
  25850. if (this.lastOffset === offset || this.lastOffset === -10000) {
  25851. return;
  25852. }
  25853. this.lastOffset = offset;
  25854. Ext.TaskQueue.requestWrite('doSetOffset', this,[offset]);
  25855. },
  25856. doSetOffset: function(offset) {
  25857. if (!this.isDestroyed) {
  25858. var axis = this.getAxis(),
  25859. domStyle = this.element.dom.style;
  25860. if (axis === 'x') {
  25861. domStyle.webkitTransform = 'translate3d(' + offset + 'px, 0, 0)';
  25862. }
  25863. else {
  25864. domStyle.webkitTransform = 'translate3d(0, ' + offset + 'px, 0)';
  25865. }
  25866. }
  25867. }
  25868. });
  25869. /**
  25870. * @private
  25871. */
  25872. Ext.define('Ext.scroll.Indicator', {
  25873. requires: [
  25874. 'Ext.scroll.indicator.Default',
  25875. 'Ext.scroll.indicator.ScrollPosition',
  25876. 'Ext.scroll.indicator.CssTransform',
  25877. 'Ext.scroll.indicator.Throttled'
  25878. ],
  25879. alternateClassName: 'Ext.util.Indicator',
  25880. constructor: function(config) {
  25881. if (Ext.os.is.Android2 || Ext.os.is.Android3 || Ext.browser.is.ChromeMobile) {
  25882. return new Ext.scroll.indicator.ScrollPosition(config);
  25883. }
  25884. else if (Ext.os.is.iOS) {
  25885. return new Ext.scroll.indicator.CssTransform(config);
  25886. }
  25887. else if (Ext.os.is.Android4) {
  25888. return new Ext.scroll.indicator.Throttled(config);
  25889. }
  25890. else {
  25891. return new Ext.scroll.indicator.Default(config);
  25892. }
  25893. }
  25894. });
  25895. /**
  25896. * This is a simple container that is used to compile content and a {@link Ext.scroll.View} instance. It also
  25897. * provides scroll indicators.
  25898. *
  25899. * 99% of the time all you need to use in this class is {@link #getScroller}.
  25900. *
  25901. * This should never should be extended.
  25902. */
  25903. Ext.define('Ext.scroll.View', {
  25904. extend: 'Ext.Evented',
  25905. alternateClassName: 'Ext.util.ScrollView',
  25906. requires: [
  25907. 'Ext.scroll.Scroller',
  25908. 'Ext.scroll.Indicator'
  25909. ],
  25910. config: {
  25911. /**
  25912. * @cfg {String} indicatorsUi
  25913. * The style of the indicators of this view. Available options are `dark` or `light`.
  25914. */
  25915. indicatorsUi: 'dark',
  25916. element: null,
  25917. scroller: {},
  25918. indicators: {
  25919. x: {
  25920. axis: 'x'
  25921. },
  25922. y: {
  25923. axis: 'y'
  25924. }
  25925. },
  25926. indicatorsHidingDelay: 100,
  25927. cls: Ext.baseCSSPrefix + 'scroll-view'
  25928. },
  25929. /**
  25930. * @method getScroller
  25931. * Returns the scroller instance in this view. Checkout the documentation of {@link Ext.scroll.Scroller} and
  25932. * {@link Ext.Container#getScrollable} for more information.
  25933. * @return {Ext.scroll.View} The scroller
  25934. */
  25935. /**
  25936. * @private
  25937. */
  25938. processConfig: function(config) {
  25939. if (!config) {
  25940. return null;
  25941. }
  25942. if (typeof config == 'string') {
  25943. config = {
  25944. direction: config
  25945. };
  25946. }
  25947. config = Ext.merge({}, config);
  25948. var scrollerConfig = config.scroller,
  25949. name;
  25950. if (!scrollerConfig) {
  25951. config.scroller = scrollerConfig = {};
  25952. }
  25953. for (name in config) {
  25954. if (config.hasOwnProperty(name)) {
  25955. if (!this.hasConfig(name)) {
  25956. scrollerConfig[name] = config[name];
  25957. delete config[name];
  25958. }
  25959. }
  25960. }
  25961. return config;
  25962. },
  25963. constructor: function(config) {
  25964. config = this.processConfig(config);
  25965. this.useIndicators = { x: true, y: true };
  25966. this.doHideIndicators = Ext.Function.bind(this.doHideIndicators, this);
  25967. this.initConfig(config);
  25968. },
  25969. setConfig: function(config) {
  25970. return this.callParent([this.processConfig(config)]);
  25971. },
  25972. updateIndicatorsUi: function(newUi) {
  25973. var indicators = this.getIndicators();
  25974. indicators.x.setUi(newUi);
  25975. indicators.y.setUi(newUi);
  25976. },
  25977. applyScroller: function(config, currentScroller) {
  25978. return Ext.factory(config, Ext.scroll.Scroller, currentScroller);
  25979. },
  25980. applyIndicators: function(config, indicators) {
  25981. var defaultClass = Ext.scroll.Indicator,
  25982. useIndicators = this.useIndicators;
  25983. if (!config) {
  25984. config = {};
  25985. }
  25986. if (!config.x) {
  25987. useIndicators.x = false;
  25988. config.x = {};
  25989. }
  25990. if (!config.y) {
  25991. useIndicators.y = false;
  25992. config.y = {};
  25993. }
  25994. return {
  25995. x: Ext.factory(config.x, defaultClass, indicators && indicators.x),
  25996. y: Ext.factory(config.y, defaultClass, indicators && indicators.y)
  25997. };
  25998. },
  25999. updateIndicators: function(indicators) {
  26000. this.indicatorsGrid = Ext.Element.create({
  26001. className: 'x-scroll-bar-grid-wrapper',
  26002. children: [{
  26003. className: 'x-scroll-bar-grid',
  26004. children: [
  26005. {
  26006. children: [{}, {
  26007. children: [indicators.y.barElement]
  26008. }]
  26009. },
  26010. {
  26011. children: [{
  26012. children: [indicators.x.barElement]
  26013. }, {}]
  26014. }
  26015. ]
  26016. }]
  26017. });
  26018. },
  26019. updateScroller: function(scroller) {
  26020. scroller.on({
  26021. scope: this,
  26022. scrollstart: 'onScrollStart',
  26023. scroll: 'onScroll',
  26024. scrollend: 'onScrollEnd',
  26025. refresh: 'refreshIndicators'
  26026. });
  26027. },
  26028. isAxisEnabled: function(axis) {
  26029. return this.getScroller().isAxisEnabled(axis) && this.useIndicators[axis];
  26030. },
  26031. applyElement: function(element) {
  26032. if (element) {
  26033. return Ext.get(element);
  26034. }
  26035. },
  26036. updateElement: function(element) {
  26037. var scrollerElement = element.getFirstChild().getFirstChild(),
  26038. scroller = this.getScroller();
  26039. element.addCls(this.getCls());
  26040. element.insertFirst(this.indicatorsGrid);
  26041. scroller.setElement(scrollerElement);
  26042. this.refreshIndicators();
  26043. return this;
  26044. },
  26045. showIndicators: function() {
  26046. var indicators = this.getIndicators();
  26047. if (this.hasOwnProperty('indicatorsHidingTimer')) {
  26048. clearTimeout(this.indicatorsHidingTimer);
  26049. delete this.indicatorsHidingTimer;
  26050. }
  26051. if (this.isAxisEnabled('x')) {
  26052. indicators.x.show();
  26053. }
  26054. if (this.isAxisEnabled('y')) {
  26055. indicators.y.show();
  26056. }
  26057. },
  26058. hideIndicators: function() {
  26059. var delay = this.getIndicatorsHidingDelay();
  26060. if (delay > 0) {
  26061. this.indicatorsHidingTimer = setTimeout(this.doHideIndicators, delay);
  26062. }
  26063. else {
  26064. this.doHideIndicators();
  26065. }
  26066. },
  26067. doHideIndicators: function() {
  26068. var indicators = this.getIndicators();
  26069. if (this.isAxisEnabled('x')) {
  26070. indicators.x.hide();
  26071. }
  26072. if (this.isAxisEnabled('y')) {
  26073. indicators.y.hide();
  26074. }
  26075. },
  26076. onScrollStart: function() {
  26077. this.onScroll.apply(this, arguments);
  26078. this.showIndicators();
  26079. },
  26080. onScrollEnd: function() {
  26081. this.hideIndicators();
  26082. },
  26083. onScroll: function(scroller, x, y) {
  26084. this.setIndicatorValue('x', x);
  26085. this.setIndicatorValue('y', y);
  26086. //<debug>
  26087. if (this.isBenchmarking) {
  26088. this.framesCount++;
  26089. }
  26090. //</debug>
  26091. },
  26092. //<debug>
  26093. isBenchmarking: false,
  26094. framesCount: 0,
  26095. getCurrentFps: function() {
  26096. var now = Date.now(),
  26097. fps;
  26098. if (!this.isBenchmarking) {
  26099. this.isBenchmarking = true;
  26100. fps = 0;
  26101. }
  26102. else {
  26103. fps = Math.round(this.framesCount * 1000 / (now - this.framesCountStartTime));
  26104. }
  26105. this.framesCountStartTime = now;
  26106. this.framesCount = 0;
  26107. return fps;
  26108. },
  26109. //</debug>
  26110. setIndicatorValue: function(axis, scrollerPosition) {
  26111. if (!this.isAxisEnabled(axis)) {
  26112. return this;
  26113. }
  26114. var scroller = this.getScroller(),
  26115. scrollerMaxPosition = scroller.getMaxPosition()[axis],
  26116. scrollerContainerSize = scroller.getContainerSize()[axis],
  26117. value;
  26118. if (scrollerMaxPosition === 0) {
  26119. value = scrollerPosition / scrollerContainerSize;
  26120. if (scrollerPosition >= 0) {
  26121. value += 1;
  26122. }
  26123. }
  26124. else {
  26125. if (scrollerPosition > scrollerMaxPosition) {
  26126. value = 1 + ((scrollerPosition - scrollerMaxPosition) / scrollerContainerSize);
  26127. }
  26128. else if (scrollerPosition < 0) {
  26129. value = scrollerPosition / scrollerContainerSize;
  26130. }
  26131. else {
  26132. value = scrollerPosition / scrollerMaxPosition;
  26133. }
  26134. }
  26135. this.getIndicators()[axis].setValue(value);
  26136. },
  26137. refreshIndicator: function(axis) {
  26138. if (!this.isAxisEnabled(axis)) {
  26139. return this;
  26140. }
  26141. var scroller = this.getScroller(),
  26142. indicator = this.getIndicators()[axis],
  26143. scrollerContainerSize = scroller.getContainerSize()[axis],
  26144. scrollerSize = scroller.getSize()[axis],
  26145. ratio = scrollerContainerSize / scrollerSize;
  26146. indicator.setRatio(ratio);
  26147. indicator.refresh();
  26148. },
  26149. refresh: function() {
  26150. return this.getScroller().refresh();
  26151. },
  26152. refreshIndicators: function() {
  26153. var indicators = this.getIndicators();
  26154. indicators.x.setActive(this.isAxisEnabled('x'));
  26155. indicators.y.setActive(this.isAxisEnabled('y'));
  26156. this.refreshIndicator('x');
  26157. this.refreshIndicator('y');
  26158. },
  26159. destroy: function() {
  26160. var element = this.getElement(),
  26161. indicators = this.getIndicators();
  26162. Ext.destroy(this.getScroller(), this.indicatorsGrid);
  26163. if (this.hasOwnProperty('indicatorsHidingTimer')) {
  26164. clearTimeout(this.indicatorsHidingTimer);
  26165. delete this.indicatorsHidingTimer;
  26166. }
  26167. if (element && !element.isDestroyed) {
  26168. element.removeCls(this.getCls());
  26169. }
  26170. indicators.x.destroy();
  26171. indicators.y.destroy();
  26172. delete this.indicatorsGrid;
  26173. this.callParent(arguments);
  26174. }
  26175. });
  26176. /**
  26177. * @private
  26178. */
  26179. Ext.define('Ext.behavior.Scrollable', {
  26180. extend: 'Ext.behavior.Behavior',
  26181. requires: [
  26182. 'Ext.scroll.View'
  26183. ],
  26184. constructor: function() {
  26185. this.listeners = {
  26186. painted: 'onComponentPainted',
  26187. scope: this
  26188. };
  26189. this.callParent(arguments);
  26190. },
  26191. onComponentPainted: function() {
  26192. this.scrollView.refresh();
  26193. },
  26194. setConfig: function(config) {
  26195. var scrollView = this.scrollView,
  26196. component = this.component,
  26197. scrollerElement;
  26198. if (config) {
  26199. if (!scrollView) {
  26200. this.scrollView = scrollView = new Ext.scroll.View(config);
  26201. scrollView.on('destroy', 'onScrollViewDestroy', this);
  26202. component.setUseBodyElement(true);
  26203. this.scrollerElement = scrollerElement = component.innerElement;
  26204. this.scrollContainer = scrollerElement.wrap();
  26205. scrollView.setElement(component.bodyElement);
  26206. if (component.isPainted()) {
  26207. this.onComponentPainted(component);
  26208. }
  26209. component.on(this.listeners);
  26210. }
  26211. else if (Ext.isString(config) || Ext.isObject(config)) {
  26212. scrollView.setConfig(config);
  26213. }
  26214. }
  26215. else if (scrollView) {
  26216. scrollView.destroy();
  26217. }
  26218. return this;
  26219. },
  26220. getScrollView: function() {
  26221. return this.scrollView;
  26222. },
  26223. onScrollViewDestroy: function() {
  26224. var component = this.component,
  26225. scrollerElement = this.scrollerElement;
  26226. if (!scrollerElement.isDestroyed) {
  26227. this.scrollerElement.unwrap();
  26228. }
  26229. this.scrollContainer.destroy();
  26230. if (!component.isDestroyed) {
  26231. component.un(this.listeners);
  26232. }
  26233. delete this.scrollerElement;
  26234. delete this.scrollView;
  26235. delete this.scrollContainer;
  26236. },
  26237. onComponentDestroy: function() {
  26238. var scrollView = this.scrollView;
  26239. if (scrollView) {
  26240. scrollView.destroy();
  26241. }
  26242. }
  26243. });
  26244. /**
  26245. * A simple class used to mask any {@link Ext.Container}.
  26246. *
  26247. * This should rarely be used directly, instead look at the {@link Ext.Container#masked} configuration.
  26248. *
  26249. * ## Example
  26250. *
  26251. * @example miniphone
  26252. * // Create our container
  26253. * var container = Ext.create('Ext.Container', {
  26254. * html: 'My container!'
  26255. * });
  26256. *
  26257. * // Add the container to the Viewport
  26258. * Ext.Viewport.add(container);
  26259. *
  26260. * // Mask the container
  26261. * container.setMasked(true);
  26262. */
  26263. Ext.define('Ext.Mask', {
  26264. extend: 'Ext.Component',
  26265. xtype: 'mask',
  26266. config: {
  26267. /**
  26268. * @cfg
  26269. * @inheritdoc
  26270. */
  26271. baseCls: Ext.baseCSSPrefix + 'mask',
  26272. /**
  26273. * @cfg {Boolean} transparent True to make this mask transparent.
  26274. */
  26275. transparent: false,
  26276. /**
  26277. * @cfg
  26278. * @hide
  26279. */
  26280. top: 0,
  26281. /**
  26282. * @cfg
  26283. * @hide
  26284. */
  26285. left: 0,
  26286. /**
  26287. * @cfg
  26288. * @hide
  26289. */
  26290. right: 0,
  26291. /**
  26292. * @cfg
  26293. * @hide
  26294. */
  26295. bottom: 0
  26296. },
  26297. /**
  26298. * @event tap
  26299. * A tap event fired when a user taps on this mask
  26300. * @param {Ext.Mask} this The mask instance
  26301. * @param {Ext.EventObject} e The event object
  26302. */
  26303. initialize: function() {
  26304. this.callSuper();
  26305. this.element.on('*', 'onEvent', this);
  26306. },
  26307. onEvent: function(e) {
  26308. var controller = arguments[arguments.length - 1];
  26309. if (controller.info.eventName === 'tap') {
  26310. this.fireEvent('tap', this, e);
  26311. return false;
  26312. }
  26313. if (e && e.stopEvent) {
  26314. e.stopEvent();
  26315. }
  26316. return false;
  26317. },
  26318. updateTransparent: function(newTransparent) {
  26319. this[newTransparent ? 'addCls' : 'removeCls'](this.getBaseCls() + '-transparent');
  26320. }
  26321. });
  26322. /**
  26323. * A Container has all of the abilities of {@link Ext.Component Component}, but lets you nest other Components inside
  26324. * it. Applications are made up of lots of components, usually nested inside one another. Containers allow you to
  26325. * render and arrange child Components inside them. Most apps have a single top-level Container called a Viewport,
  26326. * which takes up the entire screen. Inside of this are child components, for example in a mail app the Viewport
  26327. * Container's two children might be a message List and an email preview pane.
  26328. *
  26329. * Containers give the following extra functionality:
  26330. *
  26331. * - Adding child Components at instantiation and run time
  26332. * - Removing child Components
  26333. * - Specifying a [Layout](#!/guide/layouts)
  26334. *
  26335. * Layouts determine how the child Components should be laid out on the screen. In our mail app example we'd use an
  26336. * HBox layout so that we can pin the email list to the left hand edge of the screen and allow the preview pane to
  26337. * occupy the rest. There are several layouts in Sencha Touch 2, each of which help you achieve your desired
  26338. * application structure, further explained in the [Layout guide](#!/guide/layouts).
  26339. *
  26340. * ## Adding Components to Containers
  26341. *
  26342. * As we mentioned above, Containers are special Components that can have child Components arranged by a Layout. One of
  26343. * the code samples above showed how to create a Panel with 2 child Panels already defined inside it but it's easy to
  26344. * do this at run time too:
  26345. *
  26346. * @example miniphone
  26347. * //this is the Panel we'll be adding below
  26348. * var aboutPanel = Ext.create('Ext.Panel', {
  26349. * html: 'About this app'
  26350. * });
  26351. *
  26352. * //this is the Panel we'll be adding to
  26353. * var mainPanel = Ext.create('Ext.Panel', {
  26354. * fullscreen: true,
  26355. *
  26356. * layout: 'hbox',
  26357. * defaults: {
  26358. * flex: 1
  26359. * },
  26360. *
  26361. * items: {
  26362. * html: 'First Panel',
  26363. * style: 'background-color: #5E99CC;'
  26364. * }
  26365. * });
  26366. *
  26367. * //now we add the first panel inside the second
  26368. * mainPanel.add(aboutPanel);
  26369. *
  26370. * Here we created three Panels in total. First we made the aboutPanel, which we might use to tell the user a little
  26371. * about the app. Then we create one called mainPanel, which already contains a third Panel in its
  26372. * {@link Ext.Container#cfg-items items} configuration, with some dummy text ("First Panel"). Finally, we add the first
  26373. * panel to the second by calling the {@link Ext.Container#method-add add} method on `mainPanel`.
  26374. *
  26375. * In this case we gave our mainPanel another hbox layout, but we also introduced some
  26376. * {@link Ext.Container#defaults defaults}. These are applied to every item in the Panel, so in this case every child
  26377. * inside `mainPanel` will be given a `flex: 1` configuration. The effect of this is that when we first render the screen
  26378. * only a single child is present inside `mainPanel`, so that child takes up the full width available to it. Once the
  26379. * `mainPanel.add` line is called though, the `aboutPanel` is rendered inside of it and also given a `flex` of 1, which will
  26380. * cause it and the first panel to both receive half the full width of the `mainPanel`.
  26381. *
  26382. * Likewise, it's easy to remove items from a Container:
  26383. *
  26384. * mainPanel.remove(aboutPanel);
  26385. *
  26386. * After this line is run everything is back to how it was, with the first child panel once again taking up the full
  26387. * width inside `mainPanel`.
  26388. *
  26389. * ## Further Reading
  26390. *
  26391. * See the [Component & Container Guide](#!/guide/components) for more information, and check out the
  26392. * {@link Ext.Container} class docs also.
  26393. *
  26394. * @aside guide components
  26395. * @aside guide layouts
  26396. */
  26397. Ext.define('Ext.Container', {
  26398. extend: 'Ext.Component',
  26399. alternateClassName: 'Ext.lib.Container',
  26400. requires: [
  26401. 'Ext.layout.*',
  26402. 'Ext.ItemCollection',
  26403. 'Ext.behavior.Scrollable',
  26404. 'Ext.Mask'
  26405. ],
  26406. xtype: 'container',
  26407. /**
  26408. * @event add
  26409. * Fires whenever item added to the Container.
  26410. * @param {Ext.Container} this The Container instance.
  26411. * @param {Object} item The item added to the Container.
  26412. * @param {Number} index The index of the item within the Container.
  26413. */
  26414. /**
  26415. * @event remove
  26416. * Fires whenever item removed from the Container.
  26417. * @param {Ext.Container} this The Container instance.
  26418. * @param {Object} item The item removed from the Container.
  26419. * @param {Number} index The index of the item that was removed.
  26420. */
  26421. /**
  26422. * @event move
  26423. * Fires whenever item moved within the Container.
  26424. * @param {Ext.Container} this The Container instance.
  26425. * @param {Object} item The item moved within the Container.
  26426. * @param {Number} toIndex The new index of the item.
  26427. * @param {Number} fromIndex The old index of the item.
  26428. */
  26429. /**
  26430. * @private
  26431. * @event renderedchange
  26432. * Fires whenever an item is rendered into a container or derendered
  26433. * from a Container.
  26434. * @param {Ext.Container} this The Container instance.
  26435. * @param {Object} item The item in the Container.
  26436. * @param {Boolean} rendered The current rendered status of the item.
  26437. */
  26438. /**
  26439. * @event activate
  26440. * Fires whenever item within the Container is activated.
  26441. * @param {Ext.Container} this The Container instance.
  26442. * @param {Object} newActiveItem The new active item within the container.
  26443. * @param {Object} oldActiveItem The old active item within the container.
  26444. */
  26445. /**
  26446. * @event deactivate
  26447. * Fires whenever item within the Container is deactivated.
  26448. * @param {Ext.Container} this The Container instance.
  26449. * @param {Object} newActiveItem The new active item within the container.
  26450. * @param {Object} oldActiveItem The old active item within the container.
  26451. */
  26452. eventedConfig: {
  26453. /**
  26454. * @cfg {Object/String/Number} activeItem The item from the {@link #cfg-items} collection that will be active first. This is
  26455. * usually only meaningful in a {@link Ext.layout.Card card layout}, where only one item can be active at a
  26456. * time. If passes a string, it will be assumed to be a {@link Ext.ComponentQuery} selector.
  26457. * @accessor
  26458. * @evented
  26459. */
  26460. activeItem: 0,
  26461. /**
  26462. * @cfg {Boolean/String/Object} scrollable
  26463. * Configuration options to make this Container scrollable. Acceptable values are:
  26464. *
  26465. * - `'horizontal'`, `'vertical'`, `'both'` to enabling scrolling for that direction.
  26466. * - `true`/`false` to explicitly enable/disable scrolling.
  26467. *
  26468. * Alternatively, you can give it an object which is then passed to the scroller instance:
  26469. *
  26470. * scrollable: {
  26471. * direction: 'vertical',
  26472. * directionLock: true
  26473. * }
  26474. *
  26475. * Please look at the {@link Ext.scroll.Scroller} documentation for more example on how to use this.
  26476. * @accessor
  26477. * @evented
  26478. */
  26479. scrollable: null
  26480. },
  26481. config: {
  26482. /**
  26483. * @cfg {String/Object/Boolean} cardSwitchAnimation
  26484. * Animation to be used during transitions of cards.
  26485. * @removed 2.0.0 Please use {@link Ext.layout.Card#animation} instead
  26486. */
  26487. /**
  26488. * @cfg {Object/String} layout Configuration for this Container's layout. Example:
  26489. *
  26490. * Ext.create('Ext.Container', {
  26491. * layout: {
  26492. * type: 'hbox',
  26493. * align: 'middle'
  26494. * },
  26495. * items: [
  26496. * {
  26497. * xtype: 'panel',
  26498. * flex: 1,
  26499. * style: 'background-color: red;'
  26500. * },
  26501. * {
  26502. * xtype: 'panel',
  26503. * flex: 2,
  26504. * style: 'background-color: green'
  26505. * }
  26506. * ]
  26507. * });
  26508. *
  26509. * See the [Layouts Guide](#!/guide/layouts) for more information.
  26510. *
  26511. * @accessor
  26512. */
  26513. layout: null,
  26514. /**
  26515. * @cfg {Object} control Enables you to easily control Components inside this Container by listening to their
  26516. * events and taking some action. For example, if we had a container with a nested Disable button, and we
  26517. * wanted to hide the Container when the Disable button is tapped, we could do this:
  26518. *
  26519. * Ext.create('Ext.Container', {
  26520. * control: {
  26521. * 'button[text=Disable]': {
  26522. * tap: 'hideMe'
  26523. * }
  26524. * },
  26525. *
  26526. * hideMe: function () {
  26527. * this.hide();
  26528. * }
  26529. * });
  26530. *
  26531. * We used a {@link Ext.ComponentQuery} selector to listen to the {@link Ext.Button#tap tap} event on any
  26532. * {@link Ext.Button button} anywhere inside the Container that has the {@link Ext.Button#text text} 'Disable'.
  26533. * Whenever a Component matching that selector fires the `tap` event our `hideMe` function is called. `hideMe` is
  26534. * called with scope: `this` (e.g. `this` is the Container instance).
  26535. *
  26536. */
  26537. control: {},
  26538. /**
  26539. * @cfg {Object} defaults A set of default configurations to apply to all child Components in this Container.
  26540. * It's often useful to specify defaults when creating more than one items with similar configurations. For
  26541. * example here we can specify that each child is a panel and avoid repeating the xtype declaration for each
  26542. * one:
  26543. *
  26544. * Ext.create('Ext.Container', {
  26545. * defaults: {
  26546. * xtype: 'panel'
  26547. * },
  26548. * items: [
  26549. * {
  26550. * html: 'Panel 1'
  26551. * },
  26552. * {
  26553. * html: 'Panel 2'
  26554. * }
  26555. * ]
  26556. * });
  26557. *
  26558. * @accessor
  26559. */
  26560. defaults: null,
  26561. /**
  26562. * @cfg {Array/Object} items The child items to add to this Container. This is usually an array of Component
  26563. * configurations or instances, for example:
  26564. *
  26565. * Ext.create('Ext.Container', {
  26566. * items: [
  26567. * {
  26568. * xtype: 'panel',
  26569. * html: 'This is an item'
  26570. * }
  26571. * ]
  26572. * });
  26573. * @accessor
  26574. */
  26575. items: null,
  26576. /**
  26577. * @cfg {Boolean} autoDestroy If `true`, child items will be destroyed as soon as they are {@link #method-remove removed}
  26578. * from this container.
  26579. * @accessor
  26580. */
  26581. autoDestroy: true,
  26582. /** @cfg {String} defaultType
  26583. * The default {@link Ext.Component xtype} of child Components to create in this Container when a child item
  26584. * is specified as a raw configuration object, rather than as an instantiated Component.
  26585. * @accessor
  26586. */
  26587. defaultType: null,
  26588. //@private
  26589. useBodyElement: null,
  26590. /**
  26591. * @cfg {Boolean/Object/Ext.Mask/Ext.LoadMask} masked
  26592. * A configuration to allow you to mask this container.
  26593. * You can optionally pass an object block with and xtype of `loadmask`, and an optional `message` value to
  26594. * display a loading mask. Please refer to the {@link Ext.LoadMask} component to see other configurations.
  26595. *
  26596. * masked: {
  26597. * xtype: 'loadmask',
  26598. * message: 'My message'
  26599. * }
  26600. *
  26601. * Alternatively, you can just call the setter at any time with `true`/`false` to show/hide the mask:
  26602. *
  26603. * setMasked(true); //show the mask
  26604. * setMasked(false); //hides the mask
  26605. *
  26606. * There are also two convenient methods, {@link #method-mask} and {@link #unmask}, to allow you to mask and unmask
  26607. * this container at any time.
  26608. *
  26609. * Remember, the {@link Ext.Viewport} is always a container, so if you want to mask your whole application at anytime,
  26610. * can call:
  26611. *
  26612. * Ext.Viewport.setMasked({
  26613. * xtype: 'loadmask',
  26614. * message: 'Hello'
  26615. * });
  26616. *
  26617. * @accessor
  26618. */
  26619. masked: null,
  26620. /**
  26621. * @cfg {Boolean} modal `true` to make this Container modal. This will create a mask underneath the Container
  26622. * that covers its parent and does not allow the user to interact with any other Components until this
  26623. * Container is dismissed.
  26624. * @accessor
  26625. */
  26626. modal: null,
  26627. /**
  26628. * @cfg {Boolean} hideOnMaskTap When using a {@link #modal} Component, setting this to `true` will hide the modal
  26629. * mask and the Container when the mask is tapped on.
  26630. * @accessor
  26631. */
  26632. hideOnMaskTap: null
  26633. },
  26634. isContainer: true,
  26635. constructor: function(config) {
  26636. var me = this;
  26637. me._items = me.items = new Ext.ItemCollection();
  26638. me.innerItems = [];
  26639. me.onItemAdd = me.onFirstItemAdd;
  26640. me.callParent(arguments);
  26641. },
  26642. getElementConfig: function() {
  26643. return {
  26644. reference: 'element',
  26645. classList: ['x-container', 'x-unsized'],
  26646. children: [{
  26647. reference: 'innerElement',
  26648. className: 'x-inner'
  26649. }]
  26650. };
  26651. },
  26652. /**
  26653. * Changes the {@link #masked} configuration when its setter is called, which will convert the value
  26654. * into a proper object/instance of {@link Ext.Mask}/{@link Ext.LoadMask}. If a mask already exists,
  26655. * it will use that instead.
  26656. * @param masked
  26657. * @param currentMask
  26658. * @return {Object}
  26659. */
  26660. applyMasked: function(masked, currentMask) {
  26661. var isVisible = true;
  26662. if (masked === false) {
  26663. masked = true;
  26664. isVisible = false;
  26665. }
  26666. currentMask = Ext.factory(masked, Ext.Mask, currentMask);
  26667. if (currentMask) {
  26668. this.add(currentMask);
  26669. currentMask.setHidden(!isVisible);
  26670. }
  26671. return currentMask;
  26672. },
  26673. /**
  26674. * Convenience method which calls {@link #setMasked} with a value of `true` (to show the mask). For additional
  26675. * functionality, call the {@link #setMasked} function direction (See the {@link #masked} configuration documentation
  26676. * for more information).
  26677. */
  26678. mask: function(mask) {
  26679. this.setMasked(mask || true);
  26680. },
  26681. /**
  26682. * Convenience method which calls {@link #setMasked} with a value of false (to hide the mask). For additional
  26683. * functionality, call the {@link #setMasked} function direction (See the {@link #masked} configuration documentation
  26684. * for more information).
  26685. */
  26686. unmask: function() {
  26687. this.setMasked(false);
  26688. },
  26689. setParent: function(container) {
  26690. this.callSuper(arguments);
  26691. if (container) {
  26692. var modal = this.getModal();
  26693. if (modal) {
  26694. container.insertBefore(modal, this);
  26695. modal.setZIndex(this.getZIndex() - 1);
  26696. }
  26697. }
  26698. },
  26699. applyModal: function(modal, currentModal) {
  26700. var isVisible = true;
  26701. if (modal === false) {
  26702. modal = true;
  26703. isVisible = false;
  26704. }
  26705. currentModal = Ext.factory(modal, Ext.Mask, currentModal);
  26706. if (currentModal) {
  26707. currentModal.setVisibility(isVisible);
  26708. }
  26709. return currentModal;
  26710. },
  26711. updateModal: function(modal) {
  26712. var container = this.getParent();
  26713. if (container) {
  26714. if (modal) {
  26715. container.insertBefore(modal, this);
  26716. modal.setZIndex(this.getZIndex() - 1);
  26717. }
  26718. else {
  26719. container.remove(modal);
  26720. }
  26721. }
  26722. },
  26723. updateHideOnMaskTap : function(hide) {
  26724. var mask = this.getModal();
  26725. if (mask) {
  26726. mask[hide ? 'on' : 'un'].call(mask, 'tap', 'hide', this);
  26727. }
  26728. },
  26729. updateZIndex: function(zIndex) {
  26730. var modal = this.getModal();
  26731. this.callParent(arguments);
  26732. if (modal) {
  26733. modal.setZIndex(zIndex - 1);
  26734. }
  26735. },
  26736. updateBaseCls: function(newBaseCls, oldBaseCls) {
  26737. var me = this,
  26738. ui = me.getUi();
  26739. if (newBaseCls) {
  26740. this.element.addCls(newBaseCls);
  26741. this.innerElement.addCls(newBaseCls, null, 'inner');
  26742. if (ui) {
  26743. this.element.addCls(newBaseCls, null, ui);
  26744. }
  26745. }
  26746. if (oldBaseCls) {
  26747. this.element.removeCls(oldBaseCls);
  26748. this.innerElement.removeCls(newBaseCls, null, 'inner');
  26749. if (ui) {
  26750. this.element.removeCls(oldBaseCls, null, ui);
  26751. }
  26752. }
  26753. },
  26754. updateUseBodyElement: function(useBodyElement) {
  26755. if (useBodyElement) {
  26756. this.link('bodyElement', this.innerElement.wrap({
  26757. cls: 'x-body'
  26758. }));
  26759. }
  26760. },
  26761. applyItems: function(items, collection) {
  26762. if (items) {
  26763. var me = this;
  26764. me.getDefaultType();
  26765. me.getDefaults();
  26766. if (me.initialized && collection.length > 0) {
  26767. me.removeAll();
  26768. }
  26769. me.add(items);
  26770. //Don't need to call setActiveItem when Container is first initialized
  26771. if (me.initialized) {
  26772. var activeItem = me.initialConfig.activeItem || me.config.activeItem || 0;
  26773. me.setActiveItem(activeItem);
  26774. }
  26775. }
  26776. },
  26777. /**
  26778. * @private
  26779. */
  26780. applyControl: function(selectors) {
  26781. var selector, key, listener, listeners;
  26782. for (selector in selectors) {
  26783. listeners = selectors[selector];
  26784. for (key in listeners) {
  26785. listener = listeners[key];
  26786. if (Ext.isObject(listener)) {
  26787. listener.delegate = selector;
  26788. }
  26789. }
  26790. listeners.delegate = selector;
  26791. this.addListener(listeners);
  26792. }
  26793. return selectors;
  26794. },
  26795. /**
  26796. * Initialize layout and event listeners the very first time an item is added
  26797. * @private
  26798. */
  26799. onFirstItemAdd: function() {
  26800. delete this.onItemAdd;
  26801. if (this.innerHtmlElement && !this.getHtml()) {
  26802. this.innerHtmlElement.destroy();
  26803. delete this.innerHtmlElement;
  26804. }
  26805. this.on('innerstatechange', 'onItemInnerStateChange', this, {
  26806. delegate: '> component'
  26807. });
  26808. return this.onItemAdd.apply(this, arguments);
  26809. },
  26810. //<debug error>
  26811. updateLayout: function(newLayout, oldLayout) {
  26812. if (oldLayout && oldLayout.isLayout) {
  26813. Ext.Logger.error('Replacing a layout after one has already been initialized is not currently supported.');
  26814. }
  26815. },
  26816. //</debug>
  26817. getLayout: function() {
  26818. var layout = this.layout;
  26819. if (!layout) {
  26820. layout = this.link('_layout', this.link('layout', Ext.factory(this._layout || 'default', Ext.layout.Default, null, 'layout')));
  26821. layout.setContainer(this);
  26822. }
  26823. return layout;
  26824. },
  26825. updateDefaultType: function(defaultType) {
  26826. // Cache the direct reference to the default item class here for performance
  26827. this.defaultItemClass = Ext.ClassManager.getByAlias('widget.' + defaultType);
  26828. //<debug error>
  26829. if (!this.defaultItemClass) {
  26830. Ext.Logger.error("Invalid defaultType of: '" + defaultType + "', must be a valid component xtype");
  26831. }
  26832. //</debug>
  26833. },
  26834. applyDefaults: function(defaults) {
  26835. if (defaults) {
  26836. this.factoryItem = this.factoryItemWithDefaults;
  26837. return defaults;
  26838. }
  26839. },
  26840. factoryItem: function(item) {
  26841. //<debug error>
  26842. if (!item) {
  26843. Ext.Logger.error("Invalid item given: " + item + ", must be either the config object to factory a new item, " +
  26844. "or an existing component instance");
  26845. }
  26846. //</debug>
  26847. return Ext.factory(item, this.defaultItemClass);
  26848. },
  26849. factoryItemWithDefaults: function(item) {
  26850. //<debug error>
  26851. if (!item) {
  26852. Ext.Logger.error("Invalid item given: " + item + ", must be either the config object to factory a new item, " +
  26853. "or an existing component instance");
  26854. }
  26855. //</debug>
  26856. var me = this,
  26857. defaults = me.getDefaults(),
  26858. instance;
  26859. if (!defaults) {
  26860. return Ext.factory(item, me.defaultItemClass);
  26861. }
  26862. // Existing instance
  26863. if (item.isComponent) {
  26864. instance = item;
  26865. // Apply defaults only if this is not already an item of this container
  26866. if (defaults && item.isInnerItem() && !me.has(instance)) {
  26867. instance.setConfig(defaults, true);
  26868. }
  26869. }
  26870. // Config object
  26871. else {
  26872. if (defaults && !item.ignoreDefaults) {
  26873. // Note:
  26874. // - defaults is only applied to inner items
  26875. // - we merge the given config together with defaults into a new object so that the original object stays intact
  26876. if (!(
  26877. item.hasOwnProperty('left') &&
  26878. item.hasOwnProperty('right') &&
  26879. item.hasOwnProperty('top') &&
  26880. item.hasOwnProperty('bottom') &&
  26881. item.hasOwnProperty('docked') &&
  26882. item.hasOwnProperty('centered')
  26883. )) {
  26884. item = Ext.mergeIf({}, item, defaults);
  26885. }
  26886. }
  26887. instance = Ext.factory(item, me.defaultItemClass);
  26888. }
  26889. return instance;
  26890. },
  26891. /**
  26892. * Adds one or more Components to this Container. Example:
  26893. *
  26894. * var myPanel = Ext.create('Ext.Panel', {
  26895. * html: 'This will be added to a Container'
  26896. * });
  26897. *
  26898. * myContainer.add([myPanel]);
  26899. *
  26900. * @param {Object/Object[]/Ext.Component/Ext.Component[]} newItems The new items to add to the Container.
  26901. * @return {Ext.Component} The last item added to the Container from the `newItems` array.
  26902. */
  26903. add: function(newItems) {
  26904. var me = this,
  26905. i, ln, item, newActiveItem;
  26906. newItems = Ext.Array.from(newItems);
  26907. ln = newItems.length;
  26908. for (i = 0; i < ln; i++) {
  26909. item = me.factoryItem(newItems[i]);
  26910. this.doAdd(item);
  26911. if (!newActiveItem && !this.getActiveItem() && this.innerItems.length > 0 && item.isInnerItem()) {
  26912. newActiveItem = item;
  26913. }
  26914. }
  26915. if (newActiveItem) {
  26916. this.setActiveItem(newActiveItem);
  26917. }
  26918. return item;
  26919. },
  26920. /**
  26921. * @private
  26922. * @param item
  26923. */
  26924. doAdd: function(item) {
  26925. var me = this,
  26926. items = me.getItems(),
  26927. index;
  26928. if (!items.has(item)) {
  26929. index = items.length;
  26930. items.add(item);
  26931. if (item.isInnerItem()) {
  26932. me.insertInner(item);
  26933. }
  26934. item.setParent(me);
  26935. me.onItemAdd(item, index);
  26936. }
  26937. },
  26938. /**
  26939. * Removes an item from this Container, optionally destroying it.
  26940. * @param {Object} item The item to remove.
  26941. * @param {Boolean} destroy Calls the Component's {@link Ext.Component#destroy destroy} method if `true`.
  26942. * @return {Ext.Component} this
  26943. */
  26944. remove: function(item, destroy) {
  26945. var me = this,
  26946. index = me.indexOf(item),
  26947. innerItems = me.getInnerItems();
  26948. if (destroy === undefined) {
  26949. destroy = me.getAutoDestroy();
  26950. }
  26951. if (index !== -1) {
  26952. if (!me.removingAll && innerItems.length > 1 && item === me.getActiveItem()) {
  26953. me.on({
  26954. activeitemchange: 'doRemove',
  26955. scope: me,
  26956. single: true,
  26957. order: 'after',
  26958. args: [item, index, destroy]
  26959. });
  26960. me.doResetActiveItem(innerItems.indexOf(item));
  26961. }
  26962. else {
  26963. me.doRemove(item, index, destroy);
  26964. if (innerItems.length === 0) {
  26965. me.setActiveItem(null);
  26966. }
  26967. }
  26968. }
  26969. return me;
  26970. },
  26971. doResetActiveItem: function(innerIndex) {
  26972. if (innerIndex === 0) {
  26973. this.setActiveItem(1);
  26974. }
  26975. else {
  26976. this.setActiveItem(0);
  26977. }
  26978. },
  26979. doRemove: function(item, index, destroy) {
  26980. var me = this;
  26981. me.items.remove(item);
  26982. if (item.isInnerItem()) {
  26983. me.removeInner(item);
  26984. }
  26985. me.onItemRemove(item, index, destroy);
  26986. item.setParent(null);
  26987. if (destroy) {
  26988. item.destroy();
  26989. }
  26990. },
  26991. /**
  26992. * Removes all items currently in the Container, optionally destroying them all.
  26993. * @param {Boolean} destroy If `true`, {@link Ext.Component#destroy destroys} each removed Component.
  26994. * @param {Boolean} everything If `true`, completely remove all items including docked / centered and floating items.
  26995. * @return {Ext.Component} this
  26996. */
  26997. removeAll: function(destroy, everything) {
  26998. var items = this.items,
  26999. ln = items.length,
  27000. i = 0,
  27001. item;
  27002. if (destroy === undefined) {
  27003. destroy = this.getAutoDestroy();
  27004. }
  27005. everything = Boolean(everything);
  27006. // removingAll flag is used so we don't unnecessarily change activeItem while removing all items.
  27007. this.removingAll = true;
  27008. for (; i < ln; i++) {
  27009. item = items.getAt(i);
  27010. if (item && (everything || item.isInnerItem())) {
  27011. this.doRemove(item, i, destroy);
  27012. i--;
  27013. ln--;
  27014. }
  27015. }
  27016. this.setActiveItem(null);
  27017. this.removingAll = false;
  27018. return this;
  27019. },
  27020. /**
  27021. * Returns the Component for a given index in the Container's {@link #property-items}.
  27022. * @param {Number} index The index of the Component to return.
  27023. * @return {Ext.Component} The item at the specified `index`, if found.
  27024. */
  27025. getAt: function(index) {
  27026. return this.items.getAt(index);
  27027. },
  27028. getInnerAt: function(index) {
  27029. return this.innerItems[index];
  27030. },
  27031. /**
  27032. * Removes the Component at the specified index:
  27033. *
  27034. * myContainer.removeAt(0); // removes the first item
  27035. *
  27036. * @param {Number} index The index of the Component to remove.
  27037. */
  27038. removeAt: function(index) {
  27039. var item = this.getAt(index);
  27040. if (item) {
  27041. this.remove(item);
  27042. }
  27043. return this;
  27044. },
  27045. /**
  27046. * Removes an inner Component at the specified index:
  27047. *
  27048. * myContainer.removeInnerAt(0); // removes the first item of the innerItems property
  27049. *
  27050. * @param {Number} index The index of the Component to remove.
  27051. */
  27052. removeInnerAt: function(index) {
  27053. var item = this.getInnerItems()[index];
  27054. if (item) {
  27055. this.remove(item);
  27056. }
  27057. return this;
  27058. },
  27059. /**
  27060. * @private
  27061. */
  27062. has: function(item) {
  27063. return this.getItems().indexOf(item) != -1;
  27064. },
  27065. /**
  27066. * @private
  27067. */
  27068. hasInnerItem: function(item) {
  27069. return this.innerItems.indexOf(item) != -1;
  27070. },
  27071. /**
  27072. * @private
  27073. */
  27074. indexOf: function(item) {
  27075. return this.getItems().indexOf(item);
  27076. },
  27077. innerIndexOf: function(item) {
  27078. return this.innerItems.indexOf(item);
  27079. },
  27080. /**
  27081. * @private
  27082. * @param item
  27083. * @param index
  27084. */
  27085. insertInner: function(item, index) {
  27086. var items = this.getItems().items,
  27087. innerItems = this.innerItems,
  27088. currentInnerIndex = innerItems.indexOf(item),
  27089. newInnerIndex = -1,
  27090. nextSibling;
  27091. if (currentInnerIndex !== -1) {
  27092. innerItems.splice(currentInnerIndex, 1);
  27093. }
  27094. if (typeof index == 'number') {
  27095. do {
  27096. nextSibling = items[++index];
  27097. } while (nextSibling && !nextSibling.isInnerItem());
  27098. if (nextSibling) {
  27099. newInnerIndex = innerItems.indexOf(nextSibling);
  27100. innerItems.splice(newInnerIndex, 0, item);
  27101. }
  27102. }
  27103. if (newInnerIndex === -1) {
  27104. innerItems.push(item);
  27105. newInnerIndex = innerItems.length - 1;
  27106. }
  27107. if (currentInnerIndex !== -1) {
  27108. this.onInnerItemMove(item, newInnerIndex, currentInnerIndex);
  27109. }
  27110. return this;
  27111. },
  27112. onInnerItemMove: Ext.emptyFn,
  27113. /**
  27114. * @private
  27115. * @param item
  27116. */
  27117. removeInner: function(item) {
  27118. Ext.Array.remove(this.innerItems, item);
  27119. return this;
  27120. },
  27121. /**
  27122. * Adds a child Component at the given index. For example, here's how we can add a new item, making it the first
  27123. * child Component of this Container:
  27124. *
  27125. * myContainer.insert(0, {xtype: 'panel', html: 'new item'});
  27126. *
  27127. * @param {Number} index The index to insert the Component at.
  27128. * @param {Object} item The Component to insert.
  27129. */
  27130. insert: function(index, item) {
  27131. var me = this,
  27132. i;
  27133. if (Ext.isArray(item)) {
  27134. for (i = item.length - 1; i >= 0; i--) {
  27135. me.insert(index, item[i]);
  27136. }
  27137. return me;
  27138. }
  27139. item = this.factoryItem(item);
  27140. this.doInsert(index, item);
  27141. return item;
  27142. },
  27143. /**
  27144. * @private
  27145. * @param index
  27146. * @param item
  27147. */
  27148. doInsert: function(index, item) {
  27149. var me = this,
  27150. items = me.items,
  27151. itemsLength = items.length,
  27152. currentIndex, isInnerItem;
  27153. isInnerItem = item.isInnerItem();
  27154. if (index > itemsLength) {
  27155. index = itemsLength;
  27156. }
  27157. if (items[index - 1] === item) {
  27158. return me;
  27159. }
  27160. currentIndex = me.indexOf(item);
  27161. if (currentIndex !== -1) {
  27162. if (currentIndex < index) {
  27163. index -= 1;
  27164. }
  27165. items.removeAt(currentIndex);
  27166. }
  27167. else {
  27168. item.setParent(me);
  27169. }
  27170. items.insert(index, item);
  27171. if (isInnerItem) {
  27172. me.insertInner(item, index);
  27173. }
  27174. if (currentIndex !== -1) {
  27175. me.onItemMove(item, index, currentIndex);
  27176. }
  27177. else {
  27178. me.onItemAdd(item, index);
  27179. }
  27180. },
  27181. /**
  27182. * @private
  27183. */
  27184. insertFirst: function(item) {
  27185. return this.insert(0, item);
  27186. },
  27187. /**
  27188. * @private
  27189. */
  27190. insertLast: function(item) {
  27191. return this.insert(this.getItems().length, item);
  27192. },
  27193. /**
  27194. * @private
  27195. */
  27196. insertBefore: function(item, relativeToItem) {
  27197. var index = this.indexOf(relativeToItem);
  27198. if (index !== -1) {
  27199. this.insert(index, item);
  27200. }
  27201. return this;
  27202. },
  27203. /**
  27204. * @private
  27205. */
  27206. insertAfter: function(item, relativeToItem) {
  27207. var index = this.indexOf(relativeToItem);
  27208. if (index !== -1) {
  27209. this.insert(index + 1, item);
  27210. }
  27211. return this;
  27212. },
  27213. /**
  27214. * @private
  27215. */
  27216. onItemAdd: function(item, index) {
  27217. this.doItemLayoutAdd(item, index);
  27218. if (this.initialized) {
  27219. this.fireEvent('add', this, item, index);
  27220. }
  27221. },
  27222. doItemLayoutAdd: function(item, index) {
  27223. var layout = this.getLayout();
  27224. if (this.isRendered() && item.setRendered(true)) {
  27225. item.fireAction('renderedchange', [this, item, true], 'onItemAdd', layout, { args: [item, index] });
  27226. }
  27227. else {
  27228. layout.onItemAdd(item, index);
  27229. }
  27230. },
  27231. /**
  27232. * @private
  27233. */
  27234. onItemRemove: function(item, index) {
  27235. this.doItemLayoutRemove(item, index);
  27236. this.fireEvent('remove', this, item, index);
  27237. },
  27238. doItemLayoutRemove: function(item, index) {
  27239. var layout = this.getLayout();
  27240. if (this.isRendered() && item.setRendered(false)) {
  27241. item.fireAction('renderedchange', [this, item, false], 'onItemRemove', layout, { args: [item, index, undefined] });
  27242. }
  27243. else {
  27244. layout.onItemRemove(item, index);
  27245. }
  27246. },
  27247. /**
  27248. * @private
  27249. */
  27250. onItemMove: function(item, toIndex, fromIndex) {
  27251. if (item.isDocked()) {
  27252. item.setDocked(null);
  27253. }
  27254. this.doItemLayoutMove(item, toIndex, fromIndex);
  27255. this.fireEvent('move', this, item, toIndex, fromIndex);
  27256. },
  27257. doItemLayoutMove: function(item, toIndex, fromIndex) {
  27258. this.getLayout().onItemMove(item, toIndex, fromIndex);
  27259. },
  27260. onItemInnerStateChange: function(item, isInner) {
  27261. var layout = this.getLayout();
  27262. if (isInner) {
  27263. this.insertInner(item, this.items.indexOf(item));
  27264. }
  27265. else {
  27266. this.removeInner(item);
  27267. }
  27268. layout.onItemInnerStateChange.apply(layout, arguments);
  27269. },
  27270. /**
  27271. * Returns all inner {@link #property-items} of this container. `inner` means that the item is not `docked` or
  27272. * `floating`.
  27273. * @return {Array} The inner items of this container.
  27274. */
  27275. getInnerItems: function() {
  27276. return this.innerItems;
  27277. },
  27278. /**
  27279. * Returns all the {@link Ext.Component#docked} items in this container.
  27280. * @return {Array} The docked items of this container.
  27281. */
  27282. getDockedItems: function() {
  27283. var items = this.getItems().items,
  27284. dockedItems = [],
  27285. ln = items.length,
  27286. item, i;
  27287. for (i = 0; i < ln; i++) {
  27288. item = items[i];
  27289. if (item.isDocked()) {
  27290. dockedItems.push(item);
  27291. }
  27292. }
  27293. return dockedItems;
  27294. },
  27295. /**
  27296. * @private
  27297. */
  27298. applyActiveItem: function(activeItem, currentActiveItem) {
  27299. var innerItems = this.getInnerItems();
  27300. // Make sure the items are already initialized
  27301. this.getItems();
  27302. // No items left to be active, reset back to 0 on falsy changes
  27303. if (!activeItem && innerItems.length === 0) {
  27304. return 0;
  27305. }
  27306. else if (typeof activeItem == 'number') {
  27307. activeItem = Math.max(0, Math.min(activeItem, innerItems.length - 1));
  27308. activeItem = innerItems[activeItem];
  27309. if (activeItem) {
  27310. return activeItem;
  27311. }
  27312. else if (currentActiveItem) {
  27313. return null;
  27314. }
  27315. }
  27316. else if (activeItem) {
  27317. var item;
  27318. //ComponentQuery selector?
  27319. if (typeof activeItem == 'string') {
  27320. item = this.child(activeItem);
  27321. activeItem = {
  27322. xtype : activeItem
  27323. };
  27324. }
  27325. if (!item || !item.isComponent) {
  27326. item = this.factoryItem(activeItem);
  27327. }
  27328. this.pendingActiveItem = item;
  27329. //<debug error>
  27330. if (!item.isInnerItem()) {
  27331. Ext.Logger.error("Setting activeItem to be a non-inner item");
  27332. }
  27333. //</debug>
  27334. if (!this.has(item)) {
  27335. this.add(item);
  27336. }
  27337. return item;
  27338. }
  27339. },
  27340. /**
  27341. * Animates to the supplied `activeItem` with a specified animation. Currently this only works
  27342. * with a Card layout. This passed animation will override any default animations on the
  27343. * container, for a single card switch. The animation will be destroyed when complete.
  27344. * @param {Object/Number} activeItem The item or item index to make active.
  27345. * @param {Object/Ext.fx.layout.Card} animation Card animation configuration or instance.
  27346. */
  27347. animateActiveItem: function(activeItem, animation) {
  27348. var layout = this.getLayout(),
  27349. defaultAnimation;
  27350. if (this.activeItemAnimation) {
  27351. this.activeItemAnimation.destroy();
  27352. }
  27353. this.activeItemAnimation = animation = new Ext.fx.layout.Card(animation);
  27354. if (animation && layout.isCard) {
  27355. animation.setLayout(layout);
  27356. defaultAnimation = layout.getAnimation();
  27357. if (defaultAnimation) {
  27358. defaultAnimation.disable();
  27359. animation.on('animationend', function() {
  27360. defaultAnimation.enable();
  27361. animation.destroy();
  27362. }, this);
  27363. }
  27364. }
  27365. return this.setActiveItem(activeItem);
  27366. },
  27367. /**
  27368. * @private
  27369. */
  27370. doSetActiveItem: function(newActiveItem, oldActiveItem) {
  27371. delete this.pendingActiveItem;
  27372. if (oldActiveItem) {
  27373. oldActiveItem.fireEvent('deactivate', oldActiveItem, this, newActiveItem);
  27374. }
  27375. if (newActiveItem) {
  27376. newActiveItem.fireEvent('activate', newActiveItem, this, oldActiveItem);
  27377. }
  27378. },
  27379. doSetHidden: function(hidden) {
  27380. var modal = this.getModal();
  27381. if (modal) {
  27382. modal.setHidden(hidden);
  27383. }
  27384. this.callSuper(arguments);
  27385. },
  27386. /**
  27387. * @private
  27388. */
  27389. setRendered: function(rendered) {
  27390. if (this.callParent(arguments)) {
  27391. var items = this.items.items,
  27392. i, ln;
  27393. for (i = 0,ln = items.length; i < ln; i++) {
  27394. items[i].setRendered(rendered);
  27395. }
  27396. return true;
  27397. }
  27398. return false;
  27399. },
  27400. /**
  27401. * @private
  27402. */
  27403. getScrollableBehavior: function() {
  27404. var behavior = this.scrollableBehavior;
  27405. if (!behavior) {
  27406. behavior = this.scrollableBehavior = new Ext.behavior.Scrollable(this);
  27407. }
  27408. return behavior;
  27409. },
  27410. /**
  27411. * @private
  27412. */
  27413. applyScrollable: function(config) {
  27414. if (config && !config.isObservable) {
  27415. this.getScrollableBehavior().setConfig(config);
  27416. }
  27417. return config;
  27418. },
  27419. doSetScrollable: function() {
  27420. // Used for plugins when they need to reinitialize scroller listeners
  27421. },
  27422. /**
  27423. * Returns an the scrollable instance for this container, which is a {@link Ext.scroll.View} class.
  27424. *
  27425. * Please checkout the documentation for {@link Ext.scroll.View}, {@link Ext.scroll.View#getScroller}
  27426. * and {@link Ext.scroll.Scroller} for more information.
  27427. * @return {Ext.scroll.View} The scroll view.
  27428. */
  27429. getScrollable: function() {
  27430. return this.getScrollableBehavior().getScrollView();
  27431. },
  27432. // Used by ComponentQuery to retrieve all of the items
  27433. // which can potentially be considered a child of this Container.
  27434. // This should be overridden by components which have child items
  27435. // that are not contained in items. For example `dockedItems`, `menu`, etc
  27436. // @private
  27437. getRefItems: function(deep) {
  27438. var items = this.getItems().items.slice(),
  27439. ln = items.length,
  27440. i, item;
  27441. if (deep) {
  27442. for (i = 0; i < ln; i++) {
  27443. item = items[i];
  27444. if (item.getRefItems) {
  27445. items = items.concat(item.getRefItems(true));
  27446. }
  27447. }
  27448. }
  27449. return items;
  27450. },
  27451. /**
  27452. * Examines this container's `{@link #property-items}` property
  27453. * and gets a direct child component of this container.
  27454. * @param {String/Number} component This parameter may be any of the following:
  27455. *
  27456. * - {String} : representing the `itemId`
  27457. * or `{@link Ext.Component#getId id}` of the child component.
  27458. * - {Number} : representing the position of the child component
  27459. * within the `{@link #property-items}` property.
  27460. *
  27461. * For additional information see {@link Ext.util.MixedCollection#get}.
  27462. * @return {Ext.Component} The component (if found).
  27463. */
  27464. getComponent: function(component) {
  27465. if (Ext.isObject(component)) {
  27466. component = component.getItemId();
  27467. }
  27468. return this.getItems().get(component);
  27469. },
  27470. /**
  27471. * Finds a docked item of this container using a reference, `id `or an `index` of its location
  27472. * in {@link #getDockedItems}.
  27473. * @param {String/Number} component The `id` or `index` of the component to find.
  27474. * @return {Ext.Component/Boolean} The docked component, if found.
  27475. */
  27476. getDockedComponent: function(component) {
  27477. if (Ext.isObject(component)) {
  27478. component = component.getItemId();
  27479. }
  27480. var dockedItems = this.getDockedItems(),
  27481. ln = dockedItems.length,
  27482. item, i;
  27483. if (Ext.isNumber(component)) {
  27484. return dockedItems[component];
  27485. }
  27486. for (i = 0; i < ln; i++) {
  27487. item = dockedItems[i];
  27488. if (item.id == component) {
  27489. return item;
  27490. }
  27491. }
  27492. return false;
  27493. },
  27494. /**
  27495. * Retrieves all descendant components which match the passed selector.
  27496. * Executes an Ext.ComponentQuery.query using this container as its root.
  27497. * @param {String} selector Selector complying to an Ext.ComponentQuery selector.
  27498. * @return {Array} Ext.Component's which matched the selector.
  27499. */
  27500. query: function(selector) {
  27501. return Ext.ComponentQuery.query(selector, this);
  27502. },
  27503. /**
  27504. * Retrieves the first direct child of this container which matches the passed selector.
  27505. * The passed in selector must comply with an {@link Ext.ComponentQuery} selector.
  27506. * @param {String} selector An {@link Ext.ComponentQuery} selector.
  27507. * @return {Ext.Component}
  27508. */
  27509. child: function(selector) {
  27510. return this.query('> ' + selector)[0] || null;
  27511. },
  27512. /**
  27513. * Retrieves the first descendant of this container which matches the passed selector.
  27514. * The passed in selector must comply with an {@link Ext.ComponentQuery} selector.
  27515. * @param {String} selector An {@link Ext.ComponentQuery} selector.
  27516. * @return {Ext.Component}
  27517. */
  27518. down: function(selector) {
  27519. return this.query(selector)[0] || null;
  27520. },
  27521. destroy: function() {
  27522. var me = this,
  27523. modal = me.getModal();
  27524. if (modal) {
  27525. modal.destroy();
  27526. }
  27527. me.removeAll(true, true);
  27528. me.unlink('_scrollable');
  27529. Ext.destroy(me.items);
  27530. me.callSuper();
  27531. }
  27532. }, function() {
  27533. this.addMember('defaultItemClass', this);
  27534. });
  27535. /**
  27536. * Represents a 2D point with x and y properties, useful for comparison and instantiation
  27537. * from an event:
  27538. *
  27539. * var point = Ext.util.Point.fromEvent(e);
  27540. */
  27541. Ext.define('Ext.util.Point', {
  27542. radianToDegreeConstant: 180 / Math.PI,
  27543. statics: {
  27544. /**
  27545. * Returns a new instance of {@link Ext.util.Point} based on the `pageX` / `pageY` values of the given event.
  27546. * @static
  27547. * @param {Event} e The event.
  27548. * @return {Ext.util.Point}
  27549. */
  27550. fromEvent: function(e) {
  27551. var changedTouches = e.changedTouches,
  27552. touch = (changedTouches && changedTouches.length > 0) ? changedTouches[0] : e;
  27553. return this.fromTouch(touch);
  27554. },
  27555. /**
  27556. * Returns a new instance of {@link Ext.util.Point} based on the `pageX` / `pageY` values of the given touch.
  27557. * @static
  27558. * @param {Event} touch
  27559. * @return {Ext.util.Point}
  27560. */
  27561. fromTouch: function(touch) {
  27562. return new this(touch.pageX, touch.pageY);
  27563. },
  27564. /**
  27565. * Returns a new point from an object that has `x` and `y` properties, if that object is not an instance
  27566. * of {@link Ext.util.Point}. Otherwise, returns the given point itself.
  27567. * @param object
  27568. * @return {Ext.util.Point}
  27569. */
  27570. from: function(object) {
  27571. if (!object) {
  27572. return new this(0, 0);
  27573. }
  27574. if (!(object instanceof this)) {
  27575. return new this(object.x, object.y);
  27576. }
  27577. return object;
  27578. }
  27579. },
  27580. /**
  27581. * Creates point on 2D plane.
  27582. * @param {Number} [x=0] X coordinate.
  27583. * @param {Number} [y=0] Y coordinate.
  27584. */
  27585. constructor: function(x, y) {
  27586. if (typeof x == 'undefined') {
  27587. x = 0;
  27588. }
  27589. if (typeof y == 'undefined') {
  27590. y = 0;
  27591. }
  27592. this.x = x;
  27593. this.y = y;
  27594. return this;
  27595. },
  27596. /**
  27597. * Copy a new instance of this point.
  27598. * @return {Ext.util.Point} The new point.
  27599. */
  27600. clone: function() {
  27601. return new this.self(this.x, this.y);
  27602. },
  27603. /**
  27604. * Clones this Point.
  27605. * @deprecated 2.0.0 Please use {@link #clone} instead.
  27606. * @return {Ext.util.Point} The new point.
  27607. */
  27608. copy: function() {
  27609. return this.clone.apply(this, arguments);
  27610. },
  27611. /**
  27612. * Copy the `x` and `y` values of another point / object to this point itself.
  27613. * @param {Ext.util.Point/Object} point.
  27614. * @return {Ext.util.Point} This point.
  27615. */
  27616. copyFrom: function(point) {
  27617. this.x = point.x;
  27618. this.y = point.y;
  27619. return this;
  27620. },
  27621. /**
  27622. * Returns a human-eye-friendly string that represents this point,
  27623. * useful for debugging.
  27624. * @return {String} For example `Point[12,8]`.
  27625. */
  27626. toString: function() {
  27627. return "Point[" + this.x + "," + this.y + "]";
  27628. },
  27629. /**
  27630. * Compare this point and another point.
  27631. * @param {Ext.util.Point/Object} The point to compare with, either an instance
  27632. * of {@link Ext.util.Point} or an object with `x` and `y` properties.
  27633. * @return {Boolean} Returns whether they are equivalent.
  27634. */
  27635. equals: function(point) {
  27636. return (this.x === point.x && this.y === point.y);
  27637. },
  27638. /**
  27639. * Whether the given point is not away from this point within the given threshold amount.
  27640. * @param {Ext.util.Point/Object} The point to check with, either an instance
  27641. * of {@link Ext.util.Point} or an object with `x` and `y` properties.
  27642. * @param {Object/Number} threshold Can be either an object with `x` and `y` properties or a number.
  27643. * @return {Boolean}
  27644. */
  27645. isCloseTo: function(point, threshold) {
  27646. if (typeof threshold == 'number') {
  27647. threshold = {x: threshold};
  27648. threshold.y = threshold.x;
  27649. }
  27650. var x = point.x,
  27651. y = point.y,
  27652. thresholdX = threshold.x,
  27653. thresholdY = threshold.y;
  27654. return (this.x <= x + thresholdX && this.x >= x - thresholdX &&
  27655. this.y <= y + thresholdY && this.y >= y - thresholdY);
  27656. },
  27657. /**
  27658. * Returns `true` if this point is close to another one.
  27659. * @deprecated 2.0.0 Please use {@link #isCloseTo} instead.
  27660. * @return {Boolean}
  27661. */
  27662. isWithin: function() {
  27663. return this.isCloseTo.apply(this, arguments);
  27664. },
  27665. /**
  27666. * Translate this point by the given amounts.
  27667. * @param {Number} x Amount to translate in the x-axis.
  27668. * @param {Number} y Amount to translate in the y-axis.
  27669. * @return {Boolean}
  27670. */
  27671. translate: function(x, y) {
  27672. this.x += x;
  27673. this.y += y;
  27674. return this;
  27675. },
  27676. /**
  27677. * Compare this point with another point when the `x` and `y` values of both points are rounded. For example:
  27678. * [100.3,199.8] will equals to [100, 200].
  27679. * @param {Ext.util.Point/Object} point The point to compare with, either an instance
  27680. * of Ext.util.Point or an object with `x` and `y` properties.
  27681. * @return {Boolean}
  27682. */
  27683. roundedEquals: function(point) {
  27684. return (Math.round(this.x) === Math.round(point.x) &&
  27685. Math.round(this.y) === Math.round(point.y));
  27686. },
  27687. getDistanceTo: function(point) {
  27688. var deltaX = this.x - point.x,
  27689. deltaY = this.y - point.y;
  27690. return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
  27691. },
  27692. getAngleTo: function(point) {
  27693. var deltaX = this.x - point.x,
  27694. deltaY = this.y - point.y;
  27695. return Math.atan2(deltaY, deltaX) * this.radianToDegreeConstant;
  27696. }
  27697. });
  27698. /**
  27699. * @class Ext.util.LineSegment
  27700. *
  27701. * Utility class that represents a line segment, constructed by two {@link Ext.util.Point}
  27702. */
  27703. Ext.define('Ext.util.LineSegment', {
  27704. requires: ['Ext.util.Point'],
  27705. /**
  27706. * Creates new LineSegment out of two points.
  27707. * @param {Ext.util.Point} point1
  27708. * @param {Ext.util.Point} point2
  27709. */
  27710. constructor: function(point1, point2) {
  27711. var Point = Ext.util.Point;
  27712. this.point1 = Point.from(point1);
  27713. this.point2 = Point.from(point2);
  27714. },
  27715. /**
  27716. * Returns the point where two lines intersect.
  27717. * @param {Ext.util.LineSegment} lineSegment The line to intersect with.
  27718. * @return {Ext.util.Point}
  27719. */
  27720. intersects: function(lineSegment) {
  27721. var point1 = this.point1,
  27722. point2 = this.point2,
  27723. point3 = lineSegment.point1,
  27724. point4 = lineSegment.point2,
  27725. x1 = point1.x,
  27726. x2 = point2.x,
  27727. x3 = point3.x,
  27728. x4 = point4.x,
  27729. y1 = point1.y,
  27730. y2 = point2.y,
  27731. y3 = point3.y,
  27732. y4 = point4.y,
  27733. d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4),
  27734. xi, yi;
  27735. if (d == 0) {
  27736. return null;
  27737. }
  27738. xi = ((x3 - x4) * (x1 * y2 - y1 * x2) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d;
  27739. yi = ((y3 - y4) * (x1 * y2 - y1 * x2) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d;
  27740. if (xi < Math.min(x1, x2) || xi > Math.max(x1, x2)
  27741. || xi < Math.min(x3, x4) || xi > Math.max(x3, x4)
  27742. || yi < Math.min(y1, y2) || yi > Math.max(y1, y2)
  27743. || yi < Math.min(y3, y4) || yi > Math.max(y3, y4)) {
  27744. return null;
  27745. }
  27746. return new Ext.util.Point(xi, yi);
  27747. },
  27748. /**
  27749. * Returns string representation of the line. Useful for debugging.
  27750. * @return {String} For example `Point[12,8] Point[0,0]`
  27751. */
  27752. toString: function() {
  27753. return this.point1.toString() + " " + this.point2.toString();
  27754. }
  27755. });
  27756. /**
  27757. * @aside guide floating_components
  27758. *
  27759. * Panels are most useful as Overlays - containers that float over your application. They contain extra styling such
  27760. * that when you {@link #showBy} another component, the container will appear in a rounded black box with a 'tip'
  27761. * pointing to a reference component.
  27762. *
  27763. * If you don't need this extra functionality, you should use {@link Ext.Container} instead. See the
  27764. * [Overlays example](#!/example/overlays) for more use cases.
  27765. *
  27766. * @example miniphone preview
  27767. *
  27768. * var button = Ext.create('Ext.Button', {
  27769. * text: 'Button',
  27770. * id: 'rightButton'
  27771. * });
  27772. *
  27773. * Ext.create('Ext.Container', {
  27774. * fullscreen: true,
  27775. * items: [
  27776. * {
  27777. * docked: 'top',
  27778. * xtype: 'titlebar',
  27779. * items: [
  27780. * button
  27781. * ]
  27782. * }
  27783. * ]
  27784. * });
  27785. *
  27786. * Ext.create('Ext.Panel', {
  27787. * html: 'Floating Panel',
  27788. * left: 0,
  27789. * padding: 10
  27790. * }).showBy(button);
  27791. *
  27792. */
  27793. Ext.define('Ext.Panel', {
  27794. extend: 'Ext.Container',
  27795. requires: ['Ext.util.LineSegment'],
  27796. alternateClassName: 'Ext.lib.Panel',
  27797. xtype: 'panel',
  27798. isPanel: true,
  27799. config: {
  27800. baseCls: Ext.baseCSSPrefix + 'panel',
  27801. /**
  27802. * @cfg {Number/Boolean/String} bodyPadding
  27803. * A shortcut for setting a padding style on the body element. The value can either be
  27804. * a number to be applied to all sides, or a normal CSS string describing padding.
  27805. * @deprecated 2.0.0
  27806. */
  27807. bodyPadding: null,
  27808. /**
  27809. * @cfg {Number/Boolean/String} bodyMargin
  27810. * A shortcut for setting a margin style on the body element. The value can either be
  27811. * a number to be applied to all sides, or a normal CSS string describing margins.
  27812. * @deprecated 2.0.0
  27813. */
  27814. bodyMargin: null,
  27815. /**
  27816. * @cfg {Number/Boolean/String} bodyBorder
  27817. * A shortcut for setting a border style on the body element. The value can either be
  27818. * a number to be applied to all sides, or a normal CSS string describing borders.
  27819. * @deprecated 2.0.0
  27820. */
  27821. bodyBorder: null
  27822. },
  27823. getElementConfig: function() {
  27824. var config = this.callParent();
  27825. config.children.push({
  27826. reference: 'tipElement',
  27827. className: 'x-anchor',
  27828. hidden: true
  27829. });
  27830. return config;
  27831. },
  27832. applyBodyPadding: function(bodyPadding) {
  27833. if (bodyPadding === true) {
  27834. bodyPadding = 5;
  27835. }
  27836. if (bodyPadding) {
  27837. bodyPadding = Ext.dom.Element.unitizeBox(bodyPadding);
  27838. }
  27839. return bodyPadding;
  27840. },
  27841. updateBodyPadding: function(newBodyPadding) {
  27842. this.element.setStyle('padding', newBodyPadding);
  27843. },
  27844. applyBodyMargin: function(bodyMargin) {
  27845. if (bodyMargin === true) {
  27846. bodyMargin = 5;
  27847. }
  27848. if (bodyMargin) {
  27849. bodyMargin = Ext.dom.Element.unitizeBox(bodyMargin);
  27850. }
  27851. return bodyMargin;
  27852. },
  27853. updateBodyMargin: function(newBodyMargin) {
  27854. this.element.setStyle('margin', newBodyMargin);
  27855. },
  27856. applyBodyBorder: function(bodyBorder) {
  27857. if (bodyBorder === true) {
  27858. bodyBorder = 1;
  27859. }
  27860. if (bodyBorder) {
  27861. bodyBorder = Ext.dom.Element.unitizeBox(bodyBorder);
  27862. }
  27863. return bodyBorder;
  27864. },
  27865. updateBodyBorder: function(newBodyBorder) {
  27866. this.element.setStyle('border-width', newBodyBorder);
  27867. },
  27868. alignTo: function(component) {
  27869. var tipElement = this.tipElement;
  27870. tipElement.hide();
  27871. if (this.currentTipPosition) {
  27872. tipElement.removeCls('x-anchor-' + this.currentTipPosition);
  27873. }
  27874. this.callParent(arguments);
  27875. var LineSegment = Ext.util.LineSegment,
  27876. alignToElement = component.isComponent ? component.renderElement : component,
  27877. element = this.renderElement,
  27878. alignToBox = alignToElement.getPageBox(),
  27879. box = element.getPageBox(),
  27880. left = box.left,
  27881. top = box.top,
  27882. right = box.right,
  27883. bottom = box.bottom,
  27884. centerX = left + (box.width / 2),
  27885. centerY = top + (box.height / 2),
  27886. leftTopPoint = { x: left, y: top },
  27887. rightTopPoint = { x: right, y: top },
  27888. leftBottomPoint = { x: left, y: bottom },
  27889. rightBottomPoint = { x: right, y: bottom },
  27890. boxCenterPoint = { x: centerX, y: centerY },
  27891. alignToCenterX = alignToBox.left + (alignToBox.width / 2),
  27892. alignToCenterY = alignToBox.top + (alignToBox.height / 2),
  27893. alignToBoxCenterPoint = { x: alignToCenterX, y: alignToCenterY },
  27894. centerLineSegment = new LineSegment(boxCenterPoint, alignToBoxCenterPoint),
  27895. offsetLeft = 0,
  27896. offsetTop = 0,
  27897. tipSize, tipWidth, tipHeight, tipPosition, tipX, tipY;
  27898. tipElement.setVisibility(false);
  27899. tipElement.show();
  27900. tipSize = tipElement.getSize();
  27901. tipWidth = tipSize.width;
  27902. tipHeight = tipSize.height;
  27903. if (centerLineSegment.intersects(new LineSegment(leftTopPoint, rightTopPoint))) {
  27904. tipX = Math.min(Math.max(alignToCenterX, left + tipWidth), right - (tipWidth));
  27905. tipY = top;
  27906. offsetTop = tipHeight + 10;
  27907. tipPosition = 'top';
  27908. }
  27909. else if (centerLineSegment.intersects(new LineSegment(leftTopPoint, leftBottomPoint))) {
  27910. tipX = left;
  27911. tipY = Math.min(Math.max(alignToCenterY + (tipWidth / 2), tipWidth * 1.6), bottom - (tipWidth / 2.2));
  27912. offsetLeft = tipHeight + 10;
  27913. tipPosition = 'left';
  27914. }
  27915. else if (centerLineSegment.intersects(new LineSegment(leftBottomPoint, rightBottomPoint))) {
  27916. tipX = Math.min(Math.max(alignToCenterX, left + tipWidth), right - tipWidth);
  27917. tipY = bottom;
  27918. offsetTop = -tipHeight - 10;
  27919. tipPosition = 'bottom';
  27920. }
  27921. else if (centerLineSegment.intersects(new LineSegment(rightTopPoint, rightBottomPoint))) {
  27922. tipX = right;
  27923. tipY = Math.max(Math.min(alignToCenterY - tipHeight, bottom - tipWidth * 1.3), tipWidth / 2);
  27924. offsetLeft = -tipHeight - 10;
  27925. tipPosition = 'right';
  27926. }
  27927. if (tipX || tipY) {
  27928. this.currentTipPosition = tipPosition;
  27929. tipElement.addCls('x-anchor-' + tipPosition);
  27930. tipElement.setLeft(tipX - left);
  27931. tipElement.setTop(tipY - top);
  27932. tipElement.setVisibility(true);
  27933. this.setLeft(this.getLeft() + offsetLeft);
  27934. this.setTop(this.getTop() + offsetTop);
  27935. }
  27936. }
  27937. });
  27938. /**
  27939. * A general sheet class. This renderable container provides base support for orientation-aware transitions for popup or
  27940. * side-anchored sliding Panels.
  27941. *
  27942. * In most cases, you should use {@link Ext.ActionSheet}, {@link Ext.MessageBox}, {@link Ext.picker.Picker}, or {@link Ext.picker.Date}.
  27943. */
  27944. Ext.define('Ext.Sheet', {
  27945. extend: 'Ext.Panel',
  27946. xtype: 'sheet',
  27947. requires: ['Ext.fx.Animation'],
  27948. config: {
  27949. /**
  27950. * @cfg
  27951. * @inheritdoc
  27952. */
  27953. baseCls: Ext.baseCSSPrefix + 'sheet',
  27954. /**
  27955. * @cfg
  27956. * @inheritdoc
  27957. */
  27958. modal: true,
  27959. /**
  27960. * @cfg {Boolean} centered
  27961. * Whether or not this component is absolutely centered inside its container.
  27962. * @accessor
  27963. * @evented
  27964. */
  27965. centered: true,
  27966. /**
  27967. * @cfg {Boolean} stretchX `true` to stretch this sheet horizontally.
  27968. */
  27969. stretchX: null,
  27970. /**
  27971. * @cfg {Boolean} stretchY `true` to stretch this sheet vertically.
  27972. */
  27973. stretchY: null,
  27974. /**
  27975. * @cfg {String} enter
  27976. * The viewport side used as the enter point when shown. Valid values are 'top', 'bottom', 'left', and 'right'.
  27977. * Applies to sliding animation effects only.
  27978. */
  27979. enter: 'bottom',
  27980. /**
  27981. * @cfg {String} exit
  27982. * The viewport side used as the exit point when hidden. Valid values are 'top', 'bottom', 'left', and 'right'.
  27983. * Applies to sliding animation effects only.
  27984. */
  27985. exit: 'bottom',
  27986. /**
  27987. * @cfg
  27988. * @inheritdoc
  27989. */
  27990. showAnimation: !Ext.os.is.Android2 ? {
  27991. type: 'slideIn',
  27992. duration: 250,
  27993. easing: 'ease-out'
  27994. } : null,
  27995. /**
  27996. * @cfg
  27997. * @inheritdoc
  27998. */
  27999. hideAnimation: !Ext.os.is.Android2 ? {
  28000. type: 'slideOut',
  28001. duration: 250,
  28002. easing: 'ease-in'
  28003. } : null
  28004. },
  28005. applyHideAnimation: function(config) {
  28006. var exit = this.getExit(),
  28007. direction = exit;
  28008. if (exit === null) {
  28009. return null;
  28010. }
  28011. if (config === true) {
  28012. config = {
  28013. type: 'slideOut'
  28014. };
  28015. }
  28016. if (Ext.isString(config)) {
  28017. config = {
  28018. type: config
  28019. };
  28020. }
  28021. var anim = Ext.factory(config, Ext.fx.Animation);
  28022. if (anim) {
  28023. if (exit == 'bottom') {
  28024. direction = 'down';
  28025. }
  28026. if (exit == 'top') {
  28027. direction = 'up';
  28028. }
  28029. anim.setDirection(direction);
  28030. }
  28031. return anim;
  28032. },
  28033. applyShowAnimation: function(config) {
  28034. var enter = this.getEnter(),
  28035. direction = enter;
  28036. if (enter === null) {
  28037. return null;
  28038. }
  28039. if (config === true) {
  28040. config = {
  28041. type: 'slideIn'
  28042. };
  28043. }
  28044. if (Ext.isString(config)) {
  28045. config = {
  28046. type: config
  28047. };
  28048. }
  28049. var anim = Ext.factory(config, Ext.fx.Animation);
  28050. if (anim) {
  28051. if (enter == 'bottom') {
  28052. direction = 'down';
  28053. }
  28054. if (enter == 'top') {
  28055. direction = 'up';
  28056. }
  28057. anim.setBefore({
  28058. display: null
  28059. });
  28060. anim.setReverse(true);
  28061. anim.setDirection(direction);
  28062. }
  28063. return anim;
  28064. },
  28065. updateStretchX: function(newStretchX) {
  28066. this.getLeft();
  28067. this.getRight();
  28068. if (newStretchX) {
  28069. this.setLeft(0);
  28070. this.setRight(0);
  28071. }
  28072. },
  28073. updateStretchY: function(newStretchY) {
  28074. this.getTop();
  28075. this.getBottom();
  28076. if (newStretchY) {
  28077. this.setTop(0);
  28078. this.setBottom(0);
  28079. }
  28080. }
  28081. });
  28082. /**
  28083. * A simple class to display a button in Sencha Touch.
  28084. *
  28085. * There are various different styles of Button you can create by using the {@link #icon},
  28086. * {@link #iconCls}, {@link #iconAlign}, {@link #iconMask}, {@link #ui}, and {@link #text}
  28087. * configurations.
  28088. *
  28089. * ## Simple Button
  28090. *
  28091. * Here is a Button in it's simplest form:
  28092. *
  28093. * @example miniphone
  28094. * var button = Ext.create('Ext.Button', {
  28095. * text: 'Button'
  28096. * });
  28097. * Ext.Viewport.add({ xtype: 'container', padding: 10, items: [button] });
  28098. *
  28099. * ## Icons
  28100. *
  28101. * You can also create a Button with just an icon using the {@link #iconCls} configuration:
  28102. *
  28103. * @example miniphone
  28104. * var button = Ext.create('Ext.Button', {
  28105. * iconCls: 'refresh',
  28106. * iconMask: true
  28107. * });
  28108. * Ext.Viewport.add({ xtype: 'container', padding: 10, items: [button] });
  28109. *
  28110. * Note that the {@link #iconMask} configuration is required when you want to use any of the
  28111. * bundled Pictos icons.
  28112. *
  28113. * Here are the included icons available (if {@link Global_CSS#$include-default-icons $include-default-icons}
  28114. * is set to `true`):
  28115. *
  28116. * - ![action](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAFI0lEQVRoBe2YW4hVVRjHZ0yzq6lFEaMlE0PShYRAJIl6iEqKHnqI6WJB0IvdICkfEk0aIyo0KFCph8giCitI7CkoohQL7SoZDaQmXSgKo4uWNf1+zt7DOXvOOXuvvc85bc+cD36ssy/r+77/Xmt9e+3TOzIy0jORbNJEEqvWruBOH/HuCHdHuMOeQOmmdO+ozaA5oxXPunSC2Re4MbgCNiB6vvqbKbx0giNxp9BeBU/BIJqnRecLN2UVrLDj4GIYgscRfSltYSuzYMUdA/0wCI8ieglM5XduK7vgWJhTegGshucRfQHkyj1XpziLNrfmOh2ug1dhMaJn0gbZZDpNpsexQb2y3azfKXCAwns4W5dMd7m2B2ANLCT/x/A/nKknN5mUhWFp1g4Z7vM14jrbBZvgEwi1tAdkDEf3ZrgI0S/RrkP4IdqGpuA+cJo0yw7iyNfJmzAcMrokfjp93HC4XrPYCdzkgPXDPPqvJN7eRh0VrBWqfKMuev6k3Qzr4SP4HWqOFIkZ73iYA/NhLpwPZ4LLS+FZzUp+GtwAA/heS/sGwv+irWnXc9bdTRF20/8eOBWmEKwnCectOrPhSlgF2+Bb+Bl+AxP8B/6FvLn8Td8fYQXMSubgsVZU8Cv4mAeNhC7k+jLYCopzrRURlvZA9P8WLIJJlcI5zi1Ypw+Dr4oqp3EAzlsbLCjfg1PeEUxLtlnXXU4/wQboq8gpl2BHx2l5UuyosuW8I6rQb8Bp1iwRefy4VN6FReaopU3pX7jnhwSO7MmVIiNnJ3L+DtgHCm3ltA0RH4/26rhKk1tdu4kr7yeuHkKgU3rMqI5ncfAQDIKbg14oi1nJv4OvTShthC9LjmTyGB8XwhZw+oQ8+Xbc68C8AOboK6+YYPpfDV+B06YdAkJiuMtzhvrOP1JYafMLpu/Z8CmEJNGOe60fz0J/cjZmWcP0G2+sWZ/aUnCqhFosOq7gyf6uOT888th+Ot0HmxF7MOkgt2AcXQNLkg5rHPv+dffjVvPX6PdeWtf7MJhUssD578ZtEGL6sY4MIfTjeh1zCWZ0Z+DwQXAkapkjtzviPdoPYB+JuJVMNfy7QQkR7MbGPfRaYhi7ruUSjLcbwe1k0tw2vgivwy6C70/ekPE4JK+N+HySWDuz+A5xXOnvlsqD6Lf/QjwBnxNc4a02YwzBeuIdyBosWDDT7RKcn1MRYA+/V8ImAv9Rcb5VP53ufoQ8AB8S0+PMFiwYz5fDzCjCF7SLCbojOm514zZ3HViYLIZVxmD4h8B0rtWtFXkEn4tTv22thPe2SawVeDs8TTz/NqoyhLqDGoC7wervt3lNCxKMY/fIc+BLuJXgn9G20pyuVuA1sJF4vt7GjHx8nZnT7XAXzIXnoK4FCcbLVHAqLW+DWF8v78Aq2EY8v7zGDK2+EmfBI3AtTAPNTU1dCxXs/a6ht+t6bM4FNykvw/0IdYSrDLHu8iyeQ7Cg6mLKQahgd0pbSOJwit/cl6Np6p+BrxGn6hNUp1z3m/tOWAH+DrIgwSTQcBcTFLnOzcRwSjZ6j/vdvQyCxRrSanu0mWvZqp3LjkbBuYTGnSac4CxreCQqJPFD+r/bhq+dtOSyCO7DyWzIcm9avKLXXb+FcskiYjlBfB0lP9KLJp+nv6N7ZL+cp7N9sgg+L6/zMvabcEWrK7iM07CZOXVHuJlPs4y+rNJ74JkyJpczp62N+vWOfpw0uqWzrnXXcGeN53g13REe/0w660x3hDtrPMer+Q9LNCcV91c+jgAAAABJRU5ErkJggg==) action
  28117. * - ![add](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAABqUlEQVRoBe2awWnDUBBE843B4NxcQSAFOC4lJeTkoxtJDykgvRhcgCFNJCFgIs+ChEHSJX93YT6ZD4ssmR3NztNFH5Wu6+6iVynlEZpbp+4J3s5OjWm7DRxZuMMCdUB9oyzNmrJe01hEejMtM5exIh6bCI3JbFkDT27EckEDs5DI8iHCWcmy6IowC4ksHyKclSyLrgizkMjyIcJZybLoijALiSwfIpyVLItuOGFso/xiuEvAgJdeK0DqJrHEhtsTTh9ul9y/ChR2KE+Y1ruDt2ccI7d6PszcK+oFFblWELt3Cn6i/8epMW5/W+LKGrUZ/0NwboF5QxuPsfY8dmOxJs41cBOYHCZF2BFeE60i3AQmh0kRdoTXRKsIN4HJYVKEHeE10frvCNvr4RH1HojH3rGHr3hqA7VdkxPKvuKJ3AA4hn7BM3xxA5N71Fdv1gz/tax3P+hFHmsJwM/8wraMadqOh5GuXda76rVqNWb7wgeevQvRRQ1MBCPFiginxEokKsJEMFKsiHBKrESiIkwEI8WKCKfESiQqwkQwUqyIcEqsRKIiTAQjxcoVrP83/9czD9EAAAAASUVORK5CYII=) add
  28118. * - ![arrow_down](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpGRTdGMTE3NDA3MjA2ODExOTJDQUMyNUQwRUE4NjdEQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxQTFBMDFDQ0I5NEYxMURGQUU1RjlGMEFERUNDQTVEMCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoyMkRCMDIxMkI5NEUxMURGQUU1RjlGMEFERUNDQTVEMCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjMwRTE0QzVBNDIyMjY4MTFCQ0ZCOTAzOTcwNzcyRkVCIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkZFN0YxMTc0MDcyMDY4MTE5MkNBQzI1RDBFQTg2N0RCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+HfrH/AAAAeVJREFUeNrs2cFHBGEUAPA3zYqIiIhOnTpFRHSKrp26RqeuEV077R/QqWtE166dOkVERHRa9hQRnZalFcv0Hk/W1Mx+38z3vvlm5j3eZW+/9+abne+9KEkSaFPMQMtCwQpWsIIVrGAFK1jBClawgo2ik/4hiqJGwLKuvfpIc5xSkWqYr5hzU1s/mRNxXTPsJ+ZqluvXlwOmSj3XBDvG3M1rpAmYYoUrFzr4ZNqTawqm2MH8Dhh7ZXJUbcAUx4FinzBnJcAUl4FhP/jIgRSYKvkYCJaO2LbNv08RMMUy5nsA4COTLy0XYIqtil9iF6aflq7AwBWuAvuQ9ZKSBgNX2ieWjtKSzeXBNZgqfe8J+4W5aXtbcg0GrvibB/BhkeuhBJhigzsghT0veh+WAlMcCGHvMOMQwcCdcIntYy6WmXhIg2PuiAvsEHO97IhHGgzckb4D8L6LmZYPMHBnhiWwXVdDPF9g4A4Vwd66nFr6BAN3ygbbw1yoMzjmjplgB5hrrufSvsHAHesZDOD2JAbxVYCBOzfIAZ9JbR6qAgN3cPwP9kZy1VIlGLiTdluCmoOBO/pnS9Bk8DzmS3pL4BMcpZEe1qX0GI/atC4dQYXRMa1MU0IX4gpWsIIVrGAFK1jBCnYUPwIMAPUPAyFL+nRdAAAAAElFTkSuQmCC) arrow_down
  28119. * - ![arrow_left](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpGRTdGMTE3NDA3MjA2ODExOTJDQUMyNUQwRUE4NjdEQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGMDZEQTFBREFDOTMxMURGOEQ2MUVDMjM0MzY2NTBDQSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGMDZEQTFBQ0FDOTMxMURGOEQ2MUVDMjM0MzY2NTBDQSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkFGQzJEMjQxRjIyMDY4MTE4QTZEQzUxMDg5Q0Y0RTRFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkZFN0YxMTc0MDcyMDY4MTE5MkNBQzI1RDBFQTg2N0RCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+FXGmxAAAAghJREFUeNrsm09ERFEUxt+rxBAxqyFm1SqiRYpMSpFapUVaRGpTRIpIbWLaFJEoRZtilChRWiRKsyklilYRERERERGZvsN57Wfmvnnnznkfv+WM+bn3e/ePN24mk3E0pcRRllC42FOWy4dc1w30R+fz3LFthEs1TelZ0KlBuAIcgmRgHS5gqlm2RsNTmqbvrUlZycLT4BhUiliWfEwEbII+UeuwT4nzqNZq2Gm1gTu/ZaUIj4NTEBW7tTTY1zUwKH4vbaive6BBw2kpAa6DkA1CeBicgZhVx8McUg5WWNi+83CWiXFfE9ZeAGQR6ukBqJKyu/Gzw7TcXEiS9UuYbiWWeU8ckXYqMT2lozyFW6SeOU0K1/FhPS75RsHUlKbj3KV0WRPC1Nd5sCuxr6anNPV12zFwk2jLCCdtk81XeAIsahL+BVOgH3xrEPayA5rAixZhyj2oB2ktwpR30A5WtQh7vR4DQ+BHg7CXLdAMXrUIU26411dahClvoBVsaBF2uMsjYFRCrwt5a7kOOnjUVQg7vE43cr9VCDu8I6Nep7QIO7z3HgCTvHYXvbCXJe71hxZhyjmv1w9ahCnP/DDb1yLs9boXzGgR9rIAusCnFmHKCff6UYsw5Ymlj7QIU75AN5gz9YVuLu8eB/S+dA+v1+l83pe2Sfg/BRe2OeGfPELhUDgUtip/AgwAw4tbozZtKFwAAAAASUVORK5CYII=) arrow_left
  28120. * - ![arrow_right](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpGRTdGMTE3NDA3MjA2ODExOTJDQUMyNUQwRUE4NjdEQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGMDZEQTFCMUFDOTMxMURGOEQ2MUVDMjM0MzY2NTBDQSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGMDZEQTFCMEFDOTMxMURGOEQ2MUVDMjM0MzY2NTBDQSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkFGQzJEMjQxRjIyMDY4MTE4QTZEQzUxMDg5Q0Y0RTRFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkZFN0YxMTc0MDcyMDY4MTE5MkNBQzI1RDBFQTg2N0RCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+xvZexwAAAhhJREFUeNrsm8FHRFEUxu9rxhARsxqiVauYXWoTpTYtUkRqlWkz0WaiTW2iNi3atGhTm4k2E5GYSJRaZcZQtIqIISIiYhgyfZdv/oF59913X+cdfst5733u+c495743XqvVUpKiSwmLWPB/j2QnP/I8L9SH9lN3/KxwQlpKT4FtaR7eAhegR1LRmgEVMCCpSg+CGtNczLbUC8pgQ9I+rCv3LiiBbkmNxwJ93S+p08qCRzAhqbVMg2tQkNRLa1/vg6ILvrY5POTAXdi+tj0tDbOYjUoaDzPgBuQlzcMpcEhSkg4A8lztjBTBin6u0d8iBOvoYwXPSRGsuEcXuWcnJAhuR4G+TksRrGOMfXhWimDFjqzCyUuE4LavS5yxExIEt0OfopRN+DpKbx6MHAtHSfAeWPN7kWQEhDbAMjg1cTHXBdfBLHiSUKXvwZBJsS4LPgCT4NP0hV1L6SZYAcdB3cAlwe9gDlQlTEsP9Gs16Bu5IPgIjIOP/34AoP26Ss82bd00LA/r1Vzk1mM1whCsfTrPpsJ62E7pE/q1HpaPbAn+Betgib1xaGEjpb+Ywrcu7H9BC35m8//mSncTZEqfgRGXxAYpeJNp3FCOhemU/ub+euXqzGlS8AuYBq8unyiYSulLNv9OizUleIcr+6MiEF4n3x7ze2n9OkSfE5/bfmg/30v7ERxaWBcc5Yj/5BELjgXHgiMVfwIMAGPkXbHq6ClAAAAAAElFTkSuQmCC) arrow_right
  28121. * - ![arrow_up](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpGRTdGMTE3NDA3MjA2ODExOTJDQUMyNUQwRUE4NjdEQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpDQUZBQUM3NEFDOTMxMURGOEQ2MUVDMjM0MzY2NTBDQSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpDQUZBQUM3M0FDOTMxMURGOEQ2MUVDMjM0MzY2NTBDQSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkFGQzJEMjQxRjIyMDY4MTE4QTZEQzUxMDg5Q0Y0RTRFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkZFN0YxMTc0MDcyMDY4MTE5MkNBQzI1RDBFQTg2N0RCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+ar3jxgAAAbFJREFUeNrs2j9ExGEcx/H71YmmpoiIaIq4KSKi6dabbo1oiqamm1qboimiNZpuuikiIqLppiPipqYjIuLp+/D95vy6X/frfr/n730e3sst53XP9x7u+V2ilKpM05qpTNkCGGCAAQYYYIABBhhggAEGeNSqpl9IkiQKWNbvfBc7PDdNIz1PPVK7Trd+OMPrRr8l9Uat2nT9+CyCW4yVnnnHowTXqa8UWHcdI3iNGozASscxgReo7h9YxTtfjwXcHoOVBjwJQYNPcmKlLk9EkODGP7FSO0TwOvU+IVjxZAQD1iPZK4CVGiGAZ6lOCVjFE7LhO/i0JKzUK3KImQY3S8ZKHZ4cr8A16sMQWPHkeANepF4MYqWmD2A9arcWsIonqOYafGYJK73yRDkB71nGSnd5r4jKBG9Sn47AunOb4CWq7xAr7dsA61G69wCreMK2TIMvPMFKfZ44I+ADz7DSQ9YhVgS87fiQGtdlmeBlvkNWnndYBljfGT8FgJVDbKco+CoQrBp6mrEyKfgoMOyvpxlZ4CT9vcXj0shWNe8nE8vCfzwABhhggAEGGGCATa1vAQYAZekAmr8OukgAAAAASUVORK5CYII=) arrow_up
  28122. * - ![bookmarks](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAHC0lEQVRoBe2aW4hVVRiAx8t4qXFMvGZGeLcblUVWdJEoiTIhI9KoHiIyKyh6SOvBh166vPTQQ2IXkKyIktIyLQzLUoMkSbKoVEwtK2+VZWrl9H3bs4Y1e/a5eDxzDsycHz7X2muv9f/r//+11p6zt91aWloaupJ070rO6mvd4c6e8XqGO3uGe5biYDck188y1LOGeuS3Hvs8AVrrWZ0LtUU27VbIbrCRlMVsluQwBptgHEyHS+BcGAxBDlLZCOvhY/gQ/oD/oFxxuw2Fy2AKTIIJ0AuUf2EbrIF18A7shcOQX0xCPhh1KsyEVWAES+U7+j4Co/PpLtTOOB2bA7uhVJu/0fdZmFRQd9ZNBvWB6+AjKNVgVr+vGX8fNEO3LFuhzftgRu+HrZClr5S2fYydC8Ohe9AfynbZpdPJ8CTsgSwDLiWXjcs4cIj6P3AUssYsoH0kZDptO4yHFZA13rYjoJ1g8+9cWz6bn3D/UmjjdDIBGhPhoOhL5WmYBY1J47F/gkGNfAEb4Ptjt5J9ehp19/XF4N7uDToRxL28Gu4m0mavVXKH02ganoGprTeOVXTG4Bp8HdgEv4L7WxsT4WoYlLvuQRmLc50Nn2NXHwhnbg9T9QDTWTMYR9nM7YTH4WzoDy55HQp4kPQDHX8AvgEzEuuxvhD6BZu5OZxO23JIZ8rxHkj3wDBoApMQbOq0q3E43AKr4U9I61lP25hgM3GYBpVMASMZT/IvrpdCwYMgKAsl/UfAc+CKiPUZPAPXI+esWZqf6mP//eD4gUFnsZK+JuEx2AGxTesvQHNiM2fYCfooiTsaYU+9IcWMZd1nnBl4Anw8xXpdkpPB+zMgvaJ09mHI3O9ZtuI2xt0EuyC2adZd2tpM9oKHVNzBTLwKJ8XKyqmjw1PXgybWv5LrK+CrVPsBrm8rx048Bh3T4KeUbgM9CZI9kI7Il7SPjZWUW0ePS+098OAKTptF92ccCIP8FPQs11YYhw4zOQ888IJNy9eh4cZUo0tsdhhciRJ90+GXlJ14ItYN8qhK2FMH0gye7LGdI0aiF8RipN+IGypQfxcdnxXQo81lTHRrgT7HdQtdnh2LUoMadTgJR3TDa5daxQTjHoBvgqd+lvjYW5Z14wTb2vmRnFoZSn1MVVqWoNBHRloMsEtvXfpGBa7b+ZHP4QrYaqsit8QWt21Nrn7n35e576Ojw6VqDuc8WUuZdsy95oldFam2w+7ltBwlu/5FVhWptsPt9lRVvIyMVNvhyHRtqnWHaxP36lmtZ7h6sa6NpXqGaxP36lmtZ7h6sa6NpXqGaxP36lntchn25XtJkvtC0JfOvhLyxVz8Q8Af8f4SksP8+vGVTUUk9zVEm841/TrKn5q+qNNmSb+4ijqMwQEoHA5nwjlwBoyHeHX4RnI7+PbzW8b4iWMHk/iZ8riF8QZUm+PgPBgDg8EvELEc4sL3YNsYs4FyC+zCrm9FMyWfw4dQ0MSIa+F6uAb6gxH2c0c60jQl35XMrFl2Ip+iYznlKibgpIoK/Z3PRXADTIFRoPPa9F4PiMWV5Qcz7WrTd2YfoOctSl8ZOZd24itUBwZcGnfB27AbVOLSCfdLLZ3APlgLD0JvmAzx+2l1bSEgFMmHsYWUm8G3IOkvEqXadb6+dPcD+SuQHpe8M44bde5HcMJxe1y3T0AHCgXE6DsBjT8EaUd20nYnuA0MdiFd3tNeMZvO1b3tx7V43i0ePGY4/XLNTvGhxGWDX9j3ghnbAlvBfhofASPB5egydN93h1gMoJkbEjdSNwDqHQTpJWsAfMm3AQyIifDaubmtxsBYuBAc3wwFxX2RJbGzLmv3w4uwHpy4WZMg6hH323i4AybDaAjiPUmL44amGn2fvBH8ILAEDJQZMzhmWXGOjTk8b66EaXA5DIO8YobbpD26XkHdyRu9Xu61YtBPB8ywE1gE+yGf/qz2TfR/FAxWUzF74T59DeZAmAFrIEu3be32sI1Ocg64RMr6uMU4l7TP7anwA+SbQGg3c/NhApQU3OBsXDLWgJvhueAqDPpD2c5h9+pM6BMrKreOHidwFbgHg9F0qbMvgSuprO/C6fmhx6fCLNgDsb02Duvs7dCYVnAi1+jzMDofXK6x8VB/nvZTTsRG1lh0erDNBvd/sNXqsI33QkWdDRNBr0vc88KgBuOWK2Fw6FfpEt06vQB8mmiv4eZc5X3KAZU2GOtDv8t7HriENe7z+YK4T0fUsXEW+GhLHL6VymaY2BHG0jqx0w9eA4273Nr8P6p0/0pcawOmwEEj7jNvPoo9VDpcsHOAv3VdYp7gS7k22x0qORv+jb3Yh/co2E+jj6KqCIZ93PnM3I5d91ZVBLtjdVj8gyJZ39WwjOHEZi3stvmvh9VwttY23MxdSuoOd/Z01zPc2TP8PxKYOEKWmL1pAAAAAElFTkSuQmCC) bookmarks
  28123. * - ![compose](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAF/0lEQVRoBe2aW4hVVRjH54xa6nSzm92sHiZNorJowMpxrDEoyECiUUpztIkeeumpoCB6rAwi6FHwIXqKXkqiEE0no0QNLWwyspmGsruWlVqp0+9/2t9hz3Lty+mcfTnpB/9Za397Xf7//a219lr7TGVsbKztZLL2k0mstJ4S/H+P+ESfwEqlMhn/VNAJpoOjoGibAIFfwDbWnT/DZOCrex34D4b9vvw4wVScRKEu0AcWgQtBmYb9DvgsA6OganCWhgFwL/lHEf35v3ci/mqVFrAO8AT4FugJHge6URZsg0s3aDfOAe+H8f0INAo3gavD9928iT2bgqvBYVAWgWEeG+E1G0wwAeQ18hTZ/cDKSvROECnaBD9Iod9DFa2BMqSDEgAqjtiH8H3v4XwM32ZwlZUPp/jbLgHDoAziXA7r4aXIhsVqgZLYA8Atb9eK9BbQGRarvOwxEDdfdU9D/UiOUH9bwTixAWGJ/QmYuKhUojU6xomu4HgL3AV89ipO3ZdYlc3LJOJTsAeR1bAEr56V+J4H00Aa0/D+BNxPM0NW4Wcyvqe0G7+Gu5b9IhAexnrYq8A+4OMa55PoDaA6p0kjG1jHvVqnetBFQBxAP9CrJ27qxYm2OX25IhdlxxGoRgqzYFOxHAIvgHMbIKKF7iIwVe+yMtsA5F4CjYiVPu2+lhG/z3QRNRTeKGIIB4NKgXgEHIrhF8Xb9WuxmmVayhphLVDPgimgEdtL5VWI3RNuxH0idp17hCGlAOg924zISmyXRdbSskVYYjVnmxFZvXt14DjBLKJummuEYXU3iNsuuvyirnXam2cRddNSRJjXj1bjteAc0Ih9QeU+RG6JayTqSeUSYYhpu/griOKR1j9MGze7EXWvKRPZUaaC6VebAYltxrFUYue64nzXRQ7pfki+CDpAI6bVWJuKD9M0Ere1TFO/7jLMV+2NbTXWh8JGTDuoxYjVySqVFRFhfV15DjQqdoQ2BuoRS/mqRS0KTZ3D9KTISuxvIKrPtP5R2rjFnaP4Ek93lInsvGmC6eM00A+asRp/RTu3esRej3+G63evKZOL4HvoJ/x1MW0k3XI/0E6PR0Q3/o/AHPeee53XHO6DzDRgw5ls3fYlNZYgYHO4JmvgfVy/DjqBPhDEWuaCIXQpDOYELNaQPg4SiQXlLfmazErEvmsOpbQ9j+RlcAH4G6Qyd9jYdVPmMAx6wDEgkXOBHrK+lIqg9RWXSmy3OzTxzQcjwOrq29x1bjn3mjK1ClbR0oYF07Z2U08FfewiPV8EMK3YOu8midYCNd9DWpHVSm1clZZC8HkQ2R4Qe4Z0kpEnr5Vb36oU+TBxy2uB6rXyluK7AehAb+UsTSU46zl8BcRuBBrSg5CuzTPyf+HTfPbNaUVvKWU2kLq2BMdM15n2OmvBd0BEw3cHGPaQ0r1XwNuhe/r2vAKxG0O+cNbWg7AvdT6zvTQrqH5rXhowWYeAqmD8Z+DTqroA9IKFYDqQSewDlN2kiywsM8GQnR3gCOkQQmeRanhL4J1Av2qY6SP7XvBklmLVWZaCV9D+6eAQ0DxVVK8EZiNkPgDvAS1sQ4jV2ThTy0Qw0ZwM69sD5joVdQV5iV8P9DOOxO5DpL5j5WaZCIb9AqAV+ij4A+hw/maA/XlEkr68lpXga+ltKxgE2sDs9vZegDMrwWsQuboAPYldtieW+A8F8p6X9VDMRHA9BPIuGyd4LG8yKfuL46WdW6xJcFQDU3i96LRTGoOPBGmnligsirQWre/AxZ4C1+DrpY/3PfeKcl1Gxz3AJ1inrsR3uiquBf3AZ9/g1FFMjZXBZkBCW1Sf7WSx1NEx0bSv1QZBQ7tVoYA8jeDEf7yhXNuZ4B2gSq0qeBjuM1MJViGsB6hSK4rW598BMO6/bKPE14YAFXQ2HQWtMrwVnINAYmufjqKEmr8mOIj0bVTWSUYb/qQPbBoaRUABOQz03znLwUQTkyat/hZDpZrxGjqLi4VgMbgJ6L1XFlNUPwYKymvgACL10FPbCYJT12zRgnFbyxaVFE/7lOD459P6d/8Bhs9x6sTqrJgAAAAASUVORK5CYII=) compose
  28124. * - ![delete](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAGcElEQVRoBdWbzYscRRjGexY1EPK9u9mVoJH4cVBPCYR8mB0IbkISyB/gOYIeFSUQQaIX8eBBDKuCsBFFxJuieFCMEb9RiZrcxKOgB7+i0RjN+vwm9Q41Nd0z1d3Vk9mGh6rufut93l93dc9katNaWlrKymytVmuD4mek7zX2YpmxqWJVwwrl2iL9qBp+LpN3okywjNYo/qh0Sjqi/ZVlxqeIdZ5HXA1HXU3xqbnDMVJGYJ+UzktMi1+le6VrY8aniMHLeeJNDdRCTWti88fCTirpSemChJHpT/Uflq6LNawah4fzwtP8aanppDQZk3sosBJNS4tSCGumf+jcMWlFjGGVGHI7D7zM12+pjRqnh+UfCKwE66SXpL8k3yDsc/4+KfmdJqfLHVMDta4bBF0IrIFrpaeloqsaQvM83S8lgyaXy2nvjdAz3KdWal5bBJ0LrAGz0rPS31KYdNA+8Y9Jtac3OVyuKjVQ+2wedB+wAqekE9Iv0iC4onNMvUelytCMdTmGTeOiGqgdhqkQugdYAdzZBakqrBXAXXlCWhkaDttnjBtb9s6at7UwwNJzp7vAOsE3KKaCfcbZwKrtP8r1oBR9p4l1Yxhb1dcfBwtMG+xCd4A5IHFHfpL8AXX7fFw8YGbDWmIlxtT19cfDBFsHWm22UVqUfpP8wFR97tbxCNjjikt1Z8PaYYMR1uwRidd5GJRyn39k8PaeCME55s4Rk9IzzAUjrNmcdEb6VwqDUu5fUv6npGsMmr47xrmUXmEu2GCcs2d4v3Y+kZqaUlbAf/J4SOKuIvocs/NNtDDBtp8L7b+lt+vgaWkU0M/IB40CFqbt3VllnQ59lu3Tyc+kpqfYZXmgJu6o5YQBln09jD07WdZSwF6JKdA0tBXWREvtMMDS6mH0d6yvoLb0sdT0lGsClpqpvW08ftt9hv2D9LVxdb6Vmn57p4SmVmreG/LYfiGwg96hwd8sE2hgqXWHweW1A4Ed9AElOTfm0MBS44E8SP/YUGAHzfQ+O6bQwFJb4TQuDexBj9v0tmkcBdvh8OmH9XUVt0nvSE1/7415kVEDtWwbVrd/PmpK9wzIsq0y+VLi6sYU1kQM3tSw1a8tpl8amKTa2s7wakAbbDsGMIypBOygdwr6C6npr4j+DMELz50hSOx+ZWAHvVvmX0mj+EaGB167Y+Hy4iaUoM7GW/sHiSvf9IYHXnhW3/KuQswxOa6SFqSqP6X6UzW2jxeeq2JqzIupNKVlyEri81K4sBVbeJ04PPGOXjH0wUsDy2i19IJ0QapTeJ2xeFPDah8mpl8KWAbc2cel36U6BacYSw3UUupORwMr8aS0KF3NOxteKGqhpqi1YWZAFLASrpdelMYJ1uCpidrWJ5nSSjQtvSyNI6wPTY1JFsRJNMqPHoMo21IjtVZeEJ9xCZYDrF0cg54pmt65z7BAp6QT0nKC9aGpvW9tOPel5WAX1KZaNrVCRtlSOwx90D13WAEsiD8nLWdYu7AwwDJwQZypUHf13wwHtWfkgwbFpDhnf/rQtyC+SeZ8Px3FnX1LPpud6KcAG5QDJtg2dZ5hdTZKi1JTC+J+MZ/K5yZ7g9KXOObHNNHvWRA/JsPzIzB9Xx53GKy1HJM41wSonxNGWLN56Wupyd+nTiv/rQYZtpyTiPELTNmHDcb5zltanTnplHRRSmlErjek60PIcJ8YF5vaHybY5vDsfizpwB4p9TLp68p5SwhXtE+sxJhU0JeUC6Y95tkF7tBn2SGd/FxK8VcAHyjPzVLP+qwZ57XEujGMrQsNAyyHfK8eYAfNM82bsw40KwJ3Sn1/teOb5/UZ48aSoyo0tcMwH3r0ATvogwrmzwWq/Pz6nsbdLpWGteIY63KQqyw0NVP7Qcvnt7nADpq1YZYzeA5iTV9T7I1S9DT2i/H75HC5yBnrT63UXLhGXAjsoNsafFaKudOvKG6zVBvWwMnlcpJ7GDQ1Umvbxue1A4EZoO2wSzToc/ptxdwgJYO1YsnpcuNRBE1twB62cUXtUGAHzTN9TsqDflPHb5OSw1rR5HYeeIXQ1ERtuc+s5bA2CthB80yHn9P8pDIrNQbbLfQKNF54GjTPLDUVPrM23tpoYAe9S8k/kjB6VdoiNQ7bLfYKNJ54UwO17LLzMW2nWA2K3vQ/we5S8N0SL5LvZHI5enCCQPnzkcU3snukd+X/YZm0/wPdHqnTTpY+CgAAAABJRU5ErkJggg==) delete
  28125. * - ![download](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAGb0lEQVRoBd2aX4gVVRzH3V1dU5JMk9Q2wVxCo0QNTYRYS4l6CBFBomA1qjcjSOgPPUgR0VNBFBT0Bx96qAiSXipCH4rKIhGNUqE2SK3MqKwsLbXPZ7rnMo73jnPnzF6v9wefPefMnPP7/b7z58yZudtz6tSpMaNlPT09E/DdDxPhMpgNJyBtfTRG4AAchePk9BflqFhP1YIRqbCZsACWwjWwGIrYZ3TaDZ/ATjhIfh6IyqwywQhdRlaLYBVcB5Mgxn5n8HbYAjsQ/lGMs/pYz3AMOFLgG/AzeH+MBvo2xqqYXB1bSiyBe2EJvAaH4SSMhtC0T2MYy5jG7i0jvmXBBJoMj4D3VjuEpkVbN6axzWFyq6JbEkyAhfAqOJtmE2l32xzMZWErogsLxvE62As+Vtotrlk8czGndUVFFxKMw41wEM7FJdxMbNhuTua2sYjoXME4cVHwEDhZhACdWpqjufblCW8qmIHOxHfCT9CpIrN5mas5N53B8wS7kPgKOumezQrMts3VnJc1O8sNV1qsmq5k0LNwI3hZx9ovONgEPk4amcvRR+HiRjtb3KborbAB0fvOGJs9EnRwwf88HIHsESzbVuisbKzQdh/Yp6z/7DhzV8OEECOU3qd148z20FgDK+DC+o74in59Y2pm7rNPVWbualhT01T3e5pgts6D9eARrzIB3LXVzF0N60FNdasL5kj0sXUtzIf+eo/zt6IGtaytaUuU1AXTugKuhyomjsR5B/xRi5rUllgimCMwltYQzAHr3WJqUdNQTWOyuFDcpbASptnoMlOT2tQ4phfl3uBzwes9byZl93lpalLbXLV6SXtzr4BuPLvISkxtauxX8DjwW5Qv9t1qalPjOAX7vJoB3TRZIec0U5saZyl4ELr57CIvMTUOKngAqlxGJt478I8aBxQ8Hbpxds4eczVOV/BUuCC7twvbapyq4Ha8JPQVOIBF+hRwk9slWVLm9miy8xjbj0PRA/YHfU828eVm99mnyFziu6/9XT+Mh5as7KPIoE/BB/BPgYgeoP05/dx3OxQR4LrBF4IHoWUrK9j7wZeNzXxJGGk5amYAPvyovj2zuWGT1eEcdjwOpeYdL8mytpyBr5BAW5akroOxy4n5MiyFUqZg78W8+yvPsZfWEyQy3WzyOsbsq/n2Q9+TYMwypsbjCj4EXlJlzPHDcD/48W+0TN8PgF9kyh5YNR4y4e/AGbKsOVveC8OcCSeUSg2fir0H7oayc445qVGtY5bBHnDmjeFXxt8GY8Mn0dhSX+Ds/RvE5OZYNao1eQ/+kNJrPNapoocg9/edIgdCH3AL6DM2L7WpcZqXtKd6L/wJsXYRDl6ABVyK+i5ltbGLGfw06DPW1KbG5NY1MS+bbyD2SIbxO/G1HFo+046BG+ALCP5iS7WpsTf5MY3KPPgYTkCs8zD+XXzNLHL5hj70dwb2WbsNgp/YUk1qm2ecINh/MXoMfoTYAGG8gV6ES4Kgs5X2hZegivkk5KEmtU2qC04q/082u9gROlZRmvgmSH6lzBNMHx9pJlZF3LQPNQ2F2PXfh9noEvF18AGdHhBb/xd/d4SAzUr63AX2jY2XHq8WNU0LceuC3YCtBiecqgP7HF0XgmZL9m2AI5BONrauBrWsTsfLCnbV9AxU8ezLJnwAv2vSwa27DX6AbP/YthrU0p+OeZrgWgLO2FvB99zYoNnx+/B5dUiA+kL4FrL9YtvmroZkZg7xEn3pRqjTcRhGIDZwo/E+rpyNZ4D1Rn1it43gdzjoSZdnnGF3Yq5h74Oq76sg5D18b4PQrrI0Z3NvuKZvKLgmegqDNkPVs3aV4rK+zNWcp6TParreVHBN9ACDt8DfkHXeaW1zNNeBtMBsPVdwTfQgTt6CThZtbuY4mBWYbZ9VcEr0mx0qWrHmdlaxiZbsEWjWxuFkeBhcm7pkPNeXtDmYizkV/r/pQmc4HAQc+934ZtgBVa/GWjmAxjYHcxkf8itStiQ4OCTIbHgO9kM7z7axjGns2SGfVspSgkMAgq4EZ0b/i3U0hevbGMZaGeKXKRv+cylOCxufY/xCcS3cCl5ii6AXqjCFeum+A2/D54j0Pbu0RQsOkRHu+6zP7avgJvDsz4VWxStyD7wPrsi+hP0ILfIbFl3zrTLB6TCId3KbCK6X58MSmAOuocW69jUcrmH9U9gF38NRRB6jrNT+AwkLDdxcvfCRAAAAAElFTkSuQmCC) download
  28126. * - ![favorites](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAFfUlEQVRoBd2aXahVRRTHz/Ujv+2mZRGZB7W6mtpFikC7+UWUZiqBD0JPFdRL1EMFPfjoU4baS0FUD/UWZBEVShA+BCpmWApRSkgllNpDmZWZt9//eOay72afvWfWOTPn3rvgz8yeWbPW+s/XmT379AwODtZSSQ+CryVgA/gVfIx/pelEhFMBVlvBOaBeFo6Cean8y09KsnMg932TqCOs9M2UhMfhMJVsxtHcAmcbmekLCsqjFKUkvAYG1xSwmEHZqoLyKEVJCDOCNxH9HUCbVl6mULAuXxjrOQlhgl8Bbi0h0Uen3FBS37GqVIQHiHh2SdR16jTlo0t0woycpuxiUDSdHcFeMv3uIWYanTDB3wIWVZBQHP10zuQKvbarUxDWT1HRz1E++Ds99fLtgp6jEmbExhPNcs+IbkZPiCpRCRP5TPCQJ4MJ6A3QSUqjSWzC2ozuC4j+fnSnB+gHq8YmvJKIJgVEpRPX9QH6waqxCa8PjEhHT981H2j6qno0wqzF63BhOUxsom3Zb7aJqGsUjTAONFJlpysXQz7VuXpavrBTzzEJaz1adlzNjHs6RTBvJyZhjZTF/kTaWZZCnlvhsyWgQkPZQpagzsX1bFlAXjGtDdAPUu1p3PPQhCCXkdwG/mta0PWLds060AuAnqtEOjpdbQR3VymX1P9F3UfgGJA9X9F92c/ADaQ2P8V0DJ4/kDbeYKaSvgI2AN0+OGJK1VAbSIhTOXEOybYll2kte77yD4rqrHyb85S9Cl4HtReAyI11/A7HpRq5PSD6oR0f3Rad+H7S1DvV7UgS+tc1cU3n3V/AWJ/SX8BxVuMinow2rNNjlPQVeH0GFg378kDBfLAPXARjZbTPwmUXmOG+bgz71EKFfqKeAUWfREZbJxyCxyOOqEuHER4qrNUWovwy0CFktBHV4eNZMNvxyaaFhKWAaBt/HJwEo4W0luSKLMF8viVhp4iBeeBd8CcYqcQ1qi+CKS7uVmklYdcQY0+C42Ckkf6EmO51cVal3oRlCFkCdKgfCWtbo7obDO3AVWQbHHyUsjo40E6uq9cvQbdG+wN892fj8s0HjXDWKA51/t4JUo72H/jTDtybjSUkbyYsJ0gdfAtSjfTn+JoWQjCv2+57a4M1QaQSvZvrMsIs7RJejGcdUlLJUhzpZsYsZsJcCen6ZwCE3IaYA2021OfUdU3fJltmwni7Fvh+KDMF16KR3ux0lWuSdgjPxeNdJq/tNdKNqJaSSUyEmVK6JNPomtqbIh3eSKNsEmvAarfJ5LEzjbbR59MtpqyEb8eZjpndkhtxvNri3Er4YZxpx+yW6Jdhi8V5MOHm+n0QZ9afo0u0fQO8A5S3iPaQ1cTSG9w4f/SqesZBH/gRWI6T+gyyxfkgvw2cMdrS+/lTzpZvGnyWxsnTwHLRd4R2a/OBqQyoztKBe/P2qp6DCBOUptKHhuA+pU1fq2Co0/F0L9CVaghxXTbWW9ktKg8lrFfCrwODeh/9wgu1bEDo6OT2Fvgb+JLWq+nQEsnaa5UPJbwKBxc8A9KXPG1O3u+u6E4F24GvD3XMDjCxFcF8uTdhjGpHfwn49L42lCeAdyDZwGi3HpwAPr6+Q29htn1ZPoSwfuz3ewShXVcBNz62lzkvq6O9DjZHgQ9p72kdQljvob9VBPAN9Q+UEQmpw5b+Sf8e0FotI/4a9ZN8bIcQXlnh9AD1y3ychuhgU0tpJyhb14epn+ljN+Sk9S9G1ct50d8SdgF9x9EO3lHB5hXwPEYfA8dbGD9LuWZBtfj0inSQWUDTKzu1dAB5Dkz2tdOOHn70LvwVyMag/FYwzse295Rukq5j+G1wEOib66PAy5FPMD46+NPmqTV7CpwGGvkJPm2l8z8GWDNDloqpGQAAAABJRU5ErkJggg==) favorites
  28127. * - ![home](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAEK0lEQVRoBe2Zy28NURzHe/vwqEepYkFIQzxWaCOC2HhELEgQImhXIrqyIEXikVQi+gdIwx9AItg1NiJELMSGhKQbobY2VY9Srfp8m5lmTO/cOXN7Zu656f0ln8zMnTNnft/z+505j5sbGxurmk5WPZ3ESuu0E1xbigjncrka3jsbftClIvsU5RZ65aLK5Lj/C75SzSjHWCuJYLxqhPXwBgYhylq4sRaixChDP8EzGIJ4UwNnCR6tgFswANegKer93LsLim4herm/JKqO8O+ZRdhL42acOwunYAacg2Hu3ePYj3Ph1A1fU2ySmZSZeCiTjxaC1LAboRs6QGJl8+AKXIU1kLqlHmHEqlFboQv2gD40QdPHqx3qKdtJkD8Hb9o+TzXCXmT1cboB+cT6evTVPgIXeWYl6DoVSy3COF2Hx0rjTthp4L0a/4xXrofn33OeqH8avKMqFcE4O4uXb4ULsNfEEa+M0v00LIIuCKc/P03NrAtGrD5Iiuh10Dia1JTOR0EZsjjpw3HlrQpGbD0v3AzFig36e4CLkeAPNs6tCUbsHBxS+mpsLSayYT2KtLBqVgQjdgFe7QP1u9VWPbRc2ZQFe2LV5zSBWG7ZP+vVTUkwYhvx6DicB+fFqvWKFuyJ1QxJ00It48rCNNgnNi+N23hQaVw2YiU0cYQRq9Q9CJdBKV1q02zMeEaWSDBil1L5JTgBDeCCzcUJ8cXImfACOeqayjbBffgDfqu6cPyJP3dgVZTvwd9jdzuoSFmgicRDGAYXRIZ9+I5fPbA6KC7feUHBVKD5rJZ1EutaZMOiv+HjbWjJJ9T/LVIwDyqyh+ApuC7WFy/RCk4r5HyRwWNewRSW2N3wGv6CX2E5HBWcB9AaFOqfTxJMQa1lNewosqNQDiLDPmqv+hFsgzpfrI7/CeamVjwnQZEtV7G+eEX6MeyHGl/0hGB+1MJdYt+B/1C5H9UdX8J2qJ6IMBfz4Ri8hXIXGfZfmdoLWr5W1zJ7ktg2aId18BuiTHNvDVUumQSNxDikLSdtBzdok0yCD8MyiLNmCqhxXBL9An+egNI3yqRT9z+O92FO/O2UuOMuymoqF06bUl53489MQw21Gm8lWmkRa6R/oVaMfT6lAmrsUVMNRa2HU3I8k2orgjNp5hK+ZLwPp/x+fR+0ONfMp9BfJ+qLmulpyze1zMtC8AACbkI/xAneQZkO0JiZimUheAjPn0MfxAnWVo3RiEG5oiwLwXJsmGFDK5iCxrCnGZNSOzVLra+EPDZ9T6EMCFVZ3KWpI8XV7uBTFcEOBsWqS5UIW21OByurRNjBoFh1qRJhq83pYGWVCDsYFKsuVSJstTkdrGz8L0VTv1i+NVF2CyTJDC0LX7E8HIx7D/Vrb3wDaLvY1D5QsI/6jXZUEwk29cDlckki5bIOY9+mneB/GfbU3e4Ey5kAAAAASUVORK5CYII=) home
  28128. * - ![info](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAHOElEQVRoBdWbXYgVZRjHXdf8ysjUQl011lbRIFEjM6Uu0iyiEDG86EItKoIuuhDJCgoioouugqKbgi4CKwulILG0mxLTUtMyTWQNPzLTPszU1cx+v+OZw9nZM3POmZl3zQd+zMz7zvs8z//MvF+z2nLhwoU+oaylpWUQvvvDYGiDdjgP1dbKRSccglNwlpxOcwxiLUULRqTCRsNUmAk3wS3QiG3hpp2wCbbDYfLzhyjMChOM0FlkNR3mw61wFeSxv2j8FayBrQjfmMdZpa1POA84UuD7cBzsHyHQtzHm58nVtpnEErgvzIB34Rj8CyGEVvs0hrGMaey+WcQ3LZhAQ+FZsG/1htBq0Z4b09jmMLRZ0U0JJsA0eAccTeOJ9Pa1OZjLtGZENywYx0tgDzit9La4pHjmYk5LGhXdkGAcLoPDcCle4SSxUbk5mduyRkSnCsaJi4IV4GARBSj6eALfR8sxunLEMUdzbU0TniiYho7ED8GvULRI/UV9cDbnrsauheXQCVnjmas5J47gaYJdSPwAIfqsPlfEnwRl/eBBOAlZROvXnGfFfUfXNQXTYCKsg38gS+B6bT6MEogfiTcKNuaIa87mPjHu2+segrnRBf8bYN+ql3jW+ntrJVNK6OJGw+VkVt+2M3c1DIrHsZ9WjPVwCxcLYQ4MqVQUf/Jjikt3VnnX4eauhoVlTZVw3QRTOhmWwjhQfCi7ppZjkjOf62FCrfomysxdDUtBTRWrCCZYK6WLYAo4aoa0JxKcu2x9CsYk1DdTrAa1LCpru9g2ese58lddD+cgT/9ppK2j8ONR7HLf9Um8B0XOCmpR04QoVmnQosDp4BHYD40kXMQ9zsPfgSI/hyNQhN+4j/34VVu/0g9b/nXbKFgJf0O8weV+rSa1tam1b3kUm0SB77sj5KUw18OhTE1qm6RWBy07t0O4S7veto8J6FLwbng+YHC1qbE0GDtnrYXeGKzsHj7NT2AejKgMJn36DODaASZEF1KbGof4hJ2vXM45cIW2nwjwKDyA0HXgDicyl4RpC5LovixHtalxnCcd4PwX0hTjcvEFRO5ICBRyoWNINXYo2Ek+5DJyP/6fgZWI9XVNs3r1aW3r1alxjIJHQqjR+Vt8L0fnpxzrmU+45pKzXsMG69U4UsHDYWCDjRq9zYFpCzwGLi5K5qyA+KQpSMHt5VtDHNQ4XMEh+s5R/L4CuxSIUKeDO8BX1pG4lrlDmlqrosCy0jxcoL+KK5PvgFbEOka8CKsgbRd0u/dDUPMJh7ArcXon/A4PwwxwyvkKkuwuKi5bwYqaDbdBNAP8wvn3kGQ+4RDdq1u8UE/YINUjv313L/35bLfo5Qte+xs5va5WXdFlrrRMImnkLCreaRxtSnE2i7q8n3VS3Jeq1HhWwY6o7k1Dmn/r3ZgSYCZ1g1Lqi6hS41EFHwC/QIQ0P5D7vbiH8Tq7DnD7Frr/qvGAgvfBnxDSNqcsOJx7Xe2FNjXuU/BeOAah1rHn8f0FJJkDlk85pKlNjXsV7KPeA34KCWUuM5OsN760qE2NJxXcBevBfhbCOnFqsB5G/72aQj8vVVuIN01tauyKFvPbuHBhEGJ6+hK/SSLaqBsPmrFfhZe9KND0q7ZtjiM+Ye0guIXzPS/atuPQflzLxlI4Go6AOys/wq+Gn6EoU5Pa1Fj6G7Dfpp0nfeT+EkXaOZx9jf+kJ+xqbAPcxy1vwhnOd8MuKMrUtB7fauz2HcsgBuuAQVCEHcLJ8RRHrr42kExpWqRPu3mYDTektGmmyhVe9x+QYJU/mVK5AHwF/QblU8nLWnyMrY6Rds69T4Kvd964tleDWhZUx6yItRBzo+7A8QcUEXQVfkZVB6x1zj3GfQ587YqIqw81qKV/dcxugsuiJ3OT/cr+lzf4S/gYXB0wfk69HwX8YRxN88aL2pu7Gib3iBcv8BpbDJ0QOch6fB0fNf+1HOVXwD2wE7L6T2rXic/FNbXVLLw4mNmfTuRMZi/tx8djUDYHPgAHlaSks5abs7mX/lrYI3a8ILqmwTB4G9xWZQ1uu7egHQbC/aBQR+88PpPamqs5D4t0xI89+nD1DTT0A9waOANJQeqVu+j4Ddx3u26vd3/WenM01zHVGuLnqYK9GXNeXg15RGcV0Wg7czPHjrjA+HVdwVWifRX/j6LNydzqii1pif8CSdc4HApPg0u1IqeQRp9i/D5zMBdzqjkT1NLS0BOOGuLYv+E6lWyFolZjcSGNXBvbHMxlQJRfI8emBEcOCeKo+xq4A+nNp20sYxq7PcqnmWMmwVEAgs4FR0Y32CGF69sYxpobxc9yzP3feMo7nJtJxDnWV2w6RPtsTnOZQn1118JH8A0ik/bWVNe33IKjEAh3qei87Ue5eeDTnwTNilfkbvgM1oHb1oMIdX2c2woTXJ0J4h3c3NyPgikwA9zjjigT7Xf3ce0XCfF8M+wAv3icQmQXx0LtP/qKurS9uZqyAAAAAElFTkSuQmCC) info
  28129. * - ![locate](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAIDklEQVRoBe2aaaxeQxiA3eqCltpLkWotLUUtsUuJrbUFtSSaiIjljz8kQhOJiAQRQYREYvmFSPrDFiSExFpL49JSS6u0Re1bLUVRz3N7ph1z53zfud8956sf3uS5s7/zvjNzZuac7/asXr16g25IT0/PKPrZAfaFXWAMvAEL4GNYgS1/EjYqPU07jKNb4sGZcBocB0MhlYVkPAgPYM+itLDWtA43BYY6m7PBZVSFXuqd2ZQ96m3S2ZkY/0lFR+PBcFlf3ZTTjTiMwQfCR4WzfxO+D8/BTxA7Vxb/nXqzmnC6docxdDg8WTj2F+EtMBrMPxiqzvqn1N2nbqebcHg6hoaZfJn4sNho0hdB2cym+bOoOzRuP9j4EBTWJuzII1F2OngEuZQfwcBVhLG8FifaxM+jfHybOgMqrtVhet4OfH6VHsjpn9xXWu3PRKrtXK1qtVo5g6q1zNfyzJ1UFOnwCcz6ZqEq8bHErwzpCqE6JtHOsBap2+FNsGrjyLIjid+PvYfBDOJPwJSovEp0wyqVqtbJ3Xqqts3Vy83EKVSUTiWns1Nd2WesY2U0XAHfDkZBpu3vbHzu3rVI3Uv6G6z6oBbL1il5b1108LG6Hf4ak+YO3qy1Gl4ltnhtqoZIrQ6z8lZi06PwWw22qUJdn9Wkq09NrQ4Xhs0hfLgGI99Fx30MotfT+sT9oG6wbhzMAzebTviRdufUbZf6anc2GInBh8A7HTj8A23Ogw2DrjrDxhzuG80118KHMP7XCo57934Ljq/TwVRX4594cGADblmXEEyDqeCrYiy+XPhC8RzcioHfETYmXXE4WI/jXi1PDOkiXE44CUd9pWxcmtilWxnt0k5lVbecteNuO+xsplLrOZsqT9PddviL1ADSn2fyGsvqtsO5N59c3v8O1zUC3Z7hDzHcm1cs5nVNuu2wr4+pNHrupp3V/cUj1d+X5vwdTsS+RmYqjKDcT0N/cjz9kSmvNav2iwfGj8HCfcDflXaGbcGPezpsuBfEsoTEMvAnFmf7K1gCXjPnMwhfEtYmg3YYB30s9oeT4TDYCbYocGY7EWf6+wJ/qZgDj0MvA+Cdu2PpyOFiifrJ9SS4AHYDv1bW+oURfUF8J/bjgj+l3gteUZd38ggMyGEc1aHJcDb4k4nLtZW4RMMy/YW4LwonQHz29hZ1NiV0yW9VhASl4rK/G2bDAhyv/JGgssM4668K58OFMB5io0muFZ+518CPb34EWAga9VuxMvxlMIhH1FGUvUCZb1G7wu4wBfaAg8E9ISe2/RjugbvQUe1rKRXbvhOj8Ax4AxxJO0pxw3kEnHk3pezLO/mbgV81Q3v17ZmzgXxXk7rU+TSENmlo3y/C9JyeNK+lsyix08vAWUs7Mq3BL8GxMDpVnqapMwqc/aDL9lum9dI0ddwETwX7ctMK7UNonndybc0OdtBZ6jANh8GV4DMYFMfhj+TfCBsFZe1C6urwXAh6Kjkc9NLO5/wW+DXSEXQZausVUPoTa9ZhGvh8OqI+F7HCEP+I/JnBkKohbXS4N9HZdoZT/bR3JssmwpmelrYJ6aEU5mRPMp09l1JOlpI5lo1mFmHYvDyPXfqzUb6CMCc+b4thv6LQgTMvK8VGdhaFblwu2yD2uQRy9m1L/s20XYYd7xH/twTPQ0ipl4XrwY/pYUbT0DKPmBgNnwc7BV1pSJm674Sg73Xio9J6IW0Z+MyrO+7Li0nZsla39unD8KArhLkZ9iw8F0ZAmbQq+6asEfnO0nx4rIgvIiydYYz8mZnSATfPVNxjysSB9X/DboWv40o5h4+igod/Tj4j02XoaOdkHkauzBWYR5nOOcNSVeZQ0UtLTrR/AuyYFLrkvQn66HikrZMw1SGk5BooW84ukxGh7voOsWUjuBnCIxKHDvylqY1uNKnEm0Na5kiOTjPXR5ql7ixuD3uU9G/55mlZzuGfqeRI5cQb11T6yj0KufpN5vlcHwRHl3TixH2YluUMf5NKXghysgmZHuzzcXoRy6VsYHJt/QXCAZ4A6gkyoMu/jQo9vm9fBWUbqD4shH9LusYp9WxbBo5Q/EzE8Qcom5i2bZemjTelBYnerdq1S8tpvzf4Y3lsUxzXdk+ALfq17ZexZiO4g8q+1cRK0vjblM9I27dKawD8EOl1FgZ006L+TNCZ1J44re03Qb8Ntt/Vkko+7FOh7OoWK/bMdefeoZWjoYx6nvFx+8oO2wdcB98nOmJ9Ie6V+PDQbxz2c9hCZGNwhNrNspU1+hO4FiZDq5uTDls/GGZ869igOK4uUKe67SNuG3SkoUeq9fvdsvp8izuI4zTYBeZClU5Cp559D8GFcCCMh82DXuJukrE+nzV/OewbeOuCbQ4FdahLnUF/u9CLzfMwLuhMw5ZfPNgNp9H4NtgdXOoDkRVUfh/cKX3mloM76u0QdOmA1793wSW7G0yEKTAcBiIOnndzLxvev/OSjkCappVL6hlw9NqN8PoqX4Vt3s/Hp/an6ewz3K/SmhvNDSj86T/otDZp25jU7ly6ksM2RIbADHgFBvJcNTXrOvpCYdOQnHO5vMoOh8Z0sA1cDi9Cq3fSphy1z2fhYsjuxMHWXNhy00JhqbCheWtyJ54Ox8D+0KT0ovwp0NmXcMYjc8DSscOhJxwfRnxHGAfHwQFwBIyEwcgvNNY5HyHxHF6Kox5rHcugHY57xnnPWS8t4lHmIHjEeNyMBXf67WACeJNbDH+Ag+ax5fE1D5YWcd/cVuKkR04t8g94XuILUVeybgAAAABJRU5ErkJggg==) locate
  28130. * - ![maps](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAADl0lEQVRoBe2b24tNURzHjfutXEPycDAltwhJbuMSJUqSB/HiES/+AK9ePc6T8uCFkImQW5KGkdwSxYyMGkZu45bbDOPzyZyTrJnjnDkGrVm/+szas2bv397f33ftPS+/Vdba2toj5igj0NcfRkG/3qWIJdcIrs/AO6gDq7cKPkOjUNAmxr8ePJsix8NUWAvLoapowSQawIUzYCZUwAqohF3QAjtgGTyCy5x/nfEu1MNDCmAxuiS4Vy8ST4DZMB9WwiTIRUGC26q1gKtWwyyYBsPB5aLIL5CNTxzotDeWTeA5DUKuO4xXoQbxHpcUbSIzJFkDi0EzdLYnBNGuYJJ4ch+YAhvB5TAORsKvib4x97vwPpk2FjJuhibu85zxAlyCangBLRQib06u68t5vk4uVYVqgO+oqy9v5ASTRLd0LQNLYB24bAfBnw5zikX0HtuhGW5ANY9ylvEBvIY3FOArcz7rWHCpboBFMAxyGjguKIZy1jzYCqfAD5BLslB8J3dCP/AdOgo+fKHXd3Sebh+EctCMieBK6Oj8QuYrXZ7roQr88PiSD4b/IVyyfhB9jQy/uppTUijYhANLytJ1F/sxzL7POpg97vQdFfwVTNYtQsHdKpLg2O1ODieHI6tAWtKRGRrISQ4HJYlsIjkcmaGBnORwUJLIJpLDkRkayEkOByWJbCI5HJmhgZzkcFCSyCaSw5EZGshJDgcliWwiORyZoYGc5HBQksgmksORGRrISQ4HJYlsIjkcmaGBnORwUJLIJpLDkRkayEkOByWJbKLbOVx0r3E7httIbttwNvzddt//JWxIfQynYX8pgu2TbgBbjw9Ds53sNHJv49gOehu5bUe2DfjXojDVpWG/9iu4CEegBp7xfO+LFfyGC5+AiQ7BFXj/c8s+xw+Z24PwvYwKnQxLoQLccGEB7Hsu9t5ckjcU2QjuozgA5+Apz9PCmItCbvqWs2vhJpwBl8ZrEuVtOebPtiWLbf2ymyL0ZVT8XJgDbgHIgFsPOhPmr4d7oAnHue9txg6jI8EfueIaHIOrcAuafieSc/IG19vw7TYD6UEBbE4vhwxMB7cizIYhYPT6MeR+WjBFPoCToEgF1hb6bD8LNpHLwT0L56EOGkhUchc6edoNcruvQWoQ7/6GMTAa3E2zACxGNjRhH9wHV4zP9oGxqCjj7C0wA06Ay/YliRT/T4MCuGnEfQ4feJ5mfvdfaG+OXSWdju+VpAoIK3D9tAAAAABJRU5ErkJggg==) maps
  28131. * - ![more](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAADJ0lEQVRoBe2YS2sUQRSFp5MgvmLU+CAMiBJFDBHcCeoPEFciuHMjroMK4lZBcONG0JW60U1UEgRx59IXuNMoKEElKL7GRwyIqNHxO0N66FT3UNU9IHRNFXz0VNW5t+vW6RcT1ev1Sie1rk4qVrWGgn13PDgcHPZsB8Il7ZmhqXKCw6kt8WwgOOyZoalygsOpLfFsIDjsmaGpcoLDqS3xbCA47JmhqXKCw6kt8Wyg6XAURV2wEy7BM5iFtzAKu2BB0dqJ7YEtcBYmQblfwzjshUVt5O4mfhjOwwQodw3GYA8snpd77n9pFXMYvoP+qDaZZewcVKXPAzE64Qn4CmZe9f/AFSiSu4e4IzANrXJfZ24gXjO/KxEcg9+QFZQcU/CSONh2RKsraMQhr85xE/psOeN5tCr2APyA5Bqzfl9D06tYtX3wC7KE5pg2ZX98UtsR7XZo5ayZW/1DENnyzi18CO1nyMqTNXYcrTapcitHkBLJiZW2RaGRuxcg6+Stxu6i73fI3Y3uZM7cU+hXQeVvzsBP6Dc5LupxztzaiEGH3AvR3S+Qe4dc0D2cp/Uj1oPI1pR7g030n+erWlTe9pMA3cu2Jre+2ERtzBdZe01BL3Ke9Al6vQZsTbfKQ5vImH9PXxtqa3qVPbWJjHk94J6r4DPGhK17A8EHm4j7UAWP2nTG/GX6NWMs1SW3rrCroLeLaxtDqDdG4368zbHVkzM5Polus+2hEs+j7YNxx9zv0FkfhoncvegvOuZ+iW6rYhtfTXTWgV7OyeLM3w+Y3xaf0PVIzAqwFf0IzW7XnLGOmLUg58y1JvsTzA83Y5o/eLcyMQISJAN0z56G9bE275HYNXAU7kAy9xv6p2Bj3pyxntjVcBDuQTL3FH19Dg/FWh0bXzUMNhsf23JkOQzCK9B1P4NY39OFG3kjgpeB8g/AR/gG0+3mJkeF9Lp9lkIVZkDfC1r3vPs8VTAir1uRd1mpNyQUXGr7HBYfHHbYpFJLgsOlts9h8cFhh00qtSQ4XGr7HBYfHHbYpFJLgsOlts9h8cFhh00qtSQ4XGr7HBYfHHbYpFJLOs7hf5j4Vg3iLoGkAAAAAElFTkSuQmCC) more
  28132. * - ![organize](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAEdUlEQVRoBe2aS2xMURjHjbbqUaLoI7RChQUiGo9YaEqkoolIkCASSki68dixsLIVYmHbkJA03UgkFRI2QgRBKl4RgtJFK0jUI+o5fv/p68ztmUlHzpzO9PZLfjP3fOfcO9//fOeee+69E4lGo6PCZKPDJFZaQyc4N1mGI5FIMfUVkAfZMPaVwE54yqn6i+8BllQwravgAEyEv5DppsQ8gYPw3hqsJi0bNJ4El0GZzSa6iHcbjLbpsp7DDGX5V8ByyDbLJ+CdUGQLPNGQnkzj3TDFspN68BNkwhDPIY5poG/T1lBYR+LOkuW4uSeR4KXssN48grF9h20NdeukYLRL96Y6vAD2wCwwbQyFvXARPpoVA85fKnXiN4HtvP2Gf0tPG3XWUKNYT4E6PxjvD3x1EDHPZZvgxTTSDBc8gMrKbql5gKHeJh7NM6/AFu91/EVmjHGTFmN+HA3qYSoE7SuO8+zcEawY4vJdfr8Z/ljiqMS3AV2RvjpTPc7V0A623rqJv8RsnynbxDUXXieJuy/LfRmmEzSd7wKtroL2Hcc5BL4LVmRCmbheEIfmHduVQ1muQV/3BN2bJZyqaANbdm/jL+xtm4nfxKcsP08Q/zX8MxV3TDXqx+PYBGUQNHVAI9AsYrsuB9sPVflDT5xH+O7OZn8kK9msJf6G3ooFOOr66+O2NOVL6A7oP/njmmREQcN5LGhy1cLJtBwK++FSLqrVSGvPcrCZGu8DZTqTBSs+zUkarTZTUrerYh50gHYY7rSpRxZCCYTByvouS2FQK42hE9w7S/tKsOaIt/AGfoMWO3OgFLyYb8FaGByHl6C1r27jlsAh8HaN14LD1+x8jN/KNVdqlAvhgq8YfJ/DLYjVUDatk8J905HObd+Cf1rEaHTp5sSL+RacaKWWyO+8E3wLdi4g1QOOCE61x7Kt/UiGsy1jqcY7kuFUeyzF9ok6WA8ZvJjLtbQWEI/hXpLIW4N1rLyiPHV5hP9MsM4or2V7hlH+702XghWE3gAcTRKN3mjY7AZOdZbNCnAug4wTrNXSItCrmmYSZ3tGTNVAo+1nvCLOyLyeT9WC7WlqXNtUCq7vlpTlGkQMeG+Vio9j6NbxMOjtn8u7udjzaJcH1H3uLViVikCzLftqEtsKbeAyNh3LuWAdVM+yr8JsU8hgt9mvGh6ATousEKwgdcvXCMWDFap2mOYBTWK6b3YtNvYDrs9hM0i9BTgB+YMRTbvp0AS6bzaP43I7LUPaDFBvHPVmIy+ZaOp1+TkJX8Dc3/V22gUrYF1jN4L1r0T4NSPXg+sZ2dZZXgRr5m6BymCW8en6rc54BrYAXfu8CFbQmoQ0c1eYoilXw0NQp7gWZzueN8H68S44DbG/IPA9H66AL7FR12tpYk9qetOwGfSaVjcMNVAFie6iqHJv6bws2YaUfLpctYP+S5WoTVr8vjOMvphN4FN4N69Dybs6yw+OCLZ0yrByhS7DmrRaoQE0Kw5707JOf/UvH/ZKewTG/kscFrHSGbpzOHSC/wHSRhVOrpN3ggAAAABJRU5ErkJggg==) organize
  28133. * - ![refresh](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAG1ElEQVRoBc2aa6hVRRiGO17yrmloWpqhllH2wyKSEIsIo8QorLSbqVRgJd3DyFAQIyIiKQz61cUgpB+B0EWii4VkGFRUJpWKphZaaVZeutjz6FmwOnuvNbPWXvvs88HD2nvNzDfzrpn55tvrnLYjR44c1wpra2vrRr8jYC9j+KOzxmCnrTL7ng2LEN+rswbRSsH/ItL+Fwqij+8M0a0UrD5Fa0vg2c4Q3WrBik3sVj480WzRXUlwG4Lnw9OI7p08haqvXUmw2tzH8+AhRPf1RtXW1QSrz4i9CJYjepA3qrSuKFh9PeEWcE9XOtMtE0yyYYROojQfa0zRc8GZ7l9TWvJGj5LtCjdj0AYll6uD90HLQMizZKZ70vzOKjKypgpmkONQMxpGwWlwAvg9STLG8jlkip4FO/H3GKJ/DzXIK2/DQV554TIGdQaNpsNkmAAjoYpj5i/8rIIFjPlXruVMwY1Czy7X8+Al+B4OgU+yag7i0wjereyYqxDrDD4Ku6FqgfX87aGfR6BPGdENCabTqfAh/A31Btesez/T32LoXVR0KcF0NByeBPdSs0SF/Nr33VBIdOEoTVDyKFkCN0OlSQH+Ys2HsReMF66ueCuyJPDqzD4HvqEIzUCzyk1WtsAcKBy8opc0zgfBU+A52CwxIb+K3Qw3FJmodN0owXTgseNxsA9Cg2pm+S76vyktoOjn2D3sfjVAhFJBqmSax8km+BZ2gBnUlXAmhMyH+B3cj8DVocq55aEnROOJsB7MdIrOnnt9DVwD48G3lAPAB21evRRCPl3G22FaaKwx5blLmk4c2DNQdN+aaa2DKdAvayCULYQ8wYnYhpZxuv+QYGf3a/gnMLD0oH+h7mIYnO6o42fK/bX0MKTbpj8nYmd1bNvI98w9zHnbh8FcDSPBwcWYe/ReWMOgfEhlTbH6ugs/75Z1Urdd1tOi8qnwGcTO7j7qXgU9snym71Mva4bt70uYmq5f1ee6M8zsOphJoOiY2XVGlsEbDKxY5kOjlLmkt4Iz+z7Xyi1LjD/QJ4PLOsbWUmklGMkbsc00fqBZYh1Y3RnmvjnyWeDREbL9VHgVdjNQZ6is/URDxb5e1kFMuyzBij0ZzLBC5n5bzUAbmV2Titvx8V6os0bLs5b0aBz3j3CuyA/A36dlzK2zFTpFrAPMmuFRlPWzQsDMpN6BMoGqO+2+h9tiZ7Y9mBpXQivPIHoYvzXjyhKsUwcUsoNU2IRjj5JCRhtXx8rYRohV5Bh4EExP8+KFK24VfAT/syzBLmeT+5Ap9LdQpYrKFTwMrgcF55k/Tj6FGsFZe/gUKhupu5q5VGOCo7Nv3RrLEryLmgdqarf2hjPsyssac9ToshobjGKepO1jzuqowQQqGVNOj+zvMPVMdWssS/Cf1IwJRAa3CcSTmABX03nBG451DMTEFleniUyNZQneQk0zqJC5xHw3HTOIkK9QuYHqQsgKtOn2Ct6ZvpF8zhK8jQou65DZ+UXQ1ADHCrKfyTAWQubK/AH8XV5jWYI3UtOzLMZMQ2cyqGbOshnZDPBYCpn79xuouyWzBLskPodDEDJf394IXiu39vgwEccXQyjDsn/H/gkovMayBCt0Hdg4xi6g0rVNmuUT8b0AzA1C5vnryjT7q3sOZ77TopH7ZQOYj+oohH89NAuKeuPBgDL7Tsrw5SmwHEJ9J+W+bLR+/8RHx2tmpzRy3yyCfZA4DF23UfcK6Nmxo6Lf8WFUfhzM10P9JuUeRZfl9ZUp2EaYeycJAInT0NU/ct0HQ/M6ziqjnft0PLwCsavLMbkNV8OQLN9HNeUWHjtfn8eJiUhIaLrcCPkaTIHo2aau+3UmbIS0v5jPnrtz8vQEBR+tcOxVz3qcmWrGdJyu42y/BXfAJKjZW9w7CaaBy/djKDKrSV/mDCsg+HCj/qmF6DsPZ8tgOJQxV8geMBnwszPobCp2IAyFYVDGXE1fwAwmaEvQQWgJtM+ySYWC90PyVLvC1aPHQHl5jI6jWqIrHpuFl3F+oAuJ/pGxzIXoP4znRumODwPHI+BFcFm2eoZ907IEBnQcZ973QoJ1hLnnXoBWiXYZ74D50CtPXL2ywoLbRRtwloKBqDNnWrEGvOugVEZXSnC76O506o8GX8QbKZst3KPnTTi33szF3istOOmAAZgVrYBm/SeeD/MruAf6Jv2WvUadw3QUNM5q30ZcCrNhDMT8lKNapil0LayCtxG4JbNmgYLKBNsnortxccbPh+lgBuUvnlhzW3iumpaaofkzbzvXyqxSwelRIb4f3w1u58AlMA6GwNkwGEwhN4PZl0vWWLABDEr7EVr3BzxlDdl/zhnCj3tOo0oAAAAASUVORK5CYII=) refresh
  28134. * - ![reply](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAES0lEQVRoBe2ZSWgUQRSGM24YTdSo4AYRTcxBEZJDJCoigrtGg6CIgihqogfRgEERguhB40UP6kHw4kEET4J4E9wPAdeg4ALigjuKcSMuMX7/mAmdSU/SXdM9PTPpBx/T3al67/31urq6K5G2trac3mR9epNYaQ0FZ3vFwwqHFc6yEQhv6SwraBc5YYW7DEmWXUhZhSORSC7UwKIgxzAlghE5CZFHoAEKgxTcz8/gCI3gfzHsh6l+xnLq2zfBaC0miXpYDvmgu+kXBGqeC0aohK2D7TAF+kPamKeCETseZdugGgZDSp4RxHFsnghGqKo4H/aB5uoASEtLWjBiZ6KsFlaAHlJpbUkJRmwl6rTcFKW1SktyRoIROhofdbARhlr8OTkMdBPNlWCE6iG0AA5AqRN1Nm1cxbTpn9Qlx8ERO4pIG0Br6yDDqH3pV4kvPdRewCd4C+/ZPdWx7xZxsk1LgqvIZDeUeZzRT/xJ8Dt4BQ/gGjSSVzO/3psEJ4JoY+A4fATNvVTwhjh34RSshMGJ8jO5biuWIJqrc6AJ/kIqhNrF+EFs3fqHYRoMMxFp7dNFME5Hwi5QMLskgrqmgb8M+hgZYRXh5riTYBxpFM9CUKKcxlWOSyHPjVi1jQqmYy7shQ/gNGjQ7f6Q6yWY7UY07XNK4CK0QtAiTOK/J29tLOQ7EU67nIGgtfU1mARMhz6a3zegtCfRHXOYxhXtndJBgGkOT9FQ1Z3oDsFqhBXAFngJpkGD7veN3NclEt1JcKwRHaaD3niCTt40vh6+q2N6rL+2gtUA03p8FL6AaeAg++ntsNwqNqor/kL8OZ2WgF71vEpeq8FvC36uDveJM8qqyenHwzg67oE1MAxMTeLOQyNod0SDqO2hCaDVIma6u3R9OAxq/9WxW9PT+wRsQ7RiE7Gbj4f4v9F8Fujxb1ptfR2tj/cbf04bfbbqZWgsFEM5LITNcBLc3HF6iM2IxXAlWJ0wJXEQfoFb4RJcEwtu8kv/PCiEGdAAevFQJbvL5Rh/j351uRbcLloVmA83ewgUn0TSgq2DRGzloVt9E9yDFoiPqfOvUBHN3erA7TFOtG6fBqdfVp4KtuZLDqr8DrgDdqIPcb2/UYXjAmmu1cLDBIGswX0THMuJHIrgDGglsMZu4nxI0oItgcbjUHP7MyRaanwXrHywvlAFj8E6v+dqZ8MTI9BzHO2DtaC9KY1wIEYurXCO4JrbjyA6CvzO80wwznS3tMAFDpfBKdArnkY4ECOXqwTWUqZvA1mJp4L/+4wKf8ZxDeyE26AlLBBD9HUC14GWr8mezWEc2/oiiNZM/TumGbRLkdQ6nChOT9eJWw3ffakwjjuMRF5wUg9b4QnE5hOHKTVNsSuO3qW9SosN/Yn4KmAQbnnl040f4pelVLCb5Pxq6/st7Vfipn5DwaYjlyn9wgpnSqVM8wwrbDpymdIvrHCmVMo0z15X4X9rh8wHLEjawQAAAABJRU5ErkJggg==) reply
  28135. * - ![search](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAGdElEQVRoBdWaa4ycUxjHd9rpbm2bqKhiUavbVZdo0LCyLl3iHhGEkkZsKBYJX4RISHwQIYIPNJoQlUjTuCakUZ9oVGRF0GywslvqbgkpDarqsn7/6XsmM5n38pzzvtudeZL/nplznvM8z//cz5ktTU5OtuWRUqk0i/qdoAN0gcXgP+CkzIcx8APYBXbi82/SaZFSKGGILiTibnA+GADHgbkgSXZT8CF4GwyDEXxvI92r4k0Yoj1EeAG4CvSDEggRkX8VbID4lhADQXXUwxZgfAF4CGwFmgdFYQJb68HJljjy6mSSJZAZ4CLwESiKZJydb7A/CGblJZVWP5UwzueBB8AfIC7IovO0mK0B89KCzlOWSBinWoBeAkWTstiT3948xJLqxhLG2Xzw4jSRdQ0yiv/upMBD8xsI40Rzdu00k3WknyeO+aHk4urFEb4TJ/80CWEdYB4BhS1kdfswe+zpGNf80RYUIr9QSdgOdNCYCfaLcABpqFxBbymu3FIlDFkdD18B5wRYHaHOJvAeGCU4fa8IdnXUPAaoMZeDk4CvfEKFM7CrhswnbpxjZQX4C7j5Y0m1d64EXc5OWoqeFsPLwTvAYt/p/Iv+6jTb1rLKHMbYgWCjZxCb0T/e6qhWj3o6hz8HRMSRykp17l5WayfksyN8oafzTegfHOLQ1aG+blc6ZGQRdeVawB4GlWno7Pim1G9rB08AZzgrfRfdw3wdxelHvl/38K01Itc2Rf22Q8BPIIuoynXQL/SQj71DwcfA4n8nev1xjWfN0yGjD2gxsYh6432LolWHQL9F91Gj/j7oacUPFhE+11hbLxbrCFBzqWh5A4PDRqN90RZqVK9XE+ET67MSv41D9s3E0nwFX1Ndu4RFjkZpjkUxTkeEdTDIEvXqW1lKoeU0pOavXj10OsuSI1CYnaWUVC7COvpliR7f9CQzlaK5/LPBQRc6mstBIsIW0WXiO4tiDh35mIr1oS4kK2ENOctwqzPu+SX0MdDLjZWw9Pb1suyv7EPYR7cuEithLRLL6moW/0VriaVRtT1qTQkSER411Cyjc4pBL4/KEirPNRj4FZ3gXy5EWM+vWaIhtJQNf2GWYkg5dtWzui9bhuqn6OkVNUhE+ANjTZG91Kjrq6bDxHnGStqvcxHWsU5bQpZ0orCK3rDs21m2quXY6+DLTWBBNTP9wxbOKZZ4E63omLYZWG4r0nkQtOtwVASwdYeH723o9uTxS/3Ks+ytHk5/R3cI5LqIK2hEDw86XVkb+wV0Z+YiHDnWCjnu4Vj3Ug3DzhDn1NPacTX4HljJ6gFPr5e5RpZ74tFz6l0ezhWk5tFTYJFPEOjrLKxhrEazktWR8zVQ9vEVp1ttLYyplyeANQinN0ydIXBUnAOXR7nsrwAbgatrTbX3nu1s5Ul1oKgIRsZYMR/jy72gY0+u6a8OJMJX1P+C9MsaqDcPAseCHtANQkRTwHIoybZd21qR0Q2k1pZP0tNJSIubLhxJOr75egO/sjbekM/VIe0qY1RDb6p//PYl6/QniO0sF2tI2kBYRpBTgVrUOWqm9DPiGgghW+GWVBGj/UCvEM1E1sWinr4sKfa0/NgedhUwqsVITzvOUTOl6gxv0qmERRw5HOi/bHz2zb3VMHp28hremYQj0rq23QhGwFSQ0ZVPu8NvAfa3Use8kJkI1wzxxRhfDcYDAotrKF0GngYnRA17D599f7KVXcVzmoszLfUi7AxhfBG4GKwFPudhBacnmpfBStDwnzrkrQIhpDW8L3ExJqXV/wBA2Vs4WelquT9Qzy8FvdHnDlKR01RQ8OrJMaAp8TnYQUA7SBsEm6pzPXgcyI6PaCG7Hdu6VcVLUkuE5ONBR8ByDGb42sPGteBPEDcV0vK0ZZ2Z5C9oSCcZKzqfwO8OJK2FbCAunqYmrICRQaA3rLRejSvTWtGwTzc94Yj0DQS/O4C05nQd6VYhrIVMpEN6Wqv3crBngY4b582aR9DXgJCFTPt05T+AtKq2jNARzxLs/UBbnY/0onwLO97sXPuwj8cidQn8OuytAe0edjUyuluqh2vIPcNnPS1rIbOKfkRf0pKEGdqSJyFwM/AZ3j+2JGHXpZDWWf4+sMvlpaTal7e3xLYEsdQ4ITIIsras29AppxrKctRM5ZDRLUvv13GnLl1p5yjellylCb5BolvWkRQMgT6g6apXmnVgPWQrc/1/boJCaHVWyukAAAAASUVORK5CYII=) search
  28136. * - ![settings](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAIkklEQVRoBdWZd6yeUxjAe2lLUbVKrFaLUhUVo1pbQtqqESOECGLGH2IkCP8YQewYtUoTKmkJ/2hVEDFixN5FadXWBjFaq0b9fl/vuc5973nf9xtvez9P8rtnPeec5zn7/W7HsmXL+vzfpKOjYxVs3hR2hlXhT/gcX94iLBYd/r+BR2vB+eBsyVJ4FPqX+eJItbUwm8rmMEZDTRAMhG1Nd4p+bABbmUZlAGwLI0D9Lmlrh7HV5boHOHuPkL6LcCisDztCEJ1aBxwYwyvgMbgfToD/pGwJ9FY5FjoZ42AuhKX7N/HX4Er4Psq33PQ0eBz+APP+gbfhAOjQl7bdvxjYH86F4Gwc/pWT74DEesYXwWWwtg6385L25J0FH0JWXOopyfrjDC+AmTj7sxWyCua1hWCgs6Ox58GPTRr1FfVmwBuhfts6rIH47NJ9Eu6BWBwM9+xU8HqaDA5OLL+ReAmm044zXZPlGzmk2iDklHUSvF4mwU4wHEbCuqDo7OdwKXgK/w4DwEfIdVC7vgjVcxnPg/fhHZjVdocWRmn8faDBKRaTf4srPoa81eFocABS9cy7ra2XNAam5BcyvZqy4vL/Er7OFsTpdnW4yK5+OBCWd+yLjw9neY04Mxsvajiru7LS3qXut2/Aq8mZ6zp0iPuOnsBeH0wYi1thL8jmW99l7ux/1G0fxHui2TiNOojdaLQt6vcF38tbwyHg0zLel57AD8Io2Ay2h+sh3r++tl6AI2AbWBv62XAlwogPoyFPVhvuJpRpyCwc/7hbQU4CPWdlMfWWEFrX2YvFpXskTIRFsD4Mgqy4Qr6gPZ+ny6XR0c/Tp7Up4GdaPBNx/KG8unn5tOV+vLOgzbj9VNwD7gHYMPRRyR5mJpyBIVDU3lD0/ISrS9B19U2A4+uqkFZywMbCYbTnqig00PJ6xYNCPCnzZD0KRuQVJvJty089PyJicdY+hfggs7y2fAl/MBGJk+DJ7grgb+YCz6ZRceY8OHaEftly08ho+AQ0IrW0zPsWjkrV72zDg+VwGB50iHse3AbhpJ5P/AzYBz6E0Jf9egqfDieBZ4Vl38E1MKirzRBJhSh6ED0D7k0bvAA2gVVifdITwQd+MCAVOgMXx/WMIx42J8M88Ep6E7YJesSd5SthBuwOzvxweBhCPw6IV5nL1y+pPWEqXAJd+7fWX2g4G6K4HTwHGhoaNnwZDoLVQh3iZ4NXRayXinuV1N7vtc779NmN9NOZejr9FowL7WdDyjyVb4TQhzY+A7Vv3qBPuquvrrwQiUMUR8JMyDobOlhI2dXgIbQaXAvhV4agkwqfQs+DxH11PrhqUnou0TkwNrYrxMn3ADoMXgUnwIm5Ano4GOqEsMceppJ76REomzGX0bNwCrgMnZmU8XGeA3UizIK8wQz6Ou0+HROMjUPyXboOngyArhUX62XjKYcvp7IHTOi4N0MH5eGs0a2kXVpZ8fBYnM3spbSrxqVdnWRHi5Y9Ne+Gn6E3Z1dnn4fBWRtbSfdY0jaGjAYf3u6j3nLabbVfK86l6qaWNP3UllGYZdMrWzzxJ8OLVXdcO8ZTjfL29CP7VvD4r71DU3qJvPnkfQ1hZWxGfMuEXl7WXxQ8AacwQ9/kKTWdn5r2kEejO8DbUM+V8yR6x8II8CM9XBdbEffJ6FVXtkUsXwC7BhuqDpN7OHRCx951flgvgTBj2XApZX7CDYHci5+ywXAOFD1QbGsq9A02VB32pXH/26Zj/cEL3JkZCs6MT7+DwfyU6PwUuBDDCq8yyr+ln5vQ3RB8ZaXOD+2xv2XovkK4AD4CB9yB+o12XG1Niw/xLeBA2Alcji5jr6Z6xJfWQRihQXULzsxG2T7rER8fbqu54J08m/7eIWxarqJm0TLLLuGQ1pCjYFUMKNwa2XLq7Au/Q2ir3tDZfQoa7jPY4LLym9Pl3Kg42q/TUDNLzDv+tUY7RF973RJNS2of1duYDv9Sr3JGz9P4jUxePUlXgnWbllYcdmY1oFnxvl3p0orDrdTV0VbrNzVYrXS6NT3mXVdlxng7bF+mlCi3Xkuiw57QzRw8Xl9DuGKaGbSNqbsrNCpuIX+YaFq86KfDuuA97AnorPl2Lju51TkTXoe6Dy8GyFm6CLwdysSJ0EH5CfwFZEqTNwNVO5+CtcjymRpKfDsY1UlI+6NZaiZ19CyYhhHey6WCv0egdDf4a2RKfiDzPVgI78OczvAD+mjphKYdjtmSRwMqPh1/VTWHz8g/AZK/Wcfto7MfzIO8thy0B+M6VccLHaZzD6aXQEPyjDTfc8CtcQD0eAWRtwdMBWevqB1n0FkdVbWjob2i7+GBdHwpnAZrQj3yPUoLQKMXwXowEhy4wVCPOLjT4AKMtL1qJXieDellEvgzS9GMrKgyz4ZTszZVkU4uaTobBrPB19CKcqqoXZf2fBhdhZNxGz0cphOvm5uhbL8VGVxFmYP9BAyMDW41nrpqDqGT8ZB3bVC0UsQfJfYGr73KJOXwLrS+QQM9NHo3NqLvw2hcA7aUqqYcdu/6ovG0LJM5KNwBX4LLuEz8Geh28OebMrE9T/p7yhQbKk/tCRrw55eXwaddaj/6a8VMGAP+93AyeBendOO85zr1hxNOA5+McXmIuwr8ifaklH2t5PU4tEJjdDYWfCdnHx1zyTsG1lAX6YAzIc/44ITh/epHffhQ8feqWEdnXWGTgl6VYa7Dnc7sQ8fvgiems3ov+M7u9poifSh4d8aGp+JXZ42nzibgP7eXgM5+CuOzelWlCx3udNqZvgGOg+QVQb467mMNTjlqnl87J6cMJ9+zZH+4BfZN6VSVV+pwPR1hpA+VNyFvz+vwJ7B3Pe2tSJ3UKY1dDctX1PBzTsfyxGeq26NXpRKHmZGleOEV4pLOk4Xo+XrrVfFir0r8bh4EG0E8057i3r8eTL0u/wJCZSL2DoplLgAAAABJRU5ErkJggg==) settings
  28137. * - ![star](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAFfUlEQVRoBd2aXahVRRTHz/Ujv+2mZRGZB7W6mtpFikC7+UWUZiqBD0JPFdRL1EMFPfjoU4baS0FUD/UWZBEVShA+BCpmWApRSkgllNpDmZWZt9//eOay72afvWfWOTPn3rvgz8yeWbPW+s/XmT379AwODtZSSQ+CryVgA/gVfIx/pelEhFMBVlvBOaBeFo6Cean8y09KsnMg932TqCOs9M2UhMfhMJVsxtHcAmcbmekLCsqjFKUkvAYG1xSwmEHZqoLyKEVJCDOCNxH9HUCbVl6mULAuXxjrOQlhgl8Bbi0h0Uen3FBS37GqVIQHiHh2SdR16jTlo0t0woycpuxiUDSdHcFeMv3uIWYanTDB3wIWVZBQHP10zuQKvbarUxDWT1HRz1E++Ds99fLtgp6jEmbExhPNcs+IbkZPiCpRCRP5TPCQJ4MJ6A3QSUqjSWzC2ozuC4j+fnSnB+gHq8YmvJKIJgVEpRPX9QH6waqxCa8PjEhHT981H2j6qno0wqzF63BhOUxsom3Zb7aJqGsUjTAONFJlpysXQz7VuXpavrBTzzEJaz1adlzNjHs6RTBvJyZhjZTF/kTaWZZCnlvhsyWgQkPZQpagzsX1bFlAXjGtDdAPUu1p3PPQhCCXkdwG/mta0PWLds060AuAnqtEOjpdbQR3VymX1P9F3UfgGJA9X9F92c/ADaQ2P8V0DJ4/kDbeYKaSvgI2AN0+OGJK1VAbSIhTOXEOybYll2kte77yD4rqrHyb85S9Cl4HtReAyI11/A7HpRq5PSD6oR0f3Rad+H7S1DvV7UgS+tc1cU3n3V/AWJ/SX8BxVuMinow2rNNjlPQVeH0GFg378kDBfLAPXARjZbTPwmUXmOG+bgz71EKFfqKeAUWfREZbJxyCxyOOqEuHER4qrNUWovwy0CFktBHV4eNZMNvxyaaFhKWAaBt/HJwEo4W0luSKLMF8viVhp4iBeeBd8CcYqcQ1qi+CKS7uVmklYdcQY0+C42Ckkf6EmO51cVal3oRlCFkCdKgfCWtbo7obDO3AVWQbHHyUsjo40E6uq9cvQbdG+wN892fj8s0HjXDWKA51/t4JUo72H/jTDtybjSUkbyYsJ0gdfAtSjfTn+JoWQjCv2+57a4M1QaQSvZvrMsIs7RJejGcdUlLJUhzpZsYsZsJcCen6ZwCE3IaYA2021OfUdU3fJltmwni7Fvh+KDMF16KR3ux0lWuSdgjPxeNdJq/tNdKNqJaSSUyEmVK6JNPomtqbIh3eSKNsEmvAarfJ5LEzjbbR59MtpqyEb8eZjpndkhtxvNri3Er4YZxpx+yW6Jdhi8V5MOHm+n0QZ9afo0u0fQO8A5S3iPaQ1cTSG9w4f/SqesZBH/gRWI6T+gyyxfkgvw2cMdrS+/lTzpZvGnyWxsnTwHLRd4R2a/OBqQyoztKBe/P2qp6DCBOUptKHhuA+pU1fq2Co0/F0L9CVaghxXTbWW9ktKg8lrFfCrwODeh/9wgu1bEDo6OT2Fvgb+JLWq+nQEsnaa5UPJbwKBxc8A9KXPG1O3u+u6E4F24GvD3XMDjCxFcF8uTdhjGpHfwn49L42lCeAdyDZwGi3HpwAPr6+Q29htn1ZPoSwfuz3ewShXVcBNz62lzkvq6O9DjZHgQ9p72kdQljvob9VBPAN9Q+UEQmpw5b+Sf8e0FotI/4a9ZN8bIcQXlnh9AD1y3ychuhgU0tpJyhb14epn+ljN+Sk9S9G1ct50d8SdgF9x9EO3lHB5hXwPEYfA8dbGD9LuWZBtfj0inSQWUDTKzu1dAB5Dkz2tdOOHn70LvwVyMag/FYwzse295Rukq5j+G1wEOib66PAy5FPMD46+NPmqTV7CpwGGvkJPm2l8z8GWDNDloqpGQAAAABJRU5ErkJggg==) star
  28138. * - ![team](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAFI0lEQVRoBe2ZSYgdVRSG+yUmnagRQYU4NbZKNLYKWTgg4gQOaDYqJIIGl4LixhBwoy50LSIiulEjCkpAUBBRURpdGceFMQ7YtgkOJE4xTjGa9vuedUl1Vd2qevSrFqvrwJ97695zzj3/PXd6nd7MzMzIQpJFC4msXDvCbc94l+Euwy2bgW5JtyyhOTpdhnNT0rKGLsMtS2iOTpfh3JS0rOGQ+eLT6/VWMNYJ4NjUmN9T/xLs4WfqvPxO7TU9DkTdNmvBbeAskJ7kv/n+AjwKXiSW7yibFQk3BSIPZHdTl5xZzML238DDYFlTsQS/jZF1AGQ1mAZZkkXfe9FbGwJrqmz6lL4cEmOgjhyO0jq2gGVj0hhhAl9M1FeB3gDRn4Pu/5NwQnJ0ALKqrgKHDmgzkHpjGR4oioPKP1H96+Dn8GvpKyLqneV5Lp0XgnHggTMFJjlYPqAcpnyLsz/LHBLL0fRfCzwbvNN3gLeI5WXKaik7DbF2/20A28HPYF+CPZQfg9tj9vS5h18DRSdyrO0j9FeW+PQenwTe138AJ+d34OPFa215zDa0l15LOLgamM0DIBukbQ60JjhLl7RL+HWQtSv7jhLGz1FgM3DJZ30Yy69gYzqGonrVHr4eJ+OgB7Ji2xi4lGUW8+PsD0vOwNGNwInMirF42K0nlmXZzvR3LNARDN3fx6WVI3VJF50Fzvr7EZtY8zQdLtUiOYXGIrJpXUmvTDdk61HCKEqiagD9SSwnLCeX3RYwSJafRd/zoUj2FzVm2hyzMJ6gV0Y46Myl/BzjeqfnyMg36G5NJqpoTPvnLGWEnS0f9lVStL/7NgT/C5XNoHTW6XesV4En/1wlGo+Oo4QJ1ivoxxqju+fKCG2lf1uFH7P3eEl2K8xndRt3VKKEE4sPKWOHiCreg28TaPR1RN/X6GwEO0GReJ3cg95kUWeqzT8W6KtMpujcVaZQRfgFjL8qcbCDvndi/Zz0h4Hr6L8JHBHRW0L7DejdAU6K6Nj8CfBQi4mH4xYmrmy1sXlK/gCAAyfkQaAT91kWj9HW/6tJ8MO3NmeC+4CHlqdu1q7o25Xk5Hqynw+WBp+hpO1K4JItsnfr5GyCbSirCHstnQpcKulBXMK+o1frCPGgWAomwL2gLsm0z3S9ny38XARWgEXJOI7xNMiS9ns9MN5ZCQhEQ1lIGCOXmZf4ZeAW8C4IAblv3wBXAIn6sjkZ3Arc80FvGKW/nu4H/nhZDiR0IngI+LYPY3i43gWuAeNgFBQSn0UYJZejRH3CPQ8cMDi19Jp6AviuVfd48ADwRZXWG3Z9J/6fApeAJUm2TYRE02OZjPfA3WAM9HVDdvt2iXHI1HkoPQd2g7SjUHef+NyU7AXgFRD65qOcZrybQXgFmtUDIDu2xE3CBuCWWBxIU+8vk9MozdQukDUO3x4qm5IJOp36ZyW6waaJci/jrkviWEV9qiQOdd8Ebr/+T0fKkYvBp6AqOB2fnQz0SA39Kn9z6Z9mfPeze/UlUOXrB3Q2AW36a77KwP7tYCwh7Mupjk1TOmZuNInlyZqxuN8n3ItrQF1xryvRl9W/3Y3/60QGCTGF71h5JB0Tbn7vsDqyP6Vkva5dymxoVQ+lIE6+3+lJCH3Zcp+E78y2Fny7Evw7kstC8YA7BtQZRP1hiwTDKnuGun8aSiekaDxXwrbG/zOtaOT/ss3MLSjpCLc93V2Guwy3bAa6Jd2yhObodBnOTUnLGroMtyyhOTpdhnNT0rKGfwD3f6JVZi/xSQAAAABJRU5ErkJggg==) team
  28139. * - ![time](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAIPElEQVRoBdWae4gVVRzH97qr66vyhWbmurY+MA111dRMkLIXRuhG/pMVSUKGBGYPMTLDR0iaJBFUlIp/FJJlpWJS6vrAlCwTe1iaippSZipmPjL7fC/3XGbnzjkzc3fudTvwYWbO73d+jzlnzjkz96YuX75cUqiSSqWaYVs0hvZQBY3AW/7gYg/8A+fgPDFd5FiQkko6YZJUYj2hNwyDAXADlIOrHEO4A3bDVvgZ9hLfBY6JlUQSJkn14CAYAiNgFPh7kqpY5SDay2EjbCfxo7Fa25TVw/UBuw/BWvgT9HwUgl3YnQXX1ydWtc0rWRyr9zRcV8FpKESSfpuX8LMXnoDm+SYeO2GcXQfz4Cz4gyrGtSa3TaDHp1HcxGMljIN+sAGKkViYj+PEMRkax0k6csIYfgoOQVggxZa/R0ydoiYdaZZmFp6C0ZmgNTVu0YSzBQ6A1tuTYEqKk5ugA/SFkdAU4pbVNHiYpLWmu4vrztBSy83TcAai9pyeba2lz0E1tIFysD5vyMrgKugIY0GToW5MVJ/SWwltXPlIZh3SNNbdV9B/QRTH59GrhQehSZhjl5z2pucXc/4rRPEvHfV0B6dtm5CGI+B3iOLse/SehVgTiM23tx6bGuafwb8QJRY909ZlK7CHadATtOZFcfAmel28QSZ9jn0914/AYQiLScvW45Cen/yx5CSMYhNYA2GGtdGfDS38Rm3X6GpO0PNsKLPpBtXTbij8BGGxaWQODrThr0RxEuguuYzqeZ0Opf72tmt09TKxHU57+JLz7rY2QfXo3wpRkt6MXs7QrtPDKHSDfeBKVpPYjKBgXHW0mQVBz+HzrnZBMuwo6b3gilNb0Yn+9v6E30UpKCiv4WnoBD4ffuPea9q8YrE91asX9Rxb2loeBG9s/nO9YlZ6bWZf4dhc9EB4B2hJsBXtYd/AgAzHLfm0cfnYhvBlUE/aSlcE473CdMIkqyTvhU5eoe9cE8E8cvXulHwqxbvM3PRFeFzn8FqKbDTpdTQ6pof1BlQDtt5V7yzDySemYUM4Eo8mz4WgFwlb0RJbbYQm4e5U6JmwFe125tiEV7KepLWlFJp7goqW2WH0spbEkkacqOJ+UPfbylIMK+mGWl4lsLOO4DR69Tynv1y04DhSF5aiDcY7FllDqdbLSq0jmB7IKiXXkNYDrXFuK+sRHLMJG0I9o09zzEeOWDQ3DWI0lyphPbuqsJU1CFzDxdau2PVfhMSpiaupEh7uiEyJfsUNtE0IjqZFF2mmdi1R+j6eTriLI7T9yLT+/h/KBYLUHttWtPSWqYevtWlQfxjOOORJiJIaPRcJ5pAjIC1LnZVwL4fSEWSFTvhqh//IoszEtSekQYUSdpUTCLUsFbI8wOw5HvRNq75Fb3LOEpawa/Z2Gg4Q2mxpjdQ6v4KkBwa0i1Nl85G1EZZwVjGBE/Mx0GbqNgQfkvQECA3cZiSkPqWEtQG3lQoEiTxj2FkCW8E1SXVG/josJecqjnGLNlGuck4Jf+PQaIcsn4/vOSaZVLTE3Q0LwLVz095en3rXknQNlHMeWtBTLl1DFHdIri2ZtmZBaFnqo51bkmBT79660UE+vXV6DOZCVZh/dJrDUvC2956fRtYeSmaAV+A/vy/MWT5yfGr4PQNa9vw+/df6VDMRrB8NkWk0/gL+tuZ6G7JroOQeh5KU50Csz6lRbwB2NQyHwhYI+1Kqbe770D7IPvXaOmp+MAn6j5pDmkH6hywZ8yuY653I2gY5SaoO+y1hKujHMOPXdnwJnZwOoG52SNsJildFzlaCzYHqRyWVnMsOfsaAetsVyzTkdX674lrP7z5HO80F/U3CGlb6G4HLSS3ynLvqCj5fGX5ag37o/g38MX1HXc6Qzui7HolPTbv07MtFPzgKfgfm+m9kY/JNIp92+BsCmmhMDJrcJvltUaeXn689ekbfe3wSefrnWpOw9rHa3nmV/OebkLf2OyzkNf606XkNDsLbkPPrJHUa4hfAH6+51kipNnFm11cqtTa6Gko20zRsCEfiuREOgEku6LgKeXY58yasRTlsaGgjkr1bVzJp4tDHx8UQlKSp0+ozzhtnNmFVUh6DsI3At+hUeo0U+xz/KVgIJjHbcTU6dR4Df8Lat34cwdAGdDoWO9FMp5Tiezq4Hj/dAHVceinyxlkn4YxB7ViibADWo1fUnsafOmQW6KOErVdN/Yvo5PzKmZNwJmmtg6ah66gXgAHeO1ioc/y0g7kR49qIXqugWGwJl9EgyjOim6GJbCaE/mUoKIAoddgeDdvBdfONTDuuXja7gQlLmdIKwrZ5xol2ObqrYyC7BNicRq3HVm9YBPpUbHy5jifQe9Rl35pwJunBGNgV0ZkC0Z5V29BR0AHKXc79MvS1zdVmoy/Mg+PgStAr0yQ1BZw3PP1Qo2QtfEnQJLYY+liVggVHqF4O60DDXjsezax6ETf7Xo0iTUQ6toZb4Ha4E+IUbX1f4AbOD2sUmrAMkLR6egHo3TWfcopGO0G9oG2ieR2t4lw92g0qIZ+iz0XzSVYjIrz4h5XtGkvqgagTmXeoFfJcb0+B/8ey5mETBNVjvClMhjjPViES1s8qy6AiKE5XnXPSCmqIE23rBsIK0PNYiIRcNn/E53jI6/08dsLem4DTcbADdMddQSYh0we6t6BeW9pIkxZOrIUJrS3Cm6EG7gJ9TE+qaFbXLP8BbOZm76mv4XonbAIg8ZacV0B/GAvDQRNdPkVfOvQe+znsJ1HXh/tY9hNL2OuV5PWu2hyqQZsIra/6FCO6gClapn6AU7AbtDfXxuUknCHRSxwTLf8Bgi31NJnvpzwAAAAASUVORK5CYII=) time
  28140. * - ![trash](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAFBElEQVRoBe2aS4gdRRRA8+L/m0QIJkYNLlQUNOAvigpRcCEIcSsiCLoLLoILcaM7QVBX4koRshDxt9CFKCoiuvGDCP5QkxCiJhInRo2Ovzie80gPNWX1dL3uesM09IUz3V1169a9daur+031aG5ubkUpGY1GK7G1Dq4Cz9vKiIY74Sv8+72tkWQ7Ay4Bxo+Hu2E3/AuOZBf+ov2TsL6Ef5WNUsGazXvgEHQJMm77N/aeg3Mrh7seOweMM2bWYH+B2OES1/9g9w0oEnSngHHCYO+FGSgRXJ0NM/0idA565BRpKyxSt9J2B5xWY+Mw5Udq6uqKT6XimESlmX4d7sTnA4n6rKJjs7QSSgTrSno7nJyodtFyGr4AP4G6TeLIHweb4A44C0LR1xtgCzwP7aTtIkBvLlSfQjwNZyl7FNa0sU077V4DX0Js25X7cRjPzDb2Nd5FnK7xPbGXskdwxsxOLLRzdnwIj8GvkQFnypqobKLLrgGnOjMzP6cqJijzfn0NXPljmXRNWNC+dcBHM7HA2NELp10nwbaz5iC4OsdidTyrYp3a68ZFi7XJFfNsOBGcUmFnPpbiBWkVZefT7g+OXcTF0EUsFPtaje0Lw0LOzfoM49B4Gy36WMKwK+WDcC2cAmGwXK7YAAYdym9c+NiIdUOdnHODc6DjpPioix9LBvwtPE3QOzjWi7MjBS0M8CGY1huUA1ISg/4cNqXiqcqSwVqJ3AQ/QEmnpm3LR+IzsLYKMD4mA6bBOfAKuFpO28nS9v0Bcxckn9V1Ad9Pg2m/H5cONLT3Mf5fFGfX63hBQG8s7/LXxcdV0nvjMtgKp0MojuaroM60xYB8Z78ZTog6c515B1ylXey+ARe3/0tqFNCy0RjrkdvgOwhH0TeiB2A1uMBNGx9Ta+FZiP34mrIrQR39cECSUzqZYYIcR0mjJtmFwmHUvdenLjwmnUl7Eh05+LP40fjvoGTACYN1Rc6CecGhM7lw2lt+AA7Fg4fOespXgYO0j3pvnXmh3rY+/52+vrXtRSd841rQJ/WV1JVX9eNj14DnjeHnJVw8DBeAnX8A2ynfXwXN+cWUPQUOjNl6i7Jt1I9nCOe+1V0NT4AB/wkvw31QRIoFjDfnwRXgfVbJGZzsry44boTNUGVjlvOToPpV5FvbjXApKE7VLZ6UkpWlDGHH+96pV93/4TSsujGA8MeF51Xw6njuO3soKTth/UTnJQOeqONFlKsBW0SlfdVyDLh9NBkth4AzBqnXKkOGe52+DOeHDGcMUq9Vhgz3On0Zzg8ZzhikXqsMGe51+jKcHzKcMUi9Vhky3Ov0ZTg/ZDhjkHqtMmS41+nLcH7IcMYg9VplOWY4/Md88cEtHbDOVg5Xx9jpsM9Yx52JeAcw1ontTXRdcm9pFz3vBveHdNJN6YPVRhrnivtMlruZ5g7DFxBuXLut8j7sA/d43Yr5CIpJsYAJ7DN2/27Bsw1gwAb3I8wLOp+g4w6+nw/6HddOyszqWDg/Qv2bXFwH4+1SyhyUYtI1YLc85wXn/ORAagWdPVRKUqh3AJwtdTLeWq2rbCoP76cm3bjeLG6ELjZim03XJujyJqXF6rtmeDvGNzMN/ajEAZi2rKOD67t00jVgN7+3dnFgqdsu5XRc6tiS/eUGvBTTNengBIVZPuYG7LcYPjdluYk++bTw++pGyQ34bSy9B35Vs5zEYGfgJfg+x7H/ADoy2VfnrtXoAAAAAElFTkSuQmCC) trash
  28141. * - ![user](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAEWElEQVRoBe2aS0gVYRiGO1lmF8nQQlETutGFokAiqEV0ISKwgmrdMtzUpnW7drWKbFGbQAKpJIhuUGIUFUkW0T1Jq4V2U4ui7GLPexpDD+ecuX1jHqcPHseZ+f9vvnf++e8n0d/fPyZONjZOYqU1doLHRV3CiURCz5gMxTANJsJg+8XJJ+iBt9BHNdO1SCwRZR1GbAFRl8F8WAFLoRwGLME/ffAM7kETvIYPxPWDo7lFIhiheURaCVtgBywHXXOzbhJcggZoRvR7twy+76uELSEAtQsqySPwGdQN+KWDPHuh2DI2+TIVm3T455M9G0Bk6ktRvd4NBZaiTQUT3AQnSNW/VAFBzl/iZw0kq56FcOtuaQHB7QIv9ZVkrqZ2YA9Mck3pMYGZYKeh2sBz1SJb2mqcmfk0E0xQ6l9rwNoKcWjm11JwEYFVW6t1/K218mspeB5B5VsFluKnIuU88Kml4PGBo3DPqBGZiVkKNgvKRFkGJ5aCv2Z4xoi6bCm4DWUaXERhZhMJS8FfolDq+DSbRFgKjrIOa8poYpaCTQKK2sl/wSHfcFSNlll1sSzhn7ys3pAvLFP275lu+L1uKVhBPfYbgMf0zz2mc01mKfgbT7vi+kT/CeT3sv9s6XNYCtbg4CJ0pX9U4Kv3yXk3cO6UjGaCWX5Rg/UArqY8I8yp1qdPQ08YJ4Pzmgl2nCqwc2DVyKjunuddqkE0MVPBBKYSuQ7tJtEhFj9apDczU8FOVB0ctZiuHYUw9obMjbxErW2bmblgApTQengVIkq1B83QEsJH2qzmgp2n3ObYCEGndZ3krbcuXcUWiWACldCjoA0yv6a8J6HJb0Yv6SMRrAcj+gmHA+B3aneDPHXk/8jR3LR3a2rOfnAlTmfDVPDb6Khrq8bPDI5PoRPxZpMSk+1SgtOKpTa8l8BC0JaLmAkloA1xr/aOhJqEtINGWeqW7jjHXrQHbRdw4WxSJf8L8Aeh2m1QaWoBfiUsA61PTwGtUYeZ1qlP1zhan3YraBSnz/0mdAUVHqiEESoxKs0a2AxloJIMI5DsWU0vQH2z2oZToAnFI7+fu2/BiF3PgzbCKqgC1bXhNH3S6rba4BocR7TquifzLBih5XjcCSrROaAGKbJWHt9uJuGq67fgAki4zrNaVsGIzCP3dNgE20B1VJ+uro8UUz3Xr39UvxugCeEZl3UzCkZsBZn1+W6HRaB6qtZ4pJp2PtTna+58DFoR3sVxqHFxyM8euFsIW6EeXoDeoPrBXEEbAlpqqoN1kD9YY6rYxSQ4DGoE9KOSXBGZLk4NYB7CfigZEP1XMBfVEJ0BJUznIFevaSBzEEolOimYkyo4AfocclVYtrjViB0C9SzJEdE+jrn+CWcTrHvdUKuRUSm0gPrZ0W7tGjjMhTiIVWFWSbAGEnGxhAT/y+HhsL9oiVWFjo3FqnRVqrETrG5pFmiSEAuTYC3TFMVCLSIzTg9H6wuIXR2OneDfMJq1NmzzbS8AAAAASUVORK5CYII=) user
  28142. *
  28143. * You can also use other pictos icons by using the {@link Global_CSS#pictos-iconmask pictos-iconmask} mixin in your Sass.
  28144. *
  28145. * ## Badges
  28146. *
  28147. * Buttons can also have a badge on them, by using the {@link #badgeText} configuration:
  28148. *
  28149. * @example
  28150. * Ext.create('Ext.Container', {
  28151. * fullscreen: true,
  28152. * padding: 10,
  28153. * items: {
  28154. * xtype: 'button',
  28155. * text: 'My Button',
  28156. * badgeText: '2'
  28157. * }
  28158. * });
  28159. *
  28160. * ## UI
  28161. *
  28162. * Buttons also come with a range of different default UIs. Here are the included UIs
  28163. * available (if {@link #$include-button-uis $include-button-uis} is set to `true`):
  28164. *
  28165. * - **normal** - a basic gray button
  28166. * - **back** - a back button
  28167. * - **forward** - a forward button
  28168. * - **round** - a round button
  28169. * - **action** - shaded using the {@link Global_CSS#$active-color $active-color} (dark blue by default)
  28170. * - **decline** - shaded using the {@link Global_CSS#$alert-color $alert-color} (red by default)
  28171. * - **confirm** - shaded using the {@link Global_CSS#$confirm-color $confirm-color} (green by default)
  28172. *
  28173. * And setting them is very simple:
  28174. *
  28175. * var uiButton = Ext.create('Ext.Button', {
  28176. * text: 'My Button',
  28177. * ui: 'action'
  28178. * });
  28179. *
  28180. * And how they look:
  28181. *
  28182. * @example miniphone preview
  28183. * Ext.create('Ext.Container', {
  28184. * fullscreen: true,
  28185. * padding: 4,
  28186. * defaults: {
  28187. * xtype: 'button',
  28188. * margin: 5
  28189. * },
  28190. * layout: {
  28191. * type: 'vbox',
  28192. * align: 'center'
  28193. * },
  28194. * items: [
  28195. * { ui: 'normal', text: 'normal' },
  28196. * { ui: 'round', text: 'round' },
  28197. * { ui: 'action', text: 'action' },
  28198. * { ui: 'decline', text: 'decline' },
  28199. * { ui: 'confirm', text: 'confirm' }
  28200. * ]
  28201. * });
  28202. *
  28203. * Note that the default {@link #ui} is **normal**.
  28204. *
  28205. * You can also use the {@link #sencha-button-ui sencha-button-ui} CSS Mixin to create your own UIs.
  28206. *
  28207. * ## Example
  28208. *
  28209. * This example shows a bunch of icons on the screen in two toolbars. When you click on the center
  28210. * button, it switches the {@link #iconCls} on every button on the page.
  28211. *
  28212. * @example preview
  28213. * Ext.createWidget('container', {
  28214. * fullscreen: true,
  28215. * layout: {
  28216. * type: 'vbox',
  28217. * pack:'center',
  28218. * align: 'center'
  28219. * },
  28220. * items: [
  28221. * {
  28222. * xtype: 'button',
  28223. * text: 'Change iconCls',
  28224. * handler: function() {
  28225. * // classes for all the icons to loop through.
  28226. * var availableIconCls = [
  28227. * 'action', 'add', 'arrow_down', 'arrow_left',
  28228. * 'arrow_right', 'arrow_up', 'compose', 'delete',
  28229. * 'organize', 'refresh', 'reply', 'search',
  28230. * 'settings', 'star', 'trash', 'maps', 'locate',
  28231. * 'home'
  28232. * ];
  28233. * // get the text of this button,
  28234. * // so we know which button we don't want to change
  28235. * var text = this.getText();
  28236. *
  28237. * // use ComponentQuery to find all buttons on the page
  28238. * // and loop through all of them
  28239. * Ext.Array.forEach(Ext.ComponentQuery.query('button'), function(button) {
  28240. * // if the button is the change iconCls button, continue
  28241. * if (button.getText() === text) {
  28242. * return;
  28243. * }
  28244. *
  28245. * // get the index of the new available iconCls
  28246. * var index = availableIconCls.indexOf(button.getIconCls()) + 1;
  28247. *
  28248. * // update the iconCls of the button with the next iconCls, if one exists.
  28249. * // if not, use the first one
  28250. * button.setIconCls(availableIconCls[(index === availableIconCls.length) ? 0 : index]);
  28251. * });
  28252. * }
  28253. * },
  28254. * {
  28255. * xtype: 'toolbar',
  28256. * docked: 'top',
  28257. * defaults: {
  28258. * iconMask: true
  28259. * },
  28260. * items: [
  28261. * { xtype: 'spacer' },
  28262. * { iconCls: 'action' },
  28263. * { iconCls: 'add' },
  28264. * { iconCls: 'arrow_down' },
  28265. * { iconCls: 'arrow_left' },
  28266. * { iconCls: 'arrow_up' },
  28267. * { iconCls: 'compose' },
  28268. * { iconCls: 'delete' },
  28269. * { iconCls: 'organize' },
  28270. * { iconCls: 'refresh' },
  28271. * { xtype: 'spacer' }
  28272. * ]
  28273. * },
  28274. * {
  28275. * xtype: 'toolbar',
  28276. * docked: 'bottom',
  28277. * ui: 'light',
  28278. * defaults: {
  28279. * iconMask: true
  28280. * },
  28281. * items: [
  28282. * { xtype: 'spacer' },
  28283. * { iconCls: 'reply' },
  28284. * { iconCls: 'search' },
  28285. * { iconCls: 'settings' },
  28286. * { iconCls: 'star' },
  28287. * { iconCls: 'trash' },
  28288. * { iconCls: 'maps' },
  28289. * { iconCls: 'locate' },
  28290. * { iconCls: 'home' },
  28291. * { xtype: 'spacer' }
  28292. * ]
  28293. * }
  28294. * ]
  28295. * });
  28296. *
  28297. */
  28298. Ext.define('Ext.Button', {
  28299. extend: 'Ext.Component',
  28300. xtype: 'button',
  28301. /**
  28302. * @event tap
  28303. * @preventable doTap
  28304. * Fires whenever a button is tapped.
  28305. * @param {Ext.Button} this The item added to the Container.
  28306. * @param {Ext.EventObject} e The event object.
  28307. */
  28308. /**
  28309. * @event release
  28310. * @preventable doRelease
  28311. * Fires whenever the button is released.
  28312. * @param {Ext.Button} this The item added to the Container.
  28313. * @param {Ext.EventObject} e The event object.
  28314. */
  28315. cachedConfig: {
  28316. /**
  28317. * @cfg {String} pressedCls
  28318. * The CSS class to add to the Button when it is pressed.
  28319. * @accessor
  28320. */
  28321. pressedCls: Ext.baseCSSPrefix + 'button-pressing',
  28322. /**
  28323. * @cfg {String} badgeCls
  28324. * The CSS class to add to the Button's badge, if it has one.
  28325. * @accessor
  28326. */
  28327. badgeCls: Ext.baseCSSPrefix + 'badge',
  28328. /**
  28329. * @cfg {String} hasBadgeCls
  28330. * The CSS class to add to the Button if it has a badge (note that this goes on the
  28331. * Button element itself, not on the badge element).
  28332. * @private
  28333. * @accessor
  28334. */
  28335. hasBadgeCls: Ext.baseCSSPrefix + 'hasbadge',
  28336. /**
  28337. * @cfg {String} labelCls
  28338. * The CSS class to add to the field's label element.
  28339. * @accessor
  28340. */
  28341. labelCls: Ext.baseCSSPrefix + 'button-label',
  28342. /**
  28343. * @cfg {String} iconMaskCls
  28344. * @private
  28345. * The CSS class to add to the icon element as allowed by {@link #iconMask}.
  28346. * @accessor
  28347. */
  28348. iconMaskCls: Ext.baseCSSPrefix + 'icon-mask',
  28349. /**
  28350. * @cfg {String} iconCls
  28351. * Optional CSS class to add to the icon element. This is useful if you want to use a CSS
  28352. * background image to create your Button icon.
  28353. * @accessor
  28354. */
  28355. iconCls: null
  28356. },
  28357. config: {
  28358. /**
  28359. * @cfg {String} badgeText
  28360. * Optional badge text.
  28361. * @accessor
  28362. */
  28363. badgeText: null,
  28364. /**
  28365. * @cfg {String} text
  28366. * The Button text.
  28367. * @accessor
  28368. */
  28369. text: null,
  28370. /**
  28371. * @cfg {String} icon
  28372. * Url to the icon image to use if you want an icon to appear on your button.
  28373. * @accessor
  28374. */
  28375. icon: null,
  28376. /**
  28377. * @cfg {String} iconAlign
  28378. * The position within the Button to render the icon Options are: `top`, `right`, `bottom`, `left` and `center` (when you have
  28379. * no {@link #text} set).
  28380. * @accessor
  28381. */
  28382. iconAlign: 'left',
  28383. /**
  28384. * @cfg {Number/Boolean} pressedDelay
  28385. * The amount of delay between the `tapstart` and the moment we add the `pressedCls` (in milliseconds).
  28386. * Settings this to `true` defaults to 100ms.
  28387. */
  28388. pressedDelay: 0,
  28389. /**
  28390. * @cfg {Boolean} iconMask
  28391. * Whether or not to mask the icon with the `iconMask` configuration.
  28392. * This is needed if you want to use any of the bundled pictos icons in the Sencha Touch Sass.
  28393. * @accessor
  28394. */
  28395. iconMask: null,
  28396. /**
  28397. * @cfg {Function} handler
  28398. * The handler function to run when the Button is tapped on.
  28399. * @accessor
  28400. */
  28401. handler: null,
  28402. /**
  28403. * @cfg {Object} scope
  28404. * The scope to fire the configured {@link #handler} in.
  28405. * @accessor
  28406. */
  28407. scope: null,
  28408. /**
  28409. * @cfg {String} autoEvent
  28410. * Optional event name that will be fired instead of `tap` when the Button is tapped on.
  28411. * @accessor
  28412. */
  28413. autoEvent: null,
  28414. /**
  28415. * @cfg {String} ui
  28416. * The ui style to render this button with. The valid default options are:
  28417. *
  28418. * - `'normal'` - a basic gray button (default).
  28419. * - `'back'` - a back button.
  28420. * - `'forward'` - a forward button.
  28421. * - `'round'` - a round button.
  28422. * - `'action'` - shaded using the {@link Global_CSS#$active-color $active-color} (dark blue by default).
  28423. * - `'decline'` - shaded using the {@link Global_CSS#$alert-color $alert-color} (red by default).
  28424. * - `'confirm'` - shaded using the {@link Global_CSS#$confirm-color $confirm-color} (green by default).
  28425. * - `'plain'`
  28426. *
  28427. * @accessor
  28428. */
  28429. ui: 'normal',
  28430. /**
  28431. * @cfg {String} html The HTML to put in this button.
  28432. *
  28433. * If you want to just add text, please use the {@link #text} configuration.
  28434. */
  28435. /**
  28436. * @cfg
  28437. * @inheritdoc
  28438. */
  28439. baseCls: Ext.baseCSSPrefix + 'button'
  28440. },
  28441. template: [
  28442. {
  28443. tag: 'span',
  28444. reference: 'badgeElement',
  28445. hidden: true
  28446. },
  28447. {
  28448. tag: 'span',
  28449. className: Ext.baseCSSPrefix + 'button-icon',
  28450. reference: 'iconElement',
  28451. hidden: true
  28452. },
  28453. {
  28454. tag: 'span',
  28455. reference: 'textElement',
  28456. hidden: true
  28457. }
  28458. ],
  28459. initialize: function() {
  28460. this.callParent();
  28461. this.element.on({
  28462. scope : this,
  28463. tap : 'onTap',
  28464. touchstart : 'onPress',
  28465. touchend : 'onRelease'
  28466. });
  28467. },
  28468. /**
  28469. * @private
  28470. */
  28471. updateBadgeText: function(badgeText) {
  28472. var element = this.element,
  28473. badgeElement = this.badgeElement;
  28474. if (badgeText) {
  28475. badgeElement.show();
  28476. badgeElement.setText(badgeText);
  28477. }
  28478. else {
  28479. badgeElement.hide();
  28480. }
  28481. element[(badgeText) ? 'addCls' : 'removeCls'](this.getHasBadgeCls());
  28482. },
  28483. /**
  28484. * @private
  28485. */
  28486. updateText: function(text) {
  28487. var textElement = this.textElement;
  28488. if (textElement) {
  28489. if (text) {
  28490. textElement.show();
  28491. textElement.setHtml(text);
  28492. }
  28493. else {
  28494. textElement.hide();
  28495. }
  28496. }
  28497. },
  28498. /**
  28499. * @private
  28500. */
  28501. updateHtml: function(html) {
  28502. var textElement = this.textElement;
  28503. if (html) {
  28504. textElement.show();
  28505. textElement.setHtml(html);
  28506. }
  28507. else {
  28508. textElement.hide();
  28509. }
  28510. },
  28511. /**
  28512. * @private
  28513. */
  28514. updateBadgeCls: function(badgeCls, oldBadgeCls) {
  28515. this.badgeElement.replaceCls(oldBadgeCls, badgeCls);
  28516. },
  28517. /**
  28518. * @private
  28519. */
  28520. updateHasBadgeCls: function(hasBadgeCls, oldHasBadgeCls) {
  28521. var element = this.element;
  28522. if (element.hasCls(oldHasBadgeCls)) {
  28523. element.replaceCls(oldHasBadgeCls, hasBadgeCls);
  28524. }
  28525. },
  28526. /**
  28527. * @private
  28528. */
  28529. updateLabelCls: function(labelCls, oldLabelCls) {
  28530. this.textElement.replaceCls(oldLabelCls, labelCls);
  28531. },
  28532. /**
  28533. * @private
  28534. */
  28535. updatePressedCls: function(pressedCls, oldPressedCls) {
  28536. var element = this.element;
  28537. if (element.hasCls(oldPressedCls)) {
  28538. element.replaceCls(oldPressedCls, pressedCls);
  28539. }
  28540. },
  28541. /**
  28542. * @private
  28543. */
  28544. updateIcon: function(icon) {
  28545. var me = this,
  28546. element = me.iconElement;
  28547. if (icon) {
  28548. me.showIconElement();
  28549. element.setStyle('background-image', icon ? 'url(' + icon + ')' : '');
  28550. me.refreshIconAlign();
  28551. me.refreshIconMask();
  28552. }
  28553. else {
  28554. me.hideIconElement();
  28555. me.setIconAlign(false);
  28556. }
  28557. },
  28558. /**
  28559. * @private
  28560. */
  28561. updateIconCls: function(iconCls, oldIconCls) {
  28562. var me = this,
  28563. element = me.iconElement;
  28564. if (iconCls) {
  28565. me.showIconElement();
  28566. element.replaceCls(oldIconCls, iconCls);
  28567. me.refreshIconAlign();
  28568. me.refreshIconMask();
  28569. }
  28570. else {
  28571. me.hideIconElement();
  28572. me.setIconAlign(false);
  28573. }
  28574. },
  28575. /**
  28576. * @private
  28577. */
  28578. updateIconAlign: function(alignment, oldAlignment) {
  28579. var element = this.element,
  28580. baseCls = Ext.baseCSSPrefix + 'iconalign-';
  28581. if (!this.getText()) {
  28582. alignment = "center";
  28583. }
  28584. element.removeCls(baseCls + "center");
  28585. element.removeCls(baseCls + oldAlignment);
  28586. if (this.getIcon() || this.getIconCls()) {
  28587. element.addCls(baseCls + alignment);
  28588. }
  28589. },
  28590. refreshIconAlign: function() {
  28591. this.updateIconAlign(this.getIconAlign());
  28592. },
  28593. /**
  28594. * @private
  28595. */
  28596. updateIconMaskCls: function(iconMaskCls, oldIconMaskCls) {
  28597. var element = this.iconElement;
  28598. if (this.getIconMask()) {
  28599. element.replaceCls(oldIconMaskCls, iconMaskCls);
  28600. }
  28601. },
  28602. /**
  28603. * @private
  28604. */
  28605. updateIconMask: function(iconMask) {
  28606. this.iconElement[iconMask ? "addCls" : "removeCls"](this.getIconMaskCls());
  28607. },
  28608. refreshIconMask: function() {
  28609. this.updateIconMask(this.getIconMask());
  28610. },
  28611. applyAutoEvent: function(autoEvent) {
  28612. var me = this;
  28613. if (typeof autoEvent == 'string') {
  28614. autoEvent = {
  28615. name : autoEvent,
  28616. scope: me.scope || me
  28617. };
  28618. }
  28619. return autoEvent;
  28620. },
  28621. /**
  28622. * @private
  28623. */
  28624. updateAutoEvent: function(autoEvent) {
  28625. var name = autoEvent.name,
  28626. scope = autoEvent.scope;
  28627. this.setHandler(function() {
  28628. scope.fireEvent(name, scope, this);
  28629. });
  28630. this.setScope(scope);
  28631. },
  28632. /**
  28633. * Used by `icon` and `iconCls` configurations to hide the icon element.
  28634. * We do this because Tab needs to change the visibility of the icon, not make
  28635. * it `display:none;`.
  28636. * @private
  28637. */
  28638. hideIconElement: function() {
  28639. this.iconElement.hide();
  28640. },
  28641. /**
  28642. * Used by `icon` and `iconCls` configurations to show the icon element.
  28643. * We do this because Tab needs to change the visibility of the icon, not make
  28644. * it `display:node;`.
  28645. * @private
  28646. */
  28647. showIconElement: function() {
  28648. this.iconElement.show();
  28649. },
  28650. /**
  28651. * We override this to check for '{ui}-back'. This is because if you have a UI of back, you need to actually add two class names.
  28652. * The ui class, and the back class:
  28653. *
  28654. * `ui: 'action-back'` would turn into:
  28655. *
  28656. * `class="x-button-action x-button-back"`
  28657. *
  28658. * But `ui: 'action'` would turn into:
  28659. *
  28660. * `class="x-button-action"`
  28661. *
  28662. * So we just split it up into an array and add both of them as a UI, when it has `back`.
  28663. * @private
  28664. */
  28665. applyUi: function(config) {
  28666. if (config && Ext.isString(config)) {
  28667. var array = config.split('-');
  28668. if (array && (array[1] == "back" || array[1] == "forward")) {
  28669. return array;
  28670. }
  28671. }
  28672. return config;
  28673. },
  28674. getUi: function() {
  28675. //Now that the UI can sometimes be an array, we need to check if it an array and return the proper value.
  28676. var ui = this._ui;
  28677. if (Ext.isArray(ui)) {
  28678. return ui.join('-');
  28679. }
  28680. return ui;
  28681. },
  28682. applyPressedDelay: function(delay) {
  28683. if (Ext.isNumber(delay)) {
  28684. return delay;
  28685. }
  28686. return (delay) ? 100 : 0;
  28687. },
  28688. // @private
  28689. onPress: function() {
  28690. var me = this,
  28691. element = me.element,
  28692. pressedDelay = me.getPressedDelay(),
  28693. pressedCls = me.getPressedCls();
  28694. if (!me.getDisabled()) {
  28695. if (pressedDelay > 0) {
  28696. me.pressedTimeout = setTimeout(function() {
  28697. delete me.pressedTimeout;
  28698. if (element) {
  28699. element.addCls(pressedCls);
  28700. }
  28701. }, pressedDelay);
  28702. }
  28703. else {
  28704. element.addCls(pressedCls);
  28705. }
  28706. }
  28707. },
  28708. // @private
  28709. onRelease: function(e) {
  28710. this.fireAction('release', [this, e], 'doRelease');
  28711. },
  28712. // @private
  28713. doRelease: function(me, e) {
  28714. if (!me.getDisabled()) {
  28715. if (me.hasOwnProperty('pressedTimeout')) {
  28716. clearTimeout(me.pressedTimeout);
  28717. delete me.pressedTimeout;
  28718. }
  28719. else {
  28720. me.element.removeCls(me.getPressedCls());
  28721. }
  28722. }
  28723. },
  28724. // @private
  28725. onTap: function(e) {
  28726. if (this.getDisabled()) {
  28727. return false;
  28728. }
  28729. this.fireAction('tap', [this, e], 'doTap');
  28730. },
  28731. /**
  28732. * @private
  28733. */
  28734. doTap: function(me, e) {
  28735. var handler = me.getHandler(),
  28736. scope = me.getScope() || me;
  28737. if (!handler) {
  28738. return;
  28739. }
  28740. if (typeof handler == 'string') {
  28741. handler = scope[handler];
  28742. }
  28743. //this is done so if you hide the button in the handler, the tap event will not fire on the new element
  28744. //where the button was.
  28745. if (e && e.preventDefault) {
  28746. e.preventDefault();
  28747. }
  28748. handler.apply(scope, arguments);
  28749. }
  28750. }, function() {
  28751. });
  28752. /**
  28753. * {@link Ext.ActionSheet ActionSheets} are used to display a list of {@link Ext.Button buttons} in a popup dialog.
  28754. *
  28755. * The key difference between ActionSheet and {@link Ext.Sheet} is that ActionSheets are docked at the bottom of the
  28756. * screen, and the {@link #defaultType} is set to {@link Ext.Button button}.
  28757. *
  28758. * ## Example
  28759. *
  28760. * @example preview miniphone
  28761. * var actionSheet = Ext.create('Ext.ActionSheet', {
  28762. * items: [
  28763. * {
  28764. * text: 'Delete draft',
  28765. * ui : 'decline'
  28766. * },
  28767. * {
  28768. * text: 'Save draft'
  28769. * },
  28770. * {
  28771. * text: 'Cancel',
  28772. * ui : 'confirm'
  28773. * }
  28774. * ]
  28775. * });
  28776. *
  28777. * Ext.Viewport.add(actionSheet);
  28778. * actionSheet.show();
  28779. *
  28780. * As you can see from the code above, you no longer have to specify a `xtype` when creating buttons within a {@link Ext.ActionSheet ActionSheet},
  28781. * because the {@link #defaultType} is set to {@link Ext.Button button}.
  28782. *
  28783. */
  28784. Ext.define('Ext.ActionSheet', {
  28785. extend: 'Ext.Sheet',
  28786. alias : 'widget.actionsheet',
  28787. requires: ['Ext.Button'],
  28788. config: {
  28789. /**
  28790. * @cfg
  28791. * @inheritdoc
  28792. */
  28793. baseCls: Ext.baseCSSPrefix + 'sheet-action',
  28794. /**
  28795. * @cfg
  28796. * @inheritdoc
  28797. */
  28798. left: 0,
  28799. /**
  28800. * @cfg
  28801. * @inheritdoc
  28802. */
  28803. right: 0,
  28804. /**
  28805. * @cfg
  28806. * @inheritdoc
  28807. */
  28808. bottom: 0,
  28809. // @hide
  28810. centered: false,
  28811. /**
  28812. * @cfg
  28813. * @inheritdoc
  28814. */
  28815. height: 'auto',
  28816. /**
  28817. * @cfg
  28818. * @inheritdoc
  28819. */
  28820. defaultType: 'button'
  28821. }
  28822. });
  28823. /**
  28824. * The Connection class encapsulates a connection to the page's originating domain, allowing requests to be made either
  28825. * to a configured URL, or to a URL specified at request time.
  28826. *
  28827. * Requests made by this class are asynchronous, and will return immediately. No data from the server will be available
  28828. * to the statement immediately following the {@link #request} call. To process returned data, use a success callback
  28829. * in the request options object, or an {@link #requestcomplete event listener}.
  28830. *
  28831. * # File Uploads
  28832. *
  28833. * File uploads are not performed using normal "Ajax" techniques, that is they are not performed using XMLHttpRequests.
  28834. * Instead the form is submitted in the standard manner with the DOM `<form>` element temporarily modified to have its
  28835. * target set to refer to a dynamically generated, hidden `<iframe>` which is inserted into the document but removed
  28836. * after the return data has been gathered.
  28837. *
  28838. * The server response is parsed by the browser to create the document for the IFRAME. If the server is using JSON to
  28839. * send the return object, then the Content-Type header must be set to "text/html" in order to tell the browser to
  28840. * insert the text unchanged into the document body.
  28841. *
  28842. * Characters which are significant to an HTML parser must be sent as HTML entities, so encode `<` as `&lt;`, `&` as
  28843. * `&amp;` etc.
  28844. *
  28845. * The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing a
  28846. * responseText property in order to conform to the requirements of event handlers and callbacks.
  28847. *
  28848. * Be aware that file upload packets are sent with the content type multipart/form and some server technologies
  28849. * (notably JEE) may require some custom processing in order to retrieve parameter names and parameter values from the
  28850. * packet content.
  28851. *
  28852. * __Note:__ It is not possible to check the response code of the hidden iframe, so the success handler will _always_ fire.
  28853. */
  28854. Ext.define('Ext.data.Connection', {
  28855. mixins: {
  28856. observable: 'Ext.mixin.Observable'
  28857. },
  28858. statics: {
  28859. requestId: 0
  28860. },
  28861. config: {
  28862. /**
  28863. * @cfg {String} url
  28864. * The default URL to be used for requests to the server.
  28865. * @accessor
  28866. */
  28867. url: null,
  28868. async: true,
  28869. /**
  28870. * @cfg {String} [method=undefined]
  28871. * The default HTTP method to be used for requests.
  28872. *
  28873. * __Note:__ This is case-sensitive and should be all caps.
  28874. *
  28875. * Defaults to `undefined`; if not set but params are present will use "POST", otherwise "GET".
  28876. */
  28877. method: null,
  28878. username: '',
  28879. password: '',
  28880. /**
  28881. * @cfg {Boolean} disableCaching
  28882. * `true` to add a unique cache-buster param to GET requests.
  28883. * @accessor
  28884. */
  28885. disableCaching: true,
  28886. /**
  28887. * @cfg {String} disableCachingParam
  28888. * Change the parameter which is sent went disabling caching through a cache buster.
  28889. * @accessor
  28890. */
  28891. disableCachingParam: '_dc',
  28892. /**
  28893. * @cfg {Number} timeout
  28894. * The timeout in milliseconds to be used for requests.
  28895. * @accessor
  28896. */
  28897. timeout : 30000,
  28898. /**
  28899. * @cfg {Object} extraParams
  28900. * Any parameters to be appended to the request.
  28901. * @accessor
  28902. */
  28903. extraParams: null,
  28904. /**
  28905. * @cfg {Object} defaultHeaders
  28906. * An object containing request headers which are added to each request made by this object.
  28907. * @accessor
  28908. */
  28909. defaultHeaders: null,
  28910. useDefaultHeader : true,
  28911. defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8',
  28912. /**
  28913. * @cfg {Boolean} useDefaultXhrHeader
  28914. * Set this to false to not send the default Xhr header (X-Requested-With) with every request.
  28915. * This should be set to false when making CORS (cross-domain) requests.
  28916. * @accessor
  28917. */
  28918. useDefaultXhrHeader : true,
  28919. /**
  28920. * @cfg {String} defaultXhrHeader
  28921. * The value of the default Xhr header (X-Requested-With). This is only used when {@link #useDefaultXhrHeader}
  28922. * is set to `true`.
  28923. */
  28924. defaultXhrHeader : 'XMLHttpRequest',
  28925. autoAbort: false
  28926. },
  28927. textAreaRe: /textarea/i,
  28928. multiPartRe: /multipart\/form-data/i,
  28929. lineBreakRe: /\r\n/g,
  28930. constructor : function(config) {
  28931. this.initConfig(config);
  28932. /**
  28933. * @event beforerequest
  28934. * Fires before a network request is made to retrieve a data object.
  28935. * @param {Ext.data.Connection} conn This Connection object.
  28936. * @param {Object} options The options config object passed to the {@link #request} method.
  28937. */
  28938. /**
  28939. * @event requestcomplete
  28940. * Fires if the request was successfully completed.
  28941. * @param {Ext.data.Connection} conn This Connection object.
  28942. * @param {Object} response The XHR object containing the response data.
  28943. * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
  28944. * @param {Object} options The options config object passed to the {@link #request} method.
  28945. */
  28946. /**
  28947. * @event requestexception
  28948. * Fires if an error HTTP status was returned from the server.
  28949. * See [HTTP Status Code Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
  28950. * for details of HTTP status codes.
  28951. * @param {Ext.data.Connection} conn This Connection object.
  28952. * @param {Object} response The XHR object containing the response data.
  28953. * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
  28954. * @param {Object} options The options config object passed to the {@link #request} method.
  28955. */
  28956. this.requests = {};
  28957. },
  28958. /**
  28959. * Sends an HTTP request to a remote server.
  28960. *
  28961. * **Important:** Ajax server requests are asynchronous, and this call will
  28962. * return before the response has been received. Process any returned data
  28963. * in a callback function.
  28964. *
  28965. * Ext.Ajax.request({
  28966. * url: 'ajax_demo/sample.json',
  28967. * success: function(response, opts) {
  28968. * var obj = Ext.decode(response.responseText);
  28969. * console.dir(obj);
  28970. * },
  28971. * failure: function(response, opts) {
  28972. * console.log('server-side failure with status code ' + response.status);
  28973. * }
  28974. * });
  28975. *
  28976. * To execute a callback function in the correct scope, use the `scope` option.
  28977. *
  28978. * @param {Object} options An object which may contain the following properties:
  28979. *
  28980. * (The options object may also contain any other property which might be needed to perform
  28981. * post-processing in a callback because it is passed to callback functions.)
  28982. *
  28983. * @param {String/Function} options.url The URL to which to send the request, or a function
  28984. * to call which returns a URL string. The scope of the function is specified by the `scope` option.
  28985. * Defaults to the configured `url`.
  28986. *
  28987. * @param {Object/String/Function} options.params An object containing properties which are
  28988. * used as parameters to the request, a url encoded string or a function to call to get either. The scope
  28989. * of the function is specified by the `scope` option.
  28990. *
  28991. * @param {String} options.method The HTTP method to use
  28992. * for the request. Defaults to the configured method, or if no method was configured,
  28993. * "GET" if no parameters are being sent, and "POST" if parameters are being sent.
  28994. *
  28995. * __Note:__ The method name is case-sensitive and should be all caps.
  28996. *
  28997. * @param {Function} options.callback The function to be called upon receipt of the HTTP response.
  28998. * The callback is called regardless of success or failure and is passed the following parameters:
  28999. * @param {Object} options.callback.options The parameter to the request call.
  29000. * @param {Boolean} options.callback.success `true` if the request succeeded.
  29001. * @param {Object} options.callback.response The XMLHttpRequest object containing the response data.
  29002. * See [www.w3.org/TR/XMLHttpRequest/](http://www.w3.org/TR/XMLHttpRequest/) for details about
  29003. * accessing elements of the response.
  29004. *
  29005. * @param {Function} options.success The function to be called upon success of the request.
  29006. * The callback is passed the following parameters:
  29007. * @param {Object} options.success.response The XMLHttpRequest object containing the response data.
  29008. * @param {Object} options.success.options The parameter to the request call.
  29009. *
  29010. * @param {Function} options.failure The function to be called upon failure of the request.
  29011. * The callback is passed the following parameters:
  29012. * @param {Object} options.failure.response The XMLHttpRequest object containing the response data.
  29013. * @param {Object} options.failure.options The parameter to the request call.
  29014. *
  29015. * @param {Object} options.scope The scope in which to execute the callbacks: The "this" object for
  29016. * the callback function. If the `url`, or `params` options were specified as functions from which to
  29017. * draw values, then this also serves as the scope for those function calls. Defaults to the browser
  29018. * window.
  29019. *
  29020. * @param {Number} [options.timeout=30000] The timeout in milliseconds to be used for this request.
  29021. *
  29022. * @param {HTMLElement/HTMLElement/String} options.form The `<form>` Element or the id of the `<form>`
  29023. * to pull parameters from.
  29024. *
  29025. * @param {Boolean} options.isUpload **Only meaningful when used with the `form` option.**
  29026. *
  29027. * True if the form object is a file upload (will be set automatically if the form was configured
  29028. * with **`enctype`** `"multipart/form-data"`).
  29029. *
  29030. * File uploads are not performed using normal "Ajax" techniques, that is they are **not**
  29031. * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
  29032. * DOM `<form>` element temporarily modified to have its [target][] set to refer to a dynamically
  29033. * generated, hidden `<iframe>` which is inserted into the document but removed after the return data
  29034. * has been gathered.
  29035. *
  29036. * The server response is parsed by the browser to create the document for the IFRAME. If the
  29037. * server is using JSON to send the return object, then the [Content-Type][] header must be set to
  29038. * "text/html" in order to tell the browser to insert the text unchanged into the document body.
  29039. *
  29040. * The response text is retrieved from the document, and a fake XMLHttpRequest object is created
  29041. * containing a `responseText` property in order to conform to the requirements of event handlers
  29042. * and callbacks.
  29043. *
  29044. * Be aware that file upload packets are sent with the content type [multipart/form][] and some server
  29045. * technologies (notably JEE) may require some custom processing in order to retrieve parameter names
  29046. * and parameter values from the packet content.
  29047. *
  29048. * [target]: http://www.w3.org/TR/REC-html40/present/frames.html#adef-target
  29049. * [Content-Type]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
  29050. * [multipart/form]: http://www.faqs.org/rfcs/rfc2388.html
  29051. *
  29052. * @param {Object} options.headers Request headers to set for the request.
  29053. *
  29054. * @param {Object} options.xmlData XML document to use for the post.
  29055. *
  29056. * __Note:__ This will be used instead
  29057. * of params for the post data. Any params will be appended to the URL.
  29058. *
  29059. * @param {Object/String} options.jsonData JSON data to use as the post.
  29060. *
  29061. * __Note:__ This will be used
  29062. * instead of params for the post data. Any params will be appended to the URL.
  29063. *
  29064. * @param {Boolean} options.disableCaching True to add a unique cache-buster param to GET requests.
  29065. *
  29066. * @return {Object/null} The request object. This may be used to cancel the request.
  29067. */
  29068. request : function(options) {
  29069. options = options || {};
  29070. var me = this,
  29071. scope = options.scope || window,
  29072. username = options.username || me.getUsername(),
  29073. password = options.password || me.getPassword() || '',
  29074. async, requestOptions, request, headers, xhr;
  29075. if (me.fireEvent('beforerequest', me, options) !== false) {
  29076. requestOptions = me.setOptions(options, scope);
  29077. if (this.isFormUpload(options) === true) {
  29078. this.upload(options.form, requestOptions.url, requestOptions.data, options);
  29079. return null;
  29080. }
  29081. // if autoabort is set, cancel the current transactions
  29082. if (options.autoAbort === true || me.getAutoAbort()) {
  29083. me.abort();
  29084. }
  29085. // create a connection object
  29086. xhr = this.getXhrInstance();
  29087. async = options.async !== false ? (options.async || me.getAsync()) : false;
  29088. // open the request
  29089. if (username) {
  29090. xhr.open(requestOptions.method, requestOptions.url, async, username, password);
  29091. } else {
  29092. xhr.open(requestOptions.method, requestOptions.url, async);
  29093. }
  29094. headers = me.setupHeaders(xhr, options, requestOptions.data, requestOptions.params);
  29095. // create the transaction object
  29096. request = {
  29097. id: ++Ext.data.Connection.requestId,
  29098. xhr: xhr,
  29099. headers: headers,
  29100. options: options,
  29101. async: async,
  29102. timeout: setTimeout(function() {
  29103. request.timedout = true;
  29104. me.abort(request);
  29105. }, options.timeout || me.getTimeout())
  29106. };
  29107. me.requests[request.id] = request;
  29108. // bind our statechange listener
  29109. if (async) {
  29110. xhr.onreadystatechange = Ext.Function.bind(me.onStateChange, me, [request]);
  29111. }
  29112. // start the request!
  29113. xhr.send(requestOptions.data);
  29114. if (!async) {
  29115. return this.onComplete(request);
  29116. }
  29117. return request;
  29118. } else {
  29119. Ext.callback(options.callback, options.scope, [options, undefined, undefined]);
  29120. return null;
  29121. }
  29122. },
  29123. /**
  29124. * Uploads a form using a hidden iframe.
  29125. * @param {String/HTMLElement/Ext.Element} form The form to upload.
  29126. * @param {String} url The url to post to.
  29127. * @param {String} params Any extra parameters to pass.
  29128. * @param {Object} options The initial options.
  29129. */
  29130. upload: function(form, url, params, options) {
  29131. form = Ext.getDom(form);
  29132. options = options || {};
  29133. var id = Ext.id(),
  29134. frame = document.createElement('iframe'),
  29135. hiddens = [],
  29136. encoding = 'multipart/form-data',
  29137. buf = {
  29138. target: form.target,
  29139. method: form.method,
  29140. encoding: form.encoding,
  29141. enctype: form.enctype,
  29142. action: form.action
  29143. }, addField = function(name, value) {
  29144. hiddenItem = document.createElement('input');
  29145. Ext.fly(hiddenItem).set({
  29146. type: 'hidden',
  29147. value: value,
  29148. name: name
  29149. });
  29150. form.appendChild(hiddenItem);
  29151. hiddens.push(hiddenItem);
  29152. }, hiddenItem;
  29153. /*
  29154. * Originally this behavior was modified for Opera 10 to apply the secure URL after
  29155. * the frame had been added to the document. It seems this has since been corrected in
  29156. * Opera so the behavior has been reverted, the URL will be set before being added.
  29157. */
  29158. Ext.fly(frame).set({
  29159. id: id,
  29160. name: id,
  29161. cls: Ext.baseCSSPrefix + 'hide-display',
  29162. src: Ext.SSL_SECURE_URL
  29163. });
  29164. document.body.appendChild(frame);
  29165. // This is required so that IE doesn't pop the response up in a new window.
  29166. if (document.frames) {
  29167. document.frames[id].name = id;
  29168. }
  29169. Ext.fly(form).set({
  29170. target: id,
  29171. method: 'POST',
  29172. enctype: encoding,
  29173. encoding: encoding,
  29174. action: url || buf.action
  29175. });
  29176. // add dynamic params
  29177. if (params) {
  29178. Ext.iterate(Ext.Object.fromQueryString(params), function(name, value) {
  29179. if (Ext.isArray(value)) {
  29180. Ext.each(value, function(v) {
  29181. addField(name, v);
  29182. });
  29183. } else {
  29184. addField(name, value);
  29185. }
  29186. });
  29187. }
  29188. Ext.fly(frame).on('load', Ext.Function.bind(this.onUploadComplete, this, [frame, options]), null, {single: true});
  29189. form.submit();
  29190. Ext.fly(form).set(buf);
  29191. Ext.each(hiddens, function(h) {
  29192. Ext.removeNode(h);
  29193. });
  29194. },
  29195. onUploadComplete: function(frame, options) {
  29196. var me = this,
  29197. // bogus response object
  29198. response = {
  29199. responseText: '',
  29200. responseXML: null
  29201. }, doc, firstChild;
  29202. try {
  29203. doc = frame.contentWindow.document || frame.contentDocument || window.frames[id].document;
  29204. if (doc) {
  29205. if (doc.body) {
  29206. if (this.textAreaRe.test((firstChild = doc.body.firstChild || {}).tagName)) { // json response wrapped in textarea
  29207. response.responseText = firstChild.value;
  29208. } else {
  29209. response.responseText = doc.body.innerHTML;
  29210. }
  29211. }
  29212. //in IE the document may still have a body even if returns XML.
  29213. response.responseXML = doc.XMLDocument || doc;
  29214. }
  29215. } catch (e) {
  29216. }
  29217. me.fireEvent('requestcomplete', me, response, options);
  29218. Ext.callback(options.success, options.scope, [response, options]);
  29219. Ext.callback(options.callback, options.scope, [options, true, response]);
  29220. setTimeout(function() {
  29221. Ext.removeNode(frame);
  29222. }, 100);
  29223. },
  29224. /**
  29225. * Detects whether the form is intended to be used for an upload.
  29226. * @private
  29227. */
  29228. isFormUpload: function(options) {
  29229. var form = this.getForm(options);
  29230. if (form) {
  29231. return (options.isUpload || (this.multiPartRe).test(form.getAttribute('enctype')));
  29232. }
  29233. return false;
  29234. },
  29235. /**
  29236. * Gets the form object from options.
  29237. * @private
  29238. * @param {Object} options The request options.
  29239. * @return {HTMLElement/null} The form, `null` if not passed.
  29240. */
  29241. getForm: function(options) {
  29242. return Ext.getDom(options.form) || null;
  29243. },
  29244. /**
  29245. * Sets various options such as the url, params for the request.
  29246. * @param {Object} options The initial options.
  29247. * @param {Object} scope The scope to execute in.
  29248. * @return {Object} The params for the request.
  29249. */
  29250. setOptions: function(options, scope) {
  29251. var me = this,
  29252. params = options.params || {},
  29253. extraParams = me.getExtraParams(),
  29254. urlParams = options.urlParams,
  29255. url = options.url || me.getUrl(),
  29256. jsonData = options.jsonData,
  29257. method,
  29258. disableCache,
  29259. data;
  29260. // allow params to be a method that returns the params object
  29261. if (Ext.isFunction(params)) {
  29262. params = params.call(scope, options);
  29263. }
  29264. // allow url to be a method that returns the actual url
  29265. if (Ext.isFunction(url)) {
  29266. url = url.call(scope, options);
  29267. }
  29268. url = this.setupUrl(options, url);
  29269. //<debug>
  29270. if (!url) {
  29271. Ext.Logger.error('No URL specified');
  29272. }
  29273. //</debug>
  29274. // check for xml or json data, and make sure json data is encoded
  29275. data = options.rawData || options.xmlData || jsonData || null;
  29276. if (jsonData && !Ext.isPrimitive(jsonData)) {
  29277. data = Ext.encode(data);
  29278. }
  29279. // make sure params are a url encoded string and include any extraParams if specified
  29280. if (Ext.isObject(params)) {
  29281. params = Ext.Object.toQueryString(params);
  29282. }
  29283. if (Ext.isObject(extraParams)) {
  29284. extraParams = Ext.Object.toQueryString(extraParams);
  29285. }
  29286. params = params + ((extraParams) ? ((params) ? '&' : '') + extraParams : '');
  29287. urlParams = Ext.isObject(urlParams) ? Ext.Object.toQueryString(urlParams) : urlParams;
  29288. params = this.setupParams(options, params);
  29289. // decide the proper method for this request
  29290. method = (options.method || me.getMethod() || ((params || data) ? 'POST' : 'GET')).toUpperCase();
  29291. this.setupMethod(options, method);
  29292. disableCache = options.disableCaching !== false ? (options.disableCaching || me.getDisableCaching()) : false;
  29293. // append date to prevent caching
  29294. if (disableCache) {
  29295. url = Ext.urlAppend(url, (options.disableCachingParam || me.getDisableCachingParam()) + '=' + (new Date().getTime()));
  29296. }
  29297. // if the method is get or there is json/xml data append the params to the url
  29298. if ((method == 'GET' || data) && params) {
  29299. url = Ext.urlAppend(url, params);
  29300. params = null;
  29301. }
  29302. // allow params to be forced into the url
  29303. if (urlParams) {
  29304. url = Ext.urlAppend(url, urlParams);
  29305. }
  29306. return {
  29307. url: url,
  29308. method: method,
  29309. data: data || params || null
  29310. };
  29311. },
  29312. /**
  29313. * Template method for overriding url.
  29314. * @private
  29315. * @param {Object} options
  29316. * @param {String} url
  29317. * @return {String} The modified url
  29318. */
  29319. setupUrl: function(options, url) {
  29320. var form = this.getForm(options);
  29321. if (form) {
  29322. url = url || form.action;
  29323. }
  29324. return url;
  29325. },
  29326. /**
  29327. * Template method for overriding params.
  29328. * @private
  29329. * @param {Object} options
  29330. * @param {String} params
  29331. * @return {String} The modified params.
  29332. */
  29333. setupParams: function(options, params) {
  29334. var form = this.getForm(options),
  29335. serializedForm;
  29336. if (form && !this.isFormUpload(options)) {
  29337. serializedForm = Ext.Element.serializeForm(form);
  29338. params = params ? (params + '&' + serializedForm) : serializedForm;
  29339. }
  29340. return params;
  29341. },
  29342. /**
  29343. * Template method for overriding method.
  29344. * @private
  29345. * @param {Object} options
  29346. * @param {String} method
  29347. * @return {String} The modified method.
  29348. */
  29349. setupMethod: function(options, method) {
  29350. if (this.isFormUpload(options)) {
  29351. return 'POST';
  29352. }
  29353. return method;
  29354. },
  29355. /**
  29356. * Setup all the headers for the request.
  29357. * @private
  29358. * @param {Object} xhr The xhr object.
  29359. * @param {Object} options The options for the request.
  29360. * @param {Object} data The data for the request.
  29361. * @param {Object} params The params for the request.
  29362. */
  29363. setupHeaders: function(xhr, options, data, params) {
  29364. var me = this,
  29365. headers = Ext.apply({}, options.headers || {}, me.getDefaultHeaders() || {}),
  29366. contentType = me.getDefaultPostHeader(),
  29367. jsonData = options.jsonData,
  29368. xmlData = options.xmlData,
  29369. key,
  29370. header;
  29371. if (!headers['Content-Type'] && (data || params)) {
  29372. if (data) {
  29373. if (options.rawData) {
  29374. contentType = 'text/plain';
  29375. } else {
  29376. if (xmlData && Ext.isDefined(xmlData)) {
  29377. contentType = 'text/xml';
  29378. } else if (jsonData && Ext.isDefined(jsonData)) {
  29379. contentType = 'application/json';
  29380. }
  29381. }
  29382. }
  29383. headers['Content-Type'] = contentType;
  29384. }
  29385. if (((me.getUseDefaultXhrHeader() && options.useDefaultXhrHeader !== false) || options.useDefaultXhrHeader) && !headers['X-Requested-With']) {
  29386. headers['X-Requested-With'] = me.getDefaultXhrHeader();
  29387. }
  29388. // set up all the request headers on the xhr object
  29389. try {
  29390. for (key in headers) {
  29391. if (headers.hasOwnProperty(key)) {
  29392. header = headers[key];
  29393. xhr.setRequestHeader(key, header);
  29394. }
  29395. }
  29396. } catch(e) {
  29397. me.fireEvent('exception', key, header);
  29398. }
  29399. if (options.withCredentials) {
  29400. xhr.withCredentials = options.withCredentials;
  29401. }
  29402. return headers;
  29403. },
  29404. /**
  29405. * Creates the appropriate XHR transport for the browser.
  29406. * @private
  29407. */
  29408. getXhrInstance: (function() {
  29409. var options = [function() {
  29410. return new XMLHttpRequest();
  29411. }, function() {
  29412. return new ActiveXObject('MSXML2.XMLHTTP.3.0');
  29413. }, function() {
  29414. return new ActiveXObject('MSXML2.XMLHTTP');
  29415. }, function() {
  29416. return new ActiveXObject('Microsoft.XMLHTTP');
  29417. }], i = 0,
  29418. len = options.length,
  29419. xhr;
  29420. for (; i < len; ++i) {
  29421. try {
  29422. xhr = options[i];
  29423. xhr();
  29424. break;
  29425. } catch(e) {
  29426. }
  29427. }
  29428. return xhr;
  29429. })(),
  29430. /**
  29431. * Determines whether this object has a request outstanding.
  29432. * @param {Object} request The request to check.
  29433. * @return {Boolean} True if there is an outstanding request.
  29434. */
  29435. isLoading : function(request) {
  29436. if (!(request && request.xhr)) {
  29437. return false;
  29438. }
  29439. // if there is a connection and readyState is not 0 or 4
  29440. var state = request.xhr.readyState;
  29441. return !(state === 0 || state == 4);
  29442. },
  29443. /**
  29444. * Aborts any outstanding request.
  29445. * @param {Object} request (Optional) Defaults to the last request.
  29446. */
  29447. abort : function(request) {
  29448. var me = this,
  29449. requests = me.requests,
  29450. id;
  29451. if (request && me.isLoading(request)) {
  29452. /*
  29453. * Clear out the onreadystatechange here, this allows us
  29454. * greater control, the browser may/may not fire the function
  29455. * depending on a series of conditions.
  29456. */
  29457. request.xhr.onreadystatechange = null;
  29458. request.xhr.abort();
  29459. me.clearTimeout(request);
  29460. if (!request.timedout) {
  29461. request.aborted = true;
  29462. }
  29463. me.onComplete(request);
  29464. me.cleanup(request);
  29465. } else if (!request) {
  29466. for (id in requests) {
  29467. if (requests.hasOwnProperty(id)) {
  29468. me.abort(requests[id]);
  29469. }
  29470. }
  29471. }
  29472. },
  29473. /**
  29474. * Aborts all outstanding requests.
  29475. */
  29476. abortAll: function() {
  29477. this.abort();
  29478. },
  29479. /**
  29480. * Fires when the state of the XHR changes.
  29481. * @private
  29482. * @param {Object} request The request
  29483. */
  29484. onStateChange : function(request) {
  29485. if (request.xhr.readyState == 4) {
  29486. this.clearTimeout(request);
  29487. this.onComplete(request);
  29488. this.cleanup(request);
  29489. }
  29490. },
  29491. /**
  29492. * Clears the timeout on the request.
  29493. * @private
  29494. * @param {Object} The request
  29495. */
  29496. clearTimeout: function(request) {
  29497. clearTimeout(request.timeout);
  29498. delete request.timeout;
  29499. },
  29500. /**
  29501. * Cleans up any left over information from the request.
  29502. * @private
  29503. * @param {Object} The request.
  29504. */
  29505. cleanup: function(request) {
  29506. request.xhr = null;
  29507. delete request.xhr;
  29508. },
  29509. /**
  29510. * To be called when the request has come back from the server.
  29511. * @private
  29512. * @param {Object} request
  29513. * @return {Object} The response.
  29514. */
  29515. onComplete : function(request) {
  29516. var me = this,
  29517. options = request.options,
  29518. result,
  29519. success,
  29520. response;
  29521. try {
  29522. result = me.parseStatus(request.xhr.status, request.xhr);
  29523. if (request.timedout) {
  29524. result.success = false;
  29525. }
  29526. } catch (e) {
  29527. // in some browsers we can't access the status if the readyState is not 4, so the request has failed
  29528. result = {
  29529. success : false,
  29530. isException : false
  29531. };
  29532. }
  29533. success = result.success;
  29534. if (success) {
  29535. response = me.createResponse(request);
  29536. me.fireEvent('requestcomplete', me, response, options);
  29537. Ext.callback(options.success, options.scope, [response, options]);
  29538. } else {
  29539. if (result.isException || request.aborted || request.timedout) {
  29540. response = me.createException(request);
  29541. } else {
  29542. response = me.createResponse(request);
  29543. }
  29544. me.fireEvent('requestexception', me, response, options);
  29545. Ext.callback(options.failure, options.scope, [response, options]);
  29546. }
  29547. Ext.callback(options.callback, options.scope, [options, success, response]);
  29548. delete me.requests[request.id];
  29549. return response;
  29550. },
  29551. /**
  29552. * Checks if the response status was successful.
  29553. * @param {Number} status The status code.
  29554. * @param xhr
  29555. * @return {Object} An object containing success/status state.
  29556. */
  29557. parseStatus: function(status, xhr) {
  29558. // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223
  29559. status = status == 1223 ? 204 : status;
  29560. var success = (status >= 200 && status < 300) || status == 304 || (status == 0 && xhr.responseText.length > 0),
  29561. isException = false;
  29562. if (!success) {
  29563. switch (status) {
  29564. case 12002:
  29565. case 12029:
  29566. case 12030:
  29567. case 12031:
  29568. case 12152:
  29569. case 13030:
  29570. isException = true;
  29571. break;
  29572. }
  29573. }
  29574. return {
  29575. success: success,
  29576. isException: isException
  29577. };
  29578. },
  29579. /**
  29580. * Creates the response object.
  29581. * @private
  29582. * @param {Object} request
  29583. */
  29584. createResponse : function(request) {
  29585. var xhr = request.xhr,
  29586. headers = {},
  29587. lines, count, line, index, key, response;
  29588. //we need to make this check here because if a request times out an exception is thrown
  29589. //when calling getAllResponseHeaders() because the response never came back to populate it
  29590. if (request.timedout || request.aborted) {
  29591. request.success = false;
  29592. lines = [];
  29593. } else {
  29594. lines = xhr.getAllResponseHeaders().replace(this.lineBreakRe, '\n').split('\n');
  29595. }
  29596. count = lines.length;
  29597. while (count--) {
  29598. line = lines[count];
  29599. index = line.indexOf(':');
  29600. if (index >= 0) {
  29601. key = line.substr(0, index).toLowerCase();
  29602. if (line.charAt(index + 1) == ' ') {
  29603. ++index;
  29604. }
  29605. headers[key] = line.substr(index + 1);
  29606. }
  29607. }
  29608. request.xhr = null;
  29609. delete request.xhr;
  29610. response = {
  29611. request: request,
  29612. requestId : request.id,
  29613. status : xhr.status,
  29614. statusText : xhr.statusText,
  29615. getResponseHeader : function(header) {
  29616. return headers[header.toLowerCase()];
  29617. },
  29618. getAllResponseHeaders : function() {
  29619. return headers;
  29620. },
  29621. responseText : xhr.responseText,
  29622. responseXML : xhr.responseXML
  29623. };
  29624. // If we don't explicitly tear down the xhr reference, IE6/IE7 will hold this in the closure of the
  29625. // functions created with getResponseHeader/getAllResponseHeaders
  29626. xhr = null;
  29627. return response;
  29628. },
  29629. /**
  29630. * Creates the exception object.
  29631. * @private
  29632. * @param {Object} request
  29633. */
  29634. createException : function(request) {
  29635. return {
  29636. request : request,
  29637. requestId : request.id,
  29638. status : request.aborted ? -1 : 0,
  29639. statusText : request.aborted ? 'transaction aborted' : 'communication failure',
  29640. aborted: request.aborted,
  29641. timedout: request.timedout
  29642. };
  29643. }
  29644. });
  29645. /**
  29646. * @aside guide ajax
  29647. *
  29648. * A singleton instance of an {@link Ext.data.Connection}. This class
  29649. * is used to communicate with your server side code. It can be used as follows:
  29650. *
  29651. * Ext.Ajax.request({
  29652. * url: 'page.php',
  29653. * params: {
  29654. * id: 1
  29655. * },
  29656. * success: function(response){
  29657. * var text = response.responseText;
  29658. * // process server response here
  29659. * }
  29660. * });
  29661. *
  29662. * Default options for all requests can be set by changing a property on the Ext.Ajax class:
  29663. *
  29664. * Ext.Ajax.setTimeout(60000); // 60 seconds
  29665. *
  29666. * Any options specified in the request method for the Ajax request will override any
  29667. * defaults set on the Ext.Ajax class. In the code sample below, the timeout for the
  29668. * request will be 60 seconds.
  29669. *
  29670. * Ext.Ajax.setTimeout(120000); // 120 seconds
  29671. * Ext.Ajax.request({
  29672. * url: 'page.aspx',
  29673. * timeout: 60000
  29674. * });
  29675. *
  29676. * In general, this class will be used for all Ajax requests in your application.
  29677. * The main reason for creating a separate {@link Ext.data.Connection} is for a
  29678. * series of requests that share common settings that are different to all other
  29679. * requests in the application.
  29680. */
  29681. Ext.define('Ext.Ajax', {
  29682. extend: 'Ext.data.Connection',
  29683. singleton: true,
  29684. /**
  29685. * @property {Boolean} autoAbort
  29686. * Whether a new request should abort any pending requests.
  29687. */
  29688. autoAbort : false
  29689. });
  29690. /**
  29691. * Ext.Anim is used to execute simple animations defined in {@link Ext.anims}. The {@link #run} method can take any of the
  29692. * properties defined below.
  29693. *
  29694. * Ext.Anim.run(this, 'fade', {
  29695. * out: false,
  29696. * autoClear: true
  29697. * });
  29698. *
  29699. * When using {@link Ext.Anim#run}, ensure you require {@link Ext.Anim} in your application. Either do this using {@link Ext#require}:
  29700. *
  29701. * Ext.requires('Ext.Anim');
  29702. *
  29703. * when using {@link Ext#setup}:
  29704. *
  29705. * Ext.setup({
  29706. * requires: ['Ext.Anim'],
  29707. * onReady: function() {
  29708. * //do something
  29709. * }
  29710. * });
  29711. *
  29712. * or when using {@link Ext#application}:
  29713. *
  29714. * Ext.application({
  29715. * requires: ['Ext.Anim'],
  29716. * launch: function() {
  29717. * //do something
  29718. * }
  29719. * });
  29720. *
  29721. * @singleton
  29722. */
  29723. Ext.define('Ext.Anim', {
  29724. isAnim: true,
  29725. /**
  29726. * @cfg {Boolean} disableAnimations
  29727. * `true` to disable animations.
  29728. */
  29729. disableAnimations: false,
  29730. defaultConfig: {
  29731. /**
  29732. * @cfg {Object} from
  29733. * An object of CSS values which the animation begins with. If you define a CSS property here, you must also
  29734. * define it in the {@link #to} config.
  29735. */
  29736. from: {},
  29737. /**
  29738. * @cfg {Object} to
  29739. * An object of CSS values which the animation ends with. If you define a CSS property here, you must also
  29740. * define it in the {@link #from} config.
  29741. */
  29742. to: {},
  29743. /**
  29744. * @cfg {Number} duration
  29745. * Time in milliseconds for the animation to last.
  29746. */
  29747. duration: 250,
  29748. /**
  29749. * @cfg {Number} delay Time to delay before starting the animation.
  29750. */
  29751. delay: 0,
  29752. /**
  29753. * @cfg {String} easing
  29754. * Valid values are 'ease', 'linear', ease-in', 'ease-out', 'ease-in-out', or a cubic-bezier curve as defined by CSS.
  29755. */
  29756. easing: 'ease-in-out',
  29757. /**
  29758. * @cfg {Boolean} autoClear
  29759. * `true` to remove all custom CSS defined in the {@link #to} config when the animation is over.
  29760. */
  29761. autoClear: true,
  29762. /**
  29763. * @cfg {Boolean} out
  29764. * `true` if you want the animation to slide out of the screen.
  29765. */
  29766. out: true,
  29767. /**
  29768. * @cfg {String} direction
  29769. * Valid values are: 'left', 'right', 'up', 'down', and `null`.
  29770. */
  29771. direction: null,
  29772. /**
  29773. * @cfg {Boolean} reverse
  29774. * `true` to reverse the animation direction. For example, if the animation direction was set to 'left', it would
  29775. * then use 'right'.
  29776. */
  29777. reverse: false
  29778. },
  29779. /**
  29780. * @cfg {Function} before
  29781. * Code to execute before starting the animation.
  29782. */
  29783. /**
  29784. * @cfg {Function} after
  29785. * Code to execute after the animation ends.
  29786. */
  29787. /**
  29788. * @cfg {Object} scope
  29789. * Scope to run the {@link #before} function in.
  29790. */
  29791. opposites: {
  29792. 'left': 'right',
  29793. 'right': 'left',
  29794. 'up': 'down',
  29795. 'down': 'up'
  29796. },
  29797. constructor: function(config) {
  29798. config = Ext.apply({}, config || {}, this.defaultConfig);
  29799. this.config = config;
  29800. this.callSuper([config]);
  29801. this.running = [];
  29802. },
  29803. initConfig: function(el, runConfig) {
  29804. var me = this,
  29805. config = Ext.apply({}, runConfig || {}, me.config);
  29806. config.el = el = Ext.get(el);
  29807. if (config.reverse && me.opposites[config.direction]) {
  29808. config.direction = me.opposites[config.direction];
  29809. }
  29810. if (me.config.before) {
  29811. me.config.before.call(config, el, config);
  29812. }
  29813. if (runConfig.before) {
  29814. runConfig.before.call(config.scope || config, el, config);
  29815. }
  29816. return config;
  29817. },
  29818. /**
  29819. * @ignore
  29820. */
  29821. run: function(el, config) {
  29822. el = Ext.get(el);
  29823. config = config || {};
  29824. var me = this,
  29825. style = el.dom.style,
  29826. property,
  29827. after = config.after;
  29828. if (me.running[el.id]) {
  29829. me.onTransitionEnd(null, el, {
  29830. config: config,
  29831. after: after
  29832. });
  29833. }
  29834. config = this.initConfig(el, config);
  29835. if (this.disableAnimations) {
  29836. for (property in config.to) {
  29837. if (!config.to.hasOwnProperty(property)) {
  29838. continue;
  29839. }
  29840. style[property] = config.to[property];
  29841. }
  29842. this.onTransitionEnd(null, el, {
  29843. config: config,
  29844. after: after
  29845. });
  29846. return me;
  29847. }
  29848. el.un('transitionend', me.onTransitionEnd, me);
  29849. style.webkitTransitionDuration = '0ms';
  29850. for (property in config.from) {
  29851. if (!config.from.hasOwnProperty(property)) {
  29852. continue;
  29853. }
  29854. style[property] = config.from[property];
  29855. }
  29856. setTimeout(function() {
  29857. // If this element has been destroyed since the timeout started, do nothing
  29858. if (!el.dom) {
  29859. return;
  29860. }
  29861. // If this is a 3d animation we have to set the perspective on the parent
  29862. if (config.is3d === true) {
  29863. el.parent().setStyle({
  29864. // See https://sencha.jira.com/browse/TOUCH-1498
  29865. '-webkit-perspective': '1200',
  29866. '-webkit-transform-style': 'preserve-3d'
  29867. });
  29868. }
  29869. style.webkitTransitionDuration = config.duration + 'ms';
  29870. style.webkitTransitionProperty = 'all';
  29871. style.webkitTransitionTimingFunction = config.easing;
  29872. // Bind our listener that fires after the animation ends
  29873. el.on('transitionend', me.onTransitionEnd, me, {
  29874. single: true,
  29875. config: config,
  29876. after: after
  29877. });
  29878. for (property in config.to) {
  29879. if (!config.to.hasOwnProperty(property)) {
  29880. continue;
  29881. }
  29882. style[property] = config.to[property];
  29883. }
  29884. }, config.delay || 5);
  29885. me.running[el.id] = config;
  29886. return me;
  29887. },
  29888. onTransitionEnd: function(ev, el, o) {
  29889. el = Ext.get(el);
  29890. if (this.running[el.id] === undefined) {
  29891. return;
  29892. }
  29893. var style = el.dom.style,
  29894. config = o.config,
  29895. me = this,
  29896. property;
  29897. if (config.autoClear) {
  29898. for (property in config.to) {
  29899. if (!config.to.hasOwnProperty(property) || config[property] === false) {
  29900. continue;
  29901. }
  29902. style[property] = '';
  29903. }
  29904. }
  29905. style.webkitTransitionDuration = null;
  29906. style.webkitTransitionProperty = null;
  29907. style.webkitTransitionTimingFunction = null;
  29908. if (config.is3d) {
  29909. el.parent().setStyle({
  29910. '-webkit-perspective': '',
  29911. '-webkit-transform-style': ''
  29912. });
  29913. }
  29914. if (me.config.after) {
  29915. me.config.after.call(config, el, config);
  29916. }
  29917. if (o.after) {
  29918. o.after.call(config.scope || me, el, config);
  29919. }
  29920. delete me.running[el.id];
  29921. }
  29922. }, function() {
  29923. Ext.Anim.seed = 1000;
  29924. /**
  29925. * Used to run an animation on a specific element. Use the config argument to customize the animation.
  29926. * @param {Ext.Element/HTMLElement} el The element to animate.
  29927. * @param {String} anim The animation type, defined in {@link Ext.anims}.
  29928. * @param {Object} config The config object for the animation.
  29929. * @method run
  29930. */
  29931. Ext.Anim.run = function(el, anim, config) {
  29932. if (el.isComponent) {
  29933. el = el.element;
  29934. }
  29935. config = config || {};
  29936. if (anim.isAnim) {
  29937. anim.run(el, config);
  29938. }
  29939. else {
  29940. if (Ext.isObject(anim)) {
  29941. if (config.before && anim.before) {
  29942. config.before = Ext.createInterceptor(config.before, anim.before, anim.scope);
  29943. }
  29944. if (config.after && anim.after) {
  29945. config.after = Ext.createInterceptor(config.after, anim.after, anim.scope);
  29946. }
  29947. config = Ext.apply({}, config, anim);
  29948. anim = anim.type;
  29949. }
  29950. if (!Ext.anims[anim]) {
  29951. throw anim + ' is not a valid animation type.';
  29952. }
  29953. else {
  29954. // add el check to make sure dom exists.
  29955. if (el && el.dom) {
  29956. Ext.anims[anim].run(el, config);
  29957. }
  29958. }
  29959. }
  29960. };
  29961. /**
  29962. * @class Ext.anims
  29963. * Defines different types of animations.
  29964. *
  29965. * __Note:__ _flip_, _cube_, and _wipe_ animations do not work on Android.
  29966. *
  29967. * Please refer to {@link Ext.Anim} on how to use animations.
  29968. * @singleton
  29969. */
  29970. Ext.anims = {
  29971. /**
  29972. * Fade Animation
  29973. */
  29974. fade: new Ext.Anim({
  29975. type: 'fade',
  29976. before: function(el) {
  29977. var fromOpacity = 1,
  29978. toOpacity = 1,
  29979. curZ = el.getStyle('z-index') == 'auto' ? 0 : el.getStyle('z-index'),
  29980. zIndex = curZ;
  29981. if (this.out) {
  29982. toOpacity = 0;
  29983. } else {
  29984. zIndex = Math.abs(curZ) + 1;
  29985. fromOpacity = 0;
  29986. }
  29987. this.from = {
  29988. 'opacity': fromOpacity,
  29989. 'z-index': zIndex
  29990. };
  29991. this.to = {
  29992. 'opacity': toOpacity,
  29993. 'z-index': zIndex
  29994. };
  29995. }
  29996. }),
  29997. /**
  29998. * Slide Animation
  29999. */
  30000. slide: new Ext.Anim({
  30001. direction: 'left',
  30002. cover: false,
  30003. reveal: false,
  30004. opacity: false,
  30005. 'z-index': false,
  30006. before: function(el) {
  30007. var currentZIndex = el.getStyle('z-index') == 'auto' ? 0 : el.getStyle('z-index'),
  30008. currentOpacity = el.getStyle('opacity'),
  30009. zIndex = currentZIndex + 1,
  30010. out = this.out,
  30011. direction = this.direction,
  30012. toX = 0,
  30013. toY = 0,
  30014. fromX = 0,
  30015. fromY = 0,
  30016. elH = el.getHeight(),
  30017. elW = el.getWidth();
  30018. if (direction == 'left' || direction == 'right') {
  30019. if (out) {
  30020. toX = -elW;
  30021. }
  30022. else {
  30023. fromX = elW;
  30024. }
  30025. }
  30026. else if (direction == 'up' || direction == 'down') {
  30027. if (out) {
  30028. toY = -elH;
  30029. }
  30030. else {
  30031. fromY = elH;
  30032. }
  30033. }
  30034. if (direction == 'right' || direction == 'down') {
  30035. toY *= -1;
  30036. toX *= -1;
  30037. fromY *= -1;
  30038. fromX *= -1;
  30039. }
  30040. if (this.cover && out) {
  30041. toX = 0;
  30042. toY = 0;
  30043. zIndex = currentZIndex;
  30044. }
  30045. else if (this.reveal && !out) {
  30046. fromX = 0;
  30047. fromY = 0;
  30048. zIndex = currentZIndex;
  30049. }
  30050. this.from = {
  30051. '-webkit-transform': 'translate3d(' + fromX + 'px, ' + fromY + 'px, 0)',
  30052. 'z-index': zIndex,
  30053. 'opacity': currentOpacity - 0.01
  30054. };
  30055. this.to = {
  30056. '-webkit-transform': 'translate3d(' + toX + 'px, ' + toY + 'px, 0)',
  30057. 'z-index': zIndex,
  30058. 'opacity': currentOpacity
  30059. };
  30060. }
  30061. }),
  30062. /**
  30063. * Pop Animation
  30064. */
  30065. pop: new Ext.Anim({
  30066. scaleOnExit: true,
  30067. before: function(el) {
  30068. var fromScale = 1,
  30069. toScale = 1,
  30070. fromOpacity = 1,
  30071. toOpacity = 1,
  30072. curZ = el.getStyle('z-index') == 'auto' ? 0 : el.getStyle('z-index'),
  30073. fromZ = curZ,
  30074. toZ = curZ;
  30075. if (!this.out) {
  30076. fromScale = 0.01;
  30077. fromZ = curZ + 1;
  30078. toZ = curZ + 1;
  30079. fromOpacity = 0;
  30080. }
  30081. else {
  30082. if (this.scaleOnExit) {
  30083. toScale = 0.01;
  30084. toOpacity = 0;
  30085. } else {
  30086. toOpacity = 0.8;
  30087. }
  30088. }
  30089. this.from = {
  30090. '-webkit-transform': 'scale(' + fromScale + ')',
  30091. '-webkit-transform-origin': '50% 50%',
  30092. 'opacity': fromOpacity,
  30093. 'z-index': fromZ
  30094. };
  30095. this.to = {
  30096. '-webkit-transform': 'scale(' + toScale + ')',
  30097. '-webkit-transform-origin': '50% 50%',
  30098. 'opacity': toOpacity,
  30099. 'z-index': toZ
  30100. };
  30101. }
  30102. }),
  30103. /**
  30104. * Flip Animation
  30105. */
  30106. flip: new Ext.Anim({
  30107. is3d: true,
  30108. direction: 'left',
  30109. before: function(el) {
  30110. var rotateProp = 'Y',
  30111. fromScale = 1,
  30112. toScale = 1,
  30113. fromRotate = 0,
  30114. toRotate = 0;
  30115. if (this.out) {
  30116. toRotate = -180;
  30117. toScale = 0.8;
  30118. }
  30119. else {
  30120. fromRotate = 180;
  30121. fromScale = 0.8;
  30122. }
  30123. if (this.direction == 'up' || this.direction == 'down') {
  30124. rotateProp = 'X';
  30125. }
  30126. if (this.direction == 'right' || this.direction == 'left') {
  30127. toRotate *= -1;
  30128. fromRotate *= -1;
  30129. }
  30130. this.from = {
  30131. '-webkit-transform': 'rotate' + rotateProp + '(' + fromRotate + 'deg) scale(' + fromScale + ')',
  30132. '-webkit-backface-visibility': 'hidden'
  30133. };
  30134. this.to = {
  30135. '-webkit-transform': 'rotate' + rotateProp + '(' + toRotate + 'deg) scale(' + toScale + ')',
  30136. '-webkit-backface-visibility': 'hidden'
  30137. };
  30138. }
  30139. }),
  30140. /**
  30141. * Cube Animation
  30142. */
  30143. cube: new Ext.Anim({
  30144. is3d: true,
  30145. direction: 'left',
  30146. style: 'outer',
  30147. before: function(el) {
  30148. var origin = '0% 0%',
  30149. fromRotate = 0,
  30150. toRotate = 0,
  30151. rotateProp = 'Y',
  30152. fromZ = 0,
  30153. toZ = 0,
  30154. elW = el.getWidth(),
  30155. elH = el.getHeight(),
  30156. showTranslateZ = true,
  30157. fromTranslate = ' translateX(0)',
  30158. toTranslate = '';
  30159. if (this.direction == 'left' || this.direction == 'right') {
  30160. if (this.out) {
  30161. origin = '100% 100%';
  30162. toZ = elW;
  30163. toRotate = -90;
  30164. } else {
  30165. origin = '0% 0%';
  30166. fromZ = elW;
  30167. fromRotate = 90;
  30168. }
  30169. } else if (this.direction == 'up' || this.direction == 'down') {
  30170. rotateProp = 'X';
  30171. if (this.out) {
  30172. origin = '100% 100%';
  30173. toZ = elH;
  30174. toRotate = 90;
  30175. } else {
  30176. origin = '0% 0%';
  30177. fromZ = elH;
  30178. fromRotate = -90;
  30179. }
  30180. }
  30181. if (this.direction == 'down' || this.direction == 'right') {
  30182. fromRotate *= -1;
  30183. toRotate *= -1;
  30184. origin = (origin == '0% 0%') ? '100% 100%': '0% 0%';
  30185. }
  30186. if (this.style == 'inner') {
  30187. fromZ *= -1;
  30188. toZ *= -1;
  30189. fromRotate *= -1;
  30190. toRotate *= -1;
  30191. if (!this.out) {
  30192. toTranslate = ' translateX(0px)';
  30193. origin = '0% 50%';
  30194. } else {
  30195. toTranslate = fromTranslate;
  30196. origin = '100% 50%';
  30197. }
  30198. }
  30199. this.from = {
  30200. '-webkit-transform': 'rotate' + rotateProp + '(' + fromRotate + 'deg)' + (showTranslateZ ? ' translateZ(' + fromZ + 'px)': '') + fromTranslate,
  30201. '-webkit-transform-origin': origin
  30202. };
  30203. this.to = {
  30204. '-webkit-transform': 'rotate' + rotateProp + '(' + toRotate + 'deg) translateZ(' + toZ + 'px)' + toTranslate,
  30205. '-webkit-transform-origin': origin
  30206. };
  30207. },
  30208. duration: 250
  30209. }),
  30210. /**
  30211. * Wipe Animation.
  30212. * Because of the amount of calculations involved, this animation is best used on small display
  30213. * changes or specifically for phone environments. Does not currently accept any parameters.
  30214. */
  30215. wipe: new Ext.Anim({
  30216. before: function(el) {
  30217. var curZ = el.getStyle('z-index'),
  30218. zIndex,
  30219. mask = '';
  30220. if (!this.out) {
  30221. zIndex = curZ + 1;
  30222. mask = '-webkit-gradient(linear, left bottom, right bottom, from(transparent), to(#000), color-stop(66%, #000), color-stop(33%, transparent))';
  30223. this.from = {
  30224. '-webkit-mask-image': mask,
  30225. '-webkit-mask-size': el.getWidth() * 3 + 'px ' + el.getHeight() + 'px',
  30226. 'z-index': zIndex,
  30227. '-webkit-mask-position-x': 0
  30228. };
  30229. this.to = {
  30230. '-webkit-mask-image': mask,
  30231. '-webkit-mask-size': el.getWidth() * 3 + 'px ' + el.getHeight() + 'px',
  30232. 'z-index': zIndex,
  30233. '-webkit-mask-position-x': -el.getWidth() * 2 + 'px'
  30234. };
  30235. }
  30236. },
  30237. duration: 500
  30238. })
  30239. };
  30240. });
  30241. /**
  30242. * Provides a base class for audio/visual controls. Should not be used directly.
  30243. *
  30244. * Please see the {@link Ext.Audio} and {@link Ext.Video} classes for more information.
  30245. * @private
  30246. */
  30247. Ext.define('Ext.Media', {
  30248. extend: 'Ext.Component',
  30249. xtype: 'media',
  30250. /**
  30251. * @event play
  30252. * Fires whenever the media is played.
  30253. * @param {Ext.Media} this
  30254. */
  30255. /**
  30256. * @event pause
  30257. * Fires whenever the media is paused.
  30258. * @param {Ext.Media} this
  30259. * @param {Number} time The time at which the media was paused at in seconds.
  30260. */
  30261. /**
  30262. * @event ended
  30263. * Fires whenever the media playback has ended.
  30264. * @param {Ext.Media} this
  30265. * @param {Number} time The time at which the media ended at in seconds.
  30266. */
  30267. /**
  30268. * @event stop
  30269. * Fires whenever the media is stopped.
  30270. * The `pause` event will also fire after the `stop` event if the media is currently playing.
  30271. * The `timeupdate` event will also fire after the `stop` event regardless of playing status.
  30272. * @param {Ext.Media} this
  30273. */
  30274. /**
  30275. * @event volumechange
  30276. * Fires whenever the volume is changed.
  30277. * @param {Ext.Media} this
  30278. * @param {Number} volume The volume level from 0 to 1.
  30279. */
  30280. /**
  30281. * @event mutedchange
  30282. * Fires whenever the muted status is changed.
  30283. * The volumechange event will also fire after the `mutedchange` event fires.
  30284. * @param {Ext.Media} this
  30285. * @param {Boolean} muted The muted status.
  30286. */
  30287. /**
  30288. * @event timeupdate
  30289. * Fires when the media is playing every 15 to 250ms.
  30290. * @param {Ext.Media} this
  30291. * @param {Number} time The current time in seconds.
  30292. */
  30293. config: {
  30294. /**
  30295. * @cfg {String} url
  30296. * Location of the media to play.
  30297. * @accessor
  30298. */
  30299. url: '',
  30300. /**
  30301. * @cfg {Boolean} enableControls
  30302. * Set this to `false` to turn off the native media controls.
  30303. * Defaults to `false` when you are on Android, as it doesn't support controls.
  30304. * @accessor
  30305. */
  30306. enableControls: Ext.os.is.Android ? false : true,
  30307. /**
  30308. * @cfg {Boolean} autoResume
  30309. * Will automatically start playing the media when the container is activated.
  30310. * @accessor
  30311. */
  30312. autoResume: false,
  30313. /**
  30314. * @cfg {Boolean} autoPause
  30315. * Will automatically pause the media when the container is deactivated.
  30316. * @accessor
  30317. */
  30318. autoPause: true,
  30319. /**
  30320. * @cfg {Boolean} preload
  30321. * Will begin preloading the media immediately.
  30322. * @accessor
  30323. */
  30324. preload: true,
  30325. /**
  30326. * @cfg {Boolean} loop
  30327. * Will loop the media forever.
  30328. * @accessor
  30329. */
  30330. loop: false,
  30331. /**
  30332. * @cfg {Ext.Element} media
  30333. * A reference to the underlying audio/video element.
  30334. * @accessor
  30335. */
  30336. media: null,
  30337. /**
  30338. * @cfg {Number} volume
  30339. * The volume of the media from 0.0 to 1.0.
  30340. * @accessor
  30341. */
  30342. volume: 1,
  30343. /**
  30344. * @cfg {Boolean} muted
  30345. * Whether or not the media is muted. This will also set the volume to zero.
  30346. * @accessor
  30347. */
  30348. muted: false
  30349. },
  30350. constructor: function() {
  30351. this.mediaEvents = {};
  30352. this.callSuper(arguments);
  30353. },
  30354. initialize: function() {
  30355. var me = this;
  30356. me.callParent();
  30357. me.on({
  30358. scope: me,
  30359. activate : me.onActivate,
  30360. deactivate: me.onDeactivate
  30361. });
  30362. me.addMediaListener({
  30363. canplay: 'onCanPlay',
  30364. play: 'onPlay',
  30365. pause: 'onPause',
  30366. ended: 'onEnd',
  30367. volumechange: 'onVolumeChange',
  30368. timeupdate: 'onTimeUpdate'
  30369. });
  30370. },
  30371. addMediaListener: function(event, fn) {
  30372. var me = this,
  30373. dom = me.media.dom,
  30374. bind = Ext.Function.bind;
  30375. Ext.Object.each(event, function(e, fn) {
  30376. fn = bind(me[fn], me);
  30377. me.mediaEvents[e] = fn;
  30378. dom.addEventListener(e, fn);
  30379. });
  30380. },
  30381. onPlay: function() {
  30382. this.fireEvent('play', this);
  30383. },
  30384. onCanPlay: function() {
  30385. this.fireEvent('canplay', this);
  30386. },
  30387. onPause: function() {
  30388. this.fireEvent('pause', this, this.getCurrentTime());
  30389. },
  30390. onEnd: function() {
  30391. this.fireEvent('ended', this, this.getCurrentTime());
  30392. },
  30393. onVolumeChange: function() {
  30394. this.fireEvent('volumechange', this, this.media.dom.volume);
  30395. },
  30396. onTimeUpdate: function() {
  30397. this.fireEvent('timeupdate', this, this.getCurrentTime());
  30398. },
  30399. /**
  30400. * Returns if the media is currently playing.
  30401. * @return {Boolean} playing `true` if the media is playing.
  30402. */
  30403. isPlaying: function() {
  30404. return !Boolean(this.media.dom.paused);
  30405. },
  30406. // @private
  30407. onActivate: function() {
  30408. var me = this;
  30409. if (me.getAutoResume() && !me.isPlaying()) {
  30410. me.play();
  30411. }
  30412. },
  30413. // @private
  30414. onDeactivate: function() {
  30415. var me = this;
  30416. if (me.getAutoPause() && me.isPlaying()) {
  30417. me.pause();
  30418. }
  30419. },
  30420. /**
  30421. * Sets the URL of the media element. If the media element already exists, it is update the src attribute of the
  30422. * element. If it is currently playing, it will start the new video.
  30423. */
  30424. updateUrl: function(newUrl) {
  30425. var dom = this.media.dom;
  30426. //when changing the src, we must call load:
  30427. //http://developer.apple.com/library/safari/#documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/ControllingMediaWithJavaScript/ControllingMediaWithJavaScript.html
  30428. dom.src = newUrl;
  30429. if ('load' in dom) {
  30430. dom.load();
  30431. }
  30432. if (this.isPlaying()) {
  30433. this.play();
  30434. }
  30435. },
  30436. /**
  30437. * Updates the controls of the video element.
  30438. */
  30439. updateEnableControls: function(enableControls) {
  30440. this.media.dom.controls = enableControls ? 'controls' : false;
  30441. },
  30442. /**
  30443. * Updates the loop setting of the media element.
  30444. */
  30445. updateLoop: function(loop) {
  30446. this.media.dom.loop = loop ? 'loop' : false;
  30447. },
  30448. /**
  30449. * Starts or resumes media playback.
  30450. */
  30451. play: function() {
  30452. var dom = this.media.dom;
  30453. if ('play' in dom) {
  30454. dom.play();
  30455. setTimeout(function() {
  30456. dom.play();
  30457. }, 10);
  30458. }
  30459. },
  30460. /**
  30461. * Pauses media playback.
  30462. */
  30463. pause: function() {
  30464. var dom = this.media.dom;
  30465. if ('pause' in dom) {
  30466. dom.pause();
  30467. }
  30468. },
  30469. /**
  30470. * Toggles the media playback state.
  30471. */
  30472. toggle: function() {
  30473. if (this.isPlaying()) {
  30474. this.pause();
  30475. } else {
  30476. this.play();
  30477. }
  30478. },
  30479. /**
  30480. * Stops media playback and returns to the beginning.
  30481. */
  30482. stop: function() {
  30483. var me = this;
  30484. me.setCurrentTime(0);
  30485. me.fireEvent('stop', me);
  30486. me.pause();
  30487. },
  30488. //@private
  30489. updateVolume: function(volume) {
  30490. this.media.dom.volume = volume;
  30491. },
  30492. //@private
  30493. updateMuted: function(muted) {
  30494. this.fireEvent('mutedchange', this, muted);
  30495. this.media.dom.muted = muted;
  30496. },
  30497. /**
  30498. * Returns the current time of the media, in seconds.
  30499. * @return {Number}
  30500. */
  30501. getCurrentTime: function() {
  30502. return this.media.dom.currentTime;
  30503. },
  30504. /*
  30505. * Set the current time of the media.
  30506. * @param {Number} time The time, in seconds.
  30507. * @return {Number}
  30508. */
  30509. setCurrentTime: function(time) {
  30510. this.media.dom.currentTime = time;
  30511. return time;
  30512. },
  30513. /**
  30514. * Returns the duration of the media, in seconds.
  30515. * @return {Number}
  30516. */
  30517. getDuration: function() {
  30518. return this.media.dom.duration;
  30519. },
  30520. destroy: function() {
  30521. var me = this,
  30522. dom = me.media.dom,
  30523. mediaEvents = me.mediaEvents;
  30524. Ext.Object.each(mediaEvents, function(event, fn) {
  30525. dom.removeEventListener(event, fn);
  30526. });
  30527. this.callSuper();
  30528. }
  30529. });
  30530. /**
  30531. * {@link Ext.Audio} is a simple class which provides a container for the [HTML5 Audio element](http://developer.mozilla.org/en-US/docs/Using_HTML5_audio_and_video).
  30532. *
  30533. * ## Recommended File Types/Compression:
  30534. * * Uncompressed WAV and AIF audio
  30535. * * MP3 audio
  30536. * * AAC-LC
  30537. * * HE-AAC audio
  30538. *
  30539. * ## Notes
  30540. * On Android devices, the audio tags controls do not show. You must use the {@link #method-play}, {@link #method-pause} and
  30541. * {@link #toggle} methods to control the audio (example below).
  30542. *
  30543. * ## Examples
  30544. *
  30545. * Here is an example of the {@link Ext.Audio} component in a fullscreen container:
  30546. *
  30547. * @example preview
  30548. * Ext.create('Ext.Container', {
  30549. * fullscreen: true,
  30550. * layout: {
  30551. * type : 'vbox',
  30552. * pack : 'center',
  30553. * align: 'stretch'
  30554. * },
  30555. * items: [
  30556. * {
  30557. * xtype : 'toolbar',
  30558. * docked: 'top',
  30559. * title : 'Ext.Audio'
  30560. * },
  30561. * {
  30562. * xtype: 'audio',
  30563. * url : 'touch/examples/audio/crash.mp3'
  30564. * }
  30565. * ]
  30566. * });
  30567. *
  30568. * You can also set the {@link #hidden} configuration of the {@link Ext.Audio} component to true by default,
  30569. * and then control the audio by using the {@link #method-play}, {@link #method-pause} and {@link #toggle} methods:
  30570. *
  30571. * @example preview
  30572. * Ext.create('Ext.Container', {
  30573. * fullscreen: true,
  30574. * layout: {
  30575. * type: 'vbox',
  30576. * pack: 'center'
  30577. * },
  30578. * items: [
  30579. * {
  30580. * xtype : 'toolbar',
  30581. * docked: 'top',
  30582. * title : 'Ext.Audio'
  30583. * },
  30584. * {
  30585. * xtype: 'toolbar',
  30586. * docked: 'bottom',
  30587. * defaults: {
  30588. * xtype: 'button',
  30589. * handler: function() {
  30590. * var container = this.getParent().getParent(),
  30591. * // use ComponentQuery to get the audio component (using its xtype)
  30592. * audio = container.down('audio');
  30593. *
  30594. * audio.toggle();
  30595. * this.setText(audio.isPlaying() ? 'Pause' : 'Play');
  30596. * }
  30597. * },
  30598. * items: [
  30599. * { text: 'Play', flex: 1 }
  30600. * ]
  30601. * },
  30602. * {
  30603. * html: 'Hidden audio!',
  30604. * styleHtmlContent: true
  30605. * },
  30606. * {
  30607. * xtype : 'audio',
  30608. * hidden: true,
  30609. * url : 'touch/examples/audio/crash.mp3'
  30610. * }
  30611. * ]
  30612. * });
  30613. * @aside example audio
  30614. */
  30615. Ext.define('Ext.Audio', {
  30616. extend: 'Ext.Media',
  30617. xtype : 'audio',
  30618. config: {
  30619. /**
  30620. * @cfg
  30621. * @inheritdoc
  30622. */
  30623. cls: Ext.baseCSSPrefix + 'audio'
  30624. /**
  30625. * @cfg {String} url
  30626. * The location of the audio to play.
  30627. *
  30628. * ### Recommended file types are:
  30629. * * Uncompressed WAV and AIF audio
  30630. * * MP3 audio
  30631. * * AAC-LC
  30632. * * HE-AAC audio
  30633. * @accessor
  30634. */
  30635. },
  30636. // @private
  30637. onActivate: function() {
  30638. var me = this;
  30639. me.callParent();
  30640. if (Ext.os.is.Phone) {
  30641. me.element.show();
  30642. }
  30643. },
  30644. // @private
  30645. onDeactivate: function() {
  30646. var me = this;
  30647. me.callParent();
  30648. if (Ext.os.is.Phone) {
  30649. me.element.hide();
  30650. }
  30651. },
  30652. template: [{
  30653. reference: 'media',
  30654. preload: 'auto',
  30655. tag: 'audio',
  30656. cls: Ext.baseCSSPrefix + 'component'
  30657. }]
  30658. });
  30659. /**
  30660. * @class Ext.ComponentQuery
  30661. * @extends Object
  30662. * @singleton
  30663. *
  30664. * Provides searching of Components within {@link Ext.ComponentManager} (globally) or a specific
  30665. * {@link Ext.Container} on the document with a similar syntax to a CSS selector.
  30666. *
  30667. * Components can be retrieved by using their {@link Ext.Component xtype} with an optional '.' prefix
  30668. *
  30669. * - `component` or `.component`
  30670. * - `gridpanel` or `.gridpanel`
  30671. *
  30672. * An itemId or id must be prefixed with a #
  30673. *
  30674. * - `#myContainer`
  30675. *
  30676. * Attributes must be wrapped in brackets
  30677. *
  30678. * - `component[autoScroll]`
  30679. * - `panel[title="Test"]`
  30680. *
  30681. * Member expressions from candidate Components may be tested. If the expression returns a *truthy* value,
  30682. * the candidate Component will be included in the query:
  30683. *
  30684. * var disabledFields = myFormPanel.query("{isDisabled()}");
  30685. *
  30686. * Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:
  30687. *
  30688. * // Function receives array and returns a filtered array.
  30689. * Ext.ComponentQuery.pseudos.invalid = function(items) {
  30690. * var i = 0, l = items.length, c, result = [];
  30691. * for (; i < l; i++) {
  30692. * if (!(c = items[i]).isValid()) {
  30693. * result.push(c);
  30694. * }
  30695. * }
  30696. * return result;
  30697. * };
  30698. *
  30699. * var invalidFields = myFormPanel.query('field:invalid');
  30700. * if (invalidFields.length) {
  30701. * invalidFields[0].getEl().scrollIntoView(myFormPanel.body);
  30702. * for (var i = 0, l = invalidFields.length; i < l; i++) {
  30703. * invalidFields[i].getEl().frame("red");
  30704. * }
  30705. * }
  30706. *
  30707. * Default pseudos include:
  30708. *
  30709. * - not
  30710. *
  30711. * Queries return an array of components.
  30712. * Here are some example queries.
  30713. *
  30714. * // retrieve all Ext.Panels in the document by xtype
  30715. * var panelsArray = Ext.ComponentQuery.query('panel');
  30716. *
  30717. * // retrieve all Ext.Panels within the container with an id myCt
  30718. * var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
  30719. *
  30720. * // retrieve all direct children which are Ext.Panels within myCt
  30721. * var directChildPanel = Ext.ComponentQuery.query('#myCt > panel');
  30722. *
  30723. * // retrieve all grids and trees
  30724. * var gridsAndTrees = Ext.ComponentQuery.query('gridpanel, treepanel');
  30725. *
  30726. * For easy access to queries based from a particular Container see the {@link Ext.Container#query},
  30727. * {@link Ext.Container#down} and {@link Ext.Container#child} methods. Also see
  30728. * {@link Ext.Component#up}.
  30729. */
  30730. Ext.define('Ext.ComponentQuery', {
  30731. singleton: true,
  30732. uses: ['Ext.ComponentManager']
  30733. }, function() {
  30734. var cq = this,
  30735. // A function source code pattern with a placeholder which accepts an expression which yields a truth value when applied
  30736. // as a member on each item in the passed array.
  30737. filterFnPattern = [
  30738. 'var r = [],',
  30739. 'i = 0,',
  30740. 'it = items,',
  30741. 'l = it.length,',
  30742. 'c;',
  30743. 'for (; i < l; i++) {',
  30744. 'c = it[i];',
  30745. 'if (c.{0}) {',
  30746. 'r.push(c);',
  30747. '}',
  30748. '}',
  30749. 'return r;'
  30750. ].join(''),
  30751. filterItems = function(items, operation) {
  30752. // Argument list for the operation is [ itemsArray, operationArg1, operationArg2...]
  30753. // The operation's method loops over each item in the candidate array and
  30754. // returns an array of items which match its criteria
  30755. return operation.method.apply(this, [ items ].concat(operation.args));
  30756. },
  30757. getItems = function(items, mode) {
  30758. var result = [],
  30759. i = 0,
  30760. length = items.length,
  30761. candidate,
  30762. deep = mode !== '>';
  30763. for (; i < length; i++) {
  30764. candidate = items[i];
  30765. if (candidate.getRefItems) {
  30766. result = result.concat(candidate.getRefItems(deep));
  30767. }
  30768. }
  30769. return result;
  30770. },
  30771. getAncestors = function(items) {
  30772. var result = [],
  30773. i = 0,
  30774. length = items.length,
  30775. candidate;
  30776. for (; i < length; i++) {
  30777. candidate = items[i];
  30778. while (!!(candidate = (candidate.ownerCt || candidate.floatParent))) {
  30779. result.push(candidate);
  30780. }
  30781. }
  30782. return result;
  30783. },
  30784. // Filters the passed candidate array and returns only items which match the passed xtype
  30785. filterByXType = function(items, xtype, shallow) {
  30786. if (xtype === '*') {
  30787. return items.slice();
  30788. }
  30789. else {
  30790. var result = [],
  30791. i = 0,
  30792. length = items.length,
  30793. candidate;
  30794. for (; i < length; i++) {
  30795. candidate = items[i];
  30796. if (candidate.isXType(xtype, shallow)) {
  30797. result.push(candidate);
  30798. }
  30799. }
  30800. return result;
  30801. }
  30802. },
  30803. // Filters the passed candidate array and returns only items which have the passed className
  30804. filterByClassName = function(items, className) {
  30805. var EA = Ext.Array,
  30806. result = [],
  30807. i = 0,
  30808. length = items.length,
  30809. candidate;
  30810. for (; i < length; i++) {
  30811. candidate = items[i];
  30812. if (candidate.el ? candidate.el.hasCls(className) : EA.contains(candidate.initCls(), className)) {
  30813. result.push(candidate);
  30814. }
  30815. }
  30816. return result;
  30817. },
  30818. // Filters the passed candidate array and returns only items which have the specified property match
  30819. filterByAttribute = function(items, property, operator, value) {
  30820. var result = [],
  30821. i = 0,
  30822. length = items.length,
  30823. candidate, getter, getValue;
  30824. for (; i < length; i++) {
  30825. candidate = items[i];
  30826. getter = Ext.Class.getConfigNameMap(property).get;
  30827. if (candidate[getter]) {
  30828. getValue = candidate[getter]();
  30829. if (!value ? !!getValue : (String(getValue) === value)) {
  30830. result.push(candidate);
  30831. }
  30832. }
  30833. else if (candidate.config && candidate.config[property]) {
  30834. if (!value ? !!candidate.config[property] : (String(candidate.config[property]) === value)) {
  30835. result.push(candidate);
  30836. }
  30837. }
  30838. else if (!value ? !!candidate[property] : (String(candidate[property]) === value)) {
  30839. result.push(candidate);
  30840. }
  30841. }
  30842. return result;
  30843. },
  30844. // Filters the passed candidate array and returns only items which have the specified itemId or id
  30845. filterById = function(items, id) {
  30846. var result = [],
  30847. i = 0,
  30848. length = items.length,
  30849. candidate;
  30850. for (; i < length; i++) {
  30851. candidate = items[i];
  30852. if (candidate.getId() === id || candidate.getItemId() === id) {
  30853. result.push(candidate);
  30854. }
  30855. }
  30856. return result;
  30857. },
  30858. // Filters the passed candidate array and returns only items which the named pseudo class matcher filters in
  30859. filterByPseudo = function(items, name, value) {
  30860. return cq.pseudos[name](items, value);
  30861. },
  30862. // Determines leading mode
  30863. // > for direct child, and ^ to switch to ownerCt axis
  30864. modeRe = /^(\s?([>\^])\s?|\s|$)/,
  30865. // Matches a token with possibly (true|false) appended for the "shallow" parameter
  30866. tokenRe = /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,
  30867. matchers = [{
  30868. // Checks for .xtype with possibly (true|false) appended for the "shallow" parameter
  30869. re: /^\.([\w\-]+)(?:\((true|false)\))?/,
  30870. method: filterByXType
  30871. },{
  30872. // checks for [attribute=value]
  30873. re: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]])/,
  30874. method: filterByAttribute
  30875. }, {
  30876. // checks for #cmpItemId
  30877. re: /^#([\w\-]+)/,
  30878. method: filterById
  30879. }, {
  30880. // checks for :<pseudo_class>(<selector>)
  30881. re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,
  30882. method: filterByPseudo
  30883. }, {
  30884. // checks for {<member_expression>}
  30885. re: /^(?:\{([^\}]+)\})/,
  30886. method: filterFnPattern
  30887. }];
  30888. cq.Query = Ext.extend(Object, {
  30889. constructor: function(cfg) {
  30890. cfg = cfg || {};
  30891. Ext.apply(this, cfg);
  30892. },
  30893. /**
  30894. * @private
  30895. * Executes this Query upon the selected root.
  30896. * The root provides the initial source of candidate Component matches which are progressively
  30897. * filtered by iterating through this Query's operations cache.
  30898. * If no root is provided, all registered Components are searched via the ComponentManager.
  30899. * root may be a Container who's descendant Components are filtered
  30900. * root may be a Component with an implementation of getRefItems which provides some nested Components such as the
  30901. * docked items within a Panel.
  30902. * root may be an array of candidate Components to filter using this Query.
  30903. */
  30904. execute : function(root) {
  30905. var operations = this.operations,
  30906. i = 0,
  30907. length = operations.length,
  30908. operation,
  30909. workingItems;
  30910. // no root, use all Components in the document
  30911. if (!root) {
  30912. workingItems = Ext.ComponentManager.all.getArray();
  30913. }
  30914. // Root is a candidate Array
  30915. else if (Ext.isArray(root)) {
  30916. workingItems = root;
  30917. }
  30918. // We are going to loop over our operations and take care of them
  30919. // one by one.
  30920. for (; i < length; i++) {
  30921. operation = operations[i];
  30922. // The mode operation requires some custom handling.
  30923. // All other operations essentially filter down our current
  30924. // working items, while mode replaces our current working
  30925. // items by getting children from each one of our current
  30926. // working items. The type of mode determines the type of
  30927. // children we get. (e.g. > only gets direct children)
  30928. if (operation.mode === '^') {
  30929. workingItems = getAncestors(workingItems || [root]);
  30930. }
  30931. else if (operation.mode) {
  30932. workingItems = getItems(workingItems || [root], operation.mode);
  30933. }
  30934. else {
  30935. workingItems = filterItems(workingItems || getItems([root]), operation);
  30936. }
  30937. // If this is the last operation, it means our current working
  30938. // items are the final matched items. Thus return them!
  30939. if (i === length -1) {
  30940. return workingItems;
  30941. }
  30942. }
  30943. return [];
  30944. },
  30945. is: function(component) {
  30946. var operations = this.operations,
  30947. components = Ext.isArray(component) ? component : [component],
  30948. originalLength = components.length,
  30949. lastOperation = operations[operations.length-1],
  30950. ln, i;
  30951. components = filterItems(components, lastOperation);
  30952. if (components.length === originalLength) {
  30953. if (operations.length > 1) {
  30954. for (i = 0, ln = components.length; i < ln; i++) {
  30955. if (Ext.Array.indexOf(this.execute(), components[i]) === -1) {
  30956. return false;
  30957. }
  30958. }
  30959. }
  30960. return true;
  30961. }
  30962. return false;
  30963. }
  30964. });
  30965. Ext.apply(this, {
  30966. // private cache of selectors and matching ComponentQuery.Query objects
  30967. cache: {},
  30968. // private cache of pseudo class filter functions
  30969. pseudos: {
  30970. not: function(components, selector){
  30971. var CQ = Ext.ComponentQuery,
  30972. i = 0,
  30973. length = components.length,
  30974. results = [],
  30975. index = -1,
  30976. component;
  30977. for(; i < length; ++i) {
  30978. component = components[i];
  30979. if (!CQ.is(component, selector)) {
  30980. results[++index] = component;
  30981. }
  30982. }
  30983. return results;
  30984. }
  30985. },
  30986. /**
  30987. * Returns an array of matched Components from within the passed root object.
  30988. *
  30989. * This method filters returned Components in a similar way to how CSS selector based DOM
  30990. * queries work using a textual selector string.
  30991. *
  30992. * See class summary for details.
  30993. *
  30994. * @param {String} selector The selector string to filter returned Components
  30995. * @param {Ext.Container} root The Container within which to perform the query.
  30996. * If omitted, all Components within the document are included in the search.
  30997. *
  30998. * This parameter may also be an array of Components to filter according to the selector.</p>
  30999. * @return {Ext.Component[]} The matched Components.
  31000. *
  31001. * @member Ext.ComponentQuery
  31002. */
  31003. query: function(selector, root) {
  31004. var selectors = selector.split(','),
  31005. length = selectors.length,
  31006. i = 0,
  31007. results = [],
  31008. noDupResults = [],
  31009. dupMatcher = {},
  31010. query, resultsLn, cmp;
  31011. for (; i < length; i++) {
  31012. selector = Ext.String.trim(selectors[i]);
  31013. query = this.parse(selector);
  31014. // query = this.cache[selector];
  31015. // if (!query) {
  31016. // this.cache[selector] = query = this.parse(selector);
  31017. // }
  31018. results = results.concat(query.execute(root));
  31019. }
  31020. // multiple selectors, potential to find duplicates
  31021. // lets filter them out.
  31022. if (length > 1) {
  31023. resultsLn = results.length;
  31024. for (i = 0; i < resultsLn; i++) {
  31025. cmp = results[i];
  31026. if (!dupMatcher[cmp.id]) {
  31027. noDupResults.push(cmp);
  31028. dupMatcher[cmp.id] = true;
  31029. }
  31030. }
  31031. results = noDupResults;
  31032. }
  31033. return results;
  31034. },
  31035. /**
  31036. * Tests whether the passed Component matches the selector string.
  31037. * @param {Ext.Component} component The Component to test.
  31038. * @param {String} selector The selector string to test against.
  31039. * @return {Boolean} `true` if the Component matches the selector.
  31040. * @member Ext.ComponentQuery
  31041. */
  31042. is: function(component, selector) {
  31043. if (!selector) {
  31044. return true;
  31045. }
  31046. var query = this.cache[selector];
  31047. if (!query) {
  31048. this.cache[selector] = query = this.parse(selector);
  31049. }
  31050. return query.is(component);
  31051. },
  31052. parse: function(selector) {
  31053. var operations = [],
  31054. length = matchers.length,
  31055. lastSelector,
  31056. tokenMatch,
  31057. matchedChar,
  31058. modeMatch,
  31059. selectorMatch,
  31060. i, matcher, method;
  31061. // We are going to parse the beginning of the selector over and
  31062. // over again, slicing off the selector any portions we converted into an
  31063. // operation, until it is an empty string.
  31064. while (selector && lastSelector !== selector) {
  31065. lastSelector = selector;
  31066. // First we check if we are dealing with a token like #, * or an xtype
  31067. tokenMatch = selector.match(tokenRe);
  31068. if (tokenMatch) {
  31069. matchedChar = tokenMatch[1];
  31070. // If the token is prefixed with a # we push a filterById operation to our stack
  31071. if (matchedChar === '#') {
  31072. operations.push({
  31073. method: filterById,
  31074. args: [Ext.String.trim(tokenMatch[2])]
  31075. });
  31076. }
  31077. // If the token is prefixed with a . we push a filterByClassName operation to our stack
  31078. // FIXME: Not enabled yet. just needs \. adding to the tokenRe prefix
  31079. else if (matchedChar === '.') {
  31080. operations.push({
  31081. method: filterByClassName,
  31082. args: [Ext.String.trim(tokenMatch[2])]
  31083. });
  31084. }
  31085. // If the token is a * or an xtype string, we push a filterByXType
  31086. // operation to the stack.
  31087. else {
  31088. operations.push({
  31089. method: filterByXType,
  31090. args: [Ext.String.trim(tokenMatch[2]), Boolean(tokenMatch[3])]
  31091. });
  31092. }
  31093. // Now we slice of the part we just converted into an operation
  31094. selector = selector.replace(tokenMatch[0], '');
  31095. }
  31096. // If the next part of the query is not a space or > or ^, it means we
  31097. // are going to check for more things that our current selection
  31098. // has to comply to.
  31099. while (!(modeMatch = selector.match(modeRe))) {
  31100. // Lets loop over each type of matcher and execute it
  31101. // on our current selector.
  31102. for (i = 0; selector && i < length; i++) {
  31103. matcher = matchers[i];
  31104. selectorMatch = selector.match(matcher.re);
  31105. method = matcher.method;
  31106. // If we have a match, add an operation with the method
  31107. // associated with this matcher, and pass the regular
  31108. // expression matches are arguments to the operation.
  31109. if (selectorMatch) {
  31110. operations.push({
  31111. method: Ext.isString(matcher.method)
  31112. // Turn a string method into a function by formatting the string with our selector matche expression
  31113. // A new method is created for different match expressions, eg {id=='textfield-1024'}
  31114. // Every expression may be different in different selectors.
  31115. ? Ext.functionFactory('items', Ext.String.format.apply(Ext.String, [method].concat(selectorMatch.slice(1))))
  31116. : matcher.method,
  31117. args: selectorMatch.slice(1)
  31118. });
  31119. selector = selector.replace(selectorMatch[0], '');
  31120. break; // Break on match
  31121. }
  31122. //<debug>
  31123. // Exhausted all matches: It's an error
  31124. if (i === (length - 1)) {
  31125. Ext.Error.raise('Invalid ComponentQuery selector: "' + arguments[0] + '"');
  31126. }
  31127. //</debug>
  31128. }
  31129. }
  31130. // Now we are going to check for a mode change. This means a space
  31131. // or a > to determine if we are going to select all the children
  31132. // of the currently matched items, or a ^ if we are going to use the
  31133. // ownerCt axis as the candidate source.
  31134. if (modeMatch[1]) { // Assignment, and test for truthiness!
  31135. operations.push({
  31136. mode: modeMatch[2]||modeMatch[1]
  31137. });
  31138. selector = selector.replace(modeMatch[0], '');
  31139. }
  31140. }
  31141. // Now that we have all our operations in an array, we are going
  31142. // to create a new Query using these operations.
  31143. return new cq.Query({
  31144. operations: operations
  31145. });
  31146. }
  31147. });
  31148. });
  31149. /**
  31150. * @class Ext.Decorator
  31151. * @extends Ext.Component
  31152. *
  31153. * In a few words, a Decorator is a Component that wraps around another Component. A typical example of a Decorator is a
  31154. * {@link Ext.field.Field Field}. A form field is nothing more than a decorator around another component, and gives the
  31155. * component a label, as well as extra styling to make it look good in a form.
  31156. *
  31157. * A Decorator can be thought of as a lightweight Container that has only one child item, and no layout overhead.
  31158. * The look and feel of decorators can be styled purely in CSS.
  31159. *
  31160. * Another powerful feature that Decorator provides is config proxying. For example: all config items of a
  31161. * {@link Ext.slider.Slider Slider} also exist in a {@link Ext.field.Slider Slider Field} for API convenience.
  31162. * The {@link Ext.field.Slider Slider Field} simply proxies all corresponding getters and setters
  31163. * to the actual {@link Ext.slider.Slider Slider} instance. Writing out all the setters and getters to do that is a tedious task
  31164. * and a waste of code space. Instead, when you sub-class Ext.Decorator, all you need to do is to specify those config items
  31165. * that you want to proxy to the Component using a special 'proxyConfig' class property. Here's how it may look like
  31166. * in a Slider Field class:
  31167. *
  31168. * Ext.define('My.field.Slider', {
  31169. * extend: 'Ext.Decorator',
  31170. *
  31171. * config: {
  31172. * component: {
  31173. * xtype: 'slider'
  31174. * }
  31175. * },
  31176. *
  31177. * proxyConfig: {
  31178. * minValue: 0,
  31179. * maxValue: 100,
  31180. * increment: 1
  31181. * }
  31182. *
  31183. * // ...
  31184. * });
  31185. *
  31186. * Once `My.field.Slider` class is created, it will have all setters and getters methods for all items listed in `proxyConfig`
  31187. * automatically generated. These methods all proxy to the same method names that exist within the Component instance.
  31188. */
  31189. Ext.define('Ext.Decorator', {
  31190. extend: 'Ext.Component',
  31191. isDecorator: true,
  31192. config: {
  31193. /**
  31194. * @cfg {Object} component The config object to factory the Component that this Decorator wraps around
  31195. */
  31196. component: {}
  31197. },
  31198. statics: {
  31199. generateProxySetter: function(name) {
  31200. return function(value) {
  31201. var component = this.getComponent();
  31202. component[name].call(component, value);
  31203. return this;
  31204. }
  31205. },
  31206. generateProxyGetter: function(name) {
  31207. return function() {
  31208. var component = this.getComponent();
  31209. return component[name].call(component);
  31210. }
  31211. }
  31212. },
  31213. onClassExtended: function(Class, members) {
  31214. if (!members.hasOwnProperty('proxyConfig')) {
  31215. return;
  31216. }
  31217. var ExtClass = Ext.Class,
  31218. proxyConfig = members.proxyConfig,
  31219. config = members.config;
  31220. members.config = (config) ? Ext.applyIf(config, proxyConfig) : proxyConfig;
  31221. var name, nameMap, setName, getName;
  31222. for (name in proxyConfig) {
  31223. if (proxyConfig.hasOwnProperty(name)) {
  31224. nameMap = ExtClass.getConfigNameMap(name);
  31225. setName = nameMap.set;
  31226. getName = nameMap.get;
  31227. members[setName] = this.generateProxySetter(setName);
  31228. members[getName] = this.generateProxyGetter(getName);
  31229. }
  31230. }
  31231. },
  31232. // @private
  31233. applyComponent: function(config) {
  31234. return Ext.factory(config, Ext.Component);
  31235. },
  31236. // @private
  31237. updateComponent: function(newComponent, oldComponent) {
  31238. if (oldComponent) {
  31239. if (this.isRendered() && oldComponent.setRendered(false)) {
  31240. oldComponent.fireAction('renderedchange', [this, oldComponent, false],
  31241. 'doUnsetComponent', this, { args: [oldComponent] });
  31242. }
  31243. else {
  31244. this.doUnsetComponent(oldComponent);
  31245. }
  31246. }
  31247. if (newComponent) {
  31248. if (this.isRendered() && newComponent.setRendered(true)) {
  31249. newComponent.fireAction('renderedchange', [this, newComponent, true],
  31250. 'doSetComponent', this, { args: [newComponent] });
  31251. }
  31252. else {
  31253. this.doSetComponent(newComponent);
  31254. }
  31255. }
  31256. },
  31257. // @private
  31258. doUnsetComponent: function(component) {
  31259. if (component.renderElement.dom) {
  31260. component.setLayoutSizeFlags(0);
  31261. this.innerElement.dom.removeChild(component.renderElement.dom);
  31262. }
  31263. },
  31264. // @private
  31265. doSetComponent: function(component) {
  31266. if (component.renderElement.dom) {
  31267. component.setLayoutSizeFlags(this.getSizeFlags());
  31268. this.innerElement.dom.appendChild(component.renderElement.dom);
  31269. }
  31270. },
  31271. // @private
  31272. setRendered: function(rendered) {
  31273. var component;
  31274. if (this.callParent(arguments)) {
  31275. component = this.getComponent();
  31276. if (component) {
  31277. component.setRendered(rendered);
  31278. }
  31279. return true;
  31280. }
  31281. return false;
  31282. },
  31283. // @private
  31284. setDisabled: function(disabled) {
  31285. this.callParent(arguments);
  31286. this.getComponent().setDisabled(disabled);
  31287. },
  31288. destroy: function() {
  31289. Ext.destroy(this.getComponent());
  31290. this.callParent();
  31291. }
  31292. });
  31293. /**
  31294. * This is a simple way to add an image of any size to your application and have it participate in the layout system
  31295. * like any other component. This component typically takes between 1 and 3 configurations - a {@link #src}, and
  31296. * optionally a {@link #height} and a {@link #width}:
  31297. *
  31298. * @example miniphone
  31299. * var img = Ext.create('Ext.Img', {
  31300. * src: 'http://www.sencha.com/assets/images/sencha-avatar-64x64.png',
  31301. * height: 64,
  31302. * width: 64
  31303. * });
  31304. * Ext.Viewport.add(img);
  31305. *
  31306. * It's also easy to add an image into a panel or other container using its xtype:
  31307. *
  31308. * @example miniphone
  31309. * Ext.create('Ext.Panel', {
  31310. * fullscreen: true,
  31311. * layout: 'hbox',
  31312. * items: [
  31313. * {
  31314. * xtype: 'image',
  31315. * src: 'http://www.sencha.com/assets/images/sencha-avatar-64x64.png',
  31316. * flex: 1
  31317. * },
  31318. * {
  31319. * xtype: 'panel',
  31320. * flex: 2,
  31321. * html: 'Sencha Inc.<br/>1700 Seaport Boulevard Suite 120, Redwood City, CA'
  31322. * }
  31323. * ]
  31324. * });
  31325. *
  31326. * Here we created a panel which contains an image (a profile picture in this case) and a text area to allow the user
  31327. * to enter profile information about themselves. In this case we used an {@link Ext.layout.HBox hbox layout} and
  31328. * flexed the image to take up one third of the width and the text area to take two thirds of the width. See the
  31329. * {@link Ext.layout.HBox hbox docs} for more information on flexing items.
  31330. */
  31331. Ext.define('Ext.Img', {
  31332. extend: 'Ext.Component',
  31333. xtype: ['image', 'img'],
  31334. /**
  31335. * @event tap
  31336. * Fires whenever the component is tapped
  31337. * @param {Ext.Img} this The Image instance
  31338. * @param {Ext.EventObject} e The event object
  31339. */
  31340. /**
  31341. * @event load
  31342. * Fires when the image is loaded
  31343. * @param {Ext.Img} this The Image instance
  31344. * @param {Ext.EventObject} e The event object
  31345. */
  31346. /**
  31347. * @event error
  31348. * Fires if an error occured when trying to load the image
  31349. * @param {Ext.Img} this The Image instance
  31350. * @param {Ext.EventObject} e The event object
  31351. */
  31352. config: {
  31353. /**
  31354. * @cfg {String} src The source of this image
  31355. * @accessor
  31356. */
  31357. src: null,
  31358. /**
  31359. * @cfg
  31360. * @inheritdoc
  31361. */
  31362. baseCls : Ext.baseCSSPrefix + 'img',
  31363. /**
  31364. * @cfg {String} imageCls The CSS class to be used when {@link #mode} is not set to 'background'
  31365. * @accessor
  31366. */
  31367. imageCls : Ext.baseCSSPrefix + 'img-image',
  31368. /**
  31369. * @cfg {String} backgroundCls The CSS class to be used when {@link #mode} is set to 'background'
  31370. * @accessor
  31371. */
  31372. backgroundCls : Ext.baseCSSPrefix + 'img-background',
  31373. /**
  31374. * @cfg {String} mode If set to 'background', uses a background-image CSS property instead of an
  31375. * `<img>` tag to display the image.
  31376. */
  31377. mode: 'background'
  31378. },
  31379. beforeInitialize: function() {
  31380. var me = this;
  31381. me.onLoad = Ext.Function.bind(me.onLoad, me);
  31382. me.onError = Ext.Function.bind(me.onError, me);
  31383. },
  31384. initialize: function() {
  31385. var me = this;
  31386. me.callParent();
  31387. me.relayEvents(me.renderElement, '*');
  31388. me.element.on({
  31389. tap: 'onTap',
  31390. scope: me
  31391. });
  31392. },
  31393. hide: function() {
  31394. this.callParent();
  31395. this.hiddenSrc = this.hiddenSrc || this.getSrc();
  31396. this.setSrc(null);
  31397. },
  31398. show: function() {
  31399. this.callParent();
  31400. if (this.hiddenSrc) {
  31401. this.setSrc(this.hiddenSrc);
  31402. delete this.hiddenSrc;
  31403. }
  31404. },
  31405. updateMode: function(mode) {
  31406. var me = this,
  31407. imageCls = me.getImageCls(),
  31408. backgroundCls = me.getBackgroundCls();
  31409. if (mode === 'background') {
  31410. if (me.imageElement) {
  31411. me.imageElement.destroy();
  31412. delete me.imageElement;
  31413. me.updateSrc(me.getSrc());
  31414. }
  31415. me.replaceCls(imageCls, backgroundCls);
  31416. } else {
  31417. me.imageElement = me.element.createChild({ tag: 'img' });
  31418. me.replaceCls(backgroundCls, imageCls);
  31419. }
  31420. },
  31421. updateImageCls : function (newCls, oldCls) {
  31422. this.replaceCls(oldCls, newCls);
  31423. },
  31424. updateBackgroundCls : function (newCls, oldCls) {
  31425. this.replaceCls(oldCls, newCls);
  31426. },
  31427. onTap: function(e) {
  31428. this.fireEvent('tap', this, e);
  31429. },
  31430. onAfterRender: function() {
  31431. this.updateSrc(this.getSrc());
  31432. },
  31433. /**
  31434. * @private
  31435. */
  31436. updateSrc: function(newSrc) {
  31437. var me = this,
  31438. dom;
  31439. if (me.getMode() === 'background') {
  31440. dom = this.imageObject || new Image();
  31441. }
  31442. else {
  31443. dom = me.imageElement.dom;
  31444. }
  31445. this.imageObject = dom;
  31446. dom.setAttribute('src', Ext.isString(newSrc) ? newSrc : '');
  31447. dom.addEventListener('load', me.onLoad, false);
  31448. dom.addEventListener('error', me.onError, false);
  31449. },
  31450. detachListeners: function() {
  31451. var dom = this.imageObject;
  31452. if (dom) {
  31453. dom.removeEventListener('load', this.onLoad, false);
  31454. dom.removeEventListener('error', this.onError, false);
  31455. }
  31456. },
  31457. onLoad : function(e) {
  31458. this.detachListeners();
  31459. if (this.getMode() === 'background') {
  31460. this.element.dom.style.backgroundImage = 'url("' + this.imageObject.src + '")';
  31461. }
  31462. this.fireEvent('load', this, e);
  31463. },
  31464. onError : function(e) {
  31465. this.detachListeners();
  31466. this.fireEvent('error', this, e);
  31467. },
  31468. doSetWidth: function(width) {
  31469. var sizingElement = (this.getMode() === 'background') ? this.element : this.imageElement;
  31470. sizingElement.setWidth(width);
  31471. this.callParent(arguments);
  31472. },
  31473. doSetHeight: function(height) {
  31474. var sizingElement = (this.getMode() === 'background') ? this.element : this.imageElement;
  31475. sizingElement.setHeight(height);
  31476. this.callParent(arguments);
  31477. },
  31478. destroy: function() {
  31479. this.detachListeners();
  31480. Ext.destroy(this.imageObject, this.imageElement);
  31481. delete this.imageObject;
  31482. delete this.imageElement;
  31483. this.callParent();
  31484. }
  31485. });
  31486. /**
  31487. * A simple label component which allows you to insert content using {@link #html} configuration.
  31488. *
  31489. * @example miniphone
  31490. * Ext.Viewport.add({
  31491. * xtype: 'label',
  31492. * html: 'My label!'
  31493. * });
  31494. */
  31495. Ext.define('Ext.Label', {
  31496. extend: 'Ext.Component',
  31497. xtype: 'label',
  31498. config: {
  31499. baseCls: Ext.baseCSSPrefix + 'label'
  31500. /**
  31501. * @cfg {String} html
  31502. * The label of this component.
  31503. */
  31504. }
  31505. });
  31506. /**
  31507. * A simple class used to mask any {@link Ext.Container}.
  31508. *
  31509. * This should rarely be used directly, instead look at the {@link Ext.Container#masked} configuration.
  31510. *
  31511. * ## Example
  31512. *
  31513. * @example miniphone
  31514. * Ext.Viewport.add({
  31515. * masked: {
  31516. * xtype: 'loadmask'
  31517. * }
  31518. * });
  31519. *
  31520. * You can customize the loading {@link #message} and whether or not you want to show the {@link #indicator}:
  31521. *
  31522. * @example miniphone
  31523. * Ext.Viewport.add({
  31524. * masked: {
  31525. * xtype: 'loadmask',
  31526. * message: 'A message..',
  31527. * indicator: false
  31528. * }
  31529. * });
  31530. *
  31531. */
  31532. Ext.define('Ext.LoadMask', {
  31533. extend: 'Ext.Mask',
  31534. xtype: 'loadmask',
  31535. config: {
  31536. /**
  31537. * @cfg {String} message
  31538. * The text to display in a centered loading message box.
  31539. * @accessor
  31540. */
  31541. message: 'Loading...',
  31542. /**
  31543. * @cfg {String} messageCls
  31544. * The CSS class to apply to the loading message element.
  31545. * @accessor
  31546. */
  31547. messageCls: Ext.baseCSSPrefix + 'mask-message',
  31548. /**
  31549. * @cfg {Boolean} indicator
  31550. * True to show the loading indicator on this {@link Ext.LoadMask}.
  31551. * @accessor
  31552. */
  31553. indicator: true
  31554. },
  31555. getTemplate: function() {
  31556. var prefix = Ext.baseCSSPrefix;
  31557. return [
  31558. {
  31559. //it needs an inner so it can be centered within the mask, and have a background
  31560. reference: 'innerElement',
  31561. cls: prefix + 'mask-inner',
  31562. children: [
  31563. //the elements required for the CSS loading {@link #indicator}
  31564. {
  31565. reference: 'indicatorElement',
  31566. cls: prefix + 'loading-spinner-outer',
  31567. children: [
  31568. {
  31569. cls: prefix + 'loading-spinner',
  31570. children: [
  31571. { tag: 'span', cls: prefix + 'loading-top' },
  31572. { tag: 'span', cls: prefix + 'loading-right' },
  31573. { tag: 'span', cls: prefix + 'loading-bottom' },
  31574. { tag: 'span', cls: prefix + 'loading-left' }
  31575. ]
  31576. }
  31577. ]
  31578. },
  31579. //the element used to display the {@link #message}
  31580. {
  31581. reference: 'messageElement'
  31582. }
  31583. ]
  31584. }
  31585. ];
  31586. },
  31587. /**
  31588. * Updates the message element with the new value of the {@link #message} configuration
  31589. * @private
  31590. */
  31591. updateMessage: function(newMessage) {
  31592. var cls = Ext.baseCSSPrefix + 'has-message';
  31593. if (newMessage) {
  31594. this.addCls(cls);
  31595. } else {
  31596. this.removeCls(cls);
  31597. }
  31598. this.messageElement.setHtml(newMessage);
  31599. },
  31600. /**
  31601. * Replaces the cls of the message element with the value of the {@link #messageCls} configuration.
  31602. * @private
  31603. */
  31604. updateMessageCls: function(newMessageCls, oldMessageCls) {
  31605. this.messageElement.replaceCls(oldMessageCls, newMessageCls);
  31606. },
  31607. /**
  31608. * Shows or hides the loading indicator when the {@link #indicator} configuration is changed.
  31609. * @private
  31610. */
  31611. updateIndicator: function(newIndicator) {
  31612. this[newIndicator ? 'removeCls' : 'addCls'](Ext.baseCSSPrefix + 'indicator-hidden');
  31613. }
  31614. }, function() {
  31615. });
  31616. /**
  31617. * Provides a cross browser class for retrieving location information.
  31618. *
  31619. * Based on the [Geolocation API Specification](http://dev.w3.org/geo/api/spec-source.html)
  31620. *
  31621. * When instantiated, by default this class immediately begins tracking location information,
  31622. * firing a {@link #locationupdate} event when new location information is available. To disable this
  31623. * location tracking (which may be battery intensive on mobile devices), set {@link #autoUpdate} to `false`.
  31624. *
  31625. * When this is done, only calls to {@link #updateLocation} will trigger a location retrieval.
  31626. *
  31627. * A {@link #locationerror} event is raised when an error occurs retrieving the location, either due to a user
  31628. * denying the application access to it, or the browser not supporting it.
  31629. *
  31630. * The below code shows a GeoLocation making a single retrieval of location information.
  31631. *
  31632. * var geo = Ext.create('Ext.util.Geolocation', {
  31633. * autoUpdate: false,
  31634. * listeners: {
  31635. * locationupdate: function(geo) {
  31636. * alert('New latitude: ' + geo.getLatitude());
  31637. * },
  31638. * locationerror: function(geo, bTimeout, bPermissionDenied, bLocationUnavailable, message) {
  31639. * if(bTimeout){
  31640. * alert('Timeout occurred.');
  31641. * } else {
  31642. * alert('Error occurred.');
  31643. * }
  31644. * }
  31645. * }
  31646. * });
  31647. * geo.updateLocation();
  31648. */
  31649. Ext.define('Ext.util.Geolocation', {
  31650. extend: 'Ext.Evented',
  31651. alternateClassName: ['Ext.util.GeoLocation'],
  31652. config: {
  31653. /**
  31654. * @event locationerror
  31655. * Raised when a location retrieval operation failed.
  31656. *
  31657. * In the case of calling updateLocation, this event will be raised only once.
  31658. *
  31659. * If {@link #autoUpdate} is set to `true`, this event could be raised repeatedly.
  31660. * The first error is relative to the moment {@link #autoUpdate} was set to `true`
  31661. * (or this {@link Ext.util.Geolocation} was initialized with the {@link #autoUpdate} config option set to `true`).
  31662. * Subsequent errors are relative to the moment when the device determines that it's position has changed.
  31663. * @param {Ext.util.Geolocation} this
  31664. * @param {Boolean} timeout
  31665. * Boolean indicating a timeout occurred
  31666. * @param {Boolean} permissionDenied
  31667. * Boolean indicating the user denied the location request
  31668. * @param {Boolean} locationUnavailable
  31669. * Boolean indicating that the location of the device could not be determined.
  31670. * For instance, one or more of the location providers used in the location acquisition
  31671. * process reported an internal error that caused the process to fail entirely.
  31672. * @param {String} message An error message describing the details of the error encountered.
  31673. *
  31674. * This attribute is primarily intended for debugging and should not be used
  31675. * directly in an application user interface.
  31676. */
  31677. /**
  31678. * @event locationupdate
  31679. * Raised when a location retrieval operation has been completed successfully.
  31680. * @param {Ext.util.Geolocation} this
  31681. * Retrieve the current location information from the GeoLocation object by using the read-only
  31682. * properties: {@link #latitude}, {@link #longitude}, {@link #accuracy}, {@link #altitude}, {@link #altitudeAccuracy}, {@link #heading}, and {@link #speed}.
  31683. */
  31684. /**
  31685. * @cfg {Boolean} autoUpdate
  31686. * When set to `true`, continually monitor the location of the device (beginning immediately)
  31687. * and fire {@link #locationupdate} and {@link #locationerror} events.
  31688. */
  31689. autoUpdate: true,
  31690. /**
  31691. * @cfg {Number} frequency
  31692. * The frequency of each update if {@link #autoUpdate} is set to `true`.
  31693. */
  31694. frequency: 10000,
  31695. /**
  31696. * Read-only property representing the last retrieved
  31697. * geographical coordinate specified in degrees.
  31698. * @type Number
  31699. * @readonly
  31700. */
  31701. latitude: null,
  31702. /**
  31703. * Read-only property representing the last retrieved
  31704. * geographical coordinate specified in degrees.
  31705. * @type Number
  31706. * @readonly
  31707. */
  31708. longitude: null,
  31709. /**
  31710. * Read-only property representing the last retrieved
  31711. * accuracy level of the latitude and longitude coordinates,
  31712. * specified in meters.
  31713. *
  31714. * This will always be a non-negative number.
  31715. *
  31716. * This corresponds to a 95% confidence level.
  31717. * @type Number
  31718. * @readonly
  31719. */
  31720. accuracy: null,
  31721. /**
  31722. * Read-only property representing the last retrieved
  31723. * height of the position, specified in meters above the ellipsoid
  31724. * [WGS84](http://dev.w3.org/geo/api/spec-source.html#ref-wgs).
  31725. * @type Number
  31726. * @readonly
  31727. */
  31728. altitude: null,
  31729. /**
  31730. * Read-only property representing the last retrieved
  31731. * accuracy level of the altitude coordinate, specified in meters.
  31732. *
  31733. * If altitude is not null then this will be a non-negative number.
  31734. * Otherwise this returns `null`.
  31735. *
  31736. * This corresponds to a 95% confidence level.
  31737. * @type Number
  31738. * @readonly
  31739. */
  31740. altitudeAccuracy: null,
  31741. /**
  31742. * Read-only property representing the last retrieved
  31743. * direction of travel of the hosting device,
  31744. * specified in non-negative degrees between 0 and 359,
  31745. * counting clockwise relative to the true north.
  31746. *
  31747. * If speed is 0 (device is stationary), then this returns `NaN`.
  31748. * @type Number
  31749. * @readonly
  31750. */
  31751. heading: null,
  31752. /**
  31753. * Read-only property representing the last retrieved
  31754. * current ground speed of the device, specified in meters per second.
  31755. *
  31756. * If this feature is unsupported by the device, this returns `null`.
  31757. *
  31758. * If the device is stationary, this returns 0,
  31759. * otherwise it returns a non-negative number.
  31760. * @type Number
  31761. * @readonly
  31762. */
  31763. speed: null,
  31764. /**
  31765. * Read-only property representing when the last retrieved
  31766. * positioning information was acquired by the device.
  31767. * @type Date
  31768. * @readonly
  31769. */
  31770. timestamp: null,
  31771. //PositionOptions interface
  31772. /**
  31773. * @cfg {Boolean} allowHighAccuracy
  31774. * When set to `true`, provide a hint that the application would like to receive
  31775. * the best possible results. This may result in slower response times or increased power consumption.
  31776. * The user might also deny this capability, or the device might not be able to provide more accurate
  31777. * results than if this option was set to `false`.
  31778. */
  31779. allowHighAccuracy: false,
  31780. /**
  31781. * @cfg {Number} timeout
  31782. * The maximum number of milliseconds allowed to elapse between a location update operation
  31783. * and the corresponding {@link #locationupdate} event being raised. If a location was not successfully
  31784. * acquired before the given timeout elapses (and no other internal errors have occurred in this interval),
  31785. * then a {@link #locationerror} event will be raised indicating a timeout as the cause.
  31786. *
  31787. * Note that the time that is spent obtaining the user permission is **not** included in the period
  31788. * covered by the timeout. The `timeout` attribute only applies to the location acquisition operation.
  31789. *
  31790. * In the case of calling `updateLocation`, the {@link #locationerror} event will be raised only once.
  31791. *
  31792. * If {@link #autoUpdate} is set to `true`, the {@link #locationerror} event could be raised repeatedly.
  31793. * The first timeout is relative to the moment {@link #autoUpdate} was set to `true`
  31794. * (or this {@link Ext.util.Geolocation} was initialized with the {@link #autoUpdate} config option set to `true`).
  31795. * Subsequent timeouts are relative to the moment when the device determines that it's position has changed.
  31796. */
  31797. timeout: Infinity,
  31798. /**
  31799. * @cfg {Number} maximumAge
  31800. * This option indicates that the application is willing to accept cached location information whose age
  31801. * is no greater than the specified time in milliseconds. If `maximumAge` is set to 0, an attempt to retrieve
  31802. * new location information is made immediately.
  31803. *
  31804. * Setting the `maximumAge` to Infinity returns a cached position regardless of its age.
  31805. *
  31806. * If the device does not have cached location information available whose age is no
  31807. * greater than the specified `maximumAge`, then it must acquire new location information.
  31808. *
  31809. * For example, if location information no older than 10 minutes is required, set this property to 600000.
  31810. */
  31811. maximumAge: 0,
  31812. // @private
  31813. provider : undefined
  31814. },
  31815. updateMaximumAge: function() {
  31816. if (this.watchOperation) {
  31817. this.updateWatchOperation();
  31818. }
  31819. },
  31820. updateTimeout: function() {
  31821. if (this.watchOperation) {
  31822. this.updateWatchOperation();
  31823. }
  31824. },
  31825. updateAllowHighAccuracy: function() {
  31826. if (this.watchOperation) {
  31827. this.updateWatchOperation();
  31828. }
  31829. },
  31830. applyProvider: function(config) {
  31831. if (Ext.feature.has.Geolocation) {
  31832. if (!config) {
  31833. if (navigator && navigator.geolocation) {
  31834. config = navigator.geolocation;
  31835. }
  31836. else if (window.google) {
  31837. config = google.gears.factory.create('beta.geolocation');
  31838. }
  31839. }
  31840. }
  31841. else {
  31842. this.fireEvent('locationerror', this, false, false, true, 'This device does not support Geolocation.');
  31843. }
  31844. return config;
  31845. },
  31846. updateAutoUpdate: function(newAutoUpdate, oldAutoUpdate) {
  31847. var me = this,
  31848. provider = me.getProvider();
  31849. if (oldAutoUpdate && provider) {
  31850. clearInterval(me.watchOperationId);
  31851. me.watchOperationId = null;
  31852. }
  31853. if (newAutoUpdate) {
  31854. if (!provider) {
  31855. me.fireEvent('locationerror', me, false, false, true, null);
  31856. return;
  31857. }
  31858. try {
  31859. me.updateWatchOperation();
  31860. }
  31861. catch(e) {
  31862. me.fireEvent('locationerror', me, false, false, true, e.message);
  31863. }
  31864. }
  31865. },
  31866. // @private
  31867. updateWatchOperation: function() {
  31868. var me = this,
  31869. provider = me.getProvider();
  31870. // The native watchPosition method is currently broken in iOS5...
  31871. if (me.watchOperationId) {
  31872. clearInterval(me.watchOperationId);
  31873. }
  31874. function pollPosition() {
  31875. provider.getCurrentPosition(
  31876. Ext.bind(me.fireUpdate, me),
  31877. Ext.bind(me.fireError, me),
  31878. me.parseOptions()
  31879. );
  31880. }
  31881. pollPosition();
  31882. me.watchOperationId = setInterval(pollPosition, this.getFrequency());
  31883. },
  31884. /**
  31885. * Executes a onetime location update operation,
  31886. * raising either a {@link #locationupdate} or {@link #locationerror} event.
  31887. *
  31888. * Does not interfere with or restart ongoing location monitoring.
  31889. * @param {Function} callback
  31890. * A callback method to be called when the location retrieval has been completed.
  31891. *
  31892. * Will be called on both success and failure.
  31893. *
  31894. * The method will be passed one parameter, {@link Ext.util.Geolocation} (**this** reference),
  31895. * set to `null` on failure.
  31896. *
  31897. * geo.updateLocation(function (geo) {
  31898. * alert('Latitude: ' + (geo !== null ? geo.latitude : 'failed'));
  31899. * });
  31900. *
  31901. * @param {Object} scope (optional) The scope (**this** reference) in which the handler function is executed.
  31902. *
  31903. * **If omitted, defaults to the object which fired the event.**
  31904. * <!--positonOptions undocumented param, see W3C spec-->
  31905. */
  31906. updateLocation: function(callback, scope, positionOptions) {
  31907. var me = this,
  31908. provider = me.getProvider();
  31909. var failFunction = function(message, error) {
  31910. if (error) {
  31911. me.fireError(error);
  31912. }
  31913. else {
  31914. me.fireEvent('locationerror', me, false, false, true, message);
  31915. }
  31916. if (callback) {
  31917. callback.call(scope || me, null, me); //last parameter for legacy purposes
  31918. }
  31919. };
  31920. if (!provider) {
  31921. failFunction(null);
  31922. return;
  31923. }
  31924. try {
  31925. provider.getCurrentPosition(
  31926. //success callback
  31927. function(position) {
  31928. me.fireUpdate(position);
  31929. if (callback) {
  31930. callback.call(scope || me, me, me); //last parameter for legacy purposes
  31931. }
  31932. },
  31933. //error callback
  31934. function(error) {
  31935. failFunction(null, error);
  31936. },
  31937. positionOptions || me.parseOptions()
  31938. );
  31939. }
  31940. catch(e) {
  31941. failFunction(e.message);
  31942. }
  31943. },
  31944. // @private
  31945. fireUpdate: function(position) {
  31946. var me = this,
  31947. coords = position.coords;
  31948. this.position = position;
  31949. me.setConfig({
  31950. timestamp: position.timestamp,
  31951. latitude: coords.latitude,
  31952. longitude: coords.longitude,
  31953. accuracy: coords.accuracy,
  31954. altitude: coords.altitude,
  31955. altitudeAccuracy: coords.altitudeAccuracy,
  31956. heading: coords.heading,
  31957. speed: coords.speed
  31958. });
  31959. me.fireEvent('locationupdate', me);
  31960. },
  31961. // @private
  31962. fireError: function(error) {
  31963. var errorCode = error.code;
  31964. this.fireEvent('locationerror', this,
  31965. errorCode == error.TIMEOUT,
  31966. errorCode == error.PERMISSION_DENIED,
  31967. errorCode == error.POSITION_UNAVAILABLE,
  31968. error.message == undefined ? null : error.message
  31969. );
  31970. },
  31971. // @private
  31972. parseOptions: function() {
  31973. var timeout = this.getTimeout(),
  31974. ret = {
  31975. maximumAge: this.getMaximumAge(),
  31976. enableHighAccuracy: this.getAllowHighAccuracy()
  31977. };
  31978. //Google doesn't like Infinity
  31979. if (timeout !== Infinity) {
  31980. ret.timeout = timeout;
  31981. }
  31982. return ret;
  31983. },
  31984. destroy : function() {
  31985. this.setAutoUpdate(false);
  31986. }
  31987. });
  31988. /**
  31989. * Wraps a Google Map in an Ext.Component using the [Google Maps API](http://code.google.com/apis/maps/documentation/v3/introduction.html).
  31990. *
  31991. * To use this component you must include an additional JavaScript file from Google:
  31992. *
  31993. * <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true"></script>
  31994. *
  31995. * ## Example
  31996. *
  31997. * Ext.Viewport.add({
  31998. * xtype: 'map',
  31999. * useCurrentLocation: true
  32000. * });
  32001. *
  32002. * @aside example maps
  32003. */
  32004. Ext.define('Ext.Map', {
  32005. extend: 'Ext.Container',
  32006. xtype : 'map',
  32007. requires: ['Ext.util.Geolocation'],
  32008. isMap: true,
  32009. config: {
  32010. /**
  32011. * @event maprender
  32012. * Fired when Map initially rendered.
  32013. * @param {Ext.Map} this
  32014. * @param {google.maps.Map} map The rendered google.map.Map instance
  32015. */
  32016. /**
  32017. * @event centerchange
  32018. * Fired when map is panned around.
  32019. * @param {Ext.Map} this
  32020. * @param {google.maps.Map} map The rendered google.map.Map instance
  32021. * @param {google.maps.LatLng} center The current LatLng center of the map
  32022. */
  32023. /**
  32024. * @event typechange
  32025. * Fired when display type of the map changes.
  32026. * @param {Ext.Map} this
  32027. * @param {google.maps.Map} map The rendered google.map.Map instance
  32028. * @param {Number} mapType The current display type of the map
  32029. */
  32030. /**
  32031. * @event zoomchange
  32032. * Fired when map is zoomed.
  32033. * @param {Ext.Map} this
  32034. * @param {google.maps.Map} map The rendered google.map.Map instance
  32035. * @param {Number} zoomLevel The current zoom level of the map
  32036. */
  32037. /**
  32038. * @cfg {String} baseCls
  32039. * The base CSS class to apply to the Map's element
  32040. * @accessor
  32041. */
  32042. baseCls: Ext.baseCSSPrefix + 'map',
  32043. /**
  32044. * @cfg {Boolean/Ext.util.Geolocation} useCurrentLocation
  32045. * Pass in true to center the map based on the geolocation coordinates or pass a
  32046. * {@link Ext.util.Geolocation GeoLocation} config to have more control over your GeoLocation options
  32047. * @accessor
  32048. */
  32049. useCurrentLocation: false,
  32050. /**
  32051. * @cfg {google.maps.Map} map
  32052. * The wrapped map.
  32053. * @accessor
  32054. */
  32055. map: null,
  32056. /**
  32057. * @cfg {Ext.util.Geolocation} geo
  32058. * Geolocation provider for the map.
  32059. * @accessor
  32060. */
  32061. geo: null,
  32062. /**
  32063. * @cfg {Object} mapOptions
  32064. * MapOptions as specified by the Google Documentation:
  32065. * [http://code.google.com/apis/maps/documentation/v3/reference.html](http://code.google.com/apis/maps/documentation/v3/reference.html)
  32066. * @accessor
  32067. */
  32068. mapOptions: {}
  32069. },
  32070. constructor: function() {
  32071. this.callParent(arguments);
  32072. // this.element.setVisibilityMode(Ext.Element.OFFSETS);
  32073. if (!(window.google || {}).maps) {
  32074. this.setHtml('Google Maps API is required');
  32075. }
  32076. },
  32077. initialize: function() {
  32078. this.callParent();
  32079. this.on({
  32080. painted: 'doResize',
  32081. scope: this
  32082. });
  32083. this.innerElement.on('touchstart', 'onTouchStart', this);
  32084. },
  32085. getElementConfig: function() {
  32086. return {
  32087. reference: 'element',
  32088. className: 'x-container',
  32089. children: [{
  32090. reference: 'innerElement',
  32091. className: 'x-inner',
  32092. children: [{
  32093. reference: 'mapContainer',
  32094. className: Ext.baseCSSPrefix + 'map-container'
  32095. }]
  32096. }]
  32097. };
  32098. },
  32099. onTouchStart: function(e) {
  32100. e.makeUnpreventable();
  32101. },
  32102. applyMapOptions: function(options) {
  32103. return Ext.merge({}, this.options, options);
  32104. },
  32105. updateMapOptions: function(newOptions) {
  32106. var me = this,
  32107. gm = (window.google || {}).maps,
  32108. map = this.getMap();
  32109. if (gm && map) {
  32110. map.setOptions(newOptions);
  32111. }
  32112. if (newOptions.center && !me.isPainted()) {
  32113. me.un('painted', 'setMapCenter', this);
  32114. me.on('painted', 'setMapCenter', this, { delay: 150, single: true, args: [newOptions.center] });
  32115. }
  32116. },
  32117. getMapOptions: function() {
  32118. return Ext.merge({}, this.options || this.getInitialConfig('mapOptions'));
  32119. },
  32120. updateUseCurrentLocation: function(useCurrentLocation) {
  32121. this.setGeo(useCurrentLocation);
  32122. if (!useCurrentLocation) {
  32123. this.renderMap();
  32124. }
  32125. },
  32126. applyGeo: function(config) {
  32127. return Ext.factory(config, Ext.util.Geolocation, this.getGeo());
  32128. },
  32129. updateGeo: function(newGeo, oldGeo) {
  32130. var events = {
  32131. locationupdate : 'onGeoUpdate',
  32132. locationerror : 'onGeoError',
  32133. scope : this
  32134. };
  32135. if (oldGeo) {
  32136. oldGeo.un(events);
  32137. }
  32138. if (newGeo) {
  32139. newGeo.on(events);
  32140. newGeo.updateLocation();
  32141. }
  32142. },
  32143. doResize: function() {
  32144. var gm = (window.google || {}).maps,
  32145. map = this.getMap();
  32146. if (gm && map) {
  32147. gm.event.trigger(map, "resize");
  32148. }
  32149. },
  32150. // @private
  32151. renderMap: function() {
  32152. var me = this,
  32153. gm = (window.google || {}).maps,
  32154. element = me.mapContainer,
  32155. mapOptions = me.getMapOptions(),
  32156. map = me.getMap(),
  32157. event;
  32158. if (gm) {
  32159. if (Ext.os.is.iPad) {
  32160. Ext.merge({
  32161. navigationControlOptions: {
  32162. style: gm.NavigationControlStyle.ZOOM_PAN
  32163. }
  32164. }, mapOptions);
  32165. }
  32166. mapOptions = Ext.merge({
  32167. zoom: 12,
  32168. mapTypeId: gm.MapTypeId.ROADMAP
  32169. }, mapOptions);
  32170. // This is done separately from the above merge so we don't have to instantiate
  32171. // a new LatLng if we don't need to
  32172. if (!mapOptions.hasOwnProperty('center')) {
  32173. mapOptions.center = new gm.LatLng(37.381592, -122.135672); // Palo Alto
  32174. }
  32175. if (element.dom.firstChild) {
  32176. Ext.fly(element.dom.firstChild).destroy();
  32177. }
  32178. if (map) {
  32179. gm.event.clearInstanceListeners(map);
  32180. }
  32181. me.setMap(new gm.Map(element.dom, mapOptions));
  32182. map = me.getMap();
  32183. //Track zoomLevel and mapType changes
  32184. event = gm.event;
  32185. event.addListener(map, 'zoom_changed', Ext.bind(me.onZoomChange, me));
  32186. event.addListener(map, 'maptypeid_changed', Ext.bind(me.onTypeChange, me));
  32187. event.addListener(map, 'center_changed', Ext.bind(me.onCenterChange, me));
  32188. me.fireEvent('maprender', me, map);
  32189. }
  32190. },
  32191. // @private
  32192. onGeoUpdate: function(geo) {
  32193. if (geo) {
  32194. this.setMapCenter(new google.maps.LatLng(geo.getLatitude(), geo.getLongitude()));
  32195. }
  32196. },
  32197. // @private
  32198. onGeoError: Ext.emptyFn,
  32199. /**
  32200. * Moves the map center to the designated coordinates hash of the form:
  32201. *
  32202. * { latitude: 37.381592, longitude: -122.135672 }
  32203. *
  32204. * or a google.maps.LatLng object representing to the target location.
  32205. *
  32206. * @param {Object/google.maps.LatLng} coordinates Object representing the desired Latitude and
  32207. * longitude upon which to center the map.
  32208. */
  32209. setMapCenter: function(coordinates) {
  32210. var me = this,
  32211. map = me.getMap(),
  32212. gm = (window.google || {}).maps;
  32213. if (gm) {
  32214. if (!me.isPainted()) {
  32215. me.un('painted', 'setMapCenter', this);
  32216. me.on('painted', 'setMapCenter', this, { delay: 150, single: true, args: [coordinates] });
  32217. return;
  32218. }
  32219. coordinates = coordinates || new gm.LatLng(37.381592, -122.135672);
  32220. if (coordinates && !(coordinates instanceof gm.LatLng) && 'longitude' in coordinates) {
  32221. coordinates = new gm.LatLng(coordinates.latitude, coordinates.longitude);
  32222. }
  32223. if (!map) {
  32224. me.renderMap();
  32225. map = me.getMap();
  32226. }
  32227. if (map && coordinates instanceof gm.LatLng) {
  32228. map.panTo(coordinates);
  32229. }
  32230. else {
  32231. this.options = Ext.apply(this.getMapOptions(), {
  32232. center: coordinates
  32233. });
  32234. }
  32235. }
  32236. },
  32237. // @private
  32238. onZoomChange : function() {
  32239. var mapOptions = this.getMapOptions(),
  32240. map = this.getMap(),
  32241. zoom;
  32242. zoom = (map && map.getZoom) ? map.getZoom() : mapOptions.zoom || 10;
  32243. this.options = Ext.apply(mapOptions, {
  32244. zoom: zoom
  32245. });
  32246. this.fireEvent('zoomchange', this, map, zoom);
  32247. },
  32248. // @private
  32249. onTypeChange : function() {
  32250. var mapOptions = this.getMapOptions(),
  32251. map = this.getMap(),
  32252. mapTypeId;
  32253. mapTypeId = (map && map.getMapTypeId) ? map.getMapTypeId() : mapOptions.mapTypeId;
  32254. this.options = Ext.apply(mapOptions, {
  32255. mapTypeId: mapTypeId
  32256. });
  32257. this.fireEvent('typechange', this, map, mapTypeId);
  32258. },
  32259. // @private
  32260. onCenterChange: function() {
  32261. var mapOptions = this.getMapOptions(),
  32262. map = this.getMap(),
  32263. center;
  32264. center = (map && map.getCenter) ? map.getCenter() : mapOptions.center;
  32265. this.options = Ext.apply(mapOptions, {
  32266. center: center
  32267. });
  32268. this.fireEvent('centerchange', this, map, center);
  32269. },
  32270. // @private
  32271. destroy: function() {
  32272. Ext.destroy(this.getGeo());
  32273. var map = this.getMap();
  32274. if (map && (window.google || {}).maps) {
  32275. google.maps.event.clearInstanceListeners(map);
  32276. }
  32277. this.callParent();
  32278. }
  32279. }, function() {
  32280. });
  32281. /**
  32282. * {@link Ext.Title} is used for the {@link Ext.Toolbar#title} configuration in the {@link Ext.Toolbar} component.
  32283. * @private
  32284. */
  32285. Ext.define('Ext.Title', {
  32286. extend: 'Ext.Component',
  32287. xtype: 'title',
  32288. config: {
  32289. /**
  32290. * @cfg
  32291. * @inheritdoc
  32292. */
  32293. baseCls: 'x-title',
  32294. /**
  32295. * @cfg {String} title The title text
  32296. */
  32297. title: ''
  32298. },
  32299. // @private
  32300. updateTitle: function(newTitle) {
  32301. this.setHtml(newTitle);
  32302. }
  32303. });
  32304. /**
  32305. The {@link Ext.Spacer} component is generally used to put space between items in {@link Ext.Toolbar} components.
  32306. ## Examples
  32307. By default the {@link #flex} configuration is set to 1:
  32308. @example miniphone preview
  32309. Ext.create('Ext.Container', {
  32310. fullscreen: true,
  32311. items: [
  32312. {
  32313. xtype : 'toolbar',
  32314. docked: 'top',
  32315. items: [
  32316. {
  32317. xtype: 'button',
  32318. text : 'Button One'
  32319. },
  32320. {
  32321. xtype: 'spacer'
  32322. },
  32323. {
  32324. xtype: 'button',
  32325. text : 'Button Two'
  32326. }
  32327. ]
  32328. }
  32329. ]
  32330. });
  32331. Alternatively you can just set the {@link #width} configuration which will get the {@link Ext.Spacer} a fixed width:
  32332. @example preview
  32333. Ext.create('Ext.Container', {
  32334. fullscreen: true,
  32335. layout: {
  32336. type: 'vbox',
  32337. pack: 'center',
  32338. align: 'stretch'
  32339. },
  32340. items: [
  32341. {
  32342. xtype : 'toolbar',
  32343. docked: 'top',
  32344. items: [
  32345. {
  32346. xtype: 'button',
  32347. text : 'Button One'
  32348. },
  32349. {
  32350. xtype: 'spacer',
  32351. width: 50
  32352. },
  32353. {
  32354. xtype: 'button',
  32355. text : 'Button Two'
  32356. }
  32357. ]
  32358. },
  32359. {
  32360. xtype: 'container',
  32361. items: [
  32362. {
  32363. xtype: 'button',
  32364. text : 'Change Ext.Spacer width',
  32365. handler: function() {
  32366. //get the spacer using ComponentQuery
  32367. var spacer = Ext.ComponentQuery.query('spacer')[0],
  32368. from = 10,
  32369. to = 250;
  32370. //set the width to a random number
  32371. spacer.setWidth(Math.floor(Math.random() * (to - from + 1) + from));
  32372. }
  32373. }
  32374. ]
  32375. }
  32376. ]
  32377. });
  32378. You can also insert multiple {@link Ext.Spacer}'s:
  32379. @example preview
  32380. Ext.create('Ext.Container', {
  32381. fullscreen: true,
  32382. items: [
  32383. {
  32384. xtype : 'toolbar',
  32385. docked: 'top',
  32386. items: [
  32387. {
  32388. xtype: 'button',
  32389. text : 'Button One'
  32390. },
  32391. {
  32392. xtype: 'spacer'
  32393. },
  32394. {
  32395. xtype: 'button',
  32396. text : 'Button Two'
  32397. },
  32398. {
  32399. xtype: 'spacer',
  32400. width: 20
  32401. },
  32402. {
  32403. xtype: 'button',
  32404. text : 'Button Three'
  32405. }
  32406. ]
  32407. }
  32408. ]
  32409. });
  32410. */
  32411. Ext.define('Ext.Spacer', {
  32412. extend: 'Ext.Component',
  32413. alias : 'widget.spacer',
  32414. config: {
  32415. /**
  32416. * @cfg {Number} flex
  32417. * The flex value of this spacer. This defaults to 1, if no width has been set.
  32418. * @accessor
  32419. */
  32420. /**
  32421. * @cfg {Number} width
  32422. * The width of this spacer. If this is set, the value of {@link #flex} will be ignored.
  32423. * @accessor
  32424. */
  32425. },
  32426. // @private
  32427. constructor: function(config) {
  32428. config = config || {};
  32429. if (!config.width) {
  32430. config.flex = 1;
  32431. }
  32432. this.callParent([config]);
  32433. }
  32434. });
  32435. /**
  32436. * @aside video tabs-toolbars
  32437. *
  32438. * {@link Ext.Toolbar}s are most commonly used as docked items as within a {@link Ext.Container}. They can be docked either `top` or `bottom` using the {@link #docked} configuration.
  32439. *
  32440. * They allow you to insert items (normally {@link Ext.Button buttons}) and also add a {@link #title}.
  32441. *
  32442. * The {@link #defaultType} of {@link Ext.Toolbar} is {@link Ext.Button}.
  32443. *
  32444. * You can alternatively use {@link Ext.TitleBar} if you want the title to automatically adjust the size of its items.
  32445. *
  32446. * ## Examples
  32447. *
  32448. * @example miniphone preview
  32449. * Ext.create('Ext.Container', {
  32450. * fullscreen: true,
  32451. * layout: {
  32452. * type: 'vbox',
  32453. * pack: 'center'
  32454. * },
  32455. * items: [
  32456. * {
  32457. * xtype : 'toolbar',
  32458. * docked: 'top',
  32459. * title: 'My Toolbar'
  32460. * },
  32461. * {
  32462. * xtype: 'container',
  32463. * defaults: {
  32464. * xtype: 'button',
  32465. * margin: '10 10 0 10'
  32466. * },
  32467. * items: [
  32468. * {
  32469. * text: 'Toggle docked',
  32470. * handler: function() {
  32471. * var toolbar = Ext.ComponentQuery.query('toolbar')[0],
  32472. * newDocked = (toolbar.getDocked() === 'top') ? 'bottom' : 'top';
  32473. *
  32474. * toolbar.setDocked(newDocked);
  32475. * }
  32476. * },
  32477. * {
  32478. * text: 'Toggle UI',
  32479. * handler: function() {
  32480. * var toolbar = Ext.ComponentQuery.query('toolbar')[0],
  32481. * newUi = (toolbar.getUi() === 'light') ? 'dark' : 'light';
  32482. *
  32483. * toolbar.setUi(newUi);
  32484. * }
  32485. * },
  32486. * {
  32487. * text: 'Change title',
  32488. * handler: function() {
  32489. * var toolbar = Ext.ComponentQuery.query('toolbar')[0],
  32490. * titles = ['My Toolbar', 'Ext.Toolbar', 'Configurations are awesome!', 'Beautiful.'],
  32491. //internally, the title configuration gets converted into a {@link Ext.Title} component,
  32492. //so you must get the title configuration of that component
  32493. * title = toolbar.getTitle().getTitle(),
  32494. * newTitle = titles[titles.indexOf(title) + 1] || titles[0];
  32495. *
  32496. * toolbar.setTitle(newTitle);
  32497. * }
  32498. * }
  32499. * ]
  32500. * }
  32501. * ]
  32502. * });
  32503. *
  32504. * ## Notes
  32505. *
  32506. * You must use a HTML5 doctype for {@link #docked} `bottom` to work. To do this, simply add the following code to the HTML file:
  32507. *
  32508. * <!doctype html>
  32509. *
  32510. * So your index.html file should look a little like this:
  32511. *
  32512. * <!doctype html>
  32513. * <html>
  32514. * <head>
  32515. * <title>MY application title</title>
  32516. * ...
  32517. *
  32518. */
  32519. Ext.define('Ext.Toolbar', {
  32520. extend: 'Ext.Container',
  32521. xtype : 'toolbar',
  32522. requires: [
  32523. 'Ext.Button',
  32524. 'Ext.Title',
  32525. 'Ext.Spacer',
  32526. 'Ext.layout.HBox'
  32527. ],
  32528. // @private
  32529. isToolbar: true,
  32530. config: {
  32531. /**
  32532. * @cfg baseCls
  32533. * @inheritdoc
  32534. */
  32535. baseCls: Ext.baseCSSPrefix + 'toolbar',
  32536. /**
  32537. * @cfg {String} ui
  32538. * The ui for this {@link Ext.Toolbar}. Either 'light' or 'dark'. You can create more UIs by using using the CSS Mixin {@link #sencha-toolbar-ui}
  32539. * @accessor
  32540. */
  32541. ui: 'dark',
  32542. /**
  32543. * @cfg {String/Ext.Title} title
  32544. * The title of the toolbar.
  32545. * @accessor
  32546. */
  32547. title: null,
  32548. /**
  32549. * @cfg {String} defaultType
  32550. * The default xtype to create.
  32551. * @accessor
  32552. */
  32553. defaultType: 'button',
  32554. /**
  32555. * @cfg {String} docked
  32556. * The docked position for this {@link Ext.Toolbar}.
  32557. * If you specify `left` or `right`, the {@link #layout} configuration will automatically change to a `vbox`. It's also
  32558. * recommended to adjust the {@link #width} of the toolbar if you do this.
  32559. * @accessor
  32560. */
  32561. /**
  32562. * @cfg {String} minHeight
  32563. * The minimum height height of the Toolbar.
  32564. * @accessor
  32565. */
  32566. minHeight: '2.6em',
  32567. /**
  32568. * @cfg {Object/String} layout Configuration for this Container's layout. Example:
  32569. *
  32570. * Ext.create('Ext.Container', {
  32571. * layout: {
  32572. * type: 'hbox',
  32573. * align: 'middle'
  32574. * },
  32575. * items: [
  32576. * {
  32577. * xtype: 'panel',
  32578. * flex: 1,
  32579. * style: 'background-color: red;'
  32580. * },
  32581. * {
  32582. * xtype: 'panel',
  32583. * flex: 2,
  32584. * style: 'background-color: green'
  32585. * }
  32586. * ]
  32587. * });
  32588. *
  32589. * See the [layouts guide](#!/guides/layouts) for more information
  32590. *
  32591. * __Note:__ If you set the {@link #docked} configuration to `left` or `right`, the default layout will change from the
  32592. * `hbox` to a `vbox`.
  32593. *
  32594. * @accessor
  32595. */
  32596. layout: {
  32597. type: 'hbox',
  32598. align: 'center'
  32599. }
  32600. },
  32601. constructor: function(config) {
  32602. config = config || {};
  32603. if (config.docked == "left" || config.docked == "right") {
  32604. config.layout = {
  32605. type: 'vbox',
  32606. align: 'stretch'
  32607. };
  32608. }
  32609. this.callParent([config]);
  32610. },
  32611. // @private
  32612. applyTitle: function(title) {
  32613. if (typeof title == 'string') {
  32614. title = {
  32615. title: title,
  32616. centered: true
  32617. };
  32618. }
  32619. return Ext.factory(title, Ext.Title, this.getTitle());
  32620. },
  32621. // @private
  32622. updateTitle: function(newTitle, oldTitle) {
  32623. if (newTitle) {
  32624. this.add(newTitle);
  32625. }
  32626. if (oldTitle) {
  32627. oldTitle.destroy();
  32628. }
  32629. },
  32630. /**
  32631. * Shows the title, if it exists.
  32632. */
  32633. showTitle: function() {
  32634. var title = this.getTitle();
  32635. if (title) {
  32636. title.show();
  32637. }
  32638. },
  32639. /**
  32640. * Hides the title, if it exists.
  32641. */
  32642. hideTitle: function() {
  32643. var title = this.getTitle();
  32644. if (title) {
  32645. title.hide();
  32646. }
  32647. }
  32648. /**
  32649. * Returns an {@link Ext.Title} component.
  32650. * @member Ext.Toolbar
  32651. * @method getTitle
  32652. * @return {Ext.Title}
  32653. */
  32654. /**
  32655. * Use this to update the {@link #title} configuration.
  32656. * @member Ext.Toolbar
  32657. * @method setTitle
  32658. * @param {String/Ext.Title} title You can either pass a String, or a config/instance of {@link Ext.Title}.
  32659. */
  32660. }, function() {
  32661. });
  32662. /**
  32663. * @private
  32664. */
  32665. Ext.define('Ext.field.Input', {
  32666. extend: 'Ext.Component',
  32667. xtype : 'input',
  32668. /**
  32669. * @event clearicontap
  32670. * Fires whenever the clear icon is tapped.
  32671. * @param {Ext.field.Input} this
  32672. * @param {Ext.EventObject} e The event object
  32673. */
  32674. /**
  32675. * @event masktap
  32676. * @preventable doMaskTap
  32677. * Fires whenever a mask is tapped.
  32678. * @param {Ext.field.Input} this
  32679. * @param {Ext.EventObject} e The event object.
  32680. */
  32681. /**
  32682. * @event focus
  32683. * @preventable doFocus
  32684. * Fires whenever the input get focus.
  32685. * @param {Ext.EventObject} e The event object.
  32686. */
  32687. /**
  32688. * @event blur
  32689. * @preventable doBlur
  32690. * Fires whenever the input loses focus.
  32691. * @param {Ext.EventObject} e The event object.
  32692. */
  32693. /**
  32694. * @event click
  32695. * Fires whenever the input is clicked.
  32696. * @param {Ext.EventObject} e The event object.
  32697. */
  32698. /**
  32699. * @event keyup
  32700. * Fires whenever keyup is detected.
  32701. * @param {Ext.EventObject} e The event object.
  32702. */
  32703. /**
  32704. * @event paste
  32705. * Fires whenever paste is detected.
  32706. * @param {Ext.EventObject} e The event object.
  32707. */
  32708. /**
  32709. * @event mousedown
  32710. * Fires whenever the input has a mousedown occur.
  32711. * @param {Ext.EventObject} e The event object.
  32712. */
  32713. /**
  32714. * @property {String} tag The el tag.
  32715. * @private
  32716. */
  32717. tag: 'input',
  32718. cachedConfig: {
  32719. /**
  32720. * @cfg {String} cls The `className` to be applied to this input.
  32721. * @accessor
  32722. */
  32723. cls: Ext.baseCSSPrefix + 'form-field',
  32724. /**
  32725. * @cfg {String} focusCls The CSS class to use when the field receives focus.
  32726. * @accessor
  32727. */
  32728. focusCls: Ext.baseCSSPrefix + 'field-focus',
  32729. // @private
  32730. maskCls: Ext.baseCSSPrefix + 'field-mask',
  32731. /**
  32732. * @cfg {String/Boolean} useMask
  32733. * `true` to use a mask on this field, or `auto` to automatically select when you should use it.
  32734. * @private
  32735. * @accessor
  32736. */
  32737. useMask: 'auto',
  32738. /**
  32739. * @cfg {String} type The type attribute for input fields -- e.g. radio, text, password, file (defaults
  32740. * to 'text'). The types 'file' and 'password' must be used to render those field types currently -- there are
  32741. * no separate Ext components for those.
  32742. * @accessor
  32743. */
  32744. type: 'text',
  32745. /**
  32746. * @cfg {Boolean} checked `true` if the checkbox should render initially checked.
  32747. * @accessor
  32748. */
  32749. checked: false
  32750. },
  32751. config: {
  32752. /**
  32753. * @cfg
  32754. * @inheritdoc
  32755. */
  32756. baseCls: Ext.baseCSSPrefix + 'field-input',
  32757. /**
  32758. * @cfg {String} name The field's HTML name attribute.
  32759. * __Note:__ This property must be set if this field is to be automatically included with
  32760. * {@link Ext.form.Panel#method-submit form submit()}.
  32761. * @accessor
  32762. */
  32763. name: null,
  32764. /**
  32765. * @cfg {Mixed} value A value to initialize this field with.
  32766. * @accessor
  32767. */
  32768. value: null,
  32769. /**
  32770. * @property {Boolean} `true` if the field currently has focus.
  32771. * @accessor
  32772. */
  32773. isFocused: false,
  32774. /**
  32775. * @cfg {Number} tabIndex The `tabIndex` for this field.
  32776. *
  32777. * __Note:__ This only applies to fields that are rendered, not those which are built via `applyTo`.
  32778. * @accessor
  32779. */
  32780. tabIndex: null,
  32781. /**
  32782. * @cfg {String} placeHolder A string value displayed in the input (if supported) when the control is empty.
  32783. * @accessor
  32784. */
  32785. placeHolder: null,
  32786. /**
  32787. * @cfg {Number} [minValue=undefined] The minimum value that this Number field can accept (defaults to `undefined`, e.g. no minimum).
  32788. * @accessor
  32789. */
  32790. minValue: null,
  32791. /**
  32792. * @cfg {Number} [maxValue=undefined] The maximum value that this Number field can accept (defaults to `undefined`, e.g. no maximum).
  32793. * @accessor
  32794. */
  32795. maxValue: null,
  32796. /**
  32797. * @cfg {Number} [stepValue=undefined] The amount by which the field is incremented or decremented each time the spinner is tapped.
  32798. * Defaults to `undefined`, which means that the field goes up or down by 1 each time the spinner is tapped.
  32799. * @accessor
  32800. */
  32801. stepValue: null,
  32802. /**
  32803. * @cfg {Number} [maxLength=0] The maximum number of permitted input characters.
  32804. * @accessor
  32805. */
  32806. maxLength: null,
  32807. /**
  32808. * @cfg {Boolean} [autoComplete=undefined]
  32809. * `true` to set the field's DOM element `autocomplete` attribute to `"on"`, `false` to set to `"off"`. Defaults to `undefined`, leaving the attribute unset.
  32810. * @accessor
  32811. */
  32812. autoComplete: null,
  32813. /**
  32814. * @cfg {Boolean} [autoCapitalize=undefined]
  32815. * `true` to set the field's DOM element `autocapitalize` attribute to `"on"`, `false` to set to `"off"`. Defaults to `undefined`, leaving the attribute unset
  32816. * @accessor
  32817. */
  32818. autoCapitalize: null,
  32819. /**
  32820. * `true` to set the field DOM element `autocorrect` attribute to `"on"`, `false` to set to `"off"`. Defaults to `undefined`, leaving the attribute unset.
  32821. * @cfg {Boolean} autoCorrect
  32822. * @accessor
  32823. */
  32824. autoCorrect: null,
  32825. /**
  32826. * @cfg {Boolean} [readOnly=undefined]
  32827. * `true` to set the field DOM element `readonly` attribute to `"true"`. Defaults to `undefined`, leaving the attribute unset.
  32828. * @accessor
  32829. */
  32830. readOnly: null,
  32831. /**
  32832. * @cfg {Number} [maxRows=undefined]
  32833. * Sets the field DOM element `maxRows` attribute. Defaults to `undefined`, leaving the attribute unset.
  32834. * @accessor
  32835. */
  32836. maxRows: null,
  32837. /**
  32838. * @cfg {String} pattern The value for the HTML5 `pattern` attribute.
  32839. * You can use this to change which keyboard layout will be used.
  32840. *
  32841. * Ext.define('Ux.field.Pattern', {
  32842. * extend : 'Ext.field.Text',
  32843. * xtype : 'patternfield',
  32844. *
  32845. * config : {
  32846. * component : {
  32847. * pattern : '[0-9]*'
  32848. * }
  32849. * }
  32850. * });
  32851. *
  32852. * Even though it extends {@link Ext.field.Text}, it will display the number keyboard.
  32853. *
  32854. * @accessor
  32855. */
  32856. pattern: null,
  32857. /**
  32858. * @cfg {Boolean} [disabled=false] `true` to disable the field.
  32859. *
  32860. * Be aware that conformant with the [HTML specification](http://www.w3.org/TR/html401/interact/forms.html),
  32861. * disabled Fields will not be {@link Ext.form.Panel#method-submit submitted}.
  32862. * @accessor
  32863. */
  32864. /**
  32865. * @cfg {Mixed} startValue
  32866. * The value that the Field had at the time it was last focused. This is the value that is passed
  32867. * to the {@link Ext.field.Text#change} event which is fired if the value has been changed when the Field is blurred.
  32868. *
  32869. * __This will be `undefined` until the Field has been visited.__ Compare {@link #originalValue}.
  32870. * @accessor
  32871. */
  32872. startValue: false
  32873. },
  32874. /**
  32875. * @cfg {String/Number} originalValue The original value when the input is rendered.
  32876. * @private
  32877. */
  32878. // @private
  32879. getTemplate: function() {
  32880. var items = [
  32881. {
  32882. reference: 'input',
  32883. tag: this.tag
  32884. },
  32885. {
  32886. reference: 'clearIcon',
  32887. cls: 'x-clear-icon'
  32888. }
  32889. ];
  32890. items.push({
  32891. reference: 'mask',
  32892. classList: [this.config.maskCls]
  32893. });
  32894. return items;
  32895. },
  32896. initElement: function() {
  32897. var me = this;
  32898. me.callParent();
  32899. me.input.on({
  32900. scope: me,
  32901. keyup: 'onKeyUp',
  32902. keydown: 'onKeyDown',
  32903. focus: 'onFocus',
  32904. blur: 'onBlur',
  32905. input: 'onInput',
  32906. paste: 'onPaste'
  32907. });
  32908. me.mask.on({
  32909. tap: 'onMaskTap',
  32910. scope: me
  32911. });
  32912. if (me.clearIcon) {
  32913. me.clearIcon.on({
  32914. tap: 'onClearIconTap',
  32915. scope: me
  32916. });
  32917. }
  32918. },
  32919. applyUseMask: function(useMask) {
  32920. if (useMask === 'auto') {
  32921. useMask = Ext.os.is.iOS && Ext.os.version.lt('5');
  32922. }
  32923. return Boolean(useMask);
  32924. },
  32925. /**
  32926. * Updates the useMask configuration
  32927. */
  32928. updateUseMask: function(newUseMask) {
  32929. this.mask[newUseMask ? 'show' : 'hide']();
  32930. },
  32931. updatePattern : function (pattern) {
  32932. this.updateFieldAttribute('pattern', pattern);
  32933. },
  32934. /**
  32935. * Helper method to update a specified attribute on the `fieldEl`, or remove the attribute all together.
  32936. * @private
  32937. */
  32938. updateFieldAttribute: function(attribute, newValue) {
  32939. var input = this.input;
  32940. if (newValue) {
  32941. input.dom.setAttribute(attribute, newValue);
  32942. } else {
  32943. input.dom.removeAttribute(attribute);
  32944. }
  32945. },
  32946. /**
  32947. * Updates the {@link #cls} configuration.
  32948. */
  32949. updateCls: function(newCls, oldCls) {
  32950. this.input.addCls(Ext.baseCSSPrefix + 'input-el');
  32951. this.input.replaceCls(oldCls, newCls);
  32952. },
  32953. /**
  32954. * Updates the type attribute with the {@link #type} configuration.
  32955. * @private
  32956. */
  32957. updateType: function(newType, oldType) {
  32958. var prefix = Ext.baseCSSPrefix + 'input-';
  32959. this.input.replaceCls(prefix + oldType, prefix + newType);
  32960. this.updateFieldAttribute('type', newType);
  32961. },
  32962. /**
  32963. * Updates the name attribute with the {@link #name} configuration.
  32964. * @private
  32965. */
  32966. updateName: function(newName) {
  32967. this.updateFieldAttribute('name', newName);
  32968. },
  32969. /**
  32970. * Returns the field data value.
  32971. * @return {Mixed} value The field value.
  32972. */
  32973. getValue: function() {
  32974. var input = this.input;
  32975. if (input) {
  32976. this._value = input.dom.value;
  32977. }
  32978. return this._value;
  32979. },
  32980. // @private
  32981. applyValue: function(value) {
  32982. return (Ext.isEmpty(value)) ? '' : value;
  32983. },
  32984. /**
  32985. * Updates the {@link #value} configuration.
  32986. * @private
  32987. */
  32988. updateValue: function(newValue) {
  32989. var input = this.input;
  32990. if (input) {
  32991. input.dom.value = newValue;
  32992. }
  32993. },
  32994. setValue: function(newValue) {
  32995. var oldValue = this._value;
  32996. this.updateValue(this.applyValue(newValue));
  32997. newValue = this.getValue();
  32998. if (String(newValue) != String(oldValue) && this.initialized) {
  32999. this.onChange(this, newValue, oldValue);
  33000. }
  33001. return this;
  33002. },
  33003. //<debug>
  33004. // @private
  33005. applyTabIndex: function(tabIndex) {
  33006. if (tabIndex !== null && typeof tabIndex != 'number') {
  33007. throw new Error("Ext.field.Field: [applyTabIndex] trying to pass a value which is not a number");
  33008. }
  33009. return tabIndex;
  33010. },
  33011. //</debug>
  33012. /**
  33013. * Updates the tabIndex attribute with the {@link #tabIndex} configuration
  33014. * @private
  33015. */
  33016. updateTabIndex: function(newTabIndex) {
  33017. this.updateFieldAttribute('tabIndex', newTabIndex);
  33018. },
  33019. // @private
  33020. testAutoFn: function(value) {
  33021. return [true, 'on'].indexOf(value) !== -1;
  33022. },
  33023. //<debug>
  33024. applyMaxLength: function(maxLength) {
  33025. if (maxLength !== null && typeof maxLength != 'number') {
  33026. throw new Error("Ext.field.Text: [applyMaxLength] trying to pass a value which is not a number");
  33027. }
  33028. return maxLength;
  33029. },
  33030. //</debug>
  33031. /**
  33032. * Updates the `maxlength` attribute with the {@link #maxLength} configuration.
  33033. * @private
  33034. */
  33035. updateMaxLength: function(newMaxLength) {
  33036. this.updateFieldAttribute('maxlength', newMaxLength);
  33037. },
  33038. /**
  33039. * Updates the `placeholder` attribute with the {@link #placeHolder} configuration.
  33040. * @private
  33041. */
  33042. updatePlaceHolder: function(newPlaceHolder) {
  33043. this.updateFieldAttribute('placeholder', newPlaceHolder);
  33044. },
  33045. // @private
  33046. applyAutoComplete: function(autoComplete) {
  33047. return this.testAutoFn(autoComplete);
  33048. },
  33049. /**
  33050. * Updates the `autocomplete` attribute with the {@link #autoComplete} configuration.
  33051. * @private
  33052. */
  33053. updateAutoComplete: function(newAutoComplete) {
  33054. var value = newAutoComplete ? 'on' : 'off';
  33055. this.updateFieldAttribute('autocomplete', value);
  33056. },
  33057. // @private
  33058. applyAutoCapitalize: function(autoCapitalize) {
  33059. return this.testAutoFn(autoCapitalize);
  33060. },
  33061. /**
  33062. * Updates the `autocapitalize` attribute with the {@link #autoCapitalize} configuration.
  33063. * @private
  33064. */
  33065. updateAutoCapitalize: function(newAutoCapitalize) {
  33066. var value = newAutoCapitalize ? 'on' : 'off';
  33067. this.updateFieldAttribute('autocapitalize', value);
  33068. },
  33069. // @private
  33070. applyAutoCorrect: function(autoCorrect) {
  33071. return this.testAutoFn(autoCorrect);
  33072. },
  33073. /**
  33074. * Updates the `autocorrect` attribute with the {@link #autoCorrect} configuration.
  33075. * @private
  33076. */
  33077. updateAutoCorrect: function(newAutoCorrect) {
  33078. var value = newAutoCorrect ? 'on' : 'off';
  33079. this.updateFieldAttribute('autocorrect', value);
  33080. },
  33081. /**
  33082. * Updates the `min` attribute with the {@link #minValue} configuration.
  33083. * @private
  33084. */
  33085. updateMinValue: function(newMinValue) {
  33086. this.updateFieldAttribute('min', newMinValue);
  33087. },
  33088. /**
  33089. * Updates the `max` attribute with the {@link #maxValue} configuration.
  33090. * @private
  33091. */
  33092. updateMaxValue: function(newMaxValue) {
  33093. this.updateFieldAttribute('max', newMaxValue);
  33094. },
  33095. /**
  33096. * Updates the `step` attribute with the {@link #stepValue} configuration
  33097. * @private
  33098. */
  33099. updateStepValue: function(newStepValue) {
  33100. this.updateFieldAttribute('step', newStepValue);
  33101. },
  33102. // @private
  33103. checkedRe: /^(true|1|on)/i,
  33104. /**
  33105. * Returns the `checked` value of this field
  33106. * @return {Mixed} value The field value
  33107. */
  33108. getChecked: function() {
  33109. var el = this.input,
  33110. checked;
  33111. if (el) {
  33112. checked = el.dom.checked;
  33113. this._checked = checked;
  33114. }
  33115. return checked;
  33116. },
  33117. // @private
  33118. applyChecked: function(checked) {
  33119. return !!this.checkedRe.test(String(checked));
  33120. },
  33121. setChecked: function(newChecked) {
  33122. this.updateChecked(this.applyChecked(newChecked));
  33123. this._checked = newChecked;
  33124. },
  33125. /**
  33126. * Updates the `autocorrect` attribute with the {@link #autoCorrect} configuration
  33127. * @private
  33128. */
  33129. updateChecked: function(newChecked) {
  33130. this.input.dom.checked = newChecked;
  33131. },
  33132. /**
  33133. * Updates the `readonly` attribute with the {@link #readOnly} configuration
  33134. * @private
  33135. */
  33136. updateReadOnly: function(readOnly) {
  33137. this.updateFieldAttribute('readonly', readOnly);
  33138. },
  33139. //<debug>
  33140. // @private
  33141. applyMaxRows: function(maxRows) {
  33142. if (maxRows !== null && typeof maxRows !== 'number') {
  33143. throw new Error("Ext.field.Input: [applyMaxRows] trying to pass a value which is not a number");
  33144. }
  33145. return maxRows;
  33146. },
  33147. //</debug>
  33148. updateMaxRows: function(newRows) {
  33149. this.updateFieldAttribute('rows', newRows);
  33150. },
  33151. doSetDisabled: function(disabled) {
  33152. this.callParent(arguments);
  33153. this.input.dom.disabled = disabled;
  33154. if (!disabled) {
  33155. this.blur();
  33156. }
  33157. },
  33158. /**
  33159. * Returns `true` if the value of this Field has been changed from its original value.
  33160. * Will return `false` if the field is disabled or has not been rendered yet.
  33161. * @return {Boolean}
  33162. */
  33163. isDirty: function() {
  33164. if (this.getDisabled()) {
  33165. return false;
  33166. }
  33167. return String(this.getValue()) !== String(this.originalValue);
  33168. },
  33169. /**
  33170. * Resets the current field value to the original value.
  33171. */
  33172. reset: function() {
  33173. this.setValue(this.originalValue);
  33174. },
  33175. // @private
  33176. onMaskTap: function(e) {
  33177. this.fireAction('masktap', [this, e], 'doMaskTap');
  33178. },
  33179. // @private
  33180. doMaskTap: function(me, e) {
  33181. if (me.getDisabled()) {
  33182. return false;
  33183. }
  33184. me.maskCorrectionTimer = Ext.defer(me.showMask, 1000, me);
  33185. me.hideMask();
  33186. },
  33187. // @private
  33188. showMask: function(e) {
  33189. if (this.mask) {
  33190. this.mask.setStyle('display', 'block');
  33191. }
  33192. },
  33193. // @private
  33194. hideMask: function(e) {
  33195. if (this.mask) {
  33196. this.mask.setStyle('display', 'none');
  33197. }
  33198. },
  33199. /**
  33200. * Attempts to set the field as the active input focus.
  33201. * @return {Ext.field.Input} this
  33202. */
  33203. focus: function() {
  33204. var me = this,
  33205. el = me.input;
  33206. if (el && el.dom.focus) {
  33207. el.dom.focus();
  33208. }
  33209. return me;
  33210. },
  33211. /**
  33212. * Attempts to forcefully blur input focus for the field.
  33213. * @return {Ext.field.Input} this
  33214. * @chainable
  33215. */
  33216. blur: function() {
  33217. var me = this,
  33218. el = this.input;
  33219. if (el && el.dom.blur) {
  33220. el.dom.blur();
  33221. }
  33222. return me;
  33223. },
  33224. /**
  33225. * Attempts to forcefully select all the contents of the input field.
  33226. * @return {Ext.field.Input} this
  33227. * @chainable
  33228. */
  33229. select: function() {
  33230. var me = this,
  33231. el = me.input;
  33232. if (el && el.dom.setSelectionRange) {
  33233. el.dom.setSelectionRange(0, 9999);
  33234. }
  33235. return me;
  33236. },
  33237. onFocus: function(e) {
  33238. this.fireAction('focus', [e], 'doFocus');
  33239. },
  33240. // @private
  33241. doFocus: function(e) {
  33242. var me = this;
  33243. if (me.mask) {
  33244. if (me.maskCorrectionTimer) {
  33245. clearTimeout(me.maskCorrectionTimer);
  33246. }
  33247. me.hideMask();
  33248. }
  33249. if (!me.getIsFocused()) {
  33250. me.setIsFocused(true);
  33251. me.setStartValue(me.getValue());
  33252. }
  33253. },
  33254. onBlur: function(e) {
  33255. this.fireAction('blur', [e], 'doBlur');
  33256. },
  33257. // @private
  33258. doBlur: function(e) {
  33259. var me = this,
  33260. value = me.getValue(),
  33261. startValue = me.getStartValue();
  33262. me.setIsFocused(false);
  33263. if (String(value) != String(startValue)) {
  33264. me.onChange(me, value, startValue);
  33265. }
  33266. me.showMask();
  33267. },
  33268. // @private
  33269. onClearIconTap: function(e) {
  33270. this.fireEvent('clearicontap', this, e);
  33271. //focus the field after cleartap happens, but only on android.
  33272. //this is to stop the keyboard from hiding. TOUCH-2064
  33273. if (Ext.os.is.Android) {
  33274. this.focus();
  33275. }
  33276. },
  33277. onClick: function(e) {
  33278. this.fireEvent('click', e);
  33279. },
  33280. onChange: function(me, value, startValue) {
  33281. this.fireEvent('change', me, value, startValue);
  33282. },
  33283. onPaste: function(e) {
  33284. this.fireEvent('paste', e);
  33285. },
  33286. onKeyUp: function(e) {
  33287. this.fireEvent('keyup', e);
  33288. },
  33289. onKeyDown: function() {
  33290. // tell the class to ignore the input event. this happens when we want to listen to the field change
  33291. // when the input autocompletes
  33292. this.ignoreInput = true;
  33293. },
  33294. onInput: function(e) {
  33295. var me = this;
  33296. // if we should ignore input, stop now.
  33297. if (me.ignoreInput) {
  33298. me.ignoreInput = false;
  33299. return;
  33300. }
  33301. // set a timeout for 10ms to check if we want to stop the input event.
  33302. // if not, then continue with the event (keyup)
  33303. setTimeout(function() {
  33304. if (!me.ignoreInput) {
  33305. me.fireEvent('keyup', e);
  33306. me.ignoreInput = false;
  33307. }
  33308. }, 10);
  33309. },
  33310. onMouseDown: function(e) {
  33311. this.fireEvent('mousedown', e);
  33312. }
  33313. });
  33314. /**
  33315. * @aside guide forms
  33316. *
  33317. * Field is the base class for all form fields used in Sencha Touch. It provides a lot of shared functionality to all
  33318. * field subclasses (for example labels, simple validation, {@link #clearIcon clearing} and tab index management), but
  33319. * is rarely used directly. Instead, it is much more common to use one of the field subclasses:
  33320. *
  33321. * xtype Class
  33322. * ---------------------------------------
  33323. * textfield {@link Ext.field.Text}
  33324. * numberfield {@link Ext.field.Number}
  33325. * textareafield {@link Ext.field.TextArea}
  33326. * hiddenfield {@link Ext.field.Hidden}
  33327. * radiofield {@link Ext.field.Radio}
  33328. * checkboxfield {@link Ext.field.Checkbox}
  33329. * selectfield {@link Ext.field.Select}
  33330. * togglefield {@link Ext.field.Toggle}
  33331. * fieldset {@link Ext.form.FieldSet}
  33332. *
  33333. * Fields are normally used within the context of a form and/or fieldset. See the {@link Ext.form.Panel FormPanel}
  33334. * and {@link Ext.form.FieldSet FieldSet} docs for examples on how to put those together, or the list of links above
  33335. * for usage of individual field types. If you wish to create your own Field subclasses you can extend this class,
  33336. * though it is sometimes more useful to extend {@link Ext.field.Text} as this provides additional text entry
  33337. * functionality.
  33338. */
  33339. Ext.define('Ext.field.Field', {
  33340. extend: 'Ext.Decorator',
  33341. alternateClassName: 'Ext.form.Field',
  33342. xtype: 'field',
  33343. requires: [
  33344. 'Ext.field.Input'
  33345. ],
  33346. /**
  33347. * Set to `true` on all Ext.field.Field subclasses. This is used by {@link Ext.form.Panel#getValues} to determine which
  33348. * components inside a form are fields.
  33349. * @property isField
  33350. * @type Boolean
  33351. */
  33352. isField: true,
  33353. // @private
  33354. isFormField: true,
  33355. config: {
  33356. /**
  33357. * @cfg
  33358. * @inheritdoc
  33359. */
  33360. baseCls: Ext.baseCSSPrefix + 'field',
  33361. /**
  33362. * The label of this field
  33363. * @cfg {String} label
  33364. * @accessor
  33365. */
  33366. label: null,
  33367. /**
  33368. * @cfg {String} labelAlign The position to render the label relative to the field input.
  33369. * Available options are: 'top', 'left', 'bottom' and 'right'
  33370. * @accessor
  33371. */
  33372. labelAlign: 'left',
  33373. /**
  33374. * @cfg {Number/String} labelWidth The width to make this field's label.
  33375. * @accessor
  33376. */
  33377. labelWidth: '30%',
  33378. /**
  33379. * @cfg {Boolean} labelWrap `true` to allow the label to wrap. If set to `false`, the label will be truncated with
  33380. * an ellipsis.
  33381. * @accessor
  33382. */
  33383. labelWrap: false,
  33384. /**
  33385. * @cfg {Boolean} clearIcon `true` to use a clear icon in this field.
  33386. * @accessor
  33387. */
  33388. clearIcon: null,
  33389. /**
  33390. * @cfg {Boolean} required `true` to make this field required.
  33391. *
  33392. * __Note:__ this only causes a visual indication.
  33393. *
  33394. * Doesn't prevent user from submitting the form.
  33395. * @accessor
  33396. */
  33397. required: false,
  33398. /**
  33399. * The label Element associated with this Field.
  33400. *
  33401. * __Note:__ Only available if a {@link #label} is specified.
  33402. * @type Ext.Element
  33403. * @property labelEl
  33404. * @deprecated 2.0
  33405. */
  33406. /**
  33407. * @cfg {String} [inputType='text'] The type attribute for input fields -- e.g. radio, text, password, file.
  33408. * The types 'file' and 'password' must be used to render those field types currently -- there are
  33409. * no separate Ext components for those.
  33410. * @deprecated 2.0 Please use `input.type` instead.
  33411. * @accessor
  33412. */
  33413. inputType: null,
  33414. /**
  33415. * @cfg {String} name The field's HTML name attribute.
  33416. *
  33417. * __Note:__ this property must be set if this field is to be automatically included with.
  33418. * {@link Ext.form.Panel#method-submit form submit()}.
  33419. * @accessor
  33420. */
  33421. name: null,
  33422. /**
  33423. * @cfg {Mixed} value A value to initialize this field with.
  33424. * @accessor
  33425. */
  33426. value: null,
  33427. /**
  33428. * @cfg {Number} tabIndex The `tabIndex` for this field. Note this only applies to fields that are rendered,
  33429. * not those which are built via `applyTo`.
  33430. * @accessor
  33431. */
  33432. tabIndex: null
  33433. /**
  33434. * @cfg {Object} component The inner component for this field.
  33435. */
  33436. /**
  33437. * @cfg {Boolean} fullscreen
  33438. * @hide
  33439. */
  33440. },
  33441. cachedConfig: {
  33442. /**
  33443. * @cfg {String} labelCls Optional CSS class to add to the Label element.
  33444. * @accessor
  33445. */
  33446. labelCls: null,
  33447. /**
  33448. * @cfg {String} requiredCls The `className` to be applied to this Field when the {@link #required} configuration is set to `true`.
  33449. * @accessor
  33450. */
  33451. requiredCls: Ext.baseCSSPrefix + 'field-required',
  33452. /**
  33453. * @cfg {String} inputCls CSS class to add to the input element of this fields {@link #component}
  33454. */
  33455. inputCls: null
  33456. },
  33457. /**
  33458. * @cfg {Boolean} isFocused
  33459. * `true` if this field is currently focused.
  33460. * @private
  33461. */
  33462. getElementConfig: function() {
  33463. var prefix = Ext.baseCSSPrefix;
  33464. return {
  33465. reference: 'element',
  33466. className: 'x-container',
  33467. children: [
  33468. {
  33469. reference: 'label',
  33470. cls: prefix + 'form-label',
  33471. children: [{
  33472. reference: 'labelspan',
  33473. tag: 'span'
  33474. }]
  33475. },
  33476. {
  33477. reference: 'innerElement',
  33478. cls: prefix + 'component-outer'
  33479. }
  33480. ]
  33481. };
  33482. },
  33483. // @private
  33484. updateLabel: function(newLabel, oldLabel) {
  33485. var renderElement = this.renderElement,
  33486. prefix = Ext.baseCSSPrefix;
  33487. if (newLabel) {
  33488. this.labelspan.setHtml(newLabel);
  33489. renderElement.addCls(prefix + 'field-labeled');
  33490. } else {
  33491. renderElement.removeCls(prefix + 'field-labeled');
  33492. }
  33493. },
  33494. // @private
  33495. updateLabelAlign: function(newLabelAlign, oldLabelAlign) {
  33496. var renderElement = this.renderElement,
  33497. prefix = Ext.baseCSSPrefix;
  33498. if (newLabelAlign) {
  33499. renderElement.addCls(prefix + 'label-align-' + newLabelAlign);
  33500. if (newLabelAlign == "top" || newLabelAlign == "bottom") {
  33501. this.label.setWidth('100%');
  33502. } else {
  33503. this.updateLabelWidth(this.getLabelWidth());
  33504. }
  33505. }
  33506. if (oldLabelAlign) {
  33507. renderElement.removeCls(prefix + 'label-align-' + oldLabelAlign);
  33508. }
  33509. },
  33510. // @private
  33511. updateLabelCls: function(newLabelCls, oldLabelCls) {
  33512. if (newLabelCls) {
  33513. this.label.addCls(newLabelCls);
  33514. }
  33515. if (oldLabelCls) {
  33516. this.label.removeCls(oldLabelCls);
  33517. }
  33518. },
  33519. // @private
  33520. updateLabelWidth: function(newLabelWidth) {
  33521. var labelAlign = this.getLabelAlign();
  33522. if (newLabelWidth) {
  33523. if (labelAlign == "top" || labelAlign == "bottom") {
  33524. this.label.setWidth('100%');
  33525. } else {
  33526. this.label.setWidth(newLabelWidth);
  33527. }
  33528. }
  33529. },
  33530. // @private
  33531. updateLabelWrap: function(newLabelWrap, oldLabelWrap) {
  33532. var cls = Ext.baseCSSPrefix + 'form-label-nowrap';
  33533. if (!newLabelWrap) {
  33534. this.addCls(cls);
  33535. } else {
  33536. this.removeCls(cls);
  33537. }
  33538. },
  33539. /**
  33540. * Updates the {@link #required} configuration.
  33541. * @private
  33542. */
  33543. updateRequired: function(newRequired) {
  33544. this.renderElement[newRequired ? 'addCls' : 'removeCls'](this.getRequiredCls());
  33545. },
  33546. /**
  33547. * Updates the {@link #required} configuration
  33548. * @private
  33549. */
  33550. updateRequiredCls: function(newRequiredCls, oldRequiredCls) {
  33551. if (this.getRequired()) {
  33552. this.renderElement.replaceCls(oldRequiredCls, newRequiredCls);
  33553. }
  33554. },
  33555. // @private
  33556. initialize: function() {
  33557. var me = this;
  33558. me.callParent();
  33559. me.doInitValue();
  33560. },
  33561. /**
  33562. * @private
  33563. */
  33564. doInitValue: function() {
  33565. /**
  33566. * @property {Mixed} originalValue
  33567. * The original value of the field as configured in the {@link #value} configuration.
  33568. * setting is `true`.
  33569. */
  33570. this.originalValue = this.getInitialConfig().value;
  33571. },
  33572. /**
  33573. * Resets the current field value back to the original value on this field when it was created.
  33574. *
  33575. * // This will create a field with an original value
  33576. * var field = Ext.Viewport.add({
  33577. * xtype: 'textfield',
  33578. * value: 'first value'
  33579. * });
  33580. *
  33581. * // Update the value
  33582. * field.setValue('new value');
  33583. *
  33584. * // Now you can reset it back to the `first value`
  33585. * field.reset();
  33586. *
  33587. * @return {Ext.field.Field} this
  33588. */
  33589. reset: function() {
  33590. this.setValue(this.originalValue);
  33591. return this;
  33592. },
  33593. /**
  33594. * Returns `true` if the value of this Field has been changed from its {@link #originalValue}.
  33595. * Will return `false` if the field is disabled or has not been rendered yet.
  33596. *
  33597. * @return {Boolean} `true` if this field has been changed from its original value (and
  33598. * is not disabled), `false` otherwise.
  33599. */
  33600. isDirty: function() {
  33601. return false;
  33602. }
  33603. }, function() {
  33604. });
  33605. /**
  33606. * @aside guide forms
  33607. *
  33608. * The text field is the basis for most of the input fields in Sencha Touch. It provides a baseline of shared
  33609. * functionality such as input validation, standard events, state management and look and feel. Typically we create
  33610. * text fields inside a form, like this:
  33611. *
  33612. * @example
  33613. * Ext.create('Ext.form.Panel', {
  33614. * fullscreen: true,
  33615. * items: [
  33616. * {
  33617. * xtype: 'fieldset',
  33618. * title: 'Enter your name',
  33619. * items: [
  33620. * {
  33621. * xtype: 'textfield',
  33622. * label: 'First Name',
  33623. * name: 'firstName'
  33624. * },
  33625. * {
  33626. * xtype: 'textfield',
  33627. * label: 'Last Name',
  33628. * name: 'lastName'
  33629. * }
  33630. * ]
  33631. * }
  33632. * ]
  33633. * });
  33634. *
  33635. * This creates two text fields inside a form. Text Fields can also be created outside of a Form, like this:
  33636. *
  33637. * Ext.create('Ext.field.Text', {
  33638. * label: 'Your Name',
  33639. * value: 'Ed Spencer'
  33640. * });
  33641. *
  33642. * ## Configuring
  33643. *
  33644. * Text field offers several configuration options, including {@link #placeHolder}, {@link #maxLength},
  33645. * {@link #autoComplete}, {@link #autoCapitalize} and {@link #autoCorrect}. For example, here is how we would configure
  33646. * a text field to have a maximum length of 10 characters, with placeholder text that disappears when the field is
  33647. * focused:
  33648. *
  33649. * Ext.create('Ext.field.Text', {
  33650. * label: 'Username',
  33651. * maxLength: 10,
  33652. * placeHolder: 'Enter your username'
  33653. * });
  33654. *
  33655. * The autoComplete, autoCapitalize and autoCorrect configs simply set those attributes on the text field and allow the
  33656. * native browser to provide those capabilities. For example, to enable auto complete and auto correct, simply
  33657. * configure your text field like this:
  33658. *
  33659. * Ext.create('Ext.field.Text', {
  33660. * label: 'Username',
  33661. * autoComplete: true,
  33662. * autoCorrect: true
  33663. * });
  33664. *
  33665. * These configurations will be picked up by the native browser, which will enable the options at the OS level.
  33666. *
  33667. * Text field inherits from {@link Ext.field.Field}, which is the base class for all fields in Sencha Touch and provides
  33668. * a lot of shared functionality for all fields, including setting values, clearing and basic validation. See the
  33669. * {@link Ext.field.Field} documentation to see how to leverage its capabilities.
  33670. */
  33671. Ext.define('Ext.field.Text', {
  33672. extend: 'Ext.field.Field',
  33673. xtype: 'textfield',
  33674. alternateClassName: 'Ext.form.Text',
  33675. /**
  33676. * @event focus
  33677. * Fires when this field receives input focus
  33678. * @param {Ext.field.Text} this This field
  33679. * @param {Ext.event.Event} e
  33680. */
  33681. /**
  33682. * @event blur
  33683. * Fires when this field loses input focus
  33684. * @param {Ext.field.Text} this This field
  33685. * @param {Ext.event.Event} e
  33686. */
  33687. /**
  33688. * @event paste
  33689. * Fires when this field is pasted.
  33690. * @param {Ext.field.Text} this This field
  33691. * @param {Ext.event.Event} e
  33692. */
  33693. /**
  33694. * @event mousedown
  33695. * Fires when this field receives a mousedown
  33696. * @param {Ext.field.Text} this This field
  33697. * @param {Ext.event.Event} e
  33698. */
  33699. /**
  33700. * @event keyup
  33701. * @preventable doKeyUp
  33702. * Fires when a key is released on the input element
  33703. * @param {Ext.field.Text} this This field
  33704. * @param {Ext.event.Event} e
  33705. */
  33706. /**
  33707. * @event clearicontap
  33708. * @preventable doClearIconTap
  33709. * Fires when the clear icon is tapped
  33710. * @param {Ext.field.Text} this This field
  33711. * @param {Ext.event.Event} e
  33712. */
  33713. /**
  33714. * @event change
  33715. * Fires just before the field blurs if the field value has changed
  33716. * @param {Ext.field.Text} this This field
  33717. * @param {Mixed} newValue The new value
  33718. * @param {Mixed} oldValue The original value
  33719. */
  33720. /**
  33721. * @event action
  33722. * @preventable doAction
  33723. * Fires whenever the return key or go is pressed. FormPanel listeners
  33724. * for this event, and submits itself whenever it fires. Also note
  33725. * that this event bubbles up to parent containers.
  33726. * @param {Ext.field.Text} this This field
  33727. * @param {Mixed} e The key event object
  33728. */
  33729. config: {
  33730. /**
  33731. * @cfg
  33732. * @inheritdoc
  33733. */
  33734. ui: 'text',
  33735. /**
  33736. * @cfg
  33737. * @inheritdoc
  33738. */
  33739. clearIcon: true,
  33740. /**
  33741. * @cfg {String} placeHolder A string value displayed in the input (if supported) when the control is empty.
  33742. * @accessor
  33743. */
  33744. placeHolder: null,
  33745. /**
  33746. * @cfg {Number} maxLength The maximum number of permitted input characters.
  33747. * @accessor
  33748. */
  33749. maxLength: null,
  33750. /**
  33751. * True to set the field's DOM element autocomplete attribute to "on", false to set to "off".
  33752. * @cfg {Boolean} autoComplete
  33753. * @accessor
  33754. */
  33755. autoComplete: null,
  33756. /**
  33757. * True to set the field's DOM element autocapitalize attribute to "on", false to set to "off".
  33758. * @cfg {Boolean} autoCapitalize
  33759. * @accessor
  33760. */
  33761. autoCapitalize: null,
  33762. /**
  33763. * True to set the field DOM element autocorrect attribute to "on", false to set to "off".
  33764. * @cfg {Boolean} autoCorrect
  33765. * @accessor
  33766. */
  33767. autoCorrect: null,
  33768. /**
  33769. * True to set the field DOM element readonly attribute to true.
  33770. * @cfg {Boolean} readOnly
  33771. * @accessor
  33772. */
  33773. readOnly: null,
  33774. /**
  33775. * @cfg {Object} component The inner component for this field, which defaults to an input text.
  33776. * @accessor
  33777. */
  33778. component: {
  33779. xtype: 'input',
  33780. type : 'text'
  33781. },
  33782. bubbleEvents: ['action']
  33783. },
  33784. // @private
  33785. initialize: function() {
  33786. var me = this;
  33787. me.callParent();
  33788. me.getComponent().on({
  33789. scope: this,
  33790. keyup : 'onKeyUp',
  33791. change : 'onChange',
  33792. focus : 'onFocus',
  33793. blur : 'onBlur',
  33794. paste : 'onPaste',
  33795. mousedown : 'onMouseDown',
  33796. clearicontap: 'onClearIconTap'
  33797. });
  33798. // set the originalValue of the textfield, if one exists
  33799. me.originalValue = me.originalValue || "";
  33800. me.getComponent().originalValue = me.originalValue;
  33801. me.syncEmptyCls();
  33802. },
  33803. syncEmptyCls: function() {
  33804. var empty = (this._value) ? this._value.length : false,
  33805. cls = Ext.baseCSSPrefix + 'empty';
  33806. if (empty) {
  33807. this.removeCls(cls);
  33808. } else {
  33809. this.addCls(cls);
  33810. }
  33811. },
  33812. // @private
  33813. updateValue: function(newValue) {
  33814. var component = this.getComponent(),
  33815. // allows newValue to be zero but not undefined, null or an empty string (other falsey values)
  33816. valueValid = newValue !== undefined && newValue !== null && newValue !== '';
  33817. if (component) {
  33818. component.setValue(newValue);
  33819. }
  33820. this[valueValid ? 'showClearIcon' : 'hideClearIcon']();
  33821. this.syncEmptyCls();
  33822. },
  33823. getValue: function() {
  33824. var me = this;
  33825. me._value = me.getComponent().getValue();
  33826. me.syncEmptyCls();
  33827. return me._value;
  33828. },
  33829. // @private
  33830. updatePlaceHolder: function(newPlaceHolder) {
  33831. this.getComponent().setPlaceHolder(newPlaceHolder);
  33832. },
  33833. // @private
  33834. updateMaxLength: function(newMaxLength) {
  33835. this.getComponent().setMaxLength(newMaxLength);
  33836. },
  33837. // @private
  33838. updateAutoComplete: function(newAutoComplete) {
  33839. this.getComponent().setAutoComplete(newAutoComplete);
  33840. },
  33841. // @private
  33842. updateAutoCapitalize: function(newAutoCapitalize) {
  33843. this.getComponent().setAutoCapitalize(newAutoCapitalize);
  33844. },
  33845. // @private
  33846. updateAutoCorrect: function(newAutoCorrect) {
  33847. this.getComponent().setAutoCorrect(newAutoCorrect);
  33848. },
  33849. // @private
  33850. updateReadOnly: function(newReadOnly) {
  33851. if (newReadOnly) {
  33852. this.hideClearIcon();
  33853. } else {
  33854. this.showClearIcon();
  33855. }
  33856. this.getComponent().setReadOnly(newReadOnly);
  33857. },
  33858. // @private
  33859. updateInputType: function(newInputType) {
  33860. var component = this.getComponent();
  33861. if (component) {
  33862. component.setType(newInputType);
  33863. }
  33864. },
  33865. // @private
  33866. updateName: function(newName) {
  33867. var component = this.getComponent();
  33868. if (component) {
  33869. component.setName(newName);
  33870. }
  33871. },
  33872. // @private
  33873. updateTabIndex: function(newTabIndex) {
  33874. var component = this.getComponent();
  33875. if (component) {
  33876. component.setTabIndex(newTabIndex);
  33877. }
  33878. },
  33879. /**
  33880. * Updates the {@link #inputCls} configuration on this fields {@link #component}
  33881. * @private
  33882. */
  33883. updateInputCls: function(newInputCls, oldInputCls) {
  33884. var component = this.getComponent();
  33885. if (component) {
  33886. component.replaceCls(oldInputCls, newInputCls);
  33887. }
  33888. },
  33889. doSetDisabled: function(disabled) {
  33890. var me = this;
  33891. me.callParent(arguments);
  33892. var component = me.getComponent();
  33893. if (component) {
  33894. component.setDisabled(disabled);
  33895. }
  33896. if (disabled) {
  33897. me.hideClearIcon();
  33898. } else {
  33899. me.showClearIcon();
  33900. }
  33901. },
  33902. // @private
  33903. showClearIcon: function() {
  33904. var me = this,
  33905. value = me.getValue(),
  33906. // allows value to be zero but not undefined, null or an empty string (other falsey values)
  33907. valueValid = value !== undefined && value !== null && value !== '';
  33908. if (me.getClearIcon() && !me.getDisabled() && !me.getReadOnly() && valueValid) {
  33909. me.element.addCls(Ext.baseCSSPrefix + 'field-clearable');
  33910. }
  33911. return me;
  33912. },
  33913. // @private
  33914. hideClearIcon: function() {
  33915. if (this.getClearIcon()) {
  33916. this.element.removeCls(Ext.baseCSSPrefix + 'field-clearable');
  33917. }
  33918. },
  33919. onKeyUp: function(e) {
  33920. this.fireAction('keyup', [this, e], 'doKeyUp');
  33921. },
  33922. /**
  33923. * Called when a key has been pressed in the `<input>`
  33924. * @private
  33925. */
  33926. doKeyUp: function(me, e) {
  33927. // getValue to ensure that we are in sync with the dom
  33928. var value = me.getValue(),
  33929. // allows value to be zero but not undefined, null or an empty string (other falsey values)
  33930. valueValid = value !== undefined && value !== null && value !== '';
  33931. this[valueValid ? 'showClearIcon' : 'hideClearIcon']();
  33932. if (e.browserEvent.keyCode === 13) {
  33933. me.fireAction('action', [me, e], 'doAction');
  33934. }
  33935. },
  33936. doAction: function() {
  33937. this.blur();
  33938. },
  33939. onClearIconTap: function(e) {
  33940. this.fireAction('clearicontap', [this, e], 'doClearIconTap');
  33941. },
  33942. // @private
  33943. doClearIconTap: function(me, e) {
  33944. me.setValue('');
  33945. //sync with the input
  33946. me.getValue();
  33947. },
  33948. onChange: function(me, value, startValue) {
  33949. me.fireEvent('change', this, value, startValue);
  33950. },
  33951. onFocus: function(e) {
  33952. this.isFocused = true;
  33953. this.fireEvent('focus', this, e);
  33954. },
  33955. onBlur: function(e) {
  33956. var me = this;
  33957. this.isFocused = false;
  33958. me.fireEvent('blur', me, e);
  33959. setTimeout(function() {
  33960. me.isFocused = false;
  33961. }, 50);
  33962. },
  33963. onPaste: function(e) {
  33964. this.fireEvent('paste', this, e);
  33965. },
  33966. onMouseDown: function(e) {
  33967. this.fireEvent('mousedown', this, e);
  33968. },
  33969. /**
  33970. * Attempts to set the field as the active input focus.
  33971. * @return {Ext.field.Text} This field
  33972. */
  33973. focus: function() {
  33974. this.getComponent().focus();
  33975. return this;
  33976. },
  33977. /**
  33978. * Attempts to forcefully blur input focus for the field.
  33979. * @return {Ext.field.Text} This field
  33980. */
  33981. blur: function() {
  33982. this.getComponent().blur();
  33983. return this;
  33984. },
  33985. /**
  33986. * Attempts to forcefully select all the contents of the input field.
  33987. * @return {Ext.field.Text} this
  33988. */
  33989. select: function() {
  33990. this.getComponent().select();
  33991. return this;
  33992. },
  33993. reset: function() {
  33994. this.getComponent().reset();
  33995. //we need to call this to sync the input with this field
  33996. this.getValue();
  33997. this[this._value ? 'showClearIcon' : 'hideClearIcon']();
  33998. },
  33999. isDirty: function() {
  34000. var component = this.getComponent();
  34001. if (component) {
  34002. return component.isDirty();
  34003. }
  34004. return false;
  34005. }
  34006. });
  34007. /**
  34008. * @private
  34009. */
  34010. Ext.define('Ext.field.TextAreaInput', {
  34011. extend: 'Ext.field.Input',
  34012. xtype : 'textareainput',
  34013. tag: 'textarea'
  34014. });
  34015. /**
  34016. * @aside guide forms
  34017. *
  34018. * Creates an HTML textarea field on the page. This is useful whenever you need the user to enter large amounts of text
  34019. * (i.e. more than a few words). Typically, text entry on mobile devices is not a pleasant experience for the user so
  34020. * it's good to limit your use of text areas to only those occasions when free form text is required or alternative
  34021. * input methods like select boxes or radio buttons are not possible. Text Areas are usually created inside forms, like
  34022. * this:
  34023. *
  34024. * @example
  34025. * Ext.create('Ext.form.Panel', {
  34026. * fullscreen: true,
  34027. * items: [
  34028. * {
  34029. * xtype: 'fieldset',
  34030. * title: 'About you',
  34031. * items: [
  34032. * {
  34033. * xtype: 'textfield',
  34034. * label: 'Name',
  34035. * name: 'name'
  34036. * },
  34037. * {
  34038. * xtype: 'textareafield',
  34039. * label: 'Bio',
  34040. * maxRows: 4,
  34041. * name: 'bio'
  34042. * }
  34043. * ]
  34044. * }
  34045. * ]
  34046. * });
  34047. *
  34048. * In the example above we're creating a form with a {@link Ext.field.Text text field} for the user's name and a text
  34049. * area for their bio. We used the {@link #maxRows} configuration on the text area to tell it to grow to a maximum of 4
  34050. * rows of text before it starts using a scroll bar inside the text area to scroll the text.
  34051. *
  34052. * We can also create a text area outside the context of a form, like this:
  34053. *
  34054. * This creates two text fields inside a form. Text Fields can also be created outside of a Form, like this:
  34055. *
  34056. * Ext.create('Ext.field.TextArea', {
  34057. * label: 'About You',
  34058. * {@link #placeHolder}: 'Tell us about yourself...'
  34059. * });
  34060. */
  34061. Ext.define('Ext.field.TextArea', {
  34062. extend: 'Ext.field.Text',
  34063. xtype: 'textareafield',
  34064. requires: ['Ext.field.TextAreaInput'],
  34065. alternateClassName: 'Ext.form.TextArea',
  34066. config: {
  34067. /**
  34068. * @cfg
  34069. * @inheritdoc
  34070. */
  34071. ui: 'textarea',
  34072. /**
  34073. * @cfg
  34074. * @inheritdoc
  34075. */
  34076. autoCapitalize: false,
  34077. /**
  34078. * @cfg
  34079. * @inheritdoc
  34080. */
  34081. component: {
  34082. xtype: 'textareainput'
  34083. },
  34084. /**
  34085. * @cfg {Number} maxRows The maximum number of lines made visible by the input.
  34086. * @accessor
  34087. */
  34088. maxRows: null
  34089. },
  34090. // @private
  34091. updateMaxRows: function(newRows) {
  34092. this.getComponent().setMaxRows(newRows);
  34093. },
  34094. doSetHeight: function(newHeight) {
  34095. this.callParent(arguments);
  34096. var component = this.getComponent();
  34097. component.input.setHeight(newHeight);
  34098. },
  34099. doSetWidth: function(newWidth) {
  34100. this.callParent(arguments);
  34101. var component = this.getComponent();
  34102. component.input.setWidth(newWidth);
  34103. },
  34104. /**
  34105. * Called when a key has been pressed in the `<input>`
  34106. * @private
  34107. */
  34108. doKeyUp: function(me) {
  34109. // getValue to ensure that we are in sync with the dom
  34110. var value = me.getValue();
  34111. // show the {@link #clearIcon} if it is being used
  34112. me[value ? 'showClearIcon' : 'hideClearIcon']();
  34113. }
  34114. });
  34115. /**
  34116. * Utility class for generating different styles of message boxes. The framework provides a global singleton
  34117. * {@link Ext.Msg} for common usage which you should use in most cases.
  34118. *
  34119. * If you want to use {@link Ext.MessageBox} directly, just think of it as a modal {@link Ext.Container}.
  34120. *
  34121. * Note that the MessageBox is asynchronous. Unlike a regular JavaScript `alert` (which will halt browser execution),
  34122. * showing a MessageBox will not cause the code to stop. For this reason, if you have code that should only run _after_
  34123. * some user feedback from the MessageBox, you must use a callback function (see the `fn` configuration option parameter
  34124. * for the {@link #method-show show} method for more details).
  34125. *
  34126. * @example preview
  34127. * Ext.Msg.alert('Title', 'The quick brown fox jumped over the lazy dog.', Ext.emptyFn);
  34128. *
  34129. * Checkout {@link Ext.Msg} for more examples.
  34130. *
  34131. */
  34132. Ext.define('Ext.MessageBox', {
  34133. extend : 'Ext.Sheet',
  34134. requires: [
  34135. 'Ext.Toolbar',
  34136. 'Ext.field.Text',
  34137. 'Ext.field.TextArea'
  34138. ],
  34139. config: {
  34140. /**
  34141. * @cfg
  34142. * @inheritdoc
  34143. */
  34144. ui: 'dark',
  34145. /**
  34146. * @cfg
  34147. * @inheritdoc
  34148. */
  34149. baseCls: Ext.baseCSSPrefix + 'msgbox',
  34150. /**
  34151. * @cfg {String} iconCls
  34152. * CSS class for the icon. The icon should be 40px x 40px.
  34153. * @accessor
  34154. */
  34155. iconCls: null,
  34156. /**
  34157. * @cfg
  34158. * @inheritdoc
  34159. */
  34160. showAnimation: {
  34161. type: 'popIn',
  34162. duration: 250,
  34163. easing: 'ease-out'
  34164. },
  34165. /**
  34166. * @cfg
  34167. * @inheritdoc
  34168. */
  34169. hideAnimation: {
  34170. type: 'popOut',
  34171. duration: 250,
  34172. easing: 'ease-out'
  34173. },
  34174. /**
  34175. * Override the default `zIndex` so it is normally always above floating components.
  34176. */
  34177. zIndex: 999,
  34178. /**
  34179. * @cfg {Number} defaultTextHeight
  34180. * The default height in pixels of the message box's multiline textarea if displayed.
  34181. * @accessor
  34182. */
  34183. defaultTextHeight: 75,
  34184. /**
  34185. * @cfg {String} title
  34186. * The title of this {@link Ext.MessageBox}.
  34187. * @accessor
  34188. */
  34189. title: null,
  34190. /**
  34191. * @cfg {Array/Object} buttons
  34192. * An array of buttons, or an object of a button to be displayed in the toolbar of this {@link Ext.MessageBox}.
  34193. */
  34194. buttons: null,
  34195. /**
  34196. * @cfg {String} message
  34197. * The message to be displayed in the {@link Ext.MessageBox}.
  34198. * @accessor
  34199. */
  34200. message: null,
  34201. /**
  34202. * @cfg {String} msg
  34203. * The message to be displayed in the {@link Ext.MessageBox}.
  34204. * @removed 2.0.0 Please use {@link #message} instead.
  34205. */
  34206. /**
  34207. * @cfg {Object} prompt
  34208. * The configuration to be passed if you want an {@link Ext.field.Text} or {@link Ext.field.TextArea} field
  34209. * in your {@link Ext.MessageBox}.
  34210. *
  34211. * Pass an object with the property `multiLine` with a value of `true`, if you want the prompt to use a TextArea.
  34212. *
  34213. * Alternatively, you can just pass in an object which has an xtype/xclass of another component.
  34214. *
  34215. * prompt: {
  34216. * xtype: 'textareafield',
  34217. * value: 'test'
  34218. * }
  34219. *
  34220. * @accessor
  34221. */
  34222. prompt: null,
  34223. /**
  34224. * @private
  34225. */
  34226. modal: true,
  34227. /**
  34228. * @cfg
  34229. * @inheritdoc
  34230. */
  34231. layout: {
  34232. type: 'vbox',
  34233. pack: 'center'
  34234. }
  34235. },
  34236. statics: {
  34237. OK : {text: 'OK', itemId: 'ok', ui: 'action'},
  34238. YES : {text: 'Yes', itemId: 'yes', ui: 'action'},
  34239. NO : {text: 'No', itemId: 'no'},
  34240. CANCEL: {text: 'Cancel', itemId: 'cancel'},
  34241. INFO : Ext.baseCSSPrefix + 'msgbox-info',
  34242. WARNING : Ext.baseCSSPrefix + 'msgbox-warning',
  34243. QUESTION: Ext.baseCSSPrefix + 'msgbox-question',
  34244. ERROR : Ext.baseCSSPrefix + 'msgbox-error',
  34245. OKCANCEL: [
  34246. {text: 'Cancel', itemId: 'cancel'},
  34247. {text: 'OK', itemId: 'ok', ui : 'action'}
  34248. ],
  34249. YESNOCANCEL: [
  34250. {text: 'Cancel', itemId: 'cancel'},
  34251. {text: 'No', itemId: 'no'},
  34252. {text: 'Yes', itemId: 'yes', ui: 'action'}
  34253. ],
  34254. YESNO: [
  34255. {text: 'No', itemId: 'no'},
  34256. {text: 'Yes', itemId: 'yes', ui: 'action'}
  34257. ]
  34258. },
  34259. constructor: function(config) {
  34260. config = config || {};
  34261. if (config.hasOwnProperty('promptConfig')) {
  34262. //<debug warn>
  34263. Ext.Logger.deprecate("'promptConfig' config is deprecated, please use 'prompt' config instead", this);
  34264. //</debug>
  34265. Ext.applyIf(config, {
  34266. prompt: config.promptConfig
  34267. });
  34268. delete config.promptConfig;
  34269. }
  34270. if (config.hasOwnProperty('multiline') || config.hasOwnProperty('multiLine')) {
  34271. config.prompt = config.prompt || {};
  34272. Ext.applyIf(config.prompt, {
  34273. multiLine: config.multiline || config.multiLine
  34274. });
  34275. delete config.multiline;
  34276. delete config.multiLine;
  34277. }
  34278. this.defaultAllowedConfig = {};
  34279. var allowedConfigs = ['ui', 'showAnimation', 'hideAnimation', 'title', 'message', 'prompt', 'iconCls', 'buttons', 'defaultTextHeight'],
  34280. ln = allowedConfigs.length,
  34281. i, allowedConfig;
  34282. for (i = 0; i < ln; i++) {
  34283. allowedConfig = allowedConfigs[i];
  34284. this.defaultAllowedConfig[allowedConfig] = this.defaultConfig[allowedConfig];
  34285. }
  34286. this.callParent([config]);
  34287. },
  34288. /**
  34289. * Creates a new {@link Ext.Toolbar} instance using {@link Ext#factory}.
  34290. * @private
  34291. */
  34292. applyTitle: function(config) {
  34293. if (typeof config == "string") {
  34294. config = {
  34295. title: config
  34296. };
  34297. }
  34298. Ext.applyIf(config, {
  34299. docked: 'top',
  34300. minHeight: '1.3em',
  34301. cls : this.getBaseCls() + '-title'
  34302. });
  34303. return Ext.factory(config, Ext.Toolbar, this.getTitle());
  34304. },
  34305. /**
  34306. * Adds the new {@link Ext.Toolbar} instance into this container.
  34307. * @private
  34308. */
  34309. updateTitle: function(newTitle) {
  34310. if (newTitle) {
  34311. this.add(newTitle);
  34312. }
  34313. },
  34314. /**
  34315. * Adds the new {@link Ext.Toolbar} instance into this container.
  34316. * @private
  34317. */
  34318. updateButtons: function(newButtons) {
  34319. var me = this;
  34320. if (newButtons) {
  34321. if (me.buttonsToolbar) {
  34322. me.buttonsToolbar.removeAll();
  34323. me.buttonsToolbar.setItems(newButtons);
  34324. } else {
  34325. me.buttonsToolbar = Ext.create('Ext.Toolbar', {
  34326. docked : 'bottom',
  34327. defaultType: 'button',
  34328. layout : {
  34329. type: 'hbox',
  34330. pack: 'center'
  34331. },
  34332. ui : me.getUi(),
  34333. cls : me.getBaseCls() + '-buttons',
  34334. items : newButtons
  34335. });
  34336. me.add(me.buttonsToolbar);
  34337. }
  34338. }
  34339. },
  34340. /**
  34341. * @private
  34342. */
  34343. applyMessage: function(config) {
  34344. config = {
  34345. html : config,
  34346. cls : this.getBaseCls() + '-text'
  34347. };
  34348. return Ext.factory(config, Ext.Component, this._message);
  34349. },
  34350. /**
  34351. * @private
  34352. */
  34353. updateMessage: function(newMessage) {
  34354. if (newMessage) {
  34355. this.add(newMessage);
  34356. }
  34357. },
  34358. getMessage: function() {
  34359. if (this._message) {
  34360. return this._message.getHtml();
  34361. }
  34362. return null;
  34363. },
  34364. /**
  34365. * @private
  34366. */
  34367. applyIconCls: function(config) {
  34368. config = {
  34369. xtype : 'component',
  34370. docked: 'left',
  34371. width : 40,
  34372. height: 40,
  34373. baseCls: Ext.baseCSSPrefix + 'icon',
  34374. hidden: (config) ? false : true,
  34375. cls: config
  34376. };
  34377. return Ext.factory(config, Ext.Component, this._iconCls);
  34378. },
  34379. /**
  34380. * @private
  34381. */
  34382. updateIconCls: function(newIconCls, oldIconCls) {
  34383. var me = this;
  34384. //ensure the title and button elements are added first
  34385. this.getTitle();
  34386. this.getButtons();
  34387. if (newIconCls) {
  34388. this.add(newIconCls);
  34389. } else {
  34390. this.remove(oldIconCls);
  34391. }
  34392. },
  34393. getIconCls: function() {
  34394. var icon = this._iconCls,
  34395. iconCls;
  34396. if (icon) {
  34397. iconCls = icon.getCls();
  34398. return (iconCls) ? iconCls[0] : null;
  34399. }
  34400. return null;
  34401. },
  34402. /**
  34403. * @private
  34404. */
  34405. applyPrompt: function(prompt) {
  34406. if (prompt) {
  34407. var config = {
  34408. label: false
  34409. };
  34410. if (Ext.isObject(prompt)) {
  34411. Ext.apply(config, prompt);
  34412. }
  34413. if (config.multiLine) {
  34414. config.height = Ext.isNumber(config.multiLine) ? parseFloat(config.multiLine) : this.getDefaultTextHeight();
  34415. return Ext.factory(config, Ext.field.TextArea, this.getPrompt());
  34416. } else {
  34417. return Ext.factory(config, Ext.field.Text, this.getPrompt());
  34418. }
  34419. }
  34420. return prompt;
  34421. },
  34422. /**
  34423. * @private
  34424. */
  34425. updatePrompt: function(newPrompt, oldPrompt) {
  34426. if (newPrompt) {
  34427. this.add(newPrompt);
  34428. }
  34429. if (oldPrompt) {
  34430. this.remove(oldPrompt);
  34431. }
  34432. },
  34433. // @private
  34434. // pass `fn` config to show method instead
  34435. onClick: function(button) {
  34436. if (button) {
  34437. var config = button.config.userConfig || {},
  34438. initialConfig = button.getInitialConfig(),
  34439. prompt = this.getPrompt();
  34440. if (typeof config.fn == 'function') {
  34441. this.on({
  34442. hiddenchange: function() {
  34443. config.fn.call(
  34444. config.scope || null,
  34445. initialConfig.itemId || initialConfig.text,
  34446. prompt ? prompt.getValue() : null,
  34447. config
  34448. );
  34449. },
  34450. single: true,
  34451. scope: this
  34452. });
  34453. }
  34454. if (config.input) {
  34455. config.input.dom.blur();
  34456. }
  34457. }
  34458. this.hide();
  34459. },
  34460. /**
  34461. * Displays the {@link Ext.MessageBox} with a specified configuration. All
  34462. * display functions (e.g. {@link #method-prompt}, {@link #alert}, {@link #confirm})
  34463. * on MessageBox call this function internally, although those calls
  34464. * are basic shortcuts and do not support all of the config options allowed here.
  34465. *
  34466. * Example usage:
  34467. *
  34468. * @example
  34469. * Ext.Msg.show({
  34470. * title: 'Address',
  34471. * message: 'Please enter your address:',
  34472. * width: 300,
  34473. * buttons: Ext.MessageBox.OKCANCEL,
  34474. * multiLine: true,
  34475. * prompt : { maxlength : 180, autocapitalize : true },
  34476. * fn: function(buttonId) {
  34477. * alert('You pressed the "' + buttonId + '" button.');
  34478. * }
  34479. * });
  34480. *
  34481. * @param {Object} config An object with the following config options:
  34482. *
  34483. * @param {Object/Array} [config.buttons=false]
  34484. * A button config object or Array of the same(e.g., `Ext.MessageBox.OKCANCEL` or `{text:'Foo', itemId:'cancel'}`),
  34485. * or false to not show any buttons.
  34486. *
  34487. * @param {String} config.cls
  34488. * A custom CSS class to apply to the message box's container element.
  34489. *
  34490. * @param {Function} config.fn
  34491. * A callback function which is called when the dialog is dismissed by clicking on the configured buttons.
  34492. *
  34493. * @param {String} config.fn.buttonId The `itemId` of the button pressed, one of: 'ok', 'yes', 'no', 'cancel'.
  34494. * @param {String} config.fn.value Value of the input field if either `prompt` or `multiline` option is `true`.
  34495. * @param {Object} config.fn.opt The config object passed to show.
  34496. *
  34497. * @param {Number} [config.width=auto]
  34498. * A fixed width for the MessageBox.
  34499. *
  34500. * @param {Number} [config.height=auto]
  34501. * A fixed height for the MessageBox.
  34502. *
  34503. * @param {Object} config.scope
  34504. * The scope of the callback function
  34505. *
  34506. * @param {String} config.icon
  34507. * A CSS class that provides a background image to be used as the body icon for the dialog
  34508. * (e.g. Ext.MessageBox.WARNING or 'custom-class').
  34509. *
  34510. * @param {Boolean} [config.modal=true]
  34511. * `false` to allow user interaction with the page while the message box is displayed.
  34512. *
  34513. * @param {String} [config.message=&#160;]
  34514. * A string that will replace the existing message box body text.
  34515. * Defaults to the XHTML-compliant non-breaking space character `&#160;`.
  34516. *
  34517. * @param {Number} [config.defaultTextHeight=75]
  34518. * The default height in pixels of the message box's multiline textarea if displayed.
  34519. *
  34520. * @param {Boolean} [config.prompt=false]
  34521. * `true` to prompt the user to enter single-line text. Please view the {@link Ext.MessageBox#method-prompt} documentation in {@link Ext.MessageBox}.
  34522. * for more information.
  34523. *
  34524. * @param {Boolean} [config.multiline=false]
  34525. * `true` to prompt the user to enter multi-line text.
  34526. *
  34527. * @param {String} config.title
  34528. * The title text.
  34529. *
  34530. * @param {String} config.value
  34531. * The string value to set into the active textbox element if displayed.
  34532. *
  34533. * @return {Ext.MessageBox} this
  34534. */
  34535. show: function(initialConfig) {
  34536. //if it has not been added to a container, add it to the Viewport.
  34537. if (!this.getParent() && Ext.Viewport) {
  34538. Ext.Viewport.add(this);
  34539. }
  34540. if (!initialConfig) {
  34541. return this.callParent();
  34542. }
  34543. var config = Ext.Object.merge({}, {
  34544. value: ''
  34545. }, initialConfig);
  34546. var buttons = initialConfig.buttons || Ext.MessageBox.OK || [],
  34547. buttonBarItems = [],
  34548. userConfig = initialConfig;
  34549. Ext.each(buttons, function(buttonConfig) {
  34550. if (!buttonConfig) {
  34551. return;
  34552. }
  34553. buttonBarItems.push(Ext.apply({
  34554. userConfig: userConfig,
  34555. scope : this,
  34556. handler : 'onClick'
  34557. }, buttonConfig));
  34558. }, this);
  34559. config.buttons = buttonBarItems;
  34560. if (config.promptConfig) {
  34561. //<debug warn>
  34562. Ext.Logger.deprecate("'promptConfig' config is deprecated, please use 'prompt' config instead", this);
  34563. //</debug>
  34564. }
  34565. config.prompt = (config.promptConfig || config.prompt) || null;
  34566. if (config.multiLine) {
  34567. config.prompt = config.prompt || {};
  34568. config.prompt.multiLine = config.multiLine;
  34569. delete config.multiLine;
  34570. }
  34571. config = Ext.merge({}, this.defaultAllowedConfig, config);
  34572. this.setConfig(config);
  34573. var prompt = this.getPrompt();
  34574. if (prompt) {
  34575. prompt.setValue(initialConfig.value || '');
  34576. }
  34577. this.callParent();
  34578. return this;
  34579. },
  34580. /**
  34581. * Displays a standard read-only message box with an OK button (comparable to the basic JavaScript alert prompt). If
  34582. * a callback function is passed it will be called after the user clicks the button, and the `itemId` of the button
  34583. * that was clicked will be passed as the only parameter to the callback.
  34584. *
  34585. * @param {String} title The title bar text.
  34586. * @param {String} message The message box body text.
  34587. * @param {Function} fn A callback function which is called when the dialog is dismissed by clicking on the configured buttons.
  34588. * @param {String} fn.buttonId The `itemId` of the button pressed, one of: 'ok', 'yes', 'no', 'cancel'.
  34589. * @param {String} fn.value Value of the input field if either `prompt` or `multiLine` option is `true`.
  34590. * @param {Object} fn.opt The config object passed to show.
  34591. * @param {Object} scope The scope (`this` reference) in which the callback is executed.
  34592. * Defaults to: the browser window
  34593. *
  34594. * @return {Ext.MessageBox} this
  34595. */
  34596. alert: function(title, message, fn, scope) {
  34597. return this.show({
  34598. title: title || null,
  34599. message: message || null,
  34600. buttons: Ext.MessageBox.OK,
  34601. promptConfig: false,
  34602. fn: function() {
  34603. if (fn) {
  34604. fn.apply(scope, arguments);
  34605. }
  34606. },
  34607. scope: scope
  34608. });
  34609. },
  34610. /**
  34611. * Displays a confirmation message box with Yes and No buttons (comparable to JavaScript's confirm). If a callback
  34612. * function is passed it will be called after the user clicks either button, and the id of the button that was
  34613. * clicked will be passed as the only parameter to the callback (could also be the top-right close button).
  34614. *
  34615. * @param {String} title The title bar text.
  34616. * @param {String} message The message box body text.
  34617. * @param {Function} fn A callback function which is called when the dialog is dismissed by clicking on the configured buttons.
  34618. * @param {String} fn.buttonId The `itemId` of the button pressed, one of: 'ok', 'yes', 'no', 'cancel'.
  34619. * @param {String} fn.value Value of the input field if either `prompt` or `multiLine` option is `true`.
  34620. * @param {Object} fn.opt The config object passed to show.
  34621. * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
  34622. *
  34623. * Defaults to: the browser window
  34624. *
  34625. * @return {Ext.MessageBox} this
  34626. */
  34627. confirm: function(title, message, fn, scope) {
  34628. return this.show({
  34629. title : title || null,
  34630. message : message || null,
  34631. buttons : Ext.MessageBox.YESNO,
  34632. promptConfig: false,
  34633. scope : scope,
  34634. fn: function() {
  34635. if (fn) {
  34636. fn.apply(scope, arguments);
  34637. }
  34638. }
  34639. });
  34640. },
  34641. /**
  34642. * Displays a message box with OK and Cancel buttons prompting the user to enter some text (comparable to
  34643. * JavaScript's prompt). The prompt can be a single-line or multi-line textbox. If a callback function is passed it
  34644. * will be called after the user clicks either button, and the id of the button that was clicked (could also be the
  34645. * top-right close button) and the text that was entered will be passed as the two parameters to the callback.
  34646. *
  34647. * Example usage:
  34648. *
  34649. * @example
  34650. * Ext.Msg.prompt(
  34651. * 'Welcome!',
  34652. * 'What\'s your name going to be today?',
  34653. * function (buttonId, value) {
  34654. * console.log(value);
  34655. * },
  34656. * null,
  34657. * false,
  34658. * null,
  34659. * {
  34660. * autoCapitalize: true,
  34661. * placeHolder: 'First-name please...'
  34662. * }
  34663. * );
  34664. *
  34665. * @param {String} title The title bar text.
  34666. * @param {String} message The message box body text.
  34667. * @param {Function} fn A callback function which is called when the dialog is dismissed by clicking on the configured buttons.
  34668. * @param {String} fn.buttonId The `itemId` of the button pressed, one of: 'ok', 'yes', 'no', 'cancel'.
  34669. * @param {String} fn.value Value of the input field if either `prompt` or `multiLine` option is `true`.
  34670. * @param {Object} fn.opt The config object passed to show.
  34671. * @param {Object} scope The scope (`this` reference) in which the callback is executed.
  34672. *
  34673. * Defaults to: the browser window.
  34674. *
  34675. * @param {Boolean/Number} [multiLine=false] `true` to create a multiline textbox using the `defaultTextHeight` property,
  34676. * or the height in pixels to create the textbox.
  34677. *
  34678. * @param {String} [value] Default value of the text input element.
  34679. *
  34680. * @param {Object} [prompt=true]
  34681. * The configuration for the prompt. See the {@link Ext.MessageBox#cfg-prompt prompt} documentation in {@link Ext.MessageBox}
  34682. * for more information.
  34683. *
  34684. * @return {Ext.MessageBox} this
  34685. */
  34686. prompt: function(title, message, fn, scope, multiLine, value, prompt) {
  34687. return this.show({
  34688. title : title || null,
  34689. message : message || null,
  34690. buttons : Ext.MessageBox.OKCANCEL,
  34691. scope : scope,
  34692. prompt : prompt || true,
  34693. multiLine: multiLine,
  34694. value : value,
  34695. fn: function() {
  34696. if (fn) {
  34697. fn.apply(scope, arguments);
  34698. }
  34699. }
  34700. });
  34701. }
  34702. }, function(MessageBox) {
  34703. Ext.onSetup(function() {
  34704. /**
  34705. * @class Ext.Msg
  34706. * @extends Ext.MessageBox
  34707. * @singleton
  34708. *
  34709. * A global shared singleton instance of the {@link Ext.MessageBox} class.
  34710. *
  34711. * Allows for simple creation of various different alerts and notifications.
  34712. *
  34713. * To change any configurations on this singleton instance, you must change the
  34714. * `defaultAllowedConfig` object. For example to remove all animations on `Msg`:
  34715. *
  34716. * Ext.Msg.defaultAllowedConfig.showAnimation = false;
  34717. * Ext.Msg.defaultAllowedConfig.hideAnimation = false;
  34718. *
  34719. * ## Examples
  34720. *
  34721. * ### Alert
  34722. * Use the {@link #alert} method to show a basic alert:
  34723. *
  34724. * @example preview
  34725. * Ext.Msg.alert('Title', 'The quick brown fox jumped over the lazy dog.', Ext.emptyFn);
  34726. *
  34727. * ### Prompt
  34728. * Use the {@link #method-prompt} method to show an alert which has a textfield:
  34729. *
  34730. * @example preview
  34731. * Ext.Msg.prompt('Name', 'Please enter your name:', function(text) {
  34732. * // process text value and close...
  34733. * });
  34734. *
  34735. * ### Confirm
  34736. * Use the {@link #confirm} method to show a confirmation alert (shows yes and no buttons).
  34737. *
  34738. * @example preview
  34739. * Ext.Msg.confirm("Confirmation", "Are you sure you want to do that?", Ext.emptyFn);
  34740. */
  34741. Ext.Msg = new MessageBox;
  34742. });
  34743. });
  34744. /**
  34745. * SegmentedButton is a container for a group of {@link Ext.Button}s. Generally a SegmentedButton would be
  34746. * a child of a {@link Ext.Toolbar} and would be used to switch between different views.
  34747. *
  34748. * ## Example usage:
  34749. *
  34750. * @example
  34751. * var segmentedButton = Ext.create('Ext.SegmentedButton', {
  34752. * allowMultiple: true,
  34753. * items: [
  34754. * {
  34755. * text: 'Option 1'
  34756. * },
  34757. * {
  34758. * text: 'Option 2',
  34759. * pressed: true
  34760. * },
  34761. * {
  34762. * text: 'Option 3'
  34763. * }
  34764. * ],
  34765. * listeners: {
  34766. * toggle: function(container, button, pressed){
  34767. * alert("User toggled the '" + button.getText() + "' button: " + (pressed ? 'on' : 'off'));
  34768. * }
  34769. * }
  34770. * });
  34771. * Ext.Viewport.add({ xtype: 'container', padding: 10, items: [segmentedButton] });
  34772. */
  34773. Ext.define('Ext.SegmentedButton', {
  34774. extend: 'Ext.Container',
  34775. xtype : 'segmentedbutton',
  34776. requires: ['Ext.Button'],
  34777. config: {
  34778. /**
  34779. * @cfg
  34780. * @inheritdoc
  34781. */
  34782. baseCls: Ext.baseCSSPrefix + 'segmentedbutton',
  34783. /**
  34784. * @cfg {String} pressedCls
  34785. * CSS class when a button is in pressed state.
  34786. * @accessor
  34787. */
  34788. pressedCls: Ext.baseCSSPrefix + 'button-pressed',
  34789. /**
  34790. * @cfg {Boolean} allowMultiple
  34791. * Allow multiple pressed buttons.
  34792. * @accessor
  34793. */
  34794. allowMultiple: false,
  34795. /**
  34796. * @cfg {Boolean} allowDepress
  34797. * Allow toggling the pressed state of each button.
  34798. * Defaults to `true` when {@link #allowMultiple} is `true`.
  34799. * @accessor
  34800. */
  34801. allowDepress: false,
  34802. /**
  34803. * @cfg {Boolean} allowToggle Allow child buttons to be pressed when tapped on. Set to `false` to allow tapping but not toggling of the buttons.
  34804. * @accessor
  34805. */
  34806. allowToggle: true,
  34807. /**
  34808. * @cfg {Array} pressedButtons
  34809. * The pressed buttons for this segmented button.
  34810. *
  34811. * You can remove all pressed buttons by calling {@link #setPressedButtons} with an empty array.
  34812. * @accessor
  34813. */
  34814. pressedButtons: [],
  34815. /**
  34816. * @cfg
  34817. * @inheritdoc
  34818. */
  34819. layout: {
  34820. type : 'hbox',
  34821. align: 'stretch'
  34822. },
  34823. /**
  34824. * @cfg
  34825. * @inheritdoc
  34826. */
  34827. defaultType: 'button'
  34828. },
  34829. /**
  34830. * @event toggle
  34831. * Fires when any child button's pressed state has changed.
  34832. * @param {Ext.SegmentedButton} this
  34833. * @param {Ext.Button} button The toggled button.
  34834. * @param {Boolean} isPressed Boolean to indicate if the button was pressed or not.
  34835. */
  34836. initialize: function() {
  34837. var me = this;
  34838. me.callParent();
  34839. me.on({
  34840. delegate: '> button',
  34841. scope : me,
  34842. tap: 'onButtonRelease'
  34843. });
  34844. me.onAfter({
  34845. delegate: '> button',
  34846. scope : me,
  34847. hiddenchange: 'onButtonHiddenChange'
  34848. });
  34849. },
  34850. updateAllowMultiple: function(allowMultiple) {
  34851. if (!this.initialized && !this.getInitialConfig().hasOwnProperty('allowDepress') && allowMultiple) {
  34852. this.setAllowDepress(true);
  34853. }
  34854. },
  34855. /**
  34856. * We override `initItems` so we can check for the pressed config.
  34857. */
  34858. applyItems: function() {
  34859. var me = this,
  34860. pressedButtons = [],
  34861. ln, i, item, items;
  34862. //call the parent first so the items get converted into a MixedCollection
  34863. me.callParent(arguments);
  34864. items = this.getItems();
  34865. ln = items.length;
  34866. for (i = 0; i < ln; i++) {
  34867. item = items.items[i];
  34868. if (item.getInitialConfig('pressed')) {
  34869. pressedButtons.push(items.items[i]);
  34870. }
  34871. }
  34872. me.updateFirstAndLastCls(items);
  34873. me.setPressedButtons(pressedButtons);
  34874. },
  34875. /**
  34876. * Button sets a timeout of 10ms to remove the {@link #pressedCls} on the release event.
  34877. * We don't want this to happen, so lets return `false` and cancel the event.
  34878. * @private
  34879. */
  34880. onButtonRelease: function(button) {
  34881. if (!this.getAllowToggle()) {
  34882. return;
  34883. }
  34884. var me = this,
  34885. pressedButtons = me.getPressedButtons() || [],
  34886. buttons = [],
  34887. alreadyPressed;
  34888. if (!me.getDisabled() && !button.getDisabled()) {
  34889. //if we allow for multiple pressed buttons, use the existing pressed buttons
  34890. if (me.getAllowMultiple()) {
  34891. buttons = pressedButtons.concat(buttons);
  34892. }
  34893. alreadyPressed = (buttons.indexOf(button) !== -1) || (pressedButtons.indexOf(button) !== -1);
  34894. //if we allow for depressing buttons, and the new pressed button is currently pressed, remove it
  34895. if (alreadyPressed && me.getAllowDepress()) {
  34896. Ext.Array.remove(buttons, button);
  34897. } else if (!alreadyPressed || !me.getAllowDepress()) {
  34898. buttons.push(button);
  34899. }
  34900. me.setPressedButtons(buttons);
  34901. }
  34902. },
  34903. onItemAdd: function() {
  34904. this.callParent(arguments);
  34905. this.updateFirstAndLastCls(this.getItems());
  34906. },
  34907. onItemRemove: function() {
  34908. this.callParent(arguments);
  34909. this.updateFirstAndLastCls(this.getItems());
  34910. },
  34911. // @private
  34912. onButtonHiddenChange: function() {
  34913. this.updateFirstAndLastCls(this.getItems());
  34914. },
  34915. // @private
  34916. updateFirstAndLastCls: function(items) {
  34917. var ln = items.length,
  34918. basePrefix = Ext.baseCSSPrefix,
  34919. firstCls = basePrefix + 'first',
  34920. lastCls = basePrefix + 'last',
  34921. item, i;
  34922. //remove all existing classes
  34923. for (i = 0; i < ln; i++) {
  34924. item = items.items[i];
  34925. item.removeCls(firstCls);
  34926. item.removeCls(lastCls);
  34927. }
  34928. //add a first cls to the first non-hidden button
  34929. for (i = 0; i < ln; i++) {
  34930. item = items.items[i];
  34931. if (!item.isHidden()) {
  34932. item.addCls(firstCls);
  34933. break;
  34934. }
  34935. }
  34936. //add a last cls to the last non-hidden button
  34937. for (i = ln - 1; i >= 0; i--) {
  34938. item = items.items[i];
  34939. if (!item.isHidden()) {
  34940. item.addCls(lastCls);
  34941. break;
  34942. }
  34943. }
  34944. },
  34945. /**
  34946. * @private
  34947. */
  34948. applyPressedButtons: function(newButtons) {
  34949. var me = this,
  34950. array = [],
  34951. button, ln, i;
  34952. if (me.getAllowToggle()) {
  34953. if (Ext.isArray(newButtons)) {
  34954. ln = newButtons.length;
  34955. for (i = 0; i< ln; i++) {
  34956. button = me.getComponent(newButtons[i]);
  34957. if (button && array.indexOf(button) === -1) {
  34958. array.push(button);
  34959. }
  34960. }
  34961. } else {
  34962. button = me.getComponent(newButtons);
  34963. if (button && array.indexOf(button) === -1) {
  34964. array.push(button);
  34965. }
  34966. }
  34967. }
  34968. return array;
  34969. },
  34970. /**
  34971. * Updates the pressed buttons.
  34972. * @private
  34973. */
  34974. updatePressedButtons: function(newButtons, oldButtons) {
  34975. var me = this,
  34976. items = me.getItems(),
  34977. pressedCls = me.getPressedCls(),
  34978. events = [],
  34979. item, button, ln, i, e;
  34980. //loop through existing items and remove the pressed cls from them
  34981. ln = items.length;
  34982. if (oldButtons && oldButtons.length) {
  34983. for (i = 0; i < ln; i++) {
  34984. item = items.items[i];
  34985. if (oldButtons.indexOf(item) != -1 && newButtons.indexOf(item) == -1) {
  34986. item.removeCls([pressedCls, item.getPressedCls()]);
  34987. events.push({
  34988. item: item,
  34989. toggle: false
  34990. });
  34991. }
  34992. }
  34993. }
  34994. //loop through the new pressed buttons and add the pressed cls to them
  34995. ln = newButtons.length;
  34996. for (i = 0; i < ln; i++) {
  34997. button = newButtons[i];
  34998. if (!oldButtons || oldButtons.indexOf(button) == -1) {
  34999. button.addCls(pressedCls);
  35000. events.push({
  35001. item: button,
  35002. toggle: true
  35003. });
  35004. }
  35005. }
  35006. //loop through each of the events and fire them after a delay
  35007. ln = events.length;
  35008. if (ln && oldButtons !== undefined) {
  35009. Ext.defer(function() {
  35010. for (i = 0; i < ln; i++) {
  35011. e = events[i];
  35012. me.fireEvent('toggle', me, e.item, e.toggle);
  35013. }
  35014. }, 50);
  35015. }
  35016. },
  35017. /**
  35018. * Returns `true` if a specified {@link Ext.Button} is pressed.
  35019. * @param {Ext.Button} button The button to check if pressed.
  35020. * @return {Boolean} pressed
  35021. */
  35022. isPressed: function(button) {
  35023. var pressedButtons = this.getPressedButtons();
  35024. return pressedButtons.indexOf(button) != -1;
  35025. },
  35026. /**
  35027. * @private
  35028. */
  35029. doSetDisabled: function(disabled) {
  35030. var me = this;
  35031. me.items.each(function(item) {
  35032. item.setDisabled(disabled);
  35033. }, me);
  35034. me.callParent(arguments);
  35035. }
  35036. }, function() {
  35037. });
  35038. /**
  35039. * A mixin which allows a data component to be sorted
  35040. * @ignore
  35041. */
  35042. Ext.define('Ext.Sortable', {
  35043. mixins: {
  35044. observable: 'Ext.mixin.Observable'
  35045. },
  35046. requires: ['Ext.util.Draggable'],
  35047. config: {
  35048. /**
  35049. * @cfg
  35050. * @inheritdoc
  35051. */
  35052. baseCls: Ext.baseCSSPrefix + 'sortable',
  35053. /**
  35054. * @cfg {Number} delay
  35055. * How many milliseconds a user must hold the draggable before starting a
  35056. * drag operation.
  35057. * @private
  35058. * @accessor
  35059. */
  35060. delay: 0
  35061. },
  35062. /**
  35063. * @cfg {String} direction
  35064. * Possible values: 'vertical', 'horizontal'.
  35065. */
  35066. direction: 'vertical',
  35067. /**
  35068. * @cfg {String} cancelSelector
  35069. * A simple CSS selector that represents elements within the draggable
  35070. * that should NOT initiate a drag.
  35071. */
  35072. cancelSelector: null,
  35073. // not yet implemented
  35074. //indicator: true,
  35075. //proxy: true,
  35076. //tolerance: null,
  35077. /**
  35078. * @cfg {HTMLElement/Boolean} constrain
  35079. * An Element to constrain the Sortable dragging to.
  35080. * If `true` is specified, the dragging will be constrained to the element
  35081. * of the sortable.
  35082. */
  35083. constrain: window,
  35084. /**
  35085. * @cfg {String} group
  35086. * Draggable and Droppable objects can participate in a group which are
  35087. * capable of interacting.
  35088. */
  35089. group: 'base',
  35090. /**
  35091. * @cfg {Boolean} revert
  35092. * This should NOT be changed.
  35093. * @private
  35094. */
  35095. revert: true,
  35096. /**
  35097. * @cfg {String} itemSelector
  35098. * A simple CSS selector that represents individual items within the Sortable.
  35099. */
  35100. itemSelector: null,
  35101. /**
  35102. * @cfg {String} handleSelector
  35103. * A simple CSS selector to indicate what is the handle to drag the Sortable.
  35104. */
  35105. handleSelector: null,
  35106. /**
  35107. * @cfg {Boolean} disabled
  35108. * Passing in `true` will disable this Sortable.
  35109. */
  35110. disabled: false,
  35111. // Properties
  35112. /**
  35113. * Read-only property that indicates whether a Sortable is currently sorting.
  35114. * @type Boolean
  35115. * @private
  35116. * @readonly
  35117. */
  35118. sorting: false,
  35119. /**
  35120. * Read-only value representing whether the Draggable can be moved vertically.
  35121. * This is automatically calculated by Draggable by the direction configuration.
  35122. * @type Boolean
  35123. * @private
  35124. * @readonly
  35125. */
  35126. vertical: false,
  35127. /**
  35128. * Creates new Sortable.
  35129. * @param {Mixed} el
  35130. * @param {Object} config
  35131. */
  35132. constructor : function(el, config) {
  35133. config = config || {};
  35134. Ext.apply(this, config);
  35135. this.addEvents(
  35136. /**
  35137. * @event sortstart
  35138. * @param {Ext.Sortable} this
  35139. * @param {Ext.event.Event} e
  35140. */
  35141. 'sortstart',
  35142. /**
  35143. * @event sortend
  35144. * @param {Ext.Sortable} this
  35145. * @param {Ext.event.Event} e
  35146. */
  35147. 'sortend',
  35148. /**
  35149. * @event sortchange
  35150. * @param {Ext.Sortable} this
  35151. * @param {Ext.Element} el The Element being dragged.
  35152. * @param {Number} index The index of the element after the sort change.
  35153. */
  35154. 'sortchange'
  35155. // not yet implemented.
  35156. // 'sortupdate',
  35157. // 'sortreceive',
  35158. // 'sortremove',
  35159. // 'sortenter',
  35160. // 'sortleave',
  35161. // 'sortactivate',
  35162. // 'sortdeactivate'
  35163. );
  35164. this.el = Ext.get(el);
  35165. this.callParent();
  35166. this.mixins.observable.constructor.call(this);
  35167. if (this.direction == 'horizontal') {
  35168. this.horizontal = true;
  35169. }
  35170. else if (this.direction == 'vertical') {
  35171. this.vertical = true;
  35172. }
  35173. else {
  35174. this.horizontal = this.vertical = true;
  35175. }
  35176. this.el.addCls(this.baseCls);
  35177. this.startEventName = (this.getDelay() > 0) ? 'taphold' : 'tapstart';
  35178. if (!this.disabled) {
  35179. this.enable();
  35180. }
  35181. },
  35182. // @private
  35183. onStart : function(e, t) {
  35184. if (this.cancelSelector && e.getTarget(this.cancelSelector)) {
  35185. return;
  35186. }
  35187. if (this.handleSelector && !e.getTarget(this.handleSelector)) {
  35188. return;
  35189. }
  35190. if (!this.sorting) {
  35191. this.onSortStart(e, t);
  35192. }
  35193. },
  35194. // @private
  35195. onSortStart : function(e, t) {
  35196. this.sorting = true;
  35197. var draggable = Ext.create('Ext.util.Draggable', t, {
  35198. threshold: 0,
  35199. revert: this.revert,
  35200. direction: this.direction,
  35201. constrain: this.constrain === true ? this.el : this.constrain,
  35202. animationDuration: 100
  35203. });
  35204. draggable.on({
  35205. drag: this.onDrag,
  35206. dragend: this.onDragEnd,
  35207. scope: this
  35208. });
  35209. this.dragEl = t;
  35210. this.calculateBoxes();
  35211. if (!draggable.dragging) {
  35212. draggable.onStart(e);
  35213. }
  35214. this.fireEvent('sortstart', this, e);
  35215. },
  35216. // @private
  35217. calculateBoxes : function() {
  35218. this.items = [];
  35219. var els = this.el.select(this.itemSelector, false),
  35220. ln = els.length, i, item, el, box;
  35221. for (i = 0; i < ln; i++) {
  35222. el = els[i];
  35223. if (el != this.dragEl) {
  35224. item = Ext.fly(el).getPageBox(true);
  35225. item.el = el;
  35226. this.items.push(item);
  35227. }
  35228. }
  35229. },
  35230. // @private
  35231. onDrag : function(draggable, e) {
  35232. var items = this.items,
  35233. ln = items.length,
  35234. region = draggable.region,
  35235. sortChange = false,
  35236. i, intersect, overlap, item;
  35237. for (i = 0; i < ln; i++) {
  35238. item = items[i];
  35239. intersect = region.intersect(item);
  35240. if (intersect) {
  35241. if (this.vertical && Math.abs(intersect.top - intersect.bottom) > (region.bottom - region.top) / 2) {
  35242. if (region.bottom > item.top && item.top > region.top) {
  35243. draggable.el.insertAfter(item.el);
  35244. }
  35245. else {
  35246. draggable.el.insertBefore(item.el);
  35247. }
  35248. sortChange = true;
  35249. }
  35250. else if (this.horizontal && Math.abs(intersect.left - intersect.right) > (region.right - region.left) / 2) {
  35251. if (region.right > item.left && item.left > region.left) {
  35252. draggable.el.insertAfter(item.el);
  35253. }
  35254. else {
  35255. draggable.el.insertBefore(item.el);
  35256. }
  35257. sortChange = true;
  35258. }
  35259. if (sortChange) {
  35260. // We reset the draggable (initializes all the new start values)
  35261. draggable.reset();
  35262. // Move the draggable to its current location (since the transform is now
  35263. // different)
  35264. draggable.moveTo(region.left, region.top);
  35265. // Finally lets recalculate all the items boxes
  35266. this.calculateBoxes();
  35267. this.fireEvent('sortchange', this, draggable.el, this.el.select(this.itemSelector, false).indexOf(draggable.el.dom));
  35268. return;
  35269. }
  35270. }
  35271. }
  35272. },
  35273. // @private
  35274. onDragEnd : function(draggable, e) {
  35275. draggable.destroy();
  35276. this.sorting = false;
  35277. this.fireEvent('sortend', this, draggable, e);
  35278. },
  35279. /**
  35280. * Enables sorting for this Sortable.
  35281. * This method is invoked immediately after construction of a Sortable unless
  35282. * the disabled configuration is set to `true`.
  35283. */
  35284. enable : function() {
  35285. this.el.on(this.startEventName, this.onStart, this, {delegate: this.itemSelector, holdThreshold: this.getDelay()});
  35286. this.disabled = false;
  35287. },
  35288. /**
  35289. * Disables sorting for this Sortable.
  35290. */
  35291. disable : function() {
  35292. this.el.un(this.startEventName, this.onStart, this);
  35293. this.disabled = true;
  35294. },
  35295. /**
  35296. * Method to determine whether this Sortable is currently disabled.
  35297. * @return {Boolean} The disabled state of this Sortable.
  35298. */
  35299. isDisabled: function() {
  35300. return this.disabled;
  35301. },
  35302. /**
  35303. * Method to determine whether this Sortable is currently sorting.
  35304. * @return {Boolean} The sorting state of this Sortable.
  35305. */
  35306. isSorting : function() {
  35307. return this.sorting;
  35308. },
  35309. /**
  35310. * Method to determine whether this Sortable is currently disabled.
  35311. * @return {Boolean} The disabled state of this Sortable.
  35312. */
  35313. isVertical : function() {
  35314. return this.vertical;
  35315. },
  35316. /**
  35317. * Method to determine whether this Sortable is currently sorting.
  35318. * @return {Boolean} The sorting state of this Sortable.
  35319. */
  35320. isHorizontal : function() {
  35321. return this.horizontal;
  35322. }
  35323. });
  35324. (function() {
  35325. var lastTime = 0,
  35326. vendors = ['ms', 'moz', 'webkit', 'o'],
  35327. ln = vendors.length,
  35328. i, vendor;
  35329. for (i = 0; i < ln && !window.requestAnimationFrame; ++i) {
  35330. vendor = vendors[i];
  35331. window.requestAnimationFrame = window[vendor + 'RequestAnimationFrame'];
  35332. window.cancelAnimationFrame = window[vendor + 'CancelAnimationFrame'] || window[vendor + 'CancelRequestAnimationFrame'];
  35333. }
  35334. if (!window.requestAnimationFrame) {
  35335. window.requestAnimationFrame = function(callback, element) {
  35336. var currTime = new Date().getTime(),
  35337. timeToCall = Math.max(0, 16 - (currTime - lastTime)),
  35338. id = window.setTimeout(function() {
  35339. callback(currTime + timeToCall);
  35340. }, timeToCall);
  35341. lastTime = currTime + timeToCall;
  35342. return id;
  35343. };
  35344. }
  35345. if (!window.cancelAnimationFrame) {
  35346. window.cancelAnimationFrame = function(id) {
  35347. clearTimeout(id);
  35348. };
  35349. }
  35350. }());
  35351. /**
  35352. * @private
  35353. * Handle batch read / write of DOMs, currently used in SizeMonitor + PaintMonitor
  35354. */
  35355. Ext.define('Ext.TaskQueue', {
  35356. singleton: true,
  35357. pending: false,
  35358. mode: true,
  35359. constructor: function() {
  35360. this.readQueue = [];
  35361. this.writeQueue = [];
  35362. this.run = Ext.Function.bind(this.run, this);
  35363. },
  35364. requestRead: function(fn, scope, args) {
  35365. this.request(true);
  35366. this.readQueue.push(arguments);
  35367. },
  35368. requestWrite: function(fn, scope, args) {
  35369. this.request(false);
  35370. this.writeQueue.push(arguments);
  35371. },
  35372. request: function(mode) {
  35373. if (!this.pending) {
  35374. this.pending = true;
  35375. this.mode = mode;
  35376. requestAnimationFrame(this.run);
  35377. }
  35378. },
  35379. run: function() {
  35380. this.pending = false;
  35381. var readQueue = this.readQueue,
  35382. writeQueue = this.writeQueue,
  35383. request = null,
  35384. queue;
  35385. if (this.mode) {
  35386. queue = readQueue;
  35387. if (writeQueue.length > 0) {
  35388. request = false;
  35389. }
  35390. }
  35391. else {
  35392. queue = writeQueue;
  35393. if (readQueue.length > 0) {
  35394. request = true;
  35395. }
  35396. }
  35397. var tasks = queue.slice(),
  35398. i, ln, task, fn, scope;
  35399. queue.length = 0;
  35400. for (i = 0, ln = tasks.length; i < ln; i++) {
  35401. task = tasks[i];
  35402. fn = task[0];
  35403. scope = task[1];
  35404. if (typeof fn == 'string') {
  35405. fn = scope[fn];
  35406. }
  35407. if (task.length > 2) {
  35408. fn.apply(scope, task[2]);
  35409. }
  35410. else {
  35411. fn.call(scope);
  35412. }
  35413. }
  35414. tasks.length = 0;
  35415. if (request !== null) {
  35416. this.request(request);
  35417. }
  35418. }
  35419. });
  35420. /**
  35421. * {@link Ext.TitleBar}'s are most commonly used as a docked item within an {@link Ext.Container}.
  35422. *
  35423. * The main difference between a {@link Ext.TitleBar} and an {@link Ext.Toolbar} is that
  35424. * the {@link #title} configuration is **always** centered horizontally in a {@link Ext.TitleBar} between
  35425. * any items aligned left or right.
  35426. *
  35427. * You can also give items of a {@link Ext.TitleBar} an `align` configuration of `left` or `right`
  35428. * which will dock them to the `left` or `right` of the bar.
  35429. *
  35430. * ## Examples
  35431. *
  35432. * @example preview
  35433. * Ext.Viewport.add({
  35434. * xtype: 'titlebar',
  35435. * docked: 'top',
  35436. * title: 'Navigation',
  35437. * items: [
  35438. * {
  35439. * iconCls: 'add',
  35440. * iconMask: true,
  35441. * align: 'left'
  35442. * },
  35443. * {
  35444. * iconCls: 'home',
  35445. * iconMask: true,
  35446. * align: 'right'
  35447. * }
  35448. * ]
  35449. * });
  35450. *
  35451. * Ext.Viewport.setStyleHtmlContent(true);
  35452. * Ext.Viewport.setHtml('This shows the title being centered and buttons using align <i>left</i> and <i>right</i>.');
  35453. *
  35454. * <br />
  35455. *
  35456. * @example preview
  35457. * Ext.Viewport.add({
  35458. * xtype: 'titlebar',
  35459. * docked: 'top',
  35460. * title: 'Navigation',
  35461. * items: [
  35462. * {
  35463. * align: 'left',
  35464. * text: 'This button has a super long title'
  35465. * },
  35466. * {
  35467. * iconCls: 'home',
  35468. * iconMask: true,
  35469. * align: 'right'
  35470. * }
  35471. * ]
  35472. * });
  35473. *
  35474. * Ext.Viewport.setStyleHtmlContent(true);
  35475. * Ext.Viewport.setHtml('This shows how the title is automatically moved to the right when one of the aligned buttons is very wide.');
  35476. *
  35477. * <br />
  35478. *
  35479. * @example preview
  35480. * Ext.Viewport.add({
  35481. * xtype: 'titlebar',
  35482. * docked: 'top',
  35483. * title: 'A very long title',
  35484. * items: [
  35485. * {
  35486. * align: 'left',
  35487. * text: 'This button has a super long title'
  35488. * },
  35489. * {
  35490. * align: 'right',
  35491. * text: 'Another button'
  35492. * }
  35493. * ]
  35494. * });
  35495. *
  35496. * Ext.Viewport.setStyleHtmlContent(true);
  35497. * Ext.Viewport.setHtml('This shows how the title and buttons will automatically adjust their size when the width of the items are too wide..');
  35498. *
  35499. * The {@link #defaultType} of Toolbar's is {@link Ext.Button button}.
  35500. */
  35501. Ext.define('Ext.TitleBar', {
  35502. extend: 'Ext.Container',
  35503. xtype: 'titlebar',
  35504. requires: [
  35505. 'Ext.Button',
  35506. 'Ext.Title',
  35507. 'Ext.Spacer'
  35508. ],
  35509. // @private
  35510. isToolbar: true,
  35511. config: {
  35512. /**
  35513. * @cfg
  35514. * @inheritdoc
  35515. */
  35516. baseCls: Ext.baseCSSPrefix + 'toolbar',
  35517. /**
  35518. * @cfg
  35519. * @inheritdoc
  35520. */
  35521. cls: Ext.baseCSSPrefix + 'navigation-bar',
  35522. /**
  35523. * @cfg {String} ui
  35524. * Style options for Toolbar. Either 'light' or 'dark'.
  35525. * @accessor
  35526. */
  35527. ui: 'dark',
  35528. /**
  35529. * @cfg {String} title
  35530. * The title of the toolbar.
  35531. * @accessor
  35532. */
  35533. title: null,
  35534. /**
  35535. * @cfg {String} defaultType
  35536. * The default xtype to create.
  35537. * @accessor
  35538. */
  35539. defaultType: 'button',
  35540. height: '2.6em',
  35541. /**
  35542. * @cfg
  35543. * @hide
  35544. */
  35545. layout: {
  35546. type: 'hbox'
  35547. },
  35548. /**
  35549. * @cfg {Array/Object} items The child items to add to this TitleBar. The {@link #defaultType} of
  35550. * a TitleBar is {@link Ext.Button}, so you do not need to specify an `xtype` if you are adding
  35551. * buttons.
  35552. *
  35553. * You can also give items a `align` configuration which will align the item to the `left` or `right` of
  35554. * the TitleBar.
  35555. * @accessor
  35556. */
  35557. items: []
  35558. },
  35559. /**
  35560. * The max button width in this toolbar
  35561. * @private
  35562. */
  35563. maxButtonWidth: '40%',
  35564. constructor: function() {
  35565. this.refreshTitlePosition = Ext.Function.createThrottled(this.refreshTitlePosition, 50, this);
  35566. this.callParent(arguments);
  35567. },
  35568. beforeInitialize: function() {
  35569. this.applyItems = this.applyInitialItems;
  35570. },
  35571. initialize: function() {
  35572. delete this.applyItems;
  35573. this.add(this.initialItems);
  35574. delete this.initialItems;
  35575. this.on({
  35576. painted: 'refreshTitlePosition',
  35577. single: true
  35578. });
  35579. },
  35580. applyInitialItems: function(items) {
  35581. var me = this,
  35582. defaults = me.getDefaults() || {};
  35583. me.initialItems = items;
  35584. me.leftBox = me.add({
  35585. xtype: 'container',
  35586. style: 'position: relative',
  35587. layout: {
  35588. type: 'hbox',
  35589. align: 'center'
  35590. },
  35591. listeners: {
  35592. resize: 'refreshTitlePosition',
  35593. scope: me
  35594. }
  35595. });
  35596. me.spacer = me.add({
  35597. xtype: 'component',
  35598. style: 'position: relative',
  35599. flex: 1,
  35600. listeners: {
  35601. resize: 'refreshTitlePosition',
  35602. scope: me
  35603. }
  35604. });
  35605. me.rightBox = me.add({
  35606. xtype: 'container',
  35607. style: 'position: relative',
  35608. layout: {
  35609. type: 'hbox',
  35610. align: 'center'
  35611. },
  35612. listeners: {
  35613. resize: 'refreshTitlePosition',
  35614. scope: me
  35615. }
  35616. });
  35617. me.titleComponent = me.add({
  35618. xtype: 'title',
  35619. hidden: defaults.hidden,
  35620. centered: true
  35621. });
  35622. me.doAdd = me.doBoxAdd;
  35623. me.remove = me.doBoxRemove;
  35624. me.doInsert = me.doBoxInsert;
  35625. },
  35626. doBoxAdd: function(item) {
  35627. if (item.config.align == 'right') {
  35628. this.rightBox.add(item);
  35629. }
  35630. else {
  35631. this.leftBox.add(item);
  35632. }
  35633. },
  35634. doBoxRemove: function(item) {
  35635. if (item.config.align == 'right') {
  35636. this.rightBox.remove(item);
  35637. }
  35638. else {
  35639. this.leftBox.remove(item);
  35640. }
  35641. },
  35642. doBoxInsert: function(index, item) {
  35643. if (item.config.align == 'right') {
  35644. this.rightBox.add(item);
  35645. }
  35646. else {
  35647. this.leftBox.add(item);
  35648. }
  35649. },
  35650. getMaxButtonWidth: function() {
  35651. var value = this.maxButtonWidth;
  35652. //check if it is a percentage
  35653. if (Ext.isString(this.maxButtonWidth)) {
  35654. value = parseInt(value.replace('%', ''), 10);
  35655. value = Math.round((this.element.getWidth() / 100) * value);
  35656. }
  35657. return value;
  35658. },
  35659. refreshTitlePosition: function() {
  35660. var titleElement = this.titleComponent.renderElement;
  35661. titleElement.setWidth(null);
  35662. titleElement.setLeft(null);
  35663. //set the min/max width of the left button
  35664. var leftBox = this.leftBox,
  35665. leftButton = leftBox.down('button'),
  35666. singleButton = leftBox.getItems().getCount() == 1,
  35667. leftBoxWidth, maxButtonWidth;
  35668. if (leftButton && singleButton) {
  35669. if (leftButton.getWidth() == null) {
  35670. leftButton.renderElement.setWidth('auto');
  35671. }
  35672. leftBoxWidth = leftBox.renderElement.getWidth();
  35673. maxButtonWidth = this.getMaxButtonWidth();
  35674. if (leftBoxWidth > maxButtonWidth) {
  35675. leftButton.renderElement.setWidth(maxButtonWidth);
  35676. }
  35677. }
  35678. var spacerBox = this.spacer.renderElement.getPageBox(),
  35679. titleBox = titleElement.getPageBox(),
  35680. widthDiff = titleBox.width - spacerBox.width,
  35681. titleLeft = titleBox.left,
  35682. titleRight = titleBox.right,
  35683. halfWidthDiff, leftDiff, rightDiff;
  35684. if (widthDiff > 0) {
  35685. titleElement.setWidth(spacerBox.width);
  35686. halfWidthDiff = widthDiff / 2;
  35687. titleLeft += halfWidthDiff;
  35688. titleRight -= halfWidthDiff;
  35689. }
  35690. leftDiff = spacerBox.left - titleLeft;
  35691. rightDiff = titleRight - spacerBox.right;
  35692. if (leftDiff > 0) {
  35693. titleElement.setLeft(leftDiff);
  35694. }
  35695. else if (rightDiff > 0) {
  35696. titleElement.setLeft(-rightDiff);
  35697. }
  35698. titleElement.repaint();
  35699. },
  35700. // @private
  35701. updateTitle: function(newTitle) {
  35702. this.titleComponent.setTitle(newTitle);
  35703. if (this.isPainted()) {
  35704. this.refreshTitlePosition();
  35705. }
  35706. }
  35707. });
  35708. /**
  35709. * @aside example video
  35710. * Provides a simple Container for HTML5 Video.
  35711. *
  35712. * ## Notes
  35713. *
  35714. * - There are quite a few issues with the `<video>` tag on Android devices. On Android 2+, the video will
  35715. * appear and play on first attempt, but any attempt afterwards will not work.
  35716. *
  35717. * ## Useful Properties
  35718. *
  35719. * - {@link #url}
  35720. * - {@link #autoPause}
  35721. * - {@link #autoResume}
  35722. *
  35723. * ## Useful Methods
  35724. *
  35725. * - {@link #method-pause}
  35726. * - {@link #method-play}
  35727. * - {@link #toggle}
  35728. *
  35729. * ## Example
  35730. *
  35731. * var panel = Ext.create('Ext.Panel', {
  35732. * fullscreen: true,
  35733. * items: [
  35734. * {
  35735. * xtype : 'video',
  35736. * x : 600,
  35737. * y : 300,
  35738. * width : 175,
  35739. * height : 98,
  35740. * url : "porsche911.mov",
  35741. * posterUrl: 'porsche.png'
  35742. * }
  35743. * ]
  35744. * });
  35745. */
  35746. Ext.define('Ext.Video', {
  35747. extend: 'Ext.Media',
  35748. xtype: 'video',
  35749. config: {
  35750. /**
  35751. * @cfg {String/Array} urls
  35752. * Location of the video to play. This should be in H.264 format and in a .mov file format.
  35753. * @accessor
  35754. */
  35755. /**
  35756. * @cfg {String} posterUrl
  35757. * Location of a poster image to be shown before showing the video.
  35758. * @accessor
  35759. */
  35760. posterUrl: null,
  35761. /**
  35762. * @cfg
  35763. * @inheritdoc
  35764. */
  35765. cls: Ext.baseCSSPrefix + 'video'
  35766. },
  35767. template: [{
  35768. /**
  35769. * @property {Ext.dom.Element} ghost
  35770. * @private
  35771. */
  35772. reference: 'ghost',
  35773. classList: [Ext.baseCSSPrefix + 'video-ghost']
  35774. }, {
  35775. tag: 'video',
  35776. reference: 'media',
  35777. classList: [Ext.baseCSSPrefix + 'media']
  35778. }],
  35779. initialize: function() {
  35780. var me = this;
  35781. me.callParent();
  35782. me.media.hide();
  35783. me.onBefore({
  35784. erased: 'onErased',
  35785. scope: me
  35786. });
  35787. me.ghost.on({
  35788. tap: 'onGhostTap',
  35789. scope: me
  35790. });
  35791. me.media.on({
  35792. pause: 'onPause',
  35793. scope: me
  35794. });
  35795. if (Ext.os.is.Android4 || Ext.os.is.iPad) {
  35796. this.isInlineVideo = true;
  35797. }
  35798. },
  35799. applyUrl: function(url) {
  35800. return [].concat(url);
  35801. },
  35802. updateUrl: function(newUrl) {
  35803. var me = this,
  35804. media = me.media,
  35805. newLn = newUrl.length,
  35806. existingSources = media.query('source'),
  35807. oldLn = existingSources.length,
  35808. i;
  35809. for (i = 0; i < oldLn; i++) {
  35810. Ext.fly(existingSources[i]).destroy();
  35811. }
  35812. for (i = 0; i < newLn; i++) {
  35813. media.appendChild(Ext.Element.create({
  35814. tag: 'source',
  35815. src: newUrl[i]
  35816. }));
  35817. }
  35818. if (me.isPlaying()) {
  35819. me.play();
  35820. }
  35821. },
  35822. onErased: function() {
  35823. this.pause();
  35824. this.media.setTop(-2000);
  35825. this.ghost.show();
  35826. },
  35827. /**
  35828. * @private
  35829. * Called when the {@link #ghost} element is tapped.
  35830. */
  35831. onGhostTap: function() {
  35832. var me = this,
  35833. media = this.media,
  35834. ghost = this.ghost;
  35835. media.show();
  35836. if (Ext.os.is.Android2) {
  35837. setTimeout(function() {
  35838. me.play();
  35839. setTimeout(function() {
  35840. media.hide();
  35841. }, 10);
  35842. }, 10);
  35843. } else {
  35844. // Browsers which support native video tag display only, move the media down so
  35845. // we can control the Viewport
  35846. ghost.hide();
  35847. me.play();
  35848. }
  35849. },
  35850. /**
  35851. * @private
  35852. * native video tag display only, move the media down so we can control the Viewport
  35853. */
  35854. onPause: function() {
  35855. this.callParent(arguments);
  35856. if (!this.isInlineVideo) {
  35857. this.media.setTop(-2000);
  35858. this.ghost.show();
  35859. }
  35860. },
  35861. /**
  35862. * @private
  35863. * native video tag display only, move the media down so we can control the Viewport
  35864. */
  35865. onPlay: function() {
  35866. this.callParent(arguments);
  35867. this.media.setTop(0);
  35868. },
  35869. /**
  35870. * Updates the URL to the poster, even if it is rendered.
  35871. * @param {Object} newUrl
  35872. */
  35873. updatePosterUrl: function(newUrl) {
  35874. var ghost = this.ghost;
  35875. if (ghost) {
  35876. ghost.setStyle('background-image', 'url(' + newUrl + ')');
  35877. }
  35878. }
  35879. });
  35880. /**
  35881. * @author Ed Spencer
  35882. * @private
  35883. *
  35884. * Represents a single action as {@link Ext.app.Application#dispatch dispatched} by an Application. This is typically
  35885. * generated as a result of a url change being matched by a Route, triggering Application's dispatch function.
  35886. *
  35887. * This is a private class and its functionality and existence may change in the future. Use at your own risk.
  35888. *
  35889. */
  35890. Ext.define('Ext.app.Action', {
  35891. config: {
  35892. /**
  35893. * @cfg {Object} scope The scope in which the {@link #action} should be called.
  35894. */
  35895. scope: null,
  35896. /**
  35897. * @cfg {Ext.app.Application} application The Application that this Action is bound to.
  35898. */
  35899. application: null,
  35900. /**
  35901. * @cfg {Ext.app.Controller} controller The {@link Ext.app.Controller controller} whose {@link #action} should
  35902. * be called.
  35903. */
  35904. controller: null,
  35905. /**
  35906. * @cfg {String} action The name of the action on the {@link #controller} that should be called.
  35907. */
  35908. action: null,
  35909. /**
  35910. * @cfg {Array} args The set of arguments that will be passed to the controller's {@link #action}.
  35911. */
  35912. args: [],
  35913. /**
  35914. * @cfg {String} url The url that was decoded into the controller/action/args in this Action.
  35915. */
  35916. url: undefined,
  35917. data: {},
  35918. title: null,
  35919. /**
  35920. * @cfg {Array} beforeFilters The (optional) set of functions to call before the {@link #action} is called.
  35921. * This is usually handled directly by the Controller or Application when an Ext.app.Action instance is
  35922. * created, but is alterable before {@link #resume} is called.
  35923. * @accessor
  35924. */
  35925. beforeFilters: [],
  35926. /**
  35927. * @private
  35928. * Keeps track of which before filter is currently being executed by {@link #resume}
  35929. */
  35930. currentFilterIndex: -1
  35931. },
  35932. constructor: function(config) {
  35933. this.initConfig(config);
  35934. this.getUrl();
  35935. },
  35936. /**
  35937. * Starts execution of this Action by calling each of the {@link #beforeFilters} in turn (if any are specified),
  35938. * before calling the Controller {@link #action}. Same as calling {@link #resume}.
  35939. */
  35940. execute: function() {
  35941. this.resume();
  35942. },
  35943. /**
  35944. * Resumes the execution of this Action (or starts it if it had not been started already). This iterates over all
  35945. * of the configured {@link #beforeFilters} and calls them. Each before filter is called with this Action as the
  35946. * sole argument, and is expected to call `action.resume()` in order to allow the next filter to be called, or if
  35947. * this is the final filter, the original {@link Ext.app.Controller Controller} function.
  35948. */
  35949. resume: function() {
  35950. var index = this.getCurrentFilterIndex() + 1,
  35951. filters = this.getBeforeFilters(),
  35952. controller = this.getController(),
  35953. nextFilter = filters[index];
  35954. if (nextFilter) {
  35955. this.setCurrentFilterIndex(index);
  35956. nextFilter.call(controller, this);
  35957. } else {
  35958. controller[this.getAction()].apply(controller, this.getArgs());
  35959. }
  35960. },
  35961. /**
  35962. * @private
  35963. */
  35964. applyUrl: function(url) {
  35965. if (url === null || url === undefined) {
  35966. url = this.urlEncode();
  35967. }
  35968. return url;
  35969. },
  35970. /**
  35971. * @private
  35972. * If the controller config is a string, swap it for a reference to the actual controller instance.
  35973. * @param {String} controller The controller name.
  35974. */
  35975. applyController: function(controller) {
  35976. var app = this.getApplication(),
  35977. profile = app.getCurrentProfile();
  35978. if (Ext.isString(controller)) {
  35979. controller = app.getController(controller, profile ? profile.getNamespace() : null);
  35980. }
  35981. return controller;
  35982. },
  35983. /**
  35984. * @private
  35985. */
  35986. urlEncode: function() {
  35987. var controller = this.getController(),
  35988. splits;
  35989. if (controller instanceof Ext.app.Controller) {
  35990. splits = controller.$className.split('.');
  35991. controller = splits[splits.length - 1];
  35992. }
  35993. return controller + "/" + this.getAction();
  35994. }
  35995. });
  35996. /**
  35997. * @author Ed Spencer
  35998. *
  35999. * @aside guide controllers
  36000. * @aside guide apps_intro
  36001. * @aside guide history_support
  36002. * @aside video mvc-part-1
  36003. * @aside video mvc-part-2
  36004. *
  36005. * Controllers are responsible for responding to events that occur within your app. If your app contains a Logout
  36006. * {@link Ext.Button button} that your user can tap on, a Controller would listen to the Button's tap event and take
  36007. * the appropriate action. It allows the View classes to handle the display of data and the Model classes to handle the
  36008. * loading and saving of data - the Controller is the glue that binds them together.
  36009. *
  36010. * ## Relation to Ext.app.Application
  36011. *
  36012. * Controllers exist within the context of an {@link Ext.app.Application Application}. An Application usually consists
  36013. * of a number of Controllers, each of which handle a specific part of the app. For example, an Application that
  36014. * handles the orders for an online shopping site might have controllers for Orders, Customers and Products.
  36015. *
  36016. * All of the Controllers that an Application uses are specified in the Application's
  36017. * {@link Ext.app.Application#controllers} config. The Application automatically instantiates each Controller and keeps
  36018. * references to each, so it is unusual to need to instantiate Controllers directly. By convention each Controller is
  36019. * named after the thing (usually the Model) that it deals with primarily, usually in the plural - for example if your
  36020. * app is called 'MyApp' and you have a Controller that manages Products, convention is to create a
  36021. * MyApp.controller.Products class in the file app/controller/Products.js.
  36022. *
  36023. * ## Refs and Control
  36024. *
  36025. * The centerpiece of Controllers is the twin configurations {@link #refs} and {@link #cfg-control}. These are used to
  36026. * easily gain references to Components inside your app and to take action on them based on events that they fire.
  36027. * Let's look at {@link #refs} first:
  36028. *
  36029. * ### Refs
  36030. *
  36031. * Refs leverage the powerful {@link Ext.ComponentQuery ComponentQuery} syntax to easily locate Components on your
  36032. * page. We can define as many refs as we like for each Controller, for example here we define a ref called 'nav' that
  36033. * finds a Component on the page with the ID 'mainNav'. We then use that ref in the addLogoutButton beneath it:
  36034. *
  36035. * Ext.define('MyApp.controller.Main', {
  36036. * extend: 'Ext.app.Controller',
  36037. *
  36038. * config: {
  36039. * refs: {
  36040. * nav: '#mainNav'
  36041. * }
  36042. * },
  36043. *
  36044. * addLogoutButton: function() {
  36045. * this.getNav().add({
  36046. * text: 'Logout'
  36047. * });
  36048. * }
  36049. * });
  36050. *
  36051. * Usually, a ref is just a key/value pair - the key ('nav' in this case) is the name of the reference that will be
  36052. * generated, the value ('#mainNav' in this case) is the {@link Ext.ComponentQuery ComponentQuery} selector that will
  36053. * be used to find the Component.
  36054. *
  36055. * Underneath that, we have created a simple function called addLogoutButton which uses this ref via its generated
  36056. * 'getNav' function. These getter functions are generated based on the refs you define and always follow the same
  36057. * format - 'get' followed by the capitalized ref name. In this case we're treating the nav reference as though it's a
  36058. * {@link Ext.Toolbar Toolbar}, and adding a Logout button to it when our function is called. This ref would recognize
  36059. * a Toolbar like this:
  36060. *
  36061. * Ext.create('Ext.Toolbar', {
  36062. * id: 'mainNav',
  36063. *
  36064. * items: [
  36065. * {
  36066. * text: 'Some Button'
  36067. * }
  36068. * ]
  36069. * });
  36070. *
  36071. * Assuming this Toolbar has already been created by the time we run our 'addLogoutButton' function (we'll see how that
  36072. * is invoked later), it will get the second button added to it.
  36073. *
  36074. * ### Advanced Refs
  36075. *
  36076. * Refs can also be passed a couple of additional options, beyond name and selector. These are autoCreate and xtype,
  36077. * which are almost always used together:
  36078. *
  36079. * Ext.define('MyApp.controller.Main', {
  36080. * extend: 'Ext.app.Controller',
  36081. *
  36082. * config: {
  36083. * refs: {
  36084. * nav: '#mainNav',
  36085. *
  36086. * infoPanel: {
  36087. * selector: 'tabpanel panel[name=fish] infopanel',
  36088. * xtype: 'infopanel',
  36089. * autoCreate: true
  36090. * }
  36091. * }
  36092. * }
  36093. * });
  36094. *
  36095. * We've added a second ref to our Controller. Again the name is the key, 'infoPanel' in this case, but this time we've
  36096. * passed an object as the value instead. This time we've used a slightly more complex selector query - in this example
  36097. * imagine that your app contains a {@link Ext.tab.Panel tab panel} and that one of the items in the tab panel has been
  36098. * given the name 'fish'. Our selector matches any Component with the xtype 'infopanel' inside that tab panel item.
  36099. *
  36100. * The difference here is that if that infopanel does not exist already inside the 'fish' panel, it will be
  36101. * automatically created when you call this.getInfoPanel inside your Controller. The Controller is able to do this
  36102. * because we provided the xtype to instantiate with in the event that the selector did not return anything.
  36103. *
  36104. * ### Control
  36105. *
  36106. * The sister config to {@link #refs} is {@link #cfg-control}. {@link #cfg-control Control} is the means by which your listen
  36107. * to events fired by Components and have your Controller react in some way. Control accepts both ComponentQuery
  36108. * selectors and refs as its keys, and listener objects as values - for example:
  36109. *
  36110. * Ext.define('MyApp.controller.Main', {
  36111. * extend: 'Ext.app.Controller',
  36112. *
  36113. * config: {
  36114. * control: {
  36115. * loginButton: {
  36116. * tap: 'doLogin'
  36117. * },
  36118. * 'button[action=logout]': {
  36119. * tap: 'doLogout'
  36120. * }
  36121. * },
  36122. *
  36123. * refs: {
  36124. * loginButton: 'button[action=login]'
  36125. * }
  36126. * },
  36127. *
  36128. * doLogin: function() {
  36129. * //called whenever the Login button is tapped
  36130. * },
  36131. *
  36132. * doLogout: function() {
  36133. * //called whenever any Button with action=logout is tapped
  36134. * }
  36135. * });
  36136. *
  36137. * Here we have set up two control declarations - one for our loginButton ref and the other for any Button on the page
  36138. * that has been given the action 'logout'. For each declaration we passed in a single event handler - in each case
  36139. * listening for the 'tap' event, specifying the action that should be called when that Button fires the tap event.
  36140. * Note that we specified the 'doLogin' and 'doLogout' methods as strings inside the control block - this is important.
  36141. *
  36142. * You can listen to as many events as you like in each control declaration, and mix and match ComponentQuery selectors
  36143. * and refs as the keys.
  36144. *
  36145. * ## Routes
  36146. *
  36147. * As of Sencha Touch 2, Controllers can now directly specify which routes they are interested in. This enables us to
  36148. * provide history support within our app, as well as the ability to deeply link to any part of the application that we
  36149. * provide a route for.
  36150. *
  36151. * For example, let's say we have a Controller responsible for logging in and viewing user profiles, and want to make
  36152. * those screens accessible via urls. We could achieve that like this:
  36153. *
  36154. * Ext.define('MyApp.controller.Users', {
  36155. * extend: 'Ext.app.Controller',
  36156. *
  36157. * config: {
  36158. * routes: {
  36159. * 'login': 'showLogin',
  36160. * 'user/:id': 'showUserById'
  36161. * },
  36162. *
  36163. * refs: {
  36164. * main: '#mainTabPanel'
  36165. * }
  36166. * },
  36167. *
  36168. * //uses our 'main' ref above to add a loginpanel to our main TabPanel (note that
  36169. * //'loginpanel' is a custom xtype created for this application)
  36170. * showLogin: function() {
  36171. * this.getMain().add({
  36172. * xtype: 'loginpanel'
  36173. * });
  36174. * },
  36175. *
  36176. * //Loads the User then adds a 'userprofile' view to the main TabPanel
  36177. * showUserById: function(id) {
  36178. * MyApp.model.User.load(id, {
  36179. * scope: this,
  36180. * success: function(user) {
  36181. * this.getMain().add({
  36182. * xtype: 'userprofile',
  36183. * user: user
  36184. * });
  36185. * }
  36186. * });
  36187. * }
  36188. * });
  36189. *
  36190. * The routes we specified above simply map the contents of the browser address bar to a Controller function to call
  36191. * when that route is matched. The routes can be simple text like the login route, which matches against
  36192. * http://myapp.com/#login, or contain wildcards like the 'user/:id' route, which matches urls like
  36193. * http://myapp.com/#user/123. Whenever the address changes the Controller automatically calls the function specified.
  36194. *
  36195. * Note that in the showUserById function we had to first load the User instance. Whenever you use a route, the
  36196. * function that is called by that route is completely responsible for loading its data and restoring state. This is
  36197. * because your user could either send that url to another person or simply refresh the page, which we wipe clear any
  36198. * cached data you had already loaded. There is a more thorough discussion of restoring state with routes in the
  36199. * application architecture guides.
  36200. *
  36201. * ## Advanced Usage
  36202. *
  36203. * See [the Controllers guide](#!/guide/controllers) for advanced Controller usage including before filters
  36204. * and customizing for different devices.
  36205. */
  36206. Ext.define('Ext.app.Controller', {
  36207. mixins: {
  36208. observable: "Ext.mixin.Observable"
  36209. },
  36210. config: {
  36211. /**
  36212. * @cfg {Object} refs A collection of named {@link Ext.ComponentQuery ComponentQuery} selectors that makes it
  36213. * easy to get references to key Components on your page. Example usage:
  36214. *
  36215. * refs: {
  36216. * main: '#mainTabPanel',
  36217. * loginButton: '#loginWindow button[action=login]',
  36218. *
  36219. * infoPanel: {
  36220. * selector: 'infopanel',
  36221. * xtype: 'infopanel',
  36222. * autoCreate: true
  36223. * }
  36224. * }
  36225. *
  36226. * The first two are simple ComponentQuery selectors, the third (infoPanel) also passes in the autoCreate and
  36227. * xtype options, which will first run the ComponentQuery to see if a Component matching that selector exists
  36228. * on the page. If not, it will automatically create one using the xtype provided:
  36229. *
  36230. * someControllerFunction: function() {
  36231. * //if the info panel didn't exist before, calling its getter will instantiate
  36232. * //it automatically and return the new instance
  36233. * this.getInfoPanel().show();
  36234. * }
  36235. *
  36236. * @accessor
  36237. */
  36238. refs: {},
  36239. /**
  36240. * @cfg {Object} routes Provides a mapping of urls to Controller actions. Whenever the specified url is matched
  36241. * in the address bar, the specified Controller action is called. Example usage:
  36242. *
  36243. * routes: {
  36244. * 'login': 'showLogin',
  36245. * 'users/:id': 'showUserById'
  36246. * }
  36247. *
  36248. * The first route will match against http://myapp.com/#login and call the Controller's showLogin function. The
  36249. * second route contains a wildcard (':id') and will match all urls like http://myapp.com/#users/123, calling
  36250. * the showUserById function with the matched ID as the first argument.
  36251. *
  36252. * @accessor
  36253. */
  36254. routes: {},
  36255. /**
  36256. * @cfg {Object} control Provides a mapping of Controller functions that should be called whenever certain
  36257. * Component events are fired. The Components can be specified using {@link Ext.ComponentQuery ComponentQuery}
  36258. * selectors or {@link #refs}. Example usage:
  36259. *
  36260. * control: {
  36261. * 'button[action=logout]': {
  36262. * tap: 'doLogout'
  36263. * },
  36264. * main: {
  36265. * activeitemchange: 'doUpdate'
  36266. * }
  36267. * }
  36268. *
  36269. * The first item uses a ComponentQuery selector to run the Controller's doLogout function whenever any Button
  36270. * with action=logout is tapped on. The second calls the Controller's doUpdate function whenever the
  36271. * activeitemchange event is fired by the Component referenced by our 'main' ref. In this case main is a tab
  36272. * panel (see {@link #refs} for how to set that reference up).
  36273. *
  36274. * @accessor
  36275. */
  36276. control: {},
  36277. /**
  36278. * @cfg {Object} before Provides a mapping of Controller functions to filter functions that are run before them
  36279. * when dispatched to from a route. These are usually used to run pre-processing functions like authentication
  36280. * before a certain function is executed. They are only called when dispatching from a route. Example usage:
  36281. *
  36282. * Ext.define('MyApp.controller.Products', {
  36283. * config: {
  36284. * before: {
  36285. * editProduct: 'authenticate'
  36286. * },
  36287. *
  36288. * routes: {
  36289. * 'product/edit/:id': 'editProduct'
  36290. * }
  36291. * },
  36292. *
  36293. * //this is not directly because our before filter is called first
  36294. * editProduct: function() {
  36295. * //... performs the product editing logic
  36296. * },
  36297. *
  36298. * //this is run before editProduct
  36299. * authenticate: function(action) {
  36300. * MyApp.authenticate({
  36301. * success: function() {
  36302. * action.resume();
  36303. * },
  36304. * failure: function() {
  36305. * Ext.Msg.alert('Not Logged In', "You can't do that, you're not logged in");
  36306. * }
  36307. * });
  36308. * }
  36309. * });
  36310. *
  36311. * @accessor
  36312. */
  36313. before: {},
  36314. /**
  36315. * @cfg {Ext.app.Application} application The Application instance this Controller is attached to. This is
  36316. * automatically provided when using the MVC architecture so should rarely need to be set directly.
  36317. * @accessor
  36318. */
  36319. application: {},
  36320. /**
  36321. * @cfg {String[]} stores The set of stores to load for this Application. Each store is expected to
  36322. * exist inside the *app/store* directory and define a class following the convention
  36323. * AppName.store.StoreName. For example, in the code below, the *AppName.store.Users* class will be loaded.
  36324. * Note that we are able to specify either the full class name (as with *AppName.store.Groups*) or just the
  36325. * final part of the class name and leave Application to automatically prepend *AppName.store.'* to each:
  36326. *
  36327. * stores: [
  36328. * 'Users',
  36329. * 'AppName.store.Groups',
  36330. * 'SomeCustomNamespace.store.Orders'
  36331. * ]
  36332. * @accessor
  36333. */
  36334. stores: [],
  36335. /**
  36336. * @cfg {String[]} models The set of models to load for this Application. Each model is expected to exist inside the
  36337. * *app/model* directory and define a class following the convention AppName.model.ModelName. For example, in the
  36338. * code below, the classes *AppName.model.User*, *AppName.model.Group* and *AppName.model.Product* will be loaded.
  36339. * Note that we are able to specify either the full class name (as with *AppName.model.Product*) or just the
  36340. * final part of the class name and leave Application to automatically prepend *AppName.model.* to each:
  36341. *
  36342. * models: [
  36343. * 'User',
  36344. * 'Group',
  36345. * 'AppName.model.Product',
  36346. * 'SomeCustomNamespace.model.Order'
  36347. * ]
  36348. * @accessor
  36349. */
  36350. models: [],
  36351. /**
  36352. * @cfg {Array} views The set of views to load for this Application. Each view is expected to exist inside the
  36353. * *app/view* directory and define a class following the convention AppName.view.ViewName. For example, in the
  36354. * code below, the classes *AppName.view.Users*, *AppName.view.Groups* and *AppName.view.Products* will be loaded.
  36355. * Note that we are able to specify either the full class name (as with *AppName.view.Products*) or just the
  36356. * final part of the class name and leave Application to automatically prepend *AppName.view.* to each:
  36357. *
  36358. * views: [
  36359. * 'Users',
  36360. * 'Groups',
  36361. * 'AppName.view.Products',
  36362. * 'SomeCustomNamespace.view.Orders'
  36363. * ]
  36364. * @accessor
  36365. */
  36366. views: []
  36367. },
  36368. /**
  36369. * Constructs a new Controller instance
  36370. */
  36371. constructor: function(config) {
  36372. this.initConfig(config);
  36373. this.mixins.observable.constructor.call(this, config);
  36374. },
  36375. /**
  36376. * @cfg
  36377. * Called by the Controller's {@link #application} to initialize the Controller. This is always called before the
  36378. * {@link Ext.app.Application Application} launches, giving the Controller a chance to run any pre-launch logic.
  36379. * See also {@link #launch}, which is called after the {@link Ext.app.Application#launch Application's launch function}
  36380. */
  36381. init: Ext.emptyFn,
  36382. /**
  36383. * @cfg
  36384. * Called by the Controller's {@link #application} immediately after the Application's own
  36385. * {@link Ext.app.Application#launch launch function} has been called. This is usually a good place to run any
  36386. * logic that has to run after the app UI is initialized. See also {@link #init}, which is called before the
  36387. * {@link Ext.app.Application#launch Application's launch function}.
  36388. */
  36389. launch: Ext.emptyFn,
  36390. /**
  36391. * Convenient way to redirect to a new url. See {@link Ext.app.Application#redirectTo} for full usage information.
  36392. * @return {Object}
  36393. */
  36394. redirectTo: function(place) {
  36395. return this.getApplication().redirectTo(place);
  36396. },
  36397. /**
  36398. * @private
  36399. * Executes an Ext.app.Action by giving it the correct before filters and kicking off execution
  36400. */
  36401. execute: function(action, skipFilters) {
  36402. action.setBeforeFilters(this.getBefore()[action.getAction()]);
  36403. action.execute();
  36404. },
  36405. /**
  36406. * @private
  36407. * Massages the before filters into an array of function references for each controller action
  36408. */
  36409. applyBefore: function(before) {
  36410. var filters, name, length, i;
  36411. for (name in before) {
  36412. filters = Ext.Array.from(before[name]);
  36413. length = filters.length;
  36414. for (i = 0; i < length; i++) {
  36415. filters[i] = this[filters[i]];
  36416. }
  36417. before[name] = filters;
  36418. }
  36419. return before;
  36420. },
  36421. /**
  36422. * @private
  36423. */
  36424. applyControl: function(config) {
  36425. this.control(config, this);
  36426. return config;
  36427. },
  36428. /**
  36429. * @private
  36430. */
  36431. applyRefs: function(refs) {
  36432. //<debug>
  36433. if (Ext.isArray(refs)) {
  36434. Ext.Logger.deprecate("In Sencha Touch 2 the refs config accepts an object but you have passed it an array.");
  36435. }
  36436. //</debug>
  36437. this.ref(refs);
  36438. return refs;
  36439. },
  36440. /**
  36441. * @private
  36442. * Adds any routes specified in this Controller to the global Application router
  36443. */
  36444. applyRoutes: function(routes) {
  36445. var app = this instanceof Ext.app.Application ? this : this.getApplication(),
  36446. router = app.getRouter(),
  36447. route, url, config;
  36448. for (url in routes) {
  36449. route = routes[url];
  36450. config = {
  36451. controller: this.$className
  36452. };
  36453. if (Ext.isString(route)) {
  36454. config.action = route;
  36455. } else {
  36456. Ext.apply(config, route);
  36457. }
  36458. router.connect(url, config);
  36459. }
  36460. return routes;
  36461. },
  36462. /**
  36463. * @private
  36464. * As a convenience developers can locally qualify store names (e.g. 'MyStore' vs
  36465. * 'MyApp.store.MyStore'). This just makes sure everything ends up fully qualified
  36466. */
  36467. applyStores: function(stores) {
  36468. return this.getFullyQualified(stores, 'store');
  36469. },
  36470. /**
  36471. * @private
  36472. * As a convenience developers can locally qualify model names (e.g. 'MyModel' vs
  36473. * 'MyApp.model.MyModel'). This just makes sure everything ends up fully qualified
  36474. */
  36475. applyModels: function(models) {
  36476. return this.getFullyQualified(models, 'model');
  36477. },
  36478. /**
  36479. * @private
  36480. * As a convenience developers can locally qualify view names (e.g. 'MyView' vs
  36481. * 'MyApp.view.MyView'). This just makes sure everything ends up fully qualified
  36482. */
  36483. applyViews: function(views) {
  36484. return this.getFullyQualified(views, 'view');
  36485. },
  36486. /**
  36487. * @private
  36488. * Returns the fully qualified name for any class name variant. This is used to find the FQ name for the model,
  36489. * view, controller, store and profiles listed in a Controller or Application.
  36490. * @param {String[]} items The array of strings to get the FQ name for
  36491. * @param {String} namespace If the name happens to be an application class, add it to this namespace
  36492. * @return {String} The fully-qualified name of the class
  36493. */
  36494. getFullyQualified: function(items, namespace) {
  36495. var length = items.length,
  36496. appName = this.getApplication().getName(),
  36497. name, i;
  36498. for (i = 0; i < length; i++) {
  36499. name = items[i];
  36500. //we check name === appName to allow MyApp.profile.MyApp to exist
  36501. if (Ext.isString(name) && (Ext.Loader.getPrefix(name) === "" || name === appName)) {
  36502. items[i] = appName + '.' + namespace + '.' + name;
  36503. }
  36504. }
  36505. return items;
  36506. },
  36507. /**
  36508. * @private
  36509. */
  36510. control: function(selectors) {
  36511. this.getApplication().control(selectors, this);
  36512. },
  36513. /**
  36514. * @private
  36515. * 1.x-inspired ref implementation
  36516. */
  36517. ref: function(refs) {
  36518. var me = this,
  36519. refName, getterName, selector, info;
  36520. for (refName in refs) {
  36521. selector = refs[refName];
  36522. getterName = "get" + Ext.String.capitalize(refName);
  36523. if (!this[getterName]) {
  36524. if (Ext.isString(refs[refName])) {
  36525. info = {
  36526. ref: refName,
  36527. selector: selector
  36528. };
  36529. } else {
  36530. info = refs[refName];
  36531. }
  36532. this[getterName] = function(refName, info) {
  36533. var args = [refName, info];
  36534. return function() {
  36535. return me.getRef.apply(me, args.concat.apply(args, arguments));
  36536. };
  36537. }(refName, info);
  36538. }
  36539. this.references = this.references || [];
  36540. this.references.push(refName.toLowerCase());
  36541. }
  36542. },
  36543. /**
  36544. * @private
  36545. */
  36546. getRef: function(ref, info, config) {
  36547. this.refCache = this.refCache || {};
  36548. info = info || {};
  36549. config = config || {};
  36550. Ext.apply(info, config);
  36551. if (info.forceCreate) {
  36552. return Ext.ComponentManager.create(info, 'component');
  36553. }
  36554. var me = this,
  36555. cached = me.refCache[ref];
  36556. if (!cached) {
  36557. me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
  36558. if (!cached && info.autoCreate) {
  36559. me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
  36560. }
  36561. if (cached) {
  36562. cached.on('destroy', function() {
  36563. me.refCache[ref] = null;
  36564. });
  36565. }
  36566. }
  36567. return cached;
  36568. },
  36569. /**
  36570. * @private
  36571. */
  36572. hasRef: function(ref) {
  36573. return this.references && this.references.indexOf(ref.toLowerCase()) !== -1;
  36574. }
  36575. }, function() {
  36576. });
  36577. /**
  36578. * @author Ed Spencer
  36579. * @private
  36580. *
  36581. * Manages the stack of {@link Ext.app.Action} instances that have been decoded, pushes new urls into the browser's
  36582. * location object and listens for changes in url, firing the {@link #change} event when a change is detected.
  36583. *
  36584. * This is tied to an {@link Ext.app.Application Application} instance. The Application performs all of the
  36585. * interactions with the History object, no additional integration should be required.
  36586. */
  36587. Ext.define('Ext.app.History', {
  36588. mixins: ['Ext.mixin.Observable'],
  36589. /**
  36590. * @event change
  36591. * Fires when a change in browser url is detected
  36592. * @param {String} url The new url, after the hash (e.g. http://myapp.com/#someUrl returns 'someUrl')
  36593. */
  36594. config: {
  36595. /**
  36596. * @cfg {Array} actions The stack of {@link Ext.app.Action action} instances that have occurred so far
  36597. */
  36598. actions: [],
  36599. /**
  36600. * @cfg {Boolean} updateUrl `true` to automatically update the browser's url when {@link #add} is called.
  36601. */
  36602. updateUrl: true,
  36603. /**
  36604. * @cfg {String} token The current token as read from the browser's location object.
  36605. */
  36606. token: ''
  36607. },
  36608. constructor: function(config) {
  36609. if (Ext.feature.has.History) {
  36610. window.addEventListener('hashchange', Ext.bind(this.detectStateChange, this));
  36611. }
  36612. else {
  36613. this.setToken(window.location.hash.substr(1));
  36614. setInterval(Ext.bind(this.detectStateChange, this), 100);
  36615. }
  36616. this.initConfig(config);
  36617. },
  36618. /**
  36619. * Adds an {@link Ext.app.Action Action} to the stack, optionally updating the browser's url and firing the
  36620. * {@link #change} event.
  36621. * @param {Ext.app.Action} action The Action to add to the stack.
  36622. * @param {Boolean} silent Cancels the firing of the {@link #change} event if `true`.
  36623. */
  36624. add: function(action, silent) {
  36625. this.getActions().push(Ext.factory(action, Ext.app.Action));
  36626. var url = action.getUrl();
  36627. if (this.getUpdateUrl()) {
  36628. // history.pushState({}, action.getTitle(), "#" + action.getUrl());
  36629. this.setToken(url);
  36630. window.location.hash = url;
  36631. }
  36632. if (silent !== true) {
  36633. this.fireEvent('change', url);
  36634. }
  36635. this.setToken(url);
  36636. },
  36637. /**
  36638. * Navigate to the previous active action. This changes the page url.
  36639. */
  36640. back: function() {
  36641. var actions = this.getActions(),
  36642. previousAction = actions[actions.length - 2],
  36643. app = previousAction.getController().getApplication();
  36644. actions.pop();
  36645. app.redirectTo(previousAction.getUrl());
  36646. },
  36647. /**
  36648. * @private
  36649. */
  36650. applyToken: function(token) {
  36651. return token[0] == '#' ? token.substr(1) : token;
  36652. },
  36653. /**
  36654. * @private
  36655. */
  36656. detectStateChange: function() {
  36657. var newToken = this.applyToken(window.location.hash),
  36658. oldToken = this.getToken();
  36659. if (newToken != oldToken) {
  36660. this.onStateChange();
  36661. this.setToken(newToken);
  36662. }
  36663. },
  36664. /**
  36665. * @private
  36666. */
  36667. onStateChange: function() {
  36668. this.fireEvent('change', window.location.hash.substr(1));
  36669. }
  36670. });
  36671. /**
  36672. * @author Ed Spencer
  36673. *
  36674. * A Profile represents a range of devices that fall under a common category. For the vast majority of apps that use
  36675. * device profiles, the app defines a Phone profile and a Tablet profile. Doing this enables you to easily customize
  36676. * the experience for the different sized screens offered by those device types.
  36677. *
  36678. * Only one Profile can be active at a time, and each Profile defines a simple {@link #isActive} function that should
  36679. * return either true or false. The first Profile to return true from its isActive function is set as your Application's
  36680. * {@link Ext.app.Application#currentProfile current profile}.
  36681. *
  36682. * A Profile can define any number of {@link #models}, {@link #views}, {@link #controllers} and {@link #stores} which
  36683. * will be loaded if the Profile is activated. It can also define a {@link #launch} function that will be called after
  36684. * all of its dependencies have been loaded, just before the {@link Ext.app.Application#launch application launch}
  36685. * function is called.
  36686. *
  36687. * ## Sample Usage
  36688. *
  36689. * First you need to tell your Application about your Profile(s):
  36690. *
  36691. * Ext.application({
  36692. * name: 'MyApp',
  36693. * profiles: ['Phone', 'Tablet']
  36694. * });
  36695. *
  36696. * This will load app/profile/Phone.js and app/profile/Tablet.js. Here's how we might define the Phone profile:
  36697. *
  36698. * Ext.define('MyApp.profile.Phone', {
  36699. * extend: 'Ext.app.Profile',
  36700. *
  36701. * views: ['Main'],
  36702. *
  36703. * isActive: function() {
  36704. * return Ext.os.is.Phone;
  36705. * }
  36706. * });
  36707. *
  36708. * The isActive function returns true if we detect that we are running on a phone device. If that is the case the
  36709. * Application will set this Profile active and load the 'Main' view specified in the Profile's {@link #views} config.
  36710. *
  36711. * ## Class Specializations
  36712. *
  36713. * Because Profiles are specializations of an application, all of the models, views, controllers and stores defined
  36714. * in a Profile are expected to be namespaced under the name of the Profile. Here's an expanded form of the example
  36715. * above:
  36716. *
  36717. * Ext.define('MyApp.profile.Phone', {
  36718. * extend: 'Ext.app.Profile',
  36719. *
  36720. * views: ['Main'],
  36721. * controllers: ['Signup'],
  36722. * models: ['MyApp.model.Group'],
  36723. *
  36724. * isActive: function() {
  36725. * return Ext.os.is.Phone;
  36726. * }
  36727. * });
  36728. *
  36729. * In this case, the Profile is going to load *app/view/phone/Main.js*, *app/controller/phone/Signup.js* and
  36730. * *app/model/Group.js*. Notice that in each of the first two cases the name of the profile ('phone' in this case) was
  36731. * injected into the class names. In the third case we specified the full Model name (for Group) so the Profile name
  36732. * was not injected.
  36733. *
  36734. * For a fuller understanding of the ideas behind Profiles and how best to use them in your app, we suggest you read
  36735. * the [device profiles guide](#!/guide/profiles).
  36736. *
  36737. * @aside guide profiles
  36738. */
  36739. Ext.define('Ext.app.Profile', {
  36740. mixins: {
  36741. observable: "Ext.mixin.Observable"
  36742. },
  36743. config: {
  36744. /**
  36745. * @cfg {String} namespace The namespace that this Profile's classes can be found in. Defaults to the lowercased
  36746. * Profile {@link #name}, for example a Profile called MyApp.profile.Phone will by default have a 'phone'
  36747. * namespace, which means that this Profile's additional models, stores, views and controllers will be loaded
  36748. * from the MyApp.model.phone.*, MyApp.store.phone.*, MyApp.view.phone.* and MyApp.controller.phone.* namespaces
  36749. * respectively.
  36750. * @accessor
  36751. */
  36752. namespace: 'auto',
  36753. /**
  36754. * @cfg {String} name The name of this Profile. Defaults to the last section of the class name (e.g. a profile
  36755. * called MyApp.profile.Phone will default the name to 'Phone').
  36756. * @accessor
  36757. */
  36758. name: 'auto',
  36759. /**
  36760. * @cfg {Array} controllers Any additional {@link Ext.app.Application#controllers Controllers} to load for this
  36761. * profile. Note that each item here will be prepended with the Profile namespace when loaded. Example usage:
  36762. *
  36763. * controllers: [
  36764. * 'Users',
  36765. * 'MyApp.controller.Products'
  36766. * ]
  36767. *
  36768. * This will load *MyApp.controller.tablet.Users* and *MyApp.controller.Products*.
  36769. * @accessor
  36770. */
  36771. controllers: [],
  36772. /**
  36773. * @cfg {Array} models Any additional {@link Ext.app.Application#models Models} to load for this profile. Note
  36774. * that each item here will be prepended with the Profile namespace when loaded. Example usage:
  36775. *
  36776. * models: [
  36777. * 'Group',
  36778. * 'MyApp.model.User'
  36779. * ]
  36780. *
  36781. * This will load *MyApp.model.tablet.Group* and *MyApp.model.User*.
  36782. * @accessor
  36783. */
  36784. models: [],
  36785. /**
  36786. * @cfg {Array} views Any additional {@link Ext.app.Application#views views} to load for this profile. Note
  36787. * that each item here will be prepended with the Profile namespace when loaded. Example usage:
  36788. *
  36789. * views: [
  36790. * 'Main',
  36791. * 'MyApp.view.Login'
  36792. * ]
  36793. *
  36794. * This will load *MyApp.view.tablet.Main* and *MyApp.view.Login*.
  36795. * @accessor
  36796. */
  36797. views: [],
  36798. /**
  36799. * @cfg {Array} stores Any additional {@link Ext.app.Application#stores Stores} to load for this profile. Note
  36800. * that each item here will be prepended with the Profile namespace when loaded. Example usage:
  36801. *
  36802. * stores: [
  36803. * 'Users',
  36804. * 'MyApp.store.Products'
  36805. * ]
  36806. *
  36807. * This will load *MyApp.store.tablet.Users* and *MyApp.store.Products*.
  36808. * @accessor
  36809. */
  36810. stores: [],
  36811. /**
  36812. * @cfg {Ext.app.Application} application The {@link Ext.app.Application Application} instance that this
  36813. * Profile is bound to. This is set automatically.
  36814. * @accessor
  36815. * @readonly
  36816. */
  36817. application: null
  36818. },
  36819. /**
  36820. * Creates a new Profile instance
  36821. */
  36822. constructor: function(config) {
  36823. this.initConfig(config);
  36824. this.mixins.observable.constructor.apply(this, arguments);
  36825. },
  36826. /**
  36827. * Determines whether or not this Profile is active on the device isActive is executed on. Should return true if
  36828. * this profile is meant to be active on this device, false otherwise. Each Profile should implement this function
  36829. * (the default implementation just returns false).
  36830. * @return {Boolean} True if this Profile should be activated on the device it is running on, false otherwise
  36831. */
  36832. isActive: function() {
  36833. return false;
  36834. },
  36835. /**
  36836. * @method
  36837. * The launch function is called by the {@link Ext.app.Application Application} if this Profile's {@link #isActive}
  36838. * function returned true. This is typically the best place to run any profile-specific app launch code. Example
  36839. * usage:
  36840. *
  36841. * launch: function() {
  36842. * Ext.create('MyApp.view.tablet.Main');
  36843. * }
  36844. */
  36845. launch: Ext.emptyFn,
  36846. /**
  36847. * @private
  36848. */
  36849. applyNamespace: function(name) {
  36850. if (name == 'auto') {
  36851. name = this.getName();
  36852. }
  36853. return name.toLowerCase();
  36854. },
  36855. /**
  36856. * @private
  36857. */
  36858. applyName: function(name) {
  36859. if (name == 'auto') {
  36860. var pieces = this.$className.split('.');
  36861. name = pieces[pieces.length - 1];
  36862. }
  36863. return name;
  36864. },
  36865. /**
  36866. * @private
  36867. * Computes the full class names of any specified model, view, controller and store dependencies, returns them in
  36868. * an object map for easy loading
  36869. */
  36870. getDependencies: function() {
  36871. var allClasses = [],
  36872. format = Ext.String.format,
  36873. appName = this.getApplication().getName(),
  36874. namespace = this.getNamespace(),
  36875. map = {
  36876. model: this.getModels(),
  36877. view: this.getViews(),
  36878. controller: this.getControllers(),
  36879. store: this.getStores()
  36880. },
  36881. classType, classNames, fullyQualified;
  36882. for (classType in map) {
  36883. classNames = [];
  36884. Ext.each(map[classType], function(className) {
  36885. if (Ext.isString(className)) {
  36886. //we check name === appName to allow MyApp.profile.MyApp to exist
  36887. if (Ext.isString(className) && (Ext.Loader.getPrefix(className) === "" || className === appName)) {
  36888. className = appName + '.' + classType + '.' + namespace + '.' + className;
  36889. }
  36890. classNames.push(className);
  36891. allClasses.push(className);
  36892. }
  36893. }, this);
  36894. map[classType] = classNames;
  36895. }
  36896. map.all = allClasses;
  36897. return map;
  36898. }
  36899. });
  36900. /**
  36901. * @author Ed Spencer
  36902. * @private
  36903. *
  36904. * Represents a mapping between a url and a controller/action pair. May also contain additional params. This is a
  36905. * private internal class that should not need to be used by end-developer code. Its API and existence are subject to
  36906. * change so use at your own risk.
  36907. *
  36908. * For information on how to use routes we suggest reading the following guides:
  36909. *
  36910. * - [Using History Support](#!/guide/history_support)
  36911. * - [Intro to Applications](#!/guide/apps_intro)
  36912. * - [Using Controllers](#!/guide/controllers)
  36913. *
  36914. */
  36915. Ext.define('Ext.app.Route', {
  36916. config: {
  36917. /**
  36918. * @cfg {Object} conditions Optional set of conditions for each token in the url string. Each key should be one
  36919. * of the tokens, each value should be a regex that the token should accept. For example, if you have a Route
  36920. * with a url like "files/:fileName" and you want it to match urls like "files/someImage.jpg" then you can set
  36921. * these conditions to allow the :fileName token to accept strings containing a period ("."):
  36922. *
  36923. * conditions: {
  36924. * ':fileName': "[0-9a-zA-Z\.]+"
  36925. * }
  36926. *
  36927. */
  36928. conditions: {},
  36929. /**
  36930. * @cfg {String} url (required) The url regex to match against.
  36931. */
  36932. url: null,
  36933. /**
  36934. * @cfg {String} controller The name of the Controller whose {@link #action} will be called if this route is
  36935. * matched.
  36936. */
  36937. controller: null,
  36938. /**
  36939. * @cfg {String} action The name of the action that will be called on the {@link #controller} if this route is
  36940. * matched.
  36941. */
  36942. action: null,
  36943. /**
  36944. * @private
  36945. * @cfg {Boolean} initialized Indicates whether or not this Route has been initialized. We don't initialize
  36946. * straight away so as to save unnecessary processing.
  36947. */
  36948. initialized: false
  36949. },
  36950. constructor: function(config) {
  36951. this.initConfig(config);
  36952. },
  36953. /**
  36954. * Attempts to recognize a given url string and return controller/action pair for it.
  36955. * @param {String} url The url to recognize.
  36956. * @return {Object/Boolean} The matched data, or `false` if no match.
  36957. */
  36958. recognize: function(url) {
  36959. if (!this.getInitialized()) {
  36960. this.initialize();
  36961. }
  36962. if (this.recognizes(url)) {
  36963. var matches = this.matchesFor(url),
  36964. args = url.match(this.matcherRegex);
  36965. args.shift();
  36966. return Ext.applyIf(matches, {
  36967. controller: this.getController(),
  36968. action : this.getAction(),
  36969. historyUrl: url,
  36970. args : args
  36971. });
  36972. }
  36973. },
  36974. /**
  36975. * @private
  36976. * Sets up the relevant regular expressions used to match against this route.
  36977. */
  36978. initialize: function() {
  36979. /*
  36980. * The regular expression we use to match a segment of a route mapping
  36981. * this will recognize segments starting with a colon,
  36982. * e.g. on 'namespace/:controller/:action', :controller and :action will be recognized
  36983. */
  36984. this.paramMatchingRegex = new RegExp(/:([0-9A-Za-z\_]*)/g);
  36985. /*
  36986. * Converts a route string into an array of symbols starting with a colon. e.g.
  36987. * ":controller/:action/:id" => [':controller', ':action', ':id']
  36988. */
  36989. this.paramsInMatchString = this.getUrl().match(this.paramMatchingRegex) || [];
  36990. this.matcherRegex = this.createMatcherRegex(this.getUrl());
  36991. this.setInitialized(true);
  36992. },
  36993. /**
  36994. * @private
  36995. * Returns true if this Route matches the given url string
  36996. * @param {String} url The url to test
  36997. * @return {Boolean} True if this Route recognizes the url
  36998. */
  36999. recognizes: function(url) {
  37000. return this.matcherRegex.test(url);
  37001. },
  37002. /**
  37003. * @private
  37004. * Returns a hash of matching url segments for the given url.
  37005. * @param {String} url The url to extract matches for
  37006. * @return {Object} matching url segments
  37007. */
  37008. matchesFor: function(url) {
  37009. var params = {},
  37010. keys = this.paramsInMatchString,
  37011. values = url.match(this.matcherRegex),
  37012. length = keys.length,
  37013. i;
  37014. //first value is the entire match so reject
  37015. values.shift();
  37016. for (i = 0; i < length; i++) {
  37017. params[keys[i].replace(":", "")] = values[i];
  37018. }
  37019. return params;
  37020. },
  37021. /**
  37022. * @private
  37023. * Returns an array of matching url segments for the given url.
  37024. * @param {String} url The url to extract matches for
  37025. * @return {Array} matching url segments
  37026. */
  37027. argsFor: function(url) {
  37028. var args = [],
  37029. keys = this.paramsInMatchString,
  37030. values = url.match(this.matcherRegex),
  37031. length = keys.length,
  37032. i;
  37033. //first value is the entire match so reject
  37034. values.shift();
  37035. for (i = 0; i < length; i++) {
  37036. args.push(keys[i].replace(':', ""));
  37037. params[keys[i].replace(":", "")] = values[i];
  37038. }
  37039. return params;
  37040. },
  37041. /**
  37042. * @private
  37043. * Constructs a url for the given config object by replacing wildcard placeholders in the Route's url
  37044. * @param {Object} config The config object
  37045. * @return {String} The constructed url
  37046. */
  37047. urlFor: function(config) {
  37048. var url = this.getUrl();
  37049. for (var key in config) {
  37050. url = url.replace(":" + key, config[key]);
  37051. }
  37052. return url;
  37053. },
  37054. /**
  37055. * @private
  37056. * Takes the configured url string including wildcards and returns a regex that can be used to match
  37057. * against a url
  37058. * @param {String} url The url string
  37059. * @return {RegExp} The matcher regex
  37060. */
  37061. createMatcherRegex: function(url) {
  37062. /**
  37063. * Converts a route string into an array of symbols starting with a colon. e.g.
  37064. * ":controller/:action/:id" => [':controller', ':action', ':id']
  37065. */
  37066. var paramsInMatchString = this.paramsInMatchString,
  37067. length = paramsInMatchString.length,
  37068. i, cond, matcher;
  37069. for (i = 0; i < length; i++) {
  37070. cond = this.getConditions()[paramsInMatchString[i]];
  37071. matcher = Ext.util.Format.format("({0})", cond || "[%a-zA-Z0-9\-\\_\\s,]+");
  37072. url = url.replace(new RegExp(paramsInMatchString[i]), matcher);
  37073. }
  37074. //we want to match the whole string, so include the anchors
  37075. return new RegExp("^" + url + "$");
  37076. }
  37077. });
  37078. /**
  37079. * @author Ed Spencer
  37080. * @private
  37081. *
  37082. * The Router is an ordered set of route definitions that decode a url into a controller function to execute. Each
  37083. * route defines a type of url to match, along with the controller function to call if it is matched. The Router is
  37084. * usually managed exclusively by an {@link Ext.app.Application Application}, which also uses a
  37085. * {@link Ext.app.History History} instance to find out when the browser's url has changed.
  37086. *
  37087. * Routes are almost always defined inside a {@link Ext.app.Controller Controller}, as opposed to on the Router itself.
  37088. * End-developers should not usually need to interact directly with the Router as the Application and Controller
  37089. * classes manage everything automatically. See the {@link Ext.app.Controller Controller documentation} for more
  37090. * information on specifying routes.
  37091. */
  37092. Ext.define('Ext.app.Router', {
  37093. requires: ['Ext.app.Route'],
  37094. config: {
  37095. /**
  37096. * @cfg {Array} routes The set of routes contained within this Router.
  37097. * @readonly
  37098. */
  37099. routes: [],
  37100. /**
  37101. * @cfg {Object} defaults Default configuration options for each Route connected to this Router.
  37102. */
  37103. defaults: {
  37104. action: 'index'
  37105. }
  37106. },
  37107. constructor: function(config) {
  37108. this.initConfig(config);
  37109. },
  37110. /**
  37111. * Connects a url-based route to a controller/action pair plus additional params.
  37112. * @param {String} url The url to recognize.
  37113. */
  37114. connect: function(url, params) {
  37115. params = Ext.apply({url: url}, params || {}, this.getDefaults());
  37116. var route = Ext.create('Ext.app.Route', params);
  37117. this.getRoutes().push(route);
  37118. return route;
  37119. },
  37120. /**
  37121. * Recognizes a url string connected to the Router, return the controller/action pair plus any additional
  37122. * config associated with it.
  37123. * @param {String} url The url to recognize.
  37124. * @return {Object/undefined} If the url was recognized, the controller and action to call, else `undefined`.
  37125. */
  37126. recognize: function(url) {
  37127. var routes = this.getRoutes(),
  37128. length = routes.length,
  37129. i, result;
  37130. for (i = 0; i < length; i++) {
  37131. result = routes[i].recognize(url);
  37132. if (result !== undefined) {
  37133. return result;
  37134. }
  37135. }
  37136. return undefined;
  37137. },
  37138. /**
  37139. * Convenience method which just calls the supplied function with the Router instance. Example usage:
  37140. *
  37141. * Ext.Router.draw(function(map) {
  37142. * map.connect('activate/:token', {controller: 'users', action: 'activate'});
  37143. * map.connect('home', {controller: 'index', action: 'home'});
  37144. * });
  37145. *
  37146. * @param {Function} fn The fn to call
  37147. */
  37148. draw: function(fn) {
  37149. fn.call(this, this);
  37150. },
  37151. /**
  37152. * @private
  37153. */
  37154. clear: function() {
  37155. this.setRoutes([]);
  37156. }
  37157. }, function() {
  37158. });
  37159. /**
  37160. * @author Ed Spencer
  37161. *
  37162. * @aside guide apps_intro
  37163. * @aside guide first_app
  37164. * @aside video mvc-part-1
  37165. * @aside video mvc-part-2
  37166. *
  37167. * Ext.app.Application defines the set of {@link Ext.data.Model Models}, {@link Ext.app.Controller Controllers},
  37168. * {@link Ext.app.Profile Profiles}, {@link Ext.data.Store Stores} and {@link Ext.Component Views} that an application
  37169. * consists of. It automatically loads all of those dependencies and can optionally specify a {@link #launch} function
  37170. * that will be called when everything is ready.
  37171. *
  37172. * Sample usage:
  37173. *
  37174. * Ext.application({
  37175. * name: 'MyApp',
  37176. *
  37177. * models: ['User', 'Group'],
  37178. * stores: ['Users'],
  37179. * controllers: ['Users'],
  37180. * views: ['Main', 'ShowUser'],
  37181. *
  37182. * launch: function() {
  37183. * Ext.create('MyApp.view.Main');
  37184. * }
  37185. * });
  37186. *
  37187. * Creating an Application instance is the only time in Sencha Touch 2 that we don't use Ext.create to create the new
  37188. * instance. Instead, the {@link Ext#application} function instantiates an Ext.app.Application internally,
  37189. * automatically loading the Ext.app.Application class if it is not present on the page already and hooking in to
  37190. * {@link Ext#onReady} before creating the instance itself. An alternative is to use Ext.create inside an Ext.onReady
  37191. * callback, but Ext.application is preferred.
  37192. *
  37193. * ## Dependencies
  37194. *
  37195. * Application follows a simple convention when it comes to specifying the controllers, views, models, stores and
  37196. * profiles it requires. By default it expects each of them to be found inside the *app/controller*, *app/view*,
  37197. * *app/model*, *app/store* and *app/profile* directories in your app - if you follow this convention you can just
  37198. * specify the last part of each class name and Application will figure out the rest for you:
  37199. *
  37200. * Ext.application({
  37201. * name: 'MyApp',
  37202. *
  37203. * controllers: ['Users'],
  37204. * models: ['User', 'Group'],
  37205. * stores: ['Users'],
  37206. * views: ['Main', 'ShowUser']
  37207. * });
  37208. *
  37209. * The example above will load 6 files:
  37210. *
  37211. * - app/model/User.js
  37212. * - app/model/Group.js
  37213. * - app/store/Users.js
  37214. * - app/controller/Users.js
  37215. * - app/view/Main.js
  37216. * - app/view/ShowUser.js
  37217. *
  37218. * ### Nested Dependencies
  37219. *
  37220. * For larger apps it's common to split the models, views and controllers into subfolders so keep the project
  37221. * organized. This is especially true of views - it's not unheard of for large apps to have over a hundred separate
  37222. * view classes so organizing them into folders can make maintenance much simpler.
  37223. *
  37224. * To specify dependencies in subfolders just use a period (".") to specify the folder:
  37225. *
  37226. * Ext.application({
  37227. * name: 'MyApp',
  37228. *
  37229. * controllers: ['Users', 'nested.MyController'],
  37230. * views: ['products.Show', 'products.Edit', 'user.Login']
  37231. * });
  37232. *
  37233. * In this case these 5 files will be loaded:
  37234. *
  37235. * - app/controller/Users.js
  37236. * - app/controller/nested/MyController.js
  37237. * - app/view/products/Show.js
  37238. * - app/view/products/Edit.js
  37239. * - app/view/user/Login.js
  37240. *
  37241. * Note that we can mix and match within each configuration here - for each model, view, controller, profile or store
  37242. * you can specify either just the final part of the class name (if you follow the directory conventions), or the full
  37243. * class name.
  37244. *
  37245. * ### External Dependencies
  37246. *
  37247. * Finally, we can specify application dependencies from outside our application by fully-qualifying the classes we
  37248. * want to load. A common use case for this is sharing authentication logic between multiple applications. Perhaps you
  37249. * have several apps that login via a common user database and you want to share that code between them. An easy way to
  37250. * do this is to create a folder alongside your app folder and then add its contents as dependencies for your app.
  37251. *
  37252. * For example, let's say our shared login code contains a login controller, a user model and a login form view. We
  37253. * want to use all of these in our application:
  37254. *
  37255. * Ext.Loader.setPath({
  37256. * 'Auth': 'Auth'
  37257. * });
  37258. *
  37259. * Ext.application({
  37260. * views: ['Auth.view.LoginForm', 'Welcome'],
  37261. * controllers: ['Auth.controller.Sessions', 'Main'],
  37262. * models: ['Auth.model.User']
  37263. * });
  37264. *
  37265. * This will load the following files:
  37266. *
  37267. * - Auth/view/LoginForm.js
  37268. * - Auth/controller/Sessions.js
  37269. * - Auth/model/User.js
  37270. * - app/view/Welcome.js
  37271. * - app/controller/Main.js
  37272. *
  37273. * The first three were loaded from outside our application, the last two from the application itself. Note how we can
  37274. * still mix and match application files and external dependency files.
  37275. *
  37276. * Note that to enable the loading of external dependencies we just have to tell the Loader where to find those files,
  37277. * which is what we do with the Ext.Loader.setPath call above. In this case we're telling the Loader to find any class
  37278. * starting with the 'Auth' namespace inside our 'Auth' folder. This means we can drop our common Auth code into our
  37279. * application alongside the app folder and the framework will be able to figure out how to load everything.
  37280. *
  37281. * ## Launching
  37282. *
  37283. * Each Application can define a {@link Ext.app.Application#launch launch} function, which is called as soon as all of
  37284. * your app's classes have been loaded and the app is ready to be launched. This is usually the best place to put any
  37285. * application startup logic, typically creating the main view structure for your app.
  37286. *
  37287. * In addition to the Application launch function, there are two other places you can put app startup logic. Firstly,
  37288. * each Controller is able to define an {@link Ext.app.Controller#init init} function, which is called before the
  37289. * Application launch function. Secondly, if you are using Device Profiles, each Profile can define a
  37290. * {@link Ext.app.Profile#launch launch} function, which is called after the Controller init functions but before the
  37291. * Application launch function.
  37292. *
  37293. * Note that only the active Profile has its launch function called - for example if you define profiles for Phone and
  37294. * Tablet and then launch the app on a tablet, only the Tablet Profile's launch function is called.
  37295. *
  37296. * 1. Controller#init functions called
  37297. * 2. Profile#launch function called
  37298. * 3. Application#launch function called
  37299. * 4. Controller#launch functions called
  37300. *
  37301. * When using Profiles it is common to place most of the bootup logic inside the Profile launch function because each
  37302. * Profile has a different set of views that need to be constructed at startup.
  37303. *
  37304. * ## Adding to Home Screen
  37305. *
  37306. * iOS devices allow your users to add your app to their home screen for easy access. iOS allows you to customize
  37307. * several aspects of this, including the icon that will appear on the home screen and the startup image. These can be
  37308. * specified in the Ext.application setup block:
  37309. *
  37310. * Ext.application({
  37311. * name: 'MyApp',
  37312. *
  37313. * {@link #icon}: 'resources/img/icon.png',
  37314. * {@link #isIconPrecomposed}: false,
  37315. * {@link #startupImage}: {
  37316. * '320x460': 'resources/startup/320x460.jpg',
  37317. * '640x920': 'resources/startup/640x920.png',
  37318. * '640x1096': 'resources/startup/640x1096.png',
  37319. * '768x1004': 'resources/startup/768x1004.png',
  37320. * '748x1024': 'resources/startup/748x1024.png',
  37321. * '1536x2008': 'resources/startup/1536x2008.png',
  37322. * '1496x2048': 'resources/startup/1496x2048.png'
  37323. * }
  37324. * });
  37325. *
  37326. * When the user adds your app to the home screen, your resources/img/icon.png file will be used as the application
  37327. * {@link #icon}. We also used the {@link #isIconPrecomposed} configuration to turn off the gloss effect that is automatically added
  37328. * to icons in iOS. Finally we used the {@link #startupImage} configuration to provide the images that will be displayed
  37329. * while your application is starting up. See also {@link #statusBarStyle}.
  37330. *
  37331. * ## Find out more
  37332. *
  37333. * If you are not already familiar with writing applications with Sencha Touch 2 we recommend reading the
  37334. * [intro to applications guide](#!/guide/apps_intro), which lays out the core principles of writing apps
  37335. * with Sencha Touch 2.
  37336. */
  37337. Ext.define('Ext.app.Application', {
  37338. extend: 'Ext.app.Controller',
  37339. requires: [
  37340. 'Ext.app.History',
  37341. 'Ext.app.Profile',
  37342. 'Ext.app.Router',
  37343. 'Ext.app.Action'
  37344. ],
  37345. config: {
  37346. /**
  37347. * @cfg {String/Object} icon
  37348. * Specifies a set of URLs to the application icon for different device form factors. This icon is displayed
  37349. * when the application is added to the device's Home Screen.
  37350. *
  37351. * Ext.setup({
  37352. * icon: {
  37353. * 57: 'resources/icons/Icon.png',
  37354. * 72: 'resources/icons/Icon~ipad.png',
  37355. * 114: 'resources/icons/Icon@2x.png',
  37356. * 144: 'resources/icons/Icon~ipad@2x.png'
  37357. * },
  37358. * onReady: function() {
  37359. * // ...
  37360. * }
  37361. * });
  37362. *
  37363. * Each key represents the dimension of the icon as a square shape. For example: '57' is the key for a 57 x 57
  37364. * icon image. Here is the breakdown of each dimension and its device target:
  37365. *
  37366. * - 57: Non-retina iPhone, iPod touch, and all Android devices
  37367. * - 72: Retina iPhone and iPod touch
  37368. * - 114: Non-retina iPad (first and second generation)
  37369. * - 144: Retina iPad (third generation)
  37370. *
  37371. * Note that the dimensions of the icon images must be exactly 57x57, 72x72, 114x114 and 144x144 respectively.
  37372. *
  37373. * It is highly recommended that you provide all these different sizes to accommodate a full range of
  37374. * devices currently available. However if you only have one icon in one size, make it 57x57 in size and
  37375. * specify it as a string value. This same icon will be used on all supported devices.
  37376. *
  37377. * Ext.application({
  37378. * icon: 'resources/icons/Icon.png',
  37379. * launch: function() {
  37380. * // ...
  37381. * }
  37382. * });
  37383. */
  37384. /**
  37385. * @cfg {Object} startupImage
  37386. * Specifies a set of URLs to the application startup images for different device form factors. This image is
  37387. * displayed when the application is being launched from the Home Screen icon. Note that this currently only applies
  37388. * to iOS devices.
  37389. *
  37390. * Ext.application({
  37391. * startupImage: {
  37392. * '320x460': 'resources/startup/320x460.jpg',
  37393. * '640x920': 'resources/startup/640x920.png',
  37394. * '640x1096': 'resources/startup/640x1096.png',
  37395. * '768x1004': 'resources/startup/768x1004.png',
  37396. * '748x1024': 'resources/startup/748x1024.png',
  37397. * '1536x2008': 'resources/startup/1536x2008.png',
  37398. * '1496x2048': 'resources/startup/1496x2048.png'
  37399. * },
  37400. * launch: function() {
  37401. * // ...
  37402. * }
  37403. * });
  37404. *
  37405. * Each key represents the dimension of the image. For example: '320x460' is the key for a 320px x 460px image.
  37406. * Here is the breakdown of each dimension and its device target:
  37407. *
  37408. * - 320x460: Non-retina iPhone, iPod touch, and all Android devices
  37409. * - 640x920: Retina iPhone and iPod touch
  37410. * - 640x1096: iPhone 5 and iPod touch (fifth generation)
  37411. * - 768x1004: Non-retina iPad (first and second generation) in portrait orientation
  37412. * - 748x1024: Non-retina iPad (first and second generation) in landscape orientation
  37413. * - 1536x2008: Retina iPad (third generation) in portrait orientation
  37414. * - 1496x2048: Retina iPad (third generation) in landscape orientation
  37415. *
  37416. * Please note that there's no automatic fallback mechanism for the startup images. In other words, if you don't specify
  37417. * a valid image for a certain device, nothing will be displayed while the application is being launched on that device.
  37418. */
  37419. /**
  37420. * @cfg {Boolean} isIconPrecomposed
  37421. * `true` to not having a glossy effect added to the icon by the OS, which will preserve its exact look. This currently
  37422. * only applies to iOS devices.
  37423. */
  37424. /**
  37425. * @cfg {String} [statusBarStyle='black'] Allows you to set the style of the status bar when your app is added to the
  37426. * home screen on iOS devices. Alternative is to set to 'black-translucent', which turns
  37427. * the status bar semi-transparent and overlaps the app content. This is usually not a good option for web apps
  37428. */
  37429. /**
  37430. * @cfg {String} tabletIcon Path to the _.png_ image file to use when your app is added to the home screen on an
  37431. * iOS **tablet** device (iPad).
  37432. * @deprecated 2.0.0 Please use the {@link #icon} configuration instead.
  37433. */
  37434. /**
  37435. * @cfg {String} phoneIcon Path to the _.png_ image file to use when your app is added to the home screen on an
  37436. * iOS **phone** device (iPhone or iPod).
  37437. * @deprecated 2.0.0 Please use the {@link #icon} configuration instead.
  37438. */
  37439. /**
  37440. * @cfg {Boolean} glossOnIcon If set to `false`, the 'gloss' effect added to home screen {@link #icon icons} on
  37441. * iOS devices will be removed.
  37442. * @deprecated 2.0.0 Please use the {@link #isIconPrecomposed} configuration instead.
  37443. */
  37444. /**
  37445. * @cfg {String} phoneStartupScreen Path to the _.png_ image file that will be displayed while the app is
  37446. * starting up once it has been added to the home screen of an iOS phone device (iPhone or iPod). This _.png_
  37447. * file should be 320px wide and 460px high.
  37448. * @deprecated 2.0.0 Please use the {@link #startupImage} configuration instead.
  37449. */
  37450. /**
  37451. * @cfg {String} tabletStartupScreen Path to the _.png_ image file that will be displayed while the app is
  37452. * starting up once it has been added to the home screen of an iOS tablet device (iPad). This _.png_ file should
  37453. * be 768px wide and 1004px high.
  37454. * @deprecated 2.0.0 Please use the {@link #startupImage} configuration instead.
  37455. */
  37456. /**
  37457. * @cfg {Array} profiles The set of profiles to load for this Application. Each profile is expected to
  37458. * exist inside the *app/profile* directory and define a class following the convention
  37459. * AppName.profile.ProfileName. For example, in the code below, the classes *AppName.profile.Phone*
  37460. * and *AppName.profile.Tablet* will be loaded. Note that we are able to specify
  37461. * either the full class name (as with *AppName.profile.Tablet*) or just the final part of the class name
  37462. * and leave Application to automatically prepend *AppName.profile.'* to each:
  37463. *
  37464. * profiles: [
  37465. * 'Phone',
  37466. * 'AppName.profile.Tablet',
  37467. * 'SomeCustomNamespace.profile.Desktop'
  37468. * ]
  37469. * @accessor
  37470. */
  37471. profiles: [],
  37472. /**
  37473. * @cfg {Array} controllers The set of controllers to load for this Application. Each controller is expected to
  37474. * exist inside the *app/controller* directory and define a class following the convention
  37475. * AppName.controller.ControllerName. For example, in the code below, the classes *AppName.controller.Users*,
  37476. * *AppName.controller.Groups* and *AppName.controller.Products* will be loaded. Note that we are able to specify
  37477. * either the full class name (as with *AppName.controller.Products*) or just the final part of the class name
  37478. * and leave Application to automatically prepend *AppName.controller.'* to each:
  37479. *
  37480. * controllers: [
  37481. * 'Users',
  37482. * 'Groups',
  37483. * 'AppName.controller.Products',
  37484. * 'SomeCustomNamespace.controller.Orders'
  37485. * ]
  37486. * @accessor
  37487. */
  37488. controllers: [],
  37489. /**
  37490. * @cfg {Ext.app.History} history The global {@link Ext.app.History History} instance attached to this
  37491. * Application.
  37492. * @accessor
  37493. * @readonly
  37494. */
  37495. history: {},
  37496. /**
  37497. * @cfg {String} name The name of the Application. This should be a single word without spaces or periods
  37498. * because it is used as the Application's global namespace. All classes in your application should be
  37499. * namespaced undef the Application's name - for example if your application name is 'MyApp', your classes
  37500. * should be named 'MyApp.model.User', 'MyApp.controller.Users', 'MyApp.view.Main' etc
  37501. * @accessor
  37502. */
  37503. name: null,
  37504. /**
  37505. * @cfg {String} appFolder The path to the directory which contains all application's classes.
  37506. * This path will be registered via {@link Ext.Loader#setPath} for the namespace specified in the {@link #name name} config.
  37507. * @accessor
  37508. */
  37509. appFolder : 'app',
  37510. /**
  37511. * @cfg {Ext.app.Router} router The global {@link Ext.app.Router Router} instance attached to this Application.
  37512. * @accessor
  37513. * @readonly
  37514. */
  37515. router: {},
  37516. /**
  37517. * @cfg {Array} controllerInstances Used internally as the collection of instantiated controllers. Use {@link #getController} instead.
  37518. * @private
  37519. * @accessor
  37520. */
  37521. controllerInstances: [],
  37522. /**
  37523. * @cfg {Array} profileInstances Used internally as the collection of instantiated profiles.
  37524. * @private
  37525. * @accessor
  37526. */
  37527. profileInstances: [],
  37528. /**
  37529. * @cfg {Ext.app.Profile} currentProfile The {@link Ext.app.Profile Profile} that is currently active for the
  37530. * Application. This is set once, automatically by the Application before launch.
  37531. * @accessor
  37532. * @readonly
  37533. */
  37534. currentProfile: null,
  37535. /**
  37536. * @cfg {Function} launch An optional function that will be called when the Application is ready to be
  37537. * launched. This is normally used to render any initial UI required by your application
  37538. * @accessor
  37539. */
  37540. launch: Ext.emptyFn,
  37541. /**
  37542. * @private
  37543. * @cfg {Boolean} enableLoader Private config to disable loading of Profiles at application construct time.
  37544. * This is used by Sencha's unit test suite to test _Application.js_ in isolation and is likely to be removed
  37545. * in favor of a more pleasing solution by the time you use it.
  37546. * @accessor
  37547. */
  37548. enableLoader: true,
  37549. /**
  37550. * @private
  37551. * @cfg {String[]} requires An array of extra dependencies, to be required after this application's {@link #name} config
  37552. * has been processed properly, but before anything else to ensure overrides get executed first.
  37553. * @accessor
  37554. */
  37555. requires: []
  37556. },
  37557. /**
  37558. * Constructs a new Application instance.
  37559. */
  37560. constructor: function(config) {
  37561. config = config || {};
  37562. Ext.applyIf(config, {
  37563. application: this
  37564. });
  37565. this.initConfig(config);
  37566. //it's common to pass in functions to an application but because they are not predictable config names they
  37567. //aren't ordinarily placed onto this so we need to do it manually
  37568. for (var key in config) {
  37569. this[key] = config[key];
  37570. }
  37571. //<debug>
  37572. Ext.Loader.setConfig({ enabled: true });
  37573. //</debug>
  37574. Ext.require(this.getRequires(), function() {
  37575. if (this.getEnableLoader() !== false) {
  37576. Ext.require(this.getProfiles(), this.onProfilesLoaded, this);
  37577. }
  37578. }, this);
  37579. },
  37580. /**
  37581. * Dispatches a given {@link Ext.app.Action} to the relevant Controller instance. This is not usually called
  37582. * directly by the developer, instead Sencha Touch's History support picks up on changes to the browser's url
  37583. * and calls dispatch automatically.
  37584. * @param {Ext.app.Action} action The action to dispatch.
  37585. * @param {Boolean} [addToHistory=true] Sets the browser's url to the action's url.
  37586. */
  37587. dispatch: function(action, addToHistory) {
  37588. action = action || {};
  37589. Ext.applyIf(action, {
  37590. application: this
  37591. });
  37592. action = Ext.factory(action, Ext.app.Action);
  37593. if (action) {
  37594. var profile = this.getCurrentProfile(),
  37595. profileNS = profile ? profile.getNamespace() : undefined,
  37596. controller = this.getController(action.getController(), profileNS);
  37597. if (controller) {
  37598. if (addToHistory !== false) {
  37599. this.getHistory().add(action, true);
  37600. }
  37601. controller.execute(action);
  37602. }
  37603. }
  37604. },
  37605. /**
  37606. * Redirects the browser to the given url. This only affects the url after the '#'. You can pass in either a String
  37607. * or a Model instance - if a Model instance is defined its {@link Ext.data.Model#toUrl toUrl} function is called,
  37608. * which returns a string representing the url for that model. Internally, this uses your application's
  37609. * {@link Ext.app.Router Router} to decode the url into a matching controller action and then calls
  37610. * {@link #dispatch}.
  37611. * @param {String/Ext.data.Model} url The String url to redirect to.
  37612. */
  37613. redirectTo: function(url) {
  37614. if (Ext.data && Ext.data.Model && url instanceof Ext.data.Model) {
  37615. var record = url;
  37616. url = record.toUrl();
  37617. }
  37618. var decoded = this.getRouter().recognize(url);
  37619. if (decoded) {
  37620. decoded.url = url;
  37621. if (record) {
  37622. decoded.data = {};
  37623. decoded.data.record = record;
  37624. }
  37625. return this.dispatch(decoded);
  37626. }
  37627. },
  37628. /**
  37629. * @private
  37630. * (documented on Controller's control config)
  37631. */
  37632. control: function(selectors, controller) {
  37633. //if the controller is not defined, use this instead (the application instance)
  37634. controller = controller || this;
  37635. var dispatcher = this.getEventDispatcher(),
  37636. refs = (controller) ? controller.getRefs() : {},
  37637. selector, eventName, listener, listeners, ref;
  37638. for (selector in selectors) {
  37639. if (selectors.hasOwnProperty(selector)) {
  37640. listeners = selectors[selector];
  37641. ref = refs[selector];
  37642. //refs can be used in place of selectors
  37643. if (ref) {
  37644. selector = ref.selector || ref;
  37645. }
  37646. for (eventName in listeners) {
  37647. listener = listeners[eventName];
  37648. if (Ext.isString(listener)) {
  37649. listener = controller[listener];
  37650. }
  37651. dispatcher.addListener('component', selector, eventName, listener, controller);
  37652. }
  37653. }
  37654. }
  37655. },
  37656. /**
  37657. * Returns the Controller instance for the given controller name.
  37658. * @param {String} name The name of the Controller.
  37659. * @param {String} [profileName] Optional profile name. If passed, this is the same as calling
  37660. * `getController('profileName.controllerName')`.
  37661. */
  37662. getController: function(name, profileName) {
  37663. var instances = this.getControllerInstances(),
  37664. appName = this.getName(),
  37665. format = Ext.String.format,
  37666. topLevelName;
  37667. if (name instanceof Ext.app.Controller) {
  37668. return name;
  37669. }
  37670. if (instances[name]) {
  37671. return instances[name];
  37672. } else {
  37673. topLevelName = format("{0}.controller.{1}", appName, name);
  37674. profileName = format("{0}.controller.{1}.{2}", appName, profileName, name);
  37675. return instances[profileName] || instances[topLevelName];
  37676. }
  37677. },
  37678. /**
  37679. * @private
  37680. * Callback that is invoked when all of the configured Profiles have been loaded. Detects the current profile and
  37681. * gathers any additional dependencies from that profile, then loads all of those dependencies.
  37682. */
  37683. onProfilesLoaded: function() {
  37684. var profiles = this.getProfiles(),
  37685. length = profiles.length,
  37686. instances = [],
  37687. requires = this.gatherDependencies(),
  37688. current, i, profileDeps;
  37689. for (i = 0; i < length; i++) {
  37690. instances[i] = Ext.create(profiles[i], {
  37691. application: this
  37692. });
  37693. /*
  37694. * Note that we actually require all of the dependencies for all Profiles - this is so that we can produce
  37695. * a single build file that will work on all defined Profiles. Although the other classes will be loaded,
  37696. * the correct Profile will still be identified and the other classes ignored. While this feels somewhat
  37697. * inefficient, the majority of the bulk of an application is likely to be the framework itself. The bigger
  37698. * the app though, the bigger the effect of this inefficiency so ideally we will create a way to create and
  37699. * load Profile-specific builds in a future release.
  37700. */
  37701. profileDeps = instances[i].getDependencies();
  37702. requires = requires.concat(profileDeps.all);
  37703. if (instances[i].isActive() && !current) {
  37704. current = instances[i];
  37705. this.setCurrentProfile(current);
  37706. this.setControllers(this.getControllers().concat(profileDeps.controller));
  37707. this.setModels(this.getModels().concat(profileDeps.model));
  37708. this.setViews(this.getViews().concat(profileDeps.view));
  37709. this.setStores(this.getStores().concat(profileDeps.store));
  37710. }
  37711. }
  37712. this.setProfileInstances(instances);
  37713. Ext.require(requires, this.loadControllerDependencies, this);
  37714. },
  37715. /**
  37716. * @private
  37717. * Controllers can also specify dependencies, so we grab them all here and require them.
  37718. */
  37719. loadControllerDependencies: function() {
  37720. this.instantiateControllers();
  37721. var controllers = this.getControllerInstances(),
  37722. classes = [],
  37723. stores = [],
  37724. i, controller, controllerStores, name;
  37725. for (name in controllers) {
  37726. controller = controllers[name];
  37727. controllerStores = controller.getStores();
  37728. stores = stores.concat(controllerStores);
  37729. classes = classes.concat(controller.getModels().concat(controller.getViews()).concat(controllerStores));
  37730. }
  37731. this.setStores(this.getStores().concat(stores));
  37732. Ext.require(classes, this.onDependenciesLoaded, this);
  37733. },
  37734. /**
  37735. * @private
  37736. * Callback that is invoked when all of the Application, Controller and Profile dependencies have been loaded.
  37737. * Launches the controllers, then the profile and application.
  37738. */
  37739. onDependenciesLoaded: function() {
  37740. var me = this,
  37741. profile = this.getCurrentProfile(),
  37742. launcher = this.getLaunch(),
  37743. controllers, name;
  37744. this.instantiateStores();
  37745. controllers = this.getControllerInstances();
  37746. for (name in controllers) {
  37747. controllers[name].init(this);
  37748. }
  37749. if (profile) {
  37750. profile.launch();
  37751. }
  37752. launcher.call(me);
  37753. for (name in controllers) {
  37754. //<debug warn>
  37755. if (controllers[name] && !(controllers[name] instanceof Ext.app.Controller)) {
  37756. Ext.Logger.warn("The controller '" + name + "' doesn't have a launch method. Are you sure it extends from Ext.app.Controller?");
  37757. } else {
  37758. //</debug>
  37759. controllers[name].launch(this);
  37760. //<debug warn>
  37761. }
  37762. //</debug>
  37763. }
  37764. me.redirectTo(window.location.hash.substr(1));
  37765. },
  37766. /**
  37767. * @private
  37768. * Gathers up all of the previously computed MVCS dependencies into a single array that we can pass to {@link Ext#require}.
  37769. */
  37770. gatherDependencies: function() {
  37771. var classes = this.getModels().concat(this.getViews()).concat(this.getControllers());
  37772. Ext.each(this.getStores(), function(storeName) {
  37773. if (Ext.isString(storeName)) {
  37774. classes.push(storeName);
  37775. }
  37776. }, this);
  37777. return classes;
  37778. },
  37779. /**
  37780. * @private
  37781. * Should be called after dependencies are loaded, instantiates all of the Stores specified in the {@link #stores}
  37782. * config. For each item in the stores array we make sure the Store is instantiated. When strings are specified,
  37783. * the corresponding _app/store/StoreName.js_ was loaded so we now instantiate a `MyApp.store.StoreName`, giving it the
  37784. * id `StoreName`.
  37785. */
  37786. instantiateStores: function() {
  37787. var stores = this.getStores(),
  37788. length = stores.length,
  37789. store, storeClass, storeName, splits, i;
  37790. for (i = 0; i < length; i++) {
  37791. store = stores[i];
  37792. if (Ext.data && Ext.data.Store && !(store instanceof Ext.data.Store)) {
  37793. if (Ext.isString(store)) {
  37794. storeName = store;
  37795. storeClass = Ext.ClassManager.classes[store];
  37796. store = {
  37797. xclass: store
  37798. };
  37799. //we don't want to wipe out a configured storeId in the app's Store subclass so need
  37800. //to check for this first
  37801. if (storeClass.prototype.defaultConfig.storeId === undefined) {
  37802. splits = storeName.split('.');
  37803. store.id = splits[splits.length - 1];
  37804. }
  37805. }
  37806. stores[i] = Ext.factory(store, Ext.data.Store);
  37807. }
  37808. }
  37809. this.setStores(stores);
  37810. },
  37811. /**
  37812. * @private
  37813. * Called once all of our controllers have been loaded
  37814. */
  37815. instantiateControllers: function() {
  37816. var controllerNames = this.getControllers(),
  37817. instances = {},
  37818. length = controllerNames.length,
  37819. name, i;
  37820. for (i = 0; i < length; i++) {
  37821. name = controllerNames[i];
  37822. instances[name] = Ext.create(name, {
  37823. application: this
  37824. });
  37825. }
  37826. return this.setControllerInstances(instances);
  37827. },
  37828. /**
  37829. * @private
  37830. * As a convenience developers can locally qualify controller names (e.g. 'MyController' vs
  37831. * 'MyApp.controller.MyController'). This just makes sure everything ends up fully qualified
  37832. */
  37833. applyControllers: function(controllers) {
  37834. return this.getFullyQualified(controllers, 'controller');
  37835. },
  37836. /**
  37837. * @private
  37838. * As a convenience developers can locally qualify profile names (e.g. 'MyProfile' vs
  37839. * 'MyApp.profile.MyProfile'). This just makes sure everything ends up fully qualified
  37840. */
  37841. applyProfiles: function(profiles) {
  37842. return this.getFullyQualified(profiles, 'profile');
  37843. },
  37844. /**
  37845. * @private
  37846. * Checks that the name configuration has any whitespace, and trims them if found.
  37847. */
  37848. applyName: function(name) {
  37849. var oldName;
  37850. if (name && name.match(/ /g)) {
  37851. oldName = name;
  37852. name = name.replace(/ /g, "");
  37853. // <debug>
  37854. Ext.Logger.warn('Attempting to create an application with a name which contains whitespace ("' + oldName + '"). Renamed to "' + name + '".');
  37855. // </debug>
  37856. }
  37857. return name;
  37858. },
  37859. /**
  37860. * @private
  37861. * Makes sure the app namespace exists, sets the `app` property of the namespace to this application and sets its
  37862. * loading path (checks to make sure the path hadn't already been set via Ext.Loader.setPath)
  37863. */
  37864. updateName: function(newName) {
  37865. Ext.ClassManager.setNamespace(newName + '.app', this);
  37866. if (!Ext.Loader.config.paths[newName]) {
  37867. Ext.Loader.setPath(newName, this.getAppFolder());
  37868. }
  37869. },
  37870. /**
  37871. * @private
  37872. */
  37873. applyRouter: function(config) {
  37874. return Ext.factory(config, Ext.app.Router, this.getRouter());
  37875. },
  37876. /**
  37877. * @private
  37878. */
  37879. applyHistory: function(config) {
  37880. var history = Ext.factory(config, Ext.app.History, this.getHistory());
  37881. history.on('change', this.onHistoryChange, this);
  37882. return history;
  37883. },
  37884. /**
  37885. * @private
  37886. */
  37887. onHistoryChange: function(url) {
  37888. this.dispatch(this.getRouter().recognize(url), false);
  37889. }
  37890. }, function() {
  37891. });
  37892. /**
  37893. * A class to replicate the behavior of the Contextual menu in BlackBerry 10.
  37894. *
  37895. * More information: http://docs.blackberry.com/en/developers/deliverables/41577/contextual_menus.jsp
  37896. *
  37897. * var menu = Ext.create('Ext.bb.CrossCut', {
  37898. * items: [
  37899. * {
  37900. * text: 'New',
  37901. * iconMask: true,
  37902. * iconCls: 'compose'
  37903. * },
  37904. * {
  37905. * text: 'Reply',
  37906. * iconMask: true,
  37907. * iconCls: 'reply'
  37908. * },
  37909. * {
  37910. * text: 'Settings',
  37911. * iconMask: true,
  37912. * iconCls: 'settings'
  37913. * }
  37914. * ]
  37915. * });
  37916. */
  37917. Ext.define('Ext.bb.CrossCut', {
  37918. extend: 'Ext.Sheet',
  37919. xtype: 'crosscut',
  37920. requires: [
  37921. 'Ext.Button'
  37922. ],
  37923. config: {
  37924. /**
  37925. * @hide
  37926. */
  37927. top: 0,
  37928. /**
  37929. * @hide
  37930. */
  37931. right: 0,
  37932. /**
  37933. * @hide
  37934. */
  37935. bottom: 0,
  37936. /**
  37937. * @hide
  37938. */
  37939. left: null,
  37940. /**
  37941. * @hide
  37942. */
  37943. enter: 'right',
  37944. /**
  37945. * @hide
  37946. */
  37947. exit: 'right',
  37948. /**
  37949. * @hide
  37950. */
  37951. hideOnMaskTap: true,
  37952. /**
  37953. * @hide
  37954. */
  37955. baseCls: 'bb-crosscut',
  37956. /**
  37957. * @hide
  37958. */
  37959. layout: {
  37960. type: 'vbox',
  37961. pack: 'middle'
  37962. },
  37963. /**
  37964. * @hide
  37965. */
  37966. defaultType: 'button',
  37967. /**
  37968. * @hide
  37969. */
  37970. showAnimation: {
  37971. preserveEndState: true,
  37972. to: {
  37973. width: 275
  37974. }
  37975. },
  37976. /**
  37977. * @hide
  37978. */
  37979. hideAnimation: {
  37980. preserveEndState: true,
  37981. to: {
  37982. width: 68
  37983. }
  37984. },
  37985. defaults: {
  37986. baseCls: 'bb-crosscut-item'
  37987. }
  37988. }
  37989. });
  37990. /**
  37991. * @private
  37992. */
  37993. Ext.define('Ext.carousel.Item', {
  37994. extend: 'Ext.Decorator',
  37995. config: {
  37996. baseCls: 'x-carousel-item',
  37997. component: null,
  37998. translatable: true
  37999. }
  38000. });
  38001. /**
  38002. * A private utility class used by Ext.Carousel to create indicators.
  38003. * @private
  38004. */
  38005. Ext.define('Ext.carousel.Indicator', {
  38006. extend: 'Ext.Component',
  38007. xtype : 'carouselindicator',
  38008. alternateClassName: 'Ext.Carousel.Indicator',
  38009. config: {
  38010. /**
  38011. * @cfg
  38012. * @inheritdoc
  38013. */
  38014. baseCls: Ext.baseCSSPrefix + 'carousel-indicator',
  38015. direction: 'horizontal'
  38016. },
  38017. /**
  38018. * @event previous
  38019. * Fires when this indicator is tapped on the left half
  38020. * @param {Ext.carousel.Indicator} this
  38021. */
  38022. /**
  38023. * @event next
  38024. * Fires when this indicator is tapped on the right half
  38025. * @param {Ext.carousel.Indicator} this
  38026. */
  38027. initialize: function() {
  38028. this.callParent();
  38029. this.indicators = [];
  38030. this.element.on({
  38031. tap: 'onTap',
  38032. scope: this
  38033. });
  38034. },
  38035. updateDirection: function(newDirection, oldDirection) {
  38036. var baseCls = this.getBaseCls();
  38037. this.element.replaceCls(oldDirection, newDirection, baseCls);
  38038. if (newDirection === 'horizontal') {
  38039. this.setBottom(0);
  38040. this.setRight(null);
  38041. }
  38042. else {
  38043. this.setRight(0);
  38044. this.setBottom(null);
  38045. }
  38046. },
  38047. addIndicator: function() {
  38048. this.indicators.push(this.element.createChild({
  38049. tag: 'span'
  38050. }));
  38051. },
  38052. removeIndicator: function() {
  38053. var indicators = this.indicators;
  38054. if (indicators.length > 0) {
  38055. indicators.pop().destroy();
  38056. }
  38057. },
  38058. setActiveIndex: function(index) {
  38059. var indicators = this.indicators,
  38060. currentActiveIndex = this.activeIndex,
  38061. currentActiveItem = indicators[currentActiveIndex],
  38062. activeItem = indicators[index],
  38063. baseCls = this.getBaseCls();
  38064. if (currentActiveItem) {
  38065. currentActiveItem.removeCls(baseCls, null, 'active');
  38066. }
  38067. if (activeItem) {
  38068. activeItem.addCls(baseCls, null, 'active');
  38069. }
  38070. this.activeIndex = index;
  38071. return this;
  38072. },
  38073. // @private
  38074. onTap: function(e) {
  38075. var touch = e.touch,
  38076. box = this.element.getPageBox(),
  38077. centerX = box.left + (box.width / 2),
  38078. centerY = box.top + (box.height / 2),
  38079. direction = this.getDirection();
  38080. if ((direction === 'horizontal' && touch.pageX >= centerX) || (direction === 'vertical' && touch.pageY >= centerY)) {
  38081. this.fireEvent('next', this);
  38082. }
  38083. else {
  38084. this.fireEvent('previous', this);
  38085. }
  38086. },
  38087. destroy: function() {
  38088. var indicators = this.indicators,
  38089. i, ln, indicator;
  38090. for (i = 0,ln = indicators.length; i < ln; i++) {
  38091. indicator = indicators[i];
  38092. indicator.destroy();
  38093. }
  38094. indicators.length = 0;
  38095. this.callParent();
  38096. }
  38097. });
  38098. /**
  38099. * @private
  38100. */
  38101. Ext.define('Ext.util.TranslatableGroup', {
  38102. extend: 'Ext.util.translatable.Abstract',
  38103. config: {
  38104. items: [],
  38105. activeIndex: 0,
  38106. itemLength: {
  38107. x: 0,
  38108. y: 0
  38109. }
  38110. },
  38111. applyItems: function(items) {
  38112. return Ext.Array.from(items);
  38113. },
  38114. doTranslate: function(x, y) {
  38115. var items = this.getItems(),
  38116. activeIndex = this.getActiveIndex(),
  38117. itemLength = this.getItemLength(),
  38118. itemLengthX = itemLength.x,
  38119. itemLengthY = itemLength.y,
  38120. useX = typeof x == 'number',
  38121. useY = typeof y == 'number',
  38122. offset, i, ln, item, translateX, translateY;
  38123. for (i = 0, ln = items.length; i < ln; i++) {
  38124. item = items[i];
  38125. if (item) {
  38126. offset = (i - activeIndex);
  38127. if (useX) {
  38128. translateX = x + offset * itemLengthX;
  38129. }
  38130. if (useY) {
  38131. translateY = y + offset * itemLengthY;
  38132. }
  38133. item.translate(translateX, translateY);
  38134. }
  38135. }
  38136. }
  38137. });
  38138. /**
  38139. * @class Ext.carousel.Carousel
  38140. * @author Jacky Nguyen <jacky@sencha.com>
  38141. *
  38142. * Carousels, like [tabs](#!/guide/tabs), are a great way to allow the user to swipe through multiple full-screen pages.
  38143. * A Carousel shows only one of its pages at a time but allows you to swipe through with your finger.
  38144. *
  38145. * Carousels can be oriented either horizontally or vertically and are easy to configure - they just work like any other
  38146. * Container. Here's how to set up a simple horizontal Carousel:
  38147. *
  38148. * @example
  38149. * Ext.create('Ext.Carousel', {
  38150. * fullscreen: true,
  38151. *
  38152. * defaults: {
  38153. * styleHtmlContent: true
  38154. * },
  38155. *
  38156. * items: [
  38157. * {
  38158. * html : 'Item 1',
  38159. * style: 'background-color: #5E99CC'
  38160. * },
  38161. * {
  38162. * html : 'Item 2',
  38163. * style: 'background-color: #759E60'
  38164. * },
  38165. * {
  38166. * html : 'Item 3'
  38167. * }
  38168. * ]
  38169. * });
  38170. *
  38171. * We can also make Carousels orient themselves vertically:
  38172. *
  38173. * @example preview
  38174. * Ext.create('Ext.Carousel', {
  38175. * fullscreen: true,
  38176. * direction: 'vertical',
  38177. *
  38178. * defaults: {
  38179. * styleHtmlContent: true
  38180. * },
  38181. *
  38182. * items: [
  38183. * {
  38184. * html : 'Item 1',
  38185. * style: 'background-color: #759E60'
  38186. * },
  38187. * {
  38188. * html : 'Item 2',
  38189. * style: 'background-color: #5E99CC'
  38190. * }
  38191. * ]
  38192. * });
  38193. *
  38194. * ### Common Configurations
  38195. * * {@link #ui} defines the style of the carousel
  38196. * * {@link #direction} defines the direction of the carousel
  38197. * * {@link #indicator} defines if the indicator show be shown
  38198. *
  38199. * ### Useful Methods
  38200. * * {@link #next} moves to the next card
  38201. * * {@link #previous} moves to the previous card
  38202. * * {@link #setActiveItem} moves to the passed card
  38203. *
  38204. * ## Further Reading
  38205. *
  38206. * For more information about Carousels see the [Carousel guide](#!/guide/carousel).
  38207. *
  38208. * @aside guide carousel
  38209. * @aside example carousel
  38210. */
  38211. Ext.define('Ext.carousel.Carousel', {
  38212. extend: 'Ext.Container',
  38213. alternateClassName: 'Ext.Carousel',
  38214. xtype: 'carousel',
  38215. requires: [
  38216. 'Ext.fx.easing.EaseOut',
  38217. 'Ext.carousel.Item',
  38218. 'Ext.carousel.Indicator',
  38219. 'Ext.util.TranslatableGroup'
  38220. ],
  38221. config: {
  38222. /**
  38223. * @cfg layout
  38224. * Hide layout config in Carousel. It only causes confusion.
  38225. * @accessor
  38226. * @private
  38227. */
  38228. /**
  38229. * @cfg
  38230. * @inheritdoc
  38231. */
  38232. baseCls: 'x-carousel',
  38233. /**
  38234. * @cfg {String} direction
  38235. * The direction of the Carousel, either 'horizontal' or 'vertical'.
  38236. * @accessor
  38237. */
  38238. direction: 'horizontal',
  38239. directionLock: false,
  38240. animation: {
  38241. duration: 250,
  38242. easing: {
  38243. type: 'ease-out'
  38244. }
  38245. },
  38246. /**
  38247. * @cfg {Boolean} indicator
  38248. * Provides an indicator while toggling between child items to let the user
  38249. * know where they are in the card stack.
  38250. * @accessor
  38251. */
  38252. indicator: true,
  38253. /**
  38254. * @cfg {String} ui
  38255. * Style options for Carousel. Default is 'dark'. 'light' is also available.
  38256. * @accessor
  38257. */
  38258. ui: 'dark',
  38259. itemConfig: {},
  38260. bufferSize: 1,
  38261. itemLength: null
  38262. },
  38263. itemLength: 0,
  38264. offset: 0,
  38265. flickStartOffset: 0,
  38266. flickStartTime: 0,
  38267. dragDirection: 0,
  38268. count: 0,
  38269. painted: false,
  38270. activeIndex: -1,
  38271. beforeInitialize: function() {
  38272. this.element.on({
  38273. dragstart: 'onDragStart',
  38274. drag: 'onDrag',
  38275. dragend: 'onDragEnd',
  38276. scope: this
  38277. });
  38278. this.element.on('resize', 'onSizeChange', this);
  38279. this.carouselItems = [];
  38280. this.orderedCarouselItems = [];
  38281. this.inactiveCarouselItems = [];
  38282. this.hiddenTranslation = 0;
  38283. },
  38284. updateBufferSize: function(size) {
  38285. var ItemClass = Ext.carousel.Item,
  38286. total = size * 2 + 1,
  38287. isRendered = this.isRendered(),
  38288. innerElement = this.innerElement,
  38289. items = this.carouselItems,
  38290. ln = items.length,
  38291. itemConfig = this.getItemConfig(),
  38292. itemLength = this.getItemLength(),
  38293. direction = this.getDirection(),
  38294. setterName = direction === 'horizontal' ? 'setWidth' : 'setHeight',
  38295. i, item;
  38296. for (i = ln; i < total; i++) {
  38297. item = Ext.factory(itemConfig, ItemClass);
  38298. if (itemLength) {
  38299. item[setterName].call(item, itemLength);
  38300. }
  38301. item.setLayoutSizeFlags(this.LAYOUT_BOTH);
  38302. items.push(item);
  38303. innerElement.append(item.renderElement);
  38304. if (isRendered && item.setRendered(true)) {
  38305. item.fireEvent('renderedchange', this, item, true);
  38306. }
  38307. }
  38308. this.getTranslatable().setActiveIndex(size);
  38309. },
  38310. setRendered: function(rendered) {
  38311. var wasRendered = this.rendered;
  38312. if (rendered !== wasRendered) {
  38313. this.rendered = rendered;
  38314. var items = this.items.items,
  38315. carouselItems = this.carouselItems,
  38316. i, ln, item;
  38317. for (i = 0,ln = items.length; i < ln; i++) {
  38318. item = items[i];
  38319. if (!item.isInnerItem()) {
  38320. item.setRendered(rendered);
  38321. }
  38322. }
  38323. for (i = 0,ln = carouselItems.length; i < ln; i++) {
  38324. carouselItems[i].setRendered(rendered);
  38325. }
  38326. return true;
  38327. }
  38328. return false;
  38329. },
  38330. onSizeChange: function() {
  38331. this.refreshSizing();
  38332. this.refreshCarouselItems();
  38333. this.refreshActiveItem();
  38334. },
  38335. onItemAdd: function(item, index) {
  38336. this.callParent(arguments);
  38337. var innerIndex = this.getInnerItems().indexOf(item),
  38338. indicator = this.getIndicator();
  38339. if (indicator && item.isInnerItem()) {
  38340. indicator.addIndicator();
  38341. }
  38342. if (innerIndex <= this.getActiveIndex()) {
  38343. this.refreshActiveIndex();
  38344. }
  38345. if (this.isIndexDirty(innerIndex) && !this.isItemsInitializing) {
  38346. this.refreshActiveItem();
  38347. }
  38348. },
  38349. doItemLayoutAdd: function(item) {
  38350. if (item.isInnerItem()) {
  38351. return;
  38352. }
  38353. this.callParent(arguments);
  38354. },
  38355. onItemRemove: function(item, index) {
  38356. this.callParent(arguments);
  38357. var innerIndex = this.getInnerItems().indexOf(item),
  38358. indicator = this.getIndicator(),
  38359. carouselItems = this.carouselItems,
  38360. i, ln, carouselItem;
  38361. if (item.isInnerItem() && indicator) {
  38362. indicator.removeIndicator();
  38363. }
  38364. if (innerIndex <= this.getActiveIndex()) {
  38365. this.refreshActiveIndex();
  38366. }
  38367. if (this.isIndexDirty(innerIndex)) {
  38368. for (i = 0,ln = carouselItems.length; i < ln; i++) {
  38369. carouselItem = carouselItems[i];
  38370. if (carouselItem.getComponent() === item) {
  38371. carouselItem.setComponent(null);
  38372. }
  38373. }
  38374. this.refreshActiveItem();
  38375. }
  38376. },
  38377. doItemLayoutRemove: function(item) {
  38378. if (item.isInnerItem()) {
  38379. return;
  38380. }
  38381. this.callParent(arguments);
  38382. },
  38383. onInnerItemMove: function(item, toIndex, fromIndex) {
  38384. if ((this.isIndexDirty(toIndex) || this.isIndexDirty(fromIndex))) {
  38385. this.refreshActiveItem();
  38386. }
  38387. },
  38388. doItemLayoutMove: function(item) {
  38389. if (item.isInnerItem()) {
  38390. return;
  38391. }
  38392. this.callParent(arguments);
  38393. },
  38394. isIndexDirty: function(index) {
  38395. var activeIndex = this.getActiveIndex(),
  38396. bufferSize = this.getBufferSize();
  38397. return (index >= activeIndex - bufferSize && index <= activeIndex + bufferSize);
  38398. },
  38399. getTranslatable: function() {
  38400. var translatable = this.translatable;
  38401. if (!translatable) {
  38402. this.translatable = translatable = new Ext.util.TranslatableGroup;
  38403. translatable.setItems(this.orderedCarouselItems);
  38404. translatable.on('animationend', 'onAnimationEnd', this);
  38405. }
  38406. return translatable;
  38407. },
  38408. onDragStart: function(e) {
  38409. var direction = this.getDirection(),
  38410. absDeltaX = e.absDeltaX,
  38411. absDeltaY = e.absDeltaY,
  38412. directionLock = this.getDirectionLock();
  38413. this.isDragging = true;
  38414. if (directionLock) {
  38415. if ((direction === 'horizontal' && absDeltaX > absDeltaY)
  38416. || (direction === 'vertical' && absDeltaY > absDeltaX)) {
  38417. e.stopPropagation();
  38418. }
  38419. else {
  38420. this.isDragging = false;
  38421. return;
  38422. }
  38423. }
  38424. this.getTranslatable().stopAnimation();
  38425. this.dragStartOffset = this.offset;
  38426. this.dragDirection = 0;
  38427. },
  38428. onDrag: function(e) {
  38429. if (!this.isDragging) {
  38430. return;
  38431. }
  38432. var startOffset = this.dragStartOffset,
  38433. direction = this.getDirection(),
  38434. delta = direction === 'horizontal' ? e.deltaX : e.deltaY,
  38435. lastOffset = this.offset,
  38436. flickStartTime = this.flickStartTime,
  38437. dragDirection = this.dragDirection,
  38438. now = Ext.Date.now(),
  38439. currentActiveIndex = this.getActiveIndex(),
  38440. maxIndex = this.getMaxItemIndex(),
  38441. lastDragDirection = dragDirection,
  38442. offset;
  38443. if ((currentActiveIndex === 0 && delta > 0) || (currentActiveIndex === maxIndex && delta < 0)) {
  38444. delta *= 0.5;
  38445. }
  38446. offset = startOffset + delta;
  38447. if (offset > lastOffset) {
  38448. dragDirection = 1;
  38449. }
  38450. else if (offset < lastOffset) {
  38451. dragDirection = -1;
  38452. }
  38453. if (dragDirection !== lastDragDirection || (now - flickStartTime) > 300) {
  38454. this.flickStartOffset = lastOffset;
  38455. this.flickStartTime = now;
  38456. }
  38457. this.dragDirection = dragDirection;
  38458. this.setOffset(offset);
  38459. },
  38460. onDragEnd: function(e) {
  38461. if (!this.isDragging) {
  38462. return;
  38463. }
  38464. this.onDrag(e);
  38465. this.isDragging = false;
  38466. var now = Ext.Date.now(),
  38467. itemLength = this.itemLength,
  38468. threshold = itemLength / 2,
  38469. offset = this.offset,
  38470. activeIndex = this.getActiveIndex(),
  38471. maxIndex = this.getMaxItemIndex(),
  38472. animationDirection = 0,
  38473. flickDistance = offset - this.flickStartOffset,
  38474. flickDuration = now - this.flickStartTime,
  38475. indicator = this.getIndicator(),
  38476. velocity;
  38477. if (flickDuration > 0 && Math.abs(flickDistance) >= 10) {
  38478. velocity = flickDistance / flickDuration;
  38479. if (Math.abs(velocity) >= 1) {
  38480. if (velocity < 0 && activeIndex < maxIndex) {
  38481. animationDirection = -1;
  38482. }
  38483. else if (velocity > 0 && activeIndex > 0) {
  38484. animationDirection = 1;
  38485. }
  38486. }
  38487. }
  38488. if (animationDirection === 0) {
  38489. if (activeIndex < maxIndex && offset < -threshold) {
  38490. animationDirection = -1;
  38491. }
  38492. else if (activeIndex > 0 && offset > threshold) {
  38493. animationDirection = 1;
  38494. }
  38495. }
  38496. if (indicator) {
  38497. indicator.setActiveIndex(activeIndex - animationDirection);
  38498. }
  38499. this.animationDirection = animationDirection;
  38500. this.setOffsetAnimated(animationDirection * itemLength);
  38501. },
  38502. applyAnimation: function(animation) {
  38503. animation.easing = Ext.factory(animation.easing, Ext.fx.easing.EaseOut);
  38504. return animation;
  38505. },
  38506. updateDirection: function(direction) {
  38507. var indicator = this.getIndicator();
  38508. this.currentAxis = (direction === 'horizontal') ? 'x' : 'y';
  38509. if (indicator) {
  38510. indicator.setDirection(direction);
  38511. }
  38512. },
  38513. /**
  38514. * @private
  38515. * @chainable
  38516. */
  38517. setOffset: function(offset) {
  38518. this.offset = offset;
  38519. this.getTranslatable().translateAxis(this.currentAxis, offset + this.itemOffset);
  38520. return this;
  38521. },
  38522. /**
  38523. * @private
  38524. * @return {Ext.carousel.Carousel} this
  38525. * @chainable
  38526. */
  38527. setOffsetAnimated: function(offset) {
  38528. var indicator = this.getIndicator();
  38529. if (indicator) {
  38530. indicator.setActiveIndex(this.getActiveIndex() - this.animationDirection);
  38531. }
  38532. this.offset = offset;
  38533. this.getTranslatable().translateAxis(this.currentAxis, offset + this.itemOffset, this.getAnimation());
  38534. return this;
  38535. },
  38536. onAnimationEnd: function(translatable) {
  38537. var currentActiveIndex = this.getActiveIndex(),
  38538. animationDirection = this.animationDirection,
  38539. axis = this.currentAxis,
  38540. currentOffset = translatable[axis],
  38541. itemLength = this.itemLength,
  38542. offset;
  38543. if (animationDirection === -1) {
  38544. offset = itemLength + currentOffset;
  38545. }
  38546. else if (animationDirection === 1) {
  38547. offset = currentOffset - itemLength;
  38548. }
  38549. else {
  38550. offset = currentOffset;
  38551. }
  38552. offset -= this.itemOffset;
  38553. this.offset = offset;
  38554. this.setActiveItem(currentActiveIndex - animationDirection);
  38555. },
  38556. refresh: function() {
  38557. this.refreshSizing();
  38558. this.refreshActiveItem();
  38559. },
  38560. refreshSizing: function() {
  38561. var element = this.element,
  38562. itemLength = this.getItemLength(),
  38563. translatableItemLength = {
  38564. x: 0,
  38565. y: 0
  38566. },
  38567. itemOffset, containerSize;
  38568. if (this.getDirection() === 'horizontal') {
  38569. containerSize = element.getWidth();
  38570. }
  38571. else {
  38572. containerSize = element.getHeight();
  38573. }
  38574. this.hiddenTranslation = -containerSize;
  38575. if (itemLength === null) {
  38576. itemLength = containerSize;
  38577. itemOffset = 0;
  38578. }
  38579. else {
  38580. itemOffset = (containerSize - itemLength) / 2;
  38581. }
  38582. this.itemLength = itemLength;
  38583. this.itemOffset = itemOffset;
  38584. translatableItemLength[this.currentAxis] = itemLength;
  38585. this.getTranslatable().setItemLength(translatableItemLength);
  38586. },
  38587. refreshOffset: function() {
  38588. this.setOffset(this.offset);
  38589. },
  38590. refreshActiveItem: function() {
  38591. this.doSetActiveItem(this.getActiveItem());
  38592. },
  38593. /**
  38594. * Returns the index of the currently active card.
  38595. * @return {Number} The index of the currently active card.
  38596. */
  38597. getActiveIndex: function() {
  38598. return this.activeIndex;
  38599. },
  38600. refreshActiveIndex: function() {
  38601. this.activeIndex = this.getInnerItemIndex(this.getActiveItem());
  38602. },
  38603. refreshCarouselItems: function() {
  38604. var items = this.carouselItems,
  38605. i, ln, item;
  38606. for (i = 0,ln = items.length; i < ln; i++) {
  38607. item = items[i];
  38608. item.getTranslatable().refresh();
  38609. }
  38610. this.refreshInactiveCarouselItems();
  38611. },
  38612. refreshInactiveCarouselItems: function() {
  38613. var items = this.inactiveCarouselItems,
  38614. hiddenTranslation = this.hiddenTranslation,
  38615. axis = this.currentAxis,
  38616. i, ln, item;
  38617. for (i = 0,ln = items.length; i < ln; i++) {
  38618. item = items[i];
  38619. item.translateAxis(axis, hiddenTranslation);
  38620. }
  38621. },
  38622. /**
  38623. * @private
  38624. * @return {Number}
  38625. */
  38626. getMaxItemIndex: function() {
  38627. return this.innerItems.length - 1;
  38628. },
  38629. /**
  38630. * @private
  38631. * @return {Number}
  38632. */
  38633. getInnerItemIndex: function(item) {
  38634. return this.innerItems.indexOf(item);
  38635. },
  38636. /**
  38637. * @private
  38638. * @return {Object}
  38639. */
  38640. getInnerItemAt: function(index) {
  38641. return this.innerItems[index];
  38642. },
  38643. /**
  38644. * @private
  38645. * @return {Object}
  38646. */
  38647. applyActiveItem: function() {
  38648. var activeItem = this.callParent(arguments),
  38649. activeIndex;
  38650. if (activeItem) {
  38651. activeIndex = this.getInnerItemIndex(activeItem);
  38652. if (activeIndex !== -1) {
  38653. this.activeIndex = activeIndex;
  38654. return activeItem;
  38655. }
  38656. }
  38657. },
  38658. doSetActiveItem: function(activeItem) {
  38659. var activeIndex = this.getActiveIndex(),
  38660. maxIndex = this.getMaxItemIndex(),
  38661. indicator = this.getIndicator(),
  38662. bufferSize = this.getBufferSize(),
  38663. carouselItems = this.carouselItems.slice(),
  38664. orderedCarouselItems = this.orderedCarouselItems,
  38665. visibleIndexes = {},
  38666. visibleItems = {},
  38667. visibleItem, component, id, i, index, ln, carouselItem;
  38668. if (carouselItems.length === 0) {
  38669. return;
  38670. }
  38671. this.callParent(arguments);
  38672. orderedCarouselItems.length = 0;
  38673. if (activeItem) {
  38674. id = activeItem.getId();
  38675. visibleItems[id] = activeItem;
  38676. visibleIndexes[id] = bufferSize;
  38677. if (activeIndex > 0) {
  38678. for (i = 1; i <= bufferSize; i++) {
  38679. index = activeIndex - i;
  38680. if (index >= 0) {
  38681. visibleItem = this.getInnerItemAt(index);
  38682. id = visibleItem.getId();
  38683. visibleItems[id] = visibleItem;
  38684. visibleIndexes[id] = bufferSize - i;
  38685. }
  38686. else {
  38687. break;
  38688. }
  38689. }
  38690. }
  38691. if (activeIndex < maxIndex) {
  38692. for (i = 1; i <= bufferSize; i++) {
  38693. index = activeIndex + i;
  38694. if (index <= maxIndex) {
  38695. visibleItem = this.getInnerItemAt(index);
  38696. id = visibleItem.getId();
  38697. visibleItems[id] = visibleItem;
  38698. visibleIndexes[id] = bufferSize + i;
  38699. }
  38700. else {
  38701. break;
  38702. }
  38703. }
  38704. }
  38705. for (i = 0,ln = carouselItems.length; i < ln; i++) {
  38706. carouselItem = carouselItems[i];
  38707. component = carouselItem.getComponent();
  38708. if (component) {
  38709. id = component.getId();
  38710. if (visibleIndexes.hasOwnProperty(id)) {
  38711. carouselItems.splice(i, 1);
  38712. i--;
  38713. ln--;
  38714. delete visibleItems[id];
  38715. orderedCarouselItems[visibleIndexes[id]] = carouselItem;
  38716. }
  38717. }
  38718. }
  38719. for (id in visibleItems) {
  38720. if (visibleItems.hasOwnProperty(id)) {
  38721. visibleItem = visibleItems[id];
  38722. carouselItem = carouselItems.pop();
  38723. carouselItem.setComponent(visibleItem);
  38724. orderedCarouselItems[visibleIndexes[id]] = carouselItem;
  38725. }
  38726. }
  38727. }
  38728. this.inactiveCarouselItems.length = 0;
  38729. this.inactiveCarouselItems = carouselItems;
  38730. this.refreshOffset();
  38731. this.refreshInactiveCarouselItems();
  38732. if (indicator) {
  38733. indicator.setActiveIndex(activeIndex);
  38734. }
  38735. },
  38736. /**
  38737. * Switches to the next card.
  38738. * @return {Ext.carousel.Carousel} this
  38739. * @chainable
  38740. */
  38741. next: function() {
  38742. this.setOffset(0);
  38743. if (this.activeIndex === this.getMaxItemIndex()) {
  38744. return this;
  38745. }
  38746. this.animationDirection = -1;
  38747. this.setOffsetAnimated(-this.itemLength);
  38748. return this;
  38749. },
  38750. /**
  38751. * Switches to the previous card.
  38752. * @return {Ext.carousel.Carousel} this
  38753. * @chainable
  38754. */
  38755. previous: function() {
  38756. this.setOffset(0);
  38757. if (this.activeIndex === 0) {
  38758. return this;
  38759. }
  38760. this.animationDirection = 1;
  38761. this.setOffsetAnimated(this.itemLength);
  38762. return this;
  38763. },
  38764. // @private
  38765. applyIndicator: function(indicator, currentIndicator) {
  38766. return Ext.factory(indicator, Ext.carousel.Indicator, currentIndicator);
  38767. },
  38768. // @private
  38769. updateIndicator: function(indicator) {
  38770. if (indicator) {
  38771. this.insertFirst(indicator);
  38772. indicator.setUi(this.getUi());
  38773. indicator.on({
  38774. next: 'next',
  38775. previous: 'previous',
  38776. scope: this
  38777. });
  38778. }
  38779. },
  38780. destroy: function() {
  38781. var carouselItems = this.carouselItems.slice();
  38782. this.carouselItems.length = 0;
  38783. Ext.destroy(carouselItems, this.getIndicator(), this.translatable);
  38784. this.callParent();
  38785. delete this.carouselItems;
  38786. }
  38787. }, function() {
  38788. });
  38789. /**
  38790. * @class Ext.carousel.Infinite
  38791. * @author Jacky Nguyen <jacky@sencha.com>
  38792. * @private
  38793. *
  38794. * The true infinite implementation of Carousel, private for now until it's stable to be public
  38795. */
  38796. Ext.define('Ext.carousel.Infinite', {
  38797. extend: 'Ext.carousel.Carousel',
  38798. config: {
  38799. indicator: null,
  38800. maxItemIndex: Infinity,
  38801. innerItemConfig: {}
  38802. },
  38803. applyIndicator: function(indicator) {
  38804. //<debug error>
  38805. if (indicator) {
  38806. Ext.Logger.error("'indicator' in Infinite Carousel implementation is not currently supported", this);
  38807. }
  38808. //</debug>
  38809. return;
  38810. },
  38811. updateBufferSize: function(size) {
  38812. this.callParent(arguments);
  38813. var total = size * 2 + 1,
  38814. ln = this.innerItems.length,
  38815. innerItemConfig = this.getInnerItemConfig(),
  38816. i;
  38817. this.isItemsInitializing = true;
  38818. for (i = ln; i < total; i++) {
  38819. this.doAdd(this.factoryItem(innerItemConfig));
  38820. }
  38821. this.isItemsInitializing = false;
  38822. this.rebuildInnerIndexes();
  38823. this.refreshActiveItem();
  38824. },
  38825. updateMaxItemIndex: function(maxIndex, oldMaxIndex) {
  38826. if (oldMaxIndex !== undefined) {
  38827. var activeIndex = this.getActiveIndex();
  38828. if (activeIndex > maxIndex) {
  38829. this.setActiveItem(maxIndex);
  38830. }
  38831. else {
  38832. this.rebuildInnerIndexes(activeIndex);
  38833. this.refreshActiveItem();
  38834. }
  38835. }
  38836. },
  38837. rebuildInnerIndexes: function(activeIndex) {
  38838. var indexToItem = this.innerIndexToItem,
  38839. idToIndex = this.innerIdToIndex,
  38840. items = this.innerItems.slice(),
  38841. ln = items.length,
  38842. bufferSize = this.getBufferSize(),
  38843. maxIndex = this.getMaxItemIndex(),
  38844. changedIndexes = [],
  38845. i, oldIndex, index, id, item;
  38846. if (activeIndex === undefined) {
  38847. this.innerIndexToItem = indexToItem = {};
  38848. this.innerIdToIndex = idToIndex = {};
  38849. for (i = 0; i < ln; i++) {
  38850. item = items[i];
  38851. id = item.getId();
  38852. idToIndex[id] = i;
  38853. indexToItem[i] = item;
  38854. this.fireEvent('itemindexchange', this, item, i, -1);
  38855. }
  38856. }
  38857. else {
  38858. for (i = activeIndex - bufferSize; i <= activeIndex + bufferSize; i++) {
  38859. if (i >= 0 && i <= maxIndex) {
  38860. if (indexToItem.hasOwnProperty(i)) {
  38861. Ext.Array.remove(items, indexToItem[i]);
  38862. continue;
  38863. }
  38864. changedIndexes.push(i);
  38865. }
  38866. }
  38867. for (i = 0,ln = changedIndexes.length; i < ln; i++) {
  38868. item = items[i];
  38869. id = item.getId();
  38870. index = changedIndexes[i];
  38871. oldIndex = idToIndex[id];
  38872. delete indexToItem[oldIndex];
  38873. idToIndex[id] = index;
  38874. indexToItem[index] = item;
  38875. this.fireEvent('itemindexchange', this, item, index, oldIndex);
  38876. }
  38877. }
  38878. },
  38879. reset: function() {
  38880. this.rebuildInnerIndexes();
  38881. this.setActiveItem(0);
  38882. },
  38883. refreshItems: function() {
  38884. var items = this.innerItems,
  38885. idToIndex = this.innerIdToIndex,
  38886. index, item, i, ln;
  38887. for (i = 0,ln = items.length; i < ln; i++) {
  38888. item = items[i];
  38889. index = idToIndex[item.getId()];
  38890. this.fireEvent('itemindexchange', this, item, index, -1);
  38891. }
  38892. },
  38893. getInnerItemIndex: function(item) {
  38894. var index = this.innerIdToIndex[item.getId()];
  38895. return (typeof index == 'number') ? index : -1;
  38896. },
  38897. getInnerItemAt: function(index) {
  38898. return this.innerIndexToItem[index];
  38899. },
  38900. applyActiveItem: function(activeItem) {
  38901. this.getItems();
  38902. this.getBufferSize();
  38903. var maxIndex = this.getMaxItemIndex(),
  38904. currentActiveIndex = this.getActiveIndex();
  38905. if (typeof activeItem == 'number') {
  38906. activeItem = Math.max(0, Math.min(activeItem, maxIndex));
  38907. if (activeItem === currentActiveIndex) {
  38908. return;
  38909. }
  38910. this.activeIndex = activeItem;
  38911. this.rebuildInnerIndexes(activeItem);
  38912. activeItem = this.getInnerItemAt(activeItem);
  38913. }
  38914. if (activeItem) {
  38915. return this.callParent([activeItem]);
  38916. }
  38917. }
  38918. });
  38919. /**
  38920. * @private
  38921. */
  38922. Ext.define('Ext.mixin.Sortable', {
  38923. extend: 'Ext.mixin.Mixin',
  38924. requires: [
  38925. 'Ext.util.Sorter'
  38926. ],
  38927. mixinConfig: {
  38928. id: 'sortable'
  38929. },
  38930. config: {
  38931. /**
  38932. * @cfg {Array} sorters
  38933. * An array with sorters. A sorter can be an instance of {@link Ext.util.Sorter}, a string
  38934. * indicating a property name, an object representing an Ext.util.Sorter configuration,
  38935. * or a sort function.
  38936. */
  38937. sorters: null,
  38938. /**
  38939. * @cfg {String} defaultSortDirection
  38940. * The default sort direction to use if one is not specified.
  38941. */
  38942. defaultSortDirection: "ASC",
  38943. /**
  38944. * @cfg {String} sortRoot
  38945. * The root inside each item in which the properties exist that we want to sort on.
  38946. * This is useful for sorting records in which the data exists inside a `data` property.
  38947. */
  38948. sortRoot: null
  38949. },
  38950. /**
  38951. * @property {Boolean} dirtySortFn
  38952. * A flag indicating whether the currently cashed sort function is still valid.
  38953. * @readonly
  38954. */
  38955. dirtySortFn: false,
  38956. /**
  38957. * @property currentSortFn
  38958. * This is the cached sorting function which is a generated function that calls all the
  38959. * configured sorters in the correct order.
  38960. * @readonly
  38961. */
  38962. sortFn: null,
  38963. /**
  38964. * @property {Boolean} sorted
  38965. * A read-only flag indicating if this object is sorted.
  38966. * @readonly
  38967. */
  38968. sorted: false,
  38969. applySorters: function(sorters, collection) {
  38970. if (!collection) {
  38971. collection = this.createSortersCollection();
  38972. }
  38973. collection.clear();
  38974. this.sorted = false;
  38975. if (sorters) {
  38976. this.addSorters(sorters);
  38977. }
  38978. return collection;
  38979. },
  38980. createSortersCollection: function() {
  38981. this._sorters = Ext.create('Ext.util.Collection', function(sorter) {
  38982. return sorter.getId();
  38983. });
  38984. return this._sorters;
  38985. },
  38986. /**
  38987. * This method adds a sorter.
  38988. * @param {Ext.util.Sorter/String/Function/Object} sorter Can be an instance of
  38989. * Ext.util.Sorter, a string indicating a property name, an object representing an Ext.util.Sorter
  38990. * configuration, or a sort function.
  38991. * @param {String} defaultDirection The default direction for each sorter in the array. Defaults
  38992. * to the value of {@link #defaultSortDirection}. Can be either 'ASC' or 'DESC'.
  38993. */
  38994. addSorter: function(sorter, defaultDirection) {
  38995. this.addSorters([sorter], defaultDirection);
  38996. },
  38997. /**
  38998. * This method adds all the sorters in a passed array.
  38999. * @param {Array} sorters An array with sorters. A sorter can be an instance of Ext.util.Sorter, a string
  39000. * indicating a property name, an object representing an Ext.util.Sorter configuration,
  39001. * or a sort function.
  39002. * @param {String} defaultDirection The default direction for each sorter in the array. Defaults
  39003. * to the value of {@link #defaultSortDirection}. Can be either 'ASC' or 'DESC'.
  39004. */
  39005. addSorters: function(sorters, defaultDirection) {
  39006. var currentSorters = this.getSorters();
  39007. return this.insertSorters(currentSorters ? currentSorters.length : 0, sorters, defaultDirection);
  39008. },
  39009. /**
  39010. * This method adds a sorter at a given index.
  39011. * @param {Number} index The index at which to insert the sorter.
  39012. * @param {Ext.util.Sorter/String/Function/Object} sorter Can be an instance of Ext.util.Sorter,
  39013. * a string indicating a property name, an object representing an Ext.util.Sorter configuration,
  39014. * or a sort function.
  39015. * @param {String} defaultDirection The default direction for each sorter in the array. Defaults
  39016. * to the value of {@link #defaultSortDirection}. Can be either 'ASC' or 'DESC'.
  39017. */
  39018. insertSorter: function(index, sorter, defaultDirection) {
  39019. return this.insertSorters(index, [sorter], defaultDirection);
  39020. },
  39021. /**
  39022. * This method inserts all the sorters in the passed array at the given index.
  39023. * @param {Number} index The index at which to insert the sorters.
  39024. * @param {Array} sorters Can be an instance of Ext.util.Sorter, a string indicating a property name,
  39025. * an object representing an Ext.util.Sorter configuration, or a sort function.
  39026. * @param {String} defaultDirection The default direction for each sorter in the array. Defaults
  39027. * to the value of {@link #defaultSortDirection}. Can be either 'ASC' or 'DESC'.
  39028. */
  39029. insertSorters: function(index, sorters, defaultDirection) {
  39030. // We begin by making sure we are dealing with an array of sorters
  39031. if (!Ext.isArray(sorters)) {
  39032. sorters = [sorters];
  39033. }
  39034. var ln = sorters.length,
  39035. direction = defaultDirection || this.getDefaultSortDirection(),
  39036. sortRoot = this.getSortRoot(),
  39037. currentSorters = this.getSorters(),
  39038. newSorters = [],
  39039. sorterConfig, i, sorter, currentSorter;
  39040. if (!currentSorters) {
  39041. // This will guarantee that we get the collection
  39042. currentSorters = this.createSortersCollection();
  39043. }
  39044. // We first have to convert every sorter into a proper Sorter instance
  39045. for (i = 0; i < ln; i++) {
  39046. sorter = sorters[i];
  39047. sorterConfig = {
  39048. direction: direction,
  39049. root: sortRoot
  39050. };
  39051. // If we are dealing with a string we assume it is a property they want to sort on.
  39052. if (typeof sorter === 'string') {
  39053. currentSorter = currentSorters.get(sorter);
  39054. if (!currentSorter) {
  39055. sorterConfig.property = sorter;
  39056. } else {
  39057. if (defaultDirection) {
  39058. currentSorter.setDirection(defaultDirection);
  39059. } else {
  39060. // If we already have a sorter for this property we just toggle its direction.
  39061. currentSorter.toggle();
  39062. }
  39063. continue;
  39064. }
  39065. }
  39066. // If it is a function, we assume its a sorting function.
  39067. else if (Ext.isFunction(sorter)) {
  39068. sorterConfig.sorterFn = sorter;
  39069. }
  39070. // If we are dealing with an object, we assume its a Sorter configuration. In this case
  39071. // we create an instance of Sorter passing this configuration.
  39072. else if (Ext.isObject(sorter)) {
  39073. if (!sorter.isSorter) {
  39074. if (sorter.fn) {
  39075. sorter.sorterFn = sorter.fn;
  39076. delete sorter.fn;
  39077. }
  39078. sorterConfig = Ext.apply(sorterConfig, sorter);
  39079. }
  39080. else {
  39081. newSorters.push(sorter);
  39082. if (!sorter.getRoot()) {
  39083. sorter.setRoot(sortRoot);
  39084. }
  39085. continue;
  39086. }
  39087. }
  39088. // Finally we get to the point where it has to be invalid
  39089. // <debug>
  39090. else {
  39091. Ext.Logger.warn('Invalid sorter specified:', sorter);
  39092. }
  39093. // </debug>
  39094. // If a sorter config was created, make it an instance
  39095. sorter = Ext.create('Ext.util.Sorter', sorterConfig);
  39096. newSorters.push(sorter);
  39097. }
  39098. // Now lets add the newly created sorters.
  39099. for (i = 0, ln = newSorters.length; i < ln; i++) {
  39100. currentSorters.insert(index + i, newSorters[i]);
  39101. }
  39102. this.dirtySortFn = true;
  39103. if (currentSorters.length) {
  39104. this.sorted = true;
  39105. }
  39106. return currentSorters;
  39107. },
  39108. /**
  39109. * This method removes a sorter.
  39110. * @param {Ext.util.Sorter/String/Function/Object} sorter Can be an instance of Ext.util.Sorter,
  39111. * a string indicating a property name, an object representing an Ext.util.Sorter configuration,
  39112. * or a sort function.
  39113. */
  39114. removeSorter: function(sorter) {
  39115. return this.removeSorters([sorter]);
  39116. },
  39117. /**
  39118. * This method removes all the sorters in a passed array.
  39119. * @param {Array} sorters Each value in the array can be a string (property name),
  39120. * function (sorterFn) or {@link Ext.util.Sorter Sorter} instance.
  39121. */
  39122. removeSorters: function(sorters) {
  39123. // We begin by making sure we are dealing with an array of sorters
  39124. if (!Ext.isArray(sorters)) {
  39125. sorters = [sorters];
  39126. }
  39127. var ln = sorters.length,
  39128. currentSorters = this.getSorters(),
  39129. i, sorter;
  39130. for (i = 0; i < ln; i++) {
  39131. sorter = sorters[i];
  39132. if (typeof sorter === 'string') {
  39133. currentSorters.removeAtKey(sorter);
  39134. }
  39135. else if (typeof sorter === 'function') {
  39136. currentSorters.each(function(item) {
  39137. if (item.getSorterFn() === sorter) {
  39138. currentSorters.remove(item);
  39139. }
  39140. });
  39141. }
  39142. else if (sorter.isSorter) {
  39143. currentSorters.remove(sorter);
  39144. }
  39145. }
  39146. if (!currentSorters.length) {
  39147. this.sorted = false;
  39148. }
  39149. },
  39150. /**
  39151. * This updates the cached sortFn based on the current sorters.
  39152. * @return {Function} The generated sort function.
  39153. * @private
  39154. */
  39155. updateSortFn: function() {
  39156. var sorters = this.getSorters().items;
  39157. this.sortFn = function(r1, r2) {
  39158. var ln = sorters.length,
  39159. result, i;
  39160. // We loop over each sorter and check if r1 should be before or after r2
  39161. for (i = 0; i < ln; i++) {
  39162. result = sorters[i].sort.call(this, r1, r2);
  39163. // If the result is -1 or 1 at this point it means that the sort is done.
  39164. // Only if they are equal (0) we continue to see if a next sort function
  39165. // actually might find a winner.
  39166. if (result !== 0) {
  39167. break;
  39168. }
  39169. }
  39170. return result;
  39171. };
  39172. this.dirtySortFn = false;
  39173. return this.sortFn;
  39174. },
  39175. /**
  39176. * Returns an up to date sort function.
  39177. * @return {Function} The sort function.
  39178. */
  39179. getSortFn: function() {
  39180. if (this.dirtySortFn) {
  39181. return this.updateSortFn();
  39182. }
  39183. return this.sortFn;
  39184. },
  39185. /**
  39186. * This method will sort an array based on the currently configured {@link #sorters}.
  39187. * @param {Array} data The array you want to have sorted.
  39188. * @return {Array} The array you passed after it is sorted.
  39189. */
  39190. sort: function(data) {
  39191. Ext.Array.sort(data, this.getSortFn());
  39192. return data;
  39193. },
  39194. /**
  39195. * This method returns the index that a given item would be inserted into a given array based
  39196. * on the current sorters.
  39197. * @param {Array} items The array that you want to insert the item into.
  39198. * @param {Mixed} item The item that you want to insert into the items array.
  39199. * @return {Number} The index for the given item in the given array based on the current sorters.
  39200. */
  39201. findInsertionIndex: function(items, item, sortFn) {
  39202. var start = 0,
  39203. end = items.length - 1,
  39204. sorterFn = sortFn || this.getSortFn(),
  39205. middle,
  39206. comparison;
  39207. while (start <= end) {
  39208. middle = (start + end) >> 1;
  39209. comparison = sorterFn(item, items[middle]);
  39210. if (comparison >= 0) {
  39211. start = middle + 1;
  39212. } else if (comparison < 0) {
  39213. end = middle - 1;
  39214. }
  39215. }
  39216. return start;
  39217. }
  39218. });
  39219. /**
  39220. * @private
  39221. */
  39222. Ext.define('Ext.mixin.Filterable', {
  39223. extend: 'Ext.mixin.Mixin',
  39224. requires: [
  39225. 'Ext.util.Filter'
  39226. ],
  39227. mixinConfig: {
  39228. id: 'filterable'
  39229. },
  39230. config: {
  39231. /**
  39232. * @cfg {Array} filters
  39233. * An array with filters. A filter can be an instance of Ext.util.Filter,
  39234. * an object representing an Ext.util.Filter configuration, or a filter function.
  39235. */
  39236. filters: null,
  39237. /**
  39238. * @cfg {String} filterRoot
  39239. * The root inside each item in which the properties exist that we want to filter on.
  39240. * This is useful for filtering records in which the data exists inside a 'data' property.
  39241. */
  39242. filterRoot: null
  39243. },
  39244. /**
  39245. * @property {Boolean} dirtyFilterFn
  39246. * A flag indicating whether the currently cashed filter function is still valid.
  39247. * @readonly
  39248. */
  39249. dirtyFilterFn: false,
  39250. /**
  39251. * @property currentSortFn
  39252. * This is the cached sorting function which is a generated function that calls all the
  39253. * configured sorters in the correct order.
  39254. * @readonly
  39255. */
  39256. filterFn: null,
  39257. /**
  39258. * @property {Boolean} filtered
  39259. * A read-only flag indicating if this object is filtered.
  39260. * @readonly
  39261. */
  39262. filtered: false,
  39263. applyFilters: function(filters, collection) {
  39264. if (!collection) {
  39265. collection = this.createFiltersCollection();
  39266. }
  39267. collection.clear();
  39268. this.filtered = false;
  39269. this.dirtyFilterFn = true;
  39270. if (filters) {
  39271. this.addFilters(filters);
  39272. }
  39273. return collection;
  39274. },
  39275. createFiltersCollection: function() {
  39276. this._filters = Ext.create('Ext.util.Collection', function(filter) {
  39277. return filter.getId();
  39278. });
  39279. return this._filters;
  39280. },
  39281. /**
  39282. * This method adds a filter.
  39283. * @param {Ext.util.Sorter/Function/Object} filter Can be an instance of Ext.util.Filter,
  39284. * an object representing an Ext.util.Filter configuration, or a filter function.
  39285. */
  39286. addFilter: function(filter) {
  39287. this.addFilters([filter]);
  39288. },
  39289. /**
  39290. * This method adds all the filters in a passed array.
  39291. * @param {Array} filters An array with filters. A filter can be an instance of {@link Ext.util.Filter},
  39292. * an object representing an Ext.util.Filter configuration, or a filter function.
  39293. * @return {Object}
  39294. */
  39295. addFilters: function(filters) {
  39296. var currentFilters = this.getFilters();
  39297. return this.insertFilters(currentFilters ? currentFilters.length : 0, filters);
  39298. },
  39299. /**
  39300. * This method adds a filter at a given index.
  39301. * @param {Number} index The index at which to insert the filter.
  39302. * @param {Ext.util.Sorter/Function/Object} filter Can be an instance of {@link Ext.util.Filter},
  39303. * an object representing an Ext.util.Filter configuration, or a filter function.
  39304. * @return {Object}
  39305. */
  39306. insertFilter: function(index, filter) {
  39307. return this.insertFilters(index, [filter]);
  39308. },
  39309. /**
  39310. * This method inserts all the filters in the passed array at the given index.
  39311. * @param {Number} index The index at which to insert the filters.
  39312. * @param {Array} filters Each filter can be an instance of {@link Ext.util.Filter},
  39313. * an object representing an Ext.util.Filter configuration, or a filter function.
  39314. * @return {Array}
  39315. */
  39316. insertFilters: function(index, filters) {
  39317. // We begin by making sure we are dealing with an array of sorters
  39318. if (!Ext.isArray(filters)) {
  39319. filters = [filters];
  39320. }
  39321. var ln = filters.length,
  39322. filterRoot = this.getFilterRoot(),
  39323. currentFilters = this.getFilters(),
  39324. newFilters = [],
  39325. filterConfig, i, filter;
  39326. if (!currentFilters) {
  39327. currentFilters = this.createFiltersCollection();
  39328. }
  39329. // We first have to convert every sorter into a proper Sorter instance
  39330. for (i = 0; i < ln; i++) {
  39331. filter = filters[i];
  39332. filterConfig = {
  39333. root: filterRoot
  39334. };
  39335. if (Ext.isFunction(filter)) {
  39336. filterConfig.filterFn = filter;
  39337. }
  39338. // If we are dealing with an object, we assume its a Sorter configuration. In this case
  39339. // we create an instance of Sorter passing this configuration.
  39340. else if (Ext.isObject(filter)) {
  39341. if (!filter.isFilter) {
  39342. if (filter.fn) {
  39343. filter.filterFn = filter.fn;
  39344. delete filter.fn;
  39345. }
  39346. filterConfig = Ext.apply(filterConfig, filter);
  39347. }
  39348. else {
  39349. newFilters.push(filter);
  39350. if (!filter.getRoot()) {
  39351. filter.setRoot(filterRoot);
  39352. }
  39353. continue;
  39354. }
  39355. }
  39356. // Finally we get to the point where it has to be invalid
  39357. // <debug>
  39358. else {
  39359. Ext.Logger.warn('Invalid filter specified:', filter);
  39360. }
  39361. // </debug>
  39362. // If a sorter config was created, make it an instance
  39363. filter = Ext.create('Ext.util.Filter', filterConfig);
  39364. newFilters.push(filter);
  39365. }
  39366. // Now lets add the newly created sorters.
  39367. for (i = 0, ln = newFilters.length; i < ln; i++) {
  39368. currentFilters.insert(index + i, newFilters[i]);
  39369. }
  39370. this.dirtyFilterFn = true;
  39371. if (currentFilters.length) {
  39372. this.filtered = true;
  39373. }
  39374. return currentFilters;
  39375. },
  39376. /**
  39377. * This method removes all the filters in a passed array.
  39378. * @param {Array} filters Each value in the array can be a string (property name),
  39379. * function (sorterFn), an object containing a property and value keys or
  39380. * {@link Ext.util.Sorter Sorter} instance.
  39381. */
  39382. removeFilters: function(filters) {
  39383. // We begin by making sure we are dealing with an array of sorters
  39384. if (!Ext.isArray(filters)) {
  39385. filters = [filters];
  39386. }
  39387. var ln = filters.length,
  39388. currentFilters = this.getFilters(),
  39389. i, filter;
  39390. for (i = 0; i < ln; i++) {
  39391. filter = filters[i];
  39392. if (typeof filter === 'string') {
  39393. currentFilters.each(function(item) {
  39394. if (item.getProperty() === filter) {
  39395. currentFilters.remove(item);
  39396. }
  39397. });
  39398. }
  39399. else if (typeof filter === 'function') {
  39400. currentFilters.each(function(item) {
  39401. if (item.getFilterFn() === filter) {
  39402. currentFilters.remove(item);
  39403. }
  39404. });
  39405. }
  39406. else {
  39407. if (filter.isFilter) {
  39408. currentFilters.remove(filter);
  39409. }
  39410. else if (filter.property !== undefined && filter.value !== undefined) {
  39411. currentFilters.each(function(item) {
  39412. if (item.getProperty() === filter.property && item.getValue() === filter.value) {
  39413. currentFilters.remove(item);
  39414. }
  39415. });
  39416. }
  39417. }
  39418. }
  39419. if (!currentFilters.length) {
  39420. this.filtered = false;
  39421. }
  39422. },
  39423. /**
  39424. * This updates the cached sortFn based on the current sorters.
  39425. * @return {Function} sortFn The generated sort function.
  39426. * @private
  39427. */
  39428. updateFilterFn: function() {
  39429. var filters = this.getFilters().items;
  39430. this.filterFn = function(item) {
  39431. var isMatch = true,
  39432. length = filters.length,
  39433. i;
  39434. for (i = 0; i < length; i++) {
  39435. var filter = filters[i],
  39436. fn = filter.getFilterFn(),
  39437. scope = filter.getScope() || this;
  39438. isMatch = isMatch && fn.call(scope, item);
  39439. }
  39440. return isMatch;
  39441. };
  39442. this.dirtyFilterFn = false;
  39443. return this.filterFn;
  39444. },
  39445. /**
  39446. * This method will sort an array based on the currently configured {@link Ext.data.Store#sorters sorters}.
  39447. * @param {Array} data The array you want to have sorted.
  39448. * @return {Array} The array you passed after it is sorted.
  39449. */
  39450. filter: function(data) {
  39451. return this.getFilters().length ? Ext.Array.filter(data, this.getFilterFn()) : data;
  39452. },
  39453. isFiltered: function(item) {
  39454. return this.getFilters().length ? !this.getFilterFn()(item) : false;
  39455. },
  39456. /**
  39457. * Returns an up to date sort function.
  39458. * @return {Function} sortFn The sort function.
  39459. */
  39460. getFilterFn: function() {
  39461. if (this.dirtyFilterFn) {
  39462. return this.updateFilterFn();
  39463. }
  39464. return this.filterFn;
  39465. }
  39466. });
  39467. /**
  39468. * @private
  39469. */
  39470. Ext.define('Ext.util.Collection', {
  39471. /**
  39472. * @cfg {Object[]} filters
  39473. * Array of {@link Ext.util.Filter Filters} for this collection.
  39474. */
  39475. /**
  39476. * @cfg {Object[]} sorters
  39477. * Array of {@link Ext.util.Sorter Sorters} for this collection.
  39478. */
  39479. config: {
  39480. autoFilter: true,
  39481. autoSort: true
  39482. },
  39483. mixins: {
  39484. sortable: 'Ext.mixin.Sortable',
  39485. filterable: 'Ext.mixin.Filterable'
  39486. },
  39487. constructor: function(keyFn, config) {
  39488. var me = this;
  39489. /**
  39490. * @property {Array} [all=[]]
  39491. * An array containing all the items (unsorted, unfiltered)
  39492. */
  39493. me.all = [];
  39494. /**
  39495. * @property {Array} [items=[]]
  39496. * An array containing the filtered items (sorted)
  39497. */
  39498. me.items = [];
  39499. /**
  39500. * @property {Array} [keys=[]]
  39501. * An array containing all the filtered keys (sorted)
  39502. */
  39503. me.keys = [];
  39504. /**
  39505. * @property {Object} [indices={}]
  39506. * An object used as map to get a sorted and filtered index of an item
  39507. */
  39508. me.indices = {};
  39509. /**
  39510. * @property {Object} [map={}]
  39511. * An object used as map to get an object based on its key
  39512. */
  39513. me.map = {};
  39514. /**
  39515. * @property {Number} [length=0]
  39516. * The count of items in the collection filtered and sorted
  39517. */
  39518. me.length = 0;
  39519. if (keyFn) {
  39520. me.getKey = keyFn;
  39521. }
  39522. this.initConfig(config);
  39523. },
  39524. updateAutoSort: function(autoSort, oldAutoSort) {
  39525. if (oldAutoSort === false && autoSort && this.items.length) {
  39526. this.sort();
  39527. }
  39528. },
  39529. updateAutoFilter: function(autoFilter, oldAutoFilter) {
  39530. if (oldAutoFilter === false && autoFilter && this.all.length) {
  39531. this.filter();
  39532. }
  39533. },
  39534. insertSorters: function() {
  39535. // We override the insertSorters method that exists on the Sortable mixin. This method always
  39536. // gets called whenever you add or insert a new sorter. We do this because we actually want
  39537. // to sort right after this happens.
  39538. this.mixins.sortable.insertSorters.apply(this, arguments);
  39539. if (this.getAutoSort() && this.items.length) {
  39540. this.sort();
  39541. }
  39542. return this;
  39543. },
  39544. removeSorters: function(sorters) {
  39545. // We override the removeSorters method that exists on the Sortable mixin. This method always
  39546. // gets called whenever you remove a sorter. If we are still sorted after we removed this sorter,
  39547. // then we have to resort the whole collection.
  39548. this.mixins.sortable.removeSorters.call(this, sorters);
  39549. if (this.sorted && this.getAutoSort() && this.items.length) {
  39550. this.sort();
  39551. }
  39552. return this;
  39553. },
  39554. applyFilters: function(filters) {
  39555. var collection = this.mixins.filterable.applyFilters.call(this, filters);
  39556. if (!filters && this.all.length && this.getAutoFilter()) {
  39557. this.filter();
  39558. }
  39559. return collection;
  39560. },
  39561. addFilters: function(filters) {
  39562. // We override the insertFilters method that exists on the Filterable mixin. This method always
  39563. // gets called whenever you add or insert a new filter. We do this because we actually want
  39564. // to filter right after this happens.
  39565. this.mixins.filterable.addFilters.call(this, filters);
  39566. if (this.items.length && this.getAutoFilter()) {
  39567. this.filter();
  39568. }
  39569. return this;
  39570. },
  39571. removeFilters: function(filters) {
  39572. // We override the removeFilters method that exists on the Filterable mixin. This method always
  39573. // gets called whenever you remove a filter. If we are still filtered after we removed this filter,
  39574. // then we have to re-filter the whole collection.
  39575. this.mixins.filterable.removeFilters.call(this, filters);
  39576. if (this.filtered && this.all.length && this.getAutoFilter()) {
  39577. this.filter();
  39578. }
  39579. return this;
  39580. },
  39581. /**
  39582. * This method will sort a collection based on the currently configured sorters.
  39583. * @param {Object} property
  39584. * @param {Object} value
  39585. * @param {Object} anyMatch
  39586. * @param {Object} caseSensitive
  39587. * @return {Array}
  39588. */
  39589. filter: function(property, value, anyMatch, caseSensitive) {
  39590. // Support for the simple case of filtering by property/value
  39591. if (property) {
  39592. if (Ext.isString(property)) {
  39593. this.addFilters({
  39594. property : property,
  39595. value : value,
  39596. anyMatch : anyMatch,
  39597. caseSensitive: caseSensitive
  39598. });
  39599. return this.items;
  39600. }
  39601. else {
  39602. this.addFilters(property);
  39603. return this.items;
  39604. }
  39605. }
  39606. this.items = this.mixins.filterable.filter.call(this, this.all.slice());
  39607. this.updateAfterFilter();
  39608. if (this.sorted && this.getAutoSort()) {
  39609. this.sort();
  39610. }
  39611. },
  39612. updateAfterFilter: function() {
  39613. var items = this.items,
  39614. keys = this.keys,
  39615. indices = this.indices = {},
  39616. ln = items.length,
  39617. i, item, key;
  39618. keys.length = 0;
  39619. for (i = 0; i < ln; i++) {
  39620. item = items[i];
  39621. key = this.getKey(item);
  39622. indices[key] = i;
  39623. keys[i] = key;
  39624. }
  39625. this.length = items.length;
  39626. this.dirtyIndices = false;
  39627. },
  39628. sort: function(sorters, defaultDirection) {
  39629. var items = this.items,
  39630. keys = this.keys,
  39631. indices = this.indices,
  39632. ln = items.length,
  39633. i, item, key;
  39634. // If we pass sorters to this method we have to add them first.
  39635. // Because adding a sorter automatically sorts the items collection
  39636. // we can just return items after we have added the sorters
  39637. if (sorters) {
  39638. this.addSorters(sorters, defaultDirection);
  39639. return this.items;
  39640. }
  39641. // We save the keys temporarily on each item
  39642. for (i = 0; i < ln; i++) {
  39643. items[i]._current_key = keys[i];
  39644. }
  39645. // Now we sort our items array
  39646. this.handleSort(items);
  39647. // And finally we update our keys and indices
  39648. for (i = 0; i < ln; i++) {
  39649. item = items[i];
  39650. key = item._current_key;
  39651. keys[i] = key;
  39652. indices[key] = i;
  39653. delete item._current_key;
  39654. }
  39655. this.dirtyIndices = true;
  39656. },
  39657. handleSort: function(items) {
  39658. this.mixins.sortable.sort.call(this, items);
  39659. },
  39660. /**
  39661. * Adds an item to the collection. Fires the {@link #add} event when complete.
  39662. * @param {String} key
  39663. *
  39664. * The key to associate with the item, or the new item.
  39665. *
  39666. * If a {@link #getKey} implementation was specified for this MixedCollection, or if the key of the stored items is
  39667. * in a property called **id**, the MixedCollection will be able to _derive_ the key for the new item. In this case
  39668. * just pass the new item in this parameter.
  39669. * @param {Object} item The item to add.
  39670. * @return {Object} The item added.
  39671. */
  39672. add: function(key, item) {
  39673. var me = this,
  39674. filtered = this.filtered,
  39675. sorted = this.sorted,
  39676. all = this.all,
  39677. items = this.items,
  39678. keys = this.keys,
  39679. indices = this.indices,
  39680. filterable = this.mixins.filterable,
  39681. currentLength = items.length,
  39682. index = currentLength;
  39683. if (arguments.length == 1) {
  39684. item = key;
  39685. key = me.getKey(item);
  39686. }
  39687. if (typeof key != 'undefined' && key !== null) {
  39688. if (typeof me.map[key] != 'undefined') {
  39689. return me.replace(key, item);
  39690. }
  39691. me.map[key] = item;
  39692. }
  39693. all.push(item);
  39694. if (filtered && this.getAutoFilter() && filterable.isFiltered.call(me, item)) {
  39695. return null;
  39696. }
  39697. me.length++;
  39698. if (sorted && this.getAutoSort()) {
  39699. index = this.findInsertionIndex(items, item);
  39700. }
  39701. if (index !== currentLength) {
  39702. this.dirtyIndices = true;
  39703. Ext.Array.splice(keys, index, 0, key);
  39704. Ext.Array.splice(items, index, 0, item);
  39705. } else {
  39706. indices[key] = currentLength;
  39707. keys.push(key);
  39708. items.push(item);
  39709. }
  39710. return item;
  39711. },
  39712. /**
  39713. * MixedCollection has a generic way to fetch keys if you implement getKey. The default implementation simply
  39714. * returns **`item.id`** but you can provide your own implementation to return a different value as in the following
  39715. * examples:
  39716. *
  39717. * // normal way
  39718. * var mc = new Ext.util.MixedCollection();
  39719. * mc.add(someEl.dom.id, someEl);
  39720. * mc.add(otherEl.dom.id, otherEl);
  39721. * //and so on
  39722. *
  39723. * // using getKey
  39724. * var mc = new Ext.util.MixedCollection();
  39725. * mc.getKey = function(el){
  39726. * return el.dom.id;
  39727. * };
  39728. * mc.add(someEl);
  39729. * mc.add(otherEl);
  39730. *
  39731. * // or via the constructor
  39732. * var mc = new Ext.util.MixedCollection(false, function(el){
  39733. * return el.dom.id;
  39734. * });
  39735. * mc.add(someEl);
  39736. * mc.add(otherEl);
  39737. * @param {Object} item The item for which to find the key.
  39738. * @return {Object} The key for the passed item.
  39739. */
  39740. getKey: function(item) {
  39741. return item.id;
  39742. },
  39743. /**
  39744. * Replaces an item in the collection. Fires the {@link #replace} event when complete.
  39745. * @param {String} oldKey
  39746. *
  39747. * The key associated with the item to replace, or the replacement item.
  39748. *
  39749. * If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key of your stored items is
  39750. * in a property called **id**, then the MixedCollection will be able to _derive_ the key of the replacement item.
  39751. * If you want to replace an item with one having the same key value, then just pass the replacement item in this
  39752. * parameter.
  39753. * @param {Object} item {Object} item (optional) If the first parameter passed was a key, the item to associate with
  39754. * that key.
  39755. * @return {Object} The new item.
  39756. */
  39757. replace: function(oldKey, item) {
  39758. var me = this,
  39759. sorted = me.sorted,
  39760. filtered = me.filtered,
  39761. filterable = me.mixins.filterable,
  39762. items = me.items,
  39763. keys = me.keys,
  39764. all = me.all,
  39765. map = me.map,
  39766. returnItem = null,
  39767. oldItemsLn = items.length,
  39768. oldItem, index, newKey;
  39769. if (arguments.length == 1) {
  39770. item = oldKey;
  39771. oldKey = newKey = me.getKey(item);
  39772. } else {
  39773. newKey = me.getKey(item);
  39774. }
  39775. oldItem = map[oldKey];
  39776. if (typeof oldKey == 'undefined' || oldKey === null || typeof oldItem == 'undefined') {
  39777. return me.add(newKey, item);
  39778. }
  39779. me.map[newKey] = item;
  39780. if (newKey !== oldKey) {
  39781. delete me.map[oldKey];
  39782. }
  39783. if (sorted && me.getAutoSort()) {
  39784. Ext.Array.remove(items, oldItem);
  39785. Ext.Array.remove(keys, oldKey);
  39786. Ext.Array.remove(all, oldItem);
  39787. all.push(item);
  39788. me.dirtyIndices = true;
  39789. if (filtered && me.getAutoFilter()) {
  39790. // If the item is now filtered we check if it was not filtered
  39791. // before. If that is the case then we subtract from the length
  39792. if (filterable.isFiltered.call(me, item)) {
  39793. if (oldItemsLn !== items.length) {
  39794. me.length--;
  39795. }
  39796. return null;
  39797. }
  39798. // If the item was filtered, but now it is not anymore then we
  39799. // add to the length
  39800. else if (oldItemsLn === items.length) {
  39801. me.length++;
  39802. returnItem = item;
  39803. }
  39804. }
  39805. index = this.findInsertionIndex(items, item);
  39806. Ext.Array.splice(keys, index, 0, newKey);
  39807. Ext.Array.splice(items, index, 0, item);
  39808. } else {
  39809. if (filtered) {
  39810. if (me.getAutoFilter() && filterable.isFiltered.call(me, item)) {
  39811. if (items.indexOf(oldItem) !== -1) {
  39812. Ext.Array.remove(items, oldItem);
  39813. Ext.Array.remove(keys, oldKey);
  39814. me.length--;
  39815. me.dirtyIndices = true;
  39816. }
  39817. return null;
  39818. }
  39819. else if (items.indexOf(oldItem) === -1) {
  39820. items.push(item);
  39821. keys.push(newKey);
  39822. me.indices[newKey] = me.length;
  39823. me.length++;
  39824. return item;
  39825. }
  39826. }
  39827. index = me.items.indexOf(oldItem);
  39828. keys[index] = newKey;
  39829. items[index] = item;
  39830. this.dirtyIndices = true;
  39831. }
  39832. return returnItem;
  39833. },
  39834. /**
  39835. * Adds all elements of an Array or an Object to the collection.
  39836. * @param {Object/Array} objs An Object containing properties which will be added to the collection, or an Array of
  39837. * values, each of which are added to the collection. Functions references will be added to the collection if {@link
  39838. * Ext.util.MixedCollection#allowFunctions allowFunctions} has been set to `true`.
  39839. */
  39840. addAll: function(addItems) {
  39841. var me = this,
  39842. filtered = me.filtered,
  39843. sorted = me.sorted,
  39844. all = me.all,
  39845. items = me.items,
  39846. keys = me.keys,
  39847. map = me.map,
  39848. autoFilter = me.getAutoFilter(),
  39849. autoSort = me.getAutoSort(),
  39850. newKeys = [],
  39851. newItems = [],
  39852. filterable = me.mixins.filterable,
  39853. addedItems = [],
  39854. ln, key, i, item;
  39855. if (Ext.isObject(addItems)) {
  39856. for (key in addItems) {
  39857. if (addItems.hasOwnProperty(key)) {
  39858. newItems.push(items[key]);
  39859. newKeys.push(key);
  39860. }
  39861. }
  39862. } else {
  39863. newItems = addItems;
  39864. ln = addItems.length;
  39865. for (i = 0; i < ln; i++) {
  39866. newKeys.push(me.getKey(addItems[i]));
  39867. }
  39868. }
  39869. for (i = 0; i < ln; i++) {
  39870. key = newKeys[i];
  39871. item = newItems[i];
  39872. if (typeof key != 'undefined' && key !== null) {
  39873. if (typeof map[key] != 'undefined') {
  39874. me.replace(key, item);
  39875. continue;
  39876. }
  39877. map[key] = item;
  39878. }
  39879. all.push(item);
  39880. if (filtered && autoFilter && filterable.isFiltered.call(me, item)) {
  39881. continue;
  39882. }
  39883. me.length++;
  39884. keys.push(key);
  39885. items.push(item);
  39886. addedItems.push(item);
  39887. }
  39888. if (addedItems.length) {
  39889. me.dirtyIndices = true;
  39890. if (sorted && autoSort) {
  39891. me.sort();
  39892. }
  39893. return addedItems;
  39894. }
  39895. return null;
  39896. },
  39897. /**
  39898. * Executes the specified function once for every item in the collection.
  39899. * The function should return a Boolean value. Returning `false` from the function will stop the iteration.
  39900. * @param {Function} fn The function to execute for each item.
  39901. * @param {Mixed} fn.item The collection item.
  39902. * @param {Number} fn.index The item's index.
  39903. * @param {Number} fn.length The total number of items in the collection.
  39904. * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to the current
  39905. * item in the iteration.
  39906. */
  39907. each: function(fn, scope) {
  39908. var items = this.items.slice(), // each safe for removal
  39909. i = 0,
  39910. len = items.length,
  39911. item;
  39912. for (; i < len; i++) {
  39913. item = items[i];
  39914. if (fn.call(scope || item, item, i, len) === false) {
  39915. break;
  39916. }
  39917. }
  39918. },
  39919. /**
  39920. * Executes the specified function once for every key in the collection, passing each key, and its associated item
  39921. * as the first two parameters.
  39922. * @param {Function} fn The function to execute for each item.
  39923. * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to the browser
  39924. * window.
  39925. */
  39926. eachKey: function(fn, scope) {
  39927. var keys = this.keys,
  39928. items = this.items,
  39929. ln = keys.length, i;
  39930. for (i = 0; i < ln; i++) {
  39931. fn.call(scope || window, keys[i], items[i], i, ln);
  39932. }
  39933. },
  39934. /**
  39935. * Returns the first item in the collection which elicits a `true` return value from the passed selection function.
  39936. * @param {Function} fn The selection function to execute for each item.
  39937. * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to the browser
  39938. * window.
  39939. * @return {Object} The first item in the collection which returned `true` from the selection function.
  39940. */
  39941. findBy: function(fn, scope) {
  39942. var keys = this.keys,
  39943. items = this.items,
  39944. i = 0,
  39945. len = items.length;
  39946. for (; i < len; i++) {
  39947. if (fn.call(scope || window, items[i], keys[i])) {
  39948. return items[i];
  39949. }
  39950. }
  39951. return null;
  39952. },
  39953. /**
  39954. * Filter by a function. Returns a _new_ collection that has been filtered. The passed function will be called with
  39955. * each object in the collection. If the function returns `true`, the value is included otherwise it is filtered.
  39956. * @param {Function} fn The function to be called.
  39957. * @param fn.o The object.
  39958. * @param fn.k The key.
  39959. * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to this
  39960. * MixedCollection.
  39961. * @return {Ext.util.MixedCollection} The new filtered collection
  39962. */
  39963. filterBy: function(fn, scope) {
  39964. var me = this,
  39965. newCollection = new this.self(),
  39966. keys = me.keys,
  39967. items = me.all,
  39968. length = items.length,
  39969. i;
  39970. newCollection.getKey = me.getKey;
  39971. for (i = 0; i < length; i++) {
  39972. if (fn.call(scope || me, items[i], me.getKey(items[i]))) {
  39973. newCollection.add(keys[i], items[i]);
  39974. }
  39975. }
  39976. return newCollection;
  39977. },
  39978. /**
  39979. * Inserts an item at the specified index in the collection. Fires the {@link #add} event when complete.
  39980. * @param {Number} index The index to insert the item at.
  39981. * @param {String} key The key to associate with the new item, or the item itself.
  39982. * @param {Object} item If the second parameter was a key, the new item.
  39983. * @return {Object} The item inserted.
  39984. */
  39985. insert: function(index, key, item) {
  39986. var me = this,
  39987. sorted = this.sorted,
  39988. map = this.map,
  39989. filtered = this.filtered;
  39990. if (arguments.length == 2) {
  39991. item = key;
  39992. key = me.getKey(item);
  39993. }
  39994. if (index >= me.length || (sorted && me.getAutoSort())) {
  39995. return me.add(key, item);
  39996. }
  39997. if (typeof key != 'undefined' && key !== null) {
  39998. if (typeof map[key] != 'undefined') {
  39999. me.replace(key, item);
  40000. return false;
  40001. }
  40002. map[key] = item;
  40003. }
  40004. this.all.push(item);
  40005. if (filtered && this.getAutoFilter() && this.mixins.filterable.isFiltered.call(me, item)) {
  40006. return null;
  40007. }
  40008. me.length++;
  40009. Ext.Array.splice(me.items, index, 0, item);
  40010. Ext.Array.splice(me.keys, index, 0, key);
  40011. me.dirtyIndices = true;
  40012. return item;
  40013. },
  40014. insertAll: function(index, insertItems) {
  40015. if (index >= this.items.length || (this.sorted && this.getAutoSort())) {
  40016. return this.addAll(insertItems);
  40017. }
  40018. var me = this,
  40019. filtered = this.filtered,
  40020. sorted = this.sorted,
  40021. all = this.all,
  40022. items = this.items,
  40023. keys = this.keys,
  40024. map = this.map,
  40025. autoFilter = this.getAutoFilter(),
  40026. autoSort = this.getAutoSort(),
  40027. newKeys = [],
  40028. newItems = [],
  40029. addedItems = [],
  40030. filterable = this.mixins.filterable,
  40031. insertedUnfilteredItem = false,
  40032. ln, key, i, item;
  40033. if (sorted && this.getAutoSort()) {
  40034. // <debug>
  40035. Ext.Logger.error('Inserting a collection of items into a sorted Collection is invalid. Please just add these items or remove the sorters.');
  40036. // </debug>
  40037. }
  40038. if (Ext.isObject(insertItems)) {
  40039. for (key in insertItems) {
  40040. if (insertItems.hasOwnProperty(key)) {
  40041. newItems.push(items[key]);
  40042. newKeys.push(key);
  40043. }
  40044. }
  40045. } else {
  40046. newItems = insertItems;
  40047. ln = insertItems.length;
  40048. for (i = 0; i < ln; i++) {
  40049. newKeys.push(me.getKey(insertItems[i]));
  40050. }
  40051. }
  40052. for (i = 0; i < ln; i++) {
  40053. key = newKeys[i];
  40054. item = newItems[i];
  40055. if (typeof key != 'undefined' && key !== null) {
  40056. if (typeof map[key] != 'undefined') {
  40057. me.replace(key, item);
  40058. continue;
  40059. }
  40060. map[key] = item;
  40061. }
  40062. all.push(item);
  40063. if (filtered && autoFilter && filterable.isFiltered.call(me, item)) {
  40064. continue;
  40065. }
  40066. me.length++;
  40067. Ext.Array.splice(items, index + i, 0, item);
  40068. Ext.Array.splice(keys, index + i, 0, key);
  40069. insertedUnfilteredItem = true;
  40070. addedItems.push(item);
  40071. }
  40072. if (insertedUnfilteredItem) {
  40073. this.dirtyIndices = true;
  40074. if (sorted && autoSort) {
  40075. this.sort();
  40076. }
  40077. return addedItems;
  40078. }
  40079. return null;
  40080. },
  40081. /**
  40082. * Remove an item from the collection.
  40083. * @param {Object} item The item to remove.
  40084. * @return {Object} The item removed or `false` if no item was removed.
  40085. */
  40086. remove: function(item) {
  40087. var index = this.items.indexOf(item);
  40088. if (index === -1) {
  40089. Ext.Array.remove(this.all, item);
  40090. if (typeof this.getKey == 'function') {
  40091. var key = this.getKey(item);
  40092. if (key !== undefined) {
  40093. delete this.map[key];
  40094. }
  40095. }
  40096. return item;
  40097. }
  40098. return this.removeAt(this.items.indexOf(item));
  40099. },
  40100. /**
  40101. * Remove all items in the passed array from the collection.
  40102. * @param {Array} items An array of items to be removed.
  40103. * @return {Ext.util.MixedCollection} this object
  40104. */
  40105. removeAll: function(items) {
  40106. if (items) {
  40107. var ln = items.length, i;
  40108. for (i = 0; i < ln; i++) {
  40109. this.remove(items[i]);
  40110. }
  40111. }
  40112. return this;
  40113. },
  40114. /**
  40115. * Remove an item from a specified index in the collection. Fires the {@link #remove} event when complete.
  40116. * @param {Number} index The index within the collection of the item to remove.
  40117. * @return {Object} The item removed or `false` if no item was removed.
  40118. */
  40119. removeAt: function(index) {
  40120. var me = this,
  40121. items = me.items,
  40122. keys = me.keys,
  40123. all = me.all,
  40124. item, key;
  40125. if (index < me.length && index >= 0) {
  40126. item = items[index];
  40127. key = keys[index];
  40128. if (typeof key != 'undefined') {
  40129. delete me.map[key];
  40130. }
  40131. Ext.Array.erase(items, index, 1);
  40132. Ext.Array.erase(keys, index, 1);
  40133. Ext.Array.remove(all, item);
  40134. delete me.indices[key];
  40135. me.length--;
  40136. this.dirtyIndices = true;
  40137. return item;
  40138. }
  40139. return false;
  40140. },
  40141. /**
  40142. * Removed an item associated with the passed key from the collection.
  40143. * @param {String} key The key of the item to remove.
  40144. * @return {Object/Boolean} The item removed or `false` if no item was removed.
  40145. */
  40146. removeAtKey: function(key) {
  40147. return this.removeAt(this.indexOfKey(key));
  40148. },
  40149. /**
  40150. * Returns the number of items in the collection.
  40151. * @return {Number} the number of items in the collection.
  40152. */
  40153. getCount: function() {
  40154. return this.length;
  40155. },
  40156. /**
  40157. * Returns index within the collection of the passed Object.
  40158. * @param {Object} item The item to find the index of.
  40159. * @return {Number} Index of the item. Returns -1 if not found.
  40160. */
  40161. indexOf: function(item) {
  40162. if (this.dirtyIndices) {
  40163. this.updateIndices();
  40164. }
  40165. var index = this.indices[this.getKey(item)];
  40166. return (index === undefined) ? -1 : index;
  40167. },
  40168. /**
  40169. * Returns index within the collection of the passed key.
  40170. * @param {String} key The key to find the index of.
  40171. * @return {Number} Index of the key.
  40172. */
  40173. indexOfKey: function(key) {
  40174. if (this.dirtyIndices) {
  40175. this.updateIndices();
  40176. }
  40177. var index = this.indices[key];
  40178. return (index === undefined) ? -1 : index;
  40179. },
  40180. updateIndices: function() {
  40181. var items = this.items,
  40182. ln = items.length,
  40183. indices = this.indices = {},
  40184. i, item, key;
  40185. for (i = 0; i < ln; i++) {
  40186. item = items[i];
  40187. key = this.getKey(item);
  40188. indices[key] = i;
  40189. }
  40190. this.dirtyIndices = false;
  40191. },
  40192. /**
  40193. * Returns the item associated with the passed key OR index. Key has priority over index. This is the equivalent of
  40194. * calling {@link #getByKey} first, then if nothing matched calling {@link #getAt}.
  40195. * @param {String/Number} key The key or index of the item.
  40196. * @return {Object} If the item is found, returns the item. If the item was not found, returns `undefined`. If an item
  40197. * was found, but is a Class, returns `null`.
  40198. */
  40199. get: function(key) {
  40200. var me = this,
  40201. fromMap = me.map[key],
  40202. item;
  40203. if (fromMap !== undefined) {
  40204. item = fromMap;
  40205. }
  40206. else if (typeof key == 'number') {
  40207. item = me.items[key];
  40208. }
  40209. return typeof item != 'function' || me.getAllowFunctions() ? item : null; // for prototype!
  40210. },
  40211. /**
  40212. * Returns the item at the specified index.
  40213. * @param {Number} index The index of the item.
  40214. * @return {Object} The item at the specified index.
  40215. */
  40216. getAt: function(index) {
  40217. return this.items[index];
  40218. },
  40219. /**
  40220. * Returns the item associated with the passed key.
  40221. * @param {String/Number} key The key of the item.
  40222. * @return {Object} The item associated with the passed key.
  40223. */
  40224. getByKey: function(key) {
  40225. return this.map[key];
  40226. },
  40227. /**
  40228. * Returns `true` if the collection contains the passed Object as an item.
  40229. * @param {Object} item The Object to look for in the collection.
  40230. * @return {Boolean} `true` if the collection contains the Object as an item.
  40231. */
  40232. contains: function(item) {
  40233. var key = this.getKey(item);
  40234. if (key) {
  40235. return this.containsKey(key);
  40236. } else {
  40237. return Ext.Array.contains(this.items, item);
  40238. }
  40239. },
  40240. /**
  40241. * Returns `true` if the collection contains the passed Object as a key.
  40242. * @param {String} key The key to look for in the collection.
  40243. * @return {Boolean} `true` if the collection contains the Object as a key.
  40244. */
  40245. containsKey: function(key) {
  40246. return typeof this.map[key] != 'undefined';
  40247. },
  40248. /**
  40249. * Removes all items from the collection. Fires the {@link #clear} event when complete.
  40250. */
  40251. clear: function(){
  40252. var me = this;
  40253. me.length = 0;
  40254. me.items.length = 0;
  40255. me.keys.length = 0;
  40256. me.all.length = 0;
  40257. me.dirtyIndices = true;
  40258. me.indices = {};
  40259. me.map = {};
  40260. },
  40261. /**
  40262. * Returns the first item in the collection.
  40263. * @return {Object} the first item in the collection.
  40264. */
  40265. first: function() {
  40266. return this.items[0];
  40267. },
  40268. /**
  40269. * Returns the last item in the collection.
  40270. * @return {Object} the last item in the collection.
  40271. */
  40272. last: function() {
  40273. return this.items[this.length - 1];
  40274. },
  40275. /**
  40276. * Returns a range of items in this collection
  40277. * @param {Number} [startIndex=0] The starting index.
  40278. * @param {Number} [endIndex=-1] The ending index. Defaults to the last item.
  40279. * @return {Array} An array of items.
  40280. */
  40281. getRange: function(start, end) {
  40282. var me = this,
  40283. items = me.items,
  40284. range = [],
  40285. i;
  40286. if (items.length < 1) {
  40287. return range;
  40288. }
  40289. start = start || 0;
  40290. end = Math.min(typeof end == 'undefined' ? me.length - 1 : end, me.length - 1);
  40291. if (start <= end) {
  40292. for (i = start; i <= end; i++) {
  40293. range[range.length] = items[i];
  40294. }
  40295. } else {
  40296. for (i = start; i >= end; i--) {
  40297. range[range.length] = items[i];
  40298. }
  40299. }
  40300. return range;
  40301. },
  40302. /**
  40303. * Find the index of the first matching object in this collection by a function. If the function returns `true` it
  40304. * is considered a match.
  40305. * @param {Function} fn The function to be called.
  40306. * @param fn.o The object.
  40307. * @param fn.k The key.
  40308. * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to this
  40309. * MixedCollection.
  40310. * @param {Number} [start=0] The index to start searching at.
  40311. * @return {Number} The matched index, or -1 if the item was not found.
  40312. */
  40313. findIndexBy: function(fn, scope, start) {
  40314. var me = this,
  40315. keys = me.keys,
  40316. items = me.items,
  40317. i = start || 0,
  40318. ln = items.length;
  40319. for (; i < ln; i++) {
  40320. if (fn.call(scope || me, items[i], keys[i])) {
  40321. return i;
  40322. }
  40323. }
  40324. return -1;
  40325. },
  40326. /**
  40327. * Creates a shallow copy of this collection
  40328. * @return {Ext.util.MixedCollection}
  40329. */
  40330. clone: function() {
  40331. var me = this,
  40332. copy = new this.self(),
  40333. keys = me.keys,
  40334. items = me.items,
  40335. i = 0,
  40336. ln = items.length;
  40337. for(; i < ln; i++) {
  40338. copy.add(keys[i], items[i]);
  40339. }
  40340. copy.getKey = me.getKey;
  40341. return copy;
  40342. },
  40343. destroy: function() {
  40344. this.callSuper();
  40345. this.clear();
  40346. }
  40347. });
  40348. /**
  40349. * @docauthor Evan Trimboli <evan@sencha.com>
  40350. * @aside guide stores
  40351. *
  40352. * Contains a collection of all stores that are created that have an identifier. An identifier can be assigned by
  40353. * setting the {@link Ext.data.Store#storeId storeId} property. When a store is in the StoreManager, it can be
  40354. * referred to via it's identifier:
  40355. *
  40356. * Ext.create('Ext.data.Store', {
  40357. * model: 'SomeModel',
  40358. * storeId: 'myStore'
  40359. * });
  40360. *
  40361. * var store = Ext.data.StoreManager.lookup('myStore');
  40362. *
  40363. * Also note that the {@link #lookup} method is aliased to {@link Ext#getStore} for convenience.
  40364. *
  40365. * If a store is registered with the StoreManager, you can also refer to the store by it's identifier when registering
  40366. * it with any Component that consumes data from a store:
  40367. *
  40368. * Ext.create('Ext.data.Store', {
  40369. * model: 'SomeModel',
  40370. * storeId: 'myStore'
  40371. * });
  40372. *
  40373. * Ext.create('Ext.view.View', {
  40374. * store: 'myStore'
  40375. * // other configuration here
  40376. * });
  40377. */
  40378. Ext.define('Ext.data.StoreManager', {
  40379. extend: 'Ext.util.Collection',
  40380. alternateClassName: ['Ext.StoreMgr', 'Ext.data.StoreMgr', 'Ext.StoreManager'],
  40381. singleton: true,
  40382. uses: ['Ext.data.ArrayStore', 'Ext.data.Store'],
  40383. /**
  40384. * Registers one or more Stores with the StoreManager. You do not normally need to register stores manually. Any
  40385. * store initialized with a {@link Ext.data.Store#storeId} will be auto-registered.
  40386. * @param {Ext.data.Store...} stores Any number of Store instances.
  40387. */
  40388. register : function() {
  40389. for (var i = 0, s; (s = arguments[i]); i++) {
  40390. this.add(s);
  40391. }
  40392. },
  40393. /**
  40394. * Unregisters one or more Stores with the StoreManager.
  40395. * @param {String/Object...} stores Any number of Store instances or ID-s.
  40396. */
  40397. unregister : function() {
  40398. for (var i = 0, s; (s = arguments[i]); i++) {
  40399. this.remove(this.lookup(s));
  40400. }
  40401. },
  40402. /**
  40403. * Gets a registered Store by id.
  40404. * @param {String/Object} store The `id` of the Store, or a Store instance, or a store configuration.
  40405. * @return {Ext.data.Store}
  40406. */
  40407. lookup : function(store) {
  40408. // handle the case when we are given an array or an array of arrays.
  40409. if (Ext.isArray(store)) {
  40410. var fields = ['field1'],
  40411. expand = !Ext.isArray(store[0]),
  40412. data = store,
  40413. i,
  40414. len;
  40415. if (expand) {
  40416. data = [];
  40417. for (i = 0, len = store.length; i < len; ++i) {
  40418. data.push([store[i]]);
  40419. }
  40420. } else {
  40421. for(i = 2, len = store[0].length; i <= len; ++i){
  40422. fields.push('field' + i);
  40423. }
  40424. }
  40425. return Ext.create('Ext.data.ArrayStore', {
  40426. data : data,
  40427. fields: fields,
  40428. // See https://sencha.jira.com/browse/TOUCH-1541
  40429. autoDestroy: true,
  40430. autoCreated: true,
  40431. expanded: expand
  40432. });
  40433. }
  40434. if (Ext.isString(store)) {
  40435. // store id
  40436. return this.get(store);
  40437. } else {
  40438. // store instance or store config
  40439. if (store instanceof Ext.data.Store) {
  40440. return store;
  40441. } else {
  40442. return Ext.factory(store, Ext.data.Store, null, 'store');
  40443. }
  40444. }
  40445. },
  40446. // getKey implementation for MixedCollection
  40447. getKey : function(o) {
  40448. return o.getStoreId();
  40449. }
  40450. }, function() {
  40451. /**
  40452. * Creates a new store for the given id and config, then registers it with the {@link Ext.data.StoreManager Store Manager}.
  40453. * Sample usage:
  40454. *
  40455. * Ext.regStore('AllUsers', {
  40456. * model: 'User'
  40457. * });
  40458. *
  40459. * // the store can now easily be used throughout the application
  40460. * new Ext.List({
  40461. * store: 'AllUsers'
  40462. * // ...
  40463. * });
  40464. *
  40465. * @param {String} id The id to set on the new store.
  40466. * @param {Object} config The store config.
  40467. * @member Ext
  40468. * @method regStore
  40469. */
  40470. Ext.regStore = function(name, config) {
  40471. var store;
  40472. if (Ext.isObject(name)) {
  40473. config = name;
  40474. } else {
  40475. if (config instanceof Ext.data.Store) {
  40476. config.setStoreId(name);
  40477. } else {
  40478. config.storeId = name;
  40479. }
  40480. }
  40481. if (config instanceof Ext.data.Store) {
  40482. store = config;
  40483. } else {
  40484. store = Ext.create('Ext.data.Store', config);
  40485. }
  40486. return Ext.data.StoreManager.register(store);
  40487. };
  40488. /**
  40489. * Shortcut to {@link Ext.data.StoreManager#lookup}.
  40490. * @member Ext
  40491. * @method getStore
  40492. * @alias Ext.data.StoreManager#lookup
  40493. */
  40494. Ext.getStore = function(name) {
  40495. return Ext.data.StoreManager.lookup(name);
  40496. };
  40497. });
  40498. /**
  40499. * A DataItem is a container for {@link Ext.dataview.DataView} with useComponents: true. It ties together
  40500. * {@link Ext.data.Model records} to its contained Components via a {@link #dataMap dataMap} configuration.
  40501. *
  40502. * For example, lets say you have a `text` configuration which, when applied, gets turned into an instance of an
  40503. * Ext.Component. We want to update the {@link #html} of a sub-component when the 'text' field of the record gets
  40504. * changed.
  40505. *
  40506. * As you can see below, it is simply a matter of setting the key of the object to be the getter of the config
  40507. * (getText), and then give that property a value of an object, which then has 'setHtml' (the html setter) as the key,
  40508. * and 'text' (the field name) as the value. You can continue this for a as many sub-components as you wish.
  40509. *
  40510. * dataMap: {
  40511. * // When the record is updated, get the text configuration, and
  40512. * // call {@link #setHtml} with the 'text' field of the record.
  40513. * getText: {
  40514. * setHtml: 'text'
  40515. * },
  40516. *
  40517. * // When the record is updated, get the userName configuration, and
  40518. * // call {@link #setHtml} with the 'from_user' field of the record.
  40519. * getUserName: {
  40520. * setHtml: 'from_user'
  40521. * },
  40522. *
  40523. * // When the record is updated, get the avatar configuration, and
  40524. * // call `setSrc` with the 'profile_image_url' field of the record.
  40525. * getAvatar: {
  40526. * setSrc: 'profile_image_url'
  40527. * }
  40528. * }
  40529. */
  40530. Ext.define('Ext.dataview.component.DataItem', {
  40531. extend: 'Ext.Container',
  40532. xtype : 'dataitem',
  40533. config: {
  40534. baseCls: Ext.baseCSSPrefix + 'data-item',
  40535. defaultType: 'component',
  40536. /**
  40537. * @cfg {Ext.data.Model} record The model instance of this DataItem. It is controlled by the Component DataView.
  40538. * @accessor
  40539. */
  40540. record: null,
  40541. /**
  40542. * @cfg {String} itemCls
  40543. * An additional CSS class to apply to items within the DataView.
  40544. * @accessor
  40545. */
  40546. itemCls: null,
  40547. /**
  40548. * @cfg dataMap
  40549. * The dataMap allows you to map {@link #record} fields to specific configurations in this component.
  40550. *
  40551. * For example, lets say you have a `text` configuration which, when applied, gets turned into an instance of an Ext.Component.
  40552. * We want to update the {@link #html} of this component when the 'text' field of the record gets changed.
  40553. * For example:
  40554. *
  40555. * dataMap: {
  40556. * getText: {
  40557. * setHtml: 'text'
  40558. * }
  40559. * }
  40560. *
  40561. * In this example, it is simply a matter of setting the key of the object to be the getter of the config (`getText`), and then give that
  40562. * property a value of an object, which then has `setHtml` (the html setter) as the key, and `text` (the field name) as the value.
  40563. */
  40564. dataMap: {},
  40565. /*
  40566. * @private dataview
  40567. */
  40568. dataview: null,
  40569. items: [{
  40570. xtype: 'component'
  40571. }]
  40572. },
  40573. updateBaseCls: function(newBaseCls, oldBaseCls) {
  40574. var me = this;
  40575. me.callParent(arguments);
  40576. },
  40577. updateItemCls: function(newCls, oldCls) {
  40578. if (oldCls) {
  40579. this.removeCls(oldCls);
  40580. }
  40581. if (newCls) {
  40582. this.addCls(newCls);
  40583. }
  40584. },
  40585. doMapData: function(dataMap, data, item) {
  40586. var componentName, component, setterMap, setterName;
  40587. for (componentName in dataMap) {
  40588. setterMap = dataMap[componentName];
  40589. component = this[componentName]();
  40590. if (component) {
  40591. for (setterName in setterMap) {
  40592. if (data && component[setterName] && data[setterMap[setterName]]) {
  40593. component[setterName](data[setterMap[setterName]]);
  40594. }
  40595. }
  40596. }
  40597. }
  40598. if (item) {
  40599. // Bypassing setter because sometimes we pass the same object (different properties)
  40600. item.updateData(data);
  40601. }
  40602. },
  40603. /**
  40604. * Updates this container's child items, passing through the `dataMap`.
  40605. * @param newRecord
  40606. * @private
  40607. */
  40608. updateRecord: function(newRecord) {
  40609. if (!newRecord) {
  40610. return;
  40611. }
  40612. this._record = newRecord;
  40613. var me = this,
  40614. dataview = me.dataview || this.getDataview(),
  40615. data = dataview.prepareData(newRecord.getData(true), dataview.getStore().indexOf(newRecord), newRecord),
  40616. items = me.getItems(),
  40617. item = items.first(),
  40618. dataMap = me.getDataMap();
  40619. if (!item) {
  40620. return;
  40621. }
  40622. if (dataMap) {
  40623. this.doMapData(dataMap, data, item);
  40624. }
  40625. /**
  40626. * @event updatedata
  40627. * Fires whenever the data of the DataItem is updated.
  40628. * @param {Ext.dataview.component.DataItem} this The DataItem instance.
  40629. * @param {Object} newData The new data.
  40630. */
  40631. me.fireEvent('updatedata', me, data);
  40632. }
  40633. });
  40634. /**
  40635. * @private
  40636. */
  40637. Ext.define('Ext.dataview.component.Container', {
  40638. extend: 'Ext.Container',
  40639. requires: [
  40640. 'Ext.dataview.component.DataItem'
  40641. ],
  40642. /**
  40643. * @event itemtouchstart
  40644. * Fires whenever an item is touched
  40645. * @param {Ext.dataview.component.Container} this
  40646. * @param {Ext.dataview.component.DataItem} item The item touched
  40647. * @param {Number} index The index of the item touched
  40648. * @param {Ext.EventObject} e The event object
  40649. */
  40650. /**
  40651. * @event itemtouchmove
  40652. * Fires whenever an item is moved
  40653. * @param {Ext.dataview.component.Container} this
  40654. * @param {Ext.dataview.component.DataItem} item The item moved
  40655. * @param {Number} index The index of the item moved
  40656. * @param {Ext.EventObject} e The event object
  40657. */
  40658. /**
  40659. * @event itemtouchend
  40660. * Fires whenever an item is touched
  40661. * @param {Ext.dataview.component.Container} this
  40662. * @param {Ext.dataview.component.DataItem} item The item touched
  40663. * @param {Number} index The index of the item touched
  40664. * @param {Ext.EventObject} e The event object
  40665. */
  40666. /**
  40667. * @event itemtap
  40668. * Fires whenever an item is tapped
  40669. * @param {Ext.dataview.component.Container} this
  40670. * @param {Ext.dataview.component.DataItem} item The item tapped
  40671. * @param {Number} index The index of the item tapped
  40672. * @param {Ext.EventObject} e The event object
  40673. */
  40674. /**
  40675. * @event itemtaphold
  40676. * Fires whenever an item is tapped
  40677. * @param {Ext.dataview.component.Container} this
  40678. * @param {Ext.dataview.component.DataItem} item The item tapped
  40679. * @param {Number} index The index of the item tapped
  40680. * @param {Ext.EventObject} e The event object
  40681. */
  40682. /**
  40683. * @event itemsingletap
  40684. * Fires whenever an item is doubletapped
  40685. * @param {Ext.dataview.component.Container} this
  40686. * @param {Ext.dataview.component.DataItem} item The item singletapped
  40687. * @param {Number} index The index of the item singletapped
  40688. * @param {Ext.EventObject} e The event object
  40689. */
  40690. /**
  40691. * @event itemdoubletap
  40692. * Fires whenever an item is doubletapped
  40693. * @param {Ext.dataview.component.Container} this
  40694. * @param {Ext.dataview.component.DataItem} item The item doubletapped
  40695. * @param {Number} index The index of the item doubletapped
  40696. * @param {Ext.EventObject} e The event object
  40697. */
  40698. /**
  40699. * @event itemswipe
  40700. * Fires whenever an item is swiped
  40701. * @param {Ext.dataview.component.Container} this
  40702. * @param {Ext.dataview.component.DataItem} item The item swiped
  40703. * @param {Number} index The index of the item swiped
  40704. * @param {Ext.EventObject} e The event object
  40705. */
  40706. constructor: function() {
  40707. this.itemCache = [];
  40708. this.callParent(arguments);
  40709. },
  40710. //@private
  40711. doInitialize: function() {
  40712. this.innerElement.on({
  40713. touchstart: 'onItemTouchStart',
  40714. touchend: 'onItemTouchEnd',
  40715. tap: 'onItemTap',
  40716. taphold: 'onItemTapHold',
  40717. touchmove: 'onItemTouchMove',
  40718. singletap: 'onItemSingleTap',
  40719. doubletap: 'onItemDoubleTap',
  40720. swipe: 'onItemSwipe',
  40721. delegate: '> .' + Ext.baseCSSPrefix + 'data-item',
  40722. scope: this
  40723. });
  40724. },
  40725. //@private
  40726. initialize: function() {
  40727. this.callParent();
  40728. this.doInitialize();
  40729. },
  40730. onItemTouchStart: function(e) {
  40731. var me = this,
  40732. target = e.getTarget(),
  40733. item = Ext.getCmp(target.id);
  40734. item.on({
  40735. touchmove: 'onItemTouchMove',
  40736. scope : me,
  40737. single: true
  40738. });
  40739. me.fireEvent('itemtouchstart', me, item, me.indexOf(item), e);
  40740. },
  40741. onItemTouchMove: function(e) {
  40742. var me = this,
  40743. target = e.getTarget(),
  40744. item = Ext.getCmp(target.id);
  40745. me.fireEvent('itemtouchmove', me, item, me.indexOf(item), e);
  40746. },
  40747. onItemTouchEnd: function(e) {
  40748. var me = this,
  40749. target = e.getTarget(),
  40750. item = Ext.getCmp(target.id);
  40751. item.un({
  40752. touchmove: 'onItemTouchMove',
  40753. scope : me
  40754. });
  40755. me.fireEvent('itemtouchend', me, item, me.indexOf(item), e);
  40756. },
  40757. onItemTap: function(e) {
  40758. var me = this,
  40759. target = e.getTarget(),
  40760. item = Ext.getCmp(target.id);
  40761. me.fireEvent('itemtap', me, item, me.indexOf(item), e);
  40762. },
  40763. onItemTapHold: function(e) {
  40764. var me = this,
  40765. target = e.getTarget(),
  40766. item = Ext.getCmp(target.id);
  40767. me.fireEvent('itemtaphold', me, item, me.indexOf(item), e);
  40768. },
  40769. onItemSingleTap: function(e) {
  40770. var me = this,
  40771. target = e.getTarget(),
  40772. item = Ext.getCmp(target.id);
  40773. me.fireEvent('itemsingletap', me, item, me.indexOf(item), e);
  40774. },
  40775. onItemDoubleTap: function(e) {
  40776. var me = this,
  40777. target = e.getTarget(),
  40778. item = Ext.getCmp(target.id);
  40779. me.fireEvent('itemdoubletap', me, item, me.indexOf(item), e);
  40780. },
  40781. onItemSwipe: function(e) {
  40782. var me = this,
  40783. target = e.getTarget(),
  40784. item = Ext.getCmp(target.id);
  40785. me.fireEvent('itemswipe', me, item, me.indexOf(item), e);
  40786. },
  40787. moveItemsToCache: function(from, to) {
  40788. var me = this,
  40789. dataview = me.dataview,
  40790. maxItemCache = dataview.getMaxItemCache(),
  40791. items = me.getViewItems(),
  40792. itemCache = me.itemCache,
  40793. cacheLn = itemCache.length,
  40794. pressedCls = dataview.getPressedCls(),
  40795. selectedCls = dataview.getSelectedCls(),
  40796. i = to - from,
  40797. item;
  40798. for (; i >= 0; i--) {
  40799. item = items[from + i];
  40800. if (cacheLn !== maxItemCache) {
  40801. me.remove(item, false);
  40802. item.removeCls([pressedCls, selectedCls]);
  40803. itemCache.push(item);
  40804. cacheLn++;
  40805. }
  40806. else {
  40807. item.destroy();
  40808. }
  40809. }
  40810. if (me.getViewItems().length == 0) {
  40811. this.dataview.showEmptyText();
  40812. }
  40813. },
  40814. moveItemsFromCache: function(records) {
  40815. var me = this,
  40816. dataview = me.dataview,
  40817. store = dataview.getStore(),
  40818. ln = records.length,
  40819. xtype = dataview.getDefaultType(),
  40820. itemConfig = dataview.getItemConfig(),
  40821. itemCache = me.itemCache,
  40822. cacheLn = itemCache.length,
  40823. items = [],
  40824. i, item, record;
  40825. if (ln) {
  40826. dataview.hideEmptyText();
  40827. }
  40828. for (i = 0; i < ln; i++) {
  40829. records[i]._tmpIndex = store.indexOf(records[i]);
  40830. }
  40831. Ext.Array.sort(records, function(record1, record2) {
  40832. return record1._tmpIndex > record2._tmpIndex ? 1 : -1;
  40833. });
  40834. for (i = 0; i < ln; i++) {
  40835. record = records[i];
  40836. if (cacheLn) {
  40837. cacheLn--;
  40838. item = itemCache.pop();
  40839. this.updateListItem(record, item);
  40840. }
  40841. else {
  40842. item = me.getDataItemConfig(xtype, record, itemConfig);
  40843. }
  40844. item = this.insert(record._tmpIndex, item);
  40845. delete record._tmpIndex;
  40846. }
  40847. return items;
  40848. },
  40849. getViewItems: function() {
  40850. return this.getInnerItems();
  40851. },
  40852. updateListItem: function(record, item) {
  40853. if (item.updateRecord) {
  40854. if (item.getRecord() === record) {
  40855. item.updateRecord(record);
  40856. } else {
  40857. item.setRecord(record);
  40858. }
  40859. }
  40860. },
  40861. getDataItemConfig: function(xtype, record, itemConfig) {
  40862. var dataview = this.dataview,
  40863. dataItemConfig = {
  40864. xtype: xtype,
  40865. record: record,
  40866. itemCls: dataview.getItemCls(),
  40867. defaults: itemConfig,
  40868. dataview: dataview
  40869. };
  40870. return Ext.merge(dataItemConfig, itemConfig);
  40871. },
  40872. doRemoveItemCls: function(cls) {
  40873. var items = this.getViewItems(),
  40874. ln = items.length,
  40875. i = 0;
  40876. for (; i < ln; i++) {
  40877. items[i].removeCls(cls);
  40878. }
  40879. },
  40880. doAddItemCls: function(cls) {
  40881. var items = this.getViewItems(),
  40882. ln = items.length,
  40883. i = 0;
  40884. for (; i < ln; i++) {
  40885. items[i].addCls(cls);
  40886. }
  40887. },
  40888. updateAtNewIndex: function(oldIndex, newIndex, record) {
  40889. this.moveItemsToCache(oldIndex, oldIndex);
  40890. this.moveItemsFromCache([record]);
  40891. },
  40892. destroy: function() {
  40893. var me = this,
  40894. itemCache = me.itemCache,
  40895. ln = itemCache.length,
  40896. i = 0;
  40897. for (; i < ln; i++) {
  40898. itemCache[i].destroy();
  40899. }
  40900. this.callParent();
  40901. }
  40902. });
  40903. /**
  40904. * @private
  40905. */
  40906. Ext.define('Ext.dataview.element.Container', {
  40907. extend: 'Ext.Component',
  40908. /**
  40909. * @event itemtouchstart
  40910. * Fires whenever an item is touched
  40911. * @param {Ext.dataview.element.Container} this
  40912. * @param {Ext.dom.Element} item The item touched
  40913. * @param {Number} index The index of the item touched
  40914. * @param {Ext.EventObject} e The event object
  40915. */
  40916. /**
  40917. * @event itemtouchmove
  40918. * Fires whenever an item is moved
  40919. * @param {Ext.dataview.element.Container} this
  40920. * @param {Ext.dom.Element} item The item moved
  40921. * @param {Number} index The index of the item moved
  40922. * @param {Ext.EventObject} e The event object
  40923. */
  40924. /**
  40925. * @event itemtouchend
  40926. * Fires whenever an item is touched
  40927. * @param {Ext.dataview.element.Container} this
  40928. * @param {Ext.dom.Element} item The item touched
  40929. * @param {Number} index The index of the item touched
  40930. * @param {Ext.EventObject} e The event object
  40931. */
  40932. /**
  40933. * @event itemtap
  40934. * Fires whenever an item is tapped
  40935. * @param {Ext.dataview.element.Container} this
  40936. * @param {Ext.dom.Element} item The item tapped
  40937. * @param {Number} index The index of the item tapped
  40938. * @param {Ext.EventObject} e The event object
  40939. */
  40940. /**
  40941. * @event itemtaphold
  40942. * Fires whenever an item is tapped
  40943. * @param {Ext.dataview.element.Container} this
  40944. * @param {Ext.dom.Element} item The item tapped
  40945. * @param {Number} index The index of the item tapped
  40946. * @param {Ext.EventObject} e The event object
  40947. */
  40948. /**
  40949. * @event itemsingletap
  40950. * Fires whenever an item is singletapped
  40951. * @param {Ext.dataview.element.Container} this
  40952. * @param {Ext.dom.Element} item The item singletapped
  40953. * @param {Number} index The index of the item singletapped
  40954. * @param {Ext.EventObject} e The event object
  40955. */
  40956. /**
  40957. * @event itemdoubletap
  40958. * Fires whenever an item is doubletapped
  40959. * @param {Ext.dataview.element.Container} this
  40960. * @param {Ext.dom.Element} item The item doubletapped
  40961. * @param {Number} index The index of the item doubletapped
  40962. * @param {Ext.EventObject} e The event object
  40963. */
  40964. /**
  40965. * @event itemswipe
  40966. * Fires whenever an item is swiped
  40967. * @param {Ext.dataview.element.Container} this
  40968. * @param {Ext.dom.Element} item The item swiped
  40969. * @param {Number} index The index of the item swiped
  40970. * @param {Ext.EventObject} e The event object
  40971. */
  40972. doInitialize: function() {
  40973. this.element.on({
  40974. touchstart: 'onItemTouchStart',
  40975. touchend: 'onItemTouchEnd',
  40976. tap: 'onItemTap',
  40977. taphold: 'onItemTapHold',
  40978. touchmove: 'onItemTouchMove',
  40979. singletap: 'onItemSingleTap',
  40980. doubletap: 'onItemDoubleTap',
  40981. swipe: 'onItemSwipe',
  40982. delegate: '> div',
  40983. scope: this
  40984. });
  40985. },
  40986. //@private
  40987. initialize: function() {
  40988. this.callParent();
  40989. this.doInitialize();
  40990. },
  40991. updateBaseCls: function(newBaseCls, oldBaseCls) {
  40992. var me = this;
  40993. me.callParent([newBaseCls + '-container', oldBaseCls]);
  40994. },
  40995. onItemTouchStart: function(e) {
  40996. var me = this,
  40997. target = e.getTarget(),
  40998. index = me.getViewItems().indexOf(target);
  40999. Ext.get(target).on({
  41000. touchmove: 'onItemTouchMove',
  41001. scope : me,
  41002. single: true
  41003. });
  41004. me.fireEvent('itemtouchstart', me, Ext.get(target), index, e);
  41005. },
  41006. onItemTouchEnd: function(e) {
  41007. var me = this,
  41008. target = e.getTarget(),
  41009. index = me.getViewItems().indexOf(target);
  41010. Ext.get(target).un({
  41011. touchmove: 'onItemTouchMove',
  41012. scope : me
  41013. });
  41014. me.fireEvent('itemtouchend', me, Ext.get(target), index, e);
  41015. },
  41016. onItemTouchMove: function(e) {
  41017. var me = this,
  41018. target = e.getTarget(),
  41019. index = me.getViewItems().indexOf(target);
  41020. me.fireEvent('itemtouchmove', me, Ext.get(target), index, e);
  41021. },
  41022. onItemTap: function(e) {
  41023. var me = this,
  41024. target = e.getTarget(),
  41025. index = me.getViewItems().indexOf(target);
  41026. me.fireEvent('itemtap', me, Ext.get(target), index, e);
  41027. },
  41028. onItemTapHold: function(e) {
  41029. var me = this,
  41030. target = e.getTarget(),
  41031. index = me.getViewItems().indexOf(target);
  41032. me.fireEvent('itemtaphold', me, Ext.get(target), index, e);
  41033. },
  41034. onItemDoubleTap: function(e) {
  41035. var me = this,
  41036. target = e.getTarget(),
  41037. index = me.getViewItems().indexOf(target);
  41038. me.fireEvent('itemdoubletap', me, Ext.get(target), index, e);
  41039. },
  41040. onItemSingleTap: function(e) {
  41041. var me = this,
  41042. target = e.getTarget(),
  41043. index = me.getViewItems().indexOf(target);
  41044. me.fireEvent('itemsingletap', me, Ext.get(target), index, e);
  41045. },
  41046. onItemSwipe: function(e) {
  41047. var me = this,
  41048. target = e.getTarget(),
  41049. index = me.getViewItems().indexOf(target);
  41050. me.fireEvent('itemswipe', me, Ext.get(target), index, e);
  41051. },
  41052. updateListItem: function(record, item) {
  41053. var me = this,
  41054. dataview = me.dataview,
  41055. store = dataview.getStore(),
  41056. index = store.indexOf(record),
  41057. data = dataview.prepareData(record.getData(true), index, record);
  41058. data.xcount = store.getCount();
  41059. data.xindex = typeof data.xindex === 'number' ? data.xindex : index;
  41060. item.innerHTML = dataview.getItemTpl().apply(data);
  41061. },
  41062. addListItem: function(index, record) {
  41063. var me = this,
  41064. dataview = me.dataview,
  41065. store = dataview.getStore(),
  41066. data = dataview.prepareData(record.getData(true), index, record),
  41067. element = me.element,
  41068. childNodes = element.dom.childNodes,
  41069. ln = childNodes.length,
  41070. wrapElement;
  41071. data.xcount = typeof data.xcount === 'number' ? data.xcount : store.getCount();
  41072. data.xindex = typeof data.xindex === 'number' ? data.xindex : index;
  41073. wrapElement = Ext.Element.create(this.getItemElementConfig(index, data));
  41074. if (!ln || index == ln) {
  41075. wrapElement.appendTo(element);
  41076. } else {
  41077. wrapElement.insertBefore(childNodes[index]);
  41078. }
  41079. },
  41080. getItemElementConfig: function(index, data) {
  41081. var dataview = this.dataview,
  41082. itemCls = dataview.getItemCls(),
  41083. cls = dataview.getBaseCls() + '-item';
  41084. if (itemCls) {
  41085. cls += ' ' + itemCls;
  41086. }
  41087. return {
  41088. cls: cls,
  41089. html: dataview.getItemTpl().apply(data)
  41090. };
  41091. },
  41092. doRemoveItemCls: function(cls) {
  41093. var elements = this.getViewItems(),
  41094. ln = elements.length,
  41095. i = 0;
  41096. for (; i < ln; i++) {
  41097. Ext.fly(elements[i]).removeCls(cls);
  41098. }
  41099. },
  41100. doAddItemCls: function(cls) {
  41101. var elements = this.getViewItems(),
  41102. ln = elements.length,
  41103. i = 0;
  41104. for (; i < ln; i++) {
  41105. Ext.fly(elements[i]).addCls(cls);
  41106. }
  41107. },
  41108. // Remove
  41109. moveItemsToCache: function(from, to) {
  41110. var me = this,
  41111. items = me.getViewItems(),
  41112. i = to - from,
  41113. item;
  41114. for (; i >= 0; i--) {
  41115. item = items[from + i];
  41116. Ext.get(item).destroy();
  41117. }
  41118. if (me.getViewItems().length == 0) {
  41119. this.dataview.showEmptyText();
  41120. }
  41121. },
  41122. // Add
  41123. moveItemsFromCache: function(records) {
  41124. var me = this,
  41125. dataview = me.dataview,
  41126. store = dataview.getStore(),
  41127. ln = records.length,
  41128. i, record;
  41129. if (ln) {
  41130. dataview.hideEmptyText();
  41131. }
  41132. for (i = 0; i < ln; i++) {
  41133. records[i]._tmpIndex = store.indexOf(records[i]);
  41134. }
  41135. Ext.Array.sort(records, function(record1, record2) {
  41136. return record1._tmpIndex > record2._tmpIndex ? 1 : -1;
  41137. });
  41138. for (i = 0; i < ln; i++) {
  41139. record = records[i];
  41140. me.addListItem(record._tmpIndex, record);
  41141. delete record._tmpIndex;
  41142. }
  41143. },
  41144. // Transform ChildNodes into a proper Array so we can do indexOf...
  41145. getViewItems: function() {
  41146. return Array.prototype.slice.call(this.element.dom.childNodes);
  41147. },
  41148. updateAtNewIndex: function(oldIndex, newIndex, record) {
  41149. this.moveItemsToCache(oldIndex, oldIndex);
  41150. this.moveItemsFromCache([record]);
  41151. },
  41152. destroy: function() {
  41153. var elements = this.getViewItems(),
  41154. ln = elements.length,
  41155. i = 0;
  41156. for (; i < ln; i++) {
  41157. Ext.get(elements[i]).destroy();
  41158. }
  41159. this.callParent();
  41160. }
  41161. });
  41162. /**
  41163. * Tracks what records are currently selected in a databound widget. This class is mixed in to {@link Ext.dataview.DataView} and
  41164. * all subclasses.
  41165. * @private
  41166. */
  41167. Ext.define('Ext.mixin.Selectable', {
  41168. extend: 'Ext.mixin.Mixin',
  41169. mixinConfig: {
  41170. id: 'selectable',
  41171. hooks: {
  41172. updateStore: 'updateStore'
  41173. }
  41174. },
  41175. /**
  41176. * @event beforeselectionchange
  41177. * Fires before an item is selected.
  41178. * @param {Ext.mixin.Selectable} this
  41179. * @preventable selectionchange
  41180. * @deprecated 2.0.0 Please listen to the {@link #selectionchange} event with an order of `before` instead.
  41181. */
  41182. /**
  41183. * @event selectionchange
  41184. * Fires when a selection changes.
  41185. * @param {Ext.mixin.Selectable} this
  41186. * @param {Ext.data.Model[]} records The records whose selection has changed.
  41187. */
  41188. config: {
  41189. /**
  41190. * @cfg {Boolean} disableSelection `true` to disable selection.
  41191. * This configuration will lock the selection model that the DataView uses.
  41192. * @accessor
  41193. */
  41194. disableSelection: null,
  41195. /**
  41196. * @cfg {String} mode
  41197. * Modes of selection.
  41198. * Valid values are `'SINGLE'`, `'SIMPLE'`, and `'MULTI'`.
  41199. * @accessor
  41200. */
  41201. mode: 'SINGLE',
  41202. /**
  41203. * @cfg {Boolean} allowDeselect
  41204. * Allow users to deselect a record in a DataView, List or Grid. Only applicable when the Selectable's `mode` is
  41205. * `'SINGLE'`.
  41206. * @accessor
  41207. */
  41208. allowDeselect: false,
  41209. /**
  41210. * @cfg {Ext.data.Model} lastSelected
  41211. * @private
  41212. * @accessor
  41213. */
  41214. lastSelected: null,
  41215. /**
  41216. * @cfg {Ext.data.Model} lastFocused
  41217. * @private
  41218. * @accessor
  41219. */
  41220. lastFocused: null,
  41221. /**
  41222. * @cfg {Boolean} deselectOnContainerClick `true` to deselect current selection when the container body is
  41223. * clicked.
  41224. * @accessor
  41225. */
  41226. deselectOnContainerClick: true
  41227. },
  41228. modes: {
  41229. SINGLE: true,
  41230. SIMPLE: true,
  41231. MULTI: true
  41232. },
  41233. selectableEventHooks: {
  41234. addrecords: 'onSelectionStoreAdd',
  41235. removerecords: 'onSelectionStoreRemove',
  41236. updaterecord: 'onSelectionStoreUpdate',
  41237. load: 'refreshSelection',
  41238. refresh: 'refreshSelection'
  41239. },
  41240. constructor: function() {
  41241. this.selected = new Ext.util.MixedCollection();
  41242. this.callParent(arguments);
  41243. },
  41244. /**
  41245. * @private
  41246. */
  41247. applyMode: function(mode) {
  41248. mode = mode ? mode.toUpperCase() : 'SINGLE';
  41249. // set to mode specified unless it doesnt exist, in that case
  41250. // use single.
  41251. return this.modes[mode] ? mode : 'SINGLE';
  41252. },
  41253. /**
  41254. * @private
  41255. */
  41256. updateStore: function(newStore, oldStore) {
  41257. var me = this,
  41258. bindEvents = Ext.apply({}, me.selectableEventHooks, { scope: me });
  41259. if (oldStore && Ext.isObject(oldStore) && oldStore.isStore) {
  41260. if (oldStore.autoDestroy) {
  41261. oldStore.destroy();
  41262. }
  41263. else {
  41264. oldStore.un(bindEvents);
  41265. newStore.un('clear', 'onSelectionStoreClear', this);
  41266. }
  41267. }
  41268. if (newStore) {
  41269. newStore.on(bindEvents);
  41270. newStore.onBefore('clear', 'onSelectionStoreClear', this);
  41271. me.refreshSelection();
  41272. }
  41273. },
  41274. /**
  41275. * Selects all records.
  41276. * @param {Boolean} silent `true` to suppress all select events.
  41277. */
  41278. selectAll: function(silent) {
  41279. var me = this,
  41280. selections = me.getStore().getRange(),
  41281. ln = selections.length,
  41282. i = 0;
  41283. for (; i < ln; i++) {
  41284. me.select(selections[i], true, silent);
  41285. }
  41286. },
  41287. /**
  41288. * Deselects all records.
  41289. */
  41290. deselectAll: function(supress) {
  41291. var me = this,
  41292. selections = me.getStore().getRange();
  41293. me.deselect(selections, supress);
  41294. me.selected.clear();
  41295. me.setLastSelected(null);
  41296. me.setLastFocused(null);
  41297. },
  41298. // Provides differentiation of logic between MULTI, SIMPLE and SINGLE
  41299. // selection modes.
  41300. selectWithEvent: function(record) {
  41301. var me = this,
  41302. isSelected = me.isSelected(record);
  41303. switch (me.getMode()) {
  41304. case 'MULTI':
  41305. case 'SIMPLE':
  41306. if (isSelected) {
  41307. me.deselect(record);
  41308. }
  41309. else {
  41310. me.select(record, true);
  41311. }
  41312. break;
  41313. case 'SINGLE':
  41314. if (me.getAllowDeselect() && isSelected) {
  41315. // if allowDeselect is on and this record isSelected, deselect it
  41316. me.deselect(record);
  41317. } else {
  41318. // select the record and do NOT maintain existing selections
  41319. me.select(record, false);
  41320. }
  41321. break;
  41322. }
  41323. },
  41324. /**
  41325. * Selects a range of rows if the selection model {@link Ext.mixin.Selectable#getDisableSelection} is not locked.
  41326. * All rows in between `startRow` and `endRow` are also selected.
  41327. * @param {Number} startRow The index of the first row in the range.
  41328. * @param {Number} endRow The index of the last row in the range.
  41329. * @param {Boolean} keepExisting (optional) `true` to retain existing selections.
  41330. */
  41331. selectRange: function(startRecord, endRecord, keepExisting) {
  41332. var me = this,
  41333. store = me.getStore(),
  41334. records = [],
  41335. tmp, i;
  41336. if (me.getDisableSelection()) {
  41337. return;
  41338. }
  41339. // swap values
  41340. if (startRecord > endRecord) {
  41341. tmp = endRecord;
  41342. endRecord = startRecord;
  41343. startRecord = tmp;
  41344. }
  41345. for (i = startRecord; i <= endRecord; i++) {
  41346. records.push(store.getAt(i));
  41347. }
  41348. this.doMultiSelect(records, keepExisting);
  41349. },
  41350. /**
  41351. * Adds the given records to the currently selected set.
  41352. * @param {Ext.data.Model/Array/Number} records The records to select.
  41353. * @param {Boolean} keepExisting If `true`, the existing selection will be added to (if not, the old selection is replaced).
  41354. * @param {Boolean} suppressEvent If `true`, the `select` event will not be fired.
  41355. */
  41356. select: function(records, keepExisting, suppressEvent) {
  41357. var me = this,
  41358. record;
  41359. if (me.getDisableSelection()) {
  41360. return;
  41361. }
  41362. if (typeof records === "number") {
  41363. records = [me.getStore().getAt(records)];
  41364. }
  41365. if (!records) {
  41366. return;
  41367. }
  41368. if (me.getMode() == "SINGLE" && records) {
  41369. record = records.length ? records[0] : records;
  41370. me.doSingleSelect(record, suppressEvent);
  41371. } else {
  41372. me.doMultiSelect(records, keepExisting, suppressEvent);
  41373. }
  41374. },
  41375. /**
  41376. * Selects a single record.
  41377. * @private
  41378. */
  41379. doSingleSelect: function(record, suppressEvent) {
  41380. var me = this,
  41381. selected = me.selected;
  41382. if (me.getDisableSelection()) {
  41383. return;
  41384. }
  41385. // already selected.
  41386. // should we also check beforeselect?
  41387. if (me.isSelected(record)) {
  41388. return;
  41389. }
  41390. if (selected.getCount() > 0) {
  41391. me.deselect(me.getLastSelected(), suppressEvent);
  41392. }
  41393. selected.add(record);
  41394. me.setLastSelected(record);
  41395. me.onItemSelect(record, suppressEvent);
  41396. me.setLastFocused(record);
  41397. if (!suppressEvent) {
  41398. me.fireSelectionChange([record]);
  41399. }
  41400. },
  41401. /**
  41402. * Selects a set of multiple records.
  41403. * @private
  41404. */
  41405. doMultiSelect: function(records, keepExisting, suppressEvent) {
  41406. if (records === null || this.getDisableSelection()) {
  41407. return;
  41408. }
  41409. records = !Ext.isArray(records) ? [records] : records;
  41410. var me = this,
  41411. selected = me.selected,
  41412. ln = records.length,
  41413. change = false,
  41414. i = 0,
  41415. record;
  41416. if (!keepExisting && selected.getCount() > 0) {
  41417. change = true;
  41418. me.deselect(me.getSelection(), true);
  41419. }
  41420. for (; i < ln; i++) {
  41421. record = records[i];
  41422. if (keepExisting && me.isSelected(record)) {
  41423. continue;
  41424. }
  41425. change = true;
  41426. me.setLastSelected(record);
  41427. selected.add(record);
  41428. if (!suppressEvent) {
  41429. me.setLastFocused(record);
  41430. }
  41431. me.onItemSelect(record, suppressEvent);
  41432. }
  41433. if (change && !suppressEvent) {
  41434. this.fireSelectionChange(records);
  41435. }
  41436. },
  41437. /**
  41438. * Deselects the given record(s). If many records are currently selected, it will only deselect those you pass in.
  41439. * @param {Number/Array/Ext.data.Model} records The record(s) to deselect. Can also be a number to reference by index.
  41440. * @param {Boolean} suppressEvent If `true` the `deselect` event will not be fired.
  41441. */
  41442. deselect: function(records, suppressEvent) {
  41443. var me = this;
  41444. if (me.getDisableSelection()) {
  41445. return;
  41446. }
  41447. records = Ext.isArray(records) ? records : [records];
  41448. var selected = me.selected,
  41449. change = false,
  41450. i = 0,
  41451. store = me.getStore(),
  41452. ln = records.length,
  41453. record;
  41454. for (; i < ln; i++) {
  41455. record = records[i];
  41456. if (typeof record === 'number') {
  41457. record = store.getAt(record);
  41458. }
  41459. if (selected.remove(record)) {
  41460. if (me.getLastSelected() == record) {
  41461. me.setLastSelected(selected.last());
  41462. }
  41463. change = true;
  41464. }
  41465. if (record) {
  41466. me.onItemDeselect(record, suppressEvent);
  41467. }
  41468. }
  41469. if (change && !suppressEvent) {
  41470. me.fireSelectionChange(records);
  41471. }
  41472. },
  41473. /**
  41474. * Sets a record as the last focused record. This does NOT mean
  41475. * that the record has been selected.
  41476. * @param {Ext.data.Record} newRecord
  41477. * @param {Ext.data.Record} oldRecord
  41478. */
  41479. updateLastFocused: function(newRecord, oldRecord) {
  41480. this.onLastFocusChanged(oldRecord, newRecord);
  41481. },
  41482. fireSelectionChange: function(records) {
  41483. var me = this;
  41484. me.fireAction('selectionchange', [me, records], 'getSelection');
  41485. },
  41486. /**
  41487. * Returns an array of the currently selected records.
  41488. * @return {Array} An array of selected records.
  41489. */
  41490. getSelection: function() {
  41491. return this.selected.getRange();
  41492. },
  41493. /**
  41494. * Returns `true` if the specified row is selected.
  41495. * @param {Ext.data.Model/Number} record The record or index of the record to check.
  41496. * @return {Boolean}
  41497. */
  41498. isSelected: function(record) {
  41499. record = Ext.isNumber(record) ? this.getStore().getAt(record) : record;
  41500. return this.selected.indexOf(record) !== -1;
  41501. },
  41502. /**
  41503. * Returns `true` if there is a selected record.
  41504. * @return {Boolean}
  41505. */
  41506. hasSelection: function() {
  41507. return this.selected.getCount() > 0;
  41508. },
  41509. /**
  41510. * @private
  41511. */
  41512. refreshSelection: function() {
  41513. var me = this,
  41514. selections = me.getSelection();
  41515. me.deselectAll(true);
  41516. if (selections.length) {
  41517. me.select(selections, false, true);
  41518. }
  41519. },
  41520. // prune records from the SelectionModel if
  41521. // they were selected at the time they were
  41522. // removed.
  41523. onSelectionStoreRemove: function(store, records) {
  41524. var me = this,
  41525. selected = me.selected,
  41526. ln = records.length,
  41527. record, i;
  41528. if (me.getDisableSelection()) {
  41529. return;
  41530. }
  41531. for (i = 0; i < ln; i++) {
  41532. record = records[i];
  41533. if (selected.remove(record)) {
  41534. if (me.getLastSelected() == record) {
  41535. me.setLastSelected(null);
  41536. }
  41537. if (me.getLastFocused() == record) {
  41538. me.setLastFocused(null);
  41539. }
  41540. me.fireSelectionChange([record]);
  41541. }
  41542. }
  41543. },
  41544. onSelectionStoreClear: function(store) {
  41545. var records = store.getData().items;
  41546. this.onSelectionStoreRemove(store, records);
  41547. },
  41548. /**
  41549. * Returns the number of selections.
  41550. * @return {Number}
  41551. */
  41552. getSelectionCount: function() {
  41553. return this.selected.getCount();
  41554. },
  41555. onSelectionStoreAdd: Ext.emptyFn,
  41556. onSelectionStoreUpdate: Ext.emptyFn,
  41557. onItemSelect: Ext.emptyFn,
  41558. onItemDeselect: Ext.emptyFn,
  41559. onLastFocusChanged: Ext.emptyFn,
  41560. onEditorKey: Ext.emptyFn
  41561. }, function() {
  41562. /**
  41563. * Selects a record instance by record instance or index.
  41564. * @member Ext.mixin.Selectable
  41565. * @method doSelect
  41566. * @param {Ext.data.Model/Number} records An array of records or an index.
  41567. * @param {Boolean} keepExisting
  41568. * @param {Boolean} suppressEvent Set to `false` to not fire a select event.
  41569. * @deprecated 2.0.0 Please use {@link #select} instead.
  41570. */
  41571. /**
  41572. * Deselects a record instance by record instance or index.
  41573. * @member Ext.mixin.Selectable
  41574. * @method doDeselect
  41575. * @param {Ext.data.Model/Number} records An array of records or an index.
  41576. * @param {Boolean} suppressEvent Set to `false` to not fire a deselect event.
  41577. * @deprecated 2.0.0 Please use {@link #deselect} instead.
  41578. */
  41579. /**
  41580. * Returns the selection mode currently used by this Selectable.
  41581. * @member Ext.mixin.Selectable
  41582. * @method getSelectionMode
  41583. * @return {String} The current mode.
  41584. * @deprecated 2.0.0 Please use {@link #getMode} instead.
  41585. */
  41586. /**
  41587. * Returns the array of previously selected items.
  41588. * @member Ext.mixin.Selectable
  41589. * @method getLastSelected
  41590. * @return {Array} The previous selection.
  41591. * @deprecated 2.0.0
  41592. */
  41593. /**
  41594. * Returns `true` if the Selectable is currently locked.
  41595. * @member Ext.mixin.Selectable
  41596. * @method isLocked
  41597. * @return {Boolean} True if currently locked
  41598. * @deprecated 2.0.0 Please use {@link #getDisableSelection} instead.
  41599. */
  41600. /**
  41601. * This was an internal function accidentally exposed in 1.x and now deprecated. Calling it has no effect
  41602. * @member Ext.mixin.Selectable
  41603. * @method setLastFocused
  41604. * @deprecated 2.0.0
  41605. */
  41606. /**
  41607. * Deselects any currently selected records and clears all stored selections.
  41608. * @member Ext.mixin.Selectable
  41609. * @method clearSelections
  41610. * @deprecated 2.0.0 Please use {@link #deselectAll} instead.
  41611. */
  41612. /**
  41613. * Returns the number of selections.
  41614. * @member Ext.mixin.Selectable
  41615. * @method getCount
  41616. * @return {Number}
  41617. * @deprecated 2.0.0 Please use {@link #getSelectionCount} instead.
  41618. */
  41619. /**
  41620. * @cfg {Boolean} locked
  41621. * @inheritdoc Ext.mixin.Selectable#disableSelection
  41622. * @deprecated 2.0.0 Please use {@link #disableSelection} instead.
  41623. */
  41624. });
  41625. /**
  41626. * @aside guide dataview
  41627. *
  41628. * DataView makes it easy to create lots of components dynamically, usually based off a {@link Ext.data.Store Store}.
  41629. * It's great for rendering lots of data from your server backend or any other data source and is what powers
  41630. * components like {@link Ext.List}.
  41631. *
  41632. * Use DataView whenever you want to show sets of the same component many times, for examples in apps like these:
  41633. *
  41634. * - List of messages in an email app
  41635. * - Showing latest news/tweets
  41636. * - Tiled set of albums in an HTML5 music player
  41637. *
  41638. * # Creating a Simple DataView
  41639. *
  41640. * At its simplest, a DataView is just a Store full of data and a simple template that we use to render each item:
  41641. *
  41642. * @example miniphone preview
  41643. * var touchTeam = Ext.create('Ext.DataView', {
  41644. * fullscreen: true,
  41645. * store: {
  41646. * fields: ['name', 'age'],
  41647. * data: [
  41648. * {name: 'Jamie', age: 100},
  41649. * {name: 'Rob', age: 21},
  41650. * {name: 'Tommy', age: 24},
  41651. * {name: 'Jacky', age: 24},
  41652. * {name: 'Ed', age: 26}
  41653. * ]
  41654. * },
  41655. *
  41656. * itemTpl: '<div>{name} is {age} years old</div>'
  41657. * });
  41658. *
  41659. * Here we just defined everything inline so it's all local with nothing being loaded from a server. For each of the 5
  41660. * data items defined in our Store, DataView will render a {@link Ext.Component Component} and pass in the name and age
  41661. * data. The component will use the tpl we provided above, rendering the data in the curly bracket placeholders we
  41662. * provided.
  41663. *
  41664. * Because DataView is integrated with Store, any changes to the Store are immediately reflected on the screen. For
  41665. * example, if we add a new record to the Store it will be rendered into our DataView:
  41666. *
  41667. * touchTeam.getStore().add({
  41668. * name: 'Abe Elias',
  41669. * age: 33
  41670. * });
  41671. *
  41672. * We didn't have to manually update the DataView, it's just automatically updated. The same happens if we modify one
  41673. * of the existing records in the Store:
  41674. *
  41675. * touchTeam.getStore().getAt(0).set('age', 42);
  41676. *
  41677. * This will get the first record in the Store (Jamie), change the age to 42 and automatically update what's on the
  41678. * screen.
  41679. *
  41680. * @example miniphone
  41681. * var touchTeam = Ext.create('Ext.DataView', {
  41682. * fullscreen: true,
  41683. * store: {
  41684. * fields: ['name', 'age'],
  41685. * data: [
  41686. * {name: 'Jamie', age: 100},
  41687. * {name: 'Rob', age: 21},
  41688. * {name: 'Tommy', age: 24},
  41689. * {name: 'Jacky', age: 24},
  41690. * {name: 'Ed', age: 26}
  41691. * ]
  41692. * },
  41693. *
  41694. * itemTpl: '<div>{name} is {age} years old</div>'
  41695. * });
  41696. *
  41697. * touchTeam.getStore().add({
  41698. * name: 'Abe Elias',
  41699. * age: 33
  41700. * });
  41701. *
  41702. * touchTeam.getStore().getAt(0).set('age', 42);
  41703. *
  41704. * # Loading data from a server
  41705. *
  41706. * We often want to load data from our server or some other web service so that we don't have to hard code it all
  41707. * locally. Let's say we want to load all of the latest tweets about Sencha Touch into a DataView, and for each one
  41708. * render the user's profile picture, user name and tweet message. To do this all we have to do is modify the
  41709. * {@link #store} and {@link #itemTpl} a little:
  41710. *
  41711. * @example portrait
  41712. * Ext.create('Ext.DataView', {
  41713. * fullscreen: true,
  41714. * cls: 'twitterView',
  41715. * store: {
  41716. * autoLoad: true,
  41717. * fields: ['from_user', 'text', 'profile_image_url'],
  41718. *
  41719. * proxy: {
  41720. * type: 'jsonp',
  41721. * url: 'http://search.twitter.com/search.json?q=Sencha Touch',
  41722. *
  41723. * reader: {
  41724. * type: 'json',
  41725. * rootProperty: 'results'
  41726. * }
  41727. * }
  41728. * },
  41729. *
  41730. * itemTpl: '<img src="{profile_image_url}" /><h2>{from_user}</h2><p>{text}</p><div style="clear: both"></div>'
  41731. * });
  41732. *
  41733. * The Store no longer has hard coded data, instead we've provided a {@link Ext.data.proxy.Proxy Proxy}, which fetches
  41734. * the data for us. In this case we used a JSON-P proxy so that we can load from Twitter's JSON-P search API. We also
  41735. * specified the fields present for each tweet, and used Store's {@link Ext.data.Store#autoLoad autoLoad} configuration
  41736. * to load automatically. Finally, we configured a Reader to decode the response from Twitter, telling it to expect
  41737. * JSON and that the tweets can be found in the 'results' part of the JSON response.
  41738. *
  41739. * The last thing we did is update our template to render the image, Twitter username and message. All we need to do
  41740. * now is add a little CSS to style the list the way we want it and we end up with a very basic Twitter viewer. Click
  41741. * the preview button on the example above to see it in action.
  41742. */
  41743. Ext.define('Ext.dataview.DataView', {
  41744. extend: 'Ext.Container',
  41745. alternateClassName: 'Ext.DataView',
  41746. mixins: ['Ext.mixin.Selectable'],
  41747. xtype: 'dataview',
  41748. requires: [
  41749. 'Ext.LoadMask',
  41750. 'Ext.data.StoreManager',
  41751. 'Ext.dataview.component.Container',
  41752. 'Ext.dataview.element.Container'
  41753. ],
  41754. /**
  41755. * @event containertap
  41756. * Fires when a tap occurs and it is not on a template node.
  41757. * @removed 2.0.0
  41758. */
  41759. /**
  41760. * @event itemtouchstart
  41761. * Fires whenever an item is touched
  41762. * @param {Ext.dataview.DataView} this
  41763. * @param {Number} index The index of the item touched
  41764. * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem touched
  41765. * @param {Ext.data.Model} record The record associated to the item
  41766. * @param {Ext.EventObject} e The event object
  41767. */
  41768. /**
  41769. * @event itemtouchmove
  41770. * Fires whenever an item is moved
  41771. * @param {Ext.dataview.DataView} this
  41772. * @param {Number} index The index of the item moved
  41773. * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem moved
  41774. * @param {Ext.data.Model} record The record associated to the item
  41775. * @param {Ext.EventObject} e The event object
  41776. */
  41777. /**
  41778. * @event itemtouchend
  41779. * Fires whenever an item is touched
  41780. * @param {Ext.dataview.DataView} this
  41781. * @param {Number} index The index of the item touched
  41782. * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem touched
  41783. * @param {Ext.data.Model} record The record associated to the item
  41784. * @param {Ext.EventObject} e The event object
  41785. */
  41786. /**
  41787. * @event itemtap
  41788. * Fires whenever an item is tapped
  41789. * @param {Ext.dataview.DataView} this
  41790. * @param {Number} index The index of the item tapped
  41791. * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem tapped
  41792. * @param {Ext.data.Model} record The record associated to the item
  41793. * @param {Ext.EventObject} e The event object
  41794. */
  41795. /**
  41796. * @event itemtaphold
  41797. * Fires whenever an item's taphold event fires
  41798. * @param {Ext.dataview.DataView} this
  41799. * @param {Number} index The index of the item touched
  41800. * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem touched
  41801. * @param {Ext.data.Model} record The record associated to the item
  41802. * @param {Ext.EventObject} e The event object
  41803. */
  41804. /**
  41805. * @event itemsingletap
  41806. * Fires whenever an item is singletapped
  41807. * @param {Ext.dataview.DataView} this
  41808. * @param {Number} index The index of the item singletapped
  41809. * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem singletapped
  41810. * @param {Ext.data.Model} record The record associated to the item
  41811. * @param {Ext.EventObject} e The event object
  41812. */
  41813. /**
  41814. * @event itemdoubletap
  41815. * Fires whenever an item is doubletapped
  41816. * @param {Ext.dataview.DataView} this
  41817. * @param {Number} index The index of the item doubletapped
  41818. * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem doubletapped
  41819. * @param {Ext.data.Model} record The record associated to the item
  41820. * @param {Ext.EventObject} e The event object
  41821. */
  41822. /**
  41823. * @event itemswipe
  41824. * Fires whenever an item is swiped
  41825. * @param {Ext.dataview.DataView} this
  41826. * @param {Number} index The index of the item swiped
  41827. * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem swiped
  41828. * @param {Ext.data.Model} record The record associated to the item
  41829. * @param {Ext.EventObject} e The event object
  41830. */
  41831. /**
  41832. * @event select
  41833. * @preventable doItemSelect
  41834. * Fires whenever an item is selected
  41835. * @param {Ext.dataview.DataView} this
  41836. * @param {Ext.data.Model} record The record associated to the item
  41837. */
  41838. /**
  41839. * @event deselect
  41840. * @preventable doItemDeselect
  41841. * Fires whenever an item is deselected
  41842. * @param {Ext.dataview.DataView} this
  41843. * @param {Ext.data.Model} record The record associated to the item
  41844. * @param {Boolean} supressed Flag to suppress the event
  41845. */
  41846. /**
  41847. * @event refresh
  41848. * @preventable doRefresh
  41849. * Fires whenever the DataView is refreshed
  41850. * @param {Ext.dataview.DataView} this
  41851. */
  41852. /**
  41853. * @hide
  41854. * @event add
  41855. */
  41856. /**
  41857. * @hide
  41858. * @event remove
  41859. */
  41860. /**
  41861. * @hide
  41862. * @event move
  41863. */
  41864. config: {
  41865. /**
  41866. * @cfg layout
  41867. * Hide layout config in DataView. It only causes confusion.
  41868. * @accessor
  41869. * @private
  41870. */
  41871. /**
  41872. * @cfg {Ext.data.Store/Object} store
  41873. * Can be either a Store instance or a configuration object that will be turned into a Store. The Store is used
  41874. * to populate the set of items that will be rendered in the DataView. See the DataView intro documentation for
  41875. * more information about the relationship between Store and DataView.
  41876. * @accessor
  41877. */
  41878. store: null,
  41879. /**
  41880. * @cfg baseCls
  41881. * @inheritdoc
  41882. */
  41883. baseCls: Ext.baseCSSPrefix + 'dataview',
  41884. /**
  41885. * @cfg {String} emptyText
  41886. * The text to display in the view when there is no data to display
  41887. */
  41888. emptyText: null,
  41889. /**
  41890. * @cfg {Boolean} deferEmptyText `true` to defer `emptyText` being applied until the store's first load.
  41891. */
  41892. deferEmptyText: true,
  41893. /**
  41894. * @cfg {String/String[]/Ext.XTemplate} itemTpl
  41895. * The `tpl` to use for each of the items displayed in this DataView.
  41896. */
  41897. itemTpl: '<div>{text}</div>',
  41898. /**
  41899. * @cfg {String} pressedCls
  41900. * The CSS class to apply to an item on the view while it is being pressed.
  41901. * @accessor
  41902. */
  41903. pressedCls: 'x-item-pressed',
  41904. /**
  41905. * @cfg {String} itemCls
  41906. * An additional CSS class to apply to items within the DataView.
  41907. * @accessor
  41908. */
  41909. itemCls: null,
  41910. /**
  41911. * @cfg {String} selectedCls
  41912. * The CSS class to apply to an item on the view while it is selected.
  41913. * @accessor
  41914. */
  41915. selectedCls: 'x-item-selected',
  41916. /**
  41917. * @cfg {String} triggerEvent
  41918. * Determines what type of touch event causes an item to be selected.
  41919. * Valid options are: 'itemtap', 'itemsingletap', 'itemdoubletap', 'itemswipe', 'itemtaphold'.
  41920. * @accessor
  41921. */
  41922. triggerEvent: 'itemtap',
  41923. /**
  41924. * @cfg {String} triggerCtEvent
  41925. * Determines what type of touch event is recognized as a touch on the container.
  41926. * Valid options are 'tap' and 'singletap'.
  41927. * @accessor
  41928. */
  41929. triggerCtEvent: 'tap',
  41930. /**
  41931. * @cfg {Boolean} deselectOnContainerClick
  41932. * When set to true, tapping on the DataView's background (i.e. not on
  41933. * an item in the DataView) will deselect any currently selected items.
  41934. * @accessor
  41935. */
  41936. deselectOnContainerClick: true,
  41937. /**
  41938. * @cfg scrollable
  41939. * @inheritdoc
  41940. */
  41941. scrollable: true,
  41942. /**
  41943. * @cfg {Boolean/Object} inline
  41944. * When set to `true` the items within the DataView will have their display set to inline-block
  41945. * and be arranged horizontally. By default the items will wrap to the width of the DataView.
  41946. * Passing an object with `{ wrap: false }` will turn off this wrapping behavior and overflowed
  41947. * items will need to be scrolled to horizontally.
  41948. * @accessor
  41949. */
  41950. inline: null,
  41951. /**
  41952. * @cfg {Number} pressedDelay
  41953. * The amount of delay between the `tapstart` and the moment we add the `pressedCls`.
  41954. *
  41955. * Settings this to `true` defaults to 100ms.
  41956. * @accessor
  41957. */
  41958. pressedDelay: 100,
  41959. /**
  41960. * @cfg {String} loadingText
  41961. * A string to display during data load operations. If specified, this text will be
  41962. * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's
  41963. * contents will continue to display normally until the new data is loaded and the contents are replaced.
  41964. */
  41965. loadingText: 'Loading...',
  41966. /**
  41967. * @cfg {Boolean} useComponents
  41968. * Flag the use a component based DataView implementation. This allows the full use of components in the
  41969. * DataView at the cost of some performance.
  41970. *
  41971. * Checkout the [DataView Guide](#!/guide/dataview) for more information on using this configuration.
  41972. * @accessor
  41973. */
  41974. useComponents: null,
  41975. /**
  41976. * @cfg {Object} itemConfig
  41977. * A configuration object that is passed to every item created by a component based DataView. Because each
  41978. * item that a DataView renders is a Component, we can pass configuration options to each component to
  41979. * easily customize how each child component behaves.
  41980. *
  41981. * __Note:__ this is only used when `{@link #useComponents}` is `true`.
  41982. * @accessor
  41983. */
  41984. itemConfig: {},
  41985. /**
  41986. * @cfg {Number} maxItemCache
  41987. * Maintains a cache of reusable components when using a component based DataView. Improving performance at
  41988. * the cost of memory.
  41989. *
  41990. * __Note:__ this is currently only used when `{@link #useComponents}` is `true`.
  41991. * @accessor
  41992. */
  41993. maxItemCache: 20,
  41994. /**
  41995. * @cfg {String} defaultType
  41996. * The xtype used for the component based DataView.
  41997. *
  41998. * __Note:__ this is only used when `{@link #useComponents}` is `true`.
  41999. * @accessor
  42000. */
  42001. defaultType: 'dataitem',
  42002. /**
  42003. * @cfg {Boolean} scrollToTopOnRefresh
  42004. * Scroll the DataView to the top when the DataView is refreshed.
  42005. * @accessor
  42006. */
  42007. scrollToTopOnRefresh: true
  42008. },
  42009. constructor: function(config) {
  42010. var me = this,
  42011. layout;
  42012. me.hasLoadedStore = false;
  42013. me.mixins.selectable.constructor.apply(me, arguments);
  42014. me.indexOffset = 0;
  42015. me.callParent(arguments);
  42016. //<debug>
  42017. layout = this.getLayout();
  42018. if (layout && !layout.isAuto) {
  42019. Ext.Logger.error('The base layout for a DataView must always be an Auto Layout');
  42020. }
  42021. //</debug>
  42022. },
  42023. updateItemCls: function(newCls, oldCls) {
  42024. var container = this.container;
  42025. if (container) {
  42026. if (oldCls) {
  42027. container.doRemoveItemCls(oldCls);
  42028. }
  42029. if (newCls) {
  42030. container.doAddItemCls(newCls);
  42031. }
  42032. }
  42033. },
  42034. storeEventHooks: {
  42035. beforeload: 'onBeforeLoad',
  42036. load: 'onLoad',
  42037. refresh: 'refresh',
  42038. addrecords: 'onStoreAdd',
  42039. removerecords: 'onStoreRemove',
  42040. updaterecord: 'onStoreUpdate'
  42041. },
  42042. initialize: function() {
  42043. this.callParent();
  42044. var me = this,
  42045. container;
  42046. me.on(me.getTriggerCtEvent(), me.onContainerTrigger, me);
  42047. container = me.container = this.add(new Ext.dataview[me.getUseComponents() ? 'component' : 'element'].Container({
  42048. baseCls: this.getBaseCls()
  42049. }));
  42050. container.dataview = me;
  42051. me.on(me.getTriggerEvent(), me.onItemTrigger, me);
  42052. container.on({
  42053. itemtouchstart: 'onItemTouchStart',
  42054. itemtouchend: 'onItemTouchEnd',
  42055. itemtap: 'onItemTap',
  42056. itemtaphold: 'onItemTapHold',
  42057. itemtouchmove: 'onItemTouchMove',
  42058. itemsingletap: 'onItemSingleTap',
  42059. itemdoubletap: 'onItemDoubleTap',
  42060. itemswipe: 'onItemSwipe',
  42061. scope: me
  42062. });
  42063. if (me.getStore()) {
  42064. if (me.isPainted()) {
  42065. me.refresh();
  42066. }
  42067. else {
  42068. me.on({
  42069. painted: 'refresh',
  42070. single: true
  42071. });
  42072. }
  42073. }
  42074. },
  42075. applyInline: function(config) {
  42076. if (Ext.isObject(config)) {
  42077. config = Ext.apply({}, config);
  42078. }
  42079. return config;
  42080. },
  42081. updateInline: function(newInline, oldInline) {
  42082. var baseCls = this.getBaseCls();
  42083. if (oldInline) {
  42084. this.removeCls([baseCls + '-inlineblock', baseCls + '-nowrap']);
  42085. }
  42086. if (newInline) {
  42087. this.addCls(baseCls + '-inlineblock');
  42088. if (Ext.isObject(newInline) && newInline.wrap === false) {
  42089. this.addCls(baseCls + '-nowrap');
  42090. }
  42091. else {
  42092. this.removeCls(baseCls + '-nowrap');
  42093. }
  42094. }
  42095. },
  42096. /**
  42097. * Function which can be overridden to provide custom formatting for each Record that is used by this
  42098. * DataView's {@link #tpl template} to render each node.
  42099. * @param {Object/Object[]} data The raw data object that was used to create the Record.
  42100. * @param {Number} recordIndex the index number of the Record being prepared for rendering.
  42101. * @param {Ext.data.Model} record The Record being prepared for rendering.
  42102. * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s `overwrite()` method.
  42103. * (either an array if your params are numeric (i.e. `{0}`) or an object (i.e. `{foo: 'bar'}`))
  42104. */
  42105. prepareData: function(data, index, record) {
  42106. return data;
  42107. },
  42108. // apply to the selection model to maintain visual UI cues
  42109. onContainerTrigger: function(e) {
  42110. var me = this;
  42111. if (e.target != me.element.dom) {
  42112. return;
  42113. }
  42114. if (me.getDeselectOnContainerClick() && me.getStore()) {
  42115. me.deselectAll();
  42116. }
  42117. },
  42118. // apply to the selection model to maintain visual UI cues
  42119. onItemTrigger: function(me, index) {
  42120. this.selectWithEvent(this.getStore().getAt(index));
  42121. },
  42122. doAddPressedCls: function(record) {
  42123. var me = this,
  42124. item = me.getItemAt(me.getStore().indexOf(record));
  42125. if (Ext.isElement(item)) {
  42126. item = Ext.get(item);
  42127. }
  42128. if (item) {
  42129. item.addCls(me.getPressedCls());
  42130. }
  42131. },
  42132. onItemTouchStart: function(container, target, index, e) {
  42133. var me = this,
  42134. store = me.getStore(),
  42135. record = store && store.getAt(index);
  42136. me.fireAction('itemtouchstart', [me, index, target, record, e], 'doItemTouchStart');
  42137. },
  42138. doItemTouchStart: function(me, index, target, record) {
  42139. var pressedDelay = me.getPressedDelay();
  42140. if (record) {
  42141. if (pressedDelay > 0) {
  42142. me.pressedTimeout = Ext.defer(me.doAddPressedCls, pressedDelay, me, [record]);
  42143. }
  42144. else {
  42145. me.doAddPressedCls(record);
  42146. }
  42147. }
  42148. },
  42149. onItemTouchEnd: function(container, target, index, e) {
  42150. var me = this,
  42151. store = me.getStore(),
  42152. record = store && store.getAt(index);
  42153. if (this.hasOwnProperty('pressedTimeout')) {
  42154. clearTimeout(this.pressedTimeout);
  42155. delete this.pressedTimeout;
  42156. }
  42157. if (record && target) {
  42158. target.removeCls(me.getPressedCls());
  42159. }
  42160. me.fireEvent('itemtouchend', me, index, target, record, e);
  42161. },
  42162. onItemTouchMove: function(container, target, index, e) {
  42163. var me = this,
  42164. store = me.getStore(),
  42165. record = store && store.getAt(index);
  42166. if (me.hasOwnProperty('pressedTimeout')) {
  42167. clearTimeout(me.pressedTimeout);
  42168. delete me.pressedTimeout;
  42169. }
  42170. if (record && target) {
  42171. target.removeCls(me.getPressedCls());
  42172. }
  42173. me.fireEvent('itemtouchmove', me, index, target, record, e);
  42174. },
  42175. onItemTap: function(container, target, index, e) {
  42176. var me = this,
  42177. store = me.getStore(),
  42178. record = store && store.getAt(index);
  42179. me.fireEvent('itemtap', me, index, target, record, e);
  42180. },
  42181. onItemTapHold: function(container, target, index, e) {
  42182. var me = this,
  42183. store = me.getStore(),
  42184. record = store && store.getAt(index);
  42185. me.fireEvent('itemtaphold', me, index, target, record, e);
  42186. },
  42187. onItemSingleTap: function(container, target, index, e) {
  42188. var me = this,
  42189. store = me.getStore(),
  42190. record = store && store.getAt(index);
  42191. me.fireEvent('itemsingletap', me, index, target, record, e);
  42192. },
  42193. onItemDoubleTap: function(container, target, index, e) {
  42194. var me = this,
  42195. store = me.getStore(),
  42196. record = store && store.getAt(index);
  42197. me.fireEvent('itemdoubletap', me, index, target, record, e);
  42198. },
  42199. onItemSwipe: function(container, target, index, e) {
  42200. var me = this,
  42201. store = me.getStore(),
  42202. record = store && store.getAt(index);
  42203. me.fireEvent('itemswipe', me, index, target, record, e);
  42204. },
  42205. // invoked by the selection model to maintain visual UI cues
  42206. onItemSelect: function(record, suppressEvent) {
  42207. var me = this;
  42208. if (suppressEvent) {
  42209. me.doItemSelect(me, record);
  42210. } else {
  42211. me.fireAction('select', [me, record], 'doItemSelect');
  42212. }
  42213. },
  42214. // invoked by the selection model to maintain visual UI cues
  42215. doItemSelect: function(me, record) {
  42216. if (me.container && !me.isDestroyed) {
  42217. var item = me.getItemAt(me.getStore().indexOf(record));
  42218. if (Ext.isElement(item)) {
  42219. item = Ext.get(item);
  42220. }
  42221. if (item) {
  42222. item.removeCls(me.getPressedCls());
  42223. item.addCls(me.getSelectedCls());
  42224. }
  42225. }
  42226. },
  42227. // invoked by the selection model to maintain visual UI cues
  42228. onItemDeselect: function(record, suppressEvent) {
  42229. var me = this;
  42230. if (me.container && !me.isDestroyed) {
  42231. if (suppressEvent) {
  42232. me.doItemDeselect(me, record);
  42233. }
  42234. else {
  42235. me.fireAction('deselect', [me, record, suppressEvent], 'doItemDeselect');
  42236. }
  42237. }
  42238. },
  42239. doItemDeselect: function(me, record) {
  42240. var item = me.getItemAt(me.getStore().indexOf(record));
  42241. if (Ext.isElement(item)) {
  42242. item = Ext.get(item);
  42243. }
  42244. if (item) {
  42245. item.removeCls([me.getPressedCls(), me.getSelectedCls()]);
  42246. }
  42247. },
  42248. updateData: function(data) {
  42249. var store = this.getStore();
  42250. if (!store) {
  42251. this.setStore(Ext.create('Ext.data.Store', {
  42252. data: data
  42253. }));
  42254. } else {
  42255. store.add(data);
  42256. }
  42257. },
  42258. applyStore: function(store) {
  42259. var me = this,
  42260. bindEvents = Ext.apply({}, me.storeEventHooks, { scope: me }),
  42261. proxy, reader;
  42262. if (store) {
  42263. store = Ext.data.StoreManager.lookup(store);
  42264. if (store && Ext.isObject(store) && store.isStore) {
  42265. store.on(bindEvents);
  42266. proxy = store.getProxy();
  42267. if (proxy) {
  42268. reader = proxy.getReader();
  42269. if (reader) {
  42270. reader.on('exception', 'handleException', this);
  42271. }
  42272. }
  42273. }
  42274. //<debug warn>
  42275. else {
  42276. Ext.Logger.warn("The specified Store cannot be found", this);
  42277. }
  42278. //</debug>
  42279. }
  42280. return store;
  42281. },
  42282. /**
  42283. * Method called when the Store's Reader throws an exception
  42284. * @method handleException
  42285. */
  42286. handleException: function() {
  42287. this.setMasked(false);
  42288. },
  42289. updateStore: function(newStore, oldStore) {
  42290. var me = this,
  42291. bindEvents = Ext.apply({}, me.storeEventHooks, { scope: me }),
  42292. proxy, reader;
  42293. if (oldStore && Ext.isObject(oldStore) && oldStore.isStore) {
  42294. me.onStoreClear();
  42295. if (oldStore.getAutoDestroy()) {
  42296. oldStore.destroy();
  42297. }
  42298. else {
  42299. oldStore.un(bindEvents);
  42300. proxy = oldStore.getProxy();
  42301. if (proxy) {
  42302. reader = proxy.getReader();
  42303. if (reader) {
  42304. reader.un('exception', 'handleException', this);
  42305. }
  42306. }
  42307. }
  42308. }
  42309. if (newStore) {
  42310. if (newStore.isLoaded()) {
  42311. this.hasLoadedStore = true;
  42312. }
  42313. if (newStore.isLoading()) {
  42314. me.onBeforeLoad();
  42315. }
  42316. if (me.container) {
  42317. me.refresh();
  42318. }
  42319. }
  42320. },
  42321. onBeforeLoad: function() {
  42322. var loadingText = this.getLoadingText();
  42323. if (loadingText && this.isPainted()) {
  42324. this.setMasked({
  42325. xtype: 'loadmask',
  42326. message: loadingText
  42327. });
  42328. }
  42329. this.hideEmptyText();
  42330. },
  42331. updateEmptyText: function(newEmptyText, oldEmptyText) {
  42332. var me = this,
  42333. store;
  42334. if (oldEmptyText && me.emptyTextCmp) {
  42335. me.remove(me.emptyTextCmp, true);
  42336. delete me.emptyTextCmp;
  42337. }
  42338. if (newEmptyText) {
  42339. me.emptyTextCmp = me.add({
  42340. xtype: 'component',
  42341. cls: me.getBaseCls() + '-emptytext',
  42342. html: newEmptyText,
  42343. hidden: true
  42344. });
  42345. store = me.getStore();
  42346. if (store && me.hasLoadedStore && !store.getCount()) {
  42347. this.showEmptyText();
  42348. }
  42349. }
  42350. },
  42351. onLoad: function(store) {
  42352. //remove any masks on the store
  42353. this.hasLoadedStore = true;
  42354. this.setMasked(false);
  42355. if (!store.getCount()) {
  42356. this.showEmptyText();
  42357. }
  42358. },
  42359. /**
  42360. * Refreshes the view by reloading the data from the store and re-rendering the template.
  42361. */
  42362. refresh: function() {
  42363. var me = this,
  42364. container = me.container;
  42365. if (!me.getStore()) {
  42366. if (!me.hasLoadedStore && !me.getDeferEmptyText()) {
  42367. me.showEmptyText();
  42368. }
  42369. return;
  42370. }
  42371. if (container) {
  42372. me.fireAction('refresh', [me], 'doRefresh');
  42373. }
  42374. },
  42375. applyItemTpl: function(config) {
  42376. return (Ext.isObject(config) && config.isTemplate) ? config : new Ext.XTemplate(config);
  42377. },
  42378. onAfterRender: function() {
  42379. var me = this;
  42380. me.callParent(arguments);
  42381. me.updateStore(me.getStore());
  42382. },
  42383. /**
  42384. * Returns an item at the specified index.
  42385. * @param {Number} index Index of the item.
  42386. * @return {Ext.dom.Element/Ext.dataview.component.DataItem} item Item at the specified index.
  42387. */
  42388. getItemAt: function(index) {
  42389. return this.getViewItems()[index - this.indexOffset];
  42390. },
  42391. /**
  42392. * Returns an index for the specified item.
  42393. * @param {Number} item The item to locate.
  42394. * @return {Number} Index for the specified item.
  42395. */
  42396. getItemIndex: function(item) {
  42397. var index = this.getViewItems().indexOf(item);
  42398. return (index === -1) ? index : this.indexOffset + index;
  42399. },
  42400. /**
  42401. * Returns an array of the current items in the DataView.
  42402. * @return {Ext.dom.Element[]/Ext.dataview.component.DataItem[]} Array of Items.
  42403. */
  42404. getViewItems: function() {
  42405. return this.container.getViewItems();
  42406. },
  42407. doRefresh: function(me) {
  42408. var container = me.container,
  42409. store = me.getStore(),
  42410. records = store.getRange(),
  42411. items = me.getViewItems(),
  42412. recordsLn = records.length,
  42413. itemsLn = items.length,
  42414. deltaLn = recordsLn - itemsLn,
  42415. scrollable = me.getScrollable(),
  42416. i, item;
  42417. if (this.getScrollToTopOnRefresh() && scrollable) {
  42418. scrollable.getScroller().scrollToTop();
  42419. }
  42420. // No items, hide all the items from the collection.
  42421. if (recordsLn < 1) {
  42422. me.onStoreClear();
  42423. return;
  42424. } else {
  42425. me.hideEmptyText();
  42426. }
  42427. // Too many items, hide the unused ones
  42428. if (deltaLn < 0) {
  42429. container.moveItemsToCache(itemsLn + deltaLn, itemsLn - 1);
  42430. // Items can changed, we need to refresh our references
  42431. items = me.getViewItems();
  42432. itemsLn = items.length;
  42433. }
  42434. // Not enough items, create new ones
  42435. else if (deltaLn > 0) {
  42436. container.moveItemsFromCache(store.getRange(itemsLn));
  42437. }
  42438. // Update Data and insert the new html for existing items
  42439. for (i = 0; i < itemsLn; i++) {
  42440. item = items[i];
  42441. container.updateListItem(records[i], item);
  42442. }
  42443. },
  42444. showEmptyText: function() {
  42445. if (this.getEmptyText() && (this.hasLoadedStore || !this.getDeferEmptyText()) ) {
  42446. this.emptyTextCmp.show();
  42447. }
  42448. },
  42449. hideEmptyText: function() {
  42450. if (this.getEmptyText()) {
  42451. this.emptyTextCmp.hide();
  42452. }
  42453. },
  42454. destroy: function() {
  42455. var store = this.getStore();
  42456. if (store && store.getAutoDestroy()) {
  42457. store.destroy();
  42458. }
  42459. this.callParent(arguments);
  42460. },
  42461. onStoreClear: function() {
  42462. var me = this,
  42463. container = me.container,
  42464. items = me.getViewItems();
  42465. container.moveItemsToCache(0, items.length - 1);
  42466. this.showEmptyText();
  42467. },
  42468. /**
  42469. * @private
  42470. * @param store
  42471. * @param records
  42472. */
  42473. onStoreAdd: function(store, records) {
  42474. if (records) {
  42475. this.hideEmptyText();
  42476. this.container.moveItemsFromCache(records);
  42477. }
  42478. },
  42479. /**
  42480. * @private
  42481. * @param store
  42482. * @param records
  42483. * @param indices
  42484. */
  42485. onStoreRemove: function(store, records, indices) {
  42486. var container = this.container,
  42487. ln = records.length,
  42488. i;
  42489. for (i = 0; i < ln; i++) {
  42490. container.moveItemsToCache(indices[i], indices[i]);
  42491. }
  42492. },
  42493. /**
  42494. * @private
  42495. * @param store
  42496. * @param record
  42497. * @param {Number} newIndex
  42498. * @param {Number} oldIndex
  42499. */
  42500. onStoreUpdate: function(store, record, newIndex, oldIndex) {
  42501. var me = this,
  42502. container = me.container;
  42503. oldIndex = (typeof oldIndex === 'undefined') ? newIndex : oldIndex;
  42504. if (oldIndex !== newIndex) {
  42505. container.updateAtNewIndex(oldIndex, newIndex, record);
  42506. if (me.isSelected(record)) {
  42507. me.doItemSelect(me, record);
  42508. }
  42509. }
  42510. else {
  42511. // Bypassing setter because sometimes we pass the same record (different data)
  42512. container.updateListItem(record, me.getViewItems()[newIndex]);
  42513. }
  42514. }
  42515. });
  42516. /**
  42517. * @author Ed Spencer
  42518. *
  42519. * Represents a single read or write operation performed by a {@link Ext.data.proxy.Proxy Proxy}. Operation objects are
  42520. * used to enable communication between Stores and Proxies. Application developers should rarely need to interact with
  42521. * Operation objects directly.
  42522. *
  42523. * Note that when you define an Operation directly, you need to specify at least the {@link #model} configuration.
  42524. *
  42525. * Several Operations can be batched together in a {@link Ext.data.Batch batch}.
  42526. */
  42527. Ext.define('Ext.data.Operation', {
  42528. config: {
  42529. /**
  42530. * @cfg {Boolean} synchronous
  42531. * True if this Operation is to be executed synchronously. This property is inspected by a
  42532. * {@link Ext.data.Batch Batch} to see if a series of Operations can be executed in parallel or not.
  42533. * @accessor
  42534. */
  42535. synchronous: true,
  42536. /**
  42537. * @cfg {String} action
  42538. * The action being performed by this Operation. Should be one of 'create', 'read', 'update' or 'destroy'.
  42539. * @accessor
  42540. */
  42541. action: null,
  42542. /**
  42543. * @cfg {Ext.util.Filter[]} filters
  42544. * Optional array of filter objects. Only applies to 'read' actions.
  42545. * @accessor
  42546. */
  42547. filters: null,
  42548. /**
  42549. * @cfg {Ext.util.Sorter[]} sorters
  42550. * Optional array of sorter objects. Only applies to 'read' actions.
  42551. * @accessor
  42552. */
  42553. sorters: null,
  42554. /**
  42555. * @cfg {Ext.util.Grouper} grouper
  42556. * Optional grouping configuration. Only applies to 'read' actions where grouping is desired.
  42557. * @accessor
  42558. */
  42559. grouper: null,
  42560. /**
  42561. * @cfg {Number} start
  42562. * The start index (offset), used in paging when running a 'read' action.
  42563. * @accessor
  42564. */
  42565. start: null,
  42566. /**
  42567. * @cfg {Number} limit
  42568. * The number of records to load. Used on 'read' actions when paging is being used.
  42569. * @accessor
  42570. */
  42571. limit: null,
  42572. /**
  42573. * @cfg {Ext.data.Batch} batch
  42574. * The batch that this Operation is a part of.
  42575. * @accessor
  42576. */
  42577. batch: null,
  42578. /**
  42579. * @cfg {Function} callback
  42580. * Function to execute when operation completed.
  42581. * @cfg {Ext.data.Model[]} callback.records Array of records.
  42582. * @cfg {Ext.data.Operation} callback.operation The Operation itself.
  42583. * @accessor
  42584. */
  42585. callback: null,
  42586. /**
  42587. * @cfg {Object} scope
  42588. * Scope for the {@link #callback} function.
  42589. * @accessor
  42590. */
  42591. scope: null,
  42592. /**
  42593. * @cfg {Ext.data.ResultSet} resultSet
  42594. * The ResultSet for this operation.
  42595. * @accessor
  42596. */
  42597. resultSet: null,
  42598. /**
  42599. * @cfg {Array} records
  42600. * The records associated to this operation. Before an operation starts, these
  42601. * are the records you are updating, creating, or destroying. After an operation
  42602. * is completed, a Proxy usually sets these records on the Operation to become
  42603. * the processed records. If you don't set these records on your operation in
  42604. * your proxy, then the getter will return the ones defined on the {@link #resultSet}
  42605. * @accessor
  42606. */
  42607. records: null,
  42608. /**
  42609. * @cfg {Ext.data.Request} request
  42610. * The request used for this Operation. Operations don't usually care about Request and Response data, but in the
  42611. * ServerProxy and any of its subclasses they may be useful for further processing.
  42612. * @accessor
  42613. */
  42614. request: null,
  42615. /**
  42616. * @cfg {Object} response
  42617. * The response that was gotten from the server if there was one.
  42618. * @accessor
  42619. */
  42620. response: null,
  42621. /**
  42622. * @cfg {Boolean} withCredentials
  42623. * This field is necessary when using cross-origin resource sharing.
  42624. * @accessor
  42625. */
  42626. withCredentials: null,
  42627. /**
  42628. * @cfg {Object} params
  42629. * The params send along with this operation. These usually apply to a Server proxy if you are
  42630. * creating your own custom proxy,
  42631. * @accessor
  42632. */
  42633. params: null,
  42634. url: null,
  42635. page: null,
  42636. node: null,
  42637. /**
  42638. * @cfg {Ext.data.Model} model
  42639. * The Model that this Operation will be dealing with. This configuration is required when defining any Operation.
  42640. * Since Operations take care of creating, updating, destroying and reading records, it needs access to the Model.
  42641. * @accessor
  42642. */
  42643. model: undefined,
  42644. addRecords: false
  42645. },
  42646. /**
  42647. * @property {Boolean} started
  42648. * Property tracking the start status of this Operation. Use {@link #isStarted}.
  42649. * @private
  42650. * @readonly
  42651. */
  42652. started: false,
  42653. /**
  42654. * @property {Boolean} running
  42655. * Property tracking the run status of this Operation. Use {@link #isRunning}.
  42656. * @private
  42657. * @readonly
  42658. */
  42659. running: false,
  42660. /**
  42661. * @property {Boolean} complete
  42662. * Property tracking the completion status of this Operation. Use {@link #isComplete}.
  42663. * @private
  42664. * @readonly
  42665. */
  42666. complete: false,
  42667. /**
  42668. * @property {Boolean} success
  42669. * Property tracking whether the Operation was successful or not. This starts as undefined and is set to `true`
  42670. * or `false` by the Proxy that is executing the Operation. It is also set to false by {@link #setException}. Use
  42671. * {@link #wasSuccessful} to query success status.
  42672. * @private
  42673. * @readonly
  42674. */
  42675. success: undefined,
  42676. /**
  42677. * @property {Boolean} exception
  42678. * Property tracking the exception status of this Operation. Use {@link #hasException} and see {@link #getError}.
  42679. * @private
  42680. * @readonly
  42681. */
  42682. exception: false,
  42683. /**
  42684. * @property {String/Object} error
  42685. * The error object passed when {@link #setException} was called. This could be any object or primitive.
  42686. * @private
  42687. */
  42688. error: undefined,
  42689. /**
  42690. * Creates new Operation object.
  42691. * @param {Object} config (optional) Config object.
  42692. */
  42693. constructor: function(config) {
  42694. this.initConfig(config);
  42695. },
  42696. applyModel: function(model) {
  42697. if (typeof model == 'string') {
  42698. model = Ext.data.ModelManager.getModel(model);
  42699. if (!model) {
  42700. Ext.Logger.error('Model with name ' + arguments[0] + ' doesnt exist.');
  42701. }
  42702. }
  42703. if (model && !model.prototype.isModel && Ext.isObject(model)) {
  42704. model = Ext.data.ModelManager.registerType(model.storeId || model.id || Ext.id(), model);
  42705. }
  42706. // <debug>
  42707. if (!model) {
  42708. Ext.Logger.warn('Unless you define your model using metadata, an Operation needs to have a model defined.');
  42709. }
  42710. // </debug>
  42711. return model;
  42712. },
  42713. getRecords: function() {
  42714. var resultSet = this.getResultSet();
  42715. return this._records || (resultSet ? resultSet.getRecords() : []);
  42716. },
  42717. /**
  42718. * Marks the Operation as started.
  42719. */
  42720. setStarted: function() {
  42721. this.started = true;
  42722. this.running = true;
  42723. },
  42724. /**
  42725. * Marks the Operation as completed.
  42726. */
  42727. setCompleted: function() {
  42728. this.complete = true;
  42729. this.running = false;
  42730. },
  42731. /**
  42732. * Marks the Operation as successful.
  42733. */
  42734. setSuccessful: function() {
  42735. this.success = true;
  42736. },
  42737. /**
  42738. * Marks the Operation as having experienced an exception. Can be supplied with an option error message/object.
  42739. * @param {String/Object} error (optional) error string/object
  42740. */
  42741. setException: function(error) {
  42742. this.exception = true;
  42743. this.success = false;
  42744. this.running = false;
  42745. this.error = error;
  42746. },
  42747. /**
  42748. * Returns `true` if this Operation encountered an exception (see also {@link #getError}).
  42749. * @return {Boolean} `true` if there was an exception.
  42750. */
  42751. hasException: function() {
  42752. return this.exception === true;
  42753. },
  42754. /**
  42755. * Returns the error string or object that was set using {@link #setException}.
  42756. * @return {String/Object} The error object.
  42757. */
  42758. getError: function() {
  42759. return this.error;
  42760. },
  42761. /**
  42762. * Returns `true` if the Operation has been started. Note that the Operation may have started AND completed, see
  42763. * {@link #isRunning} to test if the Operation is currently running.
  42764. * @return {Boolean} `true` if the Operation has started
  42765. */
  42766. isStarted: function() {
  42767. return this.started === true;
  42768. },
  42769. /**
  42770. * Returns `true` if the Operation has been started but has not yet completed.
  42771. * @return {Boolean} `true` if the Operation is currently running
  42772. */
  42773. isRunning: function() {
  42774. return this.running === true;
  42775. },
  42776. /**
  42777. * Returns `true` if the Operation has been completed
  42778. * @return {Boolean} `true` if the Operation is complete
  42779. */
  42780. isComplete: function() {
  42781. return this.complete === true;
  42782. },
  42783. /**
  42784. * Returns `true` if the Operation has completed and was successful
  42785. * @return {Boolean} `true` if successful
  42786. */
  42787. wasSuccessful: function() {
  42788. return this.isComplete() && this.success === true;
  42789. },
  42790. /**
  42791. * Checks whether this operation should cause writing to occur.
  42792. * @return {Boolean} Whether the operation should cause a write to occur.
  42793. */
  42794. allowWrite: function() {
  42795. return this.getAction() != 'read';
  42796. },
  42797. process: function(action, resultSet, request, response) {
  42798. if (resultSet.getSuccess() !== false) {
  42799. this.setResponse(response);
  42800. this.setResultSet(resultSet);
  42801. this.setCompleted();
  42802. this.setSuccessful();
  42803. } else {
  42804. return false;
  42805. }
  42806. return this['process' + Ext.String.capitalize(action)].call(this, resultSet, request, response);
  42807. },
  42808. processRead: function(resultSet) {
  42809. var records = resultSet.getRecords(),
  42810. processedRecords = [],
  42811. Model = this.getModel(),
  42812. ln = records.length,
  42813. i, record;
  42814. for (i = 0; i < ln; i++) {
  42815. record = records[i];
  42816. processedRecords.push(new Model(record.data, record.id, record.node));
  42817. }
  42818. this.setRecords(processedRecords);
  42819. resultSet.setRecords(processedRecords);
  42820. return true;
  42821. },
  42822. processCreate: function(resultSet) {
  42823. var updatedRecords = resultSet.getRecords(),
  42824. currentRecords = this.getRecords(),
  42825. ln = updatedRecords.length,
  42826. i, currentRecord, updatedRecord;
  42827. for (i = 0; i < ln; i++) {
  42828. updatedRecord = updatedRecords[i];
  42829. if (updatedRecord.clientId === null && currentRecords.length == 1 && updatedRecords.length == 1) {
  42830. currentRecord = currentRecords[i];
  42831. } else {
  42832. currentRecord = this.findCurrentRecord(updatedRecord.clientId);
  42833. }
  42834. if (currentRecord) {
  42835. this.updateRecord(currentRecord, updatedRecord);
  42836. }
  42837. // <debug>
  42838. else {
  42839. Ext.Logger.warn('Unable to match the record that came back from the server.');
  42840. }
  42841. // </debug>
  42842. }
  42843. return true;
  42844. },
  42845. processUpdate: function(resultSet) {
  42846. var updatedRecords = resultSet.getRecords(),
  42847. currentRecords = this.getRecords(),
  42848. ln = updatedRecords.length,
  42849. i, currentRecord, updatedRecord;
  42850. for (i = 0; i < ln; i++) {
  42851. updatedRecord = updatedRecords[i];
  42852. currentRecord = currentRecords[i];
  42853. if (currentRecord) {
  42854. this.updateRecord(currentRecord, updatedRecord);
  42855. }
  42856. // <debug>
  42857. else {
  42858. Ext.Logger.warn('Unable to match the updated record that came back from the server.');
  42859. }
  42860. // </debug>
  42861. }
  42862. return true;
  42863. },
  42864. processDestroy: function(resultSet) {
  42865. var updatedRecords = resultSet.getRecords(),
  42866. ln = updatedRecords.length,
  42867. i, currentRecord, updatedRecord;
  42868. for (i = 0; i < ln; i++) {
  42869. updatedRecord = updatedRecords[i];
  42870. currentRecord = this.findCurrentRecord(updatedRecord.id);
  42871. if (currentRecord) {
  42872. currentRecord.setIsErased(true);
  42873. currentRecord.notifyStores('afterErase', currentRecord);
  42874. }
  42875. // <debug>
  42876. else {
  42877. Ext.Logger.warn('Unable to match the destroyed record that came back from the server.');
  42878. }
  42879. // </debug>
  42880. }
  42881. },
  42882. findCurrentRecord: function(clientId) {
  42883. var currentRecords = this.getRecords(),
  42884. ln = currentRecords.length,
  42885. i, currentRecord;
  42886. for (i = 0; i < ln; i++) {
  42887. currentRecord = currentRecords[i];
  42888. if (currentRecord.getId() === clientId) {
  42889. return currentRecord;
  42890. }
  42891. }
  42892. },
  42893. updateRecord: function(currentRecord, updatedRecord) {
  42894. var recordData = updatedRecord.data,
  42895. recordId = updatedRecord.id;
  42896. currentRecord.beginEdit();
  42897. currentRecord.set(recordData);
  42898. if (recordId !== null) {
  42899. currentRecord.setId(recordId);
  42900. }
  42901. // We call endEdit with silent: true because the commit below already makes
  42902. // sure any store is notified of the record being updated.
  42903. currentRecord.endEdit(true);
  42904. currentRecord.commit();
  42905. }
  42906. });
  42907. /**
  42908. * @author Ed Spencer
  42909. *
  42910. * Simple wrapper class that represents a set of records returned by a Proxy.
  42911. */
  42912. Ext.define('Ext.data.ResultSet', {
  42913. config: {
  42914. /**
  42915. * @cfg {Boolean} loaded
  42916. * True if the records have already been loaded. This is only meaningful when dealing with
  42917. * SQL-backed proxies.
  42918. */
  42919. loaded: true,
  42920. /**
  42921. * @cfg {Number} count
  42922. * The number of records in this ResultSet. Note that total may differ from this number.
  42923. */
  42924. count: null,
  42925. /**
  42926. * @cfg {Number} total
  42927. * The total number of records reported by the data source. This ResultSet may form a subset of
  42928. * those records (see {@link #count}).
  42929. */
  42930. total: null,
  42931. /**
  42932. * @cfg {Boolean} success
  42933. * True if the ResultSet loaded successfully, false if any errors were encountered.
  42934. */
  42935. success: false,
  42936. /**
  42937. * @cfg {Ext.data.Model[]} records (required)
  42938. * The array of record instances.
  42939. */
  42940. records: null,
  42941. /**
  42942. * @cfg {String} message
  42943. * The message that was read in from the data
  42944. */
  42945. message: null
  42946. },
  42947. /**
  42948. * Creates the resultSet
  42949. * @param {Object} [config] Config object.
  42950. */
  42951. constructor: function(config) {
  42952. this.initConfig(config);
  42953. },
  42954. applyCount: function(count) {
  42955. if (!count && count !== 0) {
  42956. return this.getRecords().length;
  42957. }
  42958. return count;
  42959. },
  42960. /**
  42961. * @private
  42962. * Make sure we set the right count when new records have been sent in
  42963. */
  42964. updateRecords: function(records) {
  42965. this.setCount(records.length);
  42966. }
  42967. });
  42968. /**
  42969. * @author Ed Spencer
  42970. *
  42971. * Readers are used to interpret data to be loaded into a {@link Ext.data.Model Model} instance or a {@link
  42972. * Ext.data.Store Store} - often in response to an AJAX request. In general there is usually no need to create
  42973. * a Reader instance directly, since a Reader is almost always used together with a {@link Ext.data.proxy.Proxy Proxy},
  42974. * and is configured using the Proxy's {@link Ext.data.proxy.Proxy#cfg-reader reader} configuration property:
  42975. *
  42976. * Ext.define("User", {
  42977. * extend: "Ext.data.Model",
  42978. * config: {
  42979. * fields: [
  42980. * "id",
  42981. * "name"
  42982. * ]
  42983. * }
  42984. * });
  42985. *
  42986. * Ext.create("Ext.data.Store", {
  42987. * model: "User",
  42988. * autoLoad: true,
  42989. * storeId: "usersStore",
  42990. * proxy: {
  42991. * type: "ajax",
  42992. * url : "users.json",
  42993. * reader: {
  42994. * type: "json",
  42995. * rootProperty: "users"
  42996. * }
  42997. * }
  42998. * });
  42999. *
  43000. * Ext.create("Ext.List", {
  43001. * fullscreen: true,
  43002. * itemTpl: "{name} (id: '{id}')",
  43003. * store: "usersStore"
  43004. * });
  43005. *
  43006. * The above reader is configured to consume a JSON string that looks something like this:
  43007. *
  43008. * {
  43009. * "success": true,
  43010. * "users": [
  43011. * { "name": "User 1" },
  43012. * { "name": "User 2" }
  43013. * ]
  43014. * }
  43015. *
  43016. *
  43017. * # Loading Nested Data
  43018. *
  43019. * Readers have the ability to automatically load deeply-nested data objects based on the {@link Ext.data.association.Association
  43020. * associations} configured on each Model. Below is an example demonstrating the flexibility of these associations in a
  43021. * fictional CRM system which manages a User, their Orders, OrderItems and Products. First we'll define the models:
  43022. *
  43023. * Ext.define("User", {
  43024. * extend: "Ext.data.Model",
  43025. * config: {
  43026. * fields: [
  43027. * "id",
  43028. * "name"
  43029. * ],
  43030. * hasMany: {
  43031. * model: "Order",
  43032. * name: "orders"
  43033. * },
  43034. * proxy: {
  43035. * type: "rest",
  43036. * url : "users.json",
  43037. * reader: {
  43038. * type: "json",
  43039. * rootProperty: "users"
  43040. * }
  43041. * }
  43042. * }
  43043. * });
  43044. *
  43045. * Ext.define("Order", {
  43046. * extend: "Ext.data.Model",
  43047. * config: {
  43048. * fields: [
  43049. * "id", "total"
  43050. * ],
  43051. * hasMany: {
  43052. * model: "OrderItem",
  43053. * name: "orderItems",
  43054. * associationKey: "order_items"
  43055. * },
  43056. * belongsTo: "User"
  43057. * }
  43058. * });
  43059. *
  43060. * Ext.define("OrderItem", {
  43061. * extend: "Ext.data.Model",
  43062. * config: {
  43063. * fields: [
  43064. * "id",
  43065. * "price",
  43066. * "quantity",
  43067. * "order_id",
  43068. * "product_id"
  43069. * ],
  43070. * belongsTo: [
  43071. * "Order", {
  43072. * model: "Product",
  43073. * associationKey: "product"
  43074. * }
  43075. * ]
  43076. * }
  43077. * });
  43078. *
  43079. * Ext.define("Product", {
  43080. * extend: "Ext.data.Model",
  43081. * config: {
  43082. * fields: [
  43083. * "id",
  43084. * "name"
  43085. * ]
  43086. * },
  43087. * hasMany: "OrderItem"
  43088. * });
  43089. *
  43090. * var store = Ext.create('Ext.data.Store', {
  43091. * model: "User"
  43092. * });
  43093. *
  43094. * store.load({
  43095. * callback: function() {
  43096. * var output = [];
  43097. *
  43098. * // the user that was loaded
  43099. * var user = store.first();
  43100. *
  43101. * output.push("Orders for " + user.get('name') + ":");
  43102. *
  43103. * // iterate over the Orders for each User
  43104. * user.orders().each(function(order) {
  43105. * output.push("Order ID: " + order.get('id') + ", which contains items:");
  43106. *
  43107. * // iterate over the OrderItems for each Order
  43108. * order.orderItems().each(function(orderItem) {
  43109. * // We know that the Product data is already loaded, so we can use the
  43110. * // synchronous getProduct() method. Usually, we would use the
  43111. * // asynchronous version (see Ext.data.association.BelongsTo).
  43112. * var product = orderItem.getProduct();
  43113. * output.push(orderItem.get("quantity") + " orders of " + product.get("name"));
  43114. * });
  43115. * });
  43116. * Ext.Msg.alert('Output:', output.join("<br/>"));
  43117. * }
  43118. * });
  43119. *
  43120. * This may be a lot to take in - basically a User has many Orders, each of which is composed of several OrderItems.
  43121. * Finally, each OrderItem has a single Product. This allows us to consume data like this (_users.json_):
  43122. *
  43123. * {
  43124. * "users": [
  43125. * {
  43126. * "id": 123,
  43127. * "name": "Ed",
  43128. * "orders": [
  43129. * {
  43130. * "id": 50,
  43131. * "total": 100,
  43132. * "order_items": [
  43133. * {
  43134. * "id" : 20,
  43135. * "price" : 40,
  43136. * "quantity": 2,
  43137. * "product" : {
  43138. * "id": 1000,
  43139. * "name": "MacBook Pro"
  43140. * }
  43141. * },
  43142. * {
  43143. * "id" : 21,
  43144. * "price" : 20,
  43145. * "quantity": 3,
  43146. * "product" : {
  43147. * "id": 1001,
  43148. * "name": "iPhone"
  43149. * }
  43150. * }
  43151. * ]
  43152. * }
  43153. * ]
  43154. * }
  43155. * ]
  43156. * }
  43157. *
  43158. * The JSON response is deeply nested - it returns all Users (in this case just 1 for simplicity's sake), all of the
  43159. * Orders for each User (again just 1 in this case), all of the OrderItems for each Order (2 order items in this case),
  43160. * and finally the Product associated with each OrderItem.
  43161. *
  43162. * Running the code above results in the following:
  43163. *
  43164. * Orders for Ed:
  43165. * Order ID: 50, which contains items:
  43166. * 2 orders of MacBook Pro
  43167. * 3 orders of iPhone
  43168. */
  43169. Ext.define('Ext.data.reader.Reader', {
  43170. requires: [
  43171. 'Ext.data.ResultSet'
  43172. ],
  43173. alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
  43174. mixins: ['Ext.mixin.Observable'],
  43175. // @private
  43176. isReader: true,
  43177. config: {
  43178. /**
  43179. * @cfg {String} idProperty
  43180. * Name of the property within a raw object that contains a record identifier value. Defaults to The id of the
  43181. * model. If an `idProperty` is explicitly specified it will override that of the one specified on the model
  43182. */
  43183. idProperty: undefined,
  43184. /**
  43185. * @cfg {String} clientIdProperty
  43186. * The name of the property with a response that contains the existing client side id for a record that we are reading.
  43187. */
  43188. clientIdProperty: 'clientId',
  43189. /**
  43190. * @cfg {String} totalProperty
  43191. * Name of the property from which to retrieve the total number of records in the dataset. This is only needed if
  43192. * the whole dataset is not passed in one go, but is being paged from the remote server.
  43193. */
  43194. totalProperty: 'total',
  43195. /**
  43196. * @cfg {String} successProperty
  43197. * Name of the property from which to retrieve the success attribute. See
  43198. * {@link Ext.data.proxy.Server}.{@link Ext.data.proxy.Server#exception exception} for additional information.
  43199. */
  43200. successProperty: 'success',
  43201. /**
  43202. * @cfg {String} messageProperty (optional)
  43203. * The name of the property which contains a response message. This property is optional.
  43204. */
  43205. messageProperty: null,
  43206. /**
  43207. * @cfg {String} rootProperty
  43208. * The name of the property which contains the Array of row objects. For JSON reader it's dot-separated list
  43209. * of property names. For XML reader it's a CSS selector. For array reader it's not applicable.
  43210. *
  43211. * By default the natural root of the data will be used. The root JSON array, the root XML element, or the array.
  43212. *
  43213. * The data packet value for this property should be an empty array to clear the data or show no data.
  43214. */
  43215. rootProperty: '',
  43216. /**
  43217. * @cfg {Boolean} implicitIncludes
  43218. * `true` to automatically parse models nested within other models in a response object. See the
  43219. * {@link Ext.data.reader.Reader} intro docs for full explanation.
  43220. */
  43221. implicitIncludes: true,
  43222. model: undefined
  43223. },
  43224. constructor: function(config) {
  43225. this.initConfig(config);
  43226. },
  43227. /**
  43228. * @property {Object} metaData
  43229. * The raw meta data that was most recently read, if any. Meta data can include existing
  43230. * Reader config options like {@link #idProperty}, {@link #totalProperty}, etc. that get
  43231. * automatically applied to the Reader, and those can still be accessed directly from the Reader
  43232. * if needed. However, meta data is also often used to pass other custom data to be processed
  43233. * by application code. For example, it is common when reconfiguring the data model of a grid to
  43234. * also pass a corresponding column model config to be applied to the grid. Any such data will
  43235. * not get applied to the Reader directly (it just gets passed through and is ignored by Ext).
  43236. * This `metaData` property gives you access to all meta data that was passed, including any such
  43237. * custom data ignored by the reader.
  43238. *
  43239. * This is a read-only property, and it will get replaced each time a new meta data object is
  43240. * passed to the reader.
  43241. * @readonly
  43242. */
  43243. fieldCount: 0,
  43244. applyModel: function(model) {
  43245. if (typeof model == 'string') {
  43246. model = Ext.data.ModelManager.getModel(model);
  43247. if (!model) {
  43248. Ext.Logger.error('Model with name ' + arguments[0] + ' doesnt exist.');
  43249. }
  43250. }
  43251. if (model && !model.prototype.isModel && Ext.isObject(model)) {
  43252. model = Ext.data.ModelManager.registerType(model.storeId || model.id || Ext.id(), model);
  43253. }
  43254. return model;
  43255. },
  43256. applyIdProperty: function(idProperty) {
  43257. if (!idProperty && this.getModel()) {
  43258. idProperty = this.getModel().getIdProperty();
  43259. }
  43260. return idProperty;
  43261. },
  43262. updateModel: function(model) {
  43263. if (model) {
  43264. if (!this.getIdProperty()) {
  43265. this.setIdProperty(model.getIdProperty());
  43266. }
  43267. this.buildExtractors();
  43268. }
  43269. },
  43270. createAccessor: Ext.emptyFn,
  43271. createFieldAccessExpression: function() {
  43272. return 'undefined';
  43273. },
  43274. /**
  43275. * @private
  43276. * This builds optimized functions for retrieving record data and meta data from an object.
  43277. * Subclasses may need to implement their own getRoot function.
  43278. */
  43279. buildExtractors: function() {
  43280. if (!this.getModel()) {
  43281. return;
  43282. }
  43283. var me = this,
  43284. totalProp = me.getTotalProperty(),
  43285. successProp = me.getSuccessProperty(),
  43286. messageProp = me.getMessageProperty();
  43287. //build the extractors for all the meta data
  43288. if (totalProp) {
  43289. me.getTotal = me.createAccessor(totalProp);
  43290. }
  43291. if (successProp) {
  43292. me.getSuccess = me.createAccessor(successProp);
  43293. }
  43294. if (messageProp) {
  43295. me.getMessage = me.createAccessor(messageProp);
  43296. }
  43297. me.extractRecordData = me.buildRecordDataExtractor();
  43298. },
  43299. /**
  43300. * @private
  43301. * Return a function which will read a raw row object in the format this Reader accepts, and populates
  43302. * a record's data object with converted data values.
  43303. *
  43304. * The returned function must be passed the following parameters:
  43305. *
  43306. * - `dest` - A record's empty data object into which the new field value properties are injected.
  43307. * - `source` - A raw row data object of whatever type this Reader consumes
  43308. * - `record - The record which is being populated.
  43309. */
  43310. buildRecordDataExtractor: function() {
  43311. var me = this,
  43312. model = me.getModel(),
  43313. fields = model.getFields(),
  43314. ln = fields.length,
  43315. fieldVarName = [],
  43316. clientIdProp = me.getModel().getClientIdProperty(),
  43317. prefix = '__field',
  43318. code = [
  43319. 'var me = this,\n',
  43320. ' fields = me.getModel().getFields(),\n',
  43321. ' idProperty = me.getIdProperty(),\n',
  43322. ' idPropertyIsFn = (typeof idProperty == "function"),',
  43323. ' value,\n',
  43324. ' internalId'
  43325. ], i, field, varName, fieldName;
  43326. fields = fields.items;
  43327. for (i = 0; i < ln; i++) {
  43328. field = fields[i];
  43329. fieldName = field.getName();
  43330. if (fieldName === model.getIdProperty()) {
  43331. fieldVarName[i] = 'idField';
  43332. } else {
  43333. fieldVarName[i] = prefix + i;
  43334. }
  43335. code.push(',\n ', fieldVarName[i], ' = fields.get("', field.getName(), '")');
  43336. }
  43337. code.push(';\n\n return function(source) {\n var dest = {};\n');
  43338. code.push(' if (idPropertyIsFn) {\n');
  43339. code.push(' idField.setMapping(idProperty);\n');
  43340. code.push(' }\n');
  43341. for (i = 0; i < ln; i++) {
  43342. field = fields[i];
  43343. varName = fieldVarName[i];
  43344. fieldName = field.getName();
  43345. if (fieldName === model.getIdProperty() && field.getMapping() === null && model.getIdProperty() !== this.getIdProperty()) {
  43346. field.setMapping(this.getIdProperty());
  43347. }
  43348. // createFieldAccessExpression must be implemented in subclasses to extract data from the source object in the correct way.
  43349. code.push(' try {\n');
  43350. code.push(' value = ', me.createFieldAccessExpression(field, varName, 'source'), ';\n');
  43351. code.push(' if (value !== undefined) {\n');
  43352. code.push(' dest["' + field.getName() + '"] = value;\n');
  43353. code.push(' }\n');
  43354. code.push(' } catch(e){}\n');
  43355. }
  43356. // set the client id as the internalId of the record.
  43357. // clientId handles the case where a client side record did not previously exist on the server,
  43358. // so the server is passing back a client id that can be used to pair the server side record up with the client record
  43359. if (clientIdProp) {
  43360. code.push(' internalId = ' + me.createFieldAccessExpression(Ext.create('Ext.data.Field', {name: clientIdProp}), null, 'source') + ';\n');
  43361. code.push(' if (internalId !== undefined) {\n');
  43362. code.push(' dest["_clientId"] = internalId;\n }\n');
  43363. }
  43364. code.push(' return dest;\n');
  43365. code.push(' };');
  43366. // Here we are creating a new Function and invoking it immediately in the scope of this Reader
  43367. // It declares several vars capturing the configured context of this Reader, and returns a function
  43368. // which, when passed a record data object, a raw data row in the format this Reader is configured to read,
  43369. // and the record which is being created, will populate the record's data object from the raw row data.
  43370. return Ext.functionFactory(code.join('')).call(me);
  43371. },
  43372. getFields: function() {
  43373. return this.getModel().getFields().items;
  43374. },
  43375. /**
  43376. * @private
  43377. * By default this function just returns what is passed to it. It can be overridden in a subclass
  43378. * to return something else. See XmlReader for an example.
  43379. * @param {Object} data The data object
  43380. * @return {Object} The normalized data object
  43381. */
  43382. getData: function(data) {
  43383. return data;
  43384. },
  43385. /**
  43386. * Takes a raw response object (as passed to this.read) and returns the useful data segment of it.
  43387. * This must be implemented by each subclass
  43388. * @param {Object} response The response object
  43389. * @return {Object} The useful data from the response
  43390. */
  43391. getResponseData: function(response) {
  43392. return response;
  43393. },
  43394. /**
  43395. * @private
  43396. * This will usually need to be implemented in a subclass. Given a generic data object (the type depends on the type
  43397. * of data we are reading), this function should return the object as configured by the Reader's 'rootProperty' meta data config.
  43398. * See XmlReader's getRoot implementation for an example. By default the same data object will simply be returned.
  43399. * @param {Object} data The data object
  43400. * @return {Object} The same data object
  43401. */
  43402. getRoot: function(data) {
  43403. return data;
  43404. },
  43405. /**
  43406. * Reads the given response object. This method normalizes the different types of response object that may be passed
  43407. * to it, before handing off the reading of records to the {@link #readRecords} function.
  43408. * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object
  43409. * @return {Ext.data.ResultSet} The parsed ResultSet object
  43410. */
  43411. read: function(response) {
  43412. var data = response,
  43413. Model = this.getModel(),
  43414. resultSet, records, i, ln, record;
  43415. if (response) {
  43416. data = this.getResponseData(response);
  43417. }
  43418. if (data) {
  43419. resultSet = this.readRecords(data);
  43420. records = resultSet.getRecords();
  43421. for (i = 0, ln = records.length; i < ln; i++) {
  43422. record = records[i];
  43423. records[i] = new Model(record.data, record.id, record.node);
  43424. }
  43425. return resultSet;
  43426. } else {
  43427. return this.nullResultSet;
  43428. }
  43429. },
  43430. process: function(response) {
  43431. var data = response;
  43432. if (response) {
  43433. data = this.getResponseData(response);
  43434. }
  43435. if (data) {
  43436. return this.readRecords(data);
  43437. } else {
  43438. return this.nullResultSet;
  43439. }
  43440. },
  43441. /**
  43442. * Abstracts common functionality used by all Reader subclasses. Each subclass is expected to call this function
  43443. * before running its own logic and returning the Ext.data.ResultSet instance. For most Readers additional
  43444. * processing should not be needed.
  43445. * @param {Object} data The raw data object
  43446. * @return {Ext.data.ResultSet} A ResultSet object
  43447. */
  43448. readRecords: function(data) {
  43449. var me = this;
  43450. /**
  43451. * @property {Object} rawData
  43452. * The raw data object that was last passed to readRecords. Stored for further processing if needed
  43453. */
  43454. me.rawData = data;
  43455. data = me.getData(data);
  43456. if (data.metaData) {
  43457. me.onMetaChange(data.metaData);
  43458. }
  43459. // <debug>
  43460. if (!me.getModel()) {
  43461. Ext.Logger.warn('In order to read record data, a Reader needs to have a Model defined on it.');
  43462. }
  43463. // </debug>
  43464. // If we pass an array as the data, we don't use getRoot on the data.
  43465. // Instead the root equals to the data.
  43466. var isArray = Ext.isArray(data),
  43467. root = isArray ? data : me.getRoot(data),
  43468. success = true,
  43469. recordCount = 0,
  43470. total, value, records, message;
  43471. if (isArray && !data.length) {
  43472. return me.nullResultSet;
  43473. }
  43474. // buildExtractors should have put getTotal, getSuccess, or getMessage methods on the instance.
  43475. // So we can check them directly
  43476. if (me.getTotal) {
  43477. value = parseInt(me.getTotal(data), 10);
  43478. if (!isNaN(value)) {
  43479. total = value;
  43480. }
  43481. }
  43482. if (me.getSuccess) {
  43483. value = me.getSuccess(data);
  43484. if (value === false || value === 'false') {
  43485. success = false;
  43486. }
  43487. }
  43488. if (me.getMessage) {
  43489. message = me.getMessage(data);
  43490. }
  43491. if (root) {
  43492. records = me.extractData(root);
  43493. recordCount = records.length;
  43494. } else {
  43495. recordCount = 0;
  43496. records = [];
  43497. }
  43498. return new Ext.data.ResultSet({
  43499. total : total,
  43500. count : recordCount,
  43501. records: records,
  43502. success: success,
  43503. message: message
  43504. });
  43505. },
  43506. /**
  43507. * Returns extracted, type-cast rows of data.
  43508. * @param {Object[]/Object} root from server response
  43509. * @private
  43510. */
  43511. extractData : function(root) {
  43512. var me = this,
  43513. records = [],
  43514. length = root.length,
  43515. model = me.getModel(),
  43516. idProperty = model.getIdProperty(),
  43517. fieldsCollection = model.getFields(),
  43518. node, i, data, id, clientId;
  43519. /*
  43520. * We check here whether the fields are dirty since the last read.
  43521. * This works around an issue when a Model is used for both a Tree and another
  43522. * source, because the tree decorates the model with extra fields and it causes
  43523. * issues because the readers aren't notified.
  43524. */
  43525. if (fieldsCollection.isDirty) {
  43526. me.buildExtractors(true);
  43527. delete fieldsCollection.isDirty;
  43528. }
  43529. if (!root.length && Ext.isObject(root)) {
  43530. root = [root];
  43531. length = 1;
  43532. }
  43533. for (i = 0; i < length; i++) {
  43534. clientId = null;
  43535. id = null;
  43536. node = root[i];
  43537. // When you use a Memory proxy, and you set data: [] to contain record instances
  43538. // this node will already be a record. In this case we should not try to extract
  43539. // the record data from the object, but just use the record data attribute.
  43540. if (node.isModel) {
  43541. data = node.data;
  43542. } else {
  43543. data = me.extractRecordData(node);
  43544. }
  43545. if (data._clientId !== undefined) {
  43546. clientId = data._clientId;
  43547. delete data._clientId;
  43548. }
  43549. if (data[idProperty] !== undefined) {
  43550. id = data[idProperty];
  43551. }
  43552. if (me.getImplicitIncludes()) {
  43553. me.readAssociated(data, node);
  43554. }
  43555. records.push({
  43556. clientId: clientId,
  43557. id: id,
  43558. data: data,
  43559. node: node
  43560. });
  43561. }
  43562. return records;
  43563. },
  43564. /**
  43565. * @private
  43566. * Loads a record's associations from the data object. This pre-populates `hasMany` and `belongsTo` associations
  43567. * on the record provided.
  43568. * @param {Ext.data.Model} record The record to load associations for
  43569. * @param {Object} data The data object
  43570. */
  43571. readAssociated: function(record, data) {
  43572. var associations = this.getModel().associations.items,
  43573. length = associations.length,
  43574. i = 0,
  43575. association, associationData, associationKey;
  43576. for (; i < length; i++) {
  43577. association = associations[i];
  43578. associationKey = association.getAssociationKey();
  43579. associationData = this.getAssociatedDataRoot(data, associationKey);
  43580. if (associationData) {
  43581. record[associationKey] = associationData;
  43582. }
  43583. }
  43584. },
  43585. /**
  43586. * @private
  43587. * Used internally by `readAssociated`. Given a data object (which could be json, xml etc) for a specific
  43588. * record, this should return the relevant part of that data for the given association name. If a complex
  43589. * mapping, this will traverse arrays and objects to resolve the data.
  43590. * @param {Object} data The raw data object
  43591. * @param {String} associationName The name of the association to get data for (uses associationKey if present)
  43592. * @return {Object} The root
  43593. */
  43594. getAssociatedDataRoot: function(data, associationName) {
  43595. var re = /[\[\.]/,
  43596. i = String(associationName).search(re);
  43597. if (i >= 0) {
  43598. return Ext.functionFactory('obj', 'return obj' + (i > 0 ? '.' : '') + associationName)(data);
  43599. }
  43600. return data[associationName];
  43601. },
  43602. /**
  43603. * @private
  43604. * Reconfigures the meta data tied to this Reader
  43605. */
  43606. onMetaChange : function(meta) {
  43607. var fields = meta.fields,
  43608. me = this,
  43609. newModel, config, idProperty;
  43610. // save off the raw meta data
  43611. me.metaData = meta;
  43612. // set any reader-specific configs from meta if available
  43613. if (meta.rootProperty !== undefined) {
  43614. me.setRootProperty(meta.rootProperty);
  43615. }
  43616. else if (meta.root !== undefined) {
  43617. me.setRootProperty(meta.root);
  43618. }
  43619. if (meta.idProperty !== undefined) {
  43620. me.setIdProperty(meta.idProperty);
  43621. }
  43622. if (meta.totalProperty !== undefined) {
  43623. me.setTotalProperty(meta.totalProperty);
  43624. }
  43625. if (meta.successProperty !== undefined) {
  43626. me.setSuccessProperty(meta.successProperty);
  43627. }
  43628. if (meta.messageProperty !== undefined) {
  43629. me.setMessageProperty(meta.messageProperty);
  43630. }
  43631. if (fields) {
  43632. if (me.getModel()) {
  43633. me.getModel().setFields(fields);
  43634. me.buildExtractors();
  43635. }
  43636. else {
  43637. idProperty = me.getIdProperty();
  43638. config = {fields: fields};
  43639. if (idProperty) {
  43640. config.idProperty = idProperty;
  43641. }
  43642. newModel = Ext.define("Ext.data.reader.MetaModel" + Ext.id(), {
  43643. extend: 'Ext.data.Model',
  43644. config: config
  43645. });
  43646. me.setModel(newModel);
  43647. }
  43648. }
  43649. else {
  43650. me.buildExtractors();
  43651. }
  43652. }
  43653. // Convert old properties in data into a config object
  43654. }, function() {
  43655. Ext.apply(this.prototype, {
  43656. // @private
  43657. // Empty ResultSet to return when response is falsy (null|undefined|empty string)
  43658. nullResultSet: new Ext.data.ResultSet({
  43659. total : 0,
  43660. count : 0,
  43661. records: [],
  43662. success: false
  43663. })
  43664. });
  43665. });
  43666. /**
  43667. * The JSON Reader is used by a Proxy to read a server response that is sent back in JSON format. This usually happens
  43668. * as a result of loading a Store - for example we might create something like this:
  43669. *
  43670. * Ext.define('User', {
  43671. * extend: 'Ext.data.Model',
  43672. * config: {
  43673. * fields: ['id', 'name', 'email']
  43674. * }
  43675. * });
  43676. *
  43677. * var store = Ext.create('Ext.data.Store', {
  43678. * model: 'User',
  43679. * proxy: {
  43680. * type: 'ajax',
  43681. * url : 'users.json',
  43682. * reader: {
  43683. * type: 'json'
  43684. * }
  43685. * }
  43686. * });
  43687. *
  43688. * The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're not
  43689. * already familiar with them.
  43690. *
  43691. * We created the simplest type of JSON Reader possible by simply telling our {@link Ext.data.Store Store}'s {@link
  43692. * Ext.data.proxy.Proxy Proxy} that we want a JSON Reader. The Store automatically passes the configured model to the
  43693. * Store, so it is as if we passed this instead:
  43694. *
  43695. * reader: {
  43696. * type : 'json',
  43697. * model: 'User'
  43698. * }
  43699. *
  43700. * The reader we set up is ready to read data from our server - at the moment it will accept a response like this:
  43701. *
  43702. * [
  43703. * {
  43704. * "id": 1,
  43705. * "name": "Ed Spencer",
  43706. * "email": "ed@sencha.com"
  43707. * },
  43708. * {
  43709. * "id": 2,
  43710. * "name": "Abe Elias",
  43711. * "email": "abe@sencha.com"
  43712. * }
  43713. * ]
  43714. *
  43715. * ## Reading other JSON formats
  43716. *
  43717. * If you already have your JSON format defined and it doesn't look quite like what we have above, you can usually pass
  43718. * JsonReader a couple of configuration options to make it parse your format. For example, we can use the
  43719. * {@link #rootProperty} configuration to parse data that comes back like this:
  43720. *
  43721. * {
  43722. * "users": [
  43723. * {
  43724. * "id": 1,
  43725. * "name": "Ed Spencer",
  43726. * "email": "ed@sencha.com"
  43727. * },
  43728. * {
  43729. * "id": 2,
  43730. * "name": "Abe Elias",
  43731. * "email": "abe@sencha.com"
  43732. * }
  43733. * ]
  43734. * }
  43735. *
  43736. * To parse this we just pass in a {@link #rootProperty} configuration that matches the 'users' above:
  43737. *
  43738. * reader: {
  43739. * type: 'json',
  43740. * rootProperty: 'users'
  43741. * }
  43742. *
  43743. * Sometimes the JSON structure is even more complicated. Document databases like CouchDB often provide metadata around
  43744. * each record inside a nested structure like this:
  43745. *
  43746. * {
  43747. * "total": 122,
  43748. * "offset": 0,
  43749. * "users": [
  43750. * {
  43751. * "id": "ed-spencer-1",
  43752. * "value": 1,
  43753. * "user": {
  43754. * "id": 1,
  43755. * "name": "Ed Spencer",
  43756. * "email": "ed@sencha.com"
  43757. * }
  43758. * }
  43759. * ]
  43760. * }
  43761. *
  43762. * In the case above the record data is nested an additional level inside the "users" array as each "user" item has
  43763. * additional metadata surrounding it ('id' and 'value' in this case). To parse data out of each "user" item in the JSON
  43764. * above we need to specify the {@link #record} configuration like this:
  43765. *
  43766. * reader: {
  43767. * type: 'json',
  43768. * record: 'user',
  43769. * rootProperty: 'users'
  43770. * }
  43771. *
  43772. * ## Response MetaData
  43773. *
  43774. * The server can return metadata in its response, in addition to the record data, that describe attributes
  43775. * of the data set itself or are used to reconfigure the Reader. To pass metadata in the response you simply
  43776. * add a `metaData` attribute to the root of the response data. The metaData attribute can contain anything,
  43777. * but supports a specific set of properties that are handled by the Reader if they are present:
  43778. *
  43779. * - {@link #idProperty}: property name for the primary key field of the data
  43780. * - {@link #rootProperty}: the property name of the root response node containing the record data
  43781. * - {@link #totalProperty}: property name for the total number of records in the data
  43782. * - {@link #successProperty}: property name for the success status of the response
  43783. * - {@link #messageProperty}: property name for an optional response message
  43784. * - {@link Ext.data.Model#cfg-fields fields}: Config used to reconfigure the Model's fields before converting the
  43785. * response data into records
  43786. *
  43787. * An initial Reader configuration containing all of these properties might look like this ("fields" would be
  43788. * included in the Model definition, not shown):
  43789. *
  43790. * reader: {
  43791. * type: 'json',
  43792. * idProperty: 'id',
  43793. * rootProperty: 'root',
  43794. * totalProperty: 'total',
  43795. * successProperty: 'success',
  43796. * messageProperty: 'message'
  43797. * }
  43798. *
  43799. * If you were to pass a response object containing attributes different from those initially defined above, you could
  43800. * use the `metaData` attribute to reconfigure the Reader on the fly. For example:
  43801. *
  43802. * {
  43803. * "count": 1,
  43804. * "ok": true,
  43805. * "msg": "Users found",
  43806. * "users": [{
  43807. * "userId": 123,
  43808. * "name": "Ed Spencer",
  43809. * "email": "ed@sencha.com"
  43810. * }],
  43811. * "metaData": {
  43812. * "idProperty": 'userId',
  43813. * "rootProperty": "users",
  43814. * "totalProperty": 'count',
  43815. * "successProperty": 'ok',
  43816. * "messageProperty": 'msg'
  43817. * }
  43818. * }
  43819. *
  43820. * You can also place any other arbitrary data you need into the `metaData` attribute which will be ignored by the Reader,
  43821. * but will be accessible via the Reader's {@link #metaData} property. Application code can then process the passed
  43822. * metadata in any way it chooses.
  43823. *
  43824. * A simple example for how this can be used would be customizing the fields for a Model that is bound to a grid. By passing
  43825. * the `fields` property the Model will be automatically updated by the Reader internally, but that change will not be
  43826. * reflected automatically in the grid unless you also update the column configuration. You could do this manually, or you
  43827. * could simply pass a standard grid column config object as part of the `metaData` attribute
  43828. * and then pass that along to the grid. Here's a very simple example for how that could be accomplished:
  43829. *
  43830. * // response format:
  43831. * {
  43832. * ...
  43833. * "metaData": {
  43834. * "fields": [
  43835. * { "name": "userId", "type": "int" },
  43836. * { "name": "name", "type": "string" },
  43837. * { "name": "birthday", "type": "date", "dateFormat": "Y-j-m" },
  43838. * ],
  43839. * "columns": [
  43840. * { "text": "User ID", "dataIndex": "userId", "width": 40 },
  43841. * { "text": "User Name", "dataIndex": "name", "flex": 1 },
  43842. * { "text": "Birthday", "dataIndex": "birthday", "flex": 1, "format": 'Y-j-m', "xtype": "datecolumn" }
  43843. * ]
  43844. * }
  43845. * }
  43846. */
  43847. Ext.define('Ext.data.reader.Json', {
  43848. extend: 'Ext.data.reader.Reader',
  43849. alternateClassName: 'Ext.data.JsonReader',
  43850. alias : 'reader.json',
  43851. config: {
  43852. /**
  43853. * @cfg {String} [record=null]
  43854. * The optional location within the JSON response that the record data itself can be found at. See the
  43855. * JsonReader intro docs for more details. This is not often needed.
  43856. */
  43857. record: null,
  43858. /**
  43859. * @cfg {Boolean} [useSimpleAccessors=false]
  43860. * `true` to ensure that field names/mappings are treated as literals when reading values. For
  43861. * example, by default, using the mapping "foo.bar.baz" will try and read a property foo from the root, then a
  43862. * property bar from foo, then a property baz from bar. Setting the simple accessors to `true` will read the
  43863. * property with the name "foo.bar.baz" direct from the root object.
  43864. */
  43865. useSimpleAccessors: false
  43866. },
  43867. objectRe: /[\[\.]/,
  43868. // @inheritdoc
  43869. getResponseData: function(response) {
  43870. var responseText = response;
  43871. // Handle an XMLHttpRequest object
  43872. if (response && response.responseText) {
  43873. responseText = response.responseText;
  43874. }
  43875. // Handle the case where data has already been decoded
  43876. if (typeof responseText !== 'string') {
  43877. return responseText;
  43878. }
  43879. var data;
  43880. try {
  43881. data = Ext.decode(responseText);
  43882. }
  43883. catch (ex) {
  43884. /**
  43885. * @event exception Fires whenever the reader is unable to parse a response.
  43886. * @param {Ext.data.reader.Xml} reader A reference to this reader.
  43887. * @param {XMLHttpRequest} response The XMLHttpRequest response object.
  43888. * @param {String} error The error message.
  43889. */
  43890. this.fireEvent('exception', this, response, 'Unable to parse the JSON returned by the server: ' + ex.toString());
  43891. Ext.Logger.warn('Unable to parse the JSON returned by the server: ' + ex.toString());
  43892. }
  43893. //<debug>
  43894. if (!data) {
  43895. this.fireEvent('exception', this, response, 'JSON object not found');
  43896. Ext.Logger.error('JSON object not found');
  43897. }
  43898. //</debug>
  43899. return data;
  43900. },
  43901. // @inheritdoc
  43902. buildExtractors: function() {
  43903. var me = this,
  43904. root = me.getRootProperty();
  43905. me.callParent(arguments);
  43906. if (root) {
  43907. me.rootAccessor = me.createAccessor(root);
  43908. } else {
  43909. delete me.rootAccessor;
  43910. }
  43911. },
  43912. /**
  43913. * We create this method because `root` is now a config so `getRoot` is already defined, but in the old
  43914. * data package `getRoot` was passed a data argument and it would return the data inside of the `root`
  43915. * property. This method handles both cases.
  43916. * @param data (Optional)
  43917. * @return {String/Object} Returns the config root value if this method was called without passing
  43918. * data. Else it returns the object in the data bound to the root.
  43919. * @private
  43920. */
  43921. getRoot: function(data) {
  43922. var fieldsCollection = this.getModel().getFields();
  43923. /*
  43924. * We check here whether the fields are dirty since the last read.
  43925. * This works around an issue when a Model is used for both a Tree and another
  43926. * source, because the tree decorates the model with extra fields and it causes
  43927. * issues because the readers aren't notified.
  43928. */
  43929. if (fieldsCollection.isDirty) {
  43930. this.buildExtractors(true);
  43931. delete fieldsCollection.isDirty;
  43932. }
  43933. if (this.rootAccessor) {
  43934. return this.rootAccessor.call(this, data);
  43935. } else {
  43936. return data;
  43937. }
  43938. },
  43939. /**
  43940. * @private
  43941. * We're just preparing the data for the superclass by pulling out the record objects we want. If a {@link #record}
  43942. * was specified we have to pull those out of the larger JSON object, which is most of what this function is doing
  43943. * @param {Object} root The JSON root node
  43944. * @return {Ext.data.Model[]} The records
  43945. */
  43946. extractData: function(root) {
  43947. var recordName = this.getRecord(),
  43948. data = [],
  43949. length, i;
  43950. if (recordName) {
  43951. length = root.length;
  43952. if (!length && Ext.isObject(root)) {
  43953. length = 1;
  43954. root = [root];
  43955. }
  43956. for (i = 0; i < length; i++) {
  43957. data[i] = root[i][recordName];
  43958. }
  43959. } else {
  43960. data = root;
  43961. }
  43962. return this.callParent([data]);
  43963. },
  43964. /**
  43965. * @private
  43966. * Returns an accessor function for the given property string. Gives support for properties such as the following:
  43967. * 'someProperty'
  43968. * 'some.property'
  43969. * 'some["property"]'
  43970. * This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
  43971. */
  43972. createAccessor: function() {
  43973. var re = /[\[\.]/;
  43974. return function(expr) {
  43975. if (Ext.isEmpty(expr)) {
  43976. return Ext.emptyFn;
  43977. }
  43978. if (Ext.isFunction(expr)) {
  43979. return expr;
  43980. }
  43981. if (this.getUseSimpleAccessors() !== true) {
  43982. var i = String(expr).search(re);
  43983. if (i >= 0) {
  43984. return Ext.functionFactory('obj', 'var value; try {value = obj' + (i > 0 ? '.' : '') + expr + '} catch(e) {}; return value;');
  43985. }
  43986. }
  43987. return function(obj) {
  43988. return obj[expr];
  43989. };
  43990. };
  43991. }(),
  43992. /**
  43993. * @private
  43994. * Returns an accessor expression for the passed Field. Gives support for properties such as the following:
  43995. * 'someProperty'
  43996. * 'some.property'
  43997. * 'some["property"]'
  43998. * This is used by buildExtractors to create optimized on extractor function which converts raw data into model instances.
  43999. */
  44000. createFieldAccessExpression: function(field, fieldVarName, dataName) {
  44001. var me = this,
  44002. re = me.objectRe,
  44003. hasMap = (field.getMapping() !== null),
  44004. map = hasMap ? field.getMapping() : field.getName(),
  44005. result, operatorSearch;
  44006. if (typeof map === 'function') {
  44007. result = fieldVarName + '.getMapping()(' + dataName + ', this)';
  44008. }
  44009. else if (me.getUseSimpleAccessors() === true || ((operatorSearch = String(map).search(re)) < 0)) {
  44010. if (!hasMap || isNaN(map)) {
  44011. // If we don't provide a mapping, we may have a field name that is numeric
  44012. map = '"' + map + '"';
  44013. }
  44014. result = dataName + "[" + map + "]";
  44015. }
  44016. else {
  44017. result = dataName + (operatorSearch > 0 ? '.' : '') + map;
  44018. }
  44019. return result;
  44020. }
  44021. });
  44022. /**
  44023. * @author Ed Spencer
  44024. *
  44025. * Base Writer class used by most subclasses of {@link Ext.data.proxy.Server}. This class is
  44026. * responsible for taking a set of {@link Ext.data.Operation} objects and a {@link Ext.data.Request}
  44027. * object and modifying that request based on the Operations.
  44028. *
  44029. * For example a Ext.data.writer.Json would format the Operations and their {@link Ext.data.Model}
  44030. * instances based on the config options passed to the JsonWriter's constructor.
  44031. *
  44032. * Writers are not needed for any kind of local storage - whether via a
  44033. * {@link Ext.data.proxy.WebStorage Web Storage proxy} (see {@link Ext.data.proxy.LocalStorage localStorage})
  44034. * or just in memory via a {@link Ext.data.proxy.Memory MemoryProxy}.
  44035. */
  44036. Ext.define('Ext.data.writer.Writer', {
  44037. alias: 'writer.base',
  44038. alternateClassName: ['Ext.data.DataWriter', 'Ext.data.Writer'],
  44039. config: {
  44040. /**
  44041. * @cfg {Boolean} writeAllFields `true` to write all fields from the record to the server. If set to `false` it
  44042. * will only send the fields that were modified. Note that any fields that have
  44043. * {@link Ext.data.Field#persist} set to false will still be ignored.
  44044. */
  44045. writeAllFields: true,
  44046. /**
  44047. * @cfg {String} nameProperty This property is used to read the key for each value that will be sent to the server.
  44048. * For example:
  44049. *
  44050. * Ext.define('Person', {
  44051. * extend: 'Ext.data.Model',
  44052. * fields: [{
  44053. * name: 'first',
  44054. * mapping: 'firstName'
  44055. * }, {
  44056. * name: 'last',
  44057. * mapping: 'lastName'
  44058. * }, {
  44059. * name: 'age'
  44060. * }]
  44061. * });
  44062. *
  44063. * new Ext.data.writer.Writer({
  44064. * writeAllFields: true,
  44065. * nameProperty: 'mapping'
  44066. * });
  44067. *
  44068. * The following data will be sent to the server:
  44069. *
  44070. * {
  44071. * firstName: 'first name value',
  44072. * lastName: 'last name value',
  44073. * age: 1
  44074. * }
  44075. *
  44076. * If the value is not present, the field name will always be used.
  44077. */
  44078. nameProperty: 'name'
  44079. },
  44080. /**
  44081. * Creates new Writer.
  44082. * @param {Object} config (optional) Config object.
  44083. */
  44084. constructor: function(config) {
  44085. this.initConfig(config);
  44086. },
  44087. /**
  44088. * Prepares a Proxy's Ext.data.Request object.
  44089. * @param {Ext.data.Request} request The request object.
  44090. * @return {Ext.data.Request} The modified request object.
  44091. */
  44092. write: function(request) {
  44093. var operation = request.getOperation(),
  44094. records = operation.getRecords() || [],
  44095. len = records.length,
  44096. i = 0,
  44097. data = [];
  44098. for (; i < len; i++) {
  44099. data.push(this.getRecordData(records[i]));
  44100. }
  44101. return this.writeRecords(request, data);
  44102. },
  44103. writeDate: function(field, date) {
  44104. var dateFormat = field.getDateFormat() || 'timestamp';
  44105. switch (dateFormat) {
  44106. case 'timestamp':
  44107. return date.getTime()/1000;
  44108. case 'time':
  44109. return date.getTime();
  44110. default:
  44111. return Ext.Date.format(date, dateFormat);
  44112. }
  44113. },
  44114. /**
  44115. * Formats the data for each record before sending it to the server. This
  44116. * method should be overridden to format the data in a way that differs from the default.
  44117. * @param {Object} record The record that we are writing to the server.
  44118. * @return {Object} An object literal of name/value keys to be written to the server.
  44119. * By default this method returns the data property on the record.
  44120. */
  44121. getRecordData: function(record) {
  44122. var isPhantom = record.phantom === true,
  44123. writeAll = this.getWriteAllFields() || isPhantom,
  44124. nameProperty = this.getNameProperty(),
  44125. fields = record.getFields(),
  44126. data = {},
  44127. changes, name, field, key, value;
  44128. if (writeAll) {
  44129. fields.each(function(field) {
  44130. if (field.getPersist()) {
  44131. name = field.config[nameProperty] || field.getName();
  44132. value = record.get(field.getName());
  44133. if (field.getType().type == 'date') {
  44134. value = this.writeDate(field, value);
  44135. }
  44136. data[name] = value;
  44137. }
  44138. }, this);
  44139. } else {
  44140. // Only write the changes
  44141. changes = record.getChanges();
  44142. for (key in changes) {
  44143. if (changes.hasOwnProperty(key)) {
  44144. field = fields.get(key);
  44145. if (field.getPersist()) {
  44146. name = field.config[nameProperty] || field.getName();
  44147. value = changes[key];
  44148. if (field.getType().type == 'date') {
  44149. value = this.writeDate(field, value);
  44150. }
  44151. data[name] = value;
  44152. }
  44153. }
  44154. }
  44155. if (!isPhantom) {
  44156. // always include the id for non phantoms
  44157. data[record.getIdProperty()] = record.getId();
  44158. }
  44159. }
  44160. return data;
  44161. }
  44162. // Convert old properties in data into a config object
  44163. });
  44164. /**
  44165. * This class is used to write {@link Ext.data.Model} data to the server in a JSON format.
  44166. * The {@link #allowSingle} configuration can be set to false to force the records to always be
  44167. * encoded in an array, even if there is only a single record being sent.
  44168. */
  44169. Ext.define('Ext.data.writer.Json', {
  44170. extend: 'Ext.data.writer.Writer',
  44171. alternateClassName: 'Ext.data.JsonWriter',
  44172. alias: 'writer.json',
  44173. config: {
  44174. /**
  44175. * @cfg {String} rootProperty
  44176. * The key under which the records in this Writer will be placed. If you specify {@link #encode} to be true,
  44177. * we default this to 'records'.
  44178. *
  44179. * Example generated request, using root: 'records':
  44180. *
  44181. * {'records': [{name: 'my record'}, {name: 'another record'}]}
  44182. *
  44183. */
  44184. rootProperty: undefined,
  44185. /**
  44186. * @cfg {Boolean} encode
  44187. * True to use Ext.encode() on the data before sending. The encode option should only be set to true when a
  44188. * {@link #root} is defined, because the values will be sent as part of the request parameters as opposed to
  44189. * a raw post. The root will be the name of the parameter sent to the server.
  44190. */
  44191. encode: false,
  44192. /**
  44193. * @cfg {Boolean} allowSingle
  44194. * False to ensure that records are always wrapped in an array, even if there is only one record being sent.
  44195. * When there is more than one record, they will always be encoded into an array.
  44196. *
  44197. * Example:
  44198. *
  44199. * // with allowSingle: true
  44200. * "root": {
  44201. * "first": "Mark",
  44202. * "last": "Corrigan"
  44203. * }
  44204. *
  44205. * // with allowSingle: false
  44206. * "root": [{
  44207. * "first": "Mark",
  44208. * "last": "Corrigan"
  44209. * }]
  44210. */
  44211. allowSingle: true,
  44212. encodeRequest: false
  44213. },
  44214. applyRootProperty: function(root) {
  44215. if (!root && (this.getEncode() || this.getEncodeRequest())) {
  44216. root = 'data';
  44217. }
  44218. return root;
  44219. },
  44220. //inherit docs
  44221. writeRecords: function(request, data) {
  44222. var root = this.getRootProperty(),
  44223. params = request.getParams(),
  44224. allowSingle = this.getAllowSingle(),
  44225. jsonData;
  44226. if (this.getAllowSingle() && data && data.length == 1) {
  44227. // convert to single object format
  44228. data = data[0];
  44229. }
  44230. if (this.getEncodeRequest()) {
  44231. jsonData = request.getJsonData() || {};
  44232. if (data && (data.length || (allowSingle && Ext.isObject(data)))) {
  44233. jsonData[root] = data;
  44234. }
  44235. request.setJsonData(Ext.apply(jsonData, params || {}));
  44236. request.setParams(null);
  44237. request.setMethod('POST');
  44238. return request;
  44239. }
  44240. if (!data || !(data.length || (allowSingle && Ext.isObject(data)))) {
  44241. return request;
  44242. }
  44243. if (this.getEncode()) {
  44244. if (root) {
  44245. // sending as a param, need to encode
  44246. params[root] = Ext.encode(data);
  44247. } else {
  44248. //<debug>
  44249. Ext.Logger.error('Must specify a root when using encode');
  44250. //</debug>
  44251. }
  44252. } else {
  44253. // send as jsonData
  44254. jsonData = request.getJsonData() || {};
  44255. if (root) {
  44256. jsonData[root] = data;
  44257. } else {
  44258. jsonData = data;
  44259. }
  44260. request.setJsonData(jsonData);
  44261. }
  44262. return request;
  44263. }
  44264. });
  44265. /*
  44266. * @allowSingle: true
  44267. * @encodeRequest: false
  44268. * Url: update.json?param1=test
  44269. * {'field1': 'test': 'field2': 'test'}
  44270. *
  44271. * @allowSingle: false
  44272. * @encodeRequest: false
  44273. * Url: update.json?param1=test
  44274. * [{'field1': 'test', 'field2': 'test'}]
  44275. *
  44276. * @allowSingle: true
  44277. * @root: 'data'
  44278. * @encodeRequest: true
  44279. * Url: update.json
  44280. * {
  44281. * 'param1': 'test',
  44282. * 'data': {'field1': 'test', 'field2': 'test'}
  44283. * }
  44284. *
  44285. * @allowSingle: false
  44286. * @root: 'data'
  44287. * @encodeRequest: true
  44288. * Url: update.json
  44289. * {
  44290. * 'param1': 'test',
  44291. * 'data': [{'field1': 'test', 'field2': 'test'}]
  44292. * }
  44293. *
  44294. * @allowSingle: true
  44295. * @root: data
  44296. * @encodeRequest: false
  44297. * Url: update.json
  44298. * param1=test&data={'field1': 'test', 'field2': 'test'}
  44299. *
  44300. * @allowSingle: false
  44301. * @root: data
  44302. * @encodeRequest: false
  44303. * @ncode: true
  44304. * Url: update.json
  44305. * param1=test&data=[{'field1': 'test', 'field2': 'test'}]
  44306. *
  44307. * @allowSingle: true
  44308. * @root: data
  44309. * @encodeRequest: false
  44310. * Url: update.json?param1=test&data={'field1': 'test', 'field2': 'test'}
  44311. *
  44312. * @allowSingle: false
  44313. * @root: data
  44314. * @encodeRequest: false
  44315. * Url: update.json?param1=test&data=[{'field1': 'test', 'field2': 'test'}]
  44316. */
  44317. /**
  44318. * @author Ed Spencer
  44319. * @class Ext.data.Batch
  44320. *
  44321. * Provides a mechanism to run one or more {@link Ext.data.Operation operations} in a given order. Fires the `operationcomplete` event
  44322. * after the completion of each Operation, and the `complete` event when all Operations have been successfully executed. Fires an `exception`
  44323. * event if any of the Operations encounter an exception.
  44324. *
  44325. * Usually these are only used internally by {@link Ext.data.proxy.Proxy} classes.
  44326. */
  44327. Ext.define('Ext.data.Batch', {
  44328. mixins: {
  44329. observable: 'Ext.mixin.Observable'
  44330. },
  44331. config: {
  44332. /**
  44333. * @cfg {Boolean} autoStart `true` to immediately start processing the batch as soon as it is constructed.
  44334. */
  44335. autoStart: false,
  44336. /**
  44337. * @cfg {Boolean} pauseOnException `true` to automatically pause the execution of the batch if any operation encounters an exception.
  44338. */
  44339. pauseOnException: true,
  44340. /**
  44341. * @cfg {Ext.data.Proxy} proxy The proxy this Batch belongs to. Used to make the requests for each operation in the Batch.
  44342. */
  44343. proxy: null
  44344. },
  44345. /**
  44346. * The index of the current operation being executed.
  44347. * @property current
  44348. * @type Number
  44349. */
  44350. current: -1,
  44351. /**
  44352. * The total number of operations in this batch.
  44353. * @property total
  44354. * @type Number
  44355. * @readonly
  44356. */
  44357. total: 0,
  44358. /**
  44359. * `true` if the batch is currently running.
  44360. * @property isRunning
  44361. * @type Boolean
  44362. */
  44363. isRunning: false,
  44364. /**
  44365. * `true` if this batch has been executed completely.
  44366. * @property isComplete
  44367. * @type Boolean
  44368. */
  44369. isComplete: false,
  44370. /**
  44371. * `true` if this batch has encountered an exception. This is cleared at the start of each operation.
  44372. * @property hasException
  44373. * @type Boolean
  44374. */
  44375. hasException: false,
  44376. /**
  44377. * @event complete
  44378. * Fired when all operations of this batch have been completed.
  44379. * @param {Ext.data.Batch} batch The batch object.
  44380. * @param {Object} operation The last operation that was executed.
  44381. */
  44382. /**
  44383. * @event exception
  44384. * Fired when a operation encountered an exception.
  44385. * @param {Ext.data.Batch} batch The batch object.
  44386. * @param {Object} operation The operation that encountered the exception.
  44387. */
  44388. /**
  44389. * @event operationcomplete
  44390. * Fired when each operation of the batch completes.
  44391. * @param {Ext.data.Batch} batch The batch object.
  44392. * @param {Object} operation The operation that just completed.
  44393. */
  44394. /**
  44395. * Creates new Batch object.
  44396. * @param {Object} config (optional) Config object.
  44397. */
  44398. constructor: function(config) {
  44399. var me = this;
  44400. me.initConfig(config);
  44401. /**
  44402. * Ordered array of operations that will be executed by this batch
  44403. * @property {Ext.data.Operation[]} operations
  44404. */
  44405. me.operations = [];
  44406. },
  44407. /**
  44408. * Adds a new operation to this batch.
  44409. * @param {Object} operation The {@link Ext.data.Operation Operation} object.
  44410. */
  44411. add: function(operation) {
  44412. this.total++;
  44413. operation.setBatch(this);
  44414. this.operations.push(operation);
  44415. },
  44416. /**
  44417. * Kicks off the execution of the batch, continuing from the next operation if the previous
  44418. * operation encountered an exception, or if execution was paused.
  44419. */
  44420. start: function() {
  44421. this.hasException = false;
  44422. this.isRunning = true;
  44423. this.runNextOperation();
  44424. },
  44425. /**
  44426. * @private
  44427. * Runs the next operation, relative to `this.current`.
  44428. */
  44429. runNextOperation: function() {
  44430. this.runOperation(this.current + 1);
  44431. },
  44432. /**
  44433. * Pauses execution of the batch, but does not cancel the current operation.
  44434. */
  44435. pause: function() {
  44436. this.isRunning = false;
  44437. },
  44438. /**
  44439. * Executes a operation by its numeric index.
  44440. * @param {Number} index The operation index to run.
  44441. */
  44442. runOperation: function(index) {
  44443. var me = this,
  44444. operations = me.operations,
  44445. operation = operations[index],
  44446. onProxyReturn;
  44447. if (operation === undefined) {
  44448. me.isRunning = false;
  44449. me.isComplete = true;
  44450. me.fireEvent('complete', me, operations[operations.length - 1]);
  44451. } else {
  44452. me.current = index;
  44453. onProxyReturn = function(operation) {
  44454. var hasException = operation.hasException();
  44455. if (hasException) {
  44456. me.hasException = true;
  44457. me.fireEvent('exception', me, operation);
  44458. } else {
  44459. me.fireEvent('operationcomplete', me, operation);
  44460. }
  44461. if (hasException && me.getPauseOnException()) {
  44462. me.pause();
  44463. } else {
  44464. operation.setCompleted();
  44465. me.runNextOperation();
  44466. }
  44467. };
  44468. operation.setStarted();
  44469. me.getProxy()[operation.getAction()](operation, onProxyReturn, me);
  44470. }
  44471. }
  44472. });
  44473. /**
  44474. * @author Ed Spencer
  44475. * @aside guide proxies
  44476. *
  44477. * Proxies are used by {@link Ext.data.Store Stores} to handle the loading and saving of {@link Ext.data.Model Model}
  44478. * data. Usually developers will not need to create or interact with proxies directly.
  44479. *
  44480. * # Types of Proxy
  44481. *
  44482. * There are two main types of Proxy - {@link Ext.data.proxy.Client Client} and {@link Ext.data.proxy.Server Server}.
  44483. * The Client proxies save their data locally and include the following subclasses:
  44484. *
  44485. * - {@link Ext.data.proxy.LocalStorage LocalStorageProxy} - saves its data to localStorage if the browser supports it
  44486. * - {@link Ext.data.proxy.Memory MemoryProxy} - holds data in memory only, any data is lost when the page is refreshed
  44487. *
  44488. * The Server proxies save their data by sending requests to some remote server. These proxies include:
  44489. *
  44490. * - {@link Ext.data.proxy.Ajax Ajax} - sends requests to a server on the same domain
  44491. * - {@link Ext.data.proxy.JsonP JsonP} - uses JSON-P to send requests to a server on a different domain
  44492. *
  44493. * Proxies operate on the principle that all operations performed are either Create, Read, Update or Delete. These four
  44494. * operations are mapped to the methods {@link #create}, {@link #read}, {@link #update} and {@link #destroy}
  44495. * respectively. Each Proxy subclass implements these functions.
  44496. *
  44497. * The CRUD methods each expect an {@link Ext.data.Operation Operation} object as the sole argument. The Operation
  44498. * encapsulates information about the action the Store wishes to perform, the {@link Ext.data.Model model} instances
  44499. * that are to be modified, etc. See the {@link Ext.data.Operation Operation} documentation for more details. Each CRUD
  44500. * method also accepts a callback function to be called asynchronously on completion.
  44501. *
  44502. * Proxies also support batching of Operations via a {@link Ext.data.Batch batch} object, invoked by the {@link #batch}
  44503. * method.
  44504. */
  44505. Ext.define('Ext.data.proxy.Proxy', {
  44506. extend: 'Ext.Evented',
  44507. alias: 'proxy.proxy',
  44508. alternateClassName: ['Ext.data.DataProxy', 'Ext.data.Proxy'],
  44509. requires: [
  44510. 'Ext.data.reader.Json',
  44511. 'Ext.data.writer.Json',
  44512. 'Ext.data.Batch',
  44513. 'Ext.data.Operation'
  44514. ],
  44515. config: {
  44516. /**
  44517. * @cfg {String} batchOrder
  44518. * Comma-separated ordering 'create', 'update' and 'destroy' actions when batching. Override this to set a different
  44519. * order for the batched CRUD actions to be executed in.
  44520. * @accessor
  44521. */
  44522. batchOrder: 'create,update,destroy',
  44523. /**
  44524. * @cfg {Boolean} batchActions
  44525. * True to batch actions of a particular type when synchronizing the store.
  44526. * @accessor
  44527. */
  44528. batchActions: true,
  44529. /**
  44530. * @cfg {String/Ext.data.Model} model (required)
  44531. * The name of the Model to tie to this Proxy. Can be either the string name of the Model, or a reference to the
  44532. * Model constructor.
  44533. * @accessor
  44534. */
  44535. model: null,
  44536. /**
  44537. * @cfg {Object/String/Ext.data.reader.Reader} reader
  44538. * The Ext.data.reader.Reader to use to decode the server's response or data read from client. This can either be a
  44539. * Reader instance, a config object or just a valid Reader type name (e.g. 'json', 'xml').
  44540. * @accessor
  44541. */
  44542. reader: {
  44543. type: 'json'
  44544. },
  44545. /**
  44546. * @cfg {Object/String/Ext.data.writer.Writer} writer
  44547. * The Ext.data.writer.Writer to use to encode any request sent to the server or saved to client. This can either be
  44548. * a Writer instance, a config object or just a valid Writer type name (e.g. 'json', 'xml').
  44549. * @accessor
  44550. */
  44551. writer: {
  44552. type: 'json'
  44553. }
  44554. },
  44555. isProxy: true,
  44556. applyModel: function(model) {
  44557. if (typeof model == 'string') {
  44558. model = Ext.data.ModelManager.getModel(model);
  44559. if (!model) {
  44560. Ext.Logger.error('Model with name ' + arguments[0] + ' doesnt exist.');
  44561. }
  44562. }
  44563. if (model && !model.prototype.isModel && Ext.isObject(model)) {
  44564. model = Ext.data.ModelManager.registerType(model.storeId || model.id || Ext.id(), model);
  44565. }
  44566. return model;
  44567. },
  44568. updateModel: function(model) {
  44569. if (model) {
  44570. var reader = this.getReader();
  44571. if (reader && !reader.getModel()) {
  44572. reader.setModel(model);
  44573. }
  44574. }
  44575. },
  44576. applyReader: function(reader, currentReader) {
  44577. return Ext.factory(reader, Ext.data.Reader, currentReader, 'reader');
  44578. },
  44579. updateReader: function(reader) {
  44580. if (reader) {
  44581. var model = this.getModel();
  44582. if (!model) {
  44583. model = reader.getModel();
  44584. if (model) {
  44585. this.setModel(model);
  44586. }
  44587. } else {
  44588. reader.setModel(model);
  44589. }
  44590. if (reader.onMetaChange) {
  44591. reader.onMetaChange = Ext.Function.createSequence(reader.onMetaChange, this.onMetaChange, this);
  44592. }
  44593. }
  44594. },
  44595. onMetaChange: function(data) {
  44596. var model = this.getReader().getModel();
  44597. if (!this.getModel() && model) {
  44598. this.setModel(model);
  44599. }
  44600. /**
  44601. * @event metachange
  44602. * Fires whenever the server has sent back new metadata to reconfigure the Reader.
  44603. * @param {Ext.data.Proxy} this
  44604. * @param {Object} data The metadata sent back from the server
  44605. */
  44606. this.fireEvent('metachange', this, data);
  44607. },
  44608. applyWriter: function(writer, currentWriter) {
  44609. return Ext.factory(writer, Ext.data.Writer, currentWriter, 'writer');
  44610. },
  44611. /**
  44612. * Performs the given create operation. If you override this method in a custom Proxy, remember to always call the provided
  44613. * callback method when you are done with your operation.
  44614. * @param {Ext.data.Operation} operation The Operation to perform
  44615. * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
  44616. * @param {Object} scope Scope to execute the callback function in
  44617. * @method
  44618. */
  44619. create: Ext.emptyFn,
  44620. /**
  44621. * Performs the given read operation. If you override this method in a custom Proxy, remember to always call the provided
  44622. * callback method when you are done with your operation.
  44623. * @param {Ext.data.Operation} operation The Operation to perform
  44624. * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
  44625. * @param {Object} scope Scope to execute the callback function in
  44626. * @method
  44627. */
  44628. read: Ext.emptyFn,
  44629. /**
  44630. * Performs the given update operation. If you override this method in a custom Proxy, remember to always call the provided
  44631. * callback method when you are done with your operation.
  44632. * @param {Ext.data.Operation} operation The Operation to perform
  44633. * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
  44634. * @param {Object} scope Scope to execute the callback function in
  44635. * @method
  44636. */
  44637. update: Ext.emptyFn,
  44638. /**
  44639. * Performs the given destroy operation. If you override this method in a custom Proxy, remember to always call the provided
  44640. * callback method when you are done with your operation.
  44641. * @param {Ext.data.Operation} operation The Operation to perform
  44642. * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
  44643. * @param {Object} scope Scope to execute the callback function in
  44644. * @method
  44645. */
  44646. destroy: Ext.emptyFn,
  44647. onDestroy: function() {
  44648. Ext.destroy(this.getReader(), this.getWriter());
  44649. },
  44650. /**
  44651. * Performs a batch of {@link Ext.data.Operation Operations}, in the order specified by {@link #batchOrder}. Used
  44652. * internally by {@link Ext.data.Store}'s {@link Ext.data.Store#sync sync} method. Example usage:
  44653. *
  44654. * myProxy.batch({
  44655. * create : [myModel1, myModel2],
  44656. * update : [myModel3],
  44657. * destroy: [myModel4, myModel5]
  44658. * });
  44659. *
  44660. * Where the myModel* above are {@link Ext.data.Model Model} instances - in this case 1 and 2 are new instances and
  44661. * have not been saved before, 3 has been saved previously but needs to be updated, and 4 and 5 have already been
  44662. * saved but should now be destroyed.
  44663. *
  44664. * @param {Object} options Object containing one or more properties supported by the batch method:
  44665. *
  44666. * @param {Object} options.operations Object containing the Model instances to act upon, keyed by action name
  44667. *
  44668. * @param {Object} [options.listeners] Event listeners object passed straight through to the Batch -
  44669. * see {@link Ext.data.Batch} for details
  44670. *
  44671. * @param {Ext.data.Batch/Object} [options.batch] A {@link Ext.data.Batch} object (or batch config to apply
  44672. * to the created batch). If unspecified a default batch will be auto-created.
  44673. *
  44674. * @param {Function} [options.callback] The function to be called upon completion of processing the batch.
  44675. * The callback is called regardless of success or failure and is passed the following parameters:
  44676. * @param {Ext.data.Batch} options.callback.batch The {@link Ext.data.Batch batch} that was processed,
  44677. * containing all operations in their current state after processing
  44678. * @param {Object} options.callback.options The options argument that was originally passed into batch
  44679. *
  44680. * @param {Function} [options.success] The function to be called upon successful completion of the batch. The
  44681. * success function is called only if no exceptions were reported in any operations. If one or more exceptions
  44682. * occurred then the `failure` function will be called instead. The success function is called
  44683. * with the following parameters:
  44684. * @param {Ext.data.Batch} options.success.batch The {@link Ext.data.Batch batch} that was processed,
  44685. * containing all operations in their current state after processing
  44686. * @param {Object} options.success.options The options argument that was originally passed into batch
  44687. *
  44688. * @param {Function} [options.failure] The function to be called upon unsuccessful completion of the batch. The
  44689. * failure function is called when one or more operations returns an exception during processing (even if some
  44690. * operations were also successful). The failure function is called with the following parameters:
  44691. * @param {Ext.data.Batch} options.failure.batch The {@link Ext.data.Batch batch} that was processed,
  44692. * containing all operations in their current state after processing
  44693. * @param {Object} options.failure.options The options argument that was originally passed into batch
  44694. *
  44695. * @param {Object} [options.scope] The scope in which to execute any callbacks (i.e. the `this` object inside
  44696. * the callback, success and/or failure functions). Defaults to the proxy.
  44697. *
  44698. * @return {Ext.data.Batch} The newly created Batch
  44699. */
  44700. batch: function(options, /* deprecated */listeners) {
  44701. var me = this,
  44702. useBatch = me.getBatchActions(),
  44703. model = me.getModel(),
  44704. batch,
  44705. records;
  44706. if (options.operations === undefined) {
  44707. // the old-style (operations, listeners) signature was called
  44708. // so convert to the single options argument syntax
  44709. options = {
  44710. operations: options,
  44711. listeners: listeners
  44712. };
  44713. // <debug warn>
  44714. Ext.Logger.deprecate('Passes old-style signature to Proxy.batch (operations, listeners). Please convert to single options argument syntax.');
  44715. // </debug>
  44716. }
  44717. if (options.batch && options.batch.isBatch) {
  44718. batch = options.batch;
  44719. } else {
  44720. batch = new Ext.data.Batch(options.batch || {});
  44721. }
  44722. batch.setProxy(me);
  44723. batch.on('complete', Ext.bind(me.onBatchComplete, me, [options], 0));
  44724. if (options.listeners) {
  44725. batch.on(options.listeners);
  44726. }
  44727. Ext.each(me.getBatchOrder().split(','), function(action) {
  44728. records = options.operations[action];
  44729. if (records) {
  44730. if (useBatch) {
  44731. batch.add(new Ext.data.Operation({
  44732. action: action,
  44733. records: records,
  44734. model: model
  44735. }));
  44736. } else {
  44737. Ext.each(records, function(record) {
  44738. batch.add(new Ext.data.Operation({
  44739. action : action,
  44740. records: [record],
  44741. model: model
  44742. }));
  44743. });
  44744. }
  44745. }
  44746. }, me);
  44747. batch.start();
  44748. return batch;
  44749. },
  44750. /**
  44751. * @private
  44752. * The internal callback that the proxy uses to call any specified user callbacks after completion of a batch
  44753. */
  44754. onBatchComplete: function(batchOptions, batch) {
  44755. var scope = batchOptions.scope || this;
  44756. if (batch.hasException) {
  44757. if (Ext.isFunction(batchOptions.failure)) {
  44758. Ext.callback(batchOptions.failure, scope, [batch, batchOptions]);
  44759. }
  44760. } else if (Ext.isFunction(batchOptions.success)) {
  44761. Ext.callback(batchOptions.success, scope, [batch, batchOptions]);
  44762. }
  44763. if (Ext.isFunction(batchOptions.callback)) {
  44764. Ext.callback(batchOptions.callback, scope, [batch, batchOptions]);
  44765. }
  44766. }
  44767. }, function() {
  44768. // Ext.data2.proxy.ProxyMgr.registerType('proxy', this);
  44769. //backwards compatibility
  44770. // Ext.data.DataProxy = this;
  44771. // Ext.deprecate('platform', '2.0', function() {
  44772. // Ext.data2.DataProxy = this;
  44773. // }, this);
  44774. });
  44775. /**
  44776. * @author Ed Spencer
  44777. *
  44778. * Base class for any client-side storage. Used as a superclass for {@link Ext.data.proxy.Memory Memory} and
  44779. * {@link Ext.data.proxy.WebStorage Web Storage} proxies. Do not use directly, use one of the subclasses instead.
  44780. * @private
  44781. */
  44782. Ext.define('Ext.data.proxy.Client', {
  44783. extend: 'Ext.data.proxy.Proxy',
  44784. alternateClassName: 'Ext.proxy.ClientProxy',
  44785. /**
  44786. * Abstract function that must be implemented by each ClientProxy subclass. This should purge all record data
  44787. * from the client side storage, as well as removing any supporting data (such as lists of record IDs)
  44788. */
  44789. clear: function() {
  44790. //<debug>
  44791. Ext.Logger.error("The Ext.data.proxy.Client subclass that you are using has not defined a 'clear' function. See src/data/ClientProxy.js for details.");
  44792. //</debug>
  44793. }
  44794. });
  44795. /**
  44796. * @author Ed Spencer
  44797. * @aside guide proxies
  44798. *
  44799. * In-memory proxy. This proxy simply uses a local variable for data storage/retrieval, so its contents are lost on
  44800. * every page refresh.
  44801. *
  44802. * Usually this Proxy isn't used directly, serving instead as a helper to a {@link Ext.data.Store Store} where a reader
  44803. * is required to load data. For example, say we have a Store for a User model and have some inline data we want to
  44804. * load, but this data isn't in quite the right format: we can use a MemoryProxy with a JsonReader to read it into our
  44805. * Store:
  44806. *
  44807. * //this is the model we will be using in the store
  44808. * Ext.define('User', {
  44809. * extend: 'Ext.data.Model',
  44810. * config: {
  44811. * fields: [
  44812. * {name: 'id', type: 'int'},
  44813. * {name: 'name', type: 'string'},
  44814. * {name: 'phone', type: 'string', mapping: 'phoneNumber'}
  44815. * ]
  44816. * }
  44817. * });
  44818. *
  44819. * //this data does not line up to our model fields - the phone field is called phoneNumber
  44820. * var data = {
  44821. * users: [
  44822. * {
  44823. * id: 1,
  44824. * name: 'Ed Spencer',
  44825. * phoneNumber: '555 1234'
  44826. * },
  44827. * {
  44828. * id: 2,
  44829. * name: 'Abe Elias',
  44830. * phoneNumber: '666 1234'
  44831. * }
  44832. * ]
  44833. * };
  44834. *
  44835. * //note how we set the 'root' in the reader to match the data structure above
  44836. * var store = Ext.create('Ext.data.Store', {
  44837. * autoLoad: true,
  44838. * model: 'User',
  44839. * data : data,
  44840. * proxy: {
  44841. * type: 'memory',
  44842. * reader: {
  44843. * type: 'json',
  44844. * root: 'users'
  44845. * }
  44846. * }
  44847. * });
  44848. */
  44849. Ext.define('Ext.data.proxy.Memory', {
  44850. extend: 'Ext.data.proxy.Client',
  44851. alias: 'proxy.memory',
  44852. alternateClassName: 'Ext.data.MemoryProxy',
  44853. isMemoryProxy: true,
  44854. config: {
  44855. /**
  44856. * @cfg {Object} data
  44857. * Optional data to pass to configured Reader.
  44858. */
  44859. data: []
  44860. },
  44861. /**
  44862. * @private
  44863. * Fake processing function to commit the records, set the current operation
  44864. * to successful and call the callback if provided. This function is shared
  44865. * by the create, update and destroy methods to perform the bare minimum
  44866. * processing required for the proxy to register a result from the action.
  44867. */
  44868. finishOperation: function(operation, callback, scope) {
  44869. if (operation) {
  44870. var i = 0,
  44871. recs = operation.getRecords(),
  44872. len = recs.length;
  44873. for (i; i < len; i++) {
  44874. recs[i].commit();
  44875. }
  44876. operation.setCompleted();
  44877. operation.setSuccessful();
  44878. Ext.callback(callback, scope || this, [operation]);
  44879. }
  44880. },
  44881. /**
  44882. * Currently this is a hard-coded method that simply commits any records and sets the operation to successful,
  44883. * then calls the callback function, if provided. It is essentially mocking a server call in memory, but since
  44884. * there is no real back end in this case there's not much else to do. This method can be easily overridden to
  44885. * implement more complex logic if needed.
  44886. * @param {Ext.data.Operation} operation The Operation to perform
  44887. * @param {Function} callback Callback function to be called when the Operation has completed (whether
  44888. * successful or not)
  44889. * @param {Object} scope Scope to execute the callback function in
  44890. * @method
  44891. */
  44892. create: function() {
  44893. this.finishOperation.apply(this, arguments);
  44894. },
  44895. /**
  44896. * Currently this is a hard-coded method that simply commits any records and sets the operation to successful,
  44897. * then calls the callback function, if provided. It is essentially mocking a server call in memory, but since
  44898. * there is no real back end in this case there's not much else to do. This method can be easily overridden to
  44899. * implement more complex logic if needed.
  44900. * @param {Ext.data.Operation} operation The Operation to perform
  44901. * @param {Function} callback Callback function to be called when the Operation has completed (whether
  44902. * successful or not)
  44903. * @param {Object} scope Scope to execute the callback function in
  44904. * @method
  44905. */
  44906. update: function() {
  44907. this.finishOperation.apply(this, arguments);
  44908. },
  44909. /**
  44910. * Currently this is a hard-coded method that simply commits any records and sets the operation to successful,
  44911. * then calls the callback function, if provided. It is essentially mocking a server call in memory, but since
  44912. * there is no real back end in this case there's not much else to do. This method can be easily overridden to
  44913. * implement more complex logic if needed.
  44914. * @param {Ext.data.Operation} operation The Operation to perform
  44915. * @param {Function} callback Callback function to be called when the Operation has completed (whether
  44916. * successful or not)
  44917. * @param {Object} scope Scope to execute the callback function in
  44918. * @method
  44919. */
  44920. destroy: function() {
  44921. this.finishOperation.apply(this, arguments);
  44922. },
  44923. /**
  44924. * Reads data from the configured {@link #data} object. Uses the Proxy's {@link #reader}, if present.
  44925. * @param {Ext.data.Operation} operation The read Operation
  44926. * @param {Function} callback The callback to call when reading has completed
  44927. * @param {Object} scope The scope to call the callback function in
  44928. */
  44929. read: function(operation, callback, scope) {
  44930. var me = this,
  44931. reader = me.getReader();
  44932. if (operation.process('read', reader.process(me.getData())) === false) {
  44933. this.fireEvent('exception', this, null, operation);
  44934. }
  44935. Ext.callback(callback, scope || me, [operation]);
  44936. },
  44937. clear: Ext.emptyFn
  44938. });
  44939. /**
  44940. * @class Ext.data.SortTypes
  44941. * This class defines a series of static methods that are used on a
  44942. * {@link Ext.data.Field} for performing sorting. The methods cast the
  44943. * underlying values into a data type that is appropriate for sorting on
  44944. * that particular field. If a {@link Ext.data.Field#type} is specified,
  44945. * the `sortType` will be set to a sane default if the `sortType` is not
  44946. * explicitly defined on the field. The `sortType` will make any necessary
  44947. * modifications to the value and return it.
  44948. *
  44949. * - `asText` - Removes any tags and converts the value to a string.
  44950. * - `asUCText` - Removes any tags and converts the value to an uppercase string.
  44951. * - `asUCString` - Converts the value to an uppercase string.
  44952. * - `asDate` - Converts the value into Unix epoch time.
  44953. * - `asFloat` - Converts the value to a floating point number.
  44954. * - `asInt` - Converts the value to an integer number.
  44955. *
  44956. * It is also possible to create a custom `sortType` that can be used throughout
  44957. * an application.
  44958. *
  44959. * Ext.apply(Ext.data.SortTypes, {
  44960. * asPerson: function(person){
  44961. * // expects an object with a first and last name property
  44962. * return person.lastName.toUpperCase() + person.firstName.toLowerCase();
  44963. * }
  44964. * });
  44965. *
  44966. * Ext.define('Employee', {
  44967. * extend: 'Ext.data.Model',
  44968. * config: {
  44969. * fields: [{
  44970. * name: 'person',
  44971. * sortType: 'asPerson'
  44972. * }, {
  44973. * name: 'salary',
  44974. * type: 'float' // sortType set to asFloat
  44975. * }]
  44976. * }
  44977. * });
  44978. *
  44979. * @singleton
  44980. * @docauthor Evan Trimboli <evan@sencha.com>
  44981. */
  44982. Ext.define('Ext.data.SortTypes', {
  44983. singleton: true,
  44984. /**
  44985. * The regular expression used to strip tags.
  44986. * @type {RegExp}
  44987. * @property
  44988. */
  44989. stripTagsRE : /<\/?[^>]+>/gi,
  44990. /**
  44991. * Default sort that does nothing.
  44992. * @param {Object} value The value being converted.
  44993. * @return {Object} The comparison value.
  44994. */
  44995. none : function(value) {
  44996. return value;
  44997. },
  44998. /**
  44999. * Strips all HTML tags to sort on text only.
  45000. * @param {Object} value The value being converted.
  45001. * @return {String} The comparison value.
  45002. */
  45003. asText : function(value) {
  45004. return String(value).replace(this.stripTagsRE, "");
  45005. },
  45006. /**
  45007. * Strips all HTML tags to sort on text only - case insensitive.
  45008. * @param {Object} value The value being converted.
  45009. * @return {String} The comparison value.
  45010. */
  45011. asUCText : function(value) {
  45012. return String(value).toUpperCase().replace(this.stripTagsRE, "");
  45013. },
  45014. /**
  45015. * Case insensitive string.
  45016. * @param {Object} value The value being converted.
  45017. * @return {String} The comparison value.
  45018. */
  45019. asUCString : function(value) {
  45020. return String(value).toUpperCase();
  45021. },
  45022. /**
  45023. * Date sorting.
  45024. * @param {Object} value The value being converted.
  45025. * @return {Number} The comparison value.
  45026. */
  45027. asDate : function(value) {
  45028. if (!value) {
  45029. return 0;
  45030. }
  45031. if (Ext.isDate(value)) {
  45032. return value.getTime();
  45033. }
  45034. return Date.parse(String(value));
  45035. },
  45036. /**
  45037. * Float sorting.
  45038. * @param {Object} value The value being converted.
  45039. * @return {Number} The comparison value.
  45040. */
  45041. asFloat : function(value) {
  45042. value = parseFloat(String(value).replace(/,/g, ""));
  45043. return isNaN(value) ? 0 : value;
  45044. },
  45045. /**
  45046. * Integer sorting.
  45047. * @param {Object} value The value being converted.
  45048. * @return {Number} The comparison value.
  45049. */
  45050. asInt : function(value) {
  45051. value = parseInt(String(value).replace(/,/g, ""), 10);
  45052. return isNaN(value) ? 0 : value;
  45053. }
  45054. });
  45055. /**
  45056. * @class Ext.data.Types
  45057. *
  45058. * This is a static class containing the system-supplied data types which may be given to a {@link Ext.data.Field Field}.
  45059. *
  45060. * The properties in this class are used as type indicators in the {@link Ext.data.Field Field} class, so to
  45061. * test whether a Field is of a certain type, compare the {@link Ext.data.Field#type type} property against properties
  45062. * of this class.
  45063. *
  45064. * Developers may add their own application-specific data types to this class. Definition names must be UPPERCASE.
  45065. * each type definition must contain three properties:
  45066. *
  45067. * - `convert`: {Function} - A function to convert raw data values from a data block into the data
  45068. * to be stored in the Field. The function is passed the following parameters:
  45069. * + `v`: {Mixed} - The data value as read by the Reader, if `undefined` will use
  45070. * the configured `{@link Ext.data.Field#defaultValue defaultValue}`.
  45071. * + `rec`: {Mixed} - The data object containing the row as read by the Reader.
  45072. * Depending on the Reader type, this could be an Array ({@link Ext.data.reader.Array ArrayReader}), an object
  45073. * ({@link Ext.data.reader.Json JsonReader}), or an XML element.
  45074. * - `sortType`: {Function} - A function to convert the stored data into comparable form, as defined by {@link Ext.data.SortTypes}.
  45075. * - `type`: {String} - A textual data type name.
  45076. *
  45077. * For example, to create a VELatLong field (See the Microsoft Bing Mapping API) containing the latitude/longitude value of a datapoint on a map from a JsonReader data block
  45078. * which contained the properties `lat` and `long`, you would define a new data type like this:
  45079. *
  45080. * // Add a new Field data type which stores a VELatLong object in the Record.
  45081. * Ext.data.Types.VELATLONG = {
  45082. * convert: function(v, data) {
  45083. * return new VELatLong(data.lat, data.long);
  45084. * },
  45085. * sortType: function(v) {
  45086. * return v.Latitude; // When sorting, order by latitude
  45087. * },
  45088. * type: 'VELatLong'
  45089. * };
  45090. *
  45091. * Then, when declaring a Model, use:
  45092. *
  45093. * var types = Ext.data.Types; // allow shorthand type access
  45094. * Ext.define('Unit', {
  45095. * extend: 'Ext.data.Model',
  45096. * config: {
  45097. * fields: [
  45098. * { name: 'unitName', mapping: 'UnitName' },
  45099. * { name: 'curSpeed', mapping: 'CurSpeed', type: types.INT },
  45100. * { name: 'latitude', mapping: 'lat', type: types.FLOAT },
  45101. * { name: 'position', type: types.VELATLONG }
  45102. * ]
  45103. * }
  45104. * });
  45105. *
  45106. * @singleton
  45107. */
  45108. Ext.define('Ext.data.Types', {
  45109. singleton: true,
  45110. requires: ['Ext.data.SortTypes'],
  45111. /**
  45112. * @property {RegExp} stripRe
  45113. * A regular expression for stripping non-numeric characters from a numeric value.
  45114. * This should be overridden for localization.
  45115. */
  45116. stripRe: /[\$,%]/g,
  45117. dashesRe: /-/g,
  45118. iso8601TestRe: /\d\dT\d\d/,
  45119. iso8601SplitRe: /[- :T\.Z\+]/
  45120. }, function() {
  45121. var Types = this,
  45122. sortTypes = Ext.data.SortTypes;
  45123. Ext.apply(Types, {
  45124. /**
  45125. * @property {Object} AUTO
  45126. * This data type means that no conversion is applied to the raw data before it is placed into a Record.
  45127. */
  45128. AUTO: {
  45129. convert: function(value) {
  45130. return value;
  45131. },
  45132. sortType: sortTypes.none,
  45133. type: 'auto'
  45134. },
  45135. /**
  45136. * @property {Object} STRING
  45137. * This data type means that the raw data is converted into a String before it is placed into a Record.
  45138. */
  45139. STRING: {
  45140. convert: function(value) {
  45141. // 'this' is the actual field that calls this convert method
  45142. return (value === undefined || value === null)
  45143. ? (this.getAllowNull() ? null : '')
  45144. : String(value);
  45145. },
  45146. sortType: sortTypes.asUCString,
  45147. type: 'string'
  45148. },
  45149. /**
  45150. * @property {Object} INT
  45151. * This data type means that the raw data is converted into an integer before it is placed into a Record.
  45152. *
  45153. * The synonym `INTEGER` is equivalent.
  45154. */
  45155. INT: {
  45156. convert: function(value) {
  45157. return (value !== undefined && value !== null && value !== '')
  45158. ? ((typeof value === 'number')
  45159. ? parseInt(value, 10)
  45160. : parseInt(String(value).replace(Types.stripRe, ''), 10)
  45161. )
  45162. : (this.getAllowNull() ? null : 0);
  45163. },
  45164. sortType: sortTypes.none,
  45165. type: 'int'
  45166. },
  45167. /**
  45168. * @property {Object} FLOAT
  45169. * This data type means that the raw data is converted into a number before it is placed into a Record.
  45170. *
  45171. * The synonym `NUMBER` is equivalent.
  45172. */
  45173. FLOAT: {
  45174. convert: function(value) {
  45175. return (value !== undefined && value !== null && value !== '')
  45176. ? ((typeof value === 'number')
  45177. ? value
  45178. : parseFloat(String(value).replace(Types.stripRe, ''), 10)
  45179. )
  45180. : (this.getAllowNull() ? null : 0);
  45181. },
  45182. sortType: sortTypes.none,
  45183. type: 'float'
  45184. },
  45185. /**
  45186. * @property {Object} BOOL
  45187. * This data type means that the raw data is converted into a Boolean before it is placed into
  45188. * a Record. The string "true" and the number 1 are converted to Boolean `true`.
  45189. *
  45190. * The synonym `BOOLEAN` is equivalent.
  45191. */
  45192. BOOL: {
  45193. convert: function(value) {
  45194. if ((value === undefined || value === null || value === '') && this.getAllowNull()) {
  45195. return null;
  45196. }
  45197. return value !== 'false' && !!value;
  45198. },
  45199. sortType: sortTypes.none,
  45200. type: 'bool'
  45201. },
  45202. /**
  45203. * @property {Object} DATE
  45204. * This data type means that the raw data is converted into a Date before it is placed into a Record.
  45205. * The date format is specified in the constructor of the {@link Ext.data.Field} to which this type is
  45206. * being applied.
  45207. */
  45208. DATE: {
  45209. convert: function(value) {
  45210. var dateFormat = this.getDateFormat(),
  45211. parsed;
  45212. if (!value) {
  45213. return null;
  45214. }
  45215. if (Ext.isDate(value)) {
  45216. return value;
  45217. }
  45218. if (dateFormat) {
  45219. if (dateFormat == 'timestamp') {
  45220. return new Date(value*1000);
  45221. }
  45222. if (dateFormat == 'time') {
  45223. return new Date(parseInt(value, 10));
  45224. }
  45225. return Ext.Date.parse(value, dateFormat);
  45226. }
  45227. parsed = new Date(Date.parse(value));
  45228. if (isNaN(parsed)) {
  45229. // Dates with ISO 8601 format are not well supported by mobile devices, this can work around the issue.
  45230. if (Types.iso8601TestRe.test(value)) {
  45231. parsed = value.split(Types.iso8601SplitRe);
  45232. parsed = new Date(parsed[0], parsed[1]-1, parsed[2], parsed[3], parsed[4], parsed[5]);
  45233. }
  45234. if (isNaN(parsed)) {
  45235. // Dates with the format "2012-01-20" fail, but "2012/01/20" work in some browsers. We'll try and
  45236. // get around that.
  45237. parsed = new Date(Date.parse(value.replace(Types.dashesRe, "/")));
  45238. //<debug>
  45239. if (isNaN(parsed)) {
  45240. Ext.Logger.warn("Cannot parse the passed value (" + value + ") into a valid date");
  45241. }
  45242. //</debug>
  45243. }
  45244. }
  45245. return isNaN(parsed) ? null : parsed;
  45246. },
  45247. sortType: sortTypes.asDate,
  45248. type: 'date'
  45249. }
  45250. });
  45251. Ext.apply(Types, {
  45252. /**
  45253. * @property {Object} BOOLEAN
  45254. * This data type means that the raw data is converted into a Boolean before it is placed into
  45255. * a Record. The string "true" and the number 1 are converted to Boolean `true`.
  45256. *
  45257. * The synonym `BOOL` is equivalent.
  45258. */
  45259. BOOLEAN: this.BOOL,
  45260. /**
  45261. * @property {Object} INTEGER
  45262. * This data type means that the raw data is converted into an integer before it is placed into a Record.
  45263. *
  45264. *The synonym `INT` is equivalent.
  45265. */
  45266. INTEGER: this.INT,
  45267. /**
  45268. * @property {Object} NUMBER
  45269. * This data type means that the raw data is converted into a number before it is placed into a Record.
  45270. *
  45271. * The synonym `FLOAT` is equivalent.
  45272. */
  45273. NUMBER: this.FLOAT
  45274. });
  45275. });
  45276. /**
  45277. * @author Ed Spencer
  45278. * @aside guide models
  45279. *
  45280. * Fields are used to define what a Model is. They aren't instantiated directly - instead, when we create a class that
  45281. * extends {@link Ext.data.Model}, it will automatically create a Field instance for each field configured in a {@link
  45282. * Ext.data.Model Model}. For example, we might set up a model like this:
  45283. *
  45284. * Ext.define('User', {
  45285. * extend: 'Ext.data.Model',
  45286. * config: {
  45287. * fields: [
  45288. * 'name', 'email',
  45289. * {name: 'age', type: 'int'},
  45290. * {name: 'gender', type: 'string', defaultValue: 'Unknown'}
  45291. * ]
  45292. * }
  45293. * });
  45294. *
  45295. * Four fields will have been created for the User Model - name, email, age, and gender. Note that we specified a couple
  45296. * of different formats here; if we only pass in the string name of the field (as with name and email), the field is set
  45297. * up with the 'auto' type. It's as if we'd done this instead:
  45298. *
  45299. * Ext.define('User', {
  45300. * extend: 'Ext.data.Model',
  45301. * config: {
  45302. * fields: [
  45303. * {name: 'name', type: 'auto'},
  45304. * {name: 'email', type: 'auto'},
  45305. * {name: 'age', type: 'int'},
  45306. * {name: 'gender', type: 'string', defaultValue: 'Unknown'}
  45307. * ]
  45308. * }
  45309. * });
  45310. *
  45311. * # Types and conversion
  45312. *
  45313. * The {@link #type} is important - it's used to automatically convert data passed to the field into the correct format.
  45314. * In our example above, the name and email fields used the 'auto' type and will just accept anything that is passed
  45315. * into them. The 'age' field had an 'int' type however, so if we passed 25.4 this would be rounded to 25.
  45316. *
  45317. * Sometimes a simple type isn't enough, or we want to perform some processing when we load a Field's data. We can do
  45318. * this using a {@link #convert} function. Here, we're going to create a new field based on another:
  45319. *
  45320. * Ext.define('User', {
  45321. * extend: 'Ext.data.Model',
  45322. * config: {
  45323. * fields: [
  45324. * 'name', 'email',
  45325. * {name: 'age', type: 'int'},
  45326. * {name: 'gender', type: 'string', defaultValue: 'Unknown'},
  45327. *
  45328. * {
  45329. * name: 'firstName',
  45330. * convert: function(value, record) {
  45331. * var fullName = record.get('name'),
  45332. * splits = fullName.split(" "),
  45333. * firstName = splits[0];
  45334. *
  45335. * return firstName;
  45336. * }
  45337. * }
  45338. * ]
  45339. * }
  45340. * });
  45341. *
  45342. * Now when we create a new User, the firstName is populated automatically based on the name:
  45343. *
  45344. * var ed = Ext.create('User', {name: 'Ed Spencer'});
  45345. *
  45346. * console.log(ed.get('firstName')); //logs 'Ed', based on our convert function
  45347. *
  45348. * In fact, if we log out all of the data inside ed, we'll see this:
  45349. *
  45350. * console.log(ed.data);
  45351. *
  45352. * //outputs this:
  45353. * {
  45354. * age: 0,
  45355. * email: "",
  45356. * firstName: "Ed",
  45357. * gender: "Unknown",
  45358. * name: "Ed Spencer"
  45359. * }
  45360. *
  45361. * The age field has been given a default of zero because we made it an int type. As an auto field, email has defaulted
  45362. * to an empty string. When we registered the User model we set gender's {@link #defaultValue} to 'Unknown' so we see
  45363. * that now. Let's correct that and satisfy ourselves that the types work as we expect:
  45364. *
  45365. * ed.set('gender', 'Male');
  45366. * ed.get('gender'); //returns 'Male'
  45367. *
  45368. * ed.set('age', 25.4);
  45369. * ed.get('age'); //returns 25 - we wanted an int, not a float, so no decimal places allowed
  45370. */
  45371. Ext.define('Ext.data.Field', {
  45372. requires: ['Ext.data.Types', 'Ext.data.SortTypes'],
  45373. alias: 'data.field',
  45374. isField: true,
  45375. config: {
  45376. /**
  45377. * @cfg {String} name
  45378. *
  45379. * The name by which the field is referenced within the Model. This is referenced by, for example, the `dataIndex`
  45380. * property in column definition objects passed to Ext.grid.property.HeaderContainer.
  45381. *
  45382. * Note: In the simplest case, if no properties other than `name` are required, a field definition may consist of
  45383. * just a String for the field name.
  45384. */
  45385. name: null,
  45386. /**
  45387. * @cfg {String/Object} type
  45388. *
  45389. * The data type for automatic conversion from received data to the *stored* value if
  45390. * `{@link Ext.data.Field#convert convert}` has not been specified. This may be specified as a string value.
  45391. * Possible values are
  45392. *
  45393. * - auto (Default, implies no conversion)
  45394. * - string
  45395. * - int
  45396. * - float
  45397. * - boolean
  45398. * - date
  45399. *
  45400. * This may also be specified by referencing a member of the {@link Ext.data.Types} class.
  45401. *
  45402. * Developers may create their own application-specific data types by defining new members of the {@link
  45403. * Ext.data.Types} class.
  45404. */
  45405. type: 'auto',
  45406. /**
  45407. * @cfg {Function} convert
  45408. *
  45409. * A function which converts the value provided by the Reader into an object that will be stored in the Model.
  45410. * It is passed the following parameters:
  45411. *
  45412. * - **v** : Mixed
  45413. *
  45414. * The data value as read by the Reader, if undefined will use the configured `{@link Ext.data.Field#defaultValue
  45415. * defaultValue}`.
  45416. *
  45417. * - **rec** : Ext.data.Model
  45418. *
  45419. * The data object containing the Model as read so far by the Reader. Note that the Model may not be fully populated
  45420. * at this point as the fields are read in the order that they are defined in your
  45421. * {@link Ext.data.Model#cfg-fields fields} array.
  45422. *
  45423. * Example of convert functions:
  45424. *
  45425. * function fullName(v, record) {
  45426. * return record.name.last + ', ' + record.name.first;
  45427. * }
  45428. *
  45429. * function location(v, record) {
  45430. * return !record.city ? '' : (record.city + ', ' + record.state);
  45431. * }
  45432. *
  45433. * Ext.define('Dude', {
  45434. * extend: 'Ext.data.Model',
  45435. * fields: [
  45436. * {name: 'fullname', convert: fullName},
  45437. * {name: 'firstname', mapping: 'name.first'},
  45438. * {name: 'lastname', mapping: 'name.last'},
  45439. * {name: 'city', defaultValue: 'homeless'},
  45440. * 'state',
  45441. * {name: 'location', convert: location}
  45442. * ]
  45443. * });
  45444. *
  45445. * // create the data store
  45446. * var store = Ext.create('Ext.data.Store', {
  45447. * reader: {
  45448. * type: 'json',
  45449. * model: 'Dude',
  45450. * idProperty: 'key',
  45451. * rootProperty: 'daRoot',
  45452. * totalProperty: 'total'
  45453. * }
  45454. * });
  45455. *
  45456. * var myData = [
  45457. * { key: 1,
  45458. * name: { first: 'Fat', last: 'Albert' }
  45459. * // notice no city, state provided in data2 object
  45460. * },
  45461. * { key: 2,
  45462. * name: { first: 'Barney', last: 'Rubble' },
  45463. * city: 'Bedrock', state: 'Stoneridge'
  45464. * },
  45465. * { key: 3,
  45466. * name: { first: 'Cliff', last: 'Claven' },
  45467. * city: 'Boston', state: 'MA'
  45468. * }
  45469. * ];
  45470. */
  45471. convert: undefined,
  45472. /**
  45473. * @cfg {String} dateFormat
  45474. *
  45475. * Used when converting received data into a Date when the {@link #type} is specified as `"date"`.
  45476. *
  45477. * A format string for the {@link Ext.Date#parse Ext.Date.parse} function, or "timestamp" if the value provided by
  45478. * the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a JavaScript millisecond
  45479. * timestamp. See {@link Ext.Date}.
  45480. */
  45481. dateFormat: null,
  45482. /**
  45483. * @cfg {Boolean} allowNull
  45484. *
  45485. * Use when converting received data into a boolean, string or number type (either int or float). If the value cannot be
  45486. * parsed, `null` will be used if `allowNull` is `true`, otherwise the value will be 0.
  45487. */
  45488. allowNull: true,
  45489. /**
  45490. * @cfg {Object} [defaultValue='']
  45491. *
  45492. * The default value used **when a Model is being created by a {@link Ext.data.reader.Reader Reader}**
  45493. * when the item referenced by the `{@link Ext.data.Field#mapping mapping}` does not exist in the data object
  45494. * (i.e. `undefined`).
  45495. */
  45496. defaultValue: undefined,
  45497. /**
  45498. * @cfg {String/Number} mapping
  45499. *
  45500. * (Optional) A path expression for use by the {@link Ext.data.reader.Reader} implementation that is creating the
  45501. * {@link Ext.data.Model Model} to extract the Field value from the data object. If the path expression is the same
  45502. * as the field name, the mapping may be omitted.
  45503. *
  45504. * The form of the mapping expression depends on the Reader being used.
  45505. *
  45506. * - {@link Ext.data.reader.Json}
  45507. *
  45508. * The mapping is a string containing the JavaScript expression to reference the data from an element of the data2
  45509. * item's {@link Ext.data.reader.Json#rootProperty rootProperty} Array. Defaults to the field name.
  45510. *
  45511. * - {@link Ext.data.reader.Xml}
  45512. *
  45513. * The mapping is an {@link Ext.DomQuery} path to the data item relative to the DOM element that represents the
  45514. * {@link Ext.data.reader.Xml#record record}. Defaults to the field name.
  45515. *
  45516. * - {@link Ext.data.reader.Array}
  45517. *
  45518. * The mapping is a number indicating the Array index of the field's value. Defaults to the field specification's
  45519. * Array position.
  45520. *
  45521. * If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
  45522. * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
  45523. * return the desired data.
  45524. */
  45525. mapping: null,
  45526. /**
  45527. * @cfg {Function} sortType
  45528. *
  45529. * A function which converts a Field's value to a comparable value in order to ensure correct sort ordering.
  45530. * Predefined functions are provided in {@link Ext.data.SortTypes}. A custom sort example:
  45531. *
  45532. * // current sort after sort we want
  45533. * // +-+------+ +-+------+
  45534. * // |1|First | |1|First |
  45535. * // |2|Last | |3|Second|
  45536. * // |3|Second| |2|Last |
  45537. * // +-+------+ +-+------+
  45538. *
  45539. * sortType: function(value) {
  45540. * switch (value.toLowerCase()) // native toLowerCase():
  45541. * {
  45542. * case 'first': return 1;
  45543. * case 'second': return 2;
  45544. * default: return 3;
  45545. * }
  45546. * }
  45547. */
  45548. sortType : undefined,
  45549. /**
  45550. * @cfg {String} sortDir
  45551. *
  45552. * Initial direction to sort (`"ASC"` or `"DESC"`).
  45553. */
  45554. sortDir : "ASC",
  45555. /**
  45556. * @cfg {Boolean} allowBlank
  45557. * @private
  45558. *
  45559. * Used for validating a {@link Ext.data.Model model}. An empty value here will cause
  45560. * {@link Ext.data.Model}.{@link Ext.data.Model#isValid isValid} to evaluate to `false`.
  45561. */
  45562. allowBlank : true,
  45563. /**
  45564. * @cfg {Boolean} persist
  45565. *
  45566. * `false` to exclude this field from being synchronized with the server or localStorage.
  45567. * This option is useful when model fields are used to keep state on the client but do
  45568. * not need to be persisted to the server.
  45569. */
  45570. persist: true,
  45571. // Used in LocalStorage stuff
  45572. encode: null,
  45573. decode: null,
  45574. bubbleEvents: 'action'
  45575. },
  45576. constructor : function(config) {
  45577. // This adds support for just passing a string used as the field name
  45578. if (Ext.isString(config)) {
  45579. config = {name: config};
  45580. }
  45581. this.initConfig(config);
  45582. },
  45583. applyType: function(type) {
  45584. var types = Ext.data.Types,
  45585. autoType = types.AUTO;
  45586. if (type) {
  45587. if (Ext.isString(type)) {
  45588. return types[type.toUpperCase()] || autoType;
  45589. } else {
  45590. // At this point we expect an actual type
  45591. return type;
  45592. }
  45593. }
  45594. return autoType;
  45595. },
  45596. updateType: function(newType, oldType) {
  45597. var convert = this.getConvert();
  45598. if (oldType && convert === oldType.convert) {
  45599. this.setConvert(newType.convert);
  45600. }
  45601. },
  45602. applySortType: function(sortType) {
  45603. var sortTypes = Ext.data.SortTypes,
  45604. type = this.getType(),
  45605. defaultSortType = type.sortType;
  45606. if (sortType) {
  45607. if (Ext.isString(sortType)) {
  45608. return sortTypes[sortType] || defaultSortType;
  45609. } else {
  45610. // At this point we expect a function
  45611. return sortType;
  45612. }
  45613. }
  45614. return defaultSortType;
  45615. },
  45616. applyConvert: function(convert) {
  45617. var defaultConvert = this.getType().convert;
  45618. if (convert && convert !== defaultConvert) {
  45619. this._hasCustomConvert = true;
  45620. return convert;
  45621. } else {
  45622. this._hasCustomConvert = false;
  45623. return defaultConvert;
  45624. }
  45625. },
  45626. hasCustomConvert: function() {
  45627. return this._hasCustomConvert;
  45628. }
  45629. });
  45630. /**
  45631. * @author Tommy Maintz
  45632. *
  45633. * This class is the simple default id generator for Model instances.
  45634. *
  45635. * An example of a configured simple generator would be:
  45636. *
  45637. * Ext.define('MyApp.data.MyModel', {
  45638. * extend: 'Ext.data.Model',
  45639. * config: {
  45640. * identifier: {
  45641. * type: 'simple',
  45642. * prefix: 'ID_'
  45643. * }
  45644. * }
  45645. * });
  45646. * // assign id's of ID_1, ID_2, ID_3, etc.
  45647. *
  45648. */
  45649. Ext.define('Ext.data.identifier.Simple', {
  45650. alias: 'data.identifier.simple',
  45651. statics: {
  45652. AUTO_ID: 1
  45653. },
  45654. config: {
  45655. prefix: 'ext-record-'
  45656. },
  45657. constructor: function(config) {
  45658. this.initConfig(config);
  45659. },
  45660. generate: function(record) {
  45661. return this._prefix + this.self.AUTO_ID++;
  45662. }
  45663. });
  45664. /**
  45665. * @author Ed Spencer
  45666. *
  45667. * The ModelManager keeps track of all {@link Ext.data.Model} types defined in your application.
  45668. *
  45669. * ## Creating Model Instances
  45670. *
  45671. * Model instances can be created by using the {@link Ext.ClassManager#create Ext.create} method. Ext.create replaces
  45672. * the deprecated {@link #create Ext.ModelManager.create} method. It is also possible to create a model instance
  45673. * this by using the Model type directly. The following 3 snippets are equivalent:
  45674. *
  45675. * Ext.define('User', {
  45676. * extend: 'Ext.data.Model',
  45677. * config: {
  45678. * fields: ['first', 'last']
  45679. * }
  45680. * });
  45681. *
  45682. * // method 1, create using Ext.create (recommended)
  45683. * Ext.create('User', {
  45684. * first: 'Ed',
  45685. * last: 'Spencer'
  45686. * });
  45687. *
  45688. * // method 2, create on the type directly
  45689. * new User({
  45690. * first: 'Ed',
  45691. * last: 'Spencer'
  45692. * });
  45693. *
  45694. * // method 3, create through the manager (deprecated)
  45695. * Ext.ModelManager.create({
  45696. * first: 'Ed',
  45697. * last: 'Spencer'
  45698. * }, 'User');
  45699. *
  45700. * ## Accessing Model Types
  45701. *
  45702. * A reference to a Model type can be obtained by using the {@link #getModel} function. Since models types
  45703. * are normal classes, you can access the type directly. The following snippets are equivalent:
  45704. *
  45705. * Ext.define('User', {
  45706. * extend: 'Ext.data.Model',
  45707. * config: {
  45708. * fields: ['first', 'last']
  45709. * }
  45710. * });
  45711. *
  45712. * // method 1, access model type through the manager
  45713. * var UserType = Ext.ModelManager.getModel('User');
  45714. *
  45715. * // method 2, reference the type directly
  45716. * var UserType = User;
  45717. */
  45718. Ext.define('Ext.data.ModelManager', {
  45719. extend: 'Ext.AbstractManager',
  45720. alternateClassName: ['Ext.ModelMgr', 'Ext.ModelManager'],
  45721. singleton: true,
  45722. /**
  45723. * @property defaultProxyType
  45724. * The string type of the default Model Proxy.
  45725. * @removed 2.0.0
  45726. */
  45727. /**
  45728. * @property associationStack
  45729. * Private stack of associations that must be created once their associated model has been defined.
  45730. * @removed 2.0.0
  45731. */
  45732. modelNamespace: null,
  45733. /**
  45734. * Registers a model definition. All model plugins marked with `isDefault: true` are bootstrapped
  45735. * immediately, as are any addition plugins defined in the model config.
  45736. * @param name
  45737. * @param config
  45738. * @return {Object}
  45739. */
  45740. registerType: function(name, config) {
  45741. var proto = config.prototype,
  45742. model;
  45743. if (proto && proto.isModel) {
  45744. // registering an already defined model
  45745. model = config;
  45746. } else {
  45747. config = {
  45748. extend: config.extend || 'Ext.data.Model',
  45749. config: config
  45750. };
  45751. model = Ext.define(name, config);
  45752. }
  45753. this.types[name] = model;
  45754. return model;
  45755. },
  45756. onModelDefined: Ext.emptyFn,
  45757. // /**
  45758. // * @private
  45759. // * Private callback called whenever a model has just been defined. This sets up any associations
  45760. // * that were waiting for the given model to be defined.
  45761. // * @param {Function} model The model that was just created.
  45762. // */
  45763. // onModelDefined: function(model) {
  45764. // var stack = this.associationStack,
  45765. // length = stack.length,
  45766. // create = [],
  45767. // association, i, created;
  45768. //
  45769. // for (i = 0; i < length; i++) {
  45770. // association = stack[i];
  45771. //
  45772. // if (association.associatedModel == model.modelName) {
  45773. // create.push(association);
  45774. // }
  45775. // }
  45776. //
  45777. // for (i = 0, length = create.length; i < length; i++) {
  45778. // created = create[i];
  45779. // this.types[created.ownerModel].prototype.associations.add(Ext.data.association.Association.create(created));
  45780. // Ext.Array.remove(stack, created);
  45781. // }
  45782. // },
  45783. //
  45784. // /**
  45785. // * Registers an association where one of the models defined doesn't exist yet.
  45786. // * The ModelManager will check when new models are registered if it can link them
  45787. // * together.
  45788. // * @private
  45789. // * @param {Ext.data.association.Association} association The association.
  45790. // */
  45791. // registerDeferredAssociation: function(association){
  45792. // this.associationStack.push(association);
  45793. // },
  45794. /**
  45795. * Returns the {@link Ext.data.Model} for a given model name.
  45796. * @param {String/Object} id The `id` of the model or the model instance.
  45797. * @return {Ext.data.Model} A model class.
  45798. */
  45799. getModel: function(id) {
  45800. var model = id;
  45801. if (typeof model == 'string') {
  45802. model = this.types[model];
  45803. if (!model && this.modelNamespace) {
  45804. model = this.types[this.modelNamespace + '.' + model];
  45805. }
  45806. }
  45807. return model;
  45808. },
  45809. /**
  45810. * Creates a new instance of a Model using the given data.
  45811. *
  45812. * __Note:__ This method is deprecated. Use {@link Ext.ClassManager#create Ext.create} instead. For example:
  45813. *
  45814. * Ext.create('User', {
  45815. * first: 'Ed',
  45816. * last: 'Spencer'
  45817. * });
  45818. *
  45819. * @param {Object} data Data to initialize the Model's fields with.
  45820. * @param {String} name The name of the model to create.
  45821. * @param {Number} id (optional) Unique id of the Model instance (see {@link Ext.data.Model}).
  45822. * @return {Object}
  45823. */
  45824. create: function(config, name, id) {
  45825. var con = typeof name == 'function' ? name : this.types[name || config.name];
  45826. return new con(config, id);
  45827. }
  45828. }, function() {
  45829. /**
  45830. * Old way for creating Model classes. Instead use:
  45831. *
  45832. * Ext.define("MyModel", {
  45833. * extend: "Ext.data.Model",
  45834. * fields: []
  45835. * });
  45836. *
  45837. * @param {String} name Name of the Model class.
  45838. * @param {Object} config A configuration object for the Model you wish to create.
  45839. * @return {Ext.data.Model} The newly registered Model.
  45840. * @member Ext
  45841. * @deprecated 2.0.0 Please use {@link Ext#define} instead.
  45842. */
  45843. Ext.regModel = function() {
  45844. //<debug>
  45845. Ext.Logger.deprecate('Ext.regModel has been deprecated. Models can now be created by ' +
  45846. 'extending Ext.data.Model: Ext.define("MyModel", {extend: "Ext.data.Model", fields: []});.');
  45847. //</debug>
  45848. return this.ModelManager.registerType.apply(this.ModelManager, arguments);
  45849. };
  45850. });
  45851. /**
  45852. * @author Ed Spencer
  45853. *
  45854. * Simple class that represents a Request that will be made by any {@link Ext.data.proxy.Server} subclass.
  45855. * All this class does is standardize the representation of a Request as used by any ServerProxy subclass,
  45856. * it does not contain any actual logic or perform the request itself.
  45857. */
  45858. Ext.define('Ext.data.Request', {
  45859. config: {
  45860. /**
  45861. * @cfg {String} action
  45862. * The name of the action this Request represents. Usually one of 'create', 'read', 'update' or 'destroy'.
  45863. */
  45864. action: null,
  45865. /**
  45866. * @cfg {Object} params
  45867. * HTTP request params. The Proxy and its Writer have access to and can modify this object.
  45868. */
  45869. params: null,
  45870. /**
  45871. * @cfg {String} method
  45872. * The HTTP method to use on this Request. Should be one of 'GET', 'POST', 'PUT' or 'DELETE'.
  45873. */
  45874. method: 'GET',
  45875. /**
  45876. * @cfg {String} url
  45877. * The url to access on this Request.
  45878. */
  45879. url: null,
  45880. /**
  45881. * @cfg {Ext.data.Operation} operation
  45882. * The operation this request belongs to.
  45883. */
  45884. operation: null,
  45885. /**
  45886. * @cfg {Ext.data.proxy.Proxy} proxy
  45887. * The proxy this request belongs to.
  45888. */
  45889. proxy: null,
  45890. /**
  45891. * @cfg {Boolean} disableCaching
  45892. * Whether or not to disable caching for this request.
  45893. */
  45894. disableCaching: false,
  45895. /**
  45896. * @cfg {Object} headers
  45897. * Some requests (like XMLHttpRequests) want to send additional server headers.
  45898. * This configuration can be set for those types of requests.
  45899. */
  45900. headers: {},
  45901. /**
  45902. * @cfg {String} callbackKey
  45903. * Some requests (like JsonP) want to send an additional key that contains
  45904. * the name of the callback function.
  45905. */
  45906. callbackKey: null,
  45907. /**
  45908. * @cfg {Ext.data.JsonP} jsonp
  45909. * JsonP requests return a handle that might be useful in the callback function.
  45910. */
  45911. jsonP: null,
  45912. /**
  45913. * @cfg {Object} jsonData
  45914. * This is used by some write actions to attach data to the request without encoding it
  45915. * as a parameter.
  45916. */
  45917. jsonData: null,
  45918. /**
  45919. * @cfg {Object} xmlData
  45920. * This is used by some write actions to attach data to the request without encoding it
  45921. * as a parameter, but instead sending it as XML.
  45922. */
  45923. xmlData: null,
  45924. /**
  45925. * @cfg {Boolean} withCredentials
  45926. * This field is necessary when using cross-origin resource sharing.
  45927. */
  45928. withCredentials: null,
  45929. /**
  45930. * @cfg {String} username
  45931. * Most oData feeds require basic HTTP authentication. This configuration allows
  45932. * you to specify the username.
  45933. * @accessor
  45934. */
  45935. username: null,
  45936. /**
  45937. * @cfg {String} password
  45938. * Most oData feeds require basic HTTP authentication. This configuration allows
  45939. * you to specify the password.
  45940. * @accessor
  45941. */
  45942. password: null,
  45943. callback: null,
  45944. scope: null,
  45945. timeout: 30000,
  45946. records: null,
  45947. // The following two configurations are only used by Ext.data.proxy.Direct and are just
  45948. // for being able to retrieve them after the request comes back from the server.
  45949. directFn: null,
  45950. args: null
  45951. },
  45952. /**
  45953. * Creates the Request object.
  45954. * @param {Object} [config] Config object.
  45955. */
  45956. constructor: function(config) {
  45957. this.initConfig(config);
  45958. }
  45959. });
  45960. /**
  45961. * @author Ed Spencer
  45962. *
  45963. * ServerProxy is a superclass of {@link Ext.data.proxy.JsonP JsonPProxy} and {@link Ext.data.proxy.Ajax AjaxProxy}, and
  45964. * would not usually be used directly.
  45965. * @private
  45966. */
  45967. Ext.define('Ext.data.proxy.Server', {
  45968. extend: 'Ext.data.proxy.Proxy',
  45969. alias : 'proxy.server',
  45970. alternateClassName: 'Ext.data.ServerProxy',
  45971. requires : ['Ext.data.Request'],
  45972. config: {
  45973. /**
  45974. * @cfg {String} url
  45975. * The URL from which to request the data object.
  45976. */
  45977. url: null,
  45978. /**
  45979. * @cfg {String} pageParam
  45980. * The name of the `page` parameter to send in a request. Set this to `false` if you don't
  45981. * want to send a page parameter.
  45982. */
  45983. pageParam: 'page',
  45984. /**
  45985. * @cfg {String} startParam
  45986. * The name of the `start` parameter to send in a request. Set this to `false` if you don't
  45987. * want to send a start parameter.
  45988. */
  45989. startParam: 'start',
  45990. /**
  45991. * @cfg {String} limitParam
  45992. * The name of the `limit` parameter to send in a request. Set this to `false` if you don't
  45993. * want to send a limit parameter.
  45994. */
  45995. limitParam: 'limit',
  45996. /**
  45997. * @cfg {String} groupParam
  45998. * The name of the `group` parameter to send in a request. Set this to `false` if you don't
  45999. * want to send a group parameter.
  46000. */
  46001. groupParam: 'group',
  46002. /**
  46003. * @cfg {String} sortParam
  46004. * The name of the `sort` parameter to send in a request. Set this to `undefined` if you don't
  46005. * want to send a sort parameter.
  46006. */
  46007. sortParam: 'sort',
  46008. /**
  46009. * @cfg {String} filterParam
  46010. * The name of the 'filter' parameter to send in a request. Set this to `undefined` if you don't
  46011. * want to send a filter parameter.
  46012. */
  46013. filterParam: 'filter',
  46014. /**
  46015. * @cfg {String} directionParam
  46016. * The name of the direction parameter to send in a request.
  46017. *
  46018. * __Note:__ This is only used when `simpleSortMode` is set to `true`.
  46019. */
  46020. directionParam: 'dir',
  46021. /**
  46022. * @cfg {Boolean} enablePagingParams This can be set to `false` if you want to prevent the paging params to be
  46023. * sent along with the requests made by this proxy.
  46024. */
  46025. enablePagingParams: true,
  46026. /**
  46027. * @cfg {Boolean} simpleSortMode
  46028. * Enabling `simpleSortMode` in conjunction with `remoteSort` will only send one sort property and a direction when a
  46029. * remote sort is requested. The `directionParam` and `sortParam` will be sent with the property name and either 'ASC'
  46030. * or 'DESC'.
  46031. */
  46032. simpleSortMode: false,
  46033. /**
  46034. * @cfg {Boolean} noCache
  46035. * Disable caching by adding a unique parameter name to the request. Set to `false` to allow caching.
  46036. */
  46037. noCache : true,
  46038. /**
  46039. * @cfg {String} cacheString
  46040. * The name of the cache param added to the url when using `noCache`.
  46041. */
  46042. cacheString: "_dc",
  46043. /**
  46044. * @cfg {Number} timeout
  46045. * The number of milliseconds to wait for a response.
  46046. */
  46047. timeout : 30000,
  46048. /**
  46049. * @cfg {Object} api
  46050. * Specific urls to call on CRUD action methods "create", "read", "update" and "destroy". Defaults to:
  46051. *
  46052. * api: {
  46053. * create : undefined,
  46054. * read : undefined,
  46055. * update : undefined,
  46056. * destroy : undefined
  46057. * }
  46058. *
  46059. * The url is built based upon the action being executed [create|read|update|destroy] using the commensurate
  46060. * {@link #api} property, or if undefined default to the configured
  46061. * {@link Ext.data.Store}.{@link Ext.data.proxy.Server#url url}.
  46062. *
  46063. * For example:
  46064. *
  46065. * api: {
  46066. * create : '/controller/new',
  46067. * read : '/controller/load',
  46068. * update : '/controller/update',
  46069. * destroy : '/controller/destroy_action'
  46070. * }
  46071. *
  46072. * If the specific URL for a given CRUD action is undefined, the CRUD action request will be directed to the
  46073. * configured {@link Ext.data.proxy.Server#url url}.
  46074. */
  46075. api: {
  46076. create : undefined,
  46077. read : undefined,
  46078. update : undefined,
  46079. destroy : undefined
  46080. },
  46081. /**
  46082. * @cfg {Object} extraParams
  46083. * Extra parameters that will be included on every request. Individual requests with params of the same name
  46084. * will override these params when they are in conflict.
  46085. */
  46086. extraParams: {}
  46087. },
  46088. constructor: function(config) {
  46089. config = config || {};
  46090. if (config.nocache !== undefined) {
  46091. config.noCache = config.nocache;
  46092. // <debug>
  46093. Ext.Logger.warn('nocache configuration on Ext.data.proxy.Server has been deprecated. Please use noCache.');
  46094. // </debug>
  46095. }
  46096. this.callParent([config]);
  46097. },
  46098. //in a ServerProxy all four CRUD operations are executed in the same manner, so we delegate to doRequest in each case
  46099. create: function() {
  46100. return this.doRequest.apply(this, arguments);
  46101. },
  46102. read: function() {
  46103. return this.doRequest.apply(this, arguments);
  46104. },
  46105. update: function() {
  46106. return this.doRequest.apply(this, arguments);
  46107. },
  46108. destroy: function() {
  46109. return this.doRequest.apply(this, arguments);
  46110. },
  46111. /**
  46112. * Sets a value in the underlying {@link #extraParams}.
  46113. * @param {String} name The key for the new value
  46114. * @param {Object} value The value
  46115. */
  46116. setExtraParam: function(name, value) {
  46117. this.getExtraParams()[name] = value;
  46118. },
  46119. /**
  46120. * Creates and returns an Ext.data.Request object based on the options passed by the {@link Ext.data.Store Store}
  46121. * that this Proxy is attached to.
  46122. * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
  46123. * @return {Ext.data.Request} The request object
  46124. */
  46125. buildRequest: function(operation) {
  46126. var me = this,
  46127. params = Ext.applyIf(operation.getParams() || {}, me.getExtraParams() || {}),
  46128. request;
  46129. //copy any sorters, filters etc into the params so they can be sent over the wire
  46130. params = Ext.applyIf(params, me.getParams(operation));
  46131. request = Ext.create('Ext.data.Request', {
  46132. params : params,
  46133. action : operation.getAction(),
  46134. records : operation.getRecords(),
  46135. url : operation.getUrl(),
  46136. operation: operation,
  46137. proxy : me
  46138. });
  46139. request.setUrl(me.buildUrl(request));
  46140. operation.setRequest(request);
  46141. return request;
  46142. },
  46143. /**
  46144. * This method handles the processing of the response and is usually overridden by subclasses to
  46145. * do additional processing.
  46146. * @param {Boolean} success Whether or not this request was successful
  46147. * @param {Ext.data.Operation} operation The operation we made this request for
  46148. * @param {Ext.data.Request} request The request that was made
  46149. * @param {Object} response The response that we got
  46150. * @param {Function} callback The callback to be fired onces the response is processed
  46151. * @param {Object} scope The scope in which we call the callback
  46152. * @protected
  46153. */
  46154. processResponse: function(success, operation, request, response, callback, scope) {
  46155. var me = this,
  46156. action = operation.getAction(),
  46157. reader, resultSet;
  46158. if (success === true) {
  46159. reader = me.getReader();
  46160. try {
  46161. resultSet = reader.process(response);
  46162. } catch(e) {
  46163. operation.setException(e.message);
  46164. me.fireEvent('exception', this, response, operation);
  46165. return;
  46166. }
  46167. // This could happen if the model was configured using metaData
  46168. if (!operation.getModel()) {
  46169. operation.setModel(this.getModel());
  46170. }
  46171. if (operation.process(action, resultSet, request, response) === false) {
  46172. this.fireEvent('exception', this, response, operation);
  46173. }
  46174. } else {
  46175. me.setException(operation, response);
  46176. /**
  46177. * @event exception
  46178. * Fires when the server returns an exception
  46179. * @param {Ext.data.proxy.Proxy} this
  46180. * @param {Object} response The response from the AJAX request
  46181. * @param {Ext.data.Operation} operation The operation that triggered request
  46182. */
  46183. me.fireEvent('exception', this, response, operation);
  46184. }
  46185. //this callback is the one that was passed to the 'read' or 'write' function above
  46186. if (typeof callback == 'function') {
  46187. callback.call(scope || me, operation);
  46188. }
  46189. me.afterRequest(request, success);
  46190. },
  46191. /**
  46192. * Sets up an exception on the operation
  46193. * @private
  46194. * @param {Ext.data.Operation} operation The operation
  46195. * @param {Object} response The response
  46196. */
  46197. setException: function(operation, response) {
  46198. if (Ext.isObject(response)) {
  46199. operation.setException({
  46200. status: response.status,
  46201. statusText: response.statusText
  46202. });
  46203. }
  46204. },
  46205. /**
  46206. * Encode any values being sent to the server. Can be overridden in subclasses.
  46207. * @private
  46208. * @param {Array} value An array of sorters/filters.
  46209. * @return {Object} The encoded value
  46210. */
  46211. applyEncoding: function(value) {
  46212. return Ext.encode(value);
  46213. },
  46214. /**
  46215. * Encodes the array of {@link Ext.util.Sorter} objects into a string to be sent in the request url. By default,
  46216. * this simply JSON-encodes the sorter data
  46217. * @param {Ext.util.Sorter[]} sorters The array of {@link Ext.util.Sorter Sorter} objects
  46218. * @return {String} The encoded sorters
  46219. */
  46220. encodeSorters: function(sorters) {
  46221. var min = [],
  46222. length = sorters.length,
  46223. i = 0;
  46224. for (; i < length; i++) {
  46225. min[i] = {
  46226. property : sorters[i].getProperty(),
  46227. direction: sorters[i].getDirection()
  46228. };
  46229. }
  46230. return this.applyEncoding(min);
  46231. },
  46232. /**
  46233. * Encodes the array of {@link Ext.util.Filter} objects into a string to be sent in the request url. By default,
  46234. * this simply JSON-encodes the filter data
  46235. * @param {Ext.util.Filter[]} filters The array of {@link Ext.util.Filter Filter} objects
  46236. * @return {String} The encoded filters
  46237. */
  46238. encodeFilters: function(filters) {
  46239. var min = [],
  46240. length = filters.length,
  46241. i = 0;
  46242. for (; i < length; i++) {
  46243. min[i] = {
  46244. property: filters[i].getProperty(),
  46245. value : filters[i].getValue()
  46246. };
  46247. }
  46248. return this.applyEncoding(min);
  46249. },
  46250. /**
  46251. * @private
  46252. * Copy any sorters, filters etc into the params so they can be sent over the wire
  46253. */
  46254. getParams: function(operation) {
  46255. var me = this,
  46256. params = {},
  46257. grouper = operation.getGrouper(),
  46258. sorters = operation.getSorters(),
  46259. filters = operation.getFilters(),
  46260. page = operation.getPage(),
  46261. start = operation.getStart(),
  46262. limit = operation.getLimit(),
  46263. simpleSortMode = me.getSimpleSortMode(),
  46264. pageParam = me.getPageParam(),
  46265. startParam = me.getStartParam(),
  46266. limitParam = me.getLimitParam(),
  46267. groupParam = me.getGroupParam(),
  46268. sortParam = me.getSortParam(),
  46269. filterParam = me.getFilterParam(),
  46270. directionParam = me.getDirectionParam();
  46271. if (me.getEnablePagingParams()) {
  46272. if (pageParam && page !== null) {
  46273. params[pageParam] = page;
  46274. }
  46275. if (startParam && start !== null) {
  46276. params[startParam] = start;
  46277. }
  46278. if (limitParam && limit !== null) {
  46279. params[limitParam] = limit;
  46280. }
  46281. }
  46282. if (groupParam && grouper) {
  46283. // Grouper is a subclass of sorter, so we can just use the sorter method
  46284. params[groupParam] = me.encodeSorters([grouper]);
  46285. }
  46286. if (sortParam && sorters && sorters.length > 0) {
  46287. if (simpleSortMode) {
  46288. params[sortParam] = sorters[0].getProperty();
  46289. params[directionParam] = sorters[0].getDirection();
  46290. } else {
  46291. params[sortParam] = me.encodeSorters(sorters);
  46292. }
  46293. }
  46294. if (filterParam && filters && filters.length > 0) {
  46295. params[filterParam] = me.encodeFilters(filters);
  46296. }
  46297. return params;
  46298. },
  46299. /**
  46300. * Generates a url based on a given Ext.data.Request object. By default, ServerProxy's buildUrl will add the
  46301. * cache-buster param to the end of the url. Subclasses may need to perform additional modifications to the url.
  46302. * @param {Ext.data.Request} request The request object
  46303. * @return {String} The url
  46304. */
  46305. buildUrl: function(request) {
  46306. var me = this,
  46307. url = me.getUrl(request);
  46308. //<debug>
  46309. if (!url) {
  46310. Ext.Logger.error("You are using a ServerProxy but have not supplied it with a url.");
  46311. }
  46312. //</debug>
  46313. if (me.getNoCache()) {
  46314. url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.getCacheString(), Ext.Date.now()));
  46315. }
  46316. return url;
  46317. },
  46318. /**
  46319. * Get the url for the request taking into account the order of priority,
  46320. * - The request
  46321. * - The api
  46322. * - The url
  46323. * @private
  46324. * @param {Ext.data.Request} request The request
  46325. * @return {String} The url
  46326. */
  46327. getUrl: function(request) {
  46328. return request ? request.getUrl() || this.getApi()[request.getAction()] || this._url : this._url;
  46329. },
  46330. /**
  46331. * In ServerProxy subclasses, the {@link #create}, {@link #read}, {@link #update} and {@link #destroy} methods all
  46332. * pass through to doRequest. Each ServerProxy subclass must implement the doRequest method - see {@link
  46333. * Ext.data.proxy.JsonP} and {@link Ext.data.proxy.Ajax} for examples. This method carries the same signature as
  46334. * each of the methods that delegate to it.
  46335. *
  46336. * @param {Ext.data.Operation} operation The Ext.data.Operation object
  46337. * @param {Function} callback The callback function to call when the Operation has completed
  46338. * @param {Object} scope The scope in which to execute the callback
  46339. * @protected
  46340. * @template
  46341. */
  46342. doRequest: function(operation, callback, scope) {
  46343. //<debug>
  46344. Ext.Logger.error("The doRequest function has not been implemented on your Ext.data.proxy.Server subclass. See src/data/ServerProxy.js for details");
  46345. //</debug>
  46346. },
  46347. /**
  46348. * Optional callback function which can be used to clean up after a request has been completed.
  46349. * @param {Ext.data.Request} request The Request object
  46350. * @param {Boolean} success True if the request was successful
  46351. * @method
  46352. */
  46353. afterRequest: Ext.emptyFn
  46354. });
  46355. /**
  46356. * @author Ed Spencer
  46357. * @aside guide proxies
  46358. *
  46359. * AjaxProxy is one of the most widely-used ways of getting data into your application. It uses AJAX
  46360. * requests to load data from the server, usually to be placed into a {@link Ext.data.Store Store}.
  46361. * Let's take a look at a typical setup. Here we're going to set up a Store that has an AjaxProxy.
  46362. * To prepare, we'll also set up a {@link Ext.data.Model Model}:
  46363. *
  46364. * Ext.define('User', {
  46365. * extend: 'Ext.data.Model',
  46366. * config: {
  46367. * fields: ['id', 'name', 'email']
  46368. * }
  46369. * });
  46370. *
  46371. * // The Store contains the AjaxProxy as an inline configuration
  46372. * var store = Ext.create('Ext.data.Store', {
  46373. * model: 'User',
  46374. * proxy: {
  46375. * type: 'ajax',
  46376. * url : 'users.json'
  46377. * }
  46378. * });
  46379. *
  46380. * store.load();
  46381. *
  46382. * Our example is going to load user data into a Store, so we start off by defining a
  46383. * {@link Ext.data.Model Model} with the fields that we expect the server to return. Next we set up
  46384. * the Store itself, along with a {@link Ext.data.Store#proxy proxy} configuration. This
  46385. * configuration was automatically turned into an Ext.data.proxy.Ajax instance, with the url we
  46386. * specified being passed into AjaxProxy's constructor. It's as if we'd done this:
  46387. *
  46388. * Ext.create('Ext.data.proxy.Ajax', {
  46389. * config: {
  46390. * url: 'users.json',
  46391. * model: 'User',
  46392. * reader: 'json'
  46393. * }
  46394. * });
  46395. *
  46396. * A couple of extra configurations appeared here - {@link #model} and {@link #reader}. These are
  46397. * set by default when we create the proxy via the Store - the Store already knows about the Model,
  46398. * and Proxy's default {@link Ext.data.reader.Reader Reader} is {@link Ext.data.reader.Json JsonReader}.
  46399. *
  46400. * Now when we call store.load(), the AjaxProxy springs into action, making a request to the url we
  46401. * configured ('users.json' in this case). As we're performing a read, it sends a GET request to
  46402. * that url (see {@link #actionMethods} to customize this - by default any kind of read will be sent
  46403. * as a GET request and any kind of write will be sent as a POST request).
  46404. *
  46405. * ## Limitations
  46406. *
  46407. * AjaxProxy cannot be used to retrieve data from other domains. If your application is running on
  46408. * http://domainA.com it cannot load data from http://domainB.com because browsers have a built-in
  46409. * security policy that prohibits domains talking to each other via AJAX.
  46410. *
  46411. * If you need to read data from another domain and can't set up a proxy server (some software that
  46412. * runs on your own domain's web server and transparently forwards requests to http://domainB.com,
  46413. * making it look like they actually came from http://domainA.com), you can use
  46414. * {@link Ext.data.proxy.JsonP} and a technique known as JSON-P (JSON with Padding), which can help
  46415. * you get around the problem so long as the server on http://domainB.com is set up to support
  46416. * JSON-P responses. See {@link Ext.data.proxy.JsonP JsonPProxy}'s introduction docs for more details.
  46417. *
  46418. * ## Readers and Writers
  46419. *
  46420. * AjaxProxy can be configured to use any type of {@link Ext.data.reader.Reader Reader} to decode
  46421. * the server's response. If no Reader is supplied, AjaxProxy will default to using a
  46422. * {@link Ext.data.reader.Json JsonReader}. Reader configuration can be passed in as a simple
  46423. * object, which the Proxy automatically turns into a {@link Ext.data.reader.Reader Reader} instance:
  46424. *
  46425. * var proxy = Ext.create('Ext.data.proxy.Ajax', {
  46426. * config: {
  46427. * model: 'User',
  46428. * reader: {
  46429. * type: 'xml',
  46430. * root: 'users'
  46431. * }
  46432. * }
  46433. * });
  46434. *
  46435. * proxy.getReader(); //returns an {@link Ext.data.reader.Xml XmlReader} instance based on the config we supplied
  46436. *
  46437. * ## Url generation
  46438. *
  46439. * AjaxProxy automatically inserts any sorting, filtering, paging and grouping options into the url
  46440. * it generates for each request. These are controlled with the following configuration options:
  46441. *
  46442. * - {@link #pageParam} - controls how the page number is sent to the server (see also
  46443. * {@link #startParam} and {@link #limitParam})
  46444. * - {@link #sortParam} - controls how sort information is sent to the server
  46445. * - {@link #groupParam} - controls how grouping information is sent to the server
  46446. * - {@link #filterParam} - controls how filter information is sent to the server
  46447. *
  46448. * Each request sent by AjaxProxy is described by an {@link Ext.data.Operation Operation}. To see
  46449. * how we can customize the generated urls, let's say we're loading the Proxy with the following
  46450. * Operation:
  46451. *
  46452. * var operation = Ext.create('Ext.data.Operation', {
  46453. * action: 'read',
  46454. * page : 2
  46455. * });
  46456. *
  46457. * Now we'll issue the request for this Operation by calling {@link #read}:
  46458. *
  46459. * var proxy = Ext.create('Ext.data.proxy.Ajax', {
  46460. * url: '/users'
  46461. * });
  46462. *
  46463. * proxy.read(operation); // GET /users?page=2
  46464. *
  46465. * Easy enough - the Proxy just copied the page property from the Operation. We can customize how
  46466. * this page data is sent to the server:
  46467. *
  46468. * var proxy = Ext.create('Ext.data.proxy.Ajax', {
  46469. * url: '/users',
  46470. * pageParam: 'pageNumber'
  46471. * });
  46472. *
  46473. * proxy.read(operation); // GET /users?pageNumber=2
  46474. *
  46475. * Alternatively, our Operation could have been configured to send start and limit parameters
  46476. * instead of page:
  46477. *
  46478. * var operation = Ext.create('Ext.data.Operation', {
  46479. * action: 'read',
  46480. * start : 50,
  46481. * limit : 25
  46482. * });
  46483. *
  46484. * var proxy = Ext.create('Ext.data.proxy.Ajax', {
  46485. * url: '/users'
  46486. * });
  46487. *
  46488. * proxy.read(operation); // GET /users?start=50&limit;=25
  46489. *
  46490. * Again we can customize this url:
  46491. *
  46492. * var proxy = Ext.create('Ext.data.proxy.Ajax', {
  46493. * url: '/users',
  46494. * startParam: 'startIndex',
  46495. * limitParam: 'limitIndex'
  46496. * });
  46497. *
  46498. * proxy.read(operation); // GET /users?startIndex=50&limitIndex;=25
  46499. *
  46500. * AjaxProxy will also send sort and filter information to the server. Let's take a look at how this
  46501. * looks with a more expressive Operation object:
  46502. *
  46503. * var operation = Ext.create('Ext.data.Operation', {
  46504. * action: 'read',
  46505. * sorters: [
  46506. * Ext.create('Ext.util.Sorter', {
  46507. * property : 'name',
  46508. * direction: 'ASC'
  46509. * }),
  46510. * Ext.create('Ext.util.Sorter', {
  46511. * property : 'age',
  46512. * direction: 'DESC'
  46513. * })
  46514. * ],
  46515. * filters: [
  46516. * Ext.create('Ext.util.Filter', {
  46517. * property: 'eyeColor',
  46518. * value : 'brown'
  46519. * })
  46520. * ]
  46521. * });
  46522. *
  46523. * This is the type of object that is generated internally when loading a {@link Ext.data.Store Store}
  46524. * with sorters and filters defined. By default the AjaxProxy will JSON encode the sorters and
  46525. * filters, resulting in something like this (note that the url is escaped before sending the
  46526. * request, but is left unescaped here for clarity):
  46527. *
  46528. * var proxy = Ext.create('Ext.data.proxy.Ajax', {
  46529. * url: '/users'
  46530. * });
  46531. *
  46532. * proxy.read(operation); // GET /users?sort=[{"property":"name","direction":"ASC"},{"property":"age","direction":"DESC"}]&filter;=[{"property":"eyeColor","value":"brown"}]
  46533. *
  46534. * We can again customize how this is created by supplying a few configuration options. Let's say
  46535. * our server is set up to receive sorting information is a format like "sortBy=name#ASC,age#DESC".
  46536. * We can configure AjaxProxy to provide that format like this:
  46537. *
  46538. * var proxy = Ext.create('Ext.data.proxy.Ajax', {
  46539. * url: '/users',
  46540. * sortParam: 'sortBy',
  46541. * filterParam: 'filterBy',
  46542. *
  46543. * // our custom implementation of sorter encoding - turns our sorters into "name#ASC,age#DESC"
  46544. * encodeSorters: function(sorters) {
  46545. * var length = sorters.length,
  46546. * sortStrs = [],
  46547. * sorter, i;
  46548. *
  46549. * for (i = 0; i < length; i++) {
  46550. * sorter = sorters[i];
  46551. *
  46552. * sortStrs[i] = sorter.property + '#' + sorter.direction;
  46553. * }
  46554. *
  46555. * return sortStrs.join(",");
  46556. * }
  46557. * });
  46558. *
  46559. * proxy.read(operation); // GET /users?sortBy=name#ASC,age#DESC&filterBy;=[{"property":"eyeColor","value":"brown"}]
  46560. *
  46561. * We can also provide a custom {@link #encodeFilters} function to encode our filters.
  46562. *
  46563. * @constructor
  46564. * Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the Store's
  46565. * call to {@link Ext.data.Store#method-load load} will override any specified callback and params
  46566. * options. In this case, use the {@link Ext.data.Store Store}'s events to modify parameters, or
  46567. * react to loading events.
  46568. *
  46569. * @param {Object} config (optional) Config object.
  46570. * If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to
  46571. * make the request.
  46572. */
  46573. Ext.define('Ext.data.proxy.Ajax', {
  46574. extend: 'Ext.data.proxy.Server',
  46575. requires: ['Ext.util.MixedCollection', 'Ext.Ajax'],
  46576. alias: 'proxy.ajax',
  46577. alternateClassName: ['Ext.data.HttpProxy', 'Ext.data.AjaxProxy'],
  46578. config: {
  46579. /**
  46580. * @cfg {Boolean} withCredentials
  46581. * This configuration is sometimes necessary when using cross-origin resource sharing.
  46582. * @accessor
  46583. */
  46584. withCredentials: false,
  46585. /**
  46586. * @cfg {String} username
  46587. * Most oData feeds require basic HTTP authentication. This configuration allows
  46588. * you to specify the username.
  46589. * @accessor
  46590. */
  46591. username: null,
  46592. /**
  46593. * @cfg {String} password
  46594. * Most oData feeds require basic HTTP authentication. This configuration allows
  46595. * you to specify the password.
  46596. * @accessor
  46597. */
  46598. password: null,
  46599. /**
  46600. * @property {Object} actionMethods
  46601. * Mapping of action name to HTTP request method. In the basic AjaxProxy these are set to
  46602. * 'GET' for 'read' actions and 'POST' for 'create', 'update' and 'destroy' actions.
  46603. * The {@link Ext.data.proxy.Rest} maps these to the correct RESTful methods.
  46604. */
  46605. actionMethods: {
  46606. create : 'POST',
  46607. read : 'GET',
  46608. update : 'POST',
  46609. destroy: 'POST'
  46610. },
  46611. /**
  46612. * @cfg {Object} [headers=undefined]
  46613. * Any headers to add to the Ajax request.
  46614. */
  46615. headers: {}
  46616. },
  46617. /**
  46618. * Performs Ajax request.
  46619. * @protected
  46620. * @param operation
  46621. * @param callback
  46622. * @param scope
  46623. * @return {Object}
  46624. */
  46625. doRequest: function(operation, callback, scope) {
  46626. var me = this,
  46627. writer = me.getWriter(),
  46628. request = me.buildRequest(operation);
  46629. request.setConfig({
  46630. headers : me.getHeaders(),
  46631. timeout : me.getTimeout(),
  46632. method : me.getMethod(request),
  46633. callback : me.createRequestCallback(request, operation, callback, scope),
  46634. scope : me,
  46635. proxy : me
  46636. });
  46637. if (operation.getWithCredentials() || me.getWithCredentials()) {
  46638. request.setWithCredentials(true);
  46639. request.setUsername(me.getUsername());
  46640. request.setPassword(me.getPassword());
  46641. }
  46642. // We now always have the writer prepare the request
  46643. request = writer.write(request);
  46644. Ext.Ajax.request(request.getCurrentConfig());
  46645. return request;
  46646. },
  46647. /**
  46648. * Returns the HTTP method name for a given request. By default this returns based on a lookup on
  46649. * {@link #actionMethods}.
  46650. * @param {Ext.data.Request} request The request object.
  46651. * @return {String} The HTTP method to use (should be one of 'GET', 'POST', 'PUT' or 'DELETE').
  46652. */
  46653. getMethod: function(request) {
  46654. return this.getActionMethods()[request.getAction()];
  46655. },
  46656. /**
  46657. * @private
  46658. * @param {Ext.data.Request} request The Request object.
  46659. * @param {Ext.data.Operation} operation The Operation being executed.
  46660. * @param {Function} callback The callback function to be called when the request completes.
  46661. * This is usually the callback passed to `doRequest`.
  46662. * @param {Object} scope The scope in which to execute the callback function.
  46663. * @return {Function} The callback function.
  46664. */
  46665. createRequestCallback: function(request, operation, callback, scope) {
  46666. var me = this;
  46667. return function(options, success, response) {
  46668. me.processResponse(success, operation, request, response, callback, scope);
  46669. };
  46670. }
  46671. });
  46672. /**
  46673. * @author Ed Spencer
  46674. * @aside guide models
  46675. *
  46676. * Associations enable you to express relationships between different {@link Ext.data.Model Models}. Let's say we're
  46677. * writing an ecommerce system where Users can make Orders - there's a relationship between these Models that we can
  46678. * express like this:
  46679. *
  46680. * Ext.define('MyApp.model.User', {
  46681. * extend: 'Ext.data.Model',
  46682. *
  46683. * config: {
  46684. * fields: ['id', 'name', 'email'],
  46685. * hasMany: {
  46686. * model: 'MyApp.model.Order',
  46687. * name: 'orders'
  46688. * }
  46689. * }
  46690. * });
  46691. *
  46692. * Ext.define('MyApp.model.Order', {
  46693. * extend: 'Ext.data.Model',
  46694. *
  46695. * config: {
  46696. * fields: ['id', 'user_id', 'status', 'price'],
  46697. * belongsTo: 'MyApp.model.User'
  46698. * }
  46699. * });
  46700. *
  46701. * We've set up two models - User and Order - and told them about each other. You can set up as many associations on
  46702. * each Model as you need using the two default types - {@link Ext.data.association.HasMany hasMany} and
  46703. * {@link Ext.data.association.BelongsTo belongsTo}. There's much more detail on the usage of each of those inside their
  46704. * documentation pages. If you're not familiar with Models already, {@link Ext.data.Model there is plenty on those too}.
  46705. *
  46706. * ## Further Reading
  46707. *
  46708. * - {@link Ext.data.association.HasMany hasMany associations}
  46709. * - {@link Ext.data.association.BelongsTo belongsTo associations}
  46710. * - {@link Ext.data.association.HasOne hasOne associations}
  46711. * - {@link Ext.data.Model using Models}
  46712. *
  46713. * ### Self-associating Models
  46714. *
  46715. * We can also have models that create parent/child associations between the same type. Below is an example, where
  46716. * groups can be nested inside other groups:
  46717. *
  46718. * // Server Data
  46719. * {
  46720. * "groups": {
  46721. * "id": 10,
  46722. * "parent_id": 100,
  46723. * "name": "Main Group",
  46724. * "parent_group": {
  46725. * "id": 100,
  46726. * "parent_id": null,
  46727. * "name": "Parent Group"
  46728. * },
  46729. * "nested" : {
  46730. * "child_groups": [{
  46731. * "id": 2,
  46732. * "parent_id": 10,
  46733. * "name": "Child Group 1"
  46734. * },{
  46735. * "id": 3,
  46736. * "parent_id": 10,
  46737. * "name": "Child Group 2"
  46738. * },{
  46739. * "id": 4,
  46740. * "parent_id": 10,
  46741. * "name": "Child Group 3"
  46742. * }]
  46743. * }
  46744. * }
  46745. * }
  46746. *
  46747. * // Client code
  46748. * Ext.define('MyApp.model.Group', {
  46749. * extend: 'Ext.data.Model',
  46750. * config: {
  46751. * fields: ['id', 'parent_id', 'name'],
  46752. * proxy: {
  46753. * type: 'ajax',
  46754. * url: 'data.json',
  46755. * reader: {
  46756. * type: 'json',
  46757. * root: 'groups'
  46758. * }
  46759. * },
  46760. * associations: [{
  46761. * type: 'hasMany',
  46762. * model: 'MyApp.model.Group',
  46763. * primaryKey: 'id',
  46764. * foreignKey: 'parent_id',
  46765. * autoLoad: true,
  46766. * associationKey: 'nested.child_groups' // read child data from nested.child_groups
  46767. * }, {
  46768. * type: 'belongsTo',
  46769. * model: 'MyApp.model.Group',
  46770. * primaryKey: 'id',
  46771. * foreignKey: 'parent_id',
  46772. * associationKey: 'parent_group' // read parent data from parent_group
  46773. * }]
  46774. * }
  46775. * });
  46776. *
  46777. *
  46778. * Ext.onReady(function(){
  46779. * MyApp.model.Group.load(10, {
  46780. * success: function(group){
  46781. * console.log(group.getGroup().get('name'));
  46782. *
  46783. * group.groups().each(function(rec){
  46784. * console.log(rec.get('name'));
  46785. * });
  46786. * }
  46787. * });
  46788. *
  46789. * });
  46790. */
  46791. Ext.define('Ext.data.association.Association', {
  46792. alternateClassName: 'Ext.data.Association',
  46793. requires: ['Ext.data.ModelManager'],
  46794. config: {
  46795. /**
  46796. * @cfg {Ext.data.Model/String} ownerModel (required) The full class name or reference to the class that owns this
  46797. * associations. This is a required configuration on every association.
  46798. * @accessor
  46799. */
  46800. ownerModel: null,
  46801. /*
  46802. * @cfg {String} ownerName The name for the owner model. This defaults to the last part
  46803. * of the class name of the {@link #ownerModel}.
  46804. */
  46805. ownerName: undefined,
  46806. /**
  46807. * @cfg {String} associatedModel (required) The full class name or reference to the class that the {@link #ownerModel}
  46808. * is being associated with. This is a required configuration on every association.
  46809. * @accessor
  46810. */
  46811. associatedModel: null,
  46812. /**
  46813. * @cfg {String} associatedName The name for the associated model. This defaults to the last part
  46814. * of the class name of the {@link #associatedModel}.
  46815. * @accessor
  46816. */
  46817. associatedName: undefined,
  46818. /**
  46819. * @cfg {String} associationKey The name of the property in the data to read the association from.
  46820. * Defaults to the {@link #associatedName} plus '_id'.
  46821. */
  46822. associationKey: undefined,
  46823. /**
  46824. * @cfg {String} primaryKey The name of the primary key on the associated model.
  46825. * In general this will be the {@link Ext.data.Model#idProperty} of the Model.
  46826. */
  46827. primaryKey: 'id',
  46828. /**
  46829. * @cfg {Ext.data.reader.Reader} reader A special reader to read associated data.
  46830. */
  46831. reader: null,
  46832. /**
  46833. * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
  46834. * Use `hasMany` to create a HasMany association.
  46835. *
  46836. * associations: [{
  46837. * type: 'hasMany',
  46838. * model: 'User'
  46839. * }]
  46840. */
  46841. type: null,
  46842. name: undefined
  46843. },
  46844. statics: {
  46845. create: function(association) {
  46846. if (!association.isAssociation) {
  46847. if (Ext.isString(association)) {
  46848. association = {
  46849. type: association
  46850. };
  46851. }
  46852. association.type = association.type.toLowerCase();
  46853. return Ext.factory(association, Ext.data.association.Association, null, 'association');
  46854. }
  46855. return association;
  46856. }
  46857. },
  46858. /**
  46859. * Creates the Association object.
  46860. * @param {Object} config (optional) Config object.
  46861. */
  46862. constructor: function(config) {
  46863. this.initConfig(config);
  46864. },
  46865. applyName: function(name) {
  46866. if (!name) {
  46867. name = this.getAssociatedName();
  46868. }
  46869. return name;
  46870. },
  46871. applyOwnerModel: function(ownerName) {
  46872. var ownerModel = Ext.data.ModelManager.getModel(ownerName);
  46873. if (ownerModel === undefined) {
  46874. Ext.Logger.error('The configured ownerModel was not valid (you tried ' + ownerName + ')');
  46875. }
  46876. return ownerModel;
  46877. },
  46878. applyOwnerName: function(ownerName) {
  46879. if (!ownerName) {
  46880. ownerName = this.getOwnerModel().modelName;
  46881. }
  46882. ownerName = ownerName.slice(ownerName.lastIndexOf('.')+1);
  46883. return ownerName;
  46884. },
  46885. updateOwnerModel: function(ownerModel, oldOwnerModel) {
  46886. if (oldOwnerModel) {
  46887. this.setOwnerName(ownerModel.modelName);
  46888. }
  46889. },
  46890. applyAssociatedModel: function(associatedName) {
  46891. var associatedModel = Ext.data.ModelManager.types[associatedName];
  46892. if (associatedModel === undefined) {
  46893. Ext.Logger.error('The configured associatedModel was not valid (you tried ' + associatedName + ')');
  46894. }
  46895. return associatedModel;
  46896. },
  46897. applyAssociatedName: function(associatedName) {
  46898. if (!associatedName) {
  46899. associatedName = this.getAssociatedModel().modelName;
  46900. }
  46901. associatedName = associatedName.slice(associatedName.lastIndexOf('.')+1);
  46902. return associatedName;
  46903. },
  46904. updateAssociatedModel: function(associatedModel, oldAssociatedModel) {
  46905. if (oldAssociatedModel) {
  46906. this.setAssociatedName(associatedModel.modelName);
  46907. }
  46908. },
  46909. applyReader: function(reader) {
  46910. if (reader) {
  46911. if (Ext.isString(reader)) {
  46912. reader = {
  46913. type: reader
  46914. };
  46915. }
  46916. if (!reader.isReader) {
  46917. Ext.applyIf(reader, {
  46918. type: 'json'
  46919. });
  46920. }
  46921. }
  46922. return Ext.factory(reader, Ext.data.Reader, this.getReader(), 'reader');
  46923. },
  46924. updateReader: function(reader) {
  46925. reader.setModel(this.getAssociatedModel());
  46926. }
  46927. // Convert old properties in data into a config object
  46928. });
  46929. /**
  46930. * General purpose inflector class that {@link #pluralize pluralizes}, {@link #singularize singularizes} and
  46931. * {@link #ordinalize ordinalizes} words. Sample usage:
  46932. *
  46933. * // turning singular words into plurals
  46934. * Ext.util.Inflector.pluralize('word'); // 'words'
  46935. * Ext.util.Inflector.pluralize('person'); // 'people'
  46936. * Ext.util.Inflector.pluralize('sheep'); // 'sheep'
  46937. *
  46938. * // turning plurals into singulars
  46939. * Ext.util.Inflector.singularize('words'); // 'word'
  46940. * Ext.util.Inflector.singularize('people'); // 'person'
  46941. * Ext.util.Inflector.singularize('sheep'); // 'sheep'
  46942. *
  46943. * // ordinalizing numbers
  46944. * Ext.util.Inflector.ordinalize(11); // "11th"
  46945. * Ext.util.Inflector.ordinalize(21); // "21st"
  46946. * Ext.util.Inflector.ordinalize(1043); // "1043rd"
  46947. *
  46948. * ## Customization
  46949. *
  46950. * The Inflector comes with a default set of US English pluralization rules. These can be augmented with additional
  46951. * rules if the default rules do not meet your application's requirements, or swapped out entirely for other languages.
  46952. * Here is how we might add a rule that pluralizes "ox" to "oxen":
  46953. *
  46954. * Ext.util.Inflector.plural(/^(ox)$/i, "$1en");
  46955. *
  46956. * Each rule consists of two items - a regular expression that matches one or more rules, and a replacement string.
  46957. * In this case, the regular expression will only match the string "ox", and will replace that match with "oxen".
  46958. * Here's how we could add the inverse rule:
  46959. *
  46960. * Ext.util.Inflector.singular(/^(ox)en$/i, "$1");
  46961. *
  46962. * __Note:__ The ox/oxen rules are present by default.
  46963. */
  46964. Ext.define('Ext.util.Inflector', {
  46965. /* Begin Definitions */
  46966. singleton: true,
  46967. /* End Definitions */
  46968. /**
  46969. * @private
  46970. * The registered plural tuples. Each item in the array should contain two items - the first must be a regular
  46971. * expression that matchers the singular form of a word, the second must be a String that replaces the matched
  46972. * part of the regular expression. This is managed by the {@link #plural} method.
  46973. * @property plurals
  46974. * @type Array
  46975. */
  46976. plurals: [
  46977. [(/(quiz)$/i), "$1zes" ],
  46978. [(/^(ox)$/i), "$1en" ],
  46979. [(/([m|l])ouse$/i), "$1ice" ],
  46980. [(/(matr|vert|ind)ix|ex$/i), "$1ices" ],
  46981. [(/(x|ch|ss|sh)$/i), "$1es" ],
  46982. [(/([^aeiouy]|qu)y$/i), "$1ies" ],
  46983. [(/(hive)$/i), "$1s" ],
  46984. [(/(?:([^f])fe|([lr])f)$/i), "$1$2ves"],
  46985. [(/sis$/i), "ses" ],
  46986. [(/([ti])um$/i), "$1a" ],
  46987. [(/(buffal|tomat|potat)o$/i), "$1oes" ],
  46988. [(/(bu)s$/i), "$1ses" ],
  46989. [(/(alias|status|sex)$/i), "$1es" ],
  46990. [(/(octop|vir)us$/i), "$1i" ],
  46991. [(/(ax|test)is$/i), "$1es" ],
  46992. [(/^person$/), "people" ],
  46993. [(/^man$/), "men" ],
  46994. [(/^(child)$/), "$1ren" ],
  46995. [(/s$/i), "s" ],
  46996. [(/$/), "s" ]
  46997. ],
  46998. /**
  46999. * @private
  47000. * The set of registered singular matchers. Each item in the array should contain two items - the first must be a
  47001. * regular expression that matches the plural form of a word, the second must be a String that replaces the
  47002. * matched part of the regular expression. This is managed by the {@link #singular} method.
  47003. * @property singulars
  47004. * @type Array
  47005. */
  47006. singulars: [
  47007. [(/(quiz)zes$/i), "$1" ],
  47008. [(/(matr)ices$/i), "$1ix" ],
  47009. [(/(vert|ind)ices$/i), "$1ex" ],
  47010. [(/^(ox)en/i), "$1" ],
  47011. [(/(alias|status)es$/i), "$1" ],
  47012. [(/(octop|vir)i$/i), "$1us" ],
  47013. [(/(cris|ax|test)es$/i), "$1is" ],
  47014. [(/(shoe)s$/i), "$1" ],
  47015. [(/(o)es$/i), "$1" ],
  47016. [(/(bus)es$/i), "$1" ],
  47017. [(/([m|l])ice$/i), "$1ouse" ],
  47018. [(/(x|ch|ss|sh)es$/i), "$1" ],
  47019. [(/(m)ovies$/i), "$1ovie" ],
  47020. [(/(s)eries$/i), "$1eries"],
  47021. [(/([^aeiouy]|qu)ies$/i), "$1y" ],
  47022. [(/([lr])ves$/i), "$1f" ],
  47023. [(/(tive)s$/i), "$1" ],
  47024. [(/(hive)s$/i), "$1" ],
  47025. [(/([^f])ves$/i), "$1fe" ],
  47026. [(/(^analy)ses$/i), "$1sis" ],
  47027. [(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i), "$1$2sis"],
  47028. [(/([ti])a$/i), "$1um" ],
  47029. [(/(n)ews$/i), "$1ews" ],
  47030. [(/people$/i), "person" ],
  47031. [(/s$/i), "" ]
  47032. ],
  47033. /**
  47034. * @private
  47035. * The registered uncountable words
  47036. * @property uncountable
  47037. * @type Array
  47038. */
  47039. uncountable: [
  47040. "sheep",
  47041. "fish",
  47042. "series",
  47043. "species",
  47044. "money",
  47045. "rice",
  47046. "information",
  47047. "equipment",
  47048. "grass",
  47049. "mud",
  47050. "offspring",
  47051. "deer",
  47052. "means"
  47053. ],
  47054. /**
  47055. * Adds a new singularization rule to the Inflector. See the intro docs for more information
  47056. * @param {RegExp} matcher The matcher regex
  47057. * @param {String} replacer The replacement string, which can reference matches from the matcher argument
  47058. */
  47059. singular: function(matcher, replacer) {
  47060. this.singulars.unshift([matcher, replacer]);
  47061. },
  47062. /**
  47063. * Adds a new pluralization rule to the Inflector. See the intro docs for more information
  47064. * @param {RegExp} matcher The matcher regex
  47065. * @param {String} replacer The replacement string, which can reference matches from the matcher argument
  47066. */
  47067. plural: function(matcher, replacer) {
  47068. this.plurals.unshift([matcher, replacer]);
  47069. },
  47070. /**
  47071. * Removes all registered singularization rules
  47072. */
  47073. clearSingulars: function() {
  47074. this.singulars = [];
  47075. },
  47076. /**
  47077. * Removes all registered pluralization rules
  47078. */
  47079. clearPlurals: function() {
  47080. this.plurals = [];
  47081. },
  47082. /**
  47083. * Returns true if the given word is transnumeral (the word is its own singular and plural form - e.g. sheep, fish)
  47084. * @param {String} word The word to test
  47085. * @return {Boolean} True if the word is transnumeral
  47086. */
  47087. isTransnumeral: function(word) {
  47088. return Ext.Array.indexOf(this.uncountable, word) != -1;
  47089. },
  47090. /**
  47091. * Returns the pluralized form of a word (e.g. Ext.util.Inflector.pluralize('word') returns 'words')
  47092. * @param {String} word The word to pluralize
  47093. * @return {String} The pluralized form of the word
  47094. */
  47095. pluralize: function(word) {
  47096. if (this.isTransnumeral(word)) {
  47097. return word;
  47098. }
  47099. var plurals = this.plurals,
  47100. length = plurals.length,
  47101. tuple, regex, i;
  47102. for (i = 0; i < length; i++) {
  47103. tuple = plurals[i];
  47104. regex = tuple[0];
  47105. if (regex == word || (regex.test && regex.test(word))) {
  47106. return word.replace(regex, tuple[1]);
  47107. }
  47108. }
  47109. return word;
  47110. },
  47111. /**
  47112. * Returns the singularized form of a word (e.g. Ext.util.Inflector.singularize('words') returns 'word')
  47113. * @param {String} word The word to singularize
  47114. * @return {String} The singularized form of the word
  47115. */
  47116. singularize: function(word) {
  47117. if (this.isTransnumeral(word)) {
  47118. return word;
  47119. }
  47120. var singulars = this.singulars,
  47121. length = singulars.length,
  47122. tuple, regex, i;
  47123. for (i = 0; i < length; i++) {
  47124. tuple = singulars[i];
  47125. regex = tuple[0];
  47126. if (regex == word || (regex.test && regex.test(word))) {
  47127. return word.replace(regex, tuple[1]);
  47128. }
  47129. }
  47130. return word;
  47131. },
  47132. /**
  47133. * Returns the correct {@link Ext.data.Model Model} name for a given string. Mostly used internally by the data
  47134. * package
  47135. * @param {String} word The word to classify
  47136. * @return {String} The classified version of the word
  47137. */
  47138. classify: function(word) {
  47139. return Ext.String.capitalize(this.singularize(word));
  47140. },
  47141. /**
  47142. * Ordinalizes a given number by adding a prefix such as 'st', 'nd', 'rd' or 'th' based on the last digit of the
  47143. * number. 21 -> 21st, 22 -> 22nd, 23 -> 23rd, 24 -> 24th etc
  47144. * @param {Number} number The number to ordinalize
  47145. * @return {String} The ordinalized number
  47146. */
  47147. ordinalize: function(number) {
  47148. var parsed = parseInt(number, 10),
  47149. mod10 = parsed % 10,
  47150. mod100 = parsed % 100;
  47151. //11 through 13 are a special case
  47152. if (11 <= mod100 && mod100 <= 13) {
  47153. return number + "th";
  47154. } else {
  47155. switch(mod10) {
  47156. case 1 : return number + "st";
  47157. case 2 : return number + "nd";
  47158. case 3 : return number + "rd";
  47159. default: return number + "th";
  47160. }
  47161. }
  47162. }
  47163. }, function() {
  47164. //aside from the rules above, there are a number of words that have irregular pluralization so we add them here
  47165. var irregulars = {
  47166. alumnus: 'alumni',
  47167. cactus : 'cacti',
  47168. focus : 'foci',
  47169. nucleus: 'nuclei',
  47170. radius: 'radii',
  47171. stimulus: 'stimuli',
  47172. ellipsis: 'ellipses',
  47173. paralysis: 'paralyses',
  47174. oasis: 'oases',
  47175. appendix: 'appendices',
  47176. index: 'indexes',
  47177. beau: 'beaux',
  47178. bureau: 'bureaux',
  47179. tableau: 'tableaux',
  47180. woman: 'women',
  47181. child: 'children',
  47182. man: 'men',
  47183. corpus: 'corpora',
  47184. criterion: 'criteria',
  47185. curriculum: 'curricula',
  47186. genus: 'genera',
  47187. memorandum: 'memoranda',
  47188. phenomenon: 'phenomena',
  47189. foot: 'feet',
  47190. goose: 'geese',
  47191. tooth: 'teeth',
  47192. antenna: 'antennae',
  47193. formula: 'formulae',
  47194. nebula: 'nebulae',
  47195. vertebra: 'vertebrae',
  47196. vita: 'vitae'
  47197. },
  47198. singular;
  47199. for (singular in irregulars) {
  47200. this.plural(singular, irregulars[singular]);
  47201. this.singular(irregulars[singular], singular);
  47202. }
  47203. });
  47204. /**
  47205. * @aside guide models
  47206. *
  47207. * Represents a one-to-many relationship between two models. Usually created indirectly via a model definition:
  47208. *
  47209. * Ext.define('Product', {
  47210. * extend: 'Ext.data.Model',
  47211. * config: {
  47212. * fields: [
  47213. * {name: 'id', type: 'int'},
  47214. * {name: 'user_id', type: 'int'},
  47215. * {name: 'name', type: 'string'}
  47216. * ]
  47217. * }
  47218. * });
  47219. *
  47220. * Ext.define('User', {
  47221. * extend: 'Ext.data.Model',
  47222. * config: {
  47223. * fields: [
  47224. * {name: 'id', type: 'int'},
  47225. * {name: 'name', type: 'string'}
  47226. * ],
  47227. * // we can use the hasMany shortcut on the model to create a hasMany association
  47228. * hasMany: {model: 'Product', name: 'products'}
  47229. * }
  47230. * });
  47231. *
  47232. * `
  47233. *
  47234. * Above we created Product and User models, and linked them by saying that a User hasMany Products. This gives us a new
  47235. * function on every User instance, in this case the function is called 'products' because that is the name we specified
  47236. * in the association configuration above.
  47237. *
  47238. * This new function returns a specialized {@link Ext.data.Store Store} which is automatically filtered to load only
  47239. * Products for the given model instance:
  47240. *
  47241. * //first, we load up a User with id of 1
  47242. * var user = Ext.create('User', {id: 1, name: 'Ed'});
  47243. *
  47244. * //the user.products function was created automatically by the association and returns a {@link Ext.data.Store Store}
  47245. * //the created store is automatically scoped to the set of Products for the User with id of 1
  47246. * var products = user.products();
  47247. *
  47248. * //we still have all of the usual Store functions, for example it's easy to add a Product for this User
  47249. * products.add({
  47250. * name: 'Another Product'
  47251. * });
  47252. *
  47253. * //saves the changes to the store - this automatically sets the new Product's user_id to 1 before saving
  47254. * products.sync();
  47255. *
  47256. * The new Store is only instantiated the first time you call products() to conserve memory and processing time, though
  47257. * calling products() a second time returns the same store instance.
  47258. *
  47259. * _Custom filtering_
  47260. *
  47261. * The Store is automatically furnished with a filter - by default this filter tells the store to only return records
  47262. * where the associated model's foreign key matches the owner model's primary key. For example, if a User with ID = 100
  47263. * hasMany Products, the filter loads only Products with user_id == 100.
  47264. *
  47265. * Sometimes we want to filter by another field - for example in the case of a Twitter search application we may have
  47266. * models for Search and Tweet:
  47267. *
  47268. * Ext.define('Search', {
  47269. * extend: 'Ext.data.Model',
  47270. * config: {
  47271. * fields: [
  47272. * 'id', 'query'
  47273. * ],
  47274. *
  47275. * hasMany: {
  47276. * model: 'Tweet',
  47277. * name : 'tweets',
  47278. * filterProperty: 'query'
  47279. * }
  47280. * }
  47281. * });
  47282. *
  47283. * Ext.define('Tweet', {
  47284. * extend: 'Ext.data.Model',
  47285. * config: {
  47286. * fields: [
  47287. * 'id', 'text', 'from_user'
  47288. * ]
  47289. * }
  47290. * });
  47291. *
  47292. * //returns a Store filtered by the filterProperty
  47293. * var store = new Search({query: 'Sencha Touch'}).tweets();
  47294. *
  47295. * The tweets association above is filtered by the query property by setting the {@link #filterProperty}, and is
  47296. * equivalent to this:
  47297. *
  47298. * var store = Ext.create('Ext.data.Store', {
  47299. * model: 'Tweet',
  47300. * filters: [
  47301. * {
  47302. * property: 'query',
  47303. * value : 'Sencha Touch'
  47304. * }
  47305. * ]
  47306. * });
  47307. */
  47308. Ext.define('Ext.data.association.HasMany', {
  47309. extend: 'Ext.data.association.Association',
  47310. alternateClassName: 'Ext.data.HasManyAssociation',
  47311. requires: ['Ext.util.Inflector'],
  47312. alias: 'association.hasmany',
  47313. config: {
  47314. /**
  47315. * @cfg {String} foreignKey
  47316. * The name of the foreign key on the associated model that links it to the owner model. Defaults to the
  47317. * lowercased name of the owner model plus "_id", e.g. an association with a model called Group hasMany Users
  47318. * would create 'group_id' as the foreign key. When the remote store is loaded, the store is automatically
  47319. * filtered so that only records with a matching foreign key are included in the resulting child store. This can
  47320. * be overridden by specifying the {@link #filterProperty}.
  47321. *
  47322. * Ext.define('Group', {
  47323. * extend: 'Ext.data.Model',
  47324. * fields: ['id', 'name'],
  47325. * hasMany: 'User'
  47326. * });
  47327. *
  47328. * Ext.define('User', {
  47329. * extend: 'Ext.data.Model',
  47330. * fields: ['id', 'name', 'group_id'], // refers to the id of the group that this user belongs to
  47331. * belongsTo: 'Group'
  47332. * });
  47333. */
  47334. foreignKey: undefined,
  47335. /**
  47336. * @cfg {String} name
  47337. * The name of the function to create on the owner model to retrieve the child store. If not specified, the
  47338. * pluralized name of the child model is used.
  47339. *
  47340. * // This will create a users() method on any Group model instance
  47341. * Ext.define('Group', {
  47342. * extend: 'Ext.data.Model',
  47343. * fields: ['id', 'name'],
  47344. * hasMany: 'User'
  47345. * });
  47346. * var group = new Group();
  47347. * console.log(group.users());
  47348. *
  47349. * // The method to retrieve the users will now be getUserList
  47350. * Ext.define('Group', {
  47351. * extend: 'Ext.data.Model',
  47352. * fields: ['id', 'name'],
  47353. * hasMany: {model: 'User', name: 'getUserList'}
  47354. * });
  47355. * var group = new Group();
  47356. * console.log(group.getUserList());
  47357. */
  47358. /**
  47359. * @cfg {Object} store
  47360. * Optional configuration object that will be passed to the generated Store. Defaults to an empty Object.
  47361. */
  47362. store: undefined,
  47363. /**
  47364. * @cfg {String} storeName
  47365. * Optional The name of the store by which you can reference it on this class as a property.
  47366. */
  47367. storeName: undefined,
  47368. /**
  47369. * @cfg {String} filterProperty
  47370. * Optionally overrides the default filter that is set up on the associated Store. If this is not set, a filter
  47371. * is automatically created which filters the association based on the configured {@link #foreignKey}. See intro
  47372. * docs for more details.
  47373. */
  47374. filterProperty: null,
  47375. /**
  47376. * @cfg {Boolean} autoLoad
  47377. * `true` to automatically load the related store from a remote source when instantiated.
  47378. */
  47379. autoLoad: false,
  47380. /**
  47381. * @cfg {Boolean} autoSync
  47382. * true to automatically synchronize the related store with the remote source
  47383. */
  47384. autoSync: false
  47385. },
  47386. constructor: function(config) {
  47387. config = config || {};
  47388. if (config.storeConfig) {
  47389. // <debug>
  47390. Ext.Logger.warn('storeConfig is deprecated on an association. Instead use the store configuration.');
  47391. // </debug>
  47392. config.store = config.storeConfig;
  47393. delete config.storeConfig;
  47394. }
  47395. this.callParent([config]);
  47396. },
  47397. applyName: function(name) {
  47398. if (!name) {
  47399. name = Ext.util.Inflector.pluralize(this.getAssociatedName().toLowerCase());
  47400. }
  47401. return name;
  47402. },
  47403. applyStoreName: function(name) {
  47404. if (!name) {
  47405. name = this.getName() + 'Store';
  47406. }
  47407. return name;
  47408. },
  47409. applyForeignKey: function(foreignKey) {
  47410. if (!foreignKey) {
  47411. var inverse = this.getInverseAssociation();
  47412. if (inverse) {
  47413. foreignKey = inverse.getForeignKey();
  47414. } else {
  47415. foreignKey = this.getOwnerName().toLowerCase() + '_id';
  47416. }
  47417. }
  47418. return foreignKey;
  47419. },
  47420. applyAssociationKey: function(associationKey) {
  47421. if (!associationKey) {
  47422. var associatedName = this.getAssociatedName();
  47423. associationKey = Ext.util.Inflector.pluralize(associatedName[0].toLowerCase() + associatedName.slice(1));
  47424. }
  47425. return associationKey;
  47426. },
  47427. updateForeignKey: function(foreignKey, oldForeignKey) {
  47428. var fields = this.getAssociatedModel().getFields(),
  47429. field = fields.get(foreignKey);
  47430. if (!field) {
  47431. field = new Ext.data.Field({
  47432. name: foreignKey
  47433. });
  47434. fields.add(field);
  47435. fields.isDirty = true;
  47436. }
  47437. if (oldForeignKey) {
  47438. field = fields.get(oldForeignKey);
  47439. if (field) {
  47440. fields.remove(field);
  47441. fields.isDirty = true;
  47442. }
  47443. }
  47444. },
  47445. /**
  47446. * @private
  47447. * Creates a function that returns an Ext.data.Store which is configured to load a set of data filtered
  47448. * by the owner model's primary key - e.g. in a `hasMany` association where Group `hasMany` Users, this function
  47449. * returns a Store configured to return the filtered set of a single Group's Users.
  47450. * @return {Function} The store-generating function.
  47451. */
  47452. applyStore: function(storeConfig) {
  47453. var me = this,
  47454. association = me,
  47455. associatedModel = me.getAssociatedModel(),
  47456. storeName = me.getStoreName(),
  47457. foreignKey = me.getForeignKey(),
  47458. primaryKey = me.getPrimaryKey(),
  47459. filterProperty = me.getFilterProperty(),
  47460. autoLoad = me.getAutoLoad(),
  47461. autoSync = me.getAutoSync();
  47462. return function() {
  47463. var record = this,
  47464. config, filter, store,
  47465. modelDefaults = {},
  47466. listeners = {
  47467. addrecords: me.onAddRecords,
  47468. removerecords: me.onRemoveRecords,
  47469. scope: me
  47470. };
  47471. if (record[storeName] === undefined) {
  47472. if (filterProperty) {
  47473. filter = {
  47474. property : filterProperty,
  47475. value : record.get(filterProperty),
  47476. exactMatch: true
  47477. };
  47478. } else {
  47479. filter = {
  47480. property : foreignKey,
  47481. value : record.get(primaryKey),
  47482. exactMatch: true
  47483. };
  47484. }
  47485. modelDefaults[foreignKey] = record.get(primaryKey);
  47486. config = Ext.apply({}, storeConfig, {
  47487. model : associatedModel,
  47488. filters : [filter],
  47489. remoteFilter : true,
  47490. autoSync : autoSync,
  47491. modelDefaults: modelDefaults,
  47492. listeners : listeners
  47493. });
  47494. store = record[storeName] = Ext.create('Ext.data.Store', config);
  47495. store.boundTo = record;
  47496. if (autoLoad) {
  47497. record[storeName].load();
  47498. }
  47499. }
  47500. return record[storeName];
  47501. };
  47502. },
  47503. onAddRecords: function(store, records) {
  47504. var ln = records.length,
  47505. id = store.boundTo.getId(),
  47506. i, record;
  47507. for (i = 0; i < ln; i++) {
  47508. record = records[i];
  47509. record.set(this.getForeignKey(), id);
  47510. }
  47511. this.updateInverseInstances(store.boundTo);
  47512. },
  47513. onRemoveRecords: function(store, records) {
  47514. var ln = records.length,
  47515. i, record;
  47516. for (i = 0; i < ln; i++) {
  47517. record = records[i];
  47518. record.set(this.getForeignKey(), null);
  47519. }
  47520. },
  47521. updateStore: function(store) {
  47522. this.getOwnerModel().prototype[this.getName()] = store;
  47523. },
  47524. /**
  47525. * Read associated data
  47526. * @private
  47527. * @param {Ext.data.Model} record The record we're writing to.
  47528. * @param {Ext.data.reader.Reader} reader The reader for the associated model.
  47529. * @param {Object} associationData The raw associated data.
  47530. */
  47531. read: function(record, reader, associationData) {
  47532. var store = record[this.getName()](),
  47533. records = reader.read(associationData).getRecords();
  47534. store.add(records);
  47535. },
  47536. updateInverseInstances: function(record) {
  47537. var store = record[this.getName()](),
  47538. inverse = this.getInverseAssociation();
  47539. //if the inverse association was found, set it now on each record we've just created
  47540. if (inverse) {
  47541. store.each(function(associatedRecord) {
  47542. associatedRecord[inverse.getInstanceName()] = record;
  47543. });
  47544. }
  47545. },
  47546. getInverseAssociation: function() {
  47547. var ownerName = this.getOwnerModel().modelName;
  47548. //now that we've added the related records to the hasMany association, set the inverse belongsTo
  47549. //association on each of them if it exists
  47550. return this.getAssociatedModel().associations.findBy(function(assoc) {
  47551. return assoc.getType().toLowerCase() === 'belongsto' && assoc.getAssociatedModel().modelName === ownerName;
  47552. });
  47553. }
  47554. });
  47555. /**
  47556. * @author Ed Spencer
  47557. * @aside guide models
  47558. *
  47559. * Represents a many to one association with another model. The owner model is expected to have
  47560. * a foreign key which references the primary key of the associated model:
  47561. *
  47562. * Ext.define('Category', {
  47563. * extend: 'Ext.data.Model',
  47564. * config: {
  47565. * fields: [
  47566. * { name: 'id', type: 'int' },
  47567. * { name: 'name', type: 'string' }
  47568. * ]
  47569. * }
  47570. * });
  47571. *
  47572. * Ext.define('Product', {
  47573. * extend: 'Ext.data.Model',
  47574. * config: {
  47575. * fields: [
  47576. * { name: 'id', type: 'int' },
  47577. * { name: 'category_id', type: 'int' },
  47578. * { name: 'name', type: 'string' }
  47579. * ],
  47580. * // we can use the belongsTo shortcut on the model to create a belongsTo association
  47581. * associations: { type: 'belongsTo', model: 'Category' }
  47582. * }
  47583. * });
  47584. *
  47585. * In the example above we have created models for Products and Categories, and linked them together
  47586. * by saying that each Product belongs to a Category. This automatically links each Product to a Category
  47587. * based on the Product's category_id, and provides new functions on the Product model:
  47588. *
  47589. * ## Generated getter function
  47590. *
  47591. * The first function that is added to the owner model is a getter function:
  47592. *
  47593. * var product = new Product({
  47594. * id: 100,
  47595. * category_id: 20,
  47596. * name: 'Sneakers'
  47597. * });
  47598. *
  47599. * product.getCategory(function(category, operation) {
  47600. * // do something with the category object
  47601. * alert(category.get('id')); // alerts 20
  47602. * }, this);
  47603. *
  47604. * The getCategory function was created on the Product model when we defined the association. This uses the
  47605. * Category's configured {@link Ext.data.proxy.Proxy proxy} to load the Category asynchronously, calling the provided
  47606. * callback when it has loaded.
  47607. *
  47608. * The new getCategory function will also accept an object containing success, failure and callback properties
  47609. * - callback will always be called, success will only be called if the associated model was loaded successfully
  47610. * and failure will only be called if the associated model could not be loaded:
  47611. *
  47612. * product.getCategory({
  47613. * reload: true, // force a reload if the owner model is already cached
  47614. * callback: function(category, operation) {}, // a function that will always be called
  47615. * success : function(category, operation) {}, // a function that will only be called if the load succeeded
  47616. * failure : function(category, operation) {}, // a function that will only be called if the load did not succeed
  47617. * scope : this // optionally pass in a scope object to execute the callbacks in
  47618. * });
  47619. *
  47620. * In each case above the callbacks are called with two arguments - the associated model instance and the
  47621. * {@link Ext.data.Operation operation} object that was executed to load that instance. The Operation object is
  47622. * useful when the instance could not be loaded.
  47623. *
  47624. * Once the getter has been called on the model, it will be cached if the getter is called a second time. To
  47625. * force the model to reload, specify reload: true in the options object.
  47626. *
  47627. * ## Generated setter function
  47628. *
  47629. * The second generated function sets the associated model instance - if only a single argument is passed to
  47630. * the setter then the following two calls are identical:
  47631. *
  47632. * // this call...
  47633. * product.setCategory(10);
  47634. *
  47635. * // is equivalent to this call:
  47636. * product.set('category_id', 10);
  47637. *
  47638. * An instance of the owner model can also be passed as a parameter.
  47639. *
  47640. * If we pass in a second argument, the model will be automatically saved and the second argument passed to
  47641. * the owner model's {@link Ext.data.Model#save save} method:
  47642. *
  47643. * product.setCategory(10, function(product, operation) {
  47644. * // the product has been saved
  47645. * alert(product.get('category_id')); //now alerts 10
  47646. * });
  47647. *
  47648. * //alternative syntax:
  47649. * product.setCategory(10, {
  47650. * callback: function(product, operation) {}, // a function that will always be called
  47651. * success : function(product, operation) {}, // a function that will only be called if the load succeeded
  47652. * failure : function(product, operation) {}, // a function that will only be called if the load did not succeed
  47653. * scope : this //optionally pass in a scope object to execute the callbacks in
  47654. * });
  47655. *
  47656. * ## Customization
  47657. *
  47658. * Associations reflect on the models they are linking to automatically set up properties such as the
  47659. * {@link #primaryKey} and {@link #foreignKey}. These can alternatively be specified:
  47660. *
  47661. * Ext.define('Product', {
  47662. * extend: 'Ext.data.Model',
  47663. * config: {
  47664. * fields: [
  47665. * // ...
  47666. * ],
  47667. *
  47668. * associations: [
  47669. * { type: 'belongsTo', model: 'Category', primaryKey: 'unique_id', foreignKey: 'cat_id' }
  47670. * ]
  47671. * }
  47672. * });
  47673. *
  47674. * Here we replaced the default primary key (defaults to 'id') and foreign key (calculated as 'category_id')
  47675. * with our own settings. Usually this will not be needed.
  47676. */
  47677. Ext.define('Ext.data.association.BelongsTo', {
  47678. extend: 'Ext.data.association.Association',
  47679. alternateClassName: 'Ext.data.BelongsToAssociation',
  47680. alias: 'association.belongsto',
  47681. config: {
  47682. /**
  47683. * @cfg {String} foreignKey The name of the foreign key on the owner model that links it to the associated
  47684. * model. Defaults to the lowercased name of the associated model plus "_id", e.g. an association with a
  47685. * model called Product would set up a product_id foreign key.
  47686. *
  47687. * Ext.define('Order', {
  47688. * extend: 'Ext.data.Model',
  47689. * fields: ['id', 'date'],
  47690. * hasMany: 'Product'
  47691. * });
  47692. *
  47693. * Ext.define('Product', {
  47694. * extend: 'Ext.data.Model',
  47695. * fields: ['id', 'name', 'order_id'], // refers to the id of the order that this product belongs to
  47696. * belongsTo: 'Group'
  47697. * });
  47698. * var product = new Product({
  47699. * id: 1,
  47700. * name: 'Product 1',
  47701. * order_id: 22
  47702. * }, 1);
  47703. * product.getOrder(); // Will make a call to the server asking for order_id 22
  47704. *
  47705. */
  47706. foreignKey: undefined,
  47707. /**
  47708. * @cfg {String} getterName The name of the getter function that will be added to the local model's prototype.
  47709. * Defaults to 'get' + the name of the foreign model, e.g. getCategory
  47710. */
  47711. getterName: undefined,
  47712. /**
  47713. * @cfg {String} setterName The name of the setter function that will be added to the local model's prototype.
  47714. * Defaults to 'set' + the name of the foreign model, e.g. setCategory
  47715. */
  47716. setterName: undefined,
  47717. instanceName: undefined
  47718. },
  47719. applyForeignKey: function(foreignKey) {
  47720. if (!foreignKey) {
  47721. foreignKey = this.getAssociatedName().toLowerCase() + '_id';
  47722. }
  47723. return foreignKey;
  47724. },
  47725. updateForeignKey: function(foreignKey, oldForeignKey) {
  47726. var fields = this.getOwnerModel().getFields(),
  47727. field = fields.get(foreignKey);
  47728. if (!field) {
  47729. field = new Ext.data.Field({
  47730. name: foreignKey
  47731. });
  47732. fields.add(field);
  47733. fields.isDirty = true;
  47734. }
  47735. if (oldForeignKey) {
  47736. field = fields.get(oldForeignKey);
  47737. if (field) {
  47738. fields.isDirty = true;
  47739. fields.remove(field);
  47740. }
  47741. }
  47742. },
  47743. applyInstanceName: function(instanceName) {
  47744. if (!instanceName) {
  47745. instanceName = this.getAssociatedName() + 'BelongsToInstance';
  47746. }
  47747. return instanceName;
  47748. },
  47749. applyAssociationKey: function(associationKey) {
  47750. if (!associationKey) {
  47751. var associatedName = this.getAssociatedName();
  47752. associationKey = associatedName[0].toLowerCase() + associatedName.slice(1);
  47753. }
  47754. return associationKey;
  47755. },
  47756. applyGetterName: function(getterName) {
  47757. if (!getterName) {
  47758. var associatedName = this.getAssociatedName();
  47759. getterName = 'get' + associatedName[0].toUpperCase() + associatedName.slice(1);
  47760. }
  47761. return getterName;
  47762. },
  47763. applySetterName: function(setterName) {
  47764. if (!setterName) {
  47765. var associatedName = this.getAssociatedName();
  47766. setterName = 'set' + associatedName[0].toUpperCase() + associatedName.slice(1);
  47767. }
  47768. return setterName;
  47769. },
  47770. updateGetterName: function(getterName, oldGetterName) {
  47771. var ownerProto = this.getOwnerModel().prototype;
  47772. if (oldGetterName) {
  47773. delete ownerProto[oldGetterName];
  47774. }
  47775. if (getterName) {
  47776. ownerProto[getterName] = this.createGetter();
  47777. }
  47778. },
  47779. updateSetterName: function(setterName, oldSetterName) {
  47780. var ownerProto = this.getOwnerModel().prototype;
  47781. if (oldSetterName) {
  47782. delete ownerProto[oldSetterName];
  47783. }
  47784. if (setterName) {
  47785. ownerProto[setterName] = this.createSetter();
  47786. }
  47787. },
  47788. /**
  47789. * @private
  47790. * Returns a setter function to be placed on the owner model's prototype
  47791. * @return {Function} The setter function
  47792. */
  47793. createSetter: function() {
  47794. var me = this,
  47795. foreignKey = me.getForeignKey(),
  47796. associatedModel = me.getAssociatedModel(),
  47797. currentOwner, newOwner, store;
  47798. //'this' refers to the Model instance inside this function
  47799. return function(value, options, scope) {
  47800. var inverse = me.getInverseAssociation(),
  47801. record = this;
  47802. // If we pass in an instance, pull the id out
  47803. if (value && value.isModel) {
  47804. value = value.getId();
  47805. }
  47806. if (Ext.isFunction(options)) {
  47807. options = {
  47808. callback: options,
  47809. scope: scope || record
  47810. };
  47811. }
  47812. // Remove the current belongsToInstance
  47813. delete record[me.getInstanceName()];
  47814. currentOwner = Ext.data.Model.cache[Ext.data.Model.generateCacheId(associatedModel.modelName, this.get(foreignKey))];
  47815. newOwner = Ext.data.Model.cache[Ext.data.Model.generateCacheId(associatedModel.modelName, value)];
  47816. record.set(foreignKey, value);
  47817. if (inverse) {
  47818. // We first add it to the new owner so that the record wouldnt be destroyed if it was the last store it was in
  47819. if (newOwner) {
  47820. if (inverse.getType().toLowerCase() === 'hasmany') {
  47821. store = newOwner[inverse.getName()]();
  47822. store.add(record);
  47823. } else {
  47824. newOwner[inverse.getInstanceName()] = record;
  47825. }
  47826. }
  47827. if (currentOwner) {
  47828. if (inverse.getType().toLowerCase() === 'hasmany') {
  47829. store = currentOwner[inverse.getName()]();
  47830. store.remove(record);
  47831. } else {
  47832. delete value[inverse.getInstanceName()];
  47833. }
  47834. }
  47835. }
  47836. if (newOwner) {
  47837. record[me.getInstanceName()] = newOwner;
  47838. }
  47839. if (Ext.isObject(options)) {
  47840. return record.save(options);
  47841. }
  47842. return record;
  47843. };
  47844. },
  47845. /**
  47846. * @private
  47847. * Returns a getter function to be placed on the owner model's prototype. We cache the loaded instance
  47848. * the first time it is loaded so that subsequent calls to the getter always receive the same reference.
  47849. * @return {Function} The getter function
  47850. */
  47851. createGetter: function() {
  47852. var me = this,
  47853. associatedModel = me.getAssociatedModel(),
  47854. foreignKey = me.getForeignKey(),
  47855. instanceName = me.getInstanceName();
  47856. //'this' refers to the Model instance inside this function
  47857. return function(options, scope) {
  47858. options = options || {};
  47859. var model = this,
  47860. foreignKeyId = model.get(foreignKey),
  47861. success,
  47862. instance,
  47863. args;
  47864. instance = model[instanceName];
  47865. if (!instance) {
  47866. instance = Ext.data.Model.cache[Ext.data.Model.generateCacheId(associatedModel.modelName, foreignKeyId)];
  47867. if (instance) {
  47868. model[instanceName] = instance;
  47869. }
  47870. }
  47871. if (options.reload === true || instance === undefined) {
  47872. if (typeof options == 'function') {
  47873. options = {
  47874. callback: options,
  47875. scope: scope || model
  47876. };
  47877. }
  47878. // Overwrite the success handler so we can assign the current instance
  47879. success = options.success;
  47880. options.success = function(rec) {
  47881. model[instanceName] = rec;
  47882. if (success) {
  47883. success.apply(this, arguments);
  47884. }
  47885. };
  47886. associatedModel.load(foreignKeyId, options);
  47887. } else {
  47888. args = [instance];
  47889. scope = scope || model;
  47890. Ext.callback(options, scope, args);
  47891. Ext.callback(options.success, scope, args);
  47892. Ext.callback(options.failure, scope, args);
  47893. Ext.callback(options.callback, scope, args);
  47894. return instance;
  47895. }
  47896. };
  47897. },
  47898. /**
  47899. * Read associated data
  47900. * @private
  47901. * @param {Ext.data.Model} record The record we're writing to
  47902. * @param {Ext.data.reader.Reader} reader The reader for the associated model
  47903. * @param {Object} associationData The raw associated data
  47904. */
  47905. read: function(record, reader, associationData){
  47906. record[this.getInstanceName()] = reader.read([associationData]).getRecords()[0];
  47907. },
  47908. getInverseAssociation: function() {
  47909. var ownerName = this.getOwnerModel().modelName,
  47910. foreignKey = this.getForeignKey();
  47911. return this.getAssociatedModel().associations.findBy(function(assoc) {
  47912. var type = assoc.getType().toLowerCase();
  47913. return (type === 'hasmany' || type === 'hasone') && assoc.getAssociatedModel().modelName === ownerName && assoc.getForeignKey() === foreignKey;
  47914. });
  47915. }
  47916. });
  47917. /**
  47918. * @aside guide models
  47919. *
  47920. * Represents a one to one association with another model. The owner model is expected to have
  47921. * a foreign key which references the primary key of the associated model:
  47922. *
  47923. * Ext.define('Person', {
  47924. * extend: 'Ext.data.Model',
  47925. * config: {
  47926. * fields: [
  47927. * { name: 'id', type: 'int' },
  47928. * { name: 'name', type: 'string' },
  47929. * { name: 'address_id', type: 'int'}
  47930. * ],
  47931. *
  47932. * // we can use the hasOne shortcut on the model to create a hasOne association
  47933. * associations: { type: 'hasOne', model: 'Address' }
  47934. * }
  47935. * });
  47936. *
  47937. * Ext.define('Address', {
  47938. * extend: 'Ext.data.Model',
  47939. * config: {
  47940. * fields: [
  47941. * { name: 'id', type: 'int' },
  47942. * { name: 'number', type: 'string' },
  47943. * { name: 'street', type: 'string' },
  47944. * { name: 'city', type: 'string' },
  47945. * { name: 'zip', type: 'string' }
  47946. * ]
  47947. * }
  47948. * });
  47949. *
  47950. * In the example above we have created models for People and Addresses, and linked them together
  47951. * by saying that each Person has a single Address. This automatically links each Person to an Address
  47952. * based on the Persons address_id, and provides new functions on the Person model:
  47953. *
  47954. * ## Generated getter function
  47955. *
  47956. * The first function that is added to the owner model is a getter function:
  47957. *
  47958. * var person = Ext.create('Person', {
  47959. * id: 100,
  47960. * address_id: 20,
  47961. * name: 'John Smith'
  47962. * });
  47963. *
  47964. * person.getAddress(function(address, operation) {
  47965. * // do something with the address object
  47966. * alert(address.get('id')); // alerts 20
  47967. * }, this);
  47968. *
  47969. * The getAddress function was created on the Person model when we defined the association. This uses the
  47970. * Persons configured {@link Ext.data.proxy.Proxy proxy} to load the Address asynchronously, calling the provided
  47971. * callback when it has loaded.
  47972. *
  47973. * The new getAddress function will also accept an object containing success, failure and callback properties
  47974. * - callback will always be called, success will only be called if the associated model was loaded successfully
  47975. * and failure will only be called if the associated model could not be loaded:
  47976. *
  47977. * person.getAddress({
  47978. * reload: true, // force a reload if the owner model is already cached
  47979. * callback: function(address, operation) {}, // a function that will always be called
  47980. * success : function(address, operation) {}, // a function that will only be called if the load succeeded
  47981. * failure : function(address, operation) {}, // a function that will only be called if the load did not succeed
  47982. * scope : this // optionally pass in a scope object to execute the callbacks in
  47983. * });
  47984. *
  47985. * In each case above the callbacks are called with two arguments - the associated model instance and the
  47986. * {@link Ext.data.Operation operation} object that was executed to load that instance. The Operation object is
  47987. * useful when the instance could not be loaded.
  47988. *
  47989. * Once the getter has been called on the model, it will be cached if the getter is called a second time. To
  47990. * force the model to reload, specify reload: true in the options object.
  47991. *
  47992. * ## Generated setter function
  47993. *
  47994. * The second generated function sets the associated model instance - if only a single argument is passed to
  47995. * the setter then the following two calls are identical:
  47996. *
  47997. * // this call...
  47998. * person.setAddress(10);
  47999. *
  48000. * // is equivalent to this call:
  48001. * person.set('address_id', 10);
  48002. *
  48003. * An instance of the owner model can also be passed as a parameter.
  48004. *
  48005. * If we pass in a second argument, the model will be automatically saved and the second argument passed to
  48006. * the owner model's {@link Ext.data.Model#save save} method:
  48007. *
  48008. * person.setAddress(10, function(address, operation) {
  48009. * // the address has been saved
  48010. * alert(address.get('address_id')); //now alerts 10
  48011. * });
  48012. *
  48013. * //alternative syntax:
  48014. * person.setAddress(10, {
  48015. * callback: function(address, operation) {}, // a function that will always be called
  48016. * success : function(address, operation) {}, // a function that will only be called if the load succeeded
  48017. * failure : function(address, operation) {}, // a function that will only be called if the load did not succeed
  48018. * scope : this //optionally pass in a scope object to execute the callbacks in
  48019. * });
  48020. *
  48021. * ## Customization
  48022. *
  48023. * Associations reflect on the models they are linking to automatically set up properties such as the
  48024. * {@link #primaryKey} and {@link #foreignKey}. These can alternatively be specified:
  48025. *
  48026. * Ext.define('Person', {
  48027. * extend: 'Ext.data.Model',
  48028. * config: {
  48029. * fields: [
  48030. * // ...
  48031. * ],
  48032. *
  48033. * associations: [
  48034. * { type: 'hasOne', model: 'Address', primaryKey: 'unique_id', foreignKey: 'addr_id' }
  48035. * ]
  48036. * }
  48037. * });
  48038. *
  48039. * Here we replaced the default primary key (defaults to 'id') and foreign key (calculated as 'address_id')
  48040. * with our own settings. Usually this will not be needed.
  48041. */
  48042. Ext.define('Ext.data.association.HasOne', {
  48043. extend: 'Ext.data.association.Association',
  48044. alternateClassName: 'Ext.data.HasOneAssociation',
  48045. alias: 'association.hasone',
  48046. config: {
  48047. /**
  48048. * @cfg {String} foreignKey The name of the foreign key on the owner model that links it to the associated
  48049. * model. Defaults to the lowercased name of the associated model plus "_id", e.g. an association with a
  48050. * model called Person would set up a address_id foreign key.
  48051. *
  48052. * Ext.define('Person', {
  48053. * extend: 'Ext.data.Model',
  48054. * fields: ['id', 'name', 'address_id'], // refers to the id of the address object
  48055. * hasOne: 'Address'
  48056. * });
  48057. *
  48058. * Ext.define('Address', {
  48059. * extend: 'Ext.data.Model',
  48060. * fields: ['id', 'number', 'street', 'city', 'zip'],
  48061. * belongsTo: 'Person'
  48062. * });
  48063. * var Person = new Person({
  48064. * id: 1,
  48065. * name: 'John Smith',
  48066. * address_id: 13
  48067. * }, 1);
  48068. * person.getAddress(); // Will make a call to the server asking for address_id 13
  48069. *
  48070. */
  48071. foreignKey: undefined,
  48072. /**
  48073. * @cfg {String} getterName The name of the getter function that will be added to the local model's prototype.
  48074. * Defaults to 'get' + the name of the foreign model, e.g. getAddress
  48075. */
  48076. getterName: undefined,
  48077. /**
  48078. * @cfg {String} setterName The name of the setter function that will be added to the local model's prototype.
  48079. * Defaults to 'set' + the name of the foreign model, e.g. setAddress
  48080. */
  48081. setterName: undefined,
  48082. instanceName: undefined
  48083. },
  48084. applyForeignKey: function(foreignKey) {
  48085. if (!foreignKey) {
  48086. var inverse = this.getInverseAssociation();
  48087. if (inverse) {
  48088. foreignKey = inverse.getForeignKey();
  48089. } else {
  48090. foreignKey = this.getAssociatedName().toLowerCase() + '_id';
  48091. }
  48092. }
  48093. return foreignKey;
  48094. },
  48095. updateForeignKey: function(foreignKey, oldForeignKey) {
  48096. var fields = this.getOwnerModel().getFields(),
  48097. field = fields.get(foreignKey);
  48098. if (!field) {
  48099. field = new Ext.data.Field({
  48100. name: foreignKey
  48101. });
  48102. fields.add(field);
  48103. fields.isDirty = true;
  48104. }
  48105. if (oldForeignKey) {
  48106. field = fields.get(oldForeignKey);
  48107. if (field) {
  48108. fields.remove(field);
  48109. fields.isDirty = true;
  48110. }
  48111. }
  48112. },
  48113. applyInstanceName: function(instanceName) {
  48114. if (!instanceName) {
  48115. instanceName = this.getAssociatedName() + 'HasOneInstance';
  48116. }
  48117. return instanceName;
  48118. },
  48119. applyAssociationKey: function(associationKey) {
  48120. if (!associationKey) {
  48121. var associatedName = this.getAssociatedName();
  48122. associationKey = associatedName[0].toLowerCase() + associatedName.slice(1);
  48123. }
  48124. return associationKey;
  48125. },
  48126. applyGetterName: function(getterName) {
  48127. if (!getterName) {
  48128. var associatedName = this.getAssociatedName();
  48129. getterName = 'get' + associatedName[0].toUpperCase() + associatedName.slice(1);
  48130. }
  48131. return getterName;
  48132. },
  48133. applySetterName: function(setterName) {
  48134. if (!setterName) {
  48135. var associatedName = this.getAssociatedName();
  48136. setterName = 'set' + associatedName[0].toUpperCase() + associatedName.slice(1);
  48137. }
  48138. return setterName;
  48139. },
  48140. updateGetterName: function(getterName, oldGetterName) {
  48141. var ownerProto = this.getOwnerModel().prototype;
  48142. if (oldGetterName) {
  48143. delete ownerProto[oldGetterName];
  48144. }
  48145. if (getterName) {
  48146. ownerProto[getterName] = this.createGetter();
  48147. }
  48148. },
  48149. updateSetterName: function(setterName, oldSetterName) {
  48150. var ownerProto = this.getOwnerModel().prototype;
  48151. if (oldSetterName) {
  48152. delete ownerProto[oldSetterName];
  48153. }
  48154. if (setterName) {
  48155. ownerProto[setterName] = this.createSetter();
  48156. }
  48157. },
  48158. /**
  48159. * @private
  48160. * Returns a setter function to be placed on the owner model's prototype
  48161. * @return {Function} The setter function
  48162. */
  48163. createSetter: function() {
  48164. var me = this,
  48165. foreignKey = me.getForeignKey(),
  48166. instanceName = me.getInstanceName(),
  48167. associatedModel = me.getAssociatedModel();
  48168. //'this' refers to the Model instance inside this function
  48169. return function(value, options, scope) {
  48170. var Model = Ext.data.Model,
  48171. record;
  48172. if (value && value.isModel) {
  48173. value = value.getId();
  48174. }
  48175. this.set(foreignKey, value);
  48176. record = Model.cache[Model.generateCacheId(associatedModel.modelName, value)];
  48177. if (record) {
  48178. this[instanceName] = record;
  48179. }
  48180. if (Ext.isFunction(options)) {
  48181. options = {
  48182. callback: options,
  48183. scope: scope || this
  48184. };
  48185. }
  48186. if (Ext.isObject(options)) {
  48187. return this.save(options);
  48188. }
  48189. return this;
  48190. };
  48191. },
  48192. /**
  48193. * @private
  48194. * Returns a getter function to be placed on the owner model's prototype. We cache the loaded instance
  48195. * the first time it is loaded so that subsequent calls to the getter always receive the same reference.
  48196. * @return {Function} The getter function
  48197. */
  48198. createGetter: function() {
  48199. var me = this,
  48200. associatedModel = me.getAssociatedModel(),
  48201. foreignKey = me.getForeignKey(),
  48202. instanceName = me.getInstanceName();
  48203. //'this' refers to the Model instance inside this function
  48204. return function(options, scope) {
  48205. options = options || {};
  48206. var model = this,
  48207. foreignKeyId = model.get(foreignKey),
  48208. success, instance, args;
  48209. if (options.reload === true || model[instanceName] === undefined) {
  48210. if (typeof options == 'function') {
  48211. options = {
  48212. callback: options,
  48213. scope: scope || model
  48214. };
  48215. }
  48216. // Overwrite the success handler so we can assign the current instance
  48217. success = options.success;
  48218. options.success = function(rec){
  48219. model[instanceName] = rec;
  48220. if (success) {
  48221. success.call(this, arguments);
  48222. }
  48223. };
  48224. associatedModel.load(foreignKeyId, options);
  48225. } else {
  48226. instance = model[instanceName];
  48227. args = [instance];
  48228. scope = scope || model;
  48229. Ext.callback(options, scope, args);
  48230. Ext.callback(options.success, scope, args);
  48231. Ext.callback(options.failure, scope, args);
  48232. Ext.callback(options.callback, scope, args);
  48233. return instance;
  48234. }
  48235. };
  48236. },
  48237. /**
  48238. * Read associated data
  48239. * @private
  48240. * @param {Ext.data.Model} record The record we're writing to
  48241. * @param {Ext.data.reader.Reader} reader The reader for the associated model
  48242. * @param {Object} associationData The raw associated data
  48243. */
  48244. read: function(record, reader, associationData) {
  48245. var inverse = this.getInverseAssociation(),
  48246. newRecord = reader.read([associationData]).getRecords()[0];
  48247. record[this.getInstanceName()] = newRecord;
  48248. //if the inverse association was found, set it now on each record we've just created
  48249. if (inverse) {
  48250. newRecord[inverse.getInstanceName()] = record;
  48251. }
  48252. },
  48253. getInverseAssociation: function() {
  48254. var ownerName = this.getOwnerModel().modelName;
  48255. return this.getAssociatedModel().associations.findBy(function(assoc) {
  48256. return assoc.getType().toLowerCase() === 'belongsto' && assoc.getAssociatedModel().modelName === ownerName;
  48257. });
  48258. }
  48259. });
  48260. /**
  48261. * @author Ed Spencer
  48262. * @class Ext.data.Error
  48263. *
  48264. * This is used when validating a record. The validate method will return an {@link Ext.data.Errors} collection
  48265. * containing Ext.data.Error instances. Each error has a field and a message.
  48266. *
  48267. * Usually this class does not need to be instantiated directly - instances are instead created
  48268. * automatically when {@link Ext.data.Model#validate validate} on a model instance.
  48269. */
  48270. Ext.define('Ext.data.Error', {
  48271. config: {
  48272. /**
  48273. * @cfg {String} field
  48274. * The name of the field this error belongs to.
  48275. */
  48276. field: null,
  48277. /**
  48278. * @cfg {String} message
  48279. * The message containing the description of the error.
  48280. */
  48281. message: ''
  48282. },
  48283. constructor: function(config) {
  48284. this.initConfig(config);
  48285. }
  48286. });
  48287. /**
  48288. * @author Ed Spencer
  48289. * @class Ext.data.Errors
  48290. * @extends Ext.util.Collection
  48291. *
  48292. * Wraps a collection of validation error responses and provides convenient functions for
  48293. * accessing and errors for specific fields.
  48294. *
  48295. * Usually this class does not need to be instantiated directly - instances are instead created
  48296. * automatically when {@link Ext.data.Model#validate validate} on a model instance:
  48297. *
  48298. * //validate some existing model instance - in this case it returned two failures messages
  48299. * var errors = myModel.validate();
  48300. *
  48301. * errors.isValid(); // false
  48302. *
  48303. * errors.length; // 2
  48304. * errors.getByField('name'); // [{field: 'name', message: 'must be present'}]
  48305. * errors.getByField('title'); // [{field: 'title', message: 'is too short'}]
  48306. */
  48307. Ext.define('Ext.data.Errors', {
  48308. extend: 'Ext.util.Collection',
  48309. requires: 'Ext.data.Error',
  48310. /**
  48311. * Returns `true` if there are no errors in the collection.
  48312. * @return {Boolean}
  48313. */
  48314. isValid: function() {
  48315. return this.length === 0;
  48316. },
  48317. /**
  48318. * Returns all of the errors for the given field.
  48319. * @param {String} fieldName The field to get errors for.
  48320. * @return {Object[]} All errors for the given field.
  48321. */
  48322. getByField: function(fieldName) {
  48323. var errors = [],
  48324. error, i;
  48325. for (i = 0; i < this.length; i++) {
  48326. error = this.items[i];
  48327. if (error.getField() == fieldName) {
  48328. errors.push(error);
  48329. }
  48330. }
  48331. return errors;
  48332. },
  48333. add: function() {
  48334. var obj = arguments.length == 1 ? arguments[0] : arguments[1];
  48335. if (!(obj instanceof Ext.data.Error)) {
  48336. obj = Ext.create('Ext.data.Error', {
  48337. field: obj.field || obj.name,
  48338. message: obj.error || obj.message
  48339. });
  48340. }
  48341. return this.callParent([obj]);
  48342. }
  48343. });
  48344. /**
  48345. * @author Ed Spencer
  48346. * @aside guide models
  48347. *
  48348. * A Model represents some object that your application manages. For example, one might define a Model for Users,
  48349. * Products, Cars, or any other real-world object that we want to model in the system. Models are registered via the
  48350. * {@link Ext.data.ModelManager model manager}, and are used by {@link Ext.data.Store stores}, which are in turn used by many
  48351. * of the data-bound components in Ext.
  48352. *
  48353. * Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:
  48354. *
  48355. * Ext.define('User', {
  48356. * extend: 'Ext.data.Model',
  48357. *
  48358. * config: {
  48359. * fields: [
  48360. * {name: 'name', type: 'string'},
  48361. * {name: 'age', type: 'int'},
  48362. * {name: 'phone', type: 'string'},
  48363. * {name: 'alive', type: 'boolean', defaultValue: true}
  48364. * ]
  48365. * },
  48366. *
  48367. * changeName: function() {
  48368. * var oldName = this.get('name'),
  48369. * newName = oldName + " The Barbarian";
  48370. *
  48371. * this.set('name', newName);
  48372. * }
  48373. * });
  48374. *
  48375. * The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link
  48376. * Ext.data.ModelManager ModelManager}, and all other functions and properties are copied to the new Model's prototype.
  48377. *
  48378. * Now we can create instances of our User model and call any model logic we defined:
  48379. *
  48380. * var user = Ext.create('User', {
  48381. * name : 'Conan',
  48382. * age : 24,
  48383. * phone: '555-555-5555'
  48384. * });
  48385. *
  48386. * user.changeName();
  48387. * user.get('name'); // returns "Conan The Barbarian"
  48388. *
  48389. * # Validations
  48390. *
  48391. * Models have built-in support for validations, which are executed against the validator functions in {@link
  48392. * Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to
  48393. * models:
  48394. *
  48395. * Ext.define('User', {
  48396. * extend: 'Ext.data.Model',
  48397. *
  48398. * config: {
  48399. * fields: [
  48400. * {name: 'name', type: 'string'},
  48401. * {name: 'age', type: 'int'},
  48402. * {name: 'phone', type: 'string'},
  48403. * {name: 'gender', type: 'string'},
  48404. * {name: 'username', type: 'string'},
  48405. * {name: 'alive', type: 'boolean', defaultValue: true}
  48406. * ],
  48407. *
  48408. * validations: [
  48409. * {type: 'presence', field: 'age'},
  48410. * {type: 'length', field: 'name', min: 2},
  48411. * {type: 'inclusion', field: 'gender', list: ['Male', 'Female']},
  48412. * {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
  48413. * {type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
  48414. * ]
  48415. * }
  48416. * });
  48417. *
  48418. * The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
  48419. * object:
  48420. *
  48421. * var instance = Ext.create('User', {
  48422. * name: 'Ed',
  48423. * gender: 'Male',
  48424. * username: 'edspencer'
  48425. * });
  48426. *
  48427. * var errors = instance.validate();
  48428. *
  48429. * # Associations
  48430. *
  48431. * Models can have associations with other Models via {@link Ext.data.association.HasOne},
  48432. * {@link Ext.data.association.BelongsTo belongsTo} and {@link Ext.data.association.HasMany hasMany} associations.
  48433. * For example, let's say we're writing a blog administration application which deals with Users, Posts and Comments.
  48434. * We can express the relationships between these models like this:
  48435. *
  48436. * Ext.define('Post', {
  48437. * extend: 'Ext.data.Model',
  48438. *
  48439. * config: {
  48440. * fields: ['id', 'user_id'],
  48441. * belongsTo: 'User',
  48442. * hasMany : {model: 'Comment', name: 'comments'}
  48443. * }
  48444. * });
  48445. *
  48446. * Ext.define('Comment', {
  48447. * extend: 'Ext.data.Model',
  48448. *
  48449. * config: {
  48450. * fields: ['id', 'user_id', 'post_id'],
  48451. * belongsTo: 'Post'
  48452. * }
  48453. * });
  48454. *
  48455. * Ext.define('User', {
  48456. * extend: 'Ext.data.Model',
  48457. *
  48458. * config: {
  48459. * fields: ['id'],
  48460. * hasMany: [
  48461. * 'Post',
  48462. * {model: 'Comment', name: 'comments'}
  48463. * ]
  48464. * }
  48465. * });
  48466. *
  48467. * See the docs for {@link Ext.data.association.HasOne}, {@link Ext.data.association.BelongsTo} and
  48468. * {@link Ext.data.association.HasMany} for details on the usage and configuration of associations.
  48469. * Note that associations can also be specified like this:
  48470. *
  48471. * Ext.define('User', {
  48472. * extend: 'Ext.data.Model',
  48473. *
  48474. * config: {
  48475. * fields: ['id'],
  48476. * associations: [
  48477. * {type: 'hasMany', model: 'Post', name: 'posts'},
  48478. * {type: 'hasMany', model: 'Comment', name: 'comments'}
  48479. * ]
  48480. * }
  48481. * });
  48482. *
  48483. * # Using a Proxy
  48484. *
  48485. * Models are great for representing types of data and relationships, but sooner or later we're going to want to load or
  48486. * save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy}, which
  48487. * can be set directly on the Model:
  48488. *
  48489. * Ext.define('User', {
  48490. * extend: 'Ext.data.Model',
  48491. *
  48492. * config: {
  48493. * fields: ['id', 'name', 'email'],
  48494. * proxy: {
  48495. * type: 'rest',
  48496. * url : '/users'
  48497. * }
  48498. * }
  48499. * });
  48500. *
  48501. * Here we've set up a {@link Ext.data.proxy.Rest Rest Proxy}, which knows how to load and save data to and from a
  48502. * RESTful backend. Let's see how this works:
  48503. *
  48504. * var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
  48505. *
  48506. * user.save(); //POST /users
  48507. *
  48508. * Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this Model's
  48509. * data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't have an id,
  48510. * and performs the appropriate action - in this case issuing a POST request to the url we configured (/users). We
  48511. * configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full list.
  48512. *
  48513. * Loading data via the Proxy is equally easy:
  48514. *
  48515. * //get a reference to the User model class
  48516. * var User = Ext.ModelManager.getModel('User');
  48517. *
  48518. * //Uses the configured RestProxy to make a GET request to /users/123
  48519. * User.load(123, {
  48520. * success: function(user) {
  48521. * console.log(user.getId()); //logs 123
  48522. * }
  48523. * });
  48524. *
  48525. * Models can also be updated and destroyed easily:
  48526. *
  48527. * //the user Model we loaded in the last snippet:
  48528. * user.set('name', 'Edward Spencer');
  48529. *
  48530. * //tells the Proxy to save the Model. In this case it will perform a PUT request to /users/123 as this Model already has an id
  48531. * user.save({
  48532. * success: function() {
  48533. * console.log('The User was updated');
  48534. * }
  48535. * });
  48536. *
  48537. * //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
  48538. * user.erase({
  48539. * success: function() {
  48540. * console.log('The User was destroyed!');
  48541. * }
  48542. * });
  48543. *
  48544. * # Usage in Stores
  48545. *
  48546. * It is very common to want to load a set of Model instances to be displayed and manipulated in the UI. We do this by
  48547. * creating a {@link Ext.data.Store Store}:
  48548. *
  48549. * var store = Ext.create('Ext.data.Store', {
  48550. * model: 'User'
  48551. * });
  48552. *
  48553. * //uses the Proxy we set up on Model to load the Store data
  48554. * store.load();
  48555. *
  48556. * A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain a
  48557. * set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the {@link
  48558. * Ext.data.Store Store docs} for more information on Stores.
  48559. */
  48560. Ext.define('Ext.data.Model', {
  48561. alternateClassName: 'Ext.data.Record',
  48562. mixins: {
  48563. observable: 'Ext.mixin.Observable'
  48564. },
  48565. /**
  48566. * Provides an easy way to quickly determine if a given class is a Model
  48567. * @property isModel
  48568. * @type Boolean
  48569. * @private
  48570. */
  48571. isModel: true,
  48572. requires: [
  48573. 'Ext.util.Collection',
  48574. 'Ext.data.Field',
  48575. 'Ext.data.identifier.Simple',
  48576. 'Ext.data.ModelManager',
  48577. 'Ext.data.proxy.Ajax',
  48578. 'Ext.data.association.HasMany',
  48579. 'Ext.data.association.BelongsTo',
  48580. 'Ext.data.association.HasOne',
  48581. 'Ext.data.Errors'
  48582. ],
  48583. config: {
  48584. /**
  48585. * @cfg {String} idProperty
  48586. * The name of the field treated as this Model's unique `id`. Note that this field
  48587. * needs to have a type of 'auto'. Setting the field type to anything else will be undone by the
  48588. * framework. This is because new records that are created without an `id`, will have one generated.
  48589. */
  48590. idProperty: 'id',
  48591. data: null,
  48592. /**
  48593. * @cfg {Object[]/String[]} fields
  48594. * The {@link Ext.data.Model field} definitions for all instances of this Model.
  48595. *
  48596. * __Note:__ this does not set the *values* of each
  48597. * field on an instance, it sets the collection of fields itself.
  48598. *
  48599. * Sample usage:
  48600. *
  48601. * Ext.define('MyApp.model.User', {
  48602. * extend: 'Ext.data.Model',
  48603. *
  48604. * config: {
  48605. * fields: [
  48606. * 'id',
  48607. * {name: 'age', type: 'int'},
  48608. * {name: 'taxRate', type: 'float'}
  48609. * ]
  48610. * }
  48611. * });
  48612. * @accessor
  48613. */
  48614. fields: undefined,
  48615. /**
  48616. * @cfg {Object[]} validations
  48617. * An array of {@link Ext.data.Validations validations} for this model.
  48618. */
  48619. validations: null,
  48620. /**
  48621. * @cfg {Object[]} associations
  48622. * An array of {@link Ext.data.association.Association associations} for this model.
  48623. */
  48624. associations: null,
  48625. /**
  48626. * @cfg {String/Object/String[]/Object[]} hasMany
  48627. * One or more {@link Ext.data.association.HasMany HasMany associations} for this model.
  48628. */
  48629. hasMany: null,
  48630. /**
  48631. * @cfg {String/Object/String[]/Object[]} hasOne
  48632. * One or more {@link Ext.data.association.HasOne HasOne associations} for this model.
  48633. */
  48634. hasOne: null,
  48635. /**
  48636. * @cfg {String/Object/String[]/Object[]} belongsTo
  48637. * One or more {@link Ext.data.association.BelongsTo BelongsTo associations} for this model.
  48638. */
  48639. belongsTo: null,
  48640. /**
  48641. * @cfg {Object/Ext.data.Proxy} proxy
  48642. * The string type of the default Model Proxy.
  48643. * @accessor
  48644. */
  48645. proxy: null,
  48646. /**
  48647. * @cfg {Object/String} identifier
  48648. * The identifier strategy used when creating new instances of this Model that don't have an id defined.
  48649. * By default this uses the simple identifier strategy that generates id's like 'ext-record-12'. If you are
  48650. * saving these records in localstorage using a LocalStorage proxy you need to ensure that this identifier
  48651. * strategy is set to something that always generates unique id's. We provide one strategy by default that
  48652. * generates these unique id's which is the uuid strategy.
  48653. */
  48654. identifier: {
  48655. type: 'simple'
  48656. },
  48657. /**
  48658. * @cfg {String} clientIdProperty
  48659. * The name of a property that is used for submitting this Model's unique client-side identifier
  48660. * to the server when multiple phantom records are saved as part of the same {@link Ext.data.Operation Operation}.
  48661. * In such a case, the server response should include the client id for each record
  48662. * so that the server response data can be used to update the client-side records if necessary.
  48663. * This property cannot have the same name as any of this Model's fields.
  48664. * @accessor
  48665. */
  48666. clientIdProperty: 'clientId',
  48667. /**
  48668. * @method getIsErased Returns `true` if the record has been erased on the server.
  48669. */
  48670. isErased: false,
  48671. /**
  48672. * @cfg {Boolean} useCache
  48673. * Change this to `false` if you want to ensure that new instances are created for each id. For example,
  48674. * this is needed when adding the same tree nodes to multiple trees.
  48675. */
  48676. useCache: true
  48677. },
  48678. staticConfigs: [
  48679. 'idProperty',
  48680. 'fields',
  48681. 'validations',
  48682. 'associations',
  48683. 'hasMany',
  48684. 'hasOne',
  48685. 'belongsTo',
  48686. 'clientIdProperty',
  48687. 'identifier',
  48688. 'useCache',
  48689. 'proxy'
  48690. ],
  48691. statics: {
  48692. EDIT : 'edit',
  48693. REJECT : 'reject',
  48694. COMMIT : 'commit',
  48695. cache: {},
  48696. generateProxyMethod: function(name) {
  48697. return function() {
  48698. var prototype = this.prototype;
  48699. return prototype[name].apply(prototype, arguments);
  48700. };
  48701. },
  48702. generateCacheId: function(record, id) {
  48703. var modelName;
  48704. if (record && record.isModel) {
  48705. modelName = record.modelName;
  48706. if (id === undefined) {
  48707. id = record.getId();
  48708. }
  48709. } else {
  48710. modelName = record;
  48711. }
  48712. return modelName.replace(/\./g, '-').toLowerCase() + '-' + id;
  48713. }
  48714. },
  48715. inheritableStatics: {
  48716. /**
  48717. * Asynchronously loads a model instance by id. Sample usage:
  48718. *
  48719. * MyApp.User = Ext.define('User', {
  48720. * extend: 'Ext.data.Model',
  48721. * fields: [
  48722. * {name: 'id', type: 'int'},
  48723. * {name: 'name', type: 'string'}
  48724. * ]
  48725. * });
  48726. *
  48727. * MyApp.User.load(10, {
  48728. * scope: this,
  48729. * failure: function(record, operation) {
  48730. * //do something if the load failed
  48731. * },
  48732. * success: function(record, operation) {
  48733. * //do something if the load succeeded
  48734. * },
  48735. * callback: function(record, operation) {
  48736. * //do something whether the load succeeded or failed
  48737. * }
  48738. * });
  48739. *
  48740. * @param {Number} id The id of the model to load
  48741. * @param {Object} config (optional) config object containing success, failure and callback functions, plus
  48742. * optional scope
  48743. * @static
  48744. * @inheritable
  48745. */
  48746. load: function(id, config, scope) {
  48747. var proxy = this.getProxy(),
  48748. idProperty = this.getIdProperty(),
  48749. record = null,
  48750. params = {},
  48751. callback, operation;
  48752. scope = scope || (config && config.scope) || this;
  48753. if (Ext.isFunction(config)) {
  48754. config = {
  48755. callback: config,
  48756. scope: scope
  48757. };
  48758. }
  48759. params[idProperty] = id;
  48760. config = Ext.apply({}, config);
  48761. config = Ext.applyIf(config, {
  48762. action: 'read',
  48763. params: params,
  48764. model: this
  48765. });
  48766. operation = Ext.create('Ext.data.Operation', config);
  48767. if (!proxy) {
  48768. Ext.Logger.error('You are trying to load a model that doesn\'t have a Proxy specified');
  48769. }
  48770. callback = function(operation) {
  48771. if (operation.wasSuccessful()) {
  48772. record = operation.getRecords()[0] || null;
  48773. Ext.callback(config.success, scope, [record, operation]);
  48774. } else {
  48775. Ext.callback(config.failure, scope, [record, operation]);
  48776. }
  48777. Ext.callback(config.callback, scope, [record, operation]);
  48778. };
  48779. proxy.read(operation, callback, this);
  48780. }
  48781. },
  48782. /**
  48783. * @property {Boolean} editing
  48784. * @readonly
  48785. * Internal flag used to track whether or not the model instance is currently being edited.
  48786. */
  48787. editing : false,
  48788. /**
  48789. * @property {Boolean} dirty
  48790. * @readonly
  48791. * `true` if this Record has been modified.
  48792. */
  48793. dirty : false,
  48794. /**
  48795. * @property {Boolean} phantom
  48796. * `true` when the record does not yet exist in a server-side database (see {@link #setDirty}).
  48797. * Any record which has a real database pk set as its id property is NOT a phantom -- it's real.
  48798. */
  48799. phantom : false,
  48800. /**
  48801. * Creates new Model instance.
  48802. * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values.
  48803. * @param {Number} id (optional) Unique ID to assign to this model instance.
  48804. * @param [raw]
  48805. * @param [convertedData]
  48806. */
  48807. constructor: function(data, id, raw, convertedData) {
  48808. var me = this,
  48809. cached = null,
  48810. useCache = me.getUseCache(),
  48811. idProperty = me.getIdProperty();
  48812. /**
  48813. * @property {Object} modified key/value pairs of all fields whose values have changed.
  48814. * The value is the original value for the field.
  48815. */
  48816. me.modified = {};
  48817. /**
  48818. * @property {Object} raw The raw data used to create this model if created via a reader.
  48819. */
  48820. me.raw = raw || data || {};
  48821. /**
  48822. * @property {Array} stores
  48823. * An array of {@link Ext.data.Store} objects that this record is bound to.
  48824. */
  48825. me.stores = [];
  48826. data = data || convertedData || {};
  48827. // We begin by checking if an id is passed to the constructor. If this is the case we override
  48828. // any possible id value that was passed in the data.
  48829. if (id || id === 0) {
  48830. // Lets skip using set here since it's so much faster
  48831. data[idProperty] = me.internalId = id;
  48832. }
  48833. id = data[idProperty];
  48834. if (useCache && (id || id === 0)) {
  48835. cached = Ext.data.Model.cache[Ext.data.Model.generateCacheId(this, id)];
  48836. if (cached) {
  48837. return cached.mergeData(convertedData || data || {});
  48838. }
  48839. }
  48840. if (convertedData) {
  48841. me.setConvertedData(data);
  48842. } else {
  48843. me.setData(data);
  48844. }
  48845. // If it does not have an id at this point, we generate it using the id strategy. This means
  48846. // that we will treat this record as a phantom record from now on
  48847. id = me.data[idProperty];
  48848. if (!id && id !== 0) {
  48849. me.data[idProperty] = me.internalId = me.id = me.getIdentifier().generate(me);
  48850. me.phantom = true;
  48851. if (this.associations.length) {
  48852. this.handleInlineAssociationData(data);
  48853. }
  48854. } else {
  48855. me.id = me.getIdentifier().generate(me);
  48856. }
  48857. if (useCache) {
  48858. Ext.data.Model.cache[Ext.data.Model.generateCacheId(me)] = me;
  48859. }
  48860. if (this.init && typeof this.init == 'function') {
  48861. this.init();
  48862. }
  48863. },
  48864. /**
  48865. * Private function that is used when you create a record that already exists in the model cache.
  48866. * In this case we loop over each field, and apply any data to the current instance that is not already
  48867. * marked as being dirty on that instance.
  48868. * @param data
  48869. * @return {Ext.data.Model} This record.
  48870. * @private
  48871. */
  48872. mergeData: function(rawData) {
  48873. var me = this,
  48874. fields = me.getFields().items,
  48875. ln = fields.length,
  48876. modified = me.modified,
  48877. data = me.data,
  48878. i, field, fieldName, value, id;
  48879. for (i = 0; i < ln; i++) {
  48880. field = fields[i];
  48881. fieldName = field._name;
  48882. value = rawData[fieldName];
  48883. if (value !== undefined && !modified.hasOwnProperty(fieldName)) {
  48884. if (field._convert) {
  48885. value = field._convert(value, me);
  48886. }
  48887. data[fieldName] = value;
  48888. }
  48889. }
  48890. if (me.associations.length) {
  48891. me.handleInlineAssociationData(rawData);
  48892. }
  48893. return this;
  48894. },
  48895. /**
  48896. * This method is used to set the data for this Record instance.
  48897. * Note that the existing data is removed. If a field is not specified
  48898. * in the passed data it will use the field's default value. If a convert
  48899. * method is specified for the field it will be called on the value.
  48900. * @param rawData
  48901. * @return {Ext.data.Model} This record.
  48902. */
  48903. setData: function(rawData) {
  48904. var me = this,
  48905. fields = me.fields.items,
  48906. ln = fields.length,
  48907. isArray = Ext.isArray(rawData),
  48908. data = me._data = me.data = {},
  48909. i, field, name, value, convert, id;
  48910. if (!rawData) {
  48911. return me;
  48912. }
  48913. for (i = 0; i < ln; i++) {
  48914. field = fields[i];
  48915. name = field._name;
  48916. convert = field._convert;
  48917. if (isArray) {
  48918. value = rawData[i];
  48919. }
  48920. else {
  48921. value = rawData[name];
  48922. if (typeof value == 'undefined') {
  48923. value = field._defaultValue;
  48924. }
  48925. }
  48926. if (convert) {
  48927. value = field._convert(value, me);
  48928. }
  48929. data[name] = value;
  48930. }
  48931. id = me.getId();
  48932. if (me.associations.length && (id || id === 0)) {
  48933. me.handleInlineAssociationData(rawData);
  48934. }
  48935. return me;
  48936. },
  48937. handleInlineAssociationData: function(data) {
  48938. var associations = this.associations.items,
  48939. ln = associations.length,
  48940. i, association, associationData, reader, proxy, associationKey;
  48941. for (i = 0; i < ln; i++) {
  48942. association = associations[i];
  48943. associationKey = association.getAssociationKey();
  48944. associationData = data[associationKey];
  48945. if (associationData) {
  48946. reader = association.getReader();
  48947. if (!reader) {
  48948. proxy = association.getAssociatedModel().getProxy();
  48949. // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
  48950. if (proxy) {
  48951. reader = proxy.getReader();
  48952. } else {
  48953. reader = new Ext.data.JsonReader({
  48954. model: association.getAssociatedModel()
  48955. });
  48956. }
  48957. }
  48958. association.read(this, reader, associationData);
  48959. }
  48960. }
  48961. },
  48962. /**
  48963. * Sets the model instance's id field to the given id.
  48964. * @param {Number/String} id The new id
  48965. */
  48966. setId: function(id) {
  48967. var currentId = this.getId();
  48968. // Lets use the direct property instead of getter here
  48969. this.set(this.getIdProperty(), id);
  48970. // We don't update the this.id since we don't want to break listeners that already
  48971. // exist on the record instance.
  48972. this.internalId = id;
  48973. if (this.getUseCache()) {
  48974. delete Ext.data.Model.cache[Ext.data.Model.generateCacheId(this, currentId)];
  48975. Ext.data.Model.cache[Ext.data.Model.generateCacheId(this)] = this;
  48976. }
  48977. },
  48978. /**
  48979. * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}.
  48980. * @return {Number/String} The `id`.
  48981. */
  48982. getId: function() {
  48983. // Lets use the direct property instead of getter here
  48984. return this.get(this.getIdProperty());
  48985. },
  48986. /**
  48987. * This sets the data directly without converting and applying default values.
  48988. * This method is used when a Record gets instantiated by a Reader. Only use
  48989. * this when you are sure you are passing correctly converted data.
  48990. * @param data
  48991. * @return {Ext.data.Model} This Record.
  48992. */
  48993. setConvertedData: function(data) {
  48994. this._data = this.data = data;
  48995. return this;
  48996. },
  48997. /**
  48998. * Returns the value of the given field.
  48999. * @param {String} fieldName The field to fetch the value for.
  49000. * @return {Object} The value.
  49001. */
  49002. get: function(fieldName) {
  49003. return this.data[fieldName];
  49004. },
  49005. /**
  49006. * Sets the given field to the given value, marks the instance as dirty.
  49007. * @param {String/Object} fieldName The field to set, or an object containing key/value pairs.
  49008. * @param {Object} value The value to set.
  49009. */
  49010. set: function(fieldName, value) {
  49011. var me = this,
  49012. // We are using the fields map since it saves lots of function calls
  49013. fieldMap = me.fields.map,
  49014. modified = me.modified,
  49015. notEditing = !me.editing,
  49016. modifiedCount = 0,
  49017. modifiedFieldNames = [],
  49018. field, key, i, currentValue, ln, convert;
  49019. /*
  49020. * If we're passed an object, iterate over that object. NOTE: we pull out fields with a convert function and
  49021. * set those last so that all other possible data is set before the convert function is called
  49022. */
  49023. if (arguments.length == 1) {
  49024. for (key in fieldName) {
  49025. if (fieldName.hasOwnProperty(key)) {
  49026. //here we check for the custom convert function. Note that if a field doesn't have a convert function,
  49027. //we default it to its type's convert function, so we have to check that here. This feels rather dirty.
  49028. field = fieldMap[key];
  49029. if (field && field.hasCustomConvert()) {
  49030. modifiedFieldNames.push(key);
  49031. continue;
  49032. }
  49033. if (!modifiedCount && notEditing) {
  49034. me.beginEdit();
  49035. }
  49036. ++modifiedCount;
  49037. me.set(key, fieldName[key]);
  49038. }
  49039. }
  49040. ln = modifiedFieldNames.length;
  49041. if (ln) {
  49042. if (!modifiedCount && notEditing) {
  49043. me.beginEdit();
  49044. }
  49045. modifiedCount += ln;
  49046. for (i = 0; i < ln; i++) {
  49047. field = modifiedFieldNames[i];
  49048. me.set(field, fieldName[field]);
  49049. }
  49050. }
  49051. if (notEditing && modifiedCount) {
  49052. me.endEdit(false, modifiedFieldNames);
  49053. }
  49054. } else {
  49055. field = fieldMap[fieldName];
  49056. convert = field && field.getConvert();
  49057. if (convert) {
  49058. value = convert.call(field, value, me);
  49059. }
  49060. currentValue = me.data[fieldName];
  49061. me.data[fieldName] = value;
  49062. if (field && !me.isEqual(currentValue, value)) {
  49063. if (modified.hasOwnProperty(fieldName)) {
  49064. if (me.isEqual(modified[fieldName], value)) {
  49065. // the original value in me.modified equals the new value, so the
  49066. // field is no longer modified
  49067. delete modified[fieldName];
  49068. // we might have removed the last modified field, so check to see if
  49069. // there are any modified fields remaining and correct me.dirty:
  49070. me.dirty = false;
  49071. for (key in modified) {
  49072. if (modified.hasOwnProperty(key)) {
  49073. me.dirty = true;
  49074. break;
  49075. }
  49076. }
  49077. }
  49078. } else {
  49079. me.dirty = true;
  49080. // We only go one level back?
  49081. modified[fieldName] = currentValue;
  49082. }
  49083. }
  49084. if (notEditing) {
  49085. me.afterEdit([fieldName], modified);
  49086. }
  49087. }
  49088. },
  49089. /**
  49090. * Checks if two values are equal, taking into account certain
  49091. * special factors, for example dates.
  49092. * @private
  49093. * @param {Object} a The first value.
  49094. * @param {Object} b The second value.
  49095. * @return {Boolean} `true` if the values are equal.
  49096. */
  49097. isEqual: function(a, b){
  49098. if (Ext.isDate(a) && Ext.isDate(b)) {
  49099. return a.getTime() === b.getTime();
  49100. }
  49101. return a === b;
  49102. },
  49103. /**
  49104. * Begins an edit. While in edit mode, no events (e.g. the `update` event) are relayed to the containing store.
  49105. * When an edit has begun, it must be followed by either {@link #endEdit} or {@link #cancelEdit}.
  49106. */
  49107. beginEdit: function() {
  49108. var me = this;
  49109. if (!me.editing) {
  49110. me.editing = true;
  49111. // We save the current states of dirty, data and modified so that when we
  49112. // cancel the edit, we can put it back to this state
  49113. me.dirtySave = me.dirty;
  49114. me.dataSave = Ext.apply({}, me.data);
  49115. me.modifiedSave = Ext.apply({}, me.modified);
  49116. }
  49117. },
  49118. /**
  49119. * Cancels all changes made in the current edit operation.
  49120. */
  49121. cancelEdit: function() {
  49122. var me = this;
  49123. if (me.editing) {
  49124. me.editing = false;
  49125. // Reset the modified state, nothing changed since the edit began
  49126. me.modified = me.modifiedSave;
  49127. me.data = me.dataSave;
  49128. me.dirty = me.dirtySave;
  49129. // Delete the saved states
  49130. delete me.modifiedSave;
  49131. delete me.dataSave;
  49132. delete me.dirtySave;
  49133. }
  49134. },
  49135. /**
  49136. * Ends an edit. If any data was modified, the containing store is notified (ie, the store's `update` event will
  49137. * fire).
  49138. * @param {Boolean} silent `true` to not notify the store of the change.
  49139. * @param {String[]} modifiedFieldNames Array of field names changed during edit.
  49140. */
  49141. endEdit: function(silent, modifiedFieldNames) {
  49142. var me = this;
  49143. if (me.editing) {
  49144. me.editing = false;
  49145. if (silent !== true && (me.changedWhileEditing())) {
  49146. me.afterEdit(modifiedFieldNames || Ext.Object.getKeys(this.modified), this.modified);
  49147. }
  49148. delete me.modifiedSave;
  49149. delete me.dataSave;
  49150. delete me.dirtySave;
  49151. }
  49152. },
  49153. /**
  49154. * Checks if the underlying data has changed during an edit. This doesn't necessarily
  49155. * mean the record is dirty, however we still need to notify the store since it may need
  49156. * to update any views.
  49157. * @private
  49158. * @return {Boolean} `true` if the underlying data has changed during an edit.
  49159. */
  49160. changedWhileEditing: function() {
  49161. var me = this,
  49162. saved = me.dataSave,
  49163. data = me.data,
  49164. key;
  49165. for (key in data) {
  49166. if (data.hasOwnProperty(key)) {
  49167. if (!me.isEqual(data[key], saved[key])) {
  49168. return true;
  49169. }
  49170. }
  49171. }
  49172. return false;
  49173. },
  49174. /**
  49175. * Gets a hash of only the fields that have been modified since this Model was created or committed.
  49176. * @return {Object}
  49177. */
  49178. getChanges : function() {
  49179. var modified = this.modified,
  49180. changes = {},
  49181. field;
  49182. for (field in modified) {
  49183. if (modified.hasOwnProperty(field)) {
  49184. changes[field] = this.get(field);
  49185. }
  49186. }
  49187. return changes;
  49188. },
  49189. /**
  49190. * Returns `true` if the passed field name has been `{@link #modified}` since the load or last commit.
  49191. * @param {String} fieldName {@link Ext.data.Field#name}
  49192. * @return {Boolean}
  49193. */
  49194. isModified : function(fieldName) {
  49195. return this.modified.hasOwnProperty(fieldName);
  49196. },
  49197. /**
  49198. * Saves the model instance using the configured proxy.
  49199. *
  49200. * @param {Object/Function} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
  49201. * If you pass a function, this will automatically become the callback method. For convenience the config
  49202. * object may also contain `success` and `failure` methods in addition to `callback` - they will all be invoked
  49203. * with the Model and Operation as arguments.
  49204. * @param {Object} scope The scope to run your callback method in. This is only used if you passed a function
  49205. * as the first argument.
  49206. * @return {Ext.data.Model} The Model instance
  49207. */
  49208. save: function(options, scope) {
  49209. var me = this,
  49210. action = me.phantom ? 'create' : 'update',
  49211. proxy = me.getProxy(),
  49212. operation,
  49213. callback;
  49214. if (!proxy) {
  49215. Ext.Logger.error('You are trying to save a model instance that doesn\'t have a Proxy specified');
  49216. }
  49217. options = options || {};
  49218. scope = scope || me;
  49219. if (Ext.isFunction(options)) {
  49220. options = {
  49221. callback: options,
  49222. scope: scope
  49223. };
  49224. }
  49225. Ext.applyIf(options, {
  49226. records: [me],
  49227. action : action,
  49228. model: me.self
  49229. });
  49230. operation = Ext.create('Ext.data.Operation', options);
  49231. callback = function(operation) {
  49232. if (operation.wasSuccessful()) {
  49233. Ext.callback(options.success, scope, [me, operation]);
  49234. } else {
  49235. Ext.callback(options.failure, scope, [me, operation]);
  49236. }
  49237. Ext.callback(options.callback, scope, [me, operation]);
  49238. };
  49239. proxy[action](operation, callback, me);
  49240. return me;
  49241. },
  49242. /**
  49243. * Destroys the record using the configured proxy. This will create a 'destroy' operation.
  49244. * Note that this doesn't destroy this instance after the server comes back with a response.
  49245. * It will however call `afterErase` on any Stores it is joined to. Stores by default will
  49246. * automatically remove this instance from their data collection.
  49247. *
  49248. * @param {Object/Function} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
  49249. * If you pass a function, this will automatically become the callback method. For convenience the config
  49250. * object may also contain `success` and `failure` methods in addition to `callback` - they will all be invoked
  49251. * with the Model and Operation as arguments.
  49252. * @param {Object} scope The scope to run your callback method in. This is only used if you passed a function
  49253. * as the first argument.
  49254. * @return {Ext.data.Model} The Model instance.
  49255. */
  49256. erase: function(options, scope) {
  49257. var me = this,
  49258. proxy = this.getProxy(),
  49259. operation,
  49260. callback;
  49261. if (!proxy) {
  49262. Ext.Logger.error('You are trying to erase a model instance that doesn\'t have a Proxy specified');
  49263. }
  49264. options = options || {};
  49265. scope = scope || me;
  49266. if (Ext.isFunction(options)) {
  49267. options = {
  49268. callback: options,
  49269. scope: scope
  49270. };
  49271. }
  49272. Ext.applyIf(options, {
  49273. records: [me],
  49274. action : 'destroy',
  49275. model: this.self
  49276. });
  49277. operation = Ext.create('Ext.data.Operation', options);
  49278. callback = function(operation) {
  49279. if (operation.wasSuccessful()) {
  49280. Ext.callback(options.success, scope, [me, operation]);
  49281. } else {
  49282. Ext.callback(options.failure, scope, [me, operation]);
  49283. }
  49284. Ext.callback(options.callback, scope, [me, operation]);
  49285. };
  49286. proxy.destroy(operation, callback, me);
  49287. return me;
  49288. },
  49289. /**
  49290. * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}. Rejects
  49291. * all changes made to the model instance since either creation, or the last commit operation. Modified fields are
  49292. * reverted to their original values.
  49293. *
  49294. * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of reject
  49295. * operations.
  49296. *
  49297. * @param {Boolean} [silent=false] (optional) `true` to skip notification of the owning store of the change.
  49298. */
  49299. reject: function(silent) {
  49300. var me = this,
  49301. modified = me.modified,
  49302. field;
  49303. for (field in modified) {
  49304. if (modified.hasOwnProperty(field)) {
  49305. if (typeof modified[field] != "function") {
  49306. me.data[field] = modified[field];
  49307. }
  49308. }
  49309. }
  49310. me.dirty = false;
  49311. me.editing = false;
  49312. me.modified = {};
  49313. if (silent !== true) {
  49314. me.afterReject();
  49315. }
  49316. },
  49317. /**
  49318. * Usually called by the {@link Ext.data.Store} which owns the model instance. Commits all changes made to the
  49319. * instance since either creation or the last commit operation.
  49320. *
  49321. * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of commit
  49322. * operations.
  49323. *
  49324. * @param {Boolean} [silent=false] (optional) `true` to skip notification of the owning store of the change.
  49325. */
  49326. commit: function(silent) {
  49327. var me = this,
  49328. modified = this.modified;
  49329. me.phantom = me.dirty = me.editing = false;
  49330. me.modified = {};
  49331. if (silent !== true) {
  49332. me.afterCommit(modified);
  49333. }
  49334. },
  49335. /**
  49336. * @private
  49337. * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
  49338. * `afterEdit` method is called.
  49339. * @param {String[]} modifiedFieldNames Array of field names changed during edit.
  49340. */
  49341. afterEdit : function(modifiedFieldNames, modified) {
  49342. this.notifyStores('afterEdit', modifiedFieldNames, modified);
  49343. },
  49344. /**
  49345. * @private
  49346. * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
  49347. * `afterReject` method is called.
  49348. */
  49349. afterReject : function() {
  49350. this.notifyStores("afterReject");
  49351. },
  49352. /**
  49353. * @private
  49354. * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
  49355. * `afterCommit` method is called.
  49356. */
  49357. afterCommit: function(modified) {
  49358. this.notifyStores('afterCommit', Ext.Object.getKeys(modified || {}), modified);
  49359. },
  49360. /**
  49361. * @private
  49362. * Helper function used by {@link #afterEdit}, {@link #afterReject}, and {@link #afterCommit}. Calls the given method on the
  49363. * {@link Ext.data.Store store} that this instance has {@link #join join}ed, if any. The store function
  49364. * will always be called with the model instance as its single argument.
  49365. * @param {String} fn The function to call on the store.
  49366. */
  49367. notifyStores: function(fn) {
  49368. var args = Ext.Array.clone(arguments),
  49369. stores = this.stores,
  49370. ln = stores.length,
  49371. i, store;
  49372. args[0] = this;
  49373. for (i = 0; i < ln; ++i) {
  49374. store = stores[i];
  49375. if (store !== undefined && typeof store[fn] == "function") {
  49376. store[fn].apply(store, args);
  49377. }
  49378. }
  49379. },
  49380. /**
  49381. * Creates a copy (clone) of this Model instance.
  49382. *
  49383. * @param {String} id A new `id`. If you don't specify this a new `id` will be generated for you.
  49384. * To generate a phantom instance with a new `id` use:
  49385. *
  49386. * var rec = record.copy(); // clone the record with a new id
  49387. *
  49388. * @return {Ext.data.Model}
  49389. */
  49390. copy: function(newId) {
  49391. var me = this,
  49392. idProperty = me.getIdProperty(),
  49393. raw = Ext.apply({}, me.raw),
  49394. data = Ext.apply({}, me.data);
  49395. delete raw[idProperty];
  49396. delete data[idProperty];
  49397. return new me.self(null, newId, raw, data);
  49398. },
  49399. /**
  49400. * Returns an object containing the data set on this record. This method also allows you to
  49401. * retrieve all the associated data. Note that if you should always use this method if you
  49402. * need all the associated data, since the data property on the record instance is not
  49403. * ensured to be updated at all times.
  49404. * @param {Boolean} includeAssociated `true` to include the associated data.
  49405. * @return {Object} The data.
  49406. */
  49407. getData: function(includeAssociated) {
  49408. var data = this.data;
  49409. if (includeAssociated === true) {
  49410. Ext.apply(data, this.getAssociatedData());
  49411. }
  49412. return data;
  49413. },
  49414. /**
  49415. * Gets all of the data from this Models *loaded* associations. It does this recursively - for example if we have a
  49416. * User which `hasMany` Orders, and each Order `hasMany` OrderItems, it will return an object like this:
  49417. *
  49418. * {
  49419. * orders: [
  49420. * {
  49421. * id: 123,
  49422. * status: 'shipped',
  49423. * orderItems: [
  49424. * // ...
  49425. * ]
  49426. * }
  49427. * ]
  49428. * }
  49429. *
  49430. * @return {Object} The nested data set for the Model's loaded associations.
  49431. */
  49432. getAssociatedData: function() {
  49433. return this.prepareAssociatedData(this, [], null);
  49434. },
  49435. /**
  49436. * @private
  49437. * This complex-looking method takes a given Model instance and returns an object containing all data from
  49438. * all of that Model's *loaded* associations. See {@link #getAssociatedData}
  49439. * @param {Ext.data.Model} record The Model instance
  49440. * @param {String[]} ids PRIVATE. The set of Model instance `internalIds` that have already been loaded
  49441. * @param {String} associationType (optional) The name of the type of association to limit to.
  49442. * @return {Object} The nested data set for the Model's loaded associations.
  49443. */
  49444. prepareAssociatedData: function(record, ids, associationType) {
  49445. //we keep track of all of the internalIds of the models that we have loaded so far in here
  49446. var associations = record.associations.items,
  49447. associationCount = associations.length,
  49448. associationData = {},
  49449. associatedStore, associationName, associatedRecords, associatedRecord,
  49450. associatedRecordCount, association, id, i, j, type, allow;
  49451. for (i = 0; i < associationCount; i++) {
  49452. association = associations[i];
  49453. associationName = association.getName();
  49454. type = association.getType();
  49455. allow = true;
  49456. if (associationType) {
  49457. allow = type == associationType;
  49458. }
  49459. if (allow && type.toLowerCase() == 'hasmany') {
  49460. //this is the hasMany store filled with the associated data
  49461. associatedStore = record[association.getStoreName()];
  49462. //we will use this to contain each associated record's data
  49463. associationData[associationName] = [];
  49464. //if it's loaded, put it into the association data
  49465. if (associatedStore && associatedStore.getCount() > 0) {
  49466. associatedRecords = associatedStore.data.items;
  49467. associatedRecordCount = associatedRecords.length;
  49468. //now we're finally iterating over the records in the association. We do this recursively
  49469. for (j = 0; j < associatedRecordCount; j++) {
  49470. associatedRecord = associatedRecords[j];
  49471. // Use the id, since it is prefixed with the model name, guaranteed to be unique
  49472. id = associatedRecord.id;
  49473. //when we load the associations for a specific model instance we add it to the set of loaded ids so that
  49474. //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
  49475. if (Ext.Array.indexOf(ids, id) == -1) {
  49476. ids.push(id);
  49477. associationData[associationName][j] = associatedRecord.getData();
  49478. Ext.apply(associationData[associationName][j], this.prepareAssociatedData(associatedRecord, ids, associationType));
  49479. }
  49480. }
  49481. }
  49482. } else if (allow && (type.toLowerCase() == 'belongsto' || type.toLowerCase() == 'hasone')) {
  49483. associatedRecord = record[association.getInstanceName()];
  49484. if (associatedRecord !== undefined) {
  49485. id = associatedRecord.id;
  49486. if (Ext.Array.indexOf(ids, id) === -1) {
  49487. ids.push(id);
  49488. associationData[associationName] = associatedRecord.getData();
  49489. Ext.apply(associationData[associationName], this.prepareAssociatedData(associatedRecord, ids, associationType));
  49490. }
  49491. }
  49492. }
  49493. }
  49494. return associationData;
  49495. },
  49496. /**
  49497. * By joining this model to an instance of a class, this model will automatically try to
  49498. * call certain template methods on that instance ({@link #afterEdit}, {@link #afterCommit}, {@link Ext.data.Store#afterErase}).
  49499. * For example, a Store calls join and unjoin whenever you add or remove a record to it's data collection.
  49500. * This way a Store can get notified of any changes made to this record.
  49501. * This functionality is usually only required when creating custom components.
  49502. * @param {Ext.data.Store} store The store to which this model has been added.
  49503. */
  49504. join: function(store) {
  49505. Ext.Array.include(this.stores, store);
  49506. },
  49507. /**
  49508. * This un-joins this record from an instance of a class. Look at the documentation for {@link #join}
  49509. * for more information about joining records to class instances.
  49510. * @param {Ext.data.Store} store The store from which this model has been removed.
  49511. */
  49512. unjoin: function(store) {
  49513. Ext.Array.remove(this.stores, store);
  49514. },
  49515. /**
  49516. * Marks this **Record** as `{@link #dirty}`. This method is used internally when adding `{@link #phantom}` records
  49517. * to a {@link Ext.data.proxy.Server#writer writer enabled store}.
  49518. *
  49519. * Marking a record `{@link #dirty}` causes the phantom to be returned by {@link Ext.data.Store#getUpdatedRecords}
  49520. * where it will have a create action composed for it during {@link Ext.data.Model#save model save} operations.
  49521. */
  49522. setDirty : function() {
  49523. var me = this,
  49524. name;
  49525. me.dirty = true;
  49526. me.fields.each(function(field) {
  49527. if (field.getPersist()) {
  49528. name = field.getName();
  49529. me.modified[name] = me.get(name);
  49530. }
  49531. });
  49532. },
  49533. /**
  49534. * Validates the current data against all of its configured {@link #cfg-validations}.
  49535. * @return {Ext.data.Errors} The errors object.
  49536. */
  49537. validate: function() {
  49538. var errors = Ext.create('Ext.data.Errors'),
  49539. validations = this.getValidations().items,
  49540. validators = Ext.data.Validations,
  49541. length, validation, field, valid, type, i;
  49542. if (validations) {
  49543. length = validations.length;
  49544. for (i = 0; i < length; i++) {
  49545. validation = validations[i];
  49546. field = validation.field || validation.name;
  49547. type = validation.type;
  49548. valid = validators[type](validation, this.get(field));
  49549. if (!valid) {
  49550. errors.add(Ext.create('Ext.data.Error', {
  49551. field : field,
  49552. message: validation.message || validators.getMessage(type)
  49553. }));
  49554. }
  49555. }
  49556. }
  49557. return errors;
  49558. },
  49559. /**
  49560. * Checks if the model is valid. See {@link #validate}.
  49561. * @return {Boolean} `true` if the model is valid.
  49562. */
  49563. isValid: function(){
  49564. return this.validate().isValid();
  49565. },
  49566. /**
  49567. * Returns a url-suitable string for this model instance. By default this just returns the name of the Model class
  49568. * followed by the instance ID - for example an instance of MyApp.model.User with ID 123 will return 'user/123'.
  49569. * @return {String} The url string for this model instance.
  49570. */
  49571. toUrl: function() {
  49572. var pieces = this.$className.split('.'),
  49573. name = pieces[pieces.length - 1].toLowerCase();
  49574. return name + '/' + this.getId();
  49575. },
  49576. /**
  49577. * Destroys this model instance. Note that this doesn't do a 'destroy' operation. If you want to destroy
  49578. * the record in your localStorage or on the server you should use the {@link #erase} method.
  49579. */
  49580. destroy: function() {
  49581. var me = this;
  49582. me.notifyStores('afterErase', me);
  49583. if (me.getUseCache()) {
  49584. delete Ext.data.Model.cache[Ext.data.Model.generateCacheId(me)];
  49585. }
  49586. me.raw = me.stores = me.modified = null;
  49587. me.callParent(arguments);
  49588. },
  49589. //<debug>
  49590. markDirty : function() {
  49591. if (Ext.isDefined(Ext.Logger)) {
  49592. Ext.Logger.deprecate('Ext.data.Model: markDirty has been deprecated. Use setDirty instead.');
  49593. }
  49594. return this.setDirty.apply(this, arguments);
  49595. },
  49596. //</debug>
  49597. applyProxy: function(proxy, currentProxy) {
  49598. return Ext.factory(proxy, Ext.data.Proxy, currentProxy, 'proxy');
  49599. },
  49600. updateProxy: function(proxy) {
  49601. if (proxy) {
  49602. proxy.setModel(this.self);
  49603. }
  49604. },
  49605. applyAssociations: function(associations) {
  49606. if (associations) {
  49607. this.addAssociations(associations, 'hasMany');
  49608. }
  49609. },
  49610. applyBelongsTo: function(belongsTo) {
  49611. if (belongsTo) {
  49612. this.addAssociations(belongsTo, 'belongsTo');
  49613. }
  49614. },
  49615. applyHasMany: function(hasMany) {
  49616. if (hasMany) {
  49617. this.addAssociations(hasMany, 'hasMany');
  49618. }
  49619. },
  49620. applyHasOne: function(hasOne) {
  49621. if (hasOne) {
  49622. this.addAssociations(hasOne, 'hasOne');
  49623. }
  49624. },
  49625. addAssociations: function(associations, defaultType) {
  49626. var ln, i, association,
  49627. name = this.self.modelName,
  49628. associationsCollection = this.self.associations,
  49629. onCreatedFn;
  49630. associations = Ext.Array.from(associations);
  49631. for (i = 0, ln = associations.length; i < ln; i++) {
  49632. association = associations[i];
  49633. if (!Ext.isObject(association)) {
  49634. association = {model: association};
  49635. }
  49636. Ext.applyIf(association, {
  49637. type: defaultType,
  49638. ownerModel: name,
  49639. associatedModel: association.model
  49640. });
  49641. delete association.model;
  49642. onCreatedFn = Ext.Function.bind(function(associationName) {
  49643. associationsCollection.add(Ext.data.association.Association.create(this));
  49644. }, association);
  49645. Ext.ClassManager.onCreated(onCreatedFn, this, (typeof association.associatedModel === 'string') ? association.associatedModel : Ext.getClassName(association.associatedModel));
  49646. }
  49647. },
  49648. applyValidations: function(validations) {
  49649. if (validations) {
  49650. if (!Ext.isArray(validations)) {
  49651. validations = [validations];
  49652. }
  49653. this.addValidations(validations);
  49654. }
  49655. },
  49656. addValidations: function(validations) {
  49657. this.self.validations.addAll(validations);
  49658. },
  49659. /**
  49660. * @method setFields
  49661. * Updates the collection of Fields that all instances of this Model use. **Does not** update field values in a Model
  49662. * instance (use {@link #set} for that), instead this updates which fields are available on the Model class. This
  49663. * is normally used when creating or updating Model definitions dynamically, for example if you allow your users to
  49664. * define their own Models and save the fields configuration to a database, this method allows you to change those
  49665. * fields later.
  49666. * @return {Array}
  49667. */
  49668. applyFields: function(fields) {
  49669. var superFields = this.superclass.fields;
  49670. if (superFields) {
  49671. fields = superFields.items.concat(fields || []);
  49672. }
  49673. return fields || [];
  49674. },
  49675. updateFields: function(fields) {
  49676. var ln = fields.length,
  49677. me = this,
  49678. prototype = me.self.prototype,
  49679. idProperty = this.getIdProperty(),
  49680. idField, fieldsCollection, field, i;
  49681. /**
  49682. * @property {Ext.util.MixedCollection} fields
  49683. * The fields defined on this model.
  49684. */
  49685. fieldsCollection = me._fields = me.fields = new Ext.util.Collection(prototype.getFieldName);
  49686. for (i = 0; i < ln; i++) {
  49687. field = fields[i];
  49688. if (!field.isField) {
  49689. field = new Ext.data.Field(fields[i]);
  49690. }
  49691. fieldsCollection.add(field);
  49692. }
  49693. // We want every Model to have an id property field
  49694. idField = fieldsCollection.get(idProperty);
  49695. if (!idField) {
  49696. fieldsCollection.add(new Ext.data.Field(idProperty));
  49697. } else {
  49698. idField.setType('auto');
  49699. }
  49700. fieldsCollection.addSorter(prototype.sortConvertFields);
  49701. },
  49702. applyIdentifier: function(identifier) {
  49703. if (typeof identifier === 'string') {
  49704. identifier = {
  49705. type: identifier
  49706. };
  49707. }
  49708. return Ext.factory(identifier, Ext.data.identifier.Simple, this.getIdentifier(), 'data.identifier');
  49709. },
  49710. /**
  49711. * This method is used by the fields collection to retrieve the key for a field
  49712. * based on it's name.
  49713. * @param field
  49714. * @return {String}
  49715. * @private
  49716. */
  49717. getFieldName: function(field) {
  49718. return field.getName();
  49719. },
  49720. /**
  49721. * This method is being used to sort the fields based on their convert method. If
  49722. * a field has a custom convert method, we ensure its more to the bottom of the collection.
  49723. * @param field1
  49724. * @param field2
  49725. * @return {Number}
  49726. * @private
  49727. */
  49728. sortConvertFields: function(field1, field2) {
  49729. var f1SpecialConvert = field1.hasCustomConvert(),
  49730. f2SpecialConvert = field2.hasCustomConvert();
  49731. if (f1SpecialConvert && !f2SpecialConvert) {
  49732. return 1;
  49733. }
  49734. if (!f1SpecialConvert && f2SpecialConvert) {
  49735. return -1;
  49736. }
  49737. return 0;
  49738. },
  49739. /**
  49740. * @private
  49741. */
  49742. onClassExtended: function(cls, data, hooks) {
  49743. var onBeforeClassCreated = hooks.onBeforeCreated,
  49744. Model = this,
  49745. prototype = Model.prototype,
  49746. configNameCache = Ext.Class.configNameCache,
  49747. staticConfigs = prototype.staticConfigs.concat(data.staticConfigs || []),
  49748. defaultConfig = prototype.config,
  49749. config = data.config || {},
  49750. key;
  49751. // Convert old properties in data into a config object
  49752. data.config = config;
  49753. hooks.onBeforeCreated = function(cls, data) {
  49754. var dependencies = [],
  49755. prototype = cls.prototype,
  49756. statics = {},
  49757. config = prototype.config,
  49758. staticConfigsLn = staticConfigs.length,
  49759. copyMethods = ['set', 'get'],
  49760. copyMethodsLn = copyMethods.length,
  49761. associations = config.associations || [],
  49762. name = Ext.getClassName(cls),
  49763. key, methodName, i, j, ln;
  49764. // Create static setters and getters for each config option
  49765. for (i = 0; i < staticConfigsLn; i++) {
  49766. key = staticConfigs[i];
  49767. for (j = 0; j < copyMethodsLn; j++) {
  49768. methodName = configNameCache[key][copyMethods[j]];
  49769. if (methodName in prototype) {
  49770. statics[methodName] = Model.generateProxyMethod(methodName);
  49771. }
  49772. }
  49773. }
  49774. cls.addStatics(statics);
  49775. // Save modelName on class and its prototype
  49776. cls.modelName = name;
  49777. prototype.modelName = name;
  49778. // Take out dependencies on other associations and the proxy type
  49779. if (config.belongsTo) {
  49780. dependencies.push('association.belongsto');
  49781. }
  49782. if (config.hasMany) {
  49783. dependencies.push('association.hasmany');
  49784. }
  49785. if (config.hasOne) {
  49786. dependencies.push('association.hasone');
  49787. }
  49788. for (i = 0,ln = associations.length; i < ln; ++i) {
  49789. dependencies.push('association.' + associations[i].type.toLowerCase());
  49790. }
  49791. if (config.identifier) {
  49792. if (typeof config.identifier === 'string') {
  49793. dependencies.push('data.identifier.' + config.identifier);
  49794. }
  49795. else if (typeof config.identifier.type === 'string') {
  49796. dependencies.push('data.identifier.' + config.identifier.type);
  49797. }
  49798. }
  49799. if (config.proxy) {
  49800. if (typeof config.proxy === 'string') {
  49801. dependencies.push('proxy.' + config.proxy);
  49802. }
  49803. else if (typeof config.proxy.type === 'string') {
  49804. dependencies.push('proxy.' + config.proxy.type);
  49805. }
  49806. }
  49807. if (config.validations) {
  49808. dependencies.push('Ext.data.Validations');
  49809. }
  49810. Ext.require(dependencies, function() {
  49811. Ext.Function.interceptBefore(hooks, 'onCreated', function() {
  49812. Ext.data.ModelManager.registerType(name, cls);
  49813. var superCls = cls.prototype.superclass;
  49814. /**
  49815. * @property {Ext.util.Collection} associations
  49816. * The associations defined on this model.
  49817. */
  49818. cls.prototype.associations = cls.associations = cls.prototype._associations = (superCls && superCls.associations)
  49819. ? superCls.associations.clone()
  49820. : new Ext.util.Collection(function(association) {
  49821. return association.getName();
  49822. });
  49823. /**
  49824. * @property {Ext.util.Collection} validations
  49825. * The validations defined on this model.
  49826. */
  49827. cls.prototype.validations = cls.validations = cls.prototype._validations = (superCls && superCls.validations)
  49828. ? superCls.validations.clone()
  49829. : new Ext.util.Collection(function(validation) {
  49830. return validation.field ? (validation.field + '-' + validation.type) : (validation.name + '-' + validation.type);
  49831. });
  49832. cls.prototype = Ext.Object.chain(cls.prototype);
  49833. cls.prototype.initConfig.call(cls.prototype, config);
  49834. delete cls.prototype.initConfig;
  49835. });
  49836. onBeforeClassCreated.call(Model, cls, data, hooks);
  49837. });
  49838. };
  49839. }
  49840. });
  49841. /**
  49842. * @private
  49843. */
  49844. Ext.define('Ext.util.Grouper', {
  49845. /* Begin Definitions */
  49846. extend: 'Ext.util.Sorter',
  49847. isGrouper: true,
  49848. config: {
  49849. /**
  49850. * @cfg {Function} groupFn This function will be called for each item in the collection to
  49851. * determine the group to which it belongs.
  49852. * @cfg {Object} groupFn.item The current item from the collection
  49853. * @cfg {String} groupFn.return The group identifier for the item
  49854. */
  49855. groupFn: null,
  49856. /**
  49857. * @cfg {String} sortProperty You can define this configuration if you want the groups to be sorted
  49858. * on something other then the group string returned by the `groupFn`.
  49859. */
  49860. sortProperty: null,
  49861. /**
  49862. * @cfg {Function} sorterFn
  49863. * Grouper has a custom sorterFn that cannot be overridden by the user. If a property has been defined
  49864. * on this grouper, we use the default `sorterFn`, else we sort based on the returned group string.
  49865. */
  49866. sorterFn: function(item1, item2) {
  49867. var property = this.getSortProperty(),
  49868. groupFn, group1, group2, modifier;
  49869. groupFn = this.getGroupFn();
  49870. group1 = groupFn.call(this, item1);
  49871. group2 = groupFn.call(this, item2);
  49872. if (property) {
  49873. if (group1 !== group2) {
  49874. return this.defaultSortFn.call(this, item1, item2);
  49875. } else {
  49876. return 0;
  49877. }
  49878. }
  49879. return (group1 > group2) ? 1 : ((group1 < group2) ? -1 : 0);
  49880. }
  49881. },
  49882. /**
  49883. * @private
  49884. * Basic default sorter function that just compares the defined property of each object.
  49885. */
  49886. defaultSortFn: function(item1, item2) {
  49887. var me = this,
  49888. transform = me._transform,
  49889. root = me._root,
  49890. value1, value2,
  49891. property = me._sortProperty;
  49892. if (root !== null) {
  49893. item1 = item1[root];
  49894. item2 = item2[root];
  49895. }
  49896. value1 = item1[property];
  49897. value2 = item2[property];
  49898. if (transform) {
  49899. value1 = transform(value1);
  49900. value2 = transform(value2);
  49901. }
  49902. return value1 > value2 ? 1 : (value1 < value2 ? -1 : 0);
  49903. },
  49904. updateProperty: function(property) {
  49905. this.setGroupFn(this.standardGroupFn);
  49906. },
  49907. standardGroupFn: function(item) {
  49908. var root = this.getRoot(),
  49909. property = this.getProperty(),
  49910. data = item;
  49911. if (root) {
  49912. data = item[root];
  49913. }
  49914. return data[property];
  49915. },
  49916. getGroupString: function(item) {
  49917. var group = this.getGroupFn().call(this, item);
  49918. return typeof group != 'undefined' ? group.toString() : '';
  49919. }
  49920. });
  49921. /**
  49922. * @author Ed Spencer
  49923. * @aside guide stores
  49924. *
  49925. * The Store class encapsulates a client side cache of {@link Ext.data.Model Model} objects. Stores load
  49926. * data via a {@link Ext.data.proxy.Proxy Proxy}, and also provide functions for {@link #sort sorting},
  49927. * {@link #filter filtering} and querying the {@link Ext.data.Model model} instances contained within it.
  49928. *
  49929. * Creating a Store is easy - we just tell it the Model and the Proxy to use to load and save its data:
  49930. *
  49931. * // Set up a {@link Ext.data.Model model} to use in our Store
  49932. * Ext.define("User", {
  49933. * extend: "Ext.data.Model",
  49934. * config: {
  49935. * fields: [
  49936. * {name: "firstName", type: "string"},
  49937. * {name: "lastName", type: "string"},
  49938. * {name: "age", type: "int"},
  49939. * {name: "eyeColor", type: "string"}
  49940. * ]
  49941. * }
  49942. * });
  49943. *
  49944. * var myStore = Ext.create("Ext.data.Store", {
  49945. * model: "User",
  49946. * proxy: {
  49947. * type: "ajax",
  49948. * url : "/users.json",
  49949. * reader: {
  49950. * type: "json",
  49951. * rootProperty: "users"
  49952. * }
  49953. * },
  49954. * autoLoad: true
  49955. * });
  49956. *
  49957. * Ext.create("Ext.List", {
  49958. * fullscreen: true,
  49959. * store: myStore,
  49960. * itemTpl: "{lastName}, {firstName} ({age})"
  49961. * });
  49962. *
  49963. * In the example above we configured an AJAX proxy to load data from the url '/users.json'. We told our Proxy
  49964. * to use a {@link Ext.data.reader.Json JsonReader} to parse the response from the server into Model object -
  49965. * {@link Ext.data.reader.Json see the docs on JsonReader} for details.
  49966. *
  49967. * The external data file, _/users.json_, is as follows:
  49968. *
  49969. * {
  49970. * "success": true,
  49971. * "users": [
  49972. * {
  49973. * "firstName": "Tommy",
  49974. * "lastName": "Maintz",
  49975. * "age": 24,
  49976. * "eyeColor": "green"
  49977. * },
  49978. * {
  49979. * "firstName": "Aaron",
  49980. * "lastName": "Conran",
  49981. * "age": 26,
  49982. * "eyeColor": "blue"
  49983. * },
  49984. * {
  49985. * "firstName": "Jamie",
  49986. * "lastName": "Avins",
  49987. * "age": 37,
  49988. * "eyeColor": "brown"
  49989. * }
  49990. * ]
  49991. * }
  49992. *
  49993. * ## Inline data
  49994. *
  49995. * Stores can also load data inline. Internally, Store converts each of the objects we pass in as {@link #cfg-data}
  49996. * into Model instances:
  49997. *
  49998. * @example
  49999. * // Set up a model to use in our Store
  50000. * Ext.define('User', {
  50001. * extend: 'Ext.data.Model',
  50002. * config: {
  50003. * fields: [
  50004. * {name: 'firstName', type: 'string'},
  50005. * {name: 'lastName', type: 'string'},
  50006. * {name: 'age', type: 'int'},
  50007. * {name: 'eyeColor', type: 'string'}
  50008. * ]
  50009. * }
  50010. * });
  50011. *
  50012. * Ext.create("Ext.data.Store", {
  50013. * storeId: "usersStore",
  50014. * model: "User",
  50015. * data : [
  50016. * {firstName: "Ed", lastName: "Spencer"},
  50017. * {firstName: "Tommy", lastName: "Maintz"},
  50018. * {firstName: "Aaron", lastName: "Conran"},
  50019. * {firstName: "Jamie", lastName: "Avins"}
  50020. * ]
  50021. * });
  50022. *
  50023. * Ext.create("Ext.List", {
  50024. * fullscreen: true,
  50025. * store: "usersStore",
  50026. * itemTpl: "{lastName}, {firstName}"
  50027. * });
  50028. *
  50029. * Loading inline data using the method above is great if the data is in the correct format already (e.g. it doesn't need
  50030. * to be processed by a {@link Ext.data.reader.Reader reader}). If your inline data requires processing to decode the data structure,
  50031. * use a {@link Ext.data.proxy.Memory MemoryProxy} instead (see the {@link Ext.data.proxy.Memory MemoryProxy} docs for an example).
  50032. *
  50033. * Additional data can also be loaded locally using {@link #method-add}.
  50034. *
  50035. * ## Loading Nested Data
  50036. *
  50037. * Applications often need to load sets of associated data - for example a CRM system might load a User and her Orders.
  50038. * Instead of issuing an AJAX request for the User and a series of additional AJAX requests for each Order, we can load a nested dataset
  50039. * and allow the Reader to automatically populate the associated models. Below is a brief example, see the {@link Ext.data.reader.Reader} intro
  50040. * documentation for a full explanation:
  50041. *
  50042. * // Set up a model to use in our Store
  50043. * Ext.define('User', {
  50044. * extend: 'Ext.data.Model',
  50045. * config: {
  50046. * fields: [
  50047. * {name: 'name', type: 'string'},
  50048. * {name: 'id', type: 'int'}
  50049. * ]
  50050. * }
  50051. * });
  50052. *
  50053. * var store = Ext.create('Ext.data.Store', {
  50054. * autoLoad: true,
  50055. * model: "User",
  50056. * proxy: {
  50057. * type: 'ajax',
  50058. * url : 'users.json',
  50059. * reader: {
  50060. * type: 'json',
  50061. * rootProperty: 'users'
  50062. * }
  50063. * }
  50064. * });
  50065. *
  50066. * Ext.create("Ext.List", {
  50067. * fullscreen: true,
  50068. * store: store,
  50069. * itemTpl: "{name} (id: {id})"
  50070. * });
  50071. *
  50072. * Which would consume a response like this:
  50073. *
  50074. * {
  50075. * "users": [
  50076. * {
  50077. * "id": 1,
  50078. * "name": "Ed",
  50079. * "orders": [
  50080. * {
  50081. * "id": 10,
  50082. * "total": 10.76,
  50083. * "status": "invoiced"
  50084. * },
  50085. * {
  50086. * "id": 11,
  50087. * "total": 13.45,
  50088. * "status": "shipped"
  50089. * }
  50090. * ]
  50091. * },
  50092. * {
  50093. * "id": 3,
  50094. * "name": "Tommy",
  50095. * "orders": [
  50096. * ]
  50097. * },
  50098. * {
  50099. * "id": 4,
  50100. * "name": "Jamie",
  50101. * "orders": [
  50102. * {
  50103. * "id": 12,
  50104. * "total": 17.76,
  50105. * "status": "shipped"
  50106. * }
  50107. * ]
  50108. * }
  50109. * ]
  50110. * }
  50111. *
  50112. * See the {@link Ext.data.reader.Reader} intro docs for a full explanation.
  50113. *
  50114. * ## Filtering and Sorting
  50115. *
  50116. * Stores can be sorted and filtered - in both cases either remotely or locally. The {@link #sorters} and {@link #filters} are
  50117. * held inside {@link Ext.util.MixedCollection MixedCollection} instances to make them easy to manage. Usually it is sufficient to
  50118. * either just specify sorters and filters in the Store configuration or call {@link #sort} or {@link #filter}:
  50119. *
  50120. * // Set up a model to use in our Store
  50121. * Ext.define('User', {
  50122. * extend: 'Ext.data.Model',
  50123. * config: {
  50124. * fields: [
  50125. * {name: 'firstName', type: 'string'},
  50126. * {name: 'lastName', type: 'string'},
  50127. * {name: 'age', type: 'int'}
  50128. * ]
  50129. * }
  50130. * });
  50131. *
  50132. * var store = Ext.create("Ext.data.Store", {
  50133. * autoLoad: true,
  50134. * model: "User",
  50135. * proxy: {
  50136. * type: "ajax",
  50137. * url : "users.json",
  50138. * reader: {
  50139. * type: "json",
  50140. * rootProperty: "users"
  50141. * }
  50142. * },
  50143. * sorters: [
  50144. * {
  50145. * property : "age",
  50146. * direction: "DESC"
  50147. * },
  50148. * {
  50149. * property : "firstName",
  50150. * direction: "ASC"
  50151. * }
  50152. * ],
  50153. * filters: [
  50154. * {
  50155. * property: "firstName",
  50156. * value: /Jamie/
  50157. * }
  50158. * ]
  50159. * });
  50160. *
  50161. * Ext.create("Ext.List", {
  50162. * fullscreen: true,
  50163. * store: store,
  50164. * itemTpl: "{lastName}, {firstName} ({age})"
  50165. * });
  50166. *
  50167. * And the data file, _users.json_, is as follows:
  50168. *
  50169. * {
  50170. * "success": true,
  50171. * "users": [
  50172. * {
  50173. * "firstName": "Tommy",
  50174. * "lastName": "Maintz",
  50175. * "age": 24
  50176. * },
  50177. * {
  50178. * "firstName": "Aaron",
  50179. * "lastName": "Conran",
  50180. * "age": 26
  50181. * },
  50182. * {
  50183. * "firstName": "Jamie",
  50184. * "lastName": "Avins",
  50185. * "age": 37
  50186. * }
  50187. * ]
  50188. * }
  50189. *
  50190. * The new Store will keep the configured sorters and filters in the MixedCollection instances mentioned above. By default, sorting
  50191. * and filtering are both performed locally by the Store - see {@link #remoteSort} and {@link #remoteFilter} to allow the server to
  50192. * perform these operations instead.
  50193. *
  50194. * Filtering and sorting after the Store has been instantiated is also easy. Calling {@link #filter} adds another filter to the Store
  50195. * and automatically filters the dataset (calling {@link #filter} with no arguments simply re-applies all existing filters). Note that by
  50196. * default your sorters are automatically reapplied if using local sorting.
  50197. *
  50198. * store.filter('eyeColor', 'Brown');
  50199. *
  50200. * Change the sorting at any time by calling {@link #sort}:
  50201. *
  50202. * store.sort('height', 'ASC');
  50203. *
  50204. * Note that all existing sorters will be removed in favor of the new sorter data (if {@link #sort} is called with no arguments,
  50205. * the existing sorters are just reapplied instead of being removed). To keep existing sorters and add new ones, just add them
  50206. * to the MixedCollection:
  50207. *
  50208. * store.sorters.add(new Ext.util.Sorter({
  50209. * property : 'shoeSize',
  50210. * direction: 'ASC'
  50211. * }));
  50212. *
  50213. * store.sort();
  50214. *
  50215. * ## Registering with StoreManager
  50216. *
  50217. * Any Store that is instantiated with a {@link #storeId} will automatically be registered with the {@link Ext.data.StoreManager StoreManager}.
  50218. * This makes it easy to reuse the same store in multiple views:
  50219. *
  50220. * // this store can be used several times
  50221. * Ext.create('Ext.data.Store', {
  50222. * model: 'User',
  50223. * storeId: 'usersStore'
  50224. * });
  50225. *
  50226. * Ext.create('Ext.List', {
  50227. * store: 'usersStore'
  50228. * // other config goes here
  50229. * });
  50230. *
  50231. * Ext.create('Ext.view.View', {
  50232. * store: 'usersStore'
  50233. * // other config goes here
  50234. * });
  50235. *
  50236. * ## Further Reading
  50237. *
  50238. * Stores are backed up by an ecosystem of classes that enables their operation. To gain a full understanding of these
  50239. * pieces and how they fit together, see:
  50240. *
  50241. * - {@link Ext.data.proxy.Proxy Proxy} - overview of what Proxies are and how they are used
  50242. * - {@link Ext.data.Model Model} - the core class in the data package
  50243. * - {@link Ext.data.reader.Reader Reader} - used by any subclass of {@link Ext.data.proxy.Server ServerProxy} to read a response
  50244. */
  50245. Ext.define('Ext.data.Store', {
  50246. alias: 'store.store',
  50247. extend: 'Ext.Evented',
  50248. requires: [
  50249. 'Ext.util.Collection',
  50250. 'Ext.data.Operation',
  50251. 'Ext.data.proxy.Memory',
  50252. 'Ext.data.Model',
  50253. 'Ext.data.StoreManager',
  50254. 'Ext.util.Grouper'
  50255. ],
  50256. /**
  50257. * @event addrecords
  50258. * Fired when one or more new Model instances have been added to this Store. You should listen
  50259. * for this event if you have to update a representation of the records in this store in your UI.
  50260. * If you need the indices of the records that were added please use the store.indexOf(record) method.
  50261. * @param {Ext.data.Store} store The store
  50262. * @param {Ext.data.Model[]} records The Model instances that were added
  50263. */
  50264. /**
  50265. * @event removerecords
  50266. * Fired when one or more Model instances have been removed from this Store. You should listen
  50267. * for this event if you have to update a representation of the records in this store in your UI.
  50268. * @param {Ext.data.Store} store The Store object
  50269. * @param {Ext.data.Model[]} records The Model instances that was removed
  50270. * @param {Number[]} indices The indices of the records that were removed. These indices already
  50271. * take into account any potential earlier records that you remove. This means that if you loop
  50272. * over the records, you can get its current index in your data representation from this array.
  50273. */
  50274. /**
  50275. * @event updaterecord
  50276. * Fires when a Model instance has been updated
  50277. * @param {Ext.data.Store} this
  50278. * @param {Ext.data.Model} record The Model instance that was updated
  50279. * @param {Number} newIndex If the update changed the index of the record (due to sorting for example), then
  50280. * this gives you the new index in the store.
  50281. * @param {Number} oldIndex If the update changed the index of the record (due to sorting for example), then
  50282. * this gives you the old index in the store.
  50283. * @param {Array} modifiedFieldNames An array containing the field names that have been modified since the
  50284. * record was committed or created
  50285. * @param {Object} modifiedValues An object where each key represents a field name that had it's value modified,
  50286. * and where the value represents the old value for that field. To get the new value in a listener
  50287. * you should use the {@link Ext.data.Model#get get} method.
  50288. */
  50289. /**
  50290. * @event update
  50291. * @inheritdoc Ext.data.Store#updaterecord
  50292. * @removed 2.0 Listen to #updaterecord instead.
  50293. */
  50294. /**
  50295. * @event refresh
  50296. * Fires whenever the records in the Store have changed in a way that your representation of the records
  50297. * need to be entirely refreshed.
  50298. * @param {Ext.data.Store} this The data store
  50299. * @param {Ext.util.Collection} data The data collection containing all the records
  50300. */
  50301. /**
  50302. * @event beforeload
  50303. * Fires before a request is made for a new data object. If the beforeload handler returns false the load
  50304. * action will be canceled. Note that you should not listen for this event in order to refresh the
  50305. * data view. Use the {@link #refresh} event for this instead.
  50306. * @param {Ext.data.Store} store This Store
  50307. * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to
  50308. * load the Store
  50309. */
  50310. /**
  50311. * @event load
  50312. * Fires whenever records have been loaded into the store. Note that you should not listen
  50313. * for this event in order to refresh the data view. Use the {@link #refresh} event for this instead.
  50314. * @param {Ext.data.Store} this
  50315. * @param {Ext.data.Model[]} records An array of records
  50316. * @param {Boolean} successful `true` if the operation was successful.
  50317. * @param {Ext.data.Operation} operation The associated operation.
  50318. */
  50319. /**
  50320. * @event write
  50321. * Fires whenever a successful write has been made via the configured {@link #proxy Proxy}
  50322. * @param {Ext.data.Store} store This Store
  50323. * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object that was used in
  50324. * the write
  50325. */
  50326. /**
  50327. * @event beforesync
  50328. * Fired before a call to {@link #sync} is executed. Return `false` from any listener to cancel the sync
  50329. * @param {Object} options Hash of all records to be synchronized, broken down into create, update and destroy
  50330. */
  50331. /**
  50332. * @event clear
  50333. * Fired after the {@link #removeAll} method is called. Note that you should not listen for this event in order
  50334. * to refresh the data view. Use the {@link #refresh} event for this instead.
  50335. * @param {Ext.data.Store} this
  50336. * @return {Ext.data.Store}
  50337. */
  50338. statics: {
  50339. create: function(store) {
  50340. if (!store.isStore) {
  50341. if (!store.type) {
  50342. store.type = 'store';
  50343. }
  50344. store = Ext.createByAlias('store.' + store.type, store);
  50345. }
  50346. return store;
  50347. }
  50348. },
  50349. isStore: true,
  50350. config: {
  50351. /**
  50352. * @cfg {String} storeId
  50353. * Unique identifier for this store. If present, this Store will be registered with the {@link Ext.data.StoreManager},
  50354. * making it easy to reuse elsewhere.
  50355. * @accessor
  50356. */
  50357. storeId: undefined,
  50358. /**
  50359. * @cfg {Object[]/Ext.data.Model[]} data
  50360. * Array of Model instances or data objects to load locally. See "Inline data" above for details.
  50361. * @accessor
  50362. */
  50363. data: null,
  50364. /**
  50365. * @cfg {Boolean/Object} [autoLoad=false]
  50366. * If data is not specified, and if `autoLoad` is `true` or an Object, this store's load method is automatically called
  50367. * after creation. If the value of `autoLoad` is an Object, this Object will be passed to the store's `load()` method.
  50368. * @accessor
  50369. */
  50370. autoLoad: null,
  50371. /**
  50372. * @cfg {Boolean} autoSync
  50373. * `true` to automatically sync the Store with its Proxy after every edit to one of its Records.
  50374. * @accessor
  50375. */
  50376. autoSync: false,
  50377. /**
  50378. * @cfg {String} model
  50379. * Name of the {@link Ext.data.Model Model} associated with this store.
  50380. * The string is used as an argument for {@link Ext.ModelManager#getModel}.
  50381. * @accessor
  50382. */
  50383. model: undefined,
  50384. /**
  50385. * @cfg {String/Ext.data.proxy.Proxy/Object} proxy The Proxy to use for this Store. This can be either a string, a config
  50386. * object or a Proxy instance - see {@link #setProxy} for details.
  50387. * @accessor
  50388. */
  50389. proxy: undefined,
  50390. /**
  50391. * @cfg {Object[]} fields
  50392. * This may be used in place of specifying a {@link #model} configuration. The fields should be a
  50393. * set of {@link Ext.data.Field} configuration objects. The store will automatically create a {@link Ext.data.Model}
  50394. * with these fields. In general this configuration option should be avoided, it exists for the purposes of
  50395. * backwards compatibility. For anything more complicated, such as specifying a particular id property or
  50396. * associations, a {@link Ext.data.Model} should be defined and specified for the {@link #model}
  50397. * config.
  50398. * @accessor
  50399. */
  50400. fields: null,
  50401. /**
  50402. * @cfg {Boolean} remoteSort
  50403. * `true` to defer any sorting operation to the server. If `false`, sorting is done locally on the client.
  50404. *
  50405. * If this is set to `true`, you will have to manually call the {@link #method-load} method after you {@link #method-sort}, to retrieve the sorted
  50406. * data from the server.
  50407. * @accessor
  50408. */
  50409. remoteSort: false,
  50410. /**
  50411. * @cfg {Boolean} remoteFilter
  50412. * `true` to defer any filtering operation to the server. If `false`, filtering is done locally on the client.
  50413. *
  50414. * If this is set to `true`, you will have to manually call the {@link #method-load} method after you {@link #method-filter} to retrieve the filtered
  50415. * data from the server.
  50416. * @accessor
  50417. */
  50418. remoteFilter: false,
  50419. /**
  50420. * @cfg {Boolean} remoteGroup
  50421. * `true` to defer any grouping operation to the server. If `false`, grouping is done locally on the client.
  50422. * @accessor
  50423. */
  50424. remoteGroup: false,
  50425. /**
  50426. * @cfg {Object[]} filters
  50427. * Array of {@link Ext.util.Filter Filters} for this store. This configuration is handled by the
  50428. * {@link Ext.mixin.Filterable Filterable} mixin of the {@link Ext.util.Collection data} collection.
  50429. * @accessor
  50430. */
  50431. filters: null,
  50432. /**
  50433. * @cfg {Object[]} sorters
  50434. * Array of {@link Ext.util.Sorter Sorters} for this store. This configuration is handled by the
  50435. * {@link Ext.mixin.Sortable Sortable} mixin of the {@link Ext.util.Collection data} collection.
  50436. * See also the {@link #sort} method.
  50437. * @accessor
  50438. */
  50439. sorters: null,
  50440. /**
  50441. * @cfg {Object[]} grouper
  50442. * A configuration object for this Store's {@link Ext.util.Grouper grouper}.
  50443. *
  50444. * For example, to group a store's items by the first letter of the last name:
  50445. *
  50446. * Ext.define('People', {
  50447. * extend: 'Ext.data.Store',
  50448. *
  50449. * config: {
  50450. * fields: ['first_name', 'last_name'],
  50451. *
  50452. * grouper: {
  50453. * groupFn: function(record) {
  50454. * return record.get('last_name').substr(0, 1);
  50455. * },
  50456. * sortProperty: 'last_name'
  50457. * }
  50458. * }
  50459. * });
  50460. *
  50461. * @accessor
  50462. */
  50463. grouper: null,
  50464. /**
  50465. * @cfg {String} groupField
  50466. * The (optional) field by which to group data in the store. Internally, grouping is very similar to sorting - the
  50467. * groupField and {@link #groupDir} are injected as the first sorter (see {@link #sort}). Stores support a single
  50468. * level of grouping, and groups can be fetched via the {@link #getGroups} method.
  50469. * @accessor
  50470. */
  50471. groupField: null,
  50472. /**
  50473. * @cfg {String} groupDir
  50474. * The direction in which sorting should be applied when grouping. If you specify a grouper by using the {@link #groupField}
  50475. * configuration, this will automatically default to "ASC" - the other supported value is "DESC"
  50476. * @accessor
  50477. */
  50478. groupDir: null,
  50479. /**
  50480. * @cfg {Function} getGroupString This function will be passed to the {@link #grouper} configuration as it's `groupFn`.
  50481. * Note that this configuration is deprecated and grouper: `{groupFn: yourFunction}}` is preferred.
  50482. * @deprecated
  50483. * @accessor
  50484. */
  50485. getGroupString: null,
  50486. /**
  50487. * @cfg {Number} pageSize
  50488. * The number of records considered to form a 'page'. This is used to power the built-in
  50489. * paging using the nextPage and previousPage functions.
  50490. * @accessor
  50491. */
  50492. pageSize: 25,
  50493. /**
  50494. * @cfg {Number} totalCount The total number of records in the full dataset, as indicated by a server. If the
  50495. * server-side dataset contains 5000 records but only returns pages of 50 at a time, `totalCount` will be set to
  50496. * 5000 and {@link #getCount} will return 50
  50497. */
  50498. totalCount: null,
  50499. /**
  50500. * @cfg {Boolean} clearOnPageLoad `true` to empty the store when loading another page via {@link #loadPage},
  50501. * {@link #nextPage} or {@link #previousPage}. Setting to `false` keeps existing records, allowing
  50502. * large data sets to be loaded one page at a time but rendered all together.
  50503. * @accessor
  50504. */
  50505. clearOnPageLoad: true,
  50506. modelDefaults: {},
  50507. /**
  50508. * @cfg {Boolean} autoDestroy This is a private configuration used in the framework whether this Store
  50509. * can be destroyed.
  50510. * @private
  50511. */
  50512. autoDestroy: false,
  50513. /**
  50514. * @cfg {Boolean} syncRemovedRecords This configuration allows you to disable the synchronization of
  50515. * removed records on this Store. By default, when you call `removeAll()` or `remove()`, records will be added
  50516. * to an internal removed array. When you then sync the Store, we send a destroy request for these records.
  50517. * If you don't want this to happen, you can set this configuration to `false`.
  50518. */
  50519. syncRemovedRecords: true,
  50520. /**
  50521. * @cfg {Boolean} destroyRemovedRecords This configuration allows you to prevent destroying record
  50522. * instances when they are removed from this store and are not in any other store.
  50523. */
  50524. destroyRemovedRecords: true
  50525. },
  50526. /**
  50527. * @property {Number} currentPage
  50528. * The page that the Store has most recently loaded (see {@link #loadPage})
  50529. */
  50530. currentPage: 1,
  50531. constructor: function(config) {
  50532. config = config || {};
  50533. this.data = this._data = this.createDataCollection();
  50534. this.data.setSortRoot('data');
  50535. this.data.setFilterRoot('data');
  50536. this.removed = [];
  50537. if (config.id && !config.storeId) {
  50538. config.storeId = config.id;
  50539. delete config.id;
  50540. }
  50541. this.initConfig(config);
  50542. this.callParent(arguments);
  50543. },
  50544. /**
  50545. * @private
  50546. * @return {Ext.util.Collection}
  50547. */
  50548. createDataCollection: function() {
  50549. return new Ext.util.Collection(function(record) {
  50550. return record.getId();
  50551. });
  50552. },
  50553. applyStoreId: function(storeId) {
  50554. if (storeId === undefined || storeId === null) {
  50555. storeId = this.getUniqueId();
  50556. }
  50557. return storeId;
  50558. },
  50559. updateStoreId: function(storeId, oldStoreId) {
  50560. if (oldStoreId) {
  50561. Ext.data.StoreManager.unregister(this);
  50562. }
  50563. if (storeId) {
  50564. Ext.data.StoreManager.register(this);
  50565. }
  50566. },
  50567. applyModel: function(model) {
  50568. if (typeof model == 'string') {
  50569. var registeredModel = Ext.data.ModelManager.getModel(model);
  50570. if (!registeredModel) {
  50571. Ext.Logger.error('Model with name "' + model + '" does not exist.');
  50572. }
  50573. model = registeredModel;
  50574. }
  50575. if (model && !model.prototype.isModel && Ext.isObject(model)) {
  50576. model = Ext.data.ModelManager.registerType(model.storeId || model.id || Ext.id(), model);
  50577. }
  50578. if (!model) {
  50579. var fields = this.getFields(),
  50580. data = this.config.data;
  50581. if (!fields && data && data.length) {
  50582. fields = Ext.Object.getKeys(data[0]);
  50583. }
  50584. if (fields) {
  50585. model = Ext.define('Ext.data.Store.ImplicitModel-' + (this.getStoreId() || Ext.id()), {
  50586. extend: 'Ext.data.Model',
  50587. config: {
  50588. fields: fields,
  50589. proxy: this.getProxy()
  50590. }
  50591. });
  50592. this.implicitModel = true;
  50593. }
  50594. }
  50595. if (!model && this.getProxy()) {
  50596. model = this.getProxy().getModel();
  50597. }
  50598. // <debug>
  50599. if (!model) {
  50600. Ext.Logger.warn('Unless you define your model through metadata, a store needs to have a model defined on either itself or on its proxy');
  50601. }
  50602. // </debug>
  50603. return model;
  50604. },
  50605. updateModel: function(model) {
  50606. var proxy = this.getProxy();
  50607. if (proxy && !proxy.getModel()) {
  50608. proxy.setModel(model);
  50609. }
  50610. },
  50611. applyProxy: function(proxy, currentProxy) {
  50612. proxy = Ext.factory(proxy, Ext.data.Proxy, currentProxy, 'proxy');
  50613. if (!proxy && this.getModel()) {
  50614. proxy = this.getModel().getProxy();
  50615. }
  50616. if (!proxy) {
  50617. proxy = new Ext.data.proxy.Memory({
  50618. model: this.getModel()
  50619. });
  50620. }
  50621. if (proxy.isMemoryProxy) {
  50622. this.setSyncRemovedRecords(false);
  50623. }
  50624. return proxy;
  50625. },
  50626. updateProxy: function(proxy) {
  50627. if (proxy) {
  50628. if (!proxy.getModel()) {
  50629. proxy.setModel(this.getModel());
  50630. }
  50631. proxy.on('metachange', this.onMetaChange, this);
  50632. }
  50633. },
  50634. /**
  50635. * We are using applyData so that we can return nothing and prevent the `this.data`
  50636. * property to be overridden.
  50637. * @param data
  50638. */
  50639. applyData: function(data) {
  50640. var me = this,
  50641. proxy;
  50642. if (data) {
  50643. proxy = me.getProxy();
  50644. if (proxy instanceof Ext.data.proxy.Memory) {
  50645. proxy.setData(data);
  50646. me.load();
  50647. return;
  50648. } else {
  50649. // We make it silent because we don't want to fire a refresh event
  50650. me.removeAll(true);
  50651. // This means we have to fire a clear event though
  50652. me.fireEvent('clear', me);
  50653. // We don't want to fire addrecords event since we will be firing
  50654. // a refresh event later which will already take care of updating
  50655. // any views bound to this store
  50656. me.suspendEvents();
  50657. me.add(data);
  50658. me.resumeEvents();
  50659. // We set this to true so isAutoLoading to try
  50660. me.dataLoaded = true;
  50661. }
  50662. } else {
  50663. me.removeAll(true);
  50664. // This means we have to fire a clear event though
  50665. me.fireEvent('clear', me);
  50666. }
  50667. me.fireEvent('refresh', me, me.data);
  50668. },
  50669. clearData: function() {
  50670. this.setData(null);
  50671. },
  50672. addData: function(data) {
  50673. var reader = this.getProxy().getReader(),
  50674. resultSet = reader.read(data),
  50675. records = resultSet.getRecords();
  50676. this.add(records);
  50677. },
  50678. updateAutoLoad: function(autoLoad) {
  50679. var proxy = this.getProxy();
  50680. if (autoLoad && (proxy && !proxy.isMemoryProxy)) {
  50681. this.load(Ext.isObject(autoLoad) ? autoLoad : null);
  50682. }
  50683. },
  50684. /**
  50685. * Returns `true` if the Store is set to {@link #autoLoad} or is a type which loads upon instantiation.
  50686. * @return {Boolean}
  50687. */
  50688. isAutoLoading: function() {
  50689. var proxy = this.getProxy();
  50690. return (this.getAutoLoad() || (proxy && proxy.isMemoryProxy) || this.dataLoaded);
  50691. },
  50692. updateGroupField: function(groupField) {
  50693. var grouper = this.getGrouper();
  50694. if (groupField) {
  50695. if (!grouper) {
  50696. this.setGrouper({
  50697. property: groupField,
  50698. direction: this.getGroupDir() || 'ASC'
  50699. });
  50700. } else {
  50701. grouper.setProperty(groupField);
  50702. }
  50703. } else if (grouper) {
  50704. this.setGrouper(null);
  50705. }
  50706. },
  50707. updateGroupDir: function(groupDir) {
  50708. var grouper = this.getGrouper();
  50709. if (grouper) {
  50710. grouper.setDirection(groupDir);
  50711. }
  50712. },
  50713. applyGetGroupString: function(getGroupStringFn) {
  50714. var grouper = this.getGrouper();
  50715. if (getGroupStringFn) {
  50716. // <debug>
  50717. Ext.Logger.warn('Specifying getGroupString on a store has been deprecated. Please use grouper: {groupFn: yourFunction}');
  50718. // </debug>
  50719. if (grouper) {
  50720. grouper.setGroupFn(getGroupStringFn);
  50721. } else {
  50722. this.setGrouper({
  50723. groupFn: getGroupStringFn
  50724. });
  50725. }
  50726. } else if (grouper) {
  50727. this.setGrouper(null);
  50728. }
  50729. },
  50730. applyGrouper: function(grouper) {
  50731. if (typeof grouper == 'string') {
  50732. grouper = {
  50733. property: grouper
  50734. };
  50735. }
  50736. else if (typeof grouper == 'function') {
  50737. grouper = {
  50738. groupFn: grouper
  50739. };
  50740. }
  50741. grouper = Ext.factory(grouper, Ext.util.Grouper, this.getGrouper());
  50742. return grouper;
  50743. },
  50744. updateGrouper: function(grouper, oldGrouper) {
  50745. var data = this.data;
  50746. if (oldGrouper) {
  50747. data.removeSorter(oldGrouper);
  50748. if (!grouper) {
  50749. data.getSorters().removeSorter('isGrouper');
  50750. }
  50751. }
  50752. if (grouper) {
  50753. data.insertSorter(0, grouper);
  50754. if (!oldGrouper) {
  50755. data.getSorters().addSorter({
  50756. direction: 'DESC',
  50757. property: 'isGrouper',
  50758. transform: function(value) {
  50759. return (value === true) ? 1 : -1;
  50760. }
  50761. });
  50762. }
  50763. }
  50764. },
  50765. /**
  50766. * This method tells you if this store has a grouper defined on it.
  50767. * @return {Boolean} `true` if this store has a grouper defined.
  50768. */
  50769. isGrouped: function() {
  50770. return !!this.getGrouper();
  50771. },
  50772. updateSorters: function(sorters) {
  50773. var grouper = this.getGrouper(),
  50774. data = this.data,
  50775. autoSort = data.getAutoSort();
  50776. // While we remove/add sorters we don't want to automatically sort because we still need
  50777. // to apply any field sortTypes as transforms on the Sorters after we have added them.
  50778. data.setAutoSort(false);
  50779. data.setSorters(sorters);
  50780. if (grouper) {
  50781. data.insertSorter(0, grouper);
  50782. }
  50783. this.updateSortTypes();
  50784. // Now we put back autoSort on the Collection to the value it had before. If it was
  50785. // auto sorted, setting this back will cause it to sort right away.
  50786. data.setAutoSort(autoSort);
  50787. },
  50788. updateSortTypes: function() {
  50789. var model = this.getModel(),
  50790. fields = model && model.getFields(),
  50791. data = this.data;
  50792. // We loop over each sorter and set it's transform method to the every field's sortType.
  50793. if (fields) {
  50794. data.getSorters().each(function(sorter) {
  50795. var property = sorter.getProperty(),
  50796. field;
  50797. if (!sorter.isGrouper && property && !sorter.getTransform()) {
  50798. field = fields.get(property);
  50799. if (field) {
  50800. sorter.setTransform(field.getSortType());
  50801. }
  50802. }
  50803. });
  50804. }
  50805. },
  50806. updateFilters: function(filters) {
  50807. this.data.setFilters(filters);
  50808. },
  50809. /**
  50810. * Adds Model instance to the Store. This method accepts either:
  50811. *
  50812. * - An array of Model instances or Model configuration objects.
  50813. * - Any number of Model instance or Model configuration object arguments.
  50814. *
  50815. * The new Model instances will be added at the end of the existing collection.
  50816. *
  50817. * Sample usage:
  50818. *
  50819. * myStore.add({some: 'data2'}, {some: 'other data2'});
  50820. *
  50821. * @param {Ext.data.Model[]/Ext.data.Model...} model An array of Model instances
  50822. * or Model configuration objects, or variable number of Model instance or config arguments.
  50823. * @return {Ext.data.Model[]} The model instances that were added.
  50824. */
  50825. add: function(records) {
  50826. if (!Ext.isArray(records)) {
  50827. records = Array.prototype.slice.call(arguments);
  50828. }
  50829. return this.insert(this.data.length, records);
  50830. },
  50831. /**
  50832. * Inserts Model instances into the Store at the given index and fires the {@link #add} event.
  50833. * See also `{@link #add}`.
  50834. * @param {Number} index The start index at which to insert the passed Records.
  50835. * @param {Ext.data.Model[]} records An Array of Ext.data.Model objects to add to the cache.
  50836. * @return {Object}
  50837. */
  50838. insert: function(index, records) {
  50839. if (!Ext.isArray(records)) {
  50840. records = Array.prototype.slice.call(arguments, 1);
  50841. }
  50842. var me = this,
  50843. sync = false,
  50844. data = this.data,
  50845. ln = records.length,
  50846. Model = this.getModel(),
  50847. modelDefaults = me.getModelDefaults(),
  50848. added = false,
  50849. i, record;
  50850. records = records.slice();
  50851. for (i = 0; i < ln; i++) {
  50852. record = records[i];
  50853. if (!record.isModel) {
  50854. record = new Model(record);
  50855. }
  50856. // If we are adding a record that is already an instance which was still in the
  50857. // removed array, then we remove it from the removed array
  50858. else if (this.removed.indexOf(record) != -1) {
  50859. Ext.Array.remove(this.removed, record);
  50860. }
  50861. record.set(modelDefaults);
  50862. record.join(me);
  50863. records[i] = record;
  50864. // If this is a newly created record, then we might want to sync it later
  50865. sync = sync || (record.phantom === true);
  50866. }
  50867. // Now we insert all these records in one go to the collection. Saves many function
  50868. // calls to data.insert. Does however create two loops over the records we are adding.
  50869. if (records.length === 1) {
  50870. added = data.insert(index, records[0]);
  50871. if (added) {
  50872. added = [added];
  50873. }
  50874. } else {
  50875. added = data.insertAll(index, records);
  50876. }
  50877. if (added) {
  50878. me.fireEvent('addrecords', me, added);
  50879. }
  50880. if (me.getAutoSync() && sync) {
  50881. me.sync();
  50882. }
  50883. return records;
  50884. },
  50885. /**
  50886. * Removes the given record from the Store, firing the `removerecords` event passing all the instances that are removed.
  50887. * @param {Ext.data.Model/Ext.data.Model[]} records Model instance or array of instances to remove.
  50888. */
  50889. remove: function (records) {
  50890. if (records.isModel) {
  50891. records = [records];
  50892. }
  50893. var me = this,
  50894. sync = false,
  50895. i = 0,
  50896. autoSync = this.getAutoSync(),
  50897. syncRemovedRecords = me.getSyncRemovedRecords(),
  50898. destroyRemovedRecords = this.getDestroyRemovedRecords(),
  50899. ln = records.length,
  50900. indices = [],
  50901. removed = [],
  50902. isPhantom,
  50903. items = me.data.items,
  50904. record, index, j;
  50905. for (; i < ln; i++) {
  50906. record = records[i];
  50907. if (me.data.contains(record)) {
  50908. isPhantom = (record.phantom === true);
  50909. index = items.indexOf(record);
  50910. if (index !== -1) {
  50911. removed.push(record);
  50912. indices.push(index);
  50913. }
  50914. record.unjoin(me);
  50915. me.data.remove(record);
  50916. if (destroyRemovedRecords && !syncRemovedRecords && !record.stores.length) {
  50917. record.destroy();
  50918. }
  50919. else if (!isPhantom && syncRemovedRecords) {
  50920. // don't push phantom records onto removed
  50921. me.removed.push(record);
  50922. }
  50923. sync = sync || !isPhantom;
  50924. }
  50925. }
  50926. me.fireEvent('removerecords', me, removed, indices);
  50927. if (autoSync && sync) {
  50928. me.sync();
  50929. }
  50930. },
  50931. /**
  50932. * Removes the model instance at the given index.
  50933. * @param {Number} index The record index.
  50934. */
  50935. removeAt: function(index) {
  50936. var record = this.getAt(index);
  50937. if (record) {
  50938. this.remove(record);
  50939. }
  50940. },
  50941. /**
  50942. * Remove all items from the store.
  50943. * @param {Boolean} silent Prevent the `clear` event from being fired.
  50944. */
  50945. removeAll: function(silent) {
  50946. if (silent !== true && this.eventFiringSuspended !== true) {
  50947. this.fireAction('clear', [this], 'doRemoveAll');
  50948. } else {
  50949. this.doRemoveAll.call(this, true);
  50950. }
  50951. },
  50952. doRemoveAll: function (silent) {
  50953. var me = this,
  50954. destroyRemovedRecords = this.getDestroyRemovedRecords(),
  50955. syncRemovedRecords = this.getSyncRemovedRecords(),
  50956. records = me.data.all.slice(),
  50957. ln = records.length,
  50958. i, record;
  50959. for (i = 0; i < ln; i++) {
  50960. record = records[i];
  50961. record.unjoin(me);
  50962. if (destroyRemovedRecords && !syncRemovedRecords && !record.stores.length) {
  50963. record.destroy();
  50964. }
  50965. else if (record.phantom !== true && syncRemovedRecords) {
  50966. me.removed.push(record);
  50967. }
  50968. }
  50969. me.data.clear();
  50970. if (silent !== true) {
  50971. me.fireEvent('refresh', me, me.data);
  50972. }
  50973. if (me.getAutoSync()) {
  50974. this.sync();
  50975. }
  50976. },
  50977. /**
  50978. * Calls the specified function for each of the {@link Ext.data.Model Records} in the cache.
  50979. *
  50980. * // Set up a model to use in our Store
  50981. * Ext.define('User', {
  50982. * extend: 'Ext.data.Model',
  50983. * config: {
  50984. * fields: [
  50985. * {name: 'firstName', type: 'string'},
  50986. * {name: 'lastName', type: 'string'}
  50987. * ]
  50988. * }
  50989. * });
  50990. *
  50991. * var store = Ext.create('Ext.data.Store', {
  50992. * model: 'User',
  50993. * data : [
  50994. * {firstName: 'Ed', lastName: 'Spencer'},
  50995. * {firstName: 'Tommy', lastName: 'Maintz'},
  50996. * {firstName: 'Aaron', lastName: 'Conran'},
  50997. * {firstName: 'Jamie', lastName: 'Avins'}
  50998. * ]
  50999. * });
  51000. *
  51001. * store.each(function (item, index, length) {
  51002. * console.log(item.get('firstName'), index);
  51003. * });
  51004. *
  51005. * @param {Function} fn The function to call. Returning `false` aborts and exits the iteration.
  51006. * @param {Ext.data.Model} fn.item
  51007. * @param {Number} fn.index
  51008. * @param {Number} fn.length
  51009. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
  51010. * Defaults to the current {@link Ext.data.Model Record} in the iteration.
  51011. */
  51012. each: function(fn, scope) {
  51013. this.data.each(fn, scope);
  51014. },
  51015. /**
  51016. * Gets the number of cached records. Note that filtered records are not included in this count.
  51017. * If using paging, this may not be the total size of the dataset.
  51018. * @return {Number} The number of Records in the Store's cache.
  51019. */
  51020. getCount: function() {
  51021. return this.data.items.length || 0;
  51022. },
  51023. /**
  51024. * Gets the number of all cached records including the ones currently filtered.
  51025. * If using paging, this may not be the total size of the dataset.
  51026. * @return {Number} The number of all Records in the Store's cache.
  51027. */
  51028. getAllCount: function () {
  51029. return this.data.all.length || 0;
  51030. },
  51031. /**
  51032. * Get the Record at the specified index.
  51033. * @param {Number} index The index of the Record to find.
  51034. * @return {Ext.data.Model/undefined} The Record at the passed index. Returns `undefined` if not found.
  51035. */
  51036. getAt: function(index) {
  51037. return this.data.getAt(index);
  51038. },
  51039. /**
  51040. * Returns a range of Records between specified indices.
  51041. * @param {Number} [startIndex=0] (optional) The starting index.
  51042. * @param {Number} [endIndex=-1] (optional) The ending index (defaults to the last Record in the Store).
  51043. * @return {Ext.data.Model[]} An array of Records.
  51044. */
  51045. getRange: function(start, end) {
  51046. return this.data.getRange(start, end);
  51047. },
  51048. /**
  51049. * Get the Record with the specified id.
  51050. * @param {String} id The id of the Record to find.
  51051. * @return {Ext.data.Model/undefined} The Record with the passed id. Returns `undefined` if not found.
  51052. */
  51053. getById: function(id) {
  51054. return this.data.findBy(function(record) {
  51055. return record.getId() == id;
  51056. });
  51057. },
  51058. /**
  51059. * Get the index within the cache of the passed Record.
  51060. * @param {Ext.data.Model} record The Ext.data.Model object to find.
  51061. * @return {Number} The index of the passed Record. Returns -1 if not found.
  51062. */
  51063. indexOf: function(record) {
  51064. return this.data.indexOf(record);
  51065. },
  51066. /**
  51067. * Get the index within the cache of the Record with the passed id.
  51068. * @param {String} id The id of the Record to find.
  51069. * @return {Number} The index of the Record. Returns -1 if not found.
  51070. */
  51071. indexOfId: function(id) {
  51072. return this.data.indexOfKey(id);
  51073. },
  51074. /**
  51075. * @private
  51076. * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
  51077. * @param {Ext.data.Model} record The model instance that was edited.
  51078. * @param {String[]} modifiedFieldNames Array of field names changed during edit.
  51079. */
  51080. afterEdit: function(record, modifiedFieldNames, modified) {
  51081. var me = this,
  51082. data = me.data,
  51083. currentId = modified[record.getIdProperty()] || record.getId(),
  51084. currentIndex = data.keys.indexOf(currentId),
  51085. newIndex;
  51086. if (currentIndex === -1 && data.map[currentId] === undefined) {
  51087. return;
  51088. }
  51089. if (me.getAutoSync()) {
  51090. me.sync();
  51091. }
  51092. if (currentId !== record.getId()) {
  51093. data.replace(currentId, record);
  51094. } else {
  51095. data.replace(record);
  51096. }
  51097. newIndex = data.indexOf(record);
  51098. if (currentIndex === -1 && newIndex !== -1) {
  51099. me.fireEvent('addrecords', me, [record]);
  51100. }
  51101. else if (currentIndex !== -1 && newIndex === -1) {
  51102. me.fireEvent('removerecords', me, [record], [currentIndex]);
  51103. }
  51104. else if (newIndex !== -1) {
  51105. me.fireEvent('updaterecord', me, record, newIndex, currentIndex, modifiedFieldNames, modified);
  51106. }
  51107. },
  51108. /**
  51109. * @private
  51110. * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
  51111. * @param {Ext.data.Model} record The model instance that was edited.
  51112. */
  51113. afterReject: function(record) {
  51114. var index = this.data.indexOf(record);
  51115. this.fireEvent('updaterecord', this, record, index, index, [], {});
  51116. },
  51117. /**
  51118. * @private
  51119. * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
  51120. * @param {Ext.data.Model} record The model instance that was edited.
  51121. */
  51122. afterCommit: function(record, modifiedFieldNames, modified) {
  51123. var me = this,
  51124. data = me.data,
  51125. currentId = modified[record.getIdProperty()] || record.getId(),
  51126. currentIndex = data.keys.indexOf(currentId),
  51127. newIndex;
  51128. if (currentIndex === -1 && data.map[currentId] === undefined) {
  51129. return;
  51130. }
  51131. if (currentId !== record.getId()) {
  51132. data.replace(currentId, record);
  51133. } else {
  51134. data.replace(record);
  51135. }
  51136. newIndex = data.indexOf(record);
  51137. if (currentIndex === -1 && newIndex !== -1) {
  51138. me.fireEvent('addrecords', me, [record]);
  51139. }
  51140. else if (currentIndex !== -1 && newIndex === -1) {
  51141. me.fireEvent('removerecords', me, [record], [currentIndex]);
  51142. }
  51143. else if (newIndex !== -1) {
  51144. me.fireEvent('updaterecord', me, record, newIndex, currentIndex, modifiedFieldNames, modified);
  51145. }
  51146. },
  51147. /**
  51148. * This gets called by a record after is gets erased from the server.
  51149. * @param record
  51150. * @private
  51151. */
  51152. afterErase: function(record) {
  51153. var me = this,
  51154. data = me.data,
  51155. index = data.indexOf(record);
  51156. if (index !== -1) {
  51157. data.remove(record);
  51158. me.fireEvent('removerecords', me, [record], [index]);
  51159. }
  51160. },
  51161. updateRemoteFilter: function(remoteFilter) {
  51162. this.data.setAutoFilter(!remoteFilter);
  51163. },
  51164. updateRemoteSort: function(remoteSort) {
  51165. this.data.setAutoSort(!remoteSort);
  51166. },
  51167. /**
  51168. * Sorts the data in the Store by one or more of its properties. Example usage:
  51169. *
  51170. * // sort by a single field
  51171. * myStore.sort('myField', 'DESC');
  51172. *
  51173. * // sorting by multiple fields
  51174. * myStore.sort([
  51175. * {
  51176. * property : 'age',
  51177. * direction: 'ASC'
  51178. * },
  51179. * {
  51180. * property : 'name',
  51181. * direction: 'DESC'
  51182. * }
  51183. * ]);
  51184. *
  51185. * Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates
  51186. * the actual sorting to its internal {@link Ext.util.Collection}.
  51187. *
  51188. * When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:
  51189. *
  51190. * store.sort('myField');
  51191. * store.sort('myField');
  51192. *
  51193. * is equivalent to this code:
  51194. *
  51195. * store.sort('myField', 'ASC');
  51196. * store.sort('myField', 'DESC');
  51197. *
  51198. * because Store handles the toggling automatically.
  51199. *
  51200. * If the {@link #remoteSort} configuration has been set to `true`, you will have to manually call the {@link #method-load}
  51201. * method after you sort to retrieve the sorted data from the server.
  51202. *
  51203. * @param {String/Ext.util.Sorter[]} sorters Either a string name of one of the fields in this Store's configured
  51204. * {@link Ext.data.Model Model}, or an array of sorter configurations.
  51205. * @param {String} [defaultDirection=ASC] The default overall direction to sort the data by.
  51206. * @param {String} where (Optional) This can be either `'prepend'` or `'append'`. If you leave this undefined
  51207. * it will clear the current sorters.
  51208. */
  51209. sort: function(sorters, defaultDirection, where) {
  51210. var data = this.data,
  51211. grouper = this.getGrouper(),
  51212. autoSort = data.getAutoSort();
  51213. if (sorters) {
  51214. // While we are adding sorters we don't want to sort right away
  51215. // since we need to update sortTypes on the sorters.
  51216. data.setAutoSort(false);
  51217. if (typeof where === 'string') {
  51218. if (where == 'prepend') {
  51219. data.insertSorters(grouper ? 1 : 0, sorters, defaultDirection);
  51220. } else {
  51221. data.addSorters(sorters, defaultDirection);
  51222. }
  51223. } else {
  51224. data.setSorters(null);
  51225. if (grouper) {
  51226. data.addSorters(grouper);
  51227. }
  51228. data.addSorters(sorters, defaultDirection);
  51229. }
  51230. this.updateSortTypes();
  51231. // Setting back autoSort to true (if it was like that before) will
  51232. // instantly sort the data again.
  51233. data.setAutoSort(autoSort);
  51234. }
  51235. if (!this.getRemoteSort()) {
  51236. // If we haven't added any new sorters we have to manually call sort
  51237. if (!sorters) {
  51238. this.data.sort();
  51239. }
  51240. this.fireEvent('sort', this, this.data, this.data.getSorters());
  51241. if (data.length) {
  51242. this.fireEvent('refresh', this, this.data);
  51243. }
  51244. }
  51245. },
  51246. /**
  51247. * Filters the loaded set of records by a given set of filters.
  51248. *
  51249. * Filtering by single field:
  51250. *
  51251. * store.filter("email", /\.com$/);
  51252. *
  51253. * Using multiple filters:
  51254. *
  51255. * store.filter([
  51256. * {property: "email", value: /\.com$/},
  51257. * {filterFn: function(item) { return item.get("age") > 10; }}
  51258. * ]);
  51259. *
  51260. * Using Ext.util.Filter instances instead of config objects
  51261. * (note that we need to specify the {@link Ext.util.Filter#root root} config option in this case):
  51262. *
  51263. * store.filter([
  51264. * Ext.create('Ext.util.Filter', {property: "email", value: /\.com$/, root: 'data'}),
  51265. * Ext.create('Ext.util.Filter', {filterFn: function(item) { return item.get("age") > 10; }, root: 'data'})
  51266. * ]);
  51267. *
  51268. * If the {@link #remoteFilter} configuration has been set to `true`, you will have to manually call the {@link #method-load}
  51269. * method after you filter to retrieve the filtered data from the server.
  51270. *
  51271. * @param {Object[]/Ext.util.Filter[]/String} filters The set of filters to apply to the data.
  51272. * These are stored internally on the store, but the filtering itself is done on the Store's
  51273. * {@link Ext.util.MixedCollection MixedCollection}. See MixedCollection's
  51274. * {@link Ext.util.MixedCollection#filter filter} method for filter syntax.
  51275. * Alternatively, pass in a property string.
  51276. * @param {String} [value] value to filter by (only if using a property string as the first argument).
  51277. * @param {Boolean} [anyMatch=false] `true` to allow any match, false to anchor regex beginning with `^`.
  51278. * @param {Boolean} [caseSensitive=false] `true` to make the filtering regex case sensitive.
  51279. */
  51280. filter: function(property, value, anyMatch, caseSensitive) {
  51281. var data = this.data,
  51282. filter = null;
  51283. if (property) {
  51284. if (Ext.isFunction(property)) {
  51285. filter = {filterFn: property};
  51286. }
  51287. else if (Ext.isArray(property) || property.isFilter) {
  51288. filter = property;
  51289. }
  51290. else {
  51291. filter = {
  51292. property : property,
  51293. value : value,
  51294. anyMatch : anyMatch,
  51295. caseSensitive: caseSensitive,
  51296. id : property
  51297. }
  51298. }
  51299. }
  51300. if (this.getRemoteFilter()) {
  51301. data.addFilters(filter);
  51302. } else {
  51303. data.filter(filter);
  51304. this.fireEvent('filter', this, data, data.getFilters());
  51305. this.fireEvent('refresh', this, data);
  51306. }
  51307. },
  51308. /**
  51309. * Filter by a function. The specified function will be called for each
  51310. * Record in this Store. If the function returns `true` the Record is included,
  51311. * otherwise it is filtered out.
  51312. * @param {Function} fn The function to be called. It will be passed the following parameters:
  51313. * @param {Ext.data.Model} fn.record The {@link Ext.data.Model record}
  51314. * to test for filtering. Access field values using {@link Ext.data.Model#get}.
  51315. * @param {Object} fn.id The ID of the Record passed.
  51316. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to this Store.
  51317. */
  51318. filterBy: function(fn, scope) {
  51319. var me = this,
  51320. data = me.data,
  51321. ln = data.length;
  51322. data.filter({
  51323. filterFn: function(record) {
  51324. return fn.call(scope || me, record, record.getId())
  51325. }
  51326. });
  51327. this.fireEvent('filter', this, data, data.getFilters());
  51328. if (data.length !== ln) {
  51329. this.fireEvent('refresh', this, data);
  51330. }
  51331. },
  51332. /**
  51333. * Query the cached records in this Store using a filtering function. The specified function
  51334. * will be called with each record in this Store. If the function returns `true` the record is
  51335. * included in the results.
  51336. * @param {Function} fn The function to be called. It will be passed the following parameters:
  51337. * @param {Ext.data.Model} fn.record The {@link Ext.data.Model record}
  51338. * to test for filtering. Access field values using {@link Ext.data.Model#get}.
  51339. * @param {Object} fn.id The ID of the Record passed.
  51340. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to this Store.
  51341. * @return {Ext.util.MixedCollection} Returns an Ext.util.MixedCollection of the matched records.
  51342. */
  51343. queryBy: function(fn, scope) {
  51344. return this.data.filterBy(fn, scope || this);
  51345. },
  51346. /**
  51347. * Reverts to a view of the Record cache with no filtering applied.
  51348. * @param {Boolean} [suppressEvent=false] `true` to clear silently without firing the `refresh` event.
  51349. */
  51350. clearFilter: function(suppressEvent) {
  51351. var ln = this.data.length;
  51352. if (suppressEvent) {
  51353. this.suspendEvents();
  51354. }
  51355. this.data.setFilters(null);
  51356. if (suppressEvent) {
  51357. this.resumeEvents();
  51358. } else if (ln !== this.data.length) {
  51359. this.fireEvent('refresh', this, this.data);
  51360. }
  51361. },
  51362. /**
  51363. * Returns `true` if this store is currently filtered.
  51364. * @return {Boolean}
  51365. */
  51366. isFiltered : function () {
  51367. return this.data.filtered;
  51368. },
  51369. /**
  51370. * Returns `true` if this store is currently sorted.
  51371. * @return {Boolean}
  51372. */
  51373. isSorted : function () {
  51374. return this.data.sorted;
  51375. },
  51376. getSorters: function() {
  51377. var sorters = this.data.getSorters();
  51378. return (sorters) ? sorters.items : [];
  51379. },
  51380. getFilters: function() {
  51381. var filters = this.data.getFilters();
  51382. return (filters) ? filters.items : [];
  51383. },
  51384. /**
  51385. * Returns an array containing the result of applying the grouper to the records in this store. See {@link #groupField},
  51386. * {@link #groupDir} and {@link #grouper}. Example for a store containing records with a color field:
  51387. *
  51388. * var myStore = Ext.create('Ext.data.Store', {
  51389. * groupField: 'color',
  51390. * groupDir : 'DESC'
  51391. * });
  51392. *
  51393. * myStore.getGroups(); //returns:
  51394. * [
  51395. * {
  51396. * name: 'yellow',
  51397. * children: [
  51398. * //all records where the color field is 'yellow'
  51399. * ]
  51400. * },
  51401. * {
  51402. * name: 'red',
  51403. * children: [
  51404. * //all records where the color field is 'red'
  51405. * ]
  51406. * }
  51407. * ]
  51408. *
  51409. * @param {String} groupName (Optional) Pass in an optional `groupName` argument to access a specific group as defined by {@link #grouper}.
  51410. * @return {Object/Object[]} The grouped data.
  51411. */
  51412. getGroups: function(requestGroupString) {
  51413. var records = this.data.items,
  51414. length = records.length,
  51415. grouper = this.getGrouper(),
  51416. groups = [],
  51417. pointers = {},
  51418. record,
  51419. groupStr,
  51420. group,
  51421. i;
  51422. // <debug>
  51423. if (!grouper) {
  51424. Ext.Logger.error('Trying to get groups for a store that has no grouper');
  51425. }
  51426. // </debug>
  51427. for (i = 0; i < length; i++) {
  51428. record = records[i];
  51429. groupStr = grouper.getGroupString(record);
  51430. group = pointers[groupStr];
  51431. if (group === undefined) {
  51432. group = {
  51433. name: groupStr,
  51434. children: []
  51435. };
  51436. groups.push(group);
  51437. pointers[groupStr] = group;
  51438. }
  51439. group.children.push(record);
  51440. }
  51441. return requestGroupString ? pointers[requestGroupString] : groups;
  51442. },
  51443. /**
  51444. * @param record
  51445. * @return {null}
  51446. */
  51447. getGroupString: function(record) {
  51448. var grouper = this.getGrouper();
  51449. if (grouper) {
  51450. return grouper.getGroupString(record);
  51451. }
  51452. return null;
  51453. },
  51454. /**
  51455. * Finds the index of the first matching Record in this store by a specific field value.
  51456. * @param {String} fieldName The name of the Record field to test.
  51457. * @param {String/RegExp} value Either a string that the field value
  51458. * should begin with, or a RegExp to test against the field.
  51459. * @param {Number} startIndex (optional) The index to start searching at.
  51460. * @param {Boolean} anyMatch (optional) `true` to match any part of the string, not just the beginning.
  51461. * @param {Boolean} caseSensitive (optional) `true` for case sensitive comparison.
  51462. * @param {Boolean} [exactMatch=false] (optional) `true` to force exact match (^ and $ characters added to the regex).
  51463. * @return {Number} The matched index or -1
  51464. */
  51465. find: function(fieldName, value, startIndex, anyMatch, caseSensitive, exactMatch) {
  51466. var filter = Ext.create('Ext.util.Filter', {
  51467. property: fieldName,
  51468. value: value,
  51469. anyMatch: anyMatch,
  51470. caseSensitive: caseSensitive,
  51471. exactMatch: exactMatch,
  51472. root: 'data'
  51473. });
  51474. return this.data.findIndexBy(filter.getFilterFn(), null, startIndex);
  51475. },
  51476. /**
  51477. * Finds the first matching Record in this store by a specific field value.
  51478. * @param {String} fieldName The name of the Record field to test.
  51479. * @param {String/RegExp} value Either a string that the field value
  51480. * should begin with, or a RegExp to test against the field.
  51481. * @param {Number} startIndex (optional) The index to start searching at.
  51482. * @param {Boolean} anyMatch (optional) `true` to match any part of the string, not just the beginning.
  51483. * @param {Boolean} caseSensitive (optional) `true` for case sensitive comparison.
  51484. * @param {Boolean} [exactMatch=false] (optional) `true` to force exact match (^ and $ characters added to the regex).
  51485. * @return {Ext.data.Model} The matched record or `null`.
  51486. */
  51487. findRecord: function() {
  51488. var me = this,
  51489. index = me.find.apply(me, arguments);
  51490. return index !== -1 ? me.getAt(index) : null;
  51491. },
  51492. /**
  51493. * Finds the index of the first matching Record in this store by a specific field value.
  51494. * @param {String} fieldName The name of the Record field to test.
  51495. * @param {Object} value The value to match the field against.
  51496. * @param {Number} startIndex (optional) The index to start searching at.
  51497. * @return {Number} The matched index or -1.
  51498. */
  51499. findExact: function(fieldName, value, startIndex) {
  51500. return this.data.findIndexBy(function(record) {
  51501. return record.get(fieldName) === value;
  51502. }, this, startIndex);
  51503. },
  51504. /**
  51505. * Find the index of the first matching Record in this Store by a function.
  51506. * If the function returns `true` it is considered a match.
  51507. * @param {Function} fn The function to be called. It will be passed the following parameters:
  51508. * @param {Ext.data.Model} fn.record The {@link Ext.data.Model record}
  51509. * to test for filtering. Access field values using {@link Ext.data.Model#get}.
  51510. * @param {Object} fn.id The ID of the Record passed.
  51511. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to this Store.
  51512. * @param {Number} startIndex (optional) The index to start searching at.
  51513. * @return {Number} The matched index or -1.
  51514. */
  51515. findBy: function(fn, scope, startIndex) {
  51516. return this.data.findIndexBy(fn, scope, startIndex);
  51517. },
  51518. /**
  51519. * Loads data into the Store via the configured {@link #proxy}. This uses the Proxy to make an
  51520. * asynchronous call to whatever storage backend the Proxy uses, automatically adding the retrieved
  51521. * instances into the Store and calling an optional callback if required. Example usage:
  51522. *
  51523. * store.load({
  51524. * callback: function(records, operation, success) {
  51525. * // the {@link Ext.data.Operation operation} object contains all of the details of the load operation
  51526. * console.log(records);
  51527. * },
  51528. * scope: this
  51529. * });
  51530. *
  51531. * If only the callback and scope options need to be specified, then one can call it simply like so:
  51532. *
  51533. * store.load(function(records, operation, success) {
  51534. * console.log('loaded records');
  51535. * }, this);
  51536. *
  51537. * @param {Object/Function} [options] config object, passed into the {@link Ext.data.Operation} object before loading.
  51538. * @param {Object} [scope] Scope for the function.
  51539. * @return {Object}
  51540. */
  51541. load: function(options, scope) {
  51542. var me = this,
  51543. operation,
  51544. currentPage = me.currentPage,
  51545. pageSize = me.getPageSize();
  51546. options = options || {};
  51547. if (Ext.isFunction(options)) {
  51548. options = {
  51549. callback: options,
  51550. scope: scope || this
  51551. };
  51552. }
  51553. if (me.getRemoteSort()) {
  51554. options.sorters = options.sorters || this.getSorters();
  51555. }
  51556. if (me.getRemoteFilter()) {
  51557. options.filters = options.filters || this.getFilters();
  51558. }
  51559. if (me.getRemoteGroup()) {
  51560. options.grouper = options.grouper || this.getGrouper();
  51561. }
  51562. Ext.applyIf(options, {
  51563. page: currentPage,
  51564. start: (currentPage - 1) * pageSize,
  51565. limit: pageSize,
  51566. addRecords: false,
  51567. action: 'read',
  51568. model: this.getModel()
  51569. });
  51570. operation = Ext.create('Ext.data.Operation', options);
  51571. if (me.fireEvent('beforeload', me, operation) !== false) {
  51572. me.loading = true;
  51573. me.getProxy().read(operation, me.onProxyLoad, me);
  51574. }
  51575. return me;
  51576. },
  51577. /**
  51578. * Returns `true` if the Store is currently performing a load operation.
  51579. * @return {Boolean} `true` if the Store is currently loading.
  51580. */
  51581. isLoading: function() {
  51582. return Boolean(this.loading);
  51583. },
  51584. /**
  51585. * Returns `true` if the Store has been loaded.
  51586. * @return {Boolean} `true` if the Store has been loaded.
  51587. */
  51588. isLoaded: function() {
  51589. return Boolean(this.loaded);
  51590. },
  51591. /**
  51592. * Synchronizes the Store with its Proxy. This asks the Proxy to batch together any new, updated
  51593. * and deleted records in the store, updating the Store's internal representation of the records
  51594. * as each operation completes.
  51595. * @return {Object}
  51596. * @return {Object} return.added
  51597. * @return {Object} return.updated
  51598. * @return {Object} return.removed
  51599. */
  51600. sync: function() {
  51601. var me = this,
  51602. operations = {},
  51603. toCreate = me.getNewRecords(),
  51604. toUpdate = me.getUpdatedRecords(),
  51605. toDestroy = me.getRemovedRecords(),
  51606. needsSync = false;
  51607. if (toCreate.length > 0) {
  51608. operations.create = toCreate;
  51609. needsSync = true;
  51610. }
  51611. if (toUpdate.length > 0) {
  51612. operations.update = toUpdate;
  51613. needsSync = true;
  51614. }
  51615. if (toDestroy.length > 0) {
  51616. operations.destroy = toDestroy;
  51617. needsSync = true;
  51618. }
  51619. if (needsSync && me.fireEvent('beforesync', this, operations) !== false) {
  51620. me.getProxy().batch({
  51621. operations: operations,
  51622. listeners: me.getBatchListeners()
  51623. });
  51624. }
  51625. return {
  51626. added: toCreate,
  51627. updated: toUpdate,
  51628. removed: toDestroy
  51629. };
  51630. },
  51631. /**
  51632. * Convenience function for getting the first model instance in the store.
  51633. * @return {Ext.data.Model/undefined} The first model instance in the store, or `undefined`.
  51634. */
  51635. first: function() {
  51636. return this.data.first();
  51637. },
  51638. /**
  51639. * Convenience function for getting the last model instance in the store.
  51640. * @return {Ext.data.Model/undefined} The last model instance in the store, or `undefined`.
  51641. */
  51642. last: function() {
  51643. return this.data.last();
  51644. },
  51645. /**
  51646. * Sums the value of `property` for each {@link Ext.data.Model record} between `start`
  51647. * and `end` and returns the result.
  51648. * @param {String} field The field in each record.
  51649. * @return {Number} The sum.
  51650. */
  51651. sum: function(field) {
  51652. var total = 0,
  51653. i = 0,
  51654. records = this.data.items,
  51655. len = records.length;
  51656. for (; i < len; ++i) {
  51657. total += records[i].get(field);
  51658. }
  51659. return total;
  51660. },
  51661. /**
  51662. * Gets the minimum value in the store.
  51663. * @param {String} field The field in each record.
  51664. * @return {Object/undefined} The minimum value, if no items exist, `undefined`.
  51665. */
  51666. min: function(field) {
  51667. var i = 1,
  51668. records = this.data.items,
  51669. len = records.length,
  51670. value, min;
  51671. if (len > 0) {
  51672. min = records[0].get(field);
  51673. }
  51674. for (; i < len; ++i) {
  51675. value = records[i].get(field);
  51676. if (value < min) {
  51677. min = value;
  51678. }
  51679. }
  51680. return min;
  51681. },
  51682. /**
  51683. * Gets the maximum value in the store.
  51684. * @param {String} field The field in each record.
  51685. * @return {Object/undefined} The maximum value, if no items exist, `undefined`.
  51686. */
  51687. max: function(field) {
  51688. var i = 1,
  51689. records = this.data.items,
  51690. len = records.length,
  51691. value,
  51692. max;
  51693. if (len > 0) {
  51694. max = records[0].get(field);
  51695. }
  51696. for (; i < len; ++i) {
  51697. value = records[i].get(field);
  51698. if (value > max) {
  51699. max = value;
  51700. }
  51701. }
  51702. return max;
  51703. },
  51704. /**
  51705. * Gets the average value in the store.
  51706. * @param {String} field The field in each record you want to get the average for.
  51707. * @return {Number} The average value, if no items exist, 0.
  51708. */
  51709. average: function(field) {
  51710. var i = 0,
  51711. records = this.data.items,
  51712. len = records.length,
  51713. sum = 0;
  51714. if (records.length > 0) {
  51715. for (; i < len; ++i) {
  51716. sum += records[i].get(field);
  51717. }
  51718. return sum / len;
  51719. }
  51720. return 0;
  51721. },
  51722. /**
  51723. * @private
  51724. * Returns an object which is passed in as the listeners argument to `proxy.batch` inside `this.sync`.
  51725. * This is broken out into a separate function to allow for customization of the listeners.
  51726. * @return {Object} The listeners object.
  51727. * @return {Object} return.scope
  51728. * @return {Object} return.exception
  51729. * @return {Object} return.complete
  51730. */
  51731. getBatchListeners: function() {
  51732. return {
  51733. scope: this,
  51734. exception: this.onBatchException,
  51735. complete: this.onBatchComplete
  51736. };
  51737. },
  51738. /**
  51739. * @private
  51740. * Attached as the `complete` event listener to a proxy's Batch object. Iterates over the batch operations
  51741. * and updates the Store's internal data MixedCollection.
  51742. */
  51743. onBatchComplete: function(batch) {
  51744. var me = this,
  51745. operations = batch.operations,
  51746. length = operations.length,
  51747. i;
  51748. for (i = 0; i < length; i++) {
  51749. me.onProxyWrite(operations[i]);
  51750. }
  51751. },
  51752. onBatchException: function(batch, operation) {
  51753. // //decide what to do... could continue with the next operation
  51754. // batch.start();
  51755. //
  51756. // //or retry the last operation
  51757. // batch.retry();
  51758. },
  51759. /**
  51760. * @private
  51761. * Called internally when a Proxy has completed a load request.
  51762. */
  51763. onProxyLoad: function(operation) {
  51764. var me = this,
  51765. records = operation.getRecords(),
  51766. resultSet = operation.getResultSet(),
  51767. successful = operation.wasSuccessful();
  51768. if (resultSet) {
  51769. me.setTotalCount(resultSet.getTotal());
  51770. }
  51771. if (successful) {
  51772. this.fireAction('datarefresh', [this, this.data, operation], 'doDataRefresh');
  51773. }
  51774. me.loaded = true;
  51775. me.loading = false;
  51776. me.fireEvent('load', this, records, successful, operation);
  51777. //this is a callback that would have been passed to the 'read' function and is optional
  51778. Ext.callback(operation.getCallback(), operation.getScope() || me, [records, operation, successful]);
  51779. },
  51780. doDataRefresh: function(store, data, operation) {
  51781. var records = operation.getRecords(),
  51782. me = this,
  51783. destroyRemovedRecords = me.getDestroyRemovedRecords(),
  51784. currentRecords = data.all.slice(),
  51785. ln = currentRecords.length,
  51786. ln2 = records.length,
  51787. ids = {},
  51788. i, record;
  51789. if (operation.getAddRecords() !== true) {
  51790. for (i = 0; i < ln2; i++) {
  51791. ids[records[i].id] = true;
  51792. }
  51793. for (i = 0; i < ln; i++) {
  51794. record = currentRecords[i];
  51795. record.unjoin(me);
  51796. // If the record we are removing is not part of the records we are about to add to the store then handle
  51797. // the destroying or removing of the record to avoid memory leaks.
  51798. if (ids[record.id] !== true && destroyRemovedRecords && !record.stores.length) {
  51799. record.destroy();
  51800. }
  51801. }
  51802. data.clear();
  51803. // This means we have to fire a clear event though
  51804. me.fireEvent('clear', me);
  51805. }
  51806. if (records && records.length) {
  51807. // Now lets add the records without firing an addrecords event
  51808. me.suspendEvents();
  51809. me.add(records);
  51810. me.resumeEvents();
  51811. }
  51812. me.fireEvent('refresh', me, data);
  51813. },
  51814. /**
  51815. * @private
  51816. * Callback for any write Operation over the Proxy. Updates the Store's MixedCollection to reflect
  51817. * the updates provided by the Proxy.
  51818. */
  51819. onProxyWrite: function(operation) {
  51820. var me = this,
  51821. success = operation.wasSuccessful(),
  51822. records = operation.getRecords();
  51823. switch (operation.getAction()) {
  51824. case 'create':
  51825. me.onCreateRecords(records, operation, success);
  51826. break;
  51827. case 'update':
  51828. me.onUpdateRecords(records, operation, success);
  51829. break;
  51830. case 'destroy':
  51831. me.onDestroyRecords(records, operation, success);
  51832. break;
  51833. }
  51834. if (success) {
  51835. me.fireEvent('write', me, operation);
  51836. }
  51837. //this is a callback that would have been passed to the 'create', 'update' or 'destroy' function and is optional
  51838. Ext.callback(operation.getCallback(), operation.getScope() || me, [records, operation, success]);
  51839. },
  51840. // These methods are now just template methods since updating the records etc is all taken care of
  51841. // by the operation itself.
  51842. onCreateRecords: function(records, operation, success) {},
  51843. onUpdateRecords: function(records, operation, success) {},
  51844. onDestroyRecords: function(records, operation, success) {
  51845. this.removed = [];
  51846. },
  51847. onMetaChange: function(data) {
  51848. var model = this.getProxy().getModel();
  51849. if (!this.getModel() && model) {
  51850. this.setModel(model);
  51851. }
  51852. /**
  51853. * @event metachange
  51854. * Fires whenever the server has sent back new metadata to reconfigure the Reader.
  51855. * @param {Ext.data.Store} this
  51856. * @param {Object} data The metadata sent back from the server.
  51857. */
  51858. this.fireEvent('metachange', this, data);
  51859. },
  51860. /**
  51861. * Returns all Model instances that are either currently a phantom (e.g. have no id), or have an ID but have not
  51862. * yet been saved on this Store (this happens when adding a non-phantom record from another Store into this one).
  51863. * @return {Ext.data.Model[]} The Model instances.
  51864. */
  51865. getNewRecords: function() {
  51866. return this.data.filterBy(function(item) {
  51867. // only want phantom records that are valid
  51868. return item.phantom === true && item.isValid();
  51869. }).items;
  51870. },
  51871. /**
  51872. * Returns all Model instances that have been updated in the Store but not yet synchronized with the Proxy.
  51873. * @return {Ext.data.Model[]} The updated Model instances.
  51874. */
  51875. getUpdatedRecords: function() {
  51876. return this.data.filterBy(function(item) {
  51877. // only want dirty records, not phantoms that are valid
  51878. return item.dirty === true && item.phantom !== true && item.isValid();
  51879. }).items;
  51880. },
  51881. /**
  51882. * Returns any records that have been removed from the store but not yet destroyed on the proxy.
  51883. * @return {Ext.data.Model[]} The removed Model instances.
  51884. */
  51885. getRemovedRecords: function() {
  51886. return this.removed;
  51887. },
  51888. // PAGING METHODS
  51889. /**
  51890. * Loads a given 'page' of data by setting the start and limit values appropriately. Internally this just causes a normal
  51891. * load operation, passing in calculated `start` and `limit` params.
  51892. * @param {Number} page The number of the page to load.
  51893. * @param {Object} options See options for {@link #method-load}.
  51894. * @param scope
  51895. */
  51896. loadPage: function(page, options, scope) {
  51897. if (typeof options === 'function') {
  51898. options = {
  51899. callback: options,
  51900. scope: scope || this
  51901. };
  51902. }
  51903. var me = this,
  51904. pageSize = me.getPageSize(),
  51905. clearOnPageLoad = me.getClearOnPageLoad();
  51906. options = Ext.apply({}, options);
  51907. me.currentPage = page;
  51908. me.load(Ext.applyIf(options, {
  51909. page: page,
  51910. start: (page - 1) * pageSize,
  51911. limit: pageSize,
  51912. addRecords: !clearOnPageLoad
  51913. }));
  51914. },
  51915. /**
  51916. * Loads the next 'page' in the current data set.
  51917. * @param {Object} options See options for {@link #method-load}.
  51918. */
  51919. nextPage: function(options) {
  51920. this.loadPage(this.currentPage + 1, options);
  51921. },
  51922. /**
  51923. * Loads the previous 'page' in the current data set.
  51924. * @param {Object} options See options for {@link #method-load}.
  51925. */
  51926. previousPage: function(options) {
  51927. this.loadPage(this.currentPage - 1, options);
  51928. },
  51929. destroy: function() {
  51930. Ext.data.StoreManager.unregister(this);
  51931. this.callParent(arguments);
  51932. }
  51933. });
  51934. /**
  51935. * @private
  51936. */
  51937. Ext.define('Ext.util.Offset', {
  51938. /* Begin Definitions */
  51939. statics: {
  51940. fromObject: function(obj) {
  51941. return new this(obj.x, obj.y);
  51942. }
  51943. },
  51944. /* End Definitions */
  51945. constructor: function(x, y) {
  51946. this.x = (x != null && !isNaN(x)) ? x : 0;
  51947. this.y = (y != null && !isNaN(y)) ? y : 0;
  51948. return this;
  51949. },
  51950. copy: function() {
  51951. return new Ext.util.Offset(this.x, this.y);
  51952. },
  51953. copyFrom: function(p) {
  51954. this.x = p.x;
  51955. this.y = p.y;
  51956. },
  51957. toString: function() {
  51958. return "Offset[" + this.x + "," + this.y + "]";
  51959. },
  51960. equals: function(offset) {
  51961. //<debug>
  51962. if(!(offset instanceof this.statics())) {
  51963. Ext.Error.raise('Offset must be an instance of Ext.util.Offset');
  51964. }
  51965. //</debug>
  51966. return (this.x == offset.x && this.y == offset.y);
  51967. },
  51968. round: function(to) {
  51969. if (!isNaN(to)) {
  51970. var factor = Math.pow(10, to);
  51971. this.x = Math.round(this.x * factor) / factor;
  51972. this.y = Math.round(this.y * factor) / factor;
  51973. } else {
  51974. this.x = Math.round(this.x);
  51975. this.y = Math.round(this.y);
  51976. }
  51977. },
  51978. isZero: function() {
  51979. return this.x == 0 && this.y == 0;
  51980. }
  51981. });
  51982. /**
  51983. * Represents a rectangular region and provides a number of utility methods
  51984. * to compare regions.
  51985. */
  51986. Ext.define('Ext.util.Region', {
  51987. requires: ['Ext.util.Offset'],
  51988. statics: {
  51989. /**
  51990. * @static
  51991. * Retrieves an Ext.util.Region for a particular element.
  51992. * @param {String/HTMLElement/Ext.Element} el The element or its ID.
  51993. * @return {Ext.util.Region} region
  51994. */
  51995. getRegion: function(el) {
  51996. return Ext.fly(el).getPageBox(true);
  51997. },
  51998. /**
  51999. * @static
  52000. * Creates new Region from an object:
  52001. *
  52002. * Ext.util.Region.from({top: 0, right: 5, bottom: 3, left: -1});
  52003. * // the above is equivalent to:
  52004. * new Ext.util.Region(0, 5, 3, -1);
  52005. *
  52006. * @param {Object} o An object with `top`, `right`, `bottom`, and `left` properties.
  52007. * @param {Number} o.top
  52008. * @param {Number} o.right
  52009. * @param {Number} o.bottom
  52010. * @param {Number} o.left
  52011. * @return {Ext.util.Region} The region constructed based on the passed object.
  52012. */
  52013. from: function(o) {
  52014. return new this(o.top, o.right, o.bottom, o.left);
  52015. }
  52016. },
  52017. /**
  52018. * Creates new Region.
  52019. * @param {Number} top Top
  52020. * @param {Number} right Right
  52021. * @param {Number} bottom Bottom
  52022. * @param {Number} left Left
  52023. */
  52024. constructor: function(top, right, bottom, left) {
  52025. var me = this;
  52026. me.top = top;
  52027. me[1] = top;
  52028. me.right = right;
  52029. me.bottom = bottom;
  52030. me.left = left;
  52031. me[0] = left;
  52032. },
  52033. /**
  52034. * Checks if this region completely contains the region that is passed in.
  52035. * @param {Ext.util.Region} region
  52036. * @return {Boolean}
  52037. */
  52038. contains: function(region) {
  52039. var me = this;
  52040. return (region.left >= me.left &&
  52041. region.right <= me.right &&
  52042. region.top >= me.top &&
  52043. region.bottom <= me.bottom);
  52044. },
  52045. /**
  52046. * Checks if this region intersects the region passed in.
  52047. * @param {Ext.util.Region} region
  52048. * @return {Ext.util.Region/Boolean} Returns the intersected region or `false` if there is no intersection.
  52049. */
  52050. intersect: function(region) {
  52051. var me = this,
  52052. t = Math.max(me.top, region.top),
  52053. r = Math.min(me.right, region.right),
  52054. b = Math.min(me.bottom, region.bottom),
  52055. l = Math.max(me.left, region.left);
  52056. if (b > t && r > l) {
  52057. return new Ext.util.Region(t, r, b, l);
  52058. }
  52059. else {
  52060. return false;
  52061. }
  52062. },
  52063. /**
  52064. * Returns the smallest region that contains the current AND `targetRegion`.
  52065. * @param {Ext.util.Region} region
  52066. * @return {Ext.util.Region}
  52067. */
  52068. union: function(region) {
  52069. var me = this,
  52070. t = Math.min(me.top, region.top),
  52071. r = Math.max(me.right, region.right),
  52072. b = Math.max(me.bottom, region.bottom),
  52073. l = Math.min(me.left, region.left);
  52074. return new Ext.util.Region(t, r, b, l);
  52075. },
  52076. /**
  52077. * Modifies the current region to be constrained to the `targetRegion`.
  52078. * @param {Ext.util.Region} targetRegion
  52079. * @return {Ext.util.Region} this
  52080. */
  52081. constrainTo: function(targetRegion) {
  52082. var me = this,
  52083. constrain = Ext.util.Numbers.constrain;
  52084. me.top = constrain(me.top, targetRegion.top, targetRegion.bottom);
  52085. me.bottom = constrain(me.bottom, targetRegion.top, targetRegion.bottom);
  52086. me.left = constrain(me.left, targetRegion.left, targetRegion.right);
  52087. me.right = constrain(me.right, targetRegion.left, targetRegion.right);
  52088. return me;
  52089. },
  52090. /**
  52091. * Modifies the current region to be adjusted by offsets.
  52092. * @param {Number} top Top offset
  52093. * @param {Number} right Right offset
  52094. * @param {Number} bottom Bottom offset
  52095. * @param {Number} left Left offset
  52096. * @return {Ext.util.Region} this
  52097. * @chainable
  52098. */
  52099. adjust: function(top, right, bottom, left) {
  52100. var me = this;
  52101. me.top += top;
  52102. me.left += left;
  52103. me.right += right;
  52104. me.bottom += bottom;
  52105. return me;
  52106. },
  52107. /**
  52108. * Get the offset amount of a point outside the region.
  52109. * @param {String/Object} axis optional.
  52110. * @param {Ext.util.Point} p The point.
  52111. * @return {Ext.util.Region}
  52112. */
  52113. getOutOfBoundOffset: function(axis, p) {
  52114. if (!Ext.isObject(axis)) {
  52115. if (axis == 'x') {
  52116. return this.getOutOfBoundOffsetX(p);
  52117. } else {
  52118. return this.getOutOfBoundOffsetY(p);
  52119. }
  52120. } else {
  52121. var d = new Ext.util.Offset();
  52122. d.x = this.getOutOfBoundOffsetX(axis.x);
  52123. d.y = this.getOutOfBoundOffsetY(axis.y);
  52124. return d;
  52125. }
  52126. },
  52127. /**
  52128. * Get the offset amount on the x-axis.
  52129. * @param {Number} p The offset.
  52130. * @return {Number}
  52131. */
  52132. getOutOfBoundOffsetX: function(p) {
  52133. if (p <= this.left) {
  52134. return this.left - p;
  52135. } else if (p >= this.right) {
  52136. return this.right - p;
  52137. }
  52138. return 0;
  52139. },
  52140. /**
  52141. * Get the offset amount on the y-axis.
  52142. * @param {Number} p The offset.
  52143. * @return {Number}
  52144. */
  52145. getOutOfBoundOffsetY: function(p) {
  52146. if (p <= this.top) {
  52147. return this.top - p;
  52148. } else if (p >= this.bottom) {
  52149. return this.bottom - p;
  52150. }
  52151. return 0;
  52152. },
  52153. /**
  52154. * Check whether the point / offset is out of bounds.
  52155. * @param {String} axis optional
  52156. * @param {Ext.util.Point/Number} p The point / offset.
  52157. * @return {Boolean}
  52158. */
  52159. isOutOfBound: function(axis, p) {
  52160. if (!Ext.isObject(axis)) {
  52161. if (axis == 'x') {
  52162. return this.isOutOfBoundX(p);
  52163. } else {
  52164. return this.isOutOfBoundY(p);
  52165. }
  52166. } else {
  52167. p = axis;
  52168. return (this.isOutOfBoundX(p.x) || this.isOutOfBoundY(p.y));
  52169. }
  52170. },
  52171. /**
  52172. * Check whether the offset is out of bound in the x-axis.
  52173. * @param {Number} p The offset.
  52174. * @return {Boolean}
  52175. */
  52176. isOutOfBoundX: function(p) {
  52177. return (p < this.left || p > this.right);
  52178. },
  52179. /**
  52180. * Check whether the offset is out of bound in the y-axis.
  52181. * @param {Number} p The offset.
  52182. * @return {Boolean}
  52183. */
  52184. isOutOfBoundY: function(p) {
  52185. return (p < this.top || p > this.bottom);
  52186. },
  52187. /*
  52188. * Restrict a point within the region by a certain factor.
  52189. * @param {String} axis Optional
  52190. * @param {Ext.util.Point/Ext.util.Offset/Object} p
  52191. * @param {Number} factor
  52192. * @return {Ext.util.Point/Ext.util.Offset/Object/Number}
  52193. */
  52194. restrict: function(axis, p, factor) {
  52195. if (Ext.isObject(axis)) {
  52196. var newP;
  52197. factor = p;
  52198. p = axis;
  52199. if (p.copy) {
  52200. newP = p.copy();
  52201. }
  52202. else {
  52203. newP = {
  52204. x: p.x,
  52205. y: p.y
  52206. };
  52207. }
  52208. newP.x = this.restrictX(p.x, factor);
  52209. newP.y = this.restrictY(p.y, factor);
  52210. return newP;
  52211. } else {
  52212. if (axis == 'x') {
  52213. return this.restrictX(p, factor);
  52214. } else {
  52215. return this.restrictY(p, factor);
  52216. }
  52217. }
  52218. },
  52219. /*
  52220. * Restrict an offset within the region by a certain factor, on the x-axis.
  52221. * @param {Number} p
  52222. * @param {Number} [factor=1] (optional) The factor.
  52223. * @return {Number}
  52224. */
  52225. restrictX: function(p, factor) {
  52226. if (!factor) {
  52227. factor = 1;
  52228. }
  52229. if (p <= this.left) {
  52230. p -= (p - this.left) * factor;
  52231. }
  52232. else if (p >= this.right) {
  52233. p -= (p - this.right) * factor;
  52234. }
  52235. return p;
  52236. },
  52237. /*
  52238. * Restrict an offset within the region by a certain factor, on the y-axis.
  52239. * @param {Number} p
  52240. * @param {Number} [factor=1] (optional) The factor.
  52241. * @return {Number}
  52242. */
  52243. restrictY: function(p, factor) {
  52244. if (!factor) {
  52245. factor = 1;
  52246. }
  52247. if (p <= this.top) {
  52248. p -= (p - this.top) * factor;
  52249. }
  52250. else if (p >= this.bottom) {
  52251. p -= (p - this.bottom) * factor;
  52252. }
  52253. return p;
  52254. },
  52255. /*
  52256. * Get the width / height of this region.
  52257. * @return {Object} An object with `width` and `height` properties.
  52258. * @return {Number} return.width
  52259. * @return {Number} return.height
  52260. */
  52261. getSize: function() {
  52262. return {
  52263. width: this.right - this.left,
  52264. height: this.bottom - this.top
  52265. };
  52266. },
  52267. /**
  52268. * Copy a new instance.
  52269. * @return {Ext.util.Region}
  52270. */
  52271. copy: function() {
  52272. return new Ext.util.Region(this.top, this.right, this.bottom, this.left);
  52273. },
  52274. /**
  52275. * Dump this to an eye-friendly string, great for debugging.
  52276. * @return {String} For example `Region[0,1,3,2]`.
  52277. */
  52278. toString: function() {
  52279. return "Region[" + this.top + "," + this.right + "," + this.bottom + "," + this.left + "]";
  52280. },
  52281. /**
  52282. * Translate this region by the given offset amount.
  52283. * @param {Object} offset
  52284. * @return {Ext.util.Region} This Region.
  52285. * @chainable
  52286. */
  52287. translateBy: function(offset) {
  52288. this.left += offset.x;
  52289. this.right += offset.x;
  52290. this.top += offset.y;
  52291. this.bottom += offset.y;
  52292. return this;
  52293. },
  52294. /**
  52295. * Round all the properties of this region.
  52296. * @return {Ext.util.Region} This Region.
  52297. * @chainable
  52298. */
  52299. round: function() {
  52300. this.top = Math.round(this.top);
  52301. this.right = Math.round(this.right);
  52302. this.bottom = Math.round(this.bottom);
  52303. this.left = Math.round(this.left);
  52304. return this;
  52305. },
  52306. /**
  52307. * Check whether this region is equivalent to the given region.
  52308. * @param {Ext.util.Region} region The region to compare with.
  52309. * @return {Boolean}
  52310. */
  52311. equals: function(region) {
  52312. return (this.top == region.top && this.right == region.right && this.bottom == region.bottom && this.left == region.left)
  52313. }
  52314. });
  52315. /**
  52316. * @private
  52317. */
  52318. Ext.define('Ext.event.publisher.Publisher', {
  52319. targetType: '',
  52320. idSelectorRegex: /^#([\w\-]+)$/i,
  52321. constructor: function() {
  52322. var handledEvents = this.handledEvents,
  52323. handledEventsMap,
  52324. i, ln, event;
  52325. handledEventsMap = this.handledEventsMap = {};
  52326. for (i = 0,ln = handledEvents.length; i < ln; i++) {
  52327. event = handledEvents[i];
  52328. handledEventsMap[event] = true;
  52329. }
  52330. this.subscribers = {};
  52331. return this;
  52332. },
  52333. handles: function(eventName) {
  52334. var map = this.handledEventsMap;
  52335. return !!map[eventName] || !!map['*'] || eventName === '*';
  52336. },
  52337. getHandledEvents: function() {
  52338. return this.handledEvents;
  52339. },
  52340. setDispatcher: function(dispatcher) {
  52341. this.dispatcher = dispatcher;
  52342. },
  52343. subscribe: function() {
  52344. return false;
  52345. },
  52346. unsubscribe: function() {
  52347. return false;
  52348. },
  52349. unsubscribeAll: function() {
  52350. delete this.subscribers;
  52351. this.subscribers = {};
  52352. return this;
  52353. },
  52354. notify: function() {
  52355. return false;
  52356. },
  52357. getTargetType: function() {
  52358. return this.targetType;
  52359. },
  52360. dispatch: function(target, eventName, args) {
  52361. this.dispatcher.doDispatchEvent(this.targetType, target, eventName, args);
  52362. }
  52363. });
  52364. /**
  52365. * @author Ed Spencer
  52366. * @class Ext.data.reader.Array
  52367. *
  52368. * Data reader class to create an Array of {@link Ext.data.Model} objects from an Array.
  52369. * Each element of that Array represents a row of data fields. The
  52370. * fields are pulled into a Record object using as a subscript, the `mapping` property
  52371. * of the field definition if it exists, or the field's ordinal position in the definition.
  52372. *
  52373. * Example code:
  52374. *
  52375. * Employee = Ext.define('Employee', {
  52376. * extend: 'Ext.data.Model',
  52377. * config: {
  52378. * fields: [
  52379. * 'id',
  52380. * {name: 'name', mapping: 1}, // "mapping" only needed if an "id" field is present which
  52381. * {name: 'occupation', mapping: 2} // precludes using the ordinal position as the index.
  52382. * ]
  52383. * }
  52384. * });
  52385. *
  52386. * var myReader = new Ext.data.reader.Array({
  52387. * model: 'Employee'
  52388. * }, Employee);
  52389. *
  52390. * This would consume an Array like this:
  52391. *
  52392. * [ [1, 'Bill', 'Gardener'], [2, 'Ben', 'Horticulturalist'] ]
  52393. *
  52394. * @constructor
  52395. * Create a new ArrayReader
  52396. * @param {Object} meta Metadata configuration options.
  52397. */
  52398. Ext.define('Ext.data.reader.Array', {
  52399. extend: 'Ext.data.reader.Json',
  52400. alternateClassName: 'Ext.data.ArrayReader',
  52401. alias : 'reader.array',
  52402. // For Array Reader, methods in the base which use these properties must not see the defaults
  52403. config: {
  52404. totalProperty: undefined,
  52405. successProperty: undefined
  52406. },
  52407. /**
  52408. * @private
  52409. * Returns an accessor expression for the passed Field from an Array using either the Field's mapping, or
  52410. * its ordinal position in the fields collection as the index.
  52411. * This is used by buildExtractors to create optimized on extractor function which converts raw data into model instances.
  52412. */
  52413. createFieldAccessExpression: function(field, fieldVarName, dataName) {
  52414. var me = this,
  52415. mapping = field.getMapping(),
  52416. index = (mapping == null) ? me.getModel().getFields().indexOf(field) : mapping,
  52417. result;
  52418. if (typeof index === 'function') {
  52419. result = fieldVarName + '.getMapping()(' + dataName + ', this)';
  52420. } else {
  52421. if (isNaN(index)) {
  52422. index = '"' + index + '"';
  52423. }
  52424. result = dataName + "[" + index + "]";
  52425. }
  52426. return result;
  52427. }
  52428. });
  52429. /**
  52430. * @author Ed Spencer
  52431. * @aside guide stores
  52432. *
  52433. * Small helper class to make creating {@link Ext.data.Store}s from Array data easier. An ArrayStore will be
  52434. * automatically configured with a {@link Ext.data.reader.Array}.
  52435. *
  52436. * A store configuration would be something like:
  52437. *
  52438. * var store = Ext.create('Ext.data.ArrayStore', {
  52439. * // store configs
  52440. * autoDestroy: true,
  52441. * storeId: 'myStore',
  52442. * // reader configs
  52443. * idIndex: 0,
  52444. * fields: [
  52445. * 'company',
  52446. * {name: 'price', type: 'float'},
  52447. * {name: 'change', type: 'float'},
  52448. * {name: 'pctChange', type: 'float'},
  52449. * {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
  52450. * ]
  52451. * });
  52452. *
  52453. * This store is configured to consume a returned object of the form:
  52454. *
  52455. * var myData = [
  52456. * ['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
  52457. * ['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
  52458. * ['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
  52459. * ['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
  52460. * ['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
  52461. * ];
  52462. *
  52463. * An object literal of this form could also be used as the {@link #data} config option.
  52464. *
  52465. * **Note:** Although not listed here, this class accepts all of the configuration options of
  52466. * **{@link Ext.data.reader.Array ArrayReader}**.
  52467. */
  52468. Ext.define('Ext.data.ArrayStore', {
  52469. extend: 'Ext.data.Store',
  52470. alias: 'store.array',
  52471. uses: ['Ext.data.reader.Array'],
  52472. config: {
  52473. proxy: {
  52474. type: 'memory',
  52475. reader: 'array'
  52476. }
  52477. },
  52478. loadData: function(data, append) {
  52479. // if (this.expandData === true) {
  52480. // var r = [],
  52481. // i = 0,
  52482. // ln = data.length;
  52483. //
  52484. // for (; i < ln; i++) {
  52485. // r[r.length] = [data[i]];
  52486. // }
  52487. //
  52488. // data = r;
  52489. // }
  52490. this.callParent([data, append]);
  52491. }
  52492. }, function() {
  52493. // backwards compat
  52494. Ext.data.SimpleStore = Ext.data.ArrayStore;
  52495. // Ext.reg('simplestore', Ext.data.SimpleStore);
  52496. });
  52497. /**
  52498. * Ext.Direct aims to streamline communication between the client and server by providing a single interface that
  52499. * reduces the amount of common code typically required to validate data and handle returned data packets (reading data,
  52500. * error conditions, etc).
  52501. *
  52502. * The Ext.direct namespace includes several classes for a closer integration with the server-side. The Ext.data
  52503. * namespace also includes classes for working with Ext.data.Stores which are backed by data from an Ext.Direct method.
  52504. *
  52505. * # Specification
  52506. *
  52507. * For additional information consult the [Ext.Direct Specification](http://sencha.com/products/extjs/extdirect).
  52508. *
  52509. * # Providers
  52510. *
  52511. * Ext.Direct uses a provider architecture, where one or more providers are used to transport data to and from the
  52512. * server. There are several providers that exist in the core at the moment:
  52513. *
  52514. * - {@link Ext.direct.JsonProvider JsonProvider} for simple JSON operations
  52515. * - {@link Ext.direct.PollingProvider PollingProvider} for repeated requests
  52516. * - {@link Ext.direct.RemotingProvider RemotingProvider} exposes server side on the client.
  52517. *
  52518. * A provider does not need to be invoked directly, providers are added via {@link Ext.direct.Manager}.{@link #addProvider}.
  52519. *
  52520. * # Router
  52521. *
  52522. * Ext.Direct utilizes a "router" on the server to direct requests from the client to the appropriate server-side
  52523. * method. Because the Ext.Direct API is completely platform-agnostic, you could completely swap out a Java based server
  52524. * solution and replace it with one that uses C# without changing the client side JavaScript at all.
  52525. *
  52526. * # Server side events
  52527. *
  52528. * Custom events from the server may be handled by the client by adding listeners, for example:
  52529. *
  52530. * {"type":"event","name":"message","data":"Successfully polled at: 11:19:30 am"}
  52531. *
  52532. * // add a handler for a 'message' event sent by the server
  52533. * Ext.direct.Manager.on('message', function(e){
  52534. * out.append(String.format('<p><i>{0}</i></p>', e.data));
  52535. * out.el.scrollTo('t', 100000, true);
  52536. * });
  52537. *
  52538. * @singleton
  52539. * @alternateClassName Ext.Direct
  52540. */
  52541. Ext.define('Ext.direct.Manager', {
  52542. singleton: true,
  52543. mixins: {
  52544. observable: 'Ext.mixin.Observable'
  52545. },
  52546. requires: ['Ext.util.Collection'],
  52547. alternateClassName: 'Ext.Direct',
  52548. exceptions: {
  52549. TRANSPORT: 'xhr',
  52550. PARSE: 'parse',
  52551. LOGIN: 'login',
  52552. SERVER: 'exception'
  52553. },
  52554. /**
  52555. * @event event
  52556. * Fires after an event.
  52557. * @param {Ext.direct.Event} e The Ext.direct.Event type that occurred.
  52558. * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
  52559. */
  52560. /**
  52561. * @event exception
  52562. * Fires after an event exception.
  52563. * @param {Ext.direct.Event} e The event type that occurred.
  52564. */
  52565. constructor: function() {
  52566. var me = this;
  52567. me.transactions = Ext.create('Ext.util.Collection', this.getKey);
  52568. me.providers = Ext.create('Ext.util.Collection', this.getKey);
  52569. },
  52570. getKey: function(item) {
  52571. return item.getId();
  52572. },
  52573. /**
  52574. * Adds an Ext.Direct Provider and creates the proxy or stub methods to execute server-side methods. If the provider
  52575. * is not already connected, it will auto-connect.
  52576. *
  52577. * Ext.direct.Manager.addProvider({
  52578. * type: "remoting", // create a {@link Ext.direct.RemotingProvider}
  52579. * url: "php/router.php", // url to connect to the Ext.Direct server-side router.
  52580. * actions: { // each property within the actions object represents a Class
  52581. * TestAction: [ // array of methods within each server side Class
  52582. * {
  52583. * name: "doEcho", // name of method
  52584. * len: 1
  52585. * },{
  52586. * name: "multiply",
  52587. * len: 1
  52588. * },{
  52589. * name: "doForm",
  52590. * formHandler: true, // handle form on server with Ext.Direct.Transaction
  52591. * len: 1
  52592. * }]
  52593. * },
  52594. * namespace: "myApplication" // namespace to create the Remoting Provider in
  52595. * });
  52596. *
  52597. * @param {Ext.direct.Provider/Object...} provider
  52598. * Accepts any number of Provider descriptions (an instance or config object for
  52599. * a Provider). Each Provider description instructs Ext.Direct how to create
  52600. * client-side stub methods.
  52601. * @return {Object}
  52602. */
  52603. addProvider : function(provider) {
  52604. var me = this,
  52605. args = Ext.toArray(arguments),
  52606. i = 0, ln;
  52607. if (args.length > 1) {
  52608. for (ln = args.length; i < ln; ++i) {
  52609. me.addProvider(args[i]);
  52610. }
  52611. return;
  52612. }
  52613. // if provider has not already been instantiated
  52614. if (!provider.isProvider) {
  52615. provider = Ext.create('direct.' + provider.type + 'provider', provider);
  52616. }
  52617. me.providers.add(provider);
  52618. provider.on('data', me.onProviderData, me);
  52619. if (!provider.isConnected()) {
  52620. provider.connect();
  52621. }
  52622. return provider;
  52623. },
  52624. /**
  52625. * Retrieves a {@link Ext.direct.Provider provider} by the **{@link Ext.direct.Provider#id id}** specified when the
  52626. * provider is {@link #addProvider added}.
  52627. * @param {String/Ext.direct.Provider} id The id of the provider, or the provider instance.
  52628. * @return {Object}
  52629. */
  52630. getProvider : function(id){
  52631. return id.isProvider ? id : this.providers.get(id);
  52632. },
  52633. /**
  52634. * Removes the provider.
  52635. * @param {String/Ext.direct.Provider} provider The provider instance or the id of the provider.
  52636. * @return {Ext.direct.Provider/null} The provider, `null` if not found.
  52637. */
  52638. removeProvider : function(provider) {
  52639. var me = this,
  52640. providers = me.providers;
  52641. provider = provider.isProvider ? provider : providers.get(provider);
  52642. if (provider) {
  52643. provider.un('data', me.onProviderData, me);
  52644. providers.remove(provider);
  52645. return provider;
  52646. }
  52647. return null;
  52648. },
  52649. /**
  52650. * Adds a transaction to the manager.
  52651. * @private
  52652. * @param {Ext.direct.Transaction} transaction The transaction to add
  52653. * @return {Ext.direct.Transaction} transaction
  52654. */
  52655. addTransaction: function(transaction) {
  52656. this.transactions.add(transaction);
  52657. return transaction;
  52658. },
  52659. /**
  52660. * Removes a transaction from the manager.
  52661. * @private
  52662. * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to remove
  52663. * @return {Ext.direct.Transaction} transaction
  52664. */
  52665. removeTransaction: function(transaction) {
  52666. transaction = this.getTransaction(transaction);
  52667. this.transactions.remove(transaction);
  52668. return transaction;
  52669. },
  52670. /**
  52671. * Gets a transaction
  52672. * @private
  52673. * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to get
  52674. * @return {Ext.direct.Transaction}
  52675. */
  52676. getTransaction: function(transaction) {
  52677. return Ext.isObject(transaction) ? transaction : this.transactions.get(transaction);
  52678. },
  52679. onProviderData : function(provider, event) {
  52680. var me = this,
  52681. i = 0, ln,
  52682. name;
  52683. if (Ext.isArray(event)) {
  52684. for (ln = event.length; i < ln; ++i) {
  52685. me.onProviderData(provider, event[i]);
  52686. }
  52687. return;
  52688. }
  52689. name = event.getName();
  52690. if (name && name != 'event' && name != 'exception') {
  52691. me.fireEvent(name, event);
  52692. } else if (event.getStatus() === false) {
  52693. me.fireEvent('exception', event);
  52694. }
  52695. me.fireEvent('event', event, provider);
  52696. },
  52697. /**
  52698. * Parses a direct function. It may be passed in a string format, for example:
  52699. * "MyApp.Person.read".
  52700. * @protected
  52701. * @param {String/Function} fn The direct function
  52702. * @return {Function} The function to use in the direct call. Null if not found
  52703. */
  52704. parseMethod: function(fn) {
  52705. if (Ext.isString(fn)) {
  52706. var parts = fn.split('.'),
  52707. i = 0,
  52708. ln = parts.length,
  52709. current = window;
  52710. while (current && i < ln) {
  52711. current = current[parts[i]];
  52712. ++i;
  52713. }
  52714. fn = Ext.isFunction(current) ? current : null;
  52715. }
  52716. return fn || null;
  52717. }
  52718. });
  52719. /**
  52720. * @aside guide proxies
  52721. *
  52722. * This class is used to send requests to the server using {@link Ext.direct.Manager Ext.Direct}. When a
  52723. * request is made, the transport mechanism is handed off to the appropriate
  52724. * {@link Ext.direct.RemotingProvider Provider} to complete the call.
  52725. *
  52726. * # Specifying the function
  52727. *
  52728. * This proxy expects a Direct remoting method to be passed in order to be able to complete requests.
  52729. * This can be done by specifying the {@link #directFn} configuration. This will use the same direct
  52730. * method for all requests. Alternatively, you can provide an {@link #api} configuration. This
  52731. * allows you to specify a different remoting method for each CRUD action.
  52732. *
  52733. * # Parameters
  52734. *
  52735. * This proxy provides options to help configure which parameters will be sent to the server.
  52736. * By specifying the {@link #paramsAsHash} option, it will send an object literal containing each
  52737. * of the passed parameters. The {@link #paramOrder} option can be used to specify the order in which
  52738. * the remoting method parameters are passed.
  52739. *
  52740. * # Example Usage
  52741. *
  52742. * Ext.define('User', {
  52743. * extend: 'Ext.data.Model',
  52744. * config: {
  52745. * fields: ['firstName', 'lastName'],
  52746. * proxy: {
  52747. * type: 'direct',
  52748. * directFn: MyApp.getUsers,
  52749. * paramOrder: 'id' // Tells the proxy to pass the id as the first parameter to the remoting method.
  52750. * }
  52751. * }
  52752. * });
  52753. * User.load(1);
  52754. */
  52755. Ext.define('Ext.data.proxy.Direct', {
  52756. extend: 'Ext.data.proxy.Server',
  52757. alternateClassName: 'Ext.data.DirectProxy',
  52758. alias: 'proxy.direct',
  52759. requires: ['Ext.direct.Manager'],
  52760. config: {
  52761. /**
  52762. * @cfg {String/String[]} paramOrder
  52763. * Defaults to undefined. A list of params to be executed server side. Specify the params in the order in
  52764. * which they must be executed on the server-side as either (1) an Array of String values, or (2) a String
  52765. * of params delimited by either whitespace, comma, or pipe. For example, any of the following would be
  52766. * acceptable:
  52767. *
  52768. * paramOrder: ['param1','param2','param3']
  52769. * paramOrder: 'param1 param2 param3'
  52770. * paramOrder: 'param1,param2,param3'
  52771. * paramOrder: 'param1|param2|param'
  52772. */
  52773. paramOrder: undefined,
  52774. /**
  52775. * @cfg {Boolean} paramsAsHash
  52776. * Send parameters as a collection of named arguments.
  52777. * Providing a {@link #paramOrder} nullifies this configuration.
  52778. */
  52779. paramsAsHash: true,
  52780. /**
  52781. * @cfg {Function/String} directFn
  52782. * Function to call when executing a request. directFn is a simple alternative to defining the api configuration-parameter
  52783. * for Store's which will not implement a full CRUD api. The directFn may also be a string reference to the fully qualified
  52784. * name of the function, for example: 'MyApp.company.GetProfile'. This can be useful when using dynamic loading. The string
  52785. * will be looked up when the proxy is created.
  52786. */
  52787. directFn : undefined,
  52788. /**
  52789. * @cfg {Object} api
  52790. * The same as {@link Ext.data.proxy.Server#api}, however instead of providing urls, you should provide a direct
  52791. * function call. See {@link #directFn}.
  52792. */
  52793. api: null,
  52794. /**
  52795. * @cfg {Object} extraParams
  52796. * Extra parameters that will be included on every read request. Individual requests with params
  52797. * of the same name will override these params when they are in conflict.
  52798. */
  52799. extraParams: null
  52800. },
  52801. // @private
  52802. paramOrderRe: /[\s,|]/,
  52803. applyParamOrder: function(paramOrder) {
  52804. if (Ext.isString(paramOrder)) {
  52805. paramOrder = paramOrder.split(this.paramOrderRe);
  52806. }
  52807. return paramOrder;
  52808. },
  52809. applyDirectFn: function(directFn) {
  52810. return Ext.direct.Manager.parseMethod(directFn);
  52811. },
  52812. applyApi: function(api) {
  52813. var fn;
  52814. if (api && Ext.isObject(api)) {
  52815. for (fn in api) {
  52816. if (api.hasOwnProperty(fn)) {
  52817. api[fn] = Ext.direct.Manager.parseMethod(api[fn]);
  52818. }
  52819. }
  52820. }
  52821. return api;
  52822. },
  52823. doRequest: function(operation, callback, scope) {
  52824. var me = this,
  52825. writer = me.getWriter(),
  52826. request = me.buildRequest(operation, callback, scope),
  52827. api = me.getApi(),
  52828. fn = api && api[request.getAction()] || me.getDirectFn(),
  52829. params = request.getParams(),
  52830. args = [],
  52831. method;
  52832. //<debug>
  52833. if (!fn) {
  52834. Ext.Logger.error('No direct function specified for this proxy');
  52835. }
  52836. //</debug>
  52837. request = writer.write(request);
  52838. if (operation.getAction() == 'read') {
  52839. // We need to pass params
  52840. method = fn.directCfg.method;
  52841. args = method.getArgs(params, me.getParamOrder(), me.getParamsAsHash());
  52842. } else {
  52843. args.push(request.getJsonData());
  52844. }
  52845. args.push(me.createRequestCallback(request, operation, callback, scope), me);
  52846. request.setConfig({
  52847. args: args,
  52848. directFn: fn
  52849. });
  52850. fn.apply(window, args);
  52851. },
  52852. /*
  52853. * Inherit docs. We don't apply any encoding here because
  52854. * all of the direct requests go out as jsonData
  52855. */
  52856. applyEncoding: function(value) {
  52857. return value;
  52858. },
  52859. createRequestCallback: function(request, operation, callback, scope) {
  52860. var me = this;
  52861. return function(data, event) {
  52862. me.processResponse(event.getStatus(), operation, request, event.getResult(), callback, scope);
  52863. };
  52864. },
  52865. // @inheritdoc
  52866. extractResponseData: function(response) {
  52867. var result = response.getResult();
  52868. return Ext.isDefined(result) ? result : response.getData();
  52869. },
  52870. // @inheritdoc
  52871. setException: function(operation, response) {
  52872. operation.setException(response.getMessage());
  52873. },
  52874. // @inheritdoc
  52875. buildUrl: function() {
  52876. return '';
  52877. }
  52878. });
  52879. /**
  52880. * @aside guide stores
  52881. *
  52882. * Small helper class to create an {@link Ext.data.Store} configured with an {@link Ext.data.proxy.Direct}
  52883. * and {@link Ext.data.reader.Json} to make interacting with an {@link Ext.direct.Manager} server-side
  52884. * {@link Ext.direct.Provider Provider} easier. To create a different proxy/reader combination create a basic
  52885. * {@link Ext.data.Store} configured as needed.
  52886. *
  52887. * Since configurations are deeply merged with the standard configuration, you can override certain proxy and
  52888. * reader configurations like this:
  52889. *
  52890. * Ext.create('Ext.data.DirectStore', {
  52891. * proxy: {
  52892. * paramsAsHash: true,
  52893. * directFn: someDirectFn,
  52894. * simpleSortMode: true,
  52895. * reader: {
  52896. * rootProperty: 'results',
  52897. * idProperty: '_id'
  52898. * }
  52899. * }
  52900. * });
  52901. *
  52902. */
  52903. Ext.define('Ext.data.DirectStore', {
  52904. extend: 'Ext.data.Store',
  52905. alias: 'store.direct',
  52906. requires: ['Ext.data.proxy.Direct'],
  52907. config: {
  52908. proxy: {
  52909. type: 'direct',
  52910. reader: {
  52911. type: 'json'
  52912. }
  52913. }
  52914. }
  52915. });
  52916. /**
  52917. * @aside guide ajax
  52918. * @singleton
  52919. *
  52920. * This class is used to create JsonP requests. JsonP is a mechanism that allows for making requests for data cross
  52921. * domain. More information is available [here](http://en.wikipedia.org/wiki/JSONP).
  52922. *
  52923. * ## Example
  52924. *
  52925. * @example preview
  52926. * Ext.Viewport.add({
  52927. * xtype: 'button',
  52928. * text: 'Make JsonP Request',
  52929. * centered: true,
  52930. * handler: function(button) {
  52931. * // Mask the viewport
  52932. * Ext.Viewport.mask();
  52933. *
  52934. * // Remove the button
  52935. * button.destroy();
  52936. *
  52937. * // Make the JsonP request
  52938. * Ext.data.JsonP.request({
  52939. * url: 'http://free.worldweatheronline.com/feed/weather.ashx',
  52940. * callbackKey: 'callback',
  52941. * params: {
  52942. * key: '23f6a0ab24185952101705',
  52943. * q: '94301', // Palo Alto
  52944. * format: 'json',
  52945. * num_of_days: 5
  52946. * },
  52947. * success: function(result, request) {
  52948. * // Unmask the viewport
  52949. * Ext.Viewport.unmask();
  52950. *
  52951. * // Get the weather data from the json object result
  52952. * var weather = result.data.weather;
  52953. * if (weather) {
  52954. * // Style the viewport html, and set the html of the max temperature
  52955. * Ext.Viewport.setStyleHtmlContent(true);
  52956. * Ext.Viewport.setHtml('The temperature in Palo Alto is <b>' + weather[0].tempMaxF + '° F</b>');
  52957. * }
  52958. * }
  52959. * });
  52960. * }
  52961. * });
  52962. *
  52963. * See the {@link #request} method for more details on making a JsonP request.
  52964. */
  52965. Ext.define('Ext.data.JsonP', {
  52966. alternateClassName: 'Ext.util.JSONP',
  52967. /* Begin Definitions */
  52968. singleton: true,
  52969. /* End Definitions */
  52970. /**
  52971. * Number of requests done so far.
  52972. * @private
  52973. */
  52974. requestCount: 0,
  52975. /**
  52976. * Hash of pending requests.
  52977. * @private
  52978. */
  52979. requests: {},
  52980. /**
  52981. * @property {Number} [timeout=30000]
  52982. * A default timeout (in milliseconds) for any JsonP requests. If the request has not completed in this time the failure callback will
  52983. * be fired.
  52984. */
  52985. timeout: 30000,
  52986. /**
  52987. * @property {Boolean} disableCaching
  52988. * `true` to add a unique cache-buster param to requests.
  52989. */
  52990. disableCaching: true,
  52991. /**
  52992. * @property {String} disableCachingParam
  52993. * Change the parameter which is sent went disabling caching through a cache buster.
  52994. */
  52995. disableCachingParam: '_dc',
  52996. /**
  52997. * @property {String} callbackKey
  52998. * Specifies the GET parameter that will be sent to the server containing the function name to be executed when the
  52999. * request completes. Thus, a common request will be in the form of:
  53000. * `url?callback=Ext.data.JsonP.callback1`
  53001. */
  53002. callbackKey: 'callback',
  53003. /**
  53004. * Makes a JSONP request.
  53005. * @param {Object} options An object which may contain the following properties. Note that options will take
  53006. * priority over any defaults that are specified in the class.
  53007. *
  53008. * @param {String} options.url The URL to request.
  53009. * @param {Object} [options.params] An object containing a series of key value pairs that will be sent along with the request.
  53010. * @param {Number} [options.timeout] See {@link #timeout}
  53011. * @param {String} [options.callbackKey] See {@link #callbackKey}
  53012. * @param {String} [options.callbackName] See {@link #callbackKey}
  53013. * The function name to use for this request. By default this name will be auto-generated: Ext.data.JsonP.callback1,
  53014. * Ext.data.JsonP.callback2, etc. Setting this option to "my_name" will force the function name to be
  53015. * Ext.data.JsonP.my_name. Use this if you want deterministic behavior, but be careful - the callbackName should be
  53016. * different in each JsonP request that you make.
  53017. * @param {Boolean} [options.disableCaching] See {@link #disableCaching}
  53018. * @param {String} [options.disableCachingParam] See {@link #disableCachingParam}
  53019. * @param {Function} [options.success] A function to execute if the request succeeds.
  53020. * @param {Function} [options.failure] A function to execute if the request fails.
  53021. * @param {Function} [options.callback] A function to execute when the request completes, whether it is a success or failure.
  53022. * @param {Object} [options.scope] The scope in which to execute the callbacks: The "this" object for the
  53023. * callback function. Defaults to the browser window.
  53024. *
  53025. * @return {Object} request An object containing the request details.
  53026. */
  53027. request: function(options){
  53028. options = Ext.apply({}, options);
  53029. //<debug>
  53030. if (!options.url) {
  53031. Ext.Logger.error('A url must be specified for a JSONP request.');
  53032. }
  53033. //</debug>
  53034. var me = this,
  53035. disableCaching = Ext.isDefined(options.disableCaching) ? options.disableCaching : me.disableCaching,
  53036. cacheParam = options.disableCachingParam || me.disableCachingParam,
  53037. id = ++me.requestCount,
  53038. callbackName = options.callbackName || 'callback' + id,
  53039. callbackKey = options.callbackKey || me.callbackKey,
  53040. timeout = Ext.isDefined(options.timeout) ? options.timeout : me.timeout,
  53041. params = Ext.apply({}, options.params),
  53042. url = options.url,
  53043. name = Ext.isSandboxed ? Ext.getUniqueGlobalNamespace() : 'Ext',
  53044. request,
  53045. script;
  53046. params[callbackKey] = name + '.data.JsonP.' + callbackName;
  53047. if (disableCaching) {
  53048. params[cacheParam] = new Date().getTime();
  53049. }
  53050. script = me.createScript(url, params, options);
  53051. me.requests[id] = request = {
  53052. url: url,
  53053. params: params,
  53054. script: script,
  53055. id: id,
  53056. scope: options.scope,
  53057. success: options.success,
  53058. failure: options.failure,
  53059. callback: options.callback,
  53060. callbackKey: callbackKey,
  53061. callbackName: callbackName
  53062. };
  53063. if (timeout > 0) {
  53064. request.timeout = setTimeout(Ext.bind(me.handleTimeout, me, [request]), timeout);
  53065. }
  53066. me.setupErrorHandling(request);
  53067. me[callbackName] = Ext.bind(me.handleResponse, me, [request], true);
  53068. me.loadScript(request);
  53069. return request;
  53070. },
  53071. /**
  53072. * Abort a request. If the request parameter is not specified all open requests will be aborted.
  53073. * @param {Object/String} request The request to abort.
  53074. */
  53075. abort: function(request){
  53076. var requests = this.requests,
  53077. key;
  53078. if (request) {
  53079. if (!request.id) {
  53080. request = requests[request];
  53081. }
  53082. this.handleAbort(request);
  53083. } else {
  53084. for (key in requests) {
  53085. if (requests.hasOwnProperty(key)) {
  53086. this.abort(requests[key]);
  53087. }
  53088. }
  53089. }
  53090. },
  53091. /**
  53092. * Sets up error handling for the script.
  53093. * @private
  53094. * @param {Object} request The request.
  53095. */
  53096. setupErrorHandling: function(request){
  53097. request.script.onerror = Ext.bind(this.handleError, this, [request]);
  53098. },
  53099. /**
  53100. * Handles any aborts when loading the script.
  53101. * @private
  53102. * @param {Object} request The request.
  53103. */
  53104. handleAbort: function(request){
  53105. request.errorType = 'abort';
  53106. this.handleResponse(null, request);
  53107. },
  53108. /**
  53109. * Handles any script errors when loading the script.
  53110. * @private
  53111. * @param {Object} request The request.
  53112. */
  53113. handleError: function(request){
  53114. request.errorType = 'error';
  53115. this.handleResponse(null, request);
  53116. },
  53117. /**
  53118. * Cleans up any script handling errors.
  53119. * @private
  53120. * @param {Object} request The request.
  53121. */
  53122. cleanupErrorHandling: function(request){
  53123. request.script.onerror = null;
  53124. },
  53125. /**
  53126. * Handle any script timeouts.
  53127. * @private
  53128. * @param {Object} request The request.
  53129. */
  53130. handleTimeout: function(request){
  53131. request.errorType = 'timeout';
  53132. this.handleResponse(null, request);
  53133. },
  53134. /**
  53135. * Handle a successful response
  53136. * @private
  53137. * @param {Object} result The result from the request
  53138. * @param {Object} request The request
  53139. */
  53140. handleResponse: function(result, request){
  53141. var success = true;
  53142. if (request.timeout) {
  53143. clearTimeout(request.timeout);
  53144. }
  53145. delete this[request.callbackName];
  53146. delete this.requests[request.id];
  53147. this.cleanupErrorHandling(request);
  53148. Ext.fly(request.script).destroy();
  53149. if (request.errorType) {
  53150. success = false;
  53151. Ext.callback(request.failure, request.scope, [request.errorType, request]);
  53152. } else {
  53153. Ext.callback(request.success, request.scope, [result, request]);
  53154. }
  53155. Ext.callback(request.callback, request.scope, [success, result, request.errorType, request]);
  53156. },
  53157. /**
  53158. * Create the script tag given the specified url, params and options. The options
  53159. * parameter is passed to allow an override to access it.
  53160. * @private
  53161. * @param {String} url The url of the request
  53162. * @param {Object} params Any extra params to be sent
  53163. * @param {Object} options The object passed to {@link #request}.
  53164. */
  53165. createScript: function(url, params, options) {
  53166. var script = document.createElement('script');
  53167. script.setAttribute("src", Ext.urlAppend(url, Ext.Object.toQueryString(params)));
  53168. script.setAttribute("async", true);
  53169. script.setAttribute("type", "text/javascript");
  53170. return script;
  53171. },
  53172. /**
  53173. * Loads the script for the given request by appending it to the HEAD element. This is
  53174. * its own method so that users can override it (as well as {@link #createScript}).
  53175. * @private
  53176. * @param request The request object.
  53177. */
  53178. loadScript: function (request) {
  53179. Ext.getHead().appendChild(request.script);
  53180. }
  53181. });
  53182. /**
  53183. * @author Ed Spencer
  53184. * @class Ext.data.JsonStore
  53185. * @extends Ext.data.Store
  53186. * @private
  53187. *
  53188. * Small helper class to make creating {@link Ext.data.Store}s from JSON data easier.
  53189. * A JsonStore will be automatically configured with a {@link Ext.data.reader.Json}.
  53190. *
  53191. * A store configuration would be something like:
  53192. *
  53193. * var store = new Ext.data.JsonStore({
  53194. * // store configs
  53195. * autoDestroy: true,
  53196. * storeId: 'myStore',
  53197. *
  53198. * proxy: {
  53199. * type: 'ajax',
  53200. * url: 'get-images.php',
  53201. * reader: {
  53202. * type: 'json',
  53203. * root: 'images',
  53204. * idProperty: 'name'
  53205. * }
  53206. * },
  53207. *
  53208. * // alternatively, a {@link Ext.data.Model} name can be given (see {@link Ext.data.Store} for an example)
  53209. * fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
  53210. * });
  53211. *
  53212. * This store is configured to consume a returned object of the form:
  53213. *
  53214. * {
  53215. * images: [
  53216. * {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
  53217. * {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
  53218. * ]
  53219. * }
  53220. *
  53221. * An object literal of this form could also be used as the {@link #data} config option.
  53222. *
  53223. * @xtype jsonstore
  53224. */
  53225. Ext.define('Ext.data.JsonStore', {
  53226. extend: 'Ext.data.Store',
  53227. alias: 'store.json',
  53228. config: {
  53229. proxy: {
  53230. type: 'ajax',
  53231. reader: 'json',
  53232. writer: 'json'
  53233. }
  53234. }
  53235. });
  53236. /**
  53237. * @class Ext.data.NodeInterface
  53238. * This class is meant to be used as a set of methods that are applied to the prototype of a
  53239. * Record to decorate it with a Node API. This means that models used in conjunction with a tree
  53240. * will have all of the tree related methods available on the model. In general this class will
  53241. * not be used directly by the developer. This class also creates extra fields on the model if
  53242. * they do not exist, to help maintain the tree state and UI. These fields are:
  53243. *
  53244. * - parentId
  53245. * - index
  53246. * - depth
  53247. * - expanded
  53248. * - expandable
  53249. * - checked
  53250. * - leaf
  53251. * - cls
  53252. * - iconCls
  53253. * - root
  53254. * - isLast
  53255. * - isFirst
  53256. * - allowDrop
  53257. * - allowDrag
  53258. * - loaded
  53259. * - loading
  53260. * - href
  53261. * - hrefTarget
  53262. * - qtip
  53263. * - qtitle
  53264. */
  53265. Ext.define('Ext.data.NodeInterface', {
  53266. requires: ['Ext.data.Field', 'Ext.data.ModelManager'],
  53267. alternateClassName: 'Ext.data.Node',
  53268. /**
  53269. * @property nextSibling
  53270. * A reference to this node's next sibling node. `null` if this node does not have a next sibling.
  53271. */
  53272. /**
  53273. * @property previousSibling
  53274. * A reference to this node's previous sibling node. `null` if this node does not have a previous sibling.
  53275. */
  53276. /**
  53277. * @property parentNode
  53278. * A reference to this node's parent node. `null` if this node is the root node.
  53279. */
  53280. /**
  53281. * @property lastChild
  53282. * A reference to this node's last child node. `null` if this node has no children.
  53283. */
  53284. /**
  53285. * @property firstChild
  53286. * A reference to this node's first child node. `null` if this node has no children.
  53287. */
  53288. /**
  53289. * @property childNodes
  53290. * An array of this nodes children. Array will be empty if this node has no children.
  53291. */
  53292. statics: {
  53293. /**
  53294. * This method allows you to decorate a Record's prototype to implement the NodeInterface.
  53295. * This adds a set of methods, new events, new properties and new fields on every Record
  53296. * with the same Model as the passed Record.
  53297. * @param {Ext.data.Model} record The Record you want to decorate the prototype of.
  53298. * @static
  53299. */
  53300. decorate: function(record) {
  53301. if (!record.isNode) {
  53302. // Apply the methods and fields to the prototype
  53303. var mgr = Ext.data.ModelManager,
  53304. modelName = record.modelName,
  53305. modelClass = mgr.getModel(modelName),
  53306. newFields = [],
  53307. i, newField, len;
  53308. // Start by adding the NodeInterface methods to the Model's prototype
  53309. modelClass.override(this.getPrototypeBody());
  53310. newFields = this.applyFields(modelClass, [
  53311. {name: 'parentId', type: 'string', defaultValue: null},
  53312. {name: 'index', type: 'int', defaultValue: 0},
  53313. {name: 'depth', type: 'int', defaultValue: 0, persist: false},
  53314. {name: 'expanded', type: 'bool', defaultValue: false, persist: false},
  53315. {name: 'expandable', type: 'bool', defaultValue: true, persist: false},
  53316. {name: 'checked', type: 'auto', defaultValue: null},
  53317. {name: 'leaf', type: 'bool', defaultValue: false, persist: false},
  53318. {name: 'cls', type: 'string', defaultValue: null, persist: false},
  53319. {name: 'iconCls', type: 'string', defaultValue: null, persist: false},
  53320. {name: 'root', type: 'boolean', defaultValue: false, persist: false},
  53321. {name: 'isLast', type: 'boolean', defaultValue: false, persist: false},
  53322. {name: 'isFirst', type: 'boolean', defaultValue: false, persist: false},
  53323. {name: 'allowDrop', type: 'boolean', defaultValue: true, persist: false},
  53324. {name: 'allowDrag', type: 'boolean', defaultValue: true, persist: false},
  53325. {name: 'loaded', type: 'boolean', defaultValue: false, persist: false},
  53326. {name: 'loading', type: 'boolean', defaultValue: false, persist: false},
  53327. {name: 'href', type: 'string', defaultValue: null, persist: false},
  53328. {name: 'hrefTarget', type: 'string', defaultValue: null, persist: false},
  53329. {name: 'qtip', type: 'string', defaultValue: null, persist: false},
  53330. {name: 'qtitle', type: 'string', defaultValue: null, persist: false}
  53331. ]);
  53332. len = newFields.length;
  53333. // We set a dirty flag on the fields collection of the model. Any reader that
  53334. // will read in data for this model will update their extractor functions.
  53335. modelClass.getFields().isDirty = true;
  53336. // Set default values
  53337. for (i = 0; i < len; ++i) {
  53338. newField = newFields[i];
  53339. if (record.get(newField.getName()) === undefined) {
  53340. record.data[newField.getName()] = newField.getDefaultValue();
  53341. }
  53342. }
  53343. }
  53344. if (!record.isDecorated) {
  53345. record.isDecorated = true;
  53346. Ext.applyIf(record, {
  53347. firstChild: null,
  53348. lastChild: null,
  53349. parentNode: null,
  53350. previousSibling: null,
  53351. nextSibling: null,
  53352. childNodes: []
  53353. });
  53354. record.enableBubble([
  53355. /**
  53356. * @event append
  53357. * Fires when a new child node is appended.
  53358. * @param {Ext.data.NodeInterface} this This node.
  53359. * @param {Ext.data.NodeInterface} node The newly appended node.
  53360. * @param {Number} index The index of the newly appended node.
  53361. */
  53362. "append",
  53363. /**
  53364. * @event remove
  53365. * Fires when a child node is removed.
  53366. * @param {Ext.data.NodeInterface} this This node.
  53367. * @param {Ext.data.NodeInterface} node The removed node.
  53368. */
  53369. "remove",
  53370. /**
  53371. * @event move
  53372. * Fires when this node is moved to a new location in the tree.
  53373. * @param {Ext.data.NodeInterface} this This node.
  53374. * @param {Ext.data.NodeInterface} oldParent The old parent of this node.
  53375. * @param {Ext.data.NodeInterface} newParent The new parent of this node.
  53376. * @param {Number} index The index it was moved to.
  53377. */
  53378. "move",
  53379. /**
  53380. * @event insert
  53381. * Fires when a new child node is inserted.
  53382. * @param {Ext.data.NodeInterface} this This node.
  53383. * @param {Ext.data.NodeInterface} node The child node inserted.
  53384. * @param {Ext.data.NodeInterface} refNode The child node the node was inserted before.
  53385. */
  53386. "insert",
  53387. /**
  53388. * @event beforeappend
  53389. * Fires before a new child is appended, return `false` to cancel the append.
  53390. * @param {Ext.data.NodeInterface} this This node.
  53391. * @param {Ext.data.NodeInterface} node The child node to be appended.
  53392. */
  53393. "beforeappend",
  53394. /**
  53395. * @event beforeremove
  53396. * Fires before a child is removed, return `false` to cancel the remove.
  53397. * @param {Ext.data.NodeInterface} this This node.
  53398. * @param {Ext.data.NodeInterface} node The child node to be removed.
  53399. */
  53400. "beforeremove",
  53401. /**
  53402. * @event beforemove
  53403. * Fires before this node is moved to a new location in the tree. Return `false` to cancel the move.
  53404. * @param {Ext.data.NodeInterface} this This node.
  53405. * @param {Ext.data.NodeInterface} oldParent The parent of this node.
  53406. * @param {Ext.data.NodeInterface} newParent The new parent this node is moving to.
  53407. * @param {Number} index The index it is being moved to.
  53408. */
  53409. "beforemove",
  53410. /**
  53411. * @event beforeinsert
  53412. * Fires before a new child is inserted, return false to cancel the insert.
  53413. * @param {Ext.data.NodeInterface} this This node
  53414. * @param {Ext.data.NodeInterface} node The child node to be inserted
  53415. * @param {Ext.data.NodeInterface} refNode The child node the node is being inserted before
  53416. */
  53417. "beforeinsert",
  53418. /**
  53419. * @event expand
  53420. * Fires when this node is expanded.
  53421. * @param {Ext.data.NodeInterface} this The expanding node.
  53422. */
  53423. "expand",
  53424. /**
  53425. * @event collapse
  53426. * Fires when this node is collapsed.
  53427. * @param {Ext.data.NodeInterface} this The collapsing node.
  53428. */
  53429. "collapse",
  53430. /**
  53431. * @event beforeexpand
  53432. * Fires before this node is expanded.
  53433. * @param {Ext.data.NodeInterface} this The expanding node.
  53434. */
  53435. "beforeexpand",
  53436. /**
  53437. * @event beforecollapse
  53438. * Fires before this node is collapsed.
  53439. * @param {Ext.data.NodeInterface} this The collapsing node.
  53440. */
  53441. "beforecollapse",
  53442. /**
  53443. * @event sort
  53444. * Fires when this node's childNodes are sorted.
  53445. * @param {Ext.data.NodeInterface} this This node.
  53446. * @param {Ext.data.NodeInterface[]} childNodes The childNodes of this node.
  53447. */
  53448. "sort",
  53449. 'load'
  53450. ]);
  53451. }
  53452. return record;
  53453. },
  53454. applyFields: function(modelClass, addFields) {
  53455. var modelPrototype = modelClass.prototype,
  53456. fields = modelPrototype.fields,
  53457. keys = fields.keys,
  53458. ln = addFields.length,
  53459. addField, i,
  53460. newFields = [];
  53461. for (i = 0; i < ln; i++) {
  53462. addField = addFields[i];
  53463. if (!Ext.Array.contains(keys, addField.name)) {
  53464. addField = Ext.create('Ext.data.Field', addField);
  53465. newFields.push(addField);
  53466. fields.add(addField);
  53467. }
  53468. }
  53469. return newFields;
  53470. },
  53471. getPrototypeBody: function() {
  53472. return {
  53473. isNode: true,
  53474. /**
  53475. * Ensures that the passed object is an instance of a Record with the NodeInterface applied
  53476. * @return {Boolean}
  53477. * @private
  53478. */
  53479. createNode: function(node) {
  53480. if (Ext.isObject(node) && !node.isModel) {
  53481. node = Ext.data.ModelManager.create(node, this.modelName);
  53482. }
  53483. // Make sure the node implements the node interface
  53484. return Ext.data.NodeInterface.decorate(node);
  53485. },
  53486. /**
  53487. * Returns true if this node is a leaf
  53488. * @return {Boolean}
  53489. */
  53490. isLeaf : function() {
  53491. return this.get('leaf') === true;
  53492. },
  53493. /**
  53494. * Sets the first child of this node
  53495. * @private
  53496. * @param {Ext.data.NodeInterface} node
  53497. */
  53498. setFirstChild : function(node) {
  53499. this.firstChild = node;
  53500. },
  53501. /**
  53502. * Sets the last child of this node
  53503. * @private
  53504. * @param {Ext.data.NodeInterface} node
  53505. */
  53506. setLastChild : function(node) {
  53507. this.lastChild = node;
  53508. },
  53509. /**
  53510. * Updates general data of this node like isFirst, isLast, depth. This
  53511. * method is internally called after a node is moved. This shouldn't
  53512. * have to be called by the developer unless they are creating custom
  53513. * Tree plugins.
  53514. * @return {Boolean}
  53515. */
  53516. updateInfo: function(silent) {
  53517. var me = this,
  53518. parentNode = me.parentNode,
  53519. isFirst = (!parentNode ? true : parentNode.firstChild == me),
  53520. isLast = (!parentNode ? true : parentNode.lastChild == me),
  53521. depth = 0,
  53522. parent = me,
  53523. children = me.childNodes,
  53524. ln = children.length,
  53525. i;
  53526. while (parent.parentNode) {
  53527. ++depth;
  53528. parent = parent.parentNode;
  53529. }
  53530. me.beginEdit();
  53531. me.set({
  53532. isFirst: isFirst,
  53533. isLast: isLast,
  53534. depth: depth,
  53535. index: parentNode ? parentNode.indexOf(me) : 0,
  53536. parentId: parentNode ? parentNode.getId() : null
  53537. });
  53538. me.endEdit(silent);
  53539. if (silent) {
  53540. me.commit(silent);
  53541. }
  53542. for (i = 0; i < ln; i++) {
  53543. children[i].updateInfo(silent);
  53544. }
  53545. },
  53546. /**
  53547. * Returns `true` if this node is the last child of its parent.
  53548. * @return {Boolean}
  53549. */
  53550. isLast : function() {
  53551. return this.get('isLast');
  53552. },
  53553. /**
  53554. * Returns `true` if this node is the first child of its parent.
  53555. * @return {Boolean}
  53556. */
  53557. isFirst : function() {
  53558. return this.get('isFirst');
  53559. },
  53560. /**
  53561. * Returns `true` if this node has one or more child nodes, else `false`.
  53562. * @return {Boolean}
  53563. */
  53564. hasChildNodes : function() {
  53565. return !this.isLeaf() && this.childNodes.length > 0;
  53566. },
  53567. /**
  53568. * Returns `true` if this node has one or more child nodes, or if the `expandable`
  53569. * node attribute is explicitly specified as `true`, otherwise returns `false`.
  53570. * @return {Boolean}
  53571. */
  53572. isExpandable : function() {
  53573. var me = this;
  53574. if (me.get('expandable')) {
  53575. return !(me.isLeaf() || (me.isLoaded() && !me.hasChildNodes()));
  53576. }
  53577. return false;
  53578. },
  53579. /**
  53580. * Insert node(s) as the last child node of this node.
  53581. *
  53582. * If the node was previously a child node of another parent node, it will be removed from that node first.
  53583. *
  53584. * @param {Ext.data.NodeInterface/Ext.data.NodeInterface[]} node The node or Array of nodes to append.
  53585. * @return {Ext.data.NodeInterface} The appended node if single append, or `null` if an array was passed.
  53586. */
  53587. appendChild : function(node, suppressEvents, suppressNodeUpdate) {
  53588. var me = this,
  53589. i, ln,
  53590. index,
  53591. oldParent,
  53592. ps;
  53593. // if passed an array or multiple args do them one by one
  53594. if (Ext.isArray(node)) {
  53595. for (i = 0, ln = node.length; i < ln; i++) {
  53596. me.appendChild(node[i]);
  53597. }
  53598. } else {
  53599. // Make sure it is a record
  53600. node = me.createNode(node);
  53601. if (suppressEvents !== true && me.fireEvent("beforeappend", me, node) === false) {
  53602. return false;
  53603. }
  53604. index = me.childNodes.length;
  53605. oldParent = node.parentNode;
  53606. // it's a move, make sure we move it cleanly
  53607. if (oldParent) {
  53608. if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index) === false) {
  53609. return false;
  53610. }
  53611. oldParent.removeChild(node, null, false, true);
  53612. }
  53613. index = me.childNodes.length;
  53614. if (index === 0) {
  53615. me.setFirstChild(node);
  53616. }
  53617. me.childNodes.push(node);
  53618. node.parentNode = me;
  53619. node.nextSibling = null;
  53620. me.setLastChild(node);
  53621. ps = me.childNodes[index - 1];
  53622. if (ps) {
  53623. node.previousSibling = ps;
  53624. ps.nextSibling = node;
  53625. ps.updateInfo(suppressNodeUpdate);
  53626. } else {
  53627. node.previousSibling = null;
  53628. }
  53629. node.updateInfo(suppressNodeUpdate);
  53630. // As soon as we append a child to this node, we are loaded
  53631. if (!me.isLoaded()) {
  53632. me.set('loaded', true);
  53633. }
  53634. // If this node didn't have any childnodes before, update myself
  53635. else if (me.childNodes.length === 1) {
  53636. me.set('loaded', me.isLoaded());
  53637. }
  53638. if (suppressEvents !== true) {
  53639. me.fireEvent("append", me, node, index);
  53640. if (oldParent) {
  53641. node.fireEvent("move", node, oldParent, me, index);
  53642. }
  53643. }
  53644. return node;
  53645. }
  53646. },
  53647. /**
  53648. * Returns the bubble target for this node.
  53649. * @private
  53650. * @return {Object} The bubble target.
  53651. */
  53652. getBubbleTarget: function() {
  53653. return this.parentNode;
  53654. },
  53655. /**
  53656. * Removes a child node from this node.
  53657. * @param {Ext.data.NodeInterface} node The node to remove.
  53658. * @param {Boolean} [destroy=false] `true` to destroy the node upon removal.
  53659. * @return {Ext.data.NodeInterface} The removed node.
  53660. */
  53661. removeChild : function(node, destroy, suppressEvents, suppressNodeUpdate) {
  53662. var me = this,
  53663. index = me.indexOf(node);
  53664. if (index == -1 || (suppressEvents !== true && me.fireEvent("beforeremove", me, node) === false)) {
  53665. return false;
  53666. }
  53667. // remove it from childNodes collection
  53668. Ext.Array.erase(me.childNodes, index, 1);
  53669. // update child refs
  53670. if (me.firstChild == node) {
  53671. me.setFirstChild(node.nextSibling);
  53672. }
  53673. if (me.lastChild == node) {
  53674. me.setLastChild(node.previousSibling);
  53675. }
  53676. if (suppressEvents !== true) {
  53677. me.fireEvent("remove", me, node);
  53678. }
  53679. // update siblings
  53680. if (node.previousSibling) {
  53681. node.previousSibling.nextSibling = node.nextSibling;
  53682. node.previousSibling.updateInfo(suppressNodeUpdate);
  53683. }
  53684. if (node.nextSibling) {
  53685. node.nextSibling.previousSibling = node.previousSibling;
  53686. node.nextSibling.updateInfo(suppressNodeUpdate);
  53687. }
  53688. // If this node suddenly doesn't have childnodes anymore, update myself
  53689. if (!me.childNodes.length) {
  53690. me.set('loaded', me.isLoaded());
  53691. }
  53692. if (destroy) {
  53693. node.destroy(true);
  53694. } else {
  53695. node.clear();
  53696. }
  53697. return node;
  53698. },
  53699. /**
  53700. * Creates a copy (clone) of this Node.
  53701. * @param {String} id (optional) A new id, defaults to this Node's id.
  53702. * @param {Boolean} deep (optional) If passed as `true`, all child Nodes are recursively copied into the new Node.
  53703. * If omitted or `false`, the copy will have no child Nodes.
  53704. * @return {Ext.data.NodeInterface} A copy of this Node.
  53705. */
  53706. copy: function(newId, deep) {
  53707. var me = this,
  53708. result = me.callOverridden(arguments),
  53709. len = me.childNodes ? me.childNodes.length : 0,
  53710. i;
  53711. // Move child nodes across to the copy if required
  53712. if (deep) {
  53713. for (i = 0; i < len; i++) {
  53714. result.appendChild(me.childNodes[i].copy(true));
  53715. }
  53716. }
  53717. return result;
  53718. },
  53719. /**
  53720. * Clear the node.
  53721. * @private
  53722. * @param {Boolean} destroy `true` to destroy the node.
  53723. */
  53724. clear : function(destroy) {
  53725. var me = this;
  53726. // clear any references from the node
  53727. me.parentNode = me.previousSibling = me.nextSibling = null;
  53728. if (destroy) {
  53729. me.firstChild = me.lastChild = null;
  53730. }
  53731. },
  53732. /**
  53733. * Destroys the node.
  53734. */
  53735. destroy : function(silent) {
  53736. /*
  53737. * Silent is to be used in a number of cases
  53738. * 1) When setRoot is called.
  53739. * 2) When destroy on the tree is called
  53740. * 3) For destroying child nodes on a node
  53741. */
  53742. var me = this,
  53743. options = me.destroyOptions;
  53744. if (silent === true) {
  53745. me.clear(true);
  53746. Ext.each(me.childNodes, function(n) {
  53747. n.destroy(true);
  53748. });
  53749. me.childNodes = null;
  53750. delete me.destroyOptions;
  53751. me.callOverridden([options]);
  53752. } else {
  53753. me.destroyOptions = silent;
  53754. // overridden method will be called, since remove will end up calling destroy(true);
  53755. me.remove(true);
  53756. }
  53757. },
  53758. /**
  53759. * Inserts the first node before the second node in this nodes `childNodes` collection.
  53760. * @param {Ext.data.NodeInterface} node The node to insert.
  53761. * @param {Ext.data.NodeInterface} refNode The node to insert before (if `null` the node is appended).
  53762. * @return {Ext.data.NodeInterface} The inserted node.
  53763. */
  53764. insertBefore : function(node, refNode, suppressEvents) {
  53765. var me = this,
  53766. index = me.indexOf(refNode),
  53767. oldParent = node.parentNode,
  53768. refIndex = index,
  53769. ps;
  53770. if (!refNode) { // like standard Dom, refNode can be null for append
  53771. return me.appendChild(node);
  53772. }
  53773. // nothing to do
  53774. if (node == refNode) {
  53775. return false;
  53776. }
  53777. // Make sure it is a record with the NodeInterface
  53778. node = me.createNode(node);
  53779. if (suppressEvents !== true && me.fireEvent("beforeinsert", me, node, refNode) === false) {
  53780. return false;
  53781. }
  53782. // when moving internally, indexes will change after remove
  53783. if (oldParent == me && me.indexOf(node) < index) {
  53784. refIndex--;
  53785. }
  53786. // it's a move, make sure we move it cleanly
  53787. if (oldParent) {
  53788. if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index, refNode) === false) {
  53789. return false;
  53790. }
  53791. oldParent.removeChild(node);
  53792. }
  53793. if (refIndex === 0) {
  53794. me.setFirstChild(node);
  53795. }
  53796. Ext.Array.splice(me.childNodes, refIndex, 0, node);
  53797. node.parentNode = me;
  53798. node.nextSibling = refNode;
  53799. refNode.previousSibling = node;
  53800. ps = me.childNodes[refIndex - 1];
  53801. if (ps) {
  53802. node.previousSibling = ps;
  53803. ps.nextSibling = node;
  53804. ps.updateInfo();
  53805. } else {
  53806. node.previousSibling = null;
  53807. }
  53808. node.updateInfo();
  53809. if (!me.isLoaded()) {
  53810. me.set('loaded', true);
  53811. }
  53812. // If this node didn't have any childnodes before, update myself
  53813. else if (me.childNodes.length === 1) {
  53814. me.set('loaded', me.isLoaded());
  53815. }
  53816. if (suppressEvents !== true) {
  53817. me.fireEvent("insert", me, node, refNode);
  53818. if (oldParent) {
  53819. node.fireEvent("move", node, oldParent, me, refIndex, refNode);
  53820. }
  53821. }
  53822. return node;
  53823. },
  53824. /**
  53825. * Insert a node into this node.
  53826. * @param {Number} index The zero-based index to insert the node at.
  53827. * @param {Ext.data.Model} node The node to insert.
  53828. * @return {Ext.data.Model} The record you just inserted.
  53829. */
  53830. insertChild: function(index, node) {
  53831. var sibling = this.childNodes[index];
  53832. if (sibling) {
  53833. return this.insertBefore(node, sibling);
  53834. }
  53835. else {
  53836. return this.appendChild(node);
  53837. }
  53838. },
  53839. /**
  53840. * Removes this node from its parent.
  53841. * @param {Boolean} [destroy=false] `true` to destroy the node upon removal.
  53842. * @return {Ext.data.NodeInterface} this
  53843. */
  53844. remove : function(destroy, suppressEvents) {
  53845. var parentNode = this.parentNode;
  53846. if (parentNode) {
  53847. parentNode.removeChild(this, destroy, suppressEvents, true);
  53848. }
  53849. return this;
  53850. },
  53851. /**
  53852. * Removes all child nodes from this node.
  53853. * @param {Boolean} [destroy=false] `true` to destroy the node upon removal.
  53854. * @return {Ext.data.NodeInterface} this
  53855. */
  53856. removeAll : function(destroy, suppressEvents) {
  53857. var cn = this.childNodes,
  53858. n;
  53859. while ((n = cn[0])) {
  53860. this.removeChild(n, destroy, suppressEvents);
  53861. }
  53862. return this;
  53863. },
  53864. /**
  53865. * Returns the child node at the specified index.
  53866. * @param {Number} index
  53867. * @return {Ext.data.NodeInterface}
  53868. */
  53869. getChildAt : function(index) {
  53870. return this.childNodes[index];
  53871. },
  53872. /**
  53873. * Replaces one child node in this node with another.
  53874. * @param {Ext.data.NodeInterface} newChild The replacement node.
  53875. * @param {Ext.data.NodeInterface} oldChild The node to replace.
  53876. * @return {Ext.data.NodeInterface} The replaced node.
  53877. */
  53878. replaceChild : function(newChild, oldChild, suppressEvents) {
  53879. var s = oldChild ? oldChild.nextSibling : null;
  53880. this.removeChild(oldChild, suppressEvents);
  53881. this.insertBefore(newChild, s, suppressEvents);
  53882. return oldChild;
  53883. },
  53884. /**
  53885. * Returns the index of a child node.
  53886. * @param {Ext.data.NodeInterface} node
  53887. * @return {Number} The index of the node or -1 if it was not found.
  53888. */
  53889. indexOf : function(child) {
  53890. return Ext.Array.indexOf(this.childNodes, child);
  53891. },
  53892. /**
  53893. * Gets the hierarchical path from the root of the current node.
  53894. * @param {String} field (optional) The field to construct the path from. Defaults to the model `idProperty`.
  53895. * @param {String} [separator=/] (optional) A separator to use.
  53896. * @return {String} The node path
  53897. */
  53898. getPath: function(field, separator) {
  53899. field = field || this.idProperty;
  53900. separator = separator || '/';
  53901. var path = [this.get(field)],
  53902. parent = this.parentNode;
  53903. while (parent) {
  53904. path.unshift(parent.get(field));
  53905. parent = parent.parentNode;
  53906. }
  53907. return separator + path.join(separator);
  53908. },
  53909. /**
  53910. * Returns depth of this node (the root node has a depth of 0).
  53911. * @return {Number}
  53912. */
  53913. getDepth : function() {
  53914. return this.get('depth');
  53915. },
  53916. /**
  53917. * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
  53918. * will be the args provided or the current node. If the function returns `false` at any point,
  53919. * the bubble is stopped.
  53920. * @param {Function} fn The function to call.
  53921. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to the current Node.
  53922. * @param {Array} args (optional) The args to call the function with (default to passing the current Node).
  53923. */
  53924. bubble : function(fn, scope, args) {
  53925. var p = this;
  53926. while (p) {
  53927. if (fn.apply(scope || p, args || [p]) === false) {
  53928. break;
  53929. }
  53930. p = p.parentNode;
  53931. }
  53932. },
  53933. /**
  53934. * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
  53935. * will be the args provided or the current node. If the function returns false at any point,
  53936. * the cascade is stopped on that branch.
  53937. * @param {Function} fn The function to call
  53938. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to the current Node.
  53939. * @param {Array} args (optional) The args to call the function with (default to passing the current Node).
  53940. */
  53941. cascadeBy : function(fn, scope, args) {
  53942. if (fn.apply(scope || this, args || [this]) !== false) {
  53943. var childNodes = this.childNodes,
  53944. length = childNodes.length,
  53945. i;
  53946. for (i = 0; i < length; i++) {
  53947. childNodes[i].cascadeBy(fn, scope, args);
  53948. }
  53949. }
  53950. },
  53951. /**
  53952. * Iterates the child nodes of this node, calling the specified function with each node. The arguments to the function
  53953. * will be the args provided or the current node. If the function returns false at any point,
  53954. * the iteration stops.
  53955. * @param {Function} fn The function to call.
  53956. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to the current Node in the iteration.
  53957. * @param {Array} args (optional) The args to call the function with (default to passing the current Node).
  53958. */
  53959. eachChild : function(fn, scope, args) {
  53960. var childNodes = this.childNodes,
  53961. length = childNodes.length,
  53962. i;
  53963. for (i = 0; i < length; i++) {
  53964. if (fn.apply(scope || this, args || [childNodes[i]]) === false) {
  53965. break;
  53966. }
  53967. }
  53968. },
  53969. /**
  53970. * Finds the first child that has the attribute with the specified value.
  53971. * @param {String} attribute The attribute name.
  53972. * @param {Object} value The value to search for.
  53973. * @param {Boolean} deep (Optional) `true` to search through nodes deeper than the immediate children.
  53974. * @return {Ext.data.NodeInterface} The found child or `null` if none was found.
  53975. */
  53976. findChild : function(attribute, value, deep) {
  53977. return this.findChildBy(function() {
  53978. return this.get(attribute) == value;
  53979. }, null, deep);
  53980. },
  53981. /**
  53982. * Finds the first child by a custom function. The child matches if the function passed returns `true`.
  53983. * @param {Function} fn A function which must return `true` if the passed Node is the required Node.
  53984. * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to the Node being tested.
  53985. * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children.
  53986. * @return {Ext.data.NodeInterface} The found child or null if `none` was found.
  53987. */
  53988. findChildBy : function(fn, scope, deep) {
  53989. var cs = this.childNodes,
  53990. len = cs.length,
  53991. i = 0, n, res;
  53992. for (; i < len; i++) {
  53993. n = cs[i];
  53994. if (fn.call(scope || n, n) === true) {
  53995. return n;
  53996. }
  53997. else if (deep) {
  53998. res = n.findChildBy(fn, scope, deep);
  53999. if (res !== null) {
  54000. return res;
  54001. }
  54002. }
  54003. }
  54004. return null;
  54005. },
  54006. /**
  54007. * Returns `true` if this node is an ancestor (at any point) of the passed node.
  54008. * @param {Ext.data.NodeInterface} node
  54009. * @return {Boolean}
  54010. */
  54011. contains : function(node) {
  54012. return node.isAncestor(this);
  54013. },
  54014. /**
  54015. * Returns `true` if the passed node is an ancestor (at any point) of this node.
  54016. * @param {Ext.data.NodeInterface} node
  54017. * @return {Boolean}
  54018. */
  54019. isAncestor : function(node) {
  54020. var p = this.parentNode;
  54021. while (p) {
  54022. if (p == node) {
  54023. return true;
  54024. }
  54025. p = p.parentNode;
  54026. }
  54027. return false;
  54028. },
  54029. /**
  54030. * Sorts this nodes children using the supplied sort function.
  54031. * @param {Function} sortFn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
  54032. * @param {Boolean} recursive Whether or not to apply this sort recursively.
  54033. * @param {Boolean} suppressEvent Set to true to not fire a sort event.
  54034. */
  54035. sort: function(sortFn, recursive, suppressEvent) {
  54036. var cs = this.childNodes,
  54037. ln = cs.length,
  54038. i, n;
  54039. if (ln > 0) {
  54040. Ext.Array.sort(cs, sortFn);
  54041. for (i = 0; i < ln; i++) {
  54042. n = cs[i];
  54043. n.previousSibling = cs[i-1];
  54044. n.nextSibling = cs[i+1];
  54045. if (i === 0) {
  54046. this.setFirstChild(n);
  54047. }
  54048. if (i == ln - 1) {
  54049. this.setLastChild(n);
  54050. }
  54051. n.updateInfo(suppressEvent);
  54052. if (recursive && !n.isLeaf()) {
  54053. n.sort(sortFn, true, true);
  54054. }
  54055. }
  54056. this.notifyStores('afterEdit', ['sorted'], {sorted: 'sorted'});
  54057. if (suppressEvent !== true) {
  54058. this.fireEvent('sort', this, cs);
  54059. }
  54060. }
  54061. },
  54062. /**
  54063. * Returns `true` if this node is expanded.
  54064. * @return {Boolean}
  54065. */
  54066. isExpanded: function() {
  54067. return this.get('expanded');
  54068. },
  54069. /**
  54070. * Returns `true` if this node is loaded.
  54071. * @return {Boolean}
  54072. */
  54073. isLoaded: function() {
  54074. return this.get('loaded');
  54075. },
  54076. /**
  54077. * Returns `true` if this node is loading.
  54078. * @return {Boolean}
  54079. */
  54080. isLoading: function() {
  54081. return this.get('loading');
  54082. },
  54083. /**
  54084. * Returns `true` if this node is the root node.
  54085. * @return {Boolean}
  54086. */
  54087. isRoot: function() {
  54088. return !this.parentNode;
  54089. },
  54090. /**
  54091. * Returns `true` if this node is visible.
  54092. * @return {Boolean}
  54093. */
  54094. isVisible: function() {
  54095. var parent = this.parentNode;
  54096. while (parent) {
  54097. if (!parent.isExpanded()) {
  54098. return false;
  54099. }
  54100. parent = parent.parentNode;
  54101. }
  54102. return true;
  54103. },
  54104. /**
  54105. * Expand this node.
  54106. * @param {Function} recursive (Optional) `true` to recursively expand all the children.
  54107. * @param {Function} callback (Optional) The function to execute once the expand completes.
  54108. * @param {Object} scope (Optional) The scope to run the callback in.
  54109. */
  54110. expand: function(recursive, callback, scope) {
  54111. var me = this;
  54112. if (!me.isLeaf()) {
  54113. if (me.isLoading()) {
  54114. me.on('expand', function() {
  54115. me.expand(recursive, callback, scope);
  54116. }, me, {single: true});
  54117. }
  54118. else {
  54119. if (!me.isExpanded()) {
  54120. // The TreeStore actually listens for the beforeexpand method and checks
  54121. // whether we have to asynchronously load the children from the server
  54122. // first. Thats why we pass a callback function to the event that the
  54123. // store can call once it has loaded and parsed all the children.
  54124. me.fireAction('expand', [this], function() {
  54125. me.set('expanded', true);
  54126. Ext.callback(callback, scope || me, [me.childNodes]);
  54127. });
  54128. }
  54129. else {
  54130. Ext.callback(callback, scope || me, [me.childNodes]);
  54131. }
  54132. }
  54133. } else {
  54134. Ext.callback(callback, scope || me);
  54135. }
  54136. },
  54137. /**
  54138. * Collapse this node.
  54139. * @param {Function} recursive (Optional) `true` to recursively collapse all the children.
  54140. * @param {Function} callback (Optional) The function to execute once the collapse completes.
  54141. * @param {Object} scope (Optional) The scope to run the callback in.
  54142. */
  54143. collapse: function(recursive, callback, scope) {
  54144. var me = this;
  54145. // First we start by checking if this node is a parent
  54146. if (!me.isLeaf() && me.isExpanded()) {
  54147. this.fireAction('collapse', [me], function() {
  54148. me.set('expanded', false);
  54149. Ext.callback(callback, scope || me, [me.childNodes]);
  54150. });
  54151. } else {
  54152. Ext.callback(callback, scope || me, [me.childNodes]);
  54153. }
  54154. }
  54155. };
  54156. }
  54157. }
  54158. });
  54159. /**
  54160. * @private
  54161. */
  54162. Ext.define('Ext.data.NodeStore', {
  54163. extend: 'Ext.data.Store',
  54164. alias: 'store.node',
  54165. requires: ['Ext.data.NodeInterface'],
  54166. config: {
  54167. /**
  54168. * @cfg {Ext.data.Model} node The Record you want to bind this Store to. Note that
  54169. * this record will be decorated with the {@link Ext.data.NodeInterface} if this is not the
  54170. * case yet.
  54171. * @accessor
  54172. */
  54173. node: null,
  54174. /**
  54175. * @cfg {Boolean} recursive Set this to `true` if you want this NodeStore to represent
  54176. * all the descendants of the node in its flat data collection. This is useful for
  54177. * rendering a tree structure to a DataView and is being used internally by
  54178. * the TreeView. Any records that are moved, removed, inserted or appended to the
  54179. * node at any depth below the node this store is bound to will be automatically
  54180. * updated in this Store's internal flat data structure.
  54181. * @accessor
  54182. */
  54183. recursive: false,
  54184. /**
  54185. * @cfg {Boolean} rootVisible `false` to not include the root node in this Stores collection.
  54186. * @accessor
  54187. */
  54188. rootVisible: false,
  54189. sorters: undefined,
  54190. filters: undefined,
  54191. /**
  54192. * @cfg {Boolean} folderSort
  54193. * Set to `true` to automatically prepend a leaf sorter.
  54194. */
  54195. folderSort: false
  54196. },
  54197. afterEdit: function(record, modifiedFields) {
  54198. if (modifiedFields) {
  54199. if (modifiedFields.indexOf('loaded') !== -1) {
  54200. return this.add(this.retrieveChildNodes(record));
  54201. }
  54202. if (modifiedFields.indexOf('expanded') !== -1) {
  54203. return this.filter();
  54204. }
  54205. if (modifiedFields.indexOf('sorted') !== -1) {
  54206. return this.sort();
  54207. }
  54208. }
  54209. this.callParent(arguments);
  54210. },
  54211. onNodeAppend: function(parent, node) {
  54212. this.add([node].concat(this.retrieveChildNodes(node)));
  54213. },
  54214. onNodeInsert: function(parent, node) {
  54215. this.add([node].concat(this.retrieveChildNodes(node)));
  54216. },
  54217. onNodeRemove: function(parent, node) {
  54218. this.remove([node].concat(this.retrieveChildNodes(node)));
  54219. },
  54220. onNodeSort: function() {
  54221. this.sort();
  54222. },
  54223. updateFolderSort: function(folderSort) {
  54224. if (folderSort) {
  54225. this.setGrouper(function(node) {
  54226. if (node.isLeaf()) {
  54227. return 1;
  54228. }
  54229. return 0;
  54230. });
  54231. } else {
  54232. this.setGrouper(null);
  54233. }
  54234. },
  54235. createDataCollection: function() {
  54236. var collection = this.callParent();
  54237. collection.handleSort = Ext.Function.bind(this.handleTreeSort, this, [collection], true);
  54238. collection.findInsertionIndex = Ext.Function.bind(this.handleTreeInsertionIndex, this, [collection, collection.findInsertionIndex], true);
  54239. return collection;
  54240. },
  54241. handleTreeInsertionIndex: function(items, item, collection, originalFn) {
  54242. return originalFn.call(collection, items, item, this.treeSortFn);
  54243. },
  54244. handleTreeSort: function(data) {
  54245. Ext.Array.sort(data, this.treeSortFn);
  54246. return data;
  54247. },
  54248. /**
  54249. * This is a custom tree sorting algorithm. It uses the index property on each node to determine
  54250. * how to sort siblings. It uses the depth property plus the index to create a weight for each node.
  54251. * This weight algorithm has the limitation of not being able to go more then 80 levels in depth, or
  54252. * more then 10k nodes per parent. The end result is a flat collection being correctly sorted based
  54253. * on this one single sort function.
  54254. * @param node1
  54255. * @param node2
  54256. * @return {Number}
  54257. * @private
  54258. */
  54259. treeSortFn: function(node1, node2) {
  54260. // A shortcut for siblings
  54261. if (node1.parentNode === node2.parentNode) {
  54262. return (node1.data.index < node2.data.index) ? -1 : 1;
  54263. }
  54264. // @NOTE: with the following algorithm we can only go 80 levels deep in the tree
  54265. // and each node can contain 10000 direct children max
  54266. var weight1 = 0,
  54267. weight2 = 0,
  54268. parent1 = node1,
  54269. parent2 = node2;
  54270. while (parent1) {
  54271. weight1 += (Math.pow(10, (parent1.data.depth+1) * -4) * (parent1.data.index+1));
  54272. parent1 = parent1.parentNode;
  54273. }
  54274. while (parent2) {
  54275. weight2 += (Math.pow(10, (parent2.data.depth+1) * -4) * (parent2.data.index+1));
  54276. parent2 = parent2.parentNode;
  54277. }
  54278. if (weight1 > weight2) {
  54279. return 1;
  54280. } else if (weight1 < weight2) {
  54281. return -1;
  54282. }
  54283. return (node1.data.index > node2.data.index) ? 1 : -1;
  54284. },
  54285. applyFilters: function(filters) {
  54286. var me = this;
  54287. return function(item) {
  54288. return me.isVisible(item);
  54289. };
  54290. },
  54291. applyProxy: function(proxy) {
  54292. //<debug>
  54293. if (proxy) {
  54294. Ext.Logger.warn("A NodeStore cannot be bound to a proxy. Instead bind it to a record " +
  54295. "decorated with the NodeInterface by setting the node config.");
  54296. }
  54297. //</debug>
  54298. },
  54299. applyNode: function(node) {
  54300. if (node) {
  54301. node = Ext.data.NodeInterface.decorate(node);
  54302. }
  54303. return node;
  54304. },
  54305. updateNode: function(node, oldNode) {
  54306. if (oldNode && !oldNode.isDestroyed) {
  54307. oldNode.un({
  54308. append : 'onNodeAppend',
  54309. insert : 'onNodeInsert',
  54310. remove : 'onNodeRemove',
  54311. load : 'onNodeLoad',
  54312. scope: this
  54313. });
  54314. oldNode.unjoin(this);
  54315. }
  54316. if (node) {
  54317. node.on({
  54318. scope : this,
  54319. append : 'onNodeAppend',
  54320. insert : 'onNodeInsert',
  54321. remove : 'onNodeRemove',
  54322. load : 'onNodeLoad'
  54323. });
  54324. node.join(this);
  54325. var data = [];
  54326. if (node.childNodes.length) {
  54327. data = data.concat(this.retrieveChildNodes(node));
  54328. }
  54329. if (this.getRootVisible()) {
  54330. data.push(node);
  54331. } else if (node.isLoaded() || node.isLoading()) {
  54332. node.set('expanded', true);
  54333. }
  54334. this.data.clear();
  54335. this.fireEvent('clear', this);
  54336. this.suspendEvents();
  54337. this.add(data);
  54338. this.resumeEvents();
  54339. this.fireEvent('refresh', this, this.data);
  54340. }
  54341. },
  54342. /**
  54343. * Private method used to deeply retrieve the children of a record without recursion.
  54344. * @private
  54345. * @param root
  54346. * @return {Array}
  54347. */
  54348. retrieveChildNodes: function(root) {
  54349. var node = this.getNode(),
  54350. recursive = this.getRecursive(),
  54351. added = [],
  54352. child = root;
  54353. if (!root.childNodes.length || (!recursive && root !== node)) {
  54354. return added;
  54355. }
  54356. if (!recursive) {
  54357. return root.childNodes;
  54358. }
  54359. while (child) {
  54360. if (child._added) {
  54361. delete child._added;
  54362. if (child === root) {
  54363. break;
  54364. } else {
  54365. child = child.nextSibling || child.parentNode;
  54366. }
  54367. } else {
  54368. if (child !== root) {
  54369. added.push(child);
  54370. }
  54371. if (child.firstChild) {
  54372. child._added = true;
  54373. child = child.firstChild;
  54374. } else {
  54375. child = child.nextSibling || child.parentNode;
  54376. }
  54377. }
  54378. }
  54379. return added;
  54380. },
  54381. /**
  54382. * @param {Object} node
  54383. * @return {Boolean}
  54384. */
  54385. isVisible: function(node) {
  54386. var parent = node.parentNode;
  54387. if (!this.getRecursive() && parent !== this.getNode()) {
  54388. return false;
  54389. }
  54390. while (parent) {
  54391. if (!parent.isExpanded()) {
  54392. return false;
  54393. }
  54394. //we need to check this because for a nodestore the node is not likely to be the root
  54395. //so we stop going up the chain when we hit the original node as we don't care about any
  54396. //ancestors above the configured node
  54397. if (parent === this.getNode()) {
  54398. break;
  54399. }
  54400. parent = parent.parentNode;
  54401. }
  54402. return true;
  54403. }
  54404. });
  54405. /**
  54406. * @aside guide stores
  54407. *
  54408. * The TreeStore is a store implementation that allows for nested data.
  54409. *
  54410. * It provides convenience methods for loading nodes, as well as the ability to use
  54411. * the hierarchical tree structure combined with a store. This class also relays many events from
  54412. * the Tree for convenience.
  54413. *
  54414. * # Using Models
  54415. *
  54416. * If no Model is specified, an implicit model will be created that implements {@link Ext.data.NodeInterface}.
  54417. * The standard Tree fields will also be copied onto the Model for maintaining their state. These fields are listed
  54418. * in the {@link Ext.data.NodeInterface} documentation.
  54419. *
  54420. * # Reading Nested Data
  54421. *
  54422. * For the tree to read nested data, the {@link Ext.data.reader.Reader} must be configured with a root property,
  54423. * so the reader can find nested data for each node. If a root is not specified, it will default to
  54424. * 'children'.
  54425. */
  54426. Ext.define('Ext.data.TreeStore', {
  54427. extend: 'Ext.data.NodeStore',
  54428. alias: 'store.tree',
  54429. config: {
  54430. /**
  54431. * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
  54432. * The root node for this store. For example:
  54433. *
  54434. * root: {
  54435. * expanded: true,
  54436. * text: "My Root",
  54437. * children: [
  54438. * { text: "Child 1", leaf: true },
  54439. * { text: "Child 2", expanded: true, children: [
  54440. * { text: "GrandChild", leaf: true }
  54441. * ] }
  54442. * ]
  54443. * }
  54444. *
  54445. * Setting the `root` config option is the same as calling {@link #setRootNode}.
  54446. * @accessor
  54447. */
  54448. root: undefined,
  54449. /**
  54450. * @cfg {Boolean} clearOnLoad
  54451. * Remove previously existing child nodes before loading. Default to true.
  54452. * @accessor
  54453. */
  54454. clearOnLoad : true,
  54455. /**
  54456. * @cfg {String} nodeParam
  54457. * The name of the parameter sent to the server which contains the identifier of the node.
  54458. * Defaults to 'node'.
  54459. * @accessor
  54460. */
  54461. nodeParam: 'node',
  54462. /**
  54463. * @cfg {String} defaultRootId
  54464. * The default root id. Defaults to 'root'
  54465. * @accessor
  54466. */
  54467. defaultRootId: 'root',
  54468. /**
  54469. * @cfg {String} defaultRootProperty
  54470. * The root property to specify on the reader if one is not explicitly defined.
  54471. * @accessor
  54472. */
  54473. defaultRootProperty: 'children',
  54474. /**
  54475. * @cfg {Boolean} recursive
  54476. * @private
  54477. * @hide
  54478. */
  54479. recursive: true
  54480. /**
  54481. * @cfg {Object} node
  54482. * @private
  54483. * @hide
  54484. */
  54485. },
  54486. applyProxy: function() {
  54487. return Ext.data.Store.prototype.applyProxy.apply(this, arguments);
  54488. },
  54489. applyRoot: function(root) {
  54490. var me = this;
  54491. root = root || {};
  54492. root = Ext.apply({}, root);
  54493. if (!root.isModel) {
  54494. Ext.applyIf(root, {
  54495. id: me.getStoreId() + '-' + me.getDefaultRootId(),
  54496. text: 'Root',
  54497. allowDrag: false
  54498. });
  54499. root = Ext.data.ModelManager.create(root, me.getModel());
  54500. }
  54501. Ext.data.NodeInterface.decorate(root);
  54502. root.set(root.raw);
  54503. return root;
  54504. },
  54505. handleTreeInsertionIndex: function(items, item, collection, originalFn) {
  54506. if (item.parentNode) {
  54507. item.parentNode.sort(collection.getSortFn(), true, true);
  54508. }
  54509. return this.callParent(arguments);
  54510. },
  54511. handleTreeSort: function(data, collection) {
  54512. if (this._sorting) {
  54513. return data;
  54514. }
  54515. this._sorting = true;
  54516. this.getNode().sort(collection.getSortFn(), true, true);
  54517. delete this._sorting;
  54518. return this.callParent(arguments);
  54519. },
  54520. updateRoot: function(root, oldRoot) {
  54521. if (oldRoot) {
  54522. oldRoot.unBefore({
  54523. expand: 'onNodeBeforeExpand',
  54524. scope: this
  54525. });
  54526. oldRoot.unjoin(this);
  54527. }
  54528. root.onBefore({
  54529. expand: 'onNodeBeforeExpand',
  54530. scope: this
  54531. });
  54532. this.onNodeAppend(null, root);
  54533. this.setNode(root);
  54534. if (!root.isLoaded() && !root.isLoading() && root.isExpanded()) {
  54535. this.load({
  54536. node: root
  54537. });
  54538. }
  54539. /**
  54540. * @event rootchange
  54541. * Fires whenever the root node changes on this TreeStore.
  54542. * @param {Ext.data.TreeStore} store This tree Store
  54543. * @param {Ext.data.Model} newRoot The new root node
  54544. * @param {Ext.data.Model} oldRoot The old root node
  54545. */
  54546. this.fireEvent('rootchange', this, root, oldRoot);
  54547. },
  54548. /**
  54549. * Returns the record node by id
  54550. * @return {Ext.data.NodeInterface}
  54551. */
  54552. getNodeById: function(id) {
  54553. return this.data.getByKey(id);
  54554. },
  54555. onNodeBeforeExpand: function(node, options, e) {
  54556. if (node.isLoading()) {
  54557. e.pause();
  54558. this.on('load', function() {
  54559. e.resume();
  54560. }, this, {single: true});
  54561. }
  54562. else if (!node.isLoaded()) {
  54563. e.pause();
  54564. this.load({
  54565. node: node,
  54566. callback: function() {
  54567. e.resume();
  54568. }
  54569. });
  54570. }
  54571. },
  54572. onNodeAppend: function(parent, node) {
  54573. var proxy = this.getProxy(),
  54574. reader = proxy.getReader(),
  54575. Model = this.getModel(),
  54576. data = node.raw,
  54577. records = [],
  54578. rootProperty = reader.getRootProperty(),
  54579. dataRoot, processedData, i, ln, processedDataItem;
  54580. if (!node.isLeaf()) {
  54581. dataRoot = reader.getRoot(data);
  54582. if (dataRoot) {
  54583. processedData = reader.extractData(dataRoot);
  54584. for (i = 0, ln = processedData.length; i < ln; i++) {
  54585. processedDataItem = processedData[i];
  54586. records.push(new Model(processedDataItem.data, processedDataItem.id, processedDataItem.node));
  54587. }
  54588. if (records.length) {
  54589. this.fillNode(node, records);
  54590. } else {
  54591. node.set('loaded', true);
  54592. }
  54593. // If the child record is not a leaf, and it has a data root (e.g. items: [])
  54594. // and there are items in this data root, then we call fillNode to automatically
  54595. // add these items. fillNode sets the loaded property on the node, meaning that
  54596. // the next time you expand that node, it's not going to the server to request the
  54597. // children. If however you pass back an empty array as items, we have to set the
  54598. // loaded property to true here as well to prevent the items from being be loaded
  54599. // from the server the next time you expand it.
  54600. // If you want to have the items loaded on the next expand, then the data for the
  54601. // node should not contain the items: [] array.
  54602. delete data[rootProperty];
  54603. }
  54604. }
  54605. },
  54606. updateAutoLoad: function(autoLoad) {
  54607. if (autoLoad) {
  54608. var root = this.getRoot();
  54609. if (!root.isLoaded() && !root.isLoading()) {
  54610. this.load({node: root});
  54611. }
  54612. }
  54613. },
  54614. /**
  54615. * Loads the Store using its configured {@link #proxy}.
  54616. * @param {Object} options (Optional) config object. This is passed into the {@link Ext.data.Operation Operation}
  54617. * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function.
  54618. * The options can also contain a node, which indicates which node is to be loaded. If not specified, it will
  54619. * default to the root node.
  54620. * @return {Object}
  54621. */
  54622. load: function(options) {
  54623. options = options || {};
  54624. options.params = options.params || {};
  54625. var me = this,
  54626. node = options.node = options.node || me.getRoot();
  54627. options.params[me.getNodeParam()] = node.getId();
  54628. if (me.getClearOnLoad()) {
  54629. node.removeAll(true);
  54630. }
  54631. node.set('loading', true);
  54632. return me.callParent([options]);
  54633. },
  54634. updateProxy: function(proxy) {
  54635. this.callParent(arguments);
  54636. var reader = proxy.getReader();
  54637. if (!reader.getRootProperty()) {
  54638. reader.setRootProperty(this.getDefaultRootProperty());
  54639. reader.buildExtractors();
  54640. }
  54641. },
  54642. /**
  54643. * @inheritdoc
  54644. */
  54645. removeAll: function() {
  54646. this.getRoot().removeAll(true);
  54647. this.callParent(arguments);
  54648. },
  54649. /**
  54650. * @inheritdoc
  54651. */
  54652. onProxyLoad: function(operation) {
  54653. var me = this,
  54654. records = operation.getRecords(),
  54655. successful = operation.wasSuccessful(),
  54656. node = operation.getNode();
  54657. node.beginEdit();
  54658. node.set('loading', false);
  54659. if (successful) {
  54660. records = me.fillNode(node, records);
  54661. }
  54662. node.endEdit();
  54663. me.loading = false;
  54664. me.loaded = true;
  54665. node.fireEvent('load', node, records, successful);
  54666. me.fireEvent('load', this, records, successful, operation);
  54667. //this is a callback that would have been passed to the 'read' function and is optional
  54668. Ext.callback(operation.getCallback(), operation.getScope() || me, [records, operation, successful]);
  54669. },
  54670. /**
  54671. * Fills a node with a series of child records.
  54672. * @private
  54673. * @param {Ext.data.NodeInterface} node The node to fill.
  54674. * @param {Ext.data.Model[]} records The records to add.
  54675. */
  54676. fillNode: function(node, records) {
  54677. var ln = records ? records.length : 0,
  54678. i, child;
  54679. for (i = 0; i < ln; i++) {
  54680. // true/true to suppress any events fired by the node, or the new child node
  54681. child = node.appendChild(records[i], true, true);
  54682. this.onNodeAppend(node, child);
  54683. }
  54684. node.set('loaded', true);
  54685. return records;
  54686. }
  54687. });
  54688. /**
  54689. * @author Ed Spencer
  54690. * @aside guide models
  54691. *
  54692. * This singleton contains a set of validation functions that can be used to validate any type of data. They are most
  54693. * often used in {@link Ext.data.Model Models}, where they are automatically set up and executed.
  54694. */
  54695. Ext.define('Ext.data.Validations', {
  54696. alternateClassName: 'Ext.data.validations',
  54697. singleton: true,
  54698. config: {
  54699. /**
  54700. * @property {String} presenceMessage
  54701. * The default error message used when a presence validation fails.
  54702. */
  54703. presenceMessage: 'must be present',
  54704. /**
  54705. * @property {String} lengthMessage
  54706. * The default error message used when a length validation fails.
  54707. */
  54708. lengthMessage: 'is the wrong length',
  54709. /**
  54710. * @property {Boolean} formatMessage
  54711. * The default error message used when a format validation fails.
  54712. */
  54713. formatMessage: 'is the wrong format',
  54714. /**
  54715. * @property {String} inclusionMessage
  54716. * The default error message used when an inclusion validation fails.
  54717. */
  54718. inclusionMessage: 'is not included in the list of acceptable values',
  54719. /**
  54720. * @property {String} exclusionMessage
  54721. * The default error message used when an exclusion validation fails.
  54722. */
  54723. exclusionMessage: 'is not an acceptable value',
  54724. /**
  54725. * @property {String} emailMessage
  54726. * The default error message used when an email validation fails
  54727. */
  54728. emailMessage: 'is not a valid email address'
  54729. },
  54730. constructor: function(config) {
  54731. this.initConfig(config);
  54732. },
  54733. /**
  54734. * Returns the configured error message for any of the validation types.
  54735. * @param {String} type The type of validation you want to get the error message for.
  54736. * @return {Object}
  54737. */
  54738. getMessage: function(type) {
  54739. var getterFn = this['get' + type[0].toUpperCase() + type.slice(1) + 'Message'];
  54740. if (getterFn) {
  54741. return getterFn.call(this);
  54742. }
  54743. return '';
  54744. },
  54745. /**
  54746. * The regular expression used to validate email addresses
  54747. * @property emailRe
  54748. * @type RegExp
  54749. */
  54750. emailRe: /^\s*[\w\-\+_]+(\.[\w\-\+_]+)*\@[\w\-\+_]+\.[\w\-\+_]+(\.[\w\-\+_]+)*\s*$/,
  54751. /**
  54752. * Validates that the given value is present.
  54753. * For example:
  54754. *
  54755. * validations: [{type: 'presence', field: 'age'}]
  54756. *
  54757. * @param {Object} config Config object.
  54758. * @param {Object} value The value to validate.
  54759. * @return {Boolean} `true` if validation passed.
  54760. */
  54761. presence: function(config, value) {
  54762. if (arguments.length === 1) {
  54763. value = config;
  54764. }
  54765. return !!value || value === 0;
  54766. },
  54767. /**
  54768. * Returns `true` if the given value is between the configured min and max values.
  54769. * For example:
  54770. *
  54771. * validations: [{type: 'length', field: 'name', min: 2}]
  54772. *
  54773. * @param {Object} config Config object.
  54774. * @param {String} value The value to validate.
  54775. * @return {Boolean} `true` if the value passes validation.
  54776. */
  54777. length: function(config, value) {
  54778. if (value === undefined || value === null) {
  54779. return false;
  54780. }
  54781. var length = value.length,
  54782. min = config.min,
  54783. max = config.max;
  54784. if ((min && length < min) || (max && length > max)) {
  54785. return false;
  54786. } else {
  54787. return true;
  54788. }
  54789. },
  54790. /**
  54791. * Validates that an email string is in the correct format.
  54792. * @param {Object} config Config object.
  54793. * @param {String} email The email address.
  54794. * @return {Boolean} `true` if the value passes validation.
  54795. */
  54796. email: function(config, email) {
  54797. return Ext.data.validations.emailRe.test(email);
  54798. },
  54799. /**
  54800. * Returns `true` if the given value passes validation against the configured `matcher` regex.
  54801. * For example:
  54802. *
  54803. * validations: [{type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}]
  54804. *
  54805. * @param {Object} config Config object.
  54806. * @param {String} value The value to validate.
  54807. * @return {Boolean} `true` if the value passes the format validation.
  54808. */
  54809. format: function(config, value) {
  54810. if (value === undefined || value === null) {
  54811. value = '';
  54812. }
  54813. return !!(config.matcher && config.matcher.test(value));
  54814. },
  54815. /**
  54816. * Validates that the given value is present in the configured `list`.
  54817. * For example:
  54818. *
  54819. * validations: [{type: 'inclusion', field: 'gender', list: ['Male', 'Female']}]
  54820. *
  54821. * @param {Object} config Config object.
  54822. * @param {String} value The value to validate.
  54823. * @return {Boolean} `true` if the value is present in the list.
  54824. */
  54825. inclusion: function(config, value) {
  54826. return config.list && Ext.Array.indexOf(config.list,value) != -1;
  54827. },
  54828. /**
  54829. * Validates that the given value is present in the configured `list`.
  54830. * For example:
  54831. *
  54832. * validations: [{type: 'exclusion', field: 'username', list: ['Admin', 'Operator']}]
  54833. *
  54834. * @param {Object} config Config object.
  54835. * @param {String} value The value to validate.
  54836. * @return {Boolean} `true` if the value is not present in the list.
  54837. */
  54838. exclusion: function(config, value) {
  54839. return config.list && Ext.Array.indexOf(config.list,value) == -1;
  54840. }
  54841. });
  54842. /**
  54843. * @author Tommy Maintz
  54844. *
  54845. * This class is a sequential id generator. A simple use of this class would be like so:
  54846. *
  54847. * Ext.define('MyApp.data.MyModel', {
  54848. * extend: 'Ext.data.Model',
  54849. * config: {
  54850. * identifier: 'sequential'
  54851. * }
  54852. * });
  54853. * // assign id's of 1, 2, 3, etc.
  54854. *
  54855. * An example of a configured generator would be:
  54856. *
  54857. * Ext.define('MyApp.data.MyModel', {
  54858. * extend: 'Ext.data.Model',
  54859. * config: {
  54860. * identifier: {
  54861. * type: 'sequential',
  54862. * prefix: 'ID_',
  54863. * seed: 1000
  54864. * }
  54865. * }
  54866. * });
  54867. * // assign id's of ID_1000, ID_1001, ID_1002, etc.
  54868. *
  54869. */
  54870. Ext.define('Ext.data.identifier.Sequential', {
  54871. extend: 'Ext.data.identifier.Simple',
  54872. alias: 'data.identifier.sequential',
  54873. config: {
  54874. /**
  54875. * @cfg {String} prefix
  54876. * The string to place in front of the sequential number for each generated id. The
  54877. * default is blank.
  54878. */
  54879. prefix: '',
  54880. /**
  54881. * @cfg {Number} seed
  54882. * The number at which to start generating sequential id's. The default is 1.
  54883. */
  54884. seed: 1
  54885. },
  54886. constructor: function() {
  54887. var me = this;
  54888. me.callParent(arguments);
  54889. me.parts = [me.getPrefix(), ''];
  54890. },
  54891. generate: function(record) {
  54892. var me = this,
  54893. parts = me.parts,
  54894. seed = me.getSeed() + 1;
  54895. me.setSeed(seed);
  54896. parts[1] = seed;
  54897. return parts.join('');
  54898. }
  54899. });
  54900. /**
  54901. * @author Tommy Maintz
  54902. *
  54903. * This class generates UUID's according to RFC 4122. This class has a default id property.
  54904. * This means that a single instance is shared unless the id property is overridden. Thus,
  54905. * two {@link Ext.data.Model} instances configured like the following share one generator:
  54906. *
  54907. * Ext.define('MyApp.data.MyModelX', {
  54908. * extend: 'Ext.data.Model',
  54909. * config: {
  54910. * identifier: 'uuid'
  54911. * }
  54912. * });
  54913. *
  54914. * Ext.define('MyApp.data.MyModelY', {
  54915. * extend: 'Ext.data.Model',
  54916. * config: {
  54917. * identifier: 'uuid'
  54918. * }
  54919. * });
  54920. *
  54921. * This allows all models using this class to share a commonly configured instance.
  54922. *
  54923. * # Using Version 1 ("Sequential") UUID's
  54924. *
  54925. * If a server can provide a proper timestamp and a "cryptographic quality random number"
  54926. * (as described in RFC 4122), the shared instance can be configured as follows:
  54927. *
  54928. * Ext.data.identifier.Uuid.Global.reconfigure({
  54929. * version: 1,
  54930. * clockSeq: clock, // 14 random bits
  54931. * salt: salt, // 48 secure random bits (the Node field)
  54932. * timestamp: ts // timestamp per Section 4.1.4
  54933. * });
  54934. *
  54935. * // or these values can be split into 32-bit chunks:
  54936. *
  54937. * Ext.data.identifier.Uuid.Global.reconfigure({
  54938. * version: 1,
  54939. * clockSeq: clock,
  54940. * salt: { lo: saltLow32, hi: saltHigh32 },
  54941. * timestamp: { lo: timestampLow32, hi: timestamptHigh32 }
  54942. * });
  54943. *
  54944. * This approach improves the generator's uniqueness by providing a valid timestamp and
  54945. * higher quality random data. Version 1 UUID's should not be used unless this information
  54946. * can be provided by a server and care should be taken to avoid caching of this data.
  54947. *
  54948. * See [http://www.ietf.org/rfc/rfc4122.txt](http://www.ietf.org/rfc/rfc4122.txt) for details.
  54949. */
  54950. Ext.define('Ext.data.identifier.Uuid', {
  54951. extend: 'Ext.data.identifier.Simple',
  54952. alias: 'data.identifier.uuid',
  54953. isUnique: true,
  54954. config: {
  54955. /**
  54956. * The id for this generator instance. By default all model instances share the same
  54957. * UUID generator instance. By specifying an id other then 'uuid', a unique generator instance
  54958. * will be created for the Model.
  54959. */
  54960. id: undefined,
  54961. /**
  54962. * @property {Number/Object} salt
  54963. * When created, this value is a 48-bit number. For computation, this value is split
  54964. * into 32-bit parts and stored in an object with `hi` and `lo` properties.
  54965. */
  54966. salt: null,
  54967. /**
  54968. * @property {Number/Object} timestamp
  54969. * When created, this value is a 60-bit number. For computation, this value is split
  54970. * into 32-bit parts and stored in an object with `hi` and `lo` properties.
  54971. */
  54972. timestamp: null,
  54973. /**
  54974. * @cfg {Number} version
  54975. * The Version of UUID. Supported values are:
  54976. *
  54977. * * 1 : Time-based, "sequential" UUID.
  54978. * * 4 : Pseudo-random UUID.
  54979. *
  54980. * The default is 4.
  54981. */
  54982. version: 4
  54983. },
  54984. applyId: function(id) {
  54985. if (id === undefined) {
  54986. return Ext.data.identifier.Uuid.Global;
  54987. }
  54988. return id;
  54989. },
  54990. constructor: function() {
  54991. var me = this;
  54992. me.callParent(arguments);
  54993. me.parts = [];
  54994. me.init();
  54995. },
  54996. /**
  54997. * Reconfigures this generator given new config properties.
  54998. */
  54999. reconfigure: function(config) {
  55000. this.setConfig(config);
  55001. this.init();
  55002. },
  55003. generate: function () {
  55004. var me = this,
  55005. parts = me.parts,
  55006. version = me.getVersion(),
  55007. salt = me.getSalt(),
  55008. time = me.getTimestamp();
  55009. /*
  55010. The magic decoder ring (derived from RFC 4122 Section 4.2.2):
  55011. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  55012. | time_low |
  55013. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  55014. | time_mid | ver | time_hi |
  55015. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  55016. |res| clock_hi | clock_low | salt 0 |M| salt 1 |
  55017. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  55018. | salt (2-5) |
  55019. +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  55020. time_mid clock_hi (low 6 bits)
  55021. time_low | time_hi |clock_lo
  55022. | | | || salt[0]
  55023. | | | || | salt[1..5]
  55024. v v v vv v v
  55025. 0badf00d-aced-1def-b123-dfad0badbeef
  55026. ^ ^ ^
  55027. version | multicast (low bit)
  55028. |
  55029. reserved (upper 2 bits)
  55030. */
  55031. parts[0] = me.toHex(time.lo, 8);
  55032. parts[1] = me.toHex(time.hi & 0xFFFF, 4);
  55033. parts[2] = me.toHex(((time.hi >>> 16) & 0xFFF) | (version << 12), 4);
  55034. parts[3] = me.toHex(0x80 | ((me.clockSeq >>> 8) & 0x3F), 2) +
  55035. me.toHex(me.clockSeq & 0xFF, 2);
  55036. parts[4] = me.toHex(salt.hi, 4) + me.toHex(salt.lo, 8);
  55037. if (version == 4) {
  55038. me.init(); // just regenerate all the random values...
  55039. } else {
  55040. // sequentially increment the timestamp...
  55041. ++time.lo;
  55042. if (time.lo >= me.twoPow32) { // if (overflow)
  55043. time.lo = 0;
  55044. ++time.hi;
  55045. }
  55046. }
  55047. return parts.join('-').toLowerCase();
  55048. },
  55049. /**
  55050. * @private
  55051. */
  55052. init: function () {
  55053. var me = this,
  55054. salt = me.getSalt(),
  55055. time = me.getTimestamp();
  55056. if (me.getVersion() == 4) {
  55057. // See RFC 4122 (Secion 4.4)
  55058. // o If the state was unavailable (e.g., non-existent or corrupted),
  55059. // or the saved node ID is different than the current node ID,
  55060. // generate a random clock sequence value.
  55061. me.clockSeq = me.rand(0, me.twoPow14-1);
  55062. if (!salt) {
  55063. salt = {};
  55064. me.setSalt(salt);
  55065. }
  55066. if (!time) {
  55067. time = {};
  55068. me.setTimestamp(time);
  55069. }
  55070. // See RFC 4122 (Secion 4.4)
  55071. salt.lo = me.rand(0, me.twoPow32-1);
  55072. salt.hi = me.rand(0, me.twoPow16-1);
  55073. time.lo = me.rand(0, me.twoPow32-1);
  55074. time.hi = me.rand(0, me.twoPow28-1);
  55075. } else {
  55076. // this is run only once per-instance
  55077. me.setSalt(me.split(me.getSalt()));
  55078. me.setTimestamp(me.split(me.getTimestamp()));
  55079. // Set multicast bit: "the least significant bit of the first octet of the
  55080. // node ID" (nodeId = salt for this implementation):
  55081. me.getSalt().hi |= 0x100;
  55082. }
  55083. },
  55084. /**
  55085. * Some private values used in methods on this class.
  55086. * @private
  55087. */
  55088. twoPow14: Math.pow(2, 14),
  55089. twoPow16: Math.pow(2, 16),
  55090. twoPow28: Math.pow(2, 28),
  55091. twoPow32: Math.pow(2, 32),
  55092. /**
  55093. * Converts a value into a hexadecimal value. Also allows for a maximum length
  55094. * of the returned value.
  55095. * @param value
  55096. * @param length
  55097. * @private
  55098. */
  55099. toHex: function(value, length) {
  55100. var ret = value.toString(16);
  55101. if (ret.length > length) {
  55102. ret = ret.substring(ret.length - length); // right-most digits
  55103. } else if (ret.length < length) {
  55104. ret = Ext.String.leftPad(ret, length, '0');
  55105. }
  55106. return ret;
  55107. },
  55108. /**
  55109. * Generates a random value with between a low and high.
  55110. * @param lo
  55111. * @param hi
  55112. * @private
  55113. */
  55114. rand: function(lo, hi) {
  55115. var v = Math.random() * (hi - lo + 1);
  55116. return Math.floor(v) + lo;
  55117. },
  55118. /**
  55119. * Splits a number into a low and high value.
  55120. * @param bignum
  55121. * @private
  55122. */
  55123. split: function(bignum) {
  55124. if (typeof(bignum) == 'number') {
  55125. var hi = Math.floor(bignum / this.twoPow32);
  55126. return {
  55127. lo: Math.floor(bignum - hi * this.twoPow32),
  55128. hi: hi
  55129. };
  55130. }
  55131. return bignum;
  55132. }
  55133. }, function() {
  55134. this.Global = new this({
  55135. id: 'uuid'
  55136. });
  55137. });
  55138. /**
  55139. * @author Ed Spencer
  55140. * @aside guide proxies
  55141. *
  55142. * The JsonP proxy is useful when you need to load data from a domain other than the one your application is running on. If
  55143. * your application is running on http://domainA.com it cannot use {@link Ext.data.proxy.Ajax Ajax} to load its data
  55144. * from http://domainB.com because cross-domain ajax requests are prohibited by the browser.
  55145. *
  55146. * We can get around this using a JsonP proxy. JsonP proxy injects a `<script>` tag into the DOM whenever an AJAX request
  55147. * would usually be made. Let's say we want to load data from http://domainB.com/users - the script tag that would be
  55148. * injected might look like this:
  55149. *
  55150. * <script src="http://domainB.com/users?callback=someCallback"></script>
  55151. *
  55152. * When we inject the tag above, the browser makes a request to that url and includes the response as if it was any
  55153. * other type of JavaScript include. By passing a callback in the url above, we're telling domainB's server that we want
  55154. * to be notified when the result comes in and that it should call our callback function with the data it sends back. So
  55155. * long as the server formats the response to look like this, everything will work:
  55156. *
  55157. * someCallback({
  55158. * users: [
  55159. * {
  55160. * id: 1,
  55161. * name: "Ed Spencer",
  55162. * email: "ed@sencha.com"
  55163. * }
  55164. * ]
  55165. * });
  55166. *
  55167. * As soon as the script finishes loading, the 'someCallback' function that we passed in the url is called with the JSON
  55168. * object that the server returned.
  55169. *
  55170. * JsonP proxy takes care of all of this automatically. It formats the url you pass, adding the callback parameter
  55171. * automatically. It even creates a temporary callback function, waits for it to be called and then puts the data into
  55172. * the Proxy making it look just like you loaded it through a normal {@link Ext.data.proxy.Ajax AjaxProxy}. Here's how
  55173. * we might set that up:
  55174. *
  55175. * Ext.define('User', {
  55176. * extend: 'Ext.data.Model',
  55177. * config: {
  55178. * fields: ['id', 'name', 'email']
  55179. * }
  55180. * });
  55181. *
  55182. * var store = Ext.create('Ext.data.Store', {
  55183. * model: 'User',
  55184. * proxy: {
  55185. * type: 'jsonp',
  55186. * url : 'http://domainB.com/users'
  55187. * }
  55188. * });
  55189. *
  55190. * store.load();
  55191. *
  55192. * That's all we need to do - JsonP proxy takes care of the rest. In this case the Proxy will have injected a script tag
  55193. * like this:
  55194. *
  55195. * <script src="http://domainB.com/users?callback=callback1"></script>
  55196. *
  55197. * # Customization
  55198. *
  55199. * This script tag can be customized using the {@link #callbackKey} configuration. For example:
  55200. *
  55201. * var store = Ext.create('Ext.data.Store', {
  55202. * model: 'User',
  55203. * proxy: {
  55204. * type: 'jsonp',
  55205. * url : 'http://domainB.com/users',
  55206. * callbackKey: 'theCallbackFunction'
  55207. * }
  55208. * });
  55209. *
  55210. * store.load();
  55211. *
  55212. * Would inject a script tag like this:
  55213. *
  55214. * <script src="http://domainB.com/users?theCallbackFunction=callback1"></script>
  55215. *
  55216. * # Implementing on the server side
  55217. *
  55218. * The remote server side needs to be configured to return data in this format. Here are suggestions for how you might
  55219. * achieve this using Java, PHP and ASP.net:
  55220. *
  55221. * Java:
  55222. *
  55223. * boolean jsonP = false;
  55224. * String cb = request.getParameter("callback");
  55225. * if (cb != null) {
  55226. * jsonP = true;
  55227. * response.setContentType("text/javascript");
  55228. * } else {
  55229. * response.setContentType("application/x-json");
  55230. * }
  55231. * Writer out = response.getWriter();
  55232. * if (jsonP) {
  55233. * out.write(cb + "(");
  55234. * }
  55235. * out.print(dataBlock.toJsonString());
  55236. * if (jsonP) {
  55237. * out.write(");");
  55238. * }
  55239. *
  55240. * PHP:
  55241. *
  55242. * $callback = $_REQUEST['callback'];
  55243. *
  55244. * // Create the output object.
  55245. * $output = array('a' => 'Apple', 'b' => 'Banana');
  55246. *
  55247. * //start output
  55248. * if ($callback) {
  55249. * header('Content-Type: text/javascript');
  55250. * echo $callback . '(' . json_encode($output) . ');';
  55251. * } else {
  55252. * header('Content-Type: application/x-json');
  55253. * echo json_encode($output);
  55254. * }
  55255. *
  55256. * ASP.net:
  55257. *
  55258. * String jsonString = "{success: true}";
  55259. * String cb = Request.Params.Get("callback");
  55260. * String responseString = "";
  55261. * if (!String.IsNullOrEmpty(cb)) {
  55262. * responseString = cb + "(" + jsonString + ")";
  55263. * } else {
  55264. * responseString = jsonString;
  55265. * }
  55266. * Response.Write(responseString);
  55267. */
  55268. Ext.define('Ext.data.proxy.JsonP', {
  55269. extend: 'Ext.data.proxy.Server',
  55270. alternateClassName: 'Ext.data.ScriptTagProxy',
  55271. alias: ['proxy.jsonp', 'proxy.scripttag'],
  55272. requires: ['Ext.data.JsonP'],
  55273. config: {
  55274. defaultWriterType: 'base',
  55275. /**
  55276. * @cfg {String} callbackKey
  55277. * See {@link Ext.data.JsonP#callbackKey}.
  55278. * @accessor
  55279. */
  55280. callbackKey : 'callback',
  55281. /**
  55282. * @cfg {String} recordParam
  55283. * The param name to use when passing records to the server (e.g. 'records=someEncodedRecordString').
  55284. * @accessor
  55285. */
  55286. recordParam: 'records',
  55287. /**
  55288. * @cfg {Boolean} autoAppendParams
  55289. * `true` to automatically append the request's params to the generated url.
  55290. * @accessor
  55291. */
  55292. autoAppendParams: true
  55293. },
  55294. /**
  55295. * Performs the read request to the remote domain. JsonP proxy does not actually create an Ajax request,
  55296. * instead we write out a `<script>` tag based on the configuration of the internal Ext.data.Request object
  55297. * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute.
  55298. * @param {Function} callback A callback function to execute when the Operation has been completed.
  55299. * @param {Object} scope The scope to execute the callback in.
  55300. * @return {Object}
  55301. * @protected
  55302. */
  55303. doRequest: function(operation, callback, scope) {
  55304. // <debug>
  55305. var action = operation.getAction();
  55306. if (action !== 'read') {
  55307. Ext.Logger.error('JsonP proxies can only be used to read data.');
  55308. }
  55309. // </debug>
  55310. //generate the unique IDs for this request
  55311. var me = this,
  55312. request = me.buildRequest(operation),
  55313. params = request.getParams();
  55314. // apply JsonP proxy-specific attributes to the Request
  55315. request.setConfig({
  55316. callbackKey: me.getCallbackKey(),
  55317. timeout: me.getTimeout(),
  55318. scope: me,
  55319. callback: me.createRequestCallback(request, operation, callback, scope)
  55320. });
  55321. // Prevent doubling up because the params are already added to the url in buildUrl
  55322. if (me.getAutoAppendParams()) {
  55323. request.setParams({});
  55324. }
  55325. request.setJsonP(Ext.data.JsonP.request(request.getCurrentConfig()));
  55326. // Set the params back once we have made the request though
  55327. request.setParams(params);
  55328. operation.setStarted();
  55329. me.lastRequest = request;
  55330. return request;
  55331. },
  55332. /**
  55333. * @private
  55334. * Creates and returns the function that is called when the request has completed. The returned function
  55335. * should accept a Response object, which contains the response to be read by the configured Reader.
  55336. * The third argument is the callback that should be called after the request has been completed and the Reader has decoded
  55337. * the response. This callback will typically be the callback passed by a store, e.g. in proxy.read(operation, theCallback, scope)
  55338. * theCallback refers to the callback argument received by this function.
  55339. * See {@link #doRequest} for details.
  55340. * @param {Ext.data.Request} request The Request object.
  55341. * @param {Ext.data.Operation} operation The Operation being executed.
  55342. * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
  55343. * passed to doRequest.
  55344. * @param {Object} scope The scope in which to execute the callback function.
  55345. * @return {Function} The callback function.
  55346. */
  55347. createRequestCallback: function(request, operation, callback, scope) {
  55348. var me = this;
  55349. return function(success, response, errorType) {
  55350. delete me.lastRequest;
  55351. me.processResponse(success, operation, request, response, callback, scope);
  55352. };
  55353. },
  55354. // @inheritdoc
  55355. setException: function(operation, response) {
  55356. operation.setException(operation.getRequest().getJsonP().errorType);
  55357. },
  55358. /**
  55359. * Generates a url based on a given Ext.data.Request object. Adds the params and callback function name to the url
  55360. * @param {Ext.data.Request} request The request object.
  55361. * @return {String} The url.
  55362. */
  55363. buildUrl: function(request) {
  55364. var me = this,
  55365. url = me.callParent(arguments),
  55366. params = Ext.apply({}, request.getParams()),
  55367. filters = params.filters,
  55368. records,
  55369. filter, i, value;
  55370. delete params.filters;
  55371. if (me.getAutoAppendParams()) {
  55372. url = Ext.urlAppend(url, Ext.Object.toQueryString(params));
  55373. }
  55374. if (filters && filters.length) {
  55375. for (i = 0; i < filters.length; i++) {
  55376. filter = filters[i];
  55377. value = filter.getValue();
  55378. if (value) {
  55379. url = Ext.urlAppend(url, filter.getProperty() + "=" + value);
  55380. }
  55381. }
  55382. }
  55383. return url;
  55384. },
  55385. /**
  55386. * @inheritdoc
  55387. */
  55388. destroy: function() {
  55389. this.abort();
  55390. this.callParent(arguments);
  55391. },
  55392. /**
  55393. * Aborts the current server request if one is currently running.
  55394. */
  55395. abort: function() {
  55396. var lastRequest = this.lastRequest;
  55397. if (lastRequest) {
  55398. Ext.data.JsonP.abort(lastRequest.getJsonP());
  55399. }
  55400. }
  55401. });
  55402. /**
  55403. * @author Ed Spencer
  55404. *
  55405. * WebStorageProxy is simply a superclass for the {@link Ext.data.proxy.LocalStorage LocalStorage} proxy. It uses the
  55406. * new HTML5 key/value client-side storage objects to save {@link Ext.data.Model model instances} for offline use.
  55407. * @private
  55408. */
  55409. Ext.define('Ext.data.proxy.WebStorage', {
  55410. extend: 'Ext.data.proxy.Client',
  55411. alternateClassName: 'Ext.data.WebStorageProxy',
  55412. requires: 'Ext.Date',
  55413. config: {
  55414. /**
  55415. * @cfg {String} id
  55416. * The unique ID used as the key in which all record data are stored in the local storage object.
  55417. */
  55418. id: undefined,
  55419. // WebStorage proxies dont use readers and writers
  55420. /**
  55421. * @cfg
  55422. * @hide
  55423. */
  55424. reader: null,
  55425. /**
  55426. * @cfg
  55427. * @hide
  55428. */
  55429. writer: null,
  55430. /**
  55431. * @cfg {Boolean} enablePagingParams This can be set to true if you want the webstorage proxy to comply
  55432. * to the paging params set on the store.
  55433. */
  55434. enablePagingParams: false
  55435. },
  55436. /**
  55437. * Creates the proxy, throws an error if local storage is not supported in the current browser.
  55438. * @param {Object} config (optional) Config object.
  55439. */
  55440. constructor: function(config) {
  55441. this.callParent(arguments);
  55442. /**
  55443. * @property {Object} cache
  55444. * Cached map of records already retrieved by this Proxy. Ensures that the same instance is always retrieved.
  55445. */
  55446. this.cache = {};
  55447. //<debug>
  55448. if (this.getStorageObject() === undefined) {
  55449. Ext.Logger.error("Local Storage is not supported in this browser, please use another type of data proxy");
  55450. }
  55451. //</debug>
  55452. },
  55453. updateModel: function(model) {
  55454. if (!this.getId()) {
  55455. this.setId(model.modelName);
  55456. }
  55457. },
  55458. //inherit docs
  55459. create: function(operation, callback, scope) {
  55460. var records = operation.getRecords(),
  55461. length = records.length,
  55462. ids = this.getIds(),
  55463. id, record, i;
  55464. operation.setStarted();
  55465. for (i = 0; i < length; i++) {
  55466. record = records[i];
  55467. // <debug>
  55468. if (!this.getModel().getIdentifier().isUnique) {
  55469. Ext.Logger.warn('Your identifier generation strategy for the model does not ensure unique id\'s. Please use the UUID strategy, or implement your own identifier strategy with the flag isUnique.');
  55470. }
  55471. // </debug>
  55472. id = record.getId();
  55473. this.setRecord(record);
  55474. ids.push(id);
  55475. }
  55476. this.setIds(ids);
  55477. operation.setCompleted();
  55478. operation.setSuccessful();
  55479. if (typeof callback == 'function') {
  55480. callback.call(scope || this, operation);
  55481. }
  55482. },
  55483. //inherit docs
  55484. read: function(operation, callback, scope) {
  55485. var records = [],
  55486. ids = this.getIds(),
  55487. model = this.getModel(),
  55488. idProperty = model.getIdProperty(),
  55489. params = operation.getParams() || {},
  55490. sorters = operation.getSorters(),
  55491. filters = operation.getFilters(),
  55492. start = operation.getStart(),
  55493. limit = operation.getLimit(),
  55494. length = ids.length,
  55495. i, record, collection;
  55496. //read a single record
  55497. if (params[idProperty] !== undefined) {
  55498. record = this.getRecord(params[idProperty]);
  55499. if (record) {
  55500. records.push(record);
  55501. operation.setSuccessful();
  55502. }
  55503. } else {
  55504. for (i = 0; i < length; i++) {
  55505. records.push(this.getRecord(ids[i]));
  55506. }
  55507. collection = Ext.create('Ext.util.Collection');
  55508. // First we comply to filters
  55509. if (filters && filters.length) {
  55510. collection.setFilters(filters);
  55511. }
  55512. // Then we comply to sorters
  55513. if (sorters && sorters.length) {
  55514. collection.setSorters(sorters);
  55515. }
  55516. collection.addAll(records);
  55517. if (this.getEnablePagingParams() && start !== undefined && limit !== undefined) {
  55518. records = collection.items.slice(start, start + limit);
  55519. } else {
  55520. records = collection.items.slice();
  55521. }
  55522. operation.setSuccessful();
  55523. }
  55524. operation.setCompleted();
  55525. operation.setResultSet(Ext.create('Ext.data.ResultSet', {
  55526. records: records,
  55527. total : records.length,
  55528. loaded : true
  55529. }));
  55530. operation.setRecords(records);
  55531. if (typeof callback == 'function') {
  55532. callback.call(scope || this, operation);
  55533. }
  55534. },
  55535. //inherit docs
  55536. update: function(operation, callback, scope) {
  55537. var records = operation.getRecords(),
  55538. length = records.length,
  55539. ids = this.getIds(),
  55540. record, id, i;
  55541. operation.setStarted();
  55542. for (i = 0; i < length; i++) {
  55543. record = records[i];
  55544. this.setRecord(record);
  55545. //we need to update the set of ids here because it's possible that a non-phantom record was added
  55546. //to this proxy - in which case the record's id would never have been added via the normal 'create' call
  55547. id = record.getId();
  55548. if (id !== undefined && Ext.Array.indexOf(ids, id) == -1) {
  55549. ids.push(id);
  55550. }
  55551. }
  55552. this.setIds(ids);
  55553. operation.setCompleted();
  55554. operation.setSuccessful();
  55555. if (typeof callback == 'function') {
  55556. callback.call(scope || this, operation);
  55557. }
  55558. },
  55559. //inherit
  55560. destroy: function(operation, callback, scope) {
  55561. var records = operation.getRecords(),
  55562. length = records.length,
  55563. ids = this.getIds(),
  55564. //newIds is a copy of ids, from which we remove the destroyed records
  55565. newIds = [].concat(ids),
  55566. i;
  55567. operation.setStarted();
  55568. for (i = 0; i < length; i++) {
  55569. Ext.Array.remove(newIds, records[i].getId());
  55570. this.removeRecord(records[i], false);
  55571. }
  55572. this.setIds(newIds);
  55573. operation.setCompleted();
  55574. operation.setSuccessful();
  55575. if (typeof callback == 'function') {
  55576. callback.call(scope || this, operation);
  55577. }
  55578. },
  55579. /**
  55580. * @private
  55581. * Fetches a model instance from the Proxy by ID. Runs each field's decode function (if present) to decode the data.
  55582. * @param {String} id The record's unique ID
  55583. * @return {Ext.data.Model} The model instance or undefined if the record did not exist in the storage.
  55584. */
  55585. getRecord: function(id) {
  55586. if (this.cache[id] === undefined) {
  55587. var recordKey = this.getRecordKey(id),
  55588. item = this.getStorageObject().getItem(recordKey),
  55589. data = {},
  55590. Model = this.getModel(),
  55591. fields = Model.getFields().items,
  55592. length = fields.length,
  55593. i, field, name, record, rawData, dateFormat;
  55594. if (!item) {
  55595. return undefined;
  55596. }
  55597. rawData = Ext.decode(item);
  55598. for (i = 0; i < length; i++) {
  55599. field = fields[i];
  55600. name = field.getName();
  55601. if (typeof field.getDecode() == 'function') {
  55602. data[name] = field.getDecode()(rawData[name]);
  55603. } else {
  55604. if (field.getType().type == 'date') {
  55605. dateFormat = field.getDateFormat();
  55606. if (dateFormat) {
  55607. data[name] = Ext.Date.parse(rawData[name], dateFormat);
  55608. } else {
  55609. data[name] = new Date(rawData[name]);
  55610. }
  55611. } else {
  55612. data[name] = rawData[name];
  55613. }
  55614. }
  55615. }
  55616. record = new Model(data, id);
  55617. this.cache[id] = record;
  55618. }
  55619. return this.cache[id];
  55620. },
  55621. /**
  55622. * Saves the given record in the Proxy. Runs each field's encode function (if present) to encode the data.
  55623. * @param {Ext.data.Model} record The model instance
  55624. * @param {String} [id] The id to save the record under (defaults to the value of the record's getId() function)
  55625. */
  55626. setRecord: function(record, id) {
  55627. if (id) {
  55628. record.setId(id);
  55629. } else {
  55630. id = record.getId();
  55631. }
  55632. var me = this,
  55633. rawData = record.getData(),
  55634. data = {},
  55635. Model = me.getModel(),
  55636. fields = Model.getFields().items,
  55637. length = fields.length,
  55638. i = 0,
  55639. field, name, obj, key, dateFormat;
  55640. for (; i < length; i++) {
  55641. field = fields[i];
  55642. name = field.getName();
  55643. if (field.getPersist() === false) {
  55644. continue;
  55645. }
  55646. if (typeof field.getEncode() == 'function') {
  55647. data[name] = field.getEncode()(rawData[name], record);
  55648. } else {
  55649. if (field.getType().type == 'date' && Ext.isDate(rawData[name])) {
  55650. dateFormat = field.getDateFormat();
  55651. if (dateFormat) {
  55652. data[name] = Ext.Date.format(rawData[name], dateFormat);
  55653. } else {
  55654. data[name] = rawData[name].getTime();
  55655. }
  55656. } else {
  55657. data[name] = rawData[name];
  55658. }
  55659. }
  55660. }
  55661. obj = me.getStorageObject();
  55662. key = me.getRecordKey(id);
  55663. //keep the cache up to date
  55664. me.cache[id] = record;
  55665. //iPad bug requires that we remove the item before setting it
  55666. obj.removeItem(key);
  55667. try {
  55668. obj.setItem(key, Ext.encode(data));
  55669. } catch(e){
  55670. this.fireEvent('exception', this, e);
  55671. }
  55672. record.commit();
  55673. },
  55674. /**
  55675. * @private
  55676. * Physically removes a given record from the local storage. Used internally by {@link #destroy}, which you should
  55677. * use instead because it updates the list of currently-stored record ids
  55678. * @param {String/Number/Ext.data.Model} id The id of the record to remove, or an Ext.data.Model instance
  55679. */
  55680. removeRecord: function(id, updateIds) {
  55681. var me = this,
  55682. ids;
  55683. if (id.isModel) {
  55684. id = id.getId();
  55685. }
  55686. if (updateIds !== false) {
  55687. ids = me.getIds();
  55688. Ext.Array.remove(ids, id);
  55689. me.setIds(ids);
  55690. }
  55691. me.getStorageObject().removeItem(me.getRecordKey(id));
  55692. },
  55693. /**
  55694. * @private
  55695. * Given the id of a record, returns a unique string based on that id and the id of this proxy. This is used when
  55696. * storing data in the local storage object and should prevent naming collisions.
  55697. * @param {String/Number/Ext.data.Model} id The record id, or a Model instance
  55698. * @return {String} The unique key for this record
  55699. */
  55700. getRecordKey: function(id) {
  55701. if (id.isModel) {
  55702. id = id.getId();
  55703. }
  55704. return Ext.String.format("{0}-{1}", this.getId(), id);
  55705. },
  55706. /**
  55707. * @private
  55708. * Returns the array of record IDs stored in this Proxy
  55709. * @return {Number[]} The record IDs. Each is cast as a Number
  55710. */
  55711. getIds: function() {
  55712. var ids = (this.getStorageObject().getItem(this.getId()) || "").split(","),
  55713. length = ids.length,
  55714. i;
  55715. if (length == 1 && ids[0] === "") {
  55716. ids = [];
  55717. }
  55718. return ids;
  55719. },
  55720. /**
  55721. * @private
  55722. * Saves the array of ids representing the set of all records in the Proxy
  55723. * @param {Number[]} ids The ids to set
  55724. */
  55725. setIds: function(ids) {
  55726. var obj = this.getStorageObject(),
  55727. str = ids.join(","),
  55728. id = this.getId();
  55729. obj.removeItem(id);
  55730. if (!Ext.isEmpty(str)) {
  55731. try {
  55732. obj.setItem(id, str);
  55733. } catch(e){
  55734. this.fireEvent('exception', this, e);
  55735. }
  55736. }
  55737. },
  55738. /**
  55739. * @private
  55740. * Sets up the Proxy by claiming the key in the storage object that corresponds to the unique id of this Proxy. Called
  55741. * automatically by the constructor, this should not need to be called again unless {@link #clear} has been called.
  55742. */
  55743. initialize: function() {
  55744. this.callParent(arguments);
  55745. var storageObject = this.getStorageObject();
  55746. try {
  55747. storageObject.setItem(this.getId(), storageObject.getItem(this.getId()) || "");
  55748. } catch(e){
  55749. this.fireEvent('exception', this, e);
  55750. }
  55751. },
  55752. /**
  55753. * Destroys all records stored in the proxy and removes all keys and values used to support the proxy from the
  55754. * storage object.
  55755. */
  55756. clear: function() {
  55757. var obj = this.getStorageObject(),
  55758. ids = this.getIds(),
  55759. len = ids.length,
  55760. i;
  55761. //remove all the records
  55762. for (i = 0; i < len; i++) {
  55763. this.removeRecord(ids[i], false);
  55764. }
  55765. //remove the supporting objects
  55766. obj.removeItem(this.getId());
  55767. },
  55768. /**
  55769. * @private
  55770. * Abstract function which should return the storage object that data will be saved to. This must be implemented
  55771. * in each subclass.
  55772. * @return {Object} The storage object
  55773. */
  55774. getStorageObject: function() {
  55775. //<debug>
  55776. Ext.Logger.error("The getStorageObject function has not been defined in your Ext.data.proxy.WebStorage subclass");
  55777. //</debug>
  55778. }
  55779. });
  55780. /**
  55781. * @author Ed Spencer
  55782. * @aside guide proxies
  55783. *
  55784. * The LocalStorageProxy uses the new HTML5 localStorage API to save {@link Ext.data.Model Model} data locally on the
  55785. * client browser. HTML5 localStorage is a key-value store (e.g. cannot save complex objects like JSON), so
  55786. * LocalStorageProxy automatically serializes and deserializes data when saving and retrieving it.
  55787. *
  55788. * localStorage is extremely useful for saving user-specific information without needing to build server-side
  55789. * infrastructure to support it. Let's imagine we're writing a Twitter search application and want to save the user's
  55790. * searches locally so they can easily perform a saved search again later. We'd start by creating a Search model:
  55791. *
  55792. * Ext.define('Search', {
  55793. * extend: 'Ext.data.Model',
  55794. * config: {
  55795. * fields: ['id', 'query'],
  55796. * proxy: {
  55797. * type: 'localstorage',
  55798. * id : 'twitter-Searches'
  55799. * }
  55800. * }
  55801. * });
  55802. *
  55803. * Our Search model contains just two fields - id and query - plus a Proxy definition. The only configuration we need to
  55804. * pass to the LocalStorage proxy is an {@link #id}. This is important as it separates the Model data in this Proxy from
  55805. * all others. The localStorage API puts all data into a single shared namespace, so by setting an id we enable
  55806. * LocalStorageProxy to manage the saved Search data.
  55807. *
  55808. * Saving our data into localStorage is easy and would usually be done with a {@link Ext.data.Store Store}:
  55809. *
  55810. * //our Store automatically picks up the LocalStorageProxy defined on the Search model
  55811. * var store = Ext.create('Ext.data.Store', {
  55812. * model: "Search"
  55813. * });
  55814. *
  55815. * //loads any existing Search data from localStorage
  55816. * store.load();
  55817. *
  55818. * //now add some Searches
  55819. * store.add({query: 'Sencha Touch'});
  55820. * store.add({query: 'Ext JS'});
  55821. *
  55822. * //finally, save our Search data to localStorage
  55823. * store.sync();
  55824. *
  55825. * The LocalStorageProxy automatically gives our new Searches an id when we call store.sync(). It encodes the Model data
  55826. * and places it into localStorage. We can also save directly to localStorage, bypassing the Store altogether:
  55827. *
  55828. * var search = Ext.create('Search', {query: 'Sencha Animator'});
  55829. *
  55830. * //uses the configured LocalStorageProxy to save the new Search to localStorage
  55831. * search.save();
  55832. *
  55833. * # Limitations
  55834. *
  55835. * If this proxy is used in a browser where local storage is not supported, the constructor will throw an error. A local
  55836. * storage proxy requires a unique ID which is used as a key in which all record data are stored in the local storage
  55837. * object.
  55838. *
  55839. * It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided but the
  55840. * attached store has a storeId, the storeId will be used. If neither option is presented the proxy will throw an error.
  55841. */
  55842. Ext.define('Ext.data.proxy.LocalStorage', {
  55843. extend: 'Ext.data.proxy.WebStorage',
  55844. alias: 'proxy.localstorage',
  55845. alternateClassName: 'Ext.data.LocalStorageProxy',
  55846. //inherit docs
  55847. getStorageObject: function() {
  55848. return window.localStorage;
  55849. }
  55850. });
  55851. /**
  55852. * @author Ed Spencer
  55853. * @aside guide proxies
  55854. *
  55855. * The Rest proxy is a specialization of the {@link Ext.data.proxy.Ajax AjaxProxy} which simply maps the four actions
  55856. * (create, read, update and destroy) to RESTful HTTP verbs. For example, let's set up a {@link Ext.data.Model Model}
  55857. * with an inline Rest proxy:
  55858. *
  55859. * Ext.define('User', {
  55860. * extend: 'Ext.data.Model',
  55861. * config: {
  55862. * fields: ['id', 'name', 'email'],
  55863. *
  55864. * proxy: {
  55865. * type: 'rest',
  55866. * url : '/users'
  55867. * }
  55868. * }
  55869. * });
  55870. *
  55871. * Now we can create a new User instance and save it via the Rest proxy. Doing this will cause the Proxy to send a POST
  55872. * request to '/users':
  55873. *
  55874. * var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
  55875. *
  55876. * user.save(); //POST /users
  55877. *
  55878. * Let's expand this a little and provide a callback for the {@link Ext.data.Model#save} call to update the Model once
  55879. * it has been created. We'll assume the creation went successfully and that the server gave this user an ID of 123:
  55880. *
  55881. * user.save({
  55882. * success: function(user) {
  55883. * user.set('name', 'Khan Noonien Singh');
  55884. *
  55885. * user.save(); //PUT /users/123
  55886. * }
  55887. * });
  55888. *
  55889. * Now that we're no longer creating a new Model instance, the request method is changed to an HTTP PUT, targeting the
  55890. * relevant url for that user. Now let's delete this user, which will use the DELETE method:
  55891. *
  55892. * user.erase(); //DELETE /users/123
  55893. *
  55894. * Finally, when we perform a load of a Model or Store, Rest proxy will use the GET method:
  55895. *
  55896. * //1. Load via Store
  55897. *
  55898. * //the Store automatically picks up the Proxy from the User model
  55899. * var store = Ext.create('Ext.data.Store', {
  55900. * model: 'User'
  55901. * });
  55902. *
  55903. * store.load(); //GET /users
  55904. *
  55905. * //2. Load directly from the Model
  55906. *
  55907. * //GET /users/123
  55908. * Ext.ModelManager.getModel('User').load(123, {
  55909. * success: function(user) {
  55910. * console.log(user.getId()); //outputs 123
  55911. * }
  55912. * });
  55913. *
  55914. * # Url generation
  55915. *
  55916. * The Rest proxy is able to automatically generate the urls above based on two configuration options - {@link #appendId} and
  55917. * {@link #format}. If appendId is true (it is by default) then Rest proxy will automatically append the ID of the Model
  55918. * instance in question to the configured url, resulting in the '/users/123' that we saw above.
  55919. *
  55920. * If the request is not for a specific Model instance (e.g. loading a Store), the url is not appended with an id.
  55921. * The Rest proxy will automatically insert a '/' before the ID if one is not already present.
  55922. *
  55923. * new Ext.data.proxy.Rest({
  55924. * url: '/users',
  55925. * appendId: true //default
  55926. * });
  55927. *
  55928. * // Collection url: /users
  55929. * // Instance url : /users/123
  55930. *
  55931. * The Rest proxy can also optionally append a format string to the end of any generated url:
  55932. *
  55933. * new Ext.data.proxy.Rest({
  55934. * url: '/users',
  55935. * format: 'json'
  55936. * });
  55937. *
  55938. * // Collection url: /users.json
  55939. * // Instance url : /users/123.json
  55940. *
  55941. * If further customization is needed, simply implement the {@link #buildUrl} method and add your custom generated url
  55942. * onto the {@link Ext.data.Request Request} object that is passed to buildUrl. See [Rest proxy's implementation][1] for
  55943. * an example of how to achieve this.
  55944. *
  55945. * Note that Rest proxy inherits from {@link Ext.data.proxy.Ajax AjaxProxy}, which already injects all of the sorter,
  55946. * filter, group and paging options into the generated url. See the {@link Ext.data.proxy.Ajax AjaxProxy docs} for more
  55947. * details.
  55948. *
  55949. * [1]: source/Rest.html#Ext-data-proxy-Rest-method-buildUrl
  55950. */
  55951. Ext.define('Ext.data.proxy.Rest', {
  55952. extend: 'Ext.data.proxy.Ajax',
  55953. alternateClassName: 'Ext.data.RestProxy',
  55954. alias : 'proxy.rest',
  55955. config: {
  55956. /**
  55957. * @cfg {Boolean} appendId
  55958. * `true` to automatically append the ID of a Model instance when performing a request based on that single instance.
  55959. * See Rest proxy intro docs for more details.
  55960. */
  55961. appendId: true,
  55962. /**
  55963. * @cfg {String} format
  55964. * Optional data format to send to the server when making any request (e.g. 'json'). See the Rest proxy intro docs
  55965. * for full details.
  55966. */
  55967. format: null,
  55968. /**
  55969. * @cfg {Boolean} batchActions
  55970. * `true` to batch actions of a particular type when synchronizing the store.
  55971. */
  55972. batchActions: false,
  55973. actionMethods: {
  55974. create : 'POST',
  55975. read : 'GET',
  55976. update : 'PUT',
  55977. destroy: 'DELETE'
  55978. }
  55979. },
  55980. /**
  55981. * Specialized version of `buildUrl` that incorporates the {@link #appendId} and {@link #format} options into the
  55982. * generated url. Override this to provide further customizations, but remember to call the superclass `buildUrl` so
  55983. * that additional parameters like the cache buster string are appended.
  55984. * @param {Object} request
  55985. * @return {Object}
  55986. */
  55987. buildUrl: function(request) {
  55988. var me = this,
  55989. operation = request.getOperation(),
  55990. records = operation.getRecords() || [],
  55991. record = records[0],
  55992. model = me.getModel(),
  55993. idProperty= model.getIdProperty(),
  55994. format = me.getFormat(),
  55995. url = me.getUrl(request),
  55996. params = request.getParams() || {},
  55997. id = (record && !record.phantom) ? record.getId() : params[idProperty];
  55998. if (me.getAppendId() && id) {
  55999. if (!url.match(/\/$/)) {
  56000. url += '/';
  56001. }
  56002. url += id;
  56003. delete params[idProperty];
  56004. }
  56005. if (format) {
  56006. if (!url.match(/\.$/)) {
  56007. url += '.';
  56008. }
  56009. url += format;
  56010. }
  56011. request.setUrl(url);
  56012. return me.callParent([request]);
  56013. }
  56014. });
  56015. /**
  56016. * SQL proxy.
  56017. */
  56018. Ext.define('Ext.data.proxy.SQL', {
  56019. alias: 'proxy.sql',
  56020. extend: 'Ext.data.proxy.Client',
  56021. config: {
  56022. /**
  56023. * @cfg {Object} reader
  56024. * @hide
  56025. */
  56026. reader: null,
  56027. /**
  56028. * @cfg {Object} writer
  56029. * @hide
  56030. */
  56031. writer: null,
  56032. table: null,
  56033. database: 'Sencha',
  56034. columns: '',
  56035. uniqueIdStrategy: false,
  56036. tableExists: false,
  56037. defaultDateFormat: 'Y-m-d H:i:s.u'
  56038. },
  56039. updateModel: function(model) {
  56040. if (model && !this.getTable()) {
  56041. var modelName = model.modelName,
  56042. defaultDateFormat = this.getDefaultDateFormat(),
  56043. table = modelName.slice(modelName.lastIndexOf('.') + 1);
  56044. model.getFields().each(function (field) {
  56045. if (field.getType().type === 'date' && !field.getDateFormat()) {
  56046. field.setDateFormat(defaultDateFormat);
  56047. }
  56048. });
  56049. this.setUniqueIdStrategy(model.getIdentifier().isUnique);
  56050. this.setTable(table);
  56051. this.setColumns(this.getPersistedModelColumns(model));
  56052. }
  56053. this.callParent(arguments);
  56054. },
  56055. create: function (operation, callback, scope) {
  56056. var me = this,
  56057. db = me.getDatabaseObject(),
  56058. records = operation.getRecords(),
  56059. tableExists = me.getTableExists();
  56060. operation.setStarted();
  56061. db.transaction(function(transaction) {
  56062. if (!tableExists) {
  56063. me.createTable(transaction);
  56064. }
  56065. me.insertRecords(records, transaction, function(resultSet, errors) {
  56066. if (operation.process(operation.getAction(), resultSet) === false) {
  56067. me.fireEvent('exception', this, operation);
  56068. }
  56069. if (typeof callback == 'function') {
  56070. callback.call(scope || this, operation);
  56071. }
  56072. }, this);
  56073. });
  56074. },
  56075. read: function(operation, callback, scope) {
  56076. var me = this,
  56077. db = me.getDatabaseObject(),
  56078. model = me.getModel(),
  56079. idProperty = model.getIdProperty(),
  56080. tableExists = me.getTableExists(),
  56081. params = operation.getParams() || {},
  56082. id = params[idProperty],
  56083. sorters = operation.getSorters(),
  56084. filters = operation.getFilters(),
  56085. page = operation.getPage(),
  56086. start = operation.getStart(),
  56087. limit = operation.getLimit(),
  56088. filtered, i, ln;
  56089. params = Ext.apply(params, {
  56090. page: page,
  56091. start: start,
  56092. limit: limit,
  56093. sorters: sorters,
  56094. filters: filters
  56095. });
  56096. operation.setStarted();
  56097. db.transaction(function(transaction) {
  56098. if (!tableExists) {
  56099. me.createTable(transaction);
  56100. }
  56101. me.selectRecords(transaction, id !== undefined ? id : params, function (resultSet, errors) {
  56102. if (operation.process(operation.getAction(), resultSet) === false) {
  56103. me.fireEvent('exception', me, operation);
  56104. }
  56105. if (filters.length) {
  56106. filtered = Ext.create('Ext.util.Collection', function(record) {
  56107. return record.getId();
  56108. });
  56109. filtered.setFilterRoot('data');
  56110. for (i = 0, ln = filters.length; i < ln; i++) {
  56111. if (filters[i].getProperty() === null) {
  56112. filtered.addFilter(filters[i]);
  56113. }
  56114. }
  56115. filtered.addAll(operation.getRecords());
  56116. operation.setRecords(filtered.items.slice());
  56117. resultSet.setRecords(operation.getRecords());
  56118. resultSet.setCount(filtered.items.length);
  56119. resultSet.setTotal(filtered.items.length);
  56120. }
  56121. if (typeof callback == 'function') {
  56122. callback.call(scope || me, operation);
  56123. }
  56124. });
  56125. });
  56126. },
  56127. update: function(operation, callback, scope) {
  56128. var me = this,
  56129. records = operation.getRecords(),
  56130. db = me.getDatabaseObject(),
  56131. tableExists = me.getTableExists();
  56132. operation.setStarted();
  56133. db.transaction(function (transaction) {
  56134. if (!tableExists) {
  56135. me.createTable(transaction);
  56136. }
  56137. me.updateRecords(transaction, records, function(resultSet, errors) {
  56138. if (operation.process(operation.getAction(), resultSet) === false) {
  56139. me.fireEvent('exception', me, operation);
  56140. }
  56141. if (typeof callback == 'function') {
  56142. callback.call(scope || me, operation);
  56143. }
  56144. });
  56145. });
  56146. },
  56147. destroy: function(operation, callback, scope) {
  56148. var me = this,
  56149. records = operation.getRecords(),
  56150. db = me.getDatabaseObject(),
  56151. tableExists = me.getTableExists();
  56152. operation.setStarted();
  56153. db.transaction(function(transaction) {
  56154. if (!tableExists) {
  56155. me.createTable(transaction);
  56156. }
  56157. me.destroyRecords(transaction, records, function(resultSet, errors) {
  56158. if (operation.process(operation.getAction(), resultSet) === false) {
  56159. me.fireEvent('exception', me, operation);
  56160. }
  56161. if (typeof callback == 'function') {
  56162. callback.call(scope || me, operation);
  56163. }
  56164. });
  56165. });
  56166. },
  56167. createTable: function (transaction) {
  56168. transaction.executeSql('CREATE TABLE IF NOT EXISTS ' + this.getTable() + ' (' + this.getSchemaString() + ')');
  56169. this.setTableExists(true);
  56170. },
  56171. insertRecords: function(records, transaction, callback, scope) {
  56172. var me = this,
  56173. table = me.getTable(),
  56174. columns = me.getColumns(),
  56175. totalRecords = records.length,
  56176. executed = 0,
  56177. tmp = [],
  56178. insertedRecords = [],
  56179. errors = [],
  56180. uniqueIdStrategy = me.getUniqueIdStrategy(),
  56181. i, ln, placeholders, result;
  56182. result = new Ext.data.ResultSet({
  56183. records: insertedRecords,
  56184. success: true
  56185. });
  56186. for (i = 0, ln = columns.length; i < ln; i++) {
  56187. tmp.push('?');
  56188. }
  56189. placeholders = tmp.join(', ');
  56190. Ext.each(records, function (record) {
  56191. var id = record.getId(),
  56192. data = me.getRecordData(record),
  56193. values = me.getColumnValues(columns, data);
  56194. transaction.executeSql(
  56195. 'INSERT INTO ' + table + ' (' + columns.join(', ') + ') VALUES (' + placeholders + ')', values,
  56196. function (transaction, resultSet) {
  56197. executed++;
  56198. insertedRecords.push({
  56199. clientId: id,
  56200. id: uniqueIdStrategy ? id : resultSet.insertId,
  56201. data: data,
  56202. node: data
  56203. });
  56204. if (executed === totalRecords && typeof callback == 'function') {
  56205. callback.call(scope || me, result, errors);
  56206. }
  56207. },
  56208. function (transaction, error) {
  56209. executed++;
  56210. errors.push({
  56211. clientId: id,
  56212. error: error
  56213. });
  56214. if (executed === totalRecords && typeof callback == 'function') {
  56215. callback.call(scope || me, result, errors);
  56216. }
  56217. }
  56218. );
  56219. });
  56220. },
  56221. selectRecords: function(transaction, params, callback, scope) {
  56222. var me = this,
  56223. table = me.getTable(),
  56224. idProperty = me.getModel().getIdProperty(),
  56225. sql = 'SELECT * FROM ' + table,
  56226. records = [],
  56227. filterStatement = ' WHERE ',
  56228. sortStatement = ' ORDER BY ',
  56229. i, ln, data, result, count, rows, filter, sorter, property, value;
  56230. result = new Ext.data.ResultSet({
  56231. records: records,
  56232. success: true
  56233. });
  56234. if (!Ext.isObject(params)) {
  56235. sql += filterStatement + idProperty + ' = ' + params;
  56236. } else {
  56237. ln = params.filters && params.filters.length;
  56238. if (ln) {
  56239. for (i = 0; i < ln; i++) {
  56240. filter = params.filters[i];
  56241. property = filter.getProperty();
  56242. value = filter.getValue();
  56243. if (property !== null) {
  56244. sql += filterStatement + property + ' ' + (filter.getAnyMatch() ? ('LIKE \'%' + value + '%\'') : ('= \'' + value + '\''));
  56245. filterStatement = ' AND ';
  56246. }
  56247. }
  56248. }
  56249. ln = params.sorters.length;
  56250. if (ln) {
  56251. for (i = 0; i < ln; i++) {
  56252. sorter = params.sorters[i];
  56253. property = sorter.getProperty();
  56254. if (property !== null) {
  56255. sql += sortStatement + property + ' ' + sorter.getDirection();
  56256. sortStatement = ', ';
  56257. }
  56258. }
  56259. }
  56260. // handle start, limit, sort, filter and group params
  56261. if (params.page !== undefined) {
  56262. sql += ' LIMIT ' + parseInt(params.start, 10) + ', ' + parseInt(params.limit, 10);
  56263. }
  56264. }
  56265. transaction.executeSql(sql, null,
  56266. function(transaction, resultSet) {
  56267. rows = resultSet.rows;
  56268. count = rows.length;
  56269. for (i = 0, ln = count; i < ln; i++) {
  56270. data = rows.item(i);
  56271. records.push({
  56272. clientId: null,
  56273. id: data[idProperty],
  56274. data: data,
  56275. node: data
  56276. });
  56277. }
  56278. result.setSuccess(true);
  56279. result.setTotal(count);
  56280. result.setCount(count);
  56281. if (typeof callback == 'function') {
  56282. callback.call(scope || me, result)
  56283. }
  56284. },
  56285. function(transaction, errors) {
  56286. result.setSuccess(false);
  56287. result.setTotal(0);
  56288. result.setCount(0);
  56289. if (typeof callback == 'function') {
  56290. callback.call(scope || me, result)
  56291. }
  56292. }
  56293. );
  56294. },
  56295. updateRecords: function (transaction, records, callback, scope) {
  56296. var me = this,
  56297. table = me.getTable(),
  56298. columns = me.getColumns(),
  56299. totalRecords = records.length,
  56300. idProperty = me.getModel().getIdProperty(),
  56301. executed = 0,
  56302. updatedRecords = [],
  56303. errors = [],
  56304. i, ln, result;
  56305. result = new Ext.data.ResultSet({
  56306. records: updatedRecords,
  56307. success: true
  56308. });
  56309. Ext.each(records, function (record) {
  56310. var id = record.getId(),
  56311. data = me.getRecordData(record),
  56312. values = me.getColumnValues(columns, data),
  56313. updates = [];
  56314. for (i = 0, ln = columns.length; i < ln; i++) {
  56315. updates.push(columns[i] + ' = ?');
  56316. }
  56317. transaction.executeSql(
  56318. 'UPDATE ' + table + ' SET ' + updates.join(', ') + ' WHERE ' + idProperty + ' = ?', values.concat(id),
  56319. function (transaction, resultSet) {
  56320. executed++;
  56321. updatedRecords.push({
  56322. clientId: id,
  56323. id: id,
  56324. data: data,
  56325. node: data
  56326. });
  56327. if (executed === totalRecords && typeof callback == 'function') {
  56328. callback.call(scope || me, result, errors);
  56329. }
  56330. },
  56331. function (transaction, error) {
  56332. executed++;
  56333. errors.push({
  56334. clientId: id,
  56335. error: error
  56336. });
  56337. if (executed === totalRecords && typeof callback == 'function') {
  56338. callback.call(scope || me, result, errors);
  56339. }
  56340. }
  56341. );
  56342. });
  56343. },
  56344. destroyRecords: function (transaction, records, callback, scope) {
  56345. var me = this,
  56346. table = me.getTable(),
  56347. idProperty = me.getModel().getIdProperty(),
  56348. ids = [],
  56349. values = [],
  56350. destroyedRecords = [],
  56351. i, ln, result, record;
  56352. for (i = 0, ln = records.length; i < ln; i++) {
  56353. ids.push(idProperty + ' = ?');
  56354. values.push(records[i].getId());
  56355. }
  56356. result = new Ext.data.ResultSet({
  56357. records: destroyedRecords,
  56358. success: true
  56359. });
  56360. transaction.executeSql(
  56361. 'DELETE FROM ' + table + ' WHERE ' + ids.join(' OR '), values,
  56362. function (transaction, resultSet) {
  56363. for (i = 0, ln = records.length; i < ln; i++) {
  56364. record = records[i];
  56365. destroyedRecords.push({
  56366. id: record.getId()
  56367. });
  56368. }
  56369. if (typeof callback == 'function') {
  56370. callback.call(scope || me, result);
  56371. }
  56372. },
  56373. function (transaction, error) {
  56374. if (typeof callback == 'function') {
  56375. callback.call(scope || me, result);
  56376. }
  56377. }
  56378. );
  56379. },
  56380. /**
  56381. * Formats the data for each record before sending it to the server. This
  56382. * method should be overridden to format the data in a way that differs from the default.
  56383. * @param {Object} record The record that we are writing to the server.
  56384. * @return {Object} An object literal of name/value keys to be written to the server.
  56385. * By default this method returns the data property on the record.
  56386. */
  56387. getRecordData: function (record) {
  56388. var me = this,
  56389. fields = record.getFields(),
  56390. idProperty = record.getIdProperty(),
  56391. uniqueIdStrategy = me.getUniqueIdStrategy(),
  56392. data = {},
  56393. name, value;
  56394. fields.each(function (field) {
  56395. if (field.getPersist()) {
  56396. name = field.getName();
  56397. if (name === idProperty && !uniqueIdStrategy) {
  56398. return;
  56399. }
  56400. value = record.get(name);
  56401. if (field.getType().type == 'date') {
  56402. value = me.writeDate(field, value);
  56403. }
  56404. data[name] = value;
  56405. }
  56406. }, this);
  56407. return data;
  56408. },
  56409. getColumnValues: function(columns, data) {
  56410. var ln = columns.length,
  56411. values = [],
  56412. i, column, value;
  56413. for (i = 0; i < ln; i++) {
  56414. column = columns[i];
  56415. value = data[column];
  56416. if (value !== undefined) {
  56417. values.push(value);
  56418. }
  56419. }
  56420. return values;
  56421. },
  56422. getSchemaString: function() {
  56423. var me = this,
  56424. schema = [],
  56425. model = me.getModel(),
  56426. idProperty = model.getIdProperty(),
  56427. fields = model.getFields().items,
  56428. uniqueIdStrategy = me.getUniqueIdStrategy(),
  56429. ln = fields.length,
  56430. i, field, type, name;
  56431. for (i = 0; i < ln; i++) {
  56432. field = fields[i];
  56433. type = field.getType().type;
  56434. name = field.getName();
  56435. if (name === idProperty) {
  56436. if (uniqueIdStrategy) {
  56437. type = me.convertToSqlType(type);
  56438. schema.unshift(idProperty + ' ' + type);
  56439. } else {
  56440. schema.unshift(idProperty + ' INTEGER PRIMARY KEY AUTOINCREMENT');
  56441. }
  56442. } else {
  56443. type = me.convertToSqlType(type);
  56444. schema.push(name + ' ' + type);
  56445. }
  56446. }
  56447. return schema.join(', ');
  56448. },
  56449. getPersistedModelColumns: function(model) {
  56450. var fields = model.getFields().items,
  56451. uniqueIdStrategy = this.getUniqueIdStrategy(),
  56452. idProperty = model.getIdProperty(),
  56453. columns = [],
  56454. ln = fields.length,
  56455. i, field, name;
  56456. for (i = 0; i < ln; i++) {
  56457. field = fields[i];
  56458. name = field.getName();
  56459. if (name === idProperty && !uniqueIdStrategy) {
  56460. continue;
  56461. }
  56462. if (field.getPersist()) {
  56463. columns.push(field.getName());
  56464. }
  56465. }
  56466. return columns;
  56467. },
  56468. convertToSqlType: function(type) {
  56469. switch (type.toLowerCase()) {
  56470. case 'date':
  56471. case 'string':
  56472. case 'auto':
  56473. return 'TEXT';
  56474. case 'int':
  56475. return 'INTEGER';
  56476. case 'float':
  56477. return 'REAL';
  56478. case 'bool':
  56479. return 'NUMERIC'
  56480. }
  56481. },
  56482. writeDate: function (field, date) {
  56483. var dateFormat = field.getDateFormat() || this.getDefaultDateFormat();
  56484. switch (dateFormat) {
  56485. case 'timestamp':
  56486. return date.getTime() / 1000;
  56487. case 'time':
  56488. return date.getTime();
  56489. default:
  56490. return Ext.Date.format(date, dateFormat);
  56491. }
  56492. },
  56493. dropTable: function() {
  56494. var me = this,
  56495. table = me.getTable(),
  56496. db = me.getDatabaseObject();
  56497. db.transaction(function(transaction) {
  56498. transaction.executeSql('DROP TABLE ' + table);
  56499. });
  56500. me.setTableExists(false);
  56501. },
  56502. getDatabaseObject: function() {
  56503. return openDatabase(this.getDatabase(), '1.0', 'Sencha Database', 5 * 1024 * 1024);
  56504. }
  56505. });
  56506. /**
  56507. * @author Ed Spencer
  56508. * @aside guide proxies
  56509. *
  56510. * Proxy which uses HTML5 session storage as its data storage/retrieval mechanism. If this proxy is used in a browser
  56511. * where session storage is not supported, the constructor will throw an error. A session storage proxy requires a
  56512. * unique ID which is used as a key in which all record data are stored in the session storage object.
  56513. *
  56514. * It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided but the
  56515. * attached store has a storeId, the storeId will be used. If neither option is presented the proxy will throw an error.
  56516. *
  56517. * Proxies are almost always used with a {@link Ext.data.Store store}:
  56518. *
  56519. * new Ext.data.Store({
  56520. * proxy: {
  56521. * type: 'sessionstorage',
  56522. * id : 'myProxyKey'
  56523. * }
  56524. * });
  56525. *
  56526. * Alternatively you can instantiate the Proxy directly:
  56527. *
  56528. * new Ext.data.proxy.SessionStorage({
  56529. * id : 'myOtherProxyKey'
  56530. * });
  56531. *
  56532. * Note that session storage is different to local storage (see {@link Ext.data.proxy.LocalStorage}) - if a browser
  56533. * session is ended (e.g. by closing the browser) then all data in a SessionStorageProxy are lost. Browser restarts
  56534. * don't affect the {@link Ext.data.proxy.LocalStorage} - the data are preserved.
  56535. */
  56536. Ext.define('Ext.data.proxy.SessionStorage', {
  56537. extend: 'Ext.data.proxy.WebStorage',
  56538. alias: 'proxy.sessionstorage',
  56539. alternateClassName: 'Ext.data.SessionStorageProxy',
  56540. //inherit docs
  56541. getStorageObject: function() {
  56542. return window.sessionStorage;
  56543. }
  56544. });
  56545. /**
  56546. * @author Ed Spencer
  56547. * @class Ext.data.reader.Xml
  56548. * @extends Ext.data.reader.Reader
  56549. *
  56550. * The XML Reader is used by a Proxy to read a server response that is sent back in XML format. This usually
  56551. * happens as a result of loading a Store - for example we might create something like this:
  56552. *
  56553. * Ext.define('User', {
  56554. * extend: 'Ext.data.Model',
  56555. * config: {
  56556. * fields: ['id', 'name', 'email']
  56557. * }
  56558. * });
  56559. *
  56560. * var store = Ext.create('Ext.data.Store', {
  56561. * model: 'User',
  56562. * proxy: {
  56563. * type: 'ajax',
  56564. * url : 'users.xml',
  56565. * reader: {
  56566. * type: 'xml',
  56567. * record: 'user'
  56568. * }
  56569. * }
  56570. * });
  56571. *
  56572. * The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
  56573. * not already familiar with them.
  56574. *
  56575. * We created the simplest type of XML Reader possible by simply telling our {@link Ext.data.Store Store}'s
  56576. * {@link Ext.data.proxy.Proxy Proxy} that we want a XML Reader. The Store automatically passes the configured model to the
  56577. * Store, so it is as if we passed this instead:
  56578. *
  56579. * reader: {
  56580. * type : 'xml',
  56581. * model: 'User',
  56582. * record: 'user'
  56583. * }
  56584. *
  56585. * The reader we set up is ready to read data from our server - at the moment it will accept a response like this:
  56586. *
  56587. * <?xml version="1.0" encoding="UTF-8"?>
  56588. * <user>
  56589. * <id>1</id>
  56590. * <name>Ed Spencer</name>
  56591. * <email>ed@sencha.com</email>
  56592. * </user>
  56593. * <user>
  56594. * <id>2</id>
  56595. * <name>Abe Elias</name>
  56596. * <email>abe@sencha.com</email>
  56597. * </user>
  56598. *
  56599. * The XML Reader uses the configured {@link #record} option to pull out the data for each record - in this case we
  56600. * set record to 'user', so each `<user>` above will be converted into a User model.
  56601. *
  56602. * ## Reading other XML formats
  56603. *
  56604. * If you already have your XML format defined and it doesn't look quite like what we have above, you can usually
  56605. * pass XmlReader a couple of configuration options to make it parse your format. For example, we can use the
  56606. * {@link #rootProperty} configuration to parse data that comes back like this:
  56607. *
  56608. * <?xml version="1.0" encoding="UTF-8"?>
  56609. * <users>
  56610. * <user>
  56611. * <id>1</id>
  56612. * <name>Ed Spencer</name>
  56613. * <email>ed@sencha.com</email>
  56614. * </user>
  56615. * <user>
  56616. * <id>2</id>
  56617. * <name>Abe Elias</name>
  56618. * <email>abe@sencha.com</email>
  56619. * </user>
  56620. * </users>
  56621. *
  56622. * To parse this we just pass in a {@link #rootProperty} configuration that matches the 'users' above:
  56623. *
  56624. * reader: {
  56625. * type: 'xml',
  56626. * record: 'user',
  56627. * rootProperty: 'users'
  56628. * }
  56629. *
  56630. * Note that XmlReader doesn't care whether your {@link #rootProperty} and {@link #record} elements are nested deep
  56631. * inside a larger structure, so a response like this will still work:
  56632. *
  56633. * <?xml version="1.0" encoding="UTF-8"?>
  56634. * <deeply>
  56635. * <nested>
  56636. * <xml>
  56637. * <users>
  56638. * <user>
  56639. * <id>1</id>
  56640. * <name>Ed Spencer</name>
  56641. * <email>ed@sencha.com</email>
  56642. * </user>
  56643. * <user>
  56644. * <id>2</id>
  56645. * <name>Abe Elias</name>
  56646. * <email>abe@sencha.com</email>
  56647. * </user>
  56648. * </users>
  56649. * </xml>
  56650. * </nested>
  56651. * </deeply>
  56652. *
  56653. * ## Response metadata
  56654. *
  56655. * The server can return additional data in its response, such as the {@link #totalProperty total number of records}
  56656. * and the {@link #successProperty success status of the response}. These are typically included in the XML response
  56657. * like this:
  56658. *
  56659. * <?xml version="1.0" encoding="UTF-8"?>
  56660. * <total>100</total>
  56661. * <success>true</success>
  56662. * <users>
  56663. * <user>
  56664. * <id>1</id>
  56665. * <name>Ed Spencer</name>
  56666. * <email>ed@sencha.com</email>
  56667. * </user>
  56668. * <user>
  56669. * <id>2</id>
  56670. * <name>Abe Elias</name>
  56671. * <email>abe@sencha.com</email>
  56672. * </user>
  56673. * </users>
  56674. *
  56675. * If these properties are present in the XML response they can be parsed out by the XmlReader and used by the
  56676. * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration
  56677. * options:
  56678. *
  56679. * reader: {
  56680. * type: 'xml',
  56681. * rootProperty: 'users',
  56682. * totalProperty : 'total',
  56683. * successProperty: 'success'
  56684. * }
  56685. *
  56686. * These final options are not necessary to make the Reader work, but can be useful when the server needs to report
  56687. * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
  56688. * returned.
  56689. *
  56690. * ## Response format
  56691. *
  56692. * __Note:__ In order for the browser to parse a returned XML document, the Content-Type header in the HTTP
  56693. * response must be set to "text/xml" or "application/xml". This is very important - the XmlReader will not
  56694. * work correctly otherwise.
  56695. */
  56696. Ext.define('Ext.data.reader.Xml', {
  56697. extend: 'Ext.data.reader.Reader',
  56698. alternateClassName: 'Ext.data.XmlReader',
  56699. alias : 'reader.xml',
  56700. config: {
  56701. /**
  56702. * @cfg {String} record The DomQuery path to the repeated element which contains record information.
  56703. */
  56704. record: null
  56705. },
  56706. /**
  56707. * @private
  56708. * Creates a function to return some particular key of data from a response. The {@link #totalProperty} and
  56709. * {@link #successProperty} are treated as special cases for type casting, everything else is just a simple selector.
  56710. * @param {String} expr
  56711. * @return {Function}
  56712. */
  56713. createAccessor: function(expr) {
  56714. var me = this;
  56715. if (Ext.isEmpty(expr)) {
  56716. return Ext.emptyFn;
  56717. }
  56718. if (Ext.isFunction(expr)) {
  56719. return expr;
  56720. }
  56721. return function(root) {
  56722. return me.getNodeValue(Ext.DomQuery.selectNode(expr, root));
  56723. };
  56724. },
  56725. getNodeValue: function(node) {
  56726. if (node && node.firstChild) {
  56727. return node.firstChild.nodeValue;
  56728. }
  56729. return undefined;
  56730. },
  56731. //inherit docs
  56732. getResponseData: function(response) {
  56733. // Check to see if the response is already an xml node.
  56734. if (response.nodeType === 1 || response.nodeType === 9) {
  56735. return response;
  56736. }
  56737. var xml = response.responseXML;
  56738. //<debug>
  56739. if (!xml) {
  56740. /**
  56741. * @event exception Fires whenever the reader is unable to parse a response.
  56742. * @param {Ext.data.reader.Xml} reader A reference to this reader.
  56743. * @param {XMLHttpRequest} response The XMLHttpRequest response object.
  56744. * @param {String} error The error message.
  56745. */
  56746. this.fireEvent('exception', this, response, 'XML data not found in the response');
  56747. Ext.Logger.warn('XML data not found in the response');
  56748. }
  56749. //</debug>
  56750. return xml;
  56751. },
  56752. /**
  56753. * Normalizes the data object.
  56754. * @param {Object} data The raw data object.
  56755. * @return {Object} Returns the `documentElement` property of the data object if present, or the same object if not.
  56756. */
  56757. getData: function(data) {
  56758. return data.documentElement || data;
  56759. },
  56760. /**
  56761. * @private
  56762. * Given an XML object, returns the Element that represents the root as configured by the Reader's meta data.
  56763. * @param {Object} data The XML data object.
  56764. * @return {XMLElement} The root node element.
  56765. */
  56766. getRoot: function(data) {
  56767. var nodeName = data.nodeName,
  56768. root = this.getRootProperty();
  56769. if (!root || (nodeName && nodeName == root)) {
  56770. return data;
  56771. } else if (Ext.DomQuery.isXml(data)) {
  56772. // This fix ensures we have XML data
  56773. // Related to TreeStore calling getRoot with the root node, which isn't XML
  56774. // Probably should be resolved in TreeStore at some point
  56775. return Ext.DomQuery.selectNode(root, data);
  56776. }
  56777. },
  56778. /**
  56779. * @private
  56780. * We're just preparing the data for the superclass by pulling out the record nodes we want.
  56781. * @param {XMLElement} root The XML root node.
  56782. * @return {Ext.data.Model[]} The records.
  56783. */
  56784. extractData: function(root) {
  56785. var recordName = this.getRecord();
  56786. //<debug>
  56787. if (!recordName) {
  56788. Ext.Logger.error('Record is a required parameter');
  56789. }
  56790. //</debug>
  56791. if (recordName != root.nodeName && recordName !== root.localName) {
  56792. root = Ext.DomQuery.select(recordName, root);
  56793. } else {
  56794. root = [root];
  56795. }
  56796. return this.callParent([root]);
  56797. },
  56798. /**
  56799. * @private
  56800. * See {@link Ext.data.reader.Reader#getAssociatedDataRoot} docs.
  56801. * @param {Object} data The raw data object.
  56802. * @param {String} associationName The name of the association to get data for (uses {@link Ext.data.association.Association#associationKey} if present).
  56803. * @return {XMLElement} The root.
  56804. */
  56805. getAssociatedDataRoot: function(data, associationName) {
  56806. return Ext.DomQuery.select(associationName, data)[0];
  56807. },
  56808. /**
  56809. * Parses an XML document and returns a ResultSet containing the model instances.
  56810. * @param {Object} doc Parsed XML document.
  56811. * @return {Ext.data.ResultSet} The parsed result set.
  56812. */
  56813. readRecords: function(doc) {
  56814. //it's possible that we get passed an array here by associations. Make sure we strip that out (see Ext.data.reader.Reader#readAssociated)
  56815. if (Ext.isArray(doc)) {
  56816. doc = doc[0];
  56817. }
  56818. return this.callParent([doc]);
  56819. },
  56820. /**
  56821. * @private
  56822. * Returns an accessor expression for the passed Field from an XML element using either the Field's mapping, or
  56823. * its ordinal position in the fields collection as the index.
  56824. *
  56825. * This is used by `buildExtractors` to create optimized on extractor function which converts raw data into model instances.
  56826. */
  56827. createFieldAccessExpression: function(field, fieldVarName, dataName) {
  56828. var selector = field.getMapping() || field.getName(),
  56829. result;
  56830. if (typeof selector === 'function') {
  56831. result = fieldVarName + '.getMapping()(' + dataName + ', this)';
  56832. } else {
  56833. selector = selector.split('@');
  56834. if (selector.length === 2 && selector[0]) {
  56835. result = 'me.getNodeValue(Ext.DomQuery.selectNode("@' + selector[1] + '", Ext.DomQuery.selectNode("' + selector[0] + '", ' + dataName + ')))';
  56836. } else if (selector.length === 2) {
  56837. result = 'me.getNodeValue(Ext.DomQuery.selectNode("@' + selector[1] + '", ' + dataName + '))';
  56838. } else if (selector.length === 1) {
  56839. result = 'me.getNodeValue(Ext.DomQuery.selectNode("' + selector[0] + '", ' + dataName + '))';
  56840. } else {
  56841. throw "Unsupported query - too many queries for attributes in " + selector.join('@');
  56842. }
  56843. }
  56844. return result;
  56845. }
  56846. });
  56847. /**
  56848. * @author Ed Spencer
  56849. * @class Ext.data.writer.Xml
  56850. *
  56851. * This class is used to write {@link Ext.data.Model} data to the server in an XML format.
  56852. * The {@link #documentRoot} property is used to specify the root element in the XML document.
  56853. * The {@link #record} option is used to specify the element name for each record that will make
  56854. * up the XML document.
  56855. */
  56856. Ext.define('Ext.data.writer.Xml', {
  56857. /* Begin Definitions */
  56858. extend: 'Ext.data.writer.Writer',
  56859. alternateClassName: 'Ext.data.XmlWriter',
  56860. alias: 'writer.xml',
  56861. /* End Definitions */
  56862. config: {
  56863. /**
  56864. * @cfg {String} documentRoot The name of the root element of the document.
  56865. * If there is more than 1 record and the root is not specified, the default document root will still be used
  56866. * to ensure a valid XML document is created.
  56867. */
  56868. documentRoot: 'xmlData',
  56869. /**
  56870. * @cfg {String} defaultDocumentRoot The root to be used if {@link #documentRoot} is empty and a root is required
  56871. * to form a valid XML document.
  56872. */
  56873. defaultDocumentRoot: 'xmlData',
  56874. /**
  56875. * @cfg {String} header A header to use in the XML document (such as setting the encoding or version).
  56876. */
  56877. header: '',
  56878. /**
  56879. * @cfg {String} record The name of the node to use for each record.
  56880. */
  56881. record: 'record'
  56882. },
  56883. /**
  56884. * @param request
  56885. * @param data
  56886. * @return {Object}
  56887. */
  56888. writeRecords: function(request, data) {
  56889. var me = this,
  56890. xml = [],
  56891. i = 0,
  56892. len = data.length,
  56893. root = me.getDocumentRoot(),
  56894. record = me.getRecord(),
  56895. needsRoot = data.length !== 1,
  56896. item,
  56897. key;
  56898. // may not exist
  56899. xml.push(me.getHeader() || '');
  56900. if (!root && needsRoot) {
  56901. root = me.getDefaultDocumentRoot();
  56902. }
  56903. if (root) {
  56904. xml.push('<', root, '>');
  56905. }
  56906. for (; i < len; ++i) {
  56907. item = data[i];
  56908. xml.push('<', record, '>');
  56909. for (key in item) {
  56910. if (item.hasOwnProperty(key)) {
  56911. xml.push('<', key, '>', item[key], '</', key, '>');
  56912. }
  56913. }
  56914. xml.push('</', record, '>');
  56915. }
  56916. if (root) {
  56917. xml.push('</', root, '>');
  56918. }
  56919. request.setXmlData(xml.join(''));
  56920. return request;
  56921. }
  56922. });
  56923. /**
  56924. * @aside video list
  56925. * @aside guide list
  56926. *
  56927. * IndexBar is a component used to display a list of data (primarily an alphabet) which can then be used to quickly
  56928. * navigate through a list (see {@link Ext.List}) of data. When a user taps on an item in the {@link Ext.IndexBar},
  56929. * it will fire the {@link #index} event.
  56930. *
  56931. * Here is an example of the usage in a {@link Ext.List}:
  56932. *
  56933. * @example phone portrait preview
  56934. * Ext.define('Contact', {
  56935. * extend: 'Ext.data.Model',
  56936. * config: {
  56937. * fields: ['firstName', 'lastName']
  56938. * }
  56939. * });
  56940. *
  56941. * var store = new Ext.data.JsonStore({
  56942. * model: 'Contact',
  56943. * sorters: 'lastName',
  56944. *
  56945. * grouper: {
  56946. * groupFn: function(record) {
  56947. * return record.get('lastName')[0];
  56948. * }
  56949. * },
  56950. *
  56951. * data: [
  56952. * {firstName: 'Tommy', lastName: 'Maintz'},
  56953. * {firstName: 'Rob', lastName: 'Dougan'},
  56954. * {firstName: 'Ed', lastName: 'Spencer'},
  56955. * {firstName: 'Jamie', lastName: 'Avins'},
  56956. * {firstName: 'Aaron', lastName: 'Conran'},
  56957. * {firstName: 'Dave', lastName: 'Kaneda'},
  56958. * {firstName: 'Jacky', lastName: 'Nguyen'},
  56959. * {firstName: 'Abraham', lastName: 'Elias'},
  56960. * {firstName: 'Jay', lastName: 'Robinson'},
  56961. * {firstName: 'Nigel', lastName: 'White'},
  56962. * {firstName: 'Don', lastName: 'Griffin'},
  56963. * {firstName: 'Nico', lastName: 'Ferrero'},
  56964. * {firstName: 'Jason', lastName: 'Johnston'}
  56965. * ]
  56966. * });
  56967. *
  56968. * var list = new Ext.List({
  56969. * fullscreen: true,
  56970. * itemTpl: '<div class="contact">{firstName} <strong>{lastName}</strong></div>',
  56971. *
  56972. * grouped : true,
  56973. * indexBar : true,
  56974. * store: store,
  56975. * hideOnMaskTap: false
  56976. * });
  56977. *
  56978. */
  56979. Ext.define('Ext.dataview.IndexBar', {
  56980. extend: 'Ext.Component',
  56981. alternateClassName: 'Ext.IndexBar',
  56982. /**
  56983. * @event index
  56984. * Fires when an item in the index bar display has been tapped.
  56985. * @param {Ext.dataview.IndexBar} this The IndexBar instance
  56986. * @param {String} html The HTML inside the tapped node.
  56987. * @param {Ext.dom.Element} target The node on the indexbar that has been tapped.
  56988. */
  56989. config: {
  56990. baseCls: Ext.baseCSSPrefix + 'indexbar',
  56991. /**
  56992. * @cfg {String} direction
  56993. * Layout direction, can be either 'vertical' or 'horizontal'
  56994. * @accessor
  56995. */
  56996. direction: 'vertical',
  56997. /**
  56998. * @cfg {Array} letters
  56999. * The letters to show on the index bar.
  57000. * @accessor
  57001. */
  57002. letters: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
  57003. ui: 'alphabet',
  57004. /**
  57005. * @cfg {String} listPrefix
  57006. * The prefix string to be used at the beginning of the list.
  57007. * E.g: useful to add a "#" prefix before numbers.
  57008. * @accessor
  57009. */
  57010. listPrefix: null
  57011. },
  57012. // @private
  57013. itemCls: Ext.baseCSSPrefix + '',
  57014. updateDirection: function(newDirection, oldDirection) {
  57015. var baseCls = this.getBaseCls();
  57016. this.element.replaceCls(baseCls + '-' + oldDirection, baseCls + '-' + newDirection);
  57017. },
  57018. getElementConfig: function() {
  57019. return {
  57020. reference: 'wrapper',
  57021. classList: ['x-centered', 'x-indexbar-wrapper'],
  57022. children: [this.callParent()]
  57023. };
  57024. },
  57025. updateLetters: function(letters) {
  57026. this.innerElement.setHtml('');
  57027. if (letters) {
  57028. var ln = letters.length,
  57029. i;
  57030. for (i = 0; i < ln; i++) {
  57031. this.innerElement.createChild({
  57032. html: letters[i]
  57033. });
  57034. }
  57035. }
  57036. },
  57037. updateListPrefix: function(listPrefix) {
  57038. if (listPrefix && listPrefix.length) {
  57039. this.innerElement.createChild({
  57040. html: listPrefix
  57041. }, 0);
  57042. }
  57043. },
  57044. // @private
  57045. initialize: function() {
  57046. this.callParent();
  57047. this.innerElement.on({
  57048. touchstart: this.onTouchStart,
  57049. touchend: this.onTouchEnd,
  57050. touchmove: this.onTouchMove,
  57051. scope: this
  57052. });
  57053. },
  57054. // @private
  57055. onTouchStart: function(e, t) {
  57056. e.stopPropagation();
  57057. this.innerElement.addCls(this.getBaseCls() + '-pressed');
  57058. this.pageBox = this.innerElement.getPageBox();
  57059. this.onTouchMove(e);
  57060. },
  57061. // @private
  57062. onTouchEnd: function(e, t) {
  57063. this.innerElement.removeCls(this.getBaseCls() + '-pressed');
  57064. },
  57065. // @private
  57066. onTouchMove: function(e) {
  57067. var point = Ext.util.Point.fromEvent(e),
  57068. target,
  57069. pageBox = this.pageBox;
  57070. if (!pageBox) {
  57071. pageBox = this.pageBox = this.el.getPageBox();
  57072. }
  57073. if (this.getDirection() === 'vertical') {
  57074. if (point.y > pageBox.bottom || point.y < pageBox.top) {
  57075. return;
  57076. }
  57077. target = Ext.Element.fromPoint(pageBox.left + (pageBox.width / 2), point.y);
  57078. }
  57079. else {
  57080. if (point.x > pageBox.right || point.x < pageBox.left) {
  57081. return;
  57082. }
  57083. target = Ext.Element.fromPoint(point.x, pageBox.top + (pageBox.height / 2));
  57084. }
  57085. if (target) {
  57086. this.fireEvent('index', this, target.dom.innerHTML, target);
  57087. }
  57088. },
  57089. destroy: function() {
  57090. var me = this,
  57091. elements = Array.prototype.slice.call(me.innerElement.dom.childNodes),
  57092. ln = elements.length,
  57093. i = 0;
  57094. for (; i < ln; i++) {
  57095. Ext.removeNode(elements[i]);
  57096. }
  57097. this.callParent();
  57098. }
  57099. }, function() {
  57100. });
  57101. /**
  57102. * @private - To be made a sample
  57103. */
  57104. Ext.define('Ext.dataview.ListItemHeader', {
  57105. extend: 'Ext.Component',
  57106. xtype : 'listitemheader',
  57107. config: {
  57108. /**
  57109. * @cfg
  57110. * @inheritdoc
  57111. */
  57112. baseCls: Ext.baseCSSPrefix + 'list-header',
  57113. docked: 'top'
  57114. }
  57115. });
  57116. /**
  57117. * A DataItem is a container for {@link Ext.dataview.DataView} with useComponents: true. It ties together
  57118. * {@link Ext.data.Model records} to its contained Components via a {@link #dataMap dataMap} configuration.
  57119. *
  57120. * For example, lets say you have a `text` configuration which, when applied, gets turned into an instance of an
  57121. * Ext.Component. We want to update the {@link #html} of a sub-component when the 'text' field of the record gets
  57122. * changed.
  57123. *
  57124. * As you can see below, it is simply a matter of setting the key of the object to be the getter of the config
  57125. * (`getText`), and then give that property a value of an object, which then has 'setHtml' (the `html` setter) as the key,
  57126. * and 'text' (the field name) as the value. You can continue this for a as many sub-components as you wish.
  57127. *
  57128. * dataMap: {
  57129. * // When the record is updated, get the text configuration, and
  57130. * // call {@link #setHtml} with the 'text' field of the record.
  57131. * getText: {
  57132. * setHtml: 'text'
  57133. * },
  57134. *
  57135. * // When the record is updated, get the userName configuration, and
  57136. * // call {@link #setHtml} with the 'from_user' field of the record.
  57137. * getUserName: {
  57138. * setHtml: 'from_user'
  57139. * },
  57140. *
  57141. * // When the record is updated, get the avatar configuration, and
  57142. * // call `setSrc` with the 'profile_image_url' field of the record.
  57143. * getAvatar: {
  57144. * setSrc: 'profile_image_url'
  57145. * }
  57146. * }
  57147. */
  57148. Ext.define('Ext.dataview.component.ListItem', {
  57149. extend: 'Ext.dataview.component.DataItem',
  57150. xtype : 'listitem',
  57151. config: {
  57152. baseCls: Ext.baseCSSPrefix + 'list-item',
  57153. dataMap: null,
  57154. body: {
  57155. xtype: 'component',
  57156. cls: 'x-list-item-body'
  57157. },
  57158. disclosure: {
  57159. xtype: 'component',
  57160. cls: 'x-list-disclosure',
  57161. hidden: true,
  57162. docked: 'right'
  57163. },
  57164. header: {
  57165. xtype: 'component',
  57166. cls: 'x-list-header',
  57167. html: ' ',
  57168. hidden: true
  57169. },
  57170. tpl: null,
  57171. items: null
  57172. },
  57173. applyBody: function(body) {
  57174. if (body && !body.isComponent) {
  57175. body = Ext.factory(body, Ext.Component, this.getBody());
  57176. }
  57177. return body;
  57178. },
  57179. updateBody: function(body, oldBody) {
  57180. if (body) {
  57181. this.add(body);
  57182. } else if (oldBody) {
  57183. oldBody.destroy();
  57184. }
  57185. },
  57186. applyHeader: function(header) {
  57187. if (header && !header.isComponent) {
  57188. header = Ext.factory(header, Ext.Component, this.getHeader());
  57189. }
  57190. return header;
  57191. },
  57192. updateHeader: function(header, oldHeader) {
  57193. if (header) {
  57194. this.element.getFirstChild().insertFirst(header.element);
  57195. } else if (oldHeader) {
  57196. oldHeader.destroy();
  57197. }
  57198. },
  57199. applyDisclosure: function(disclosure) {
  57200. if (disclosure && !disclosure.isComponent) {
  57201. disclosure = Ext.factory(disclosure, Ext.Component, this.getDisclosure());
  57202. }
  57203. return disclosure;
  57204. },
  57205. updateDisclosure: function(disclosure, oldDisclosure) {
  57206. if (disclosure) {
  57207. this.add(disclosure);
  57208. } else if (oldDisclosure) {
  57209. oldDisclosure.destroy();
  57210. }
  57211. },
  57212. updateTpl: function(tpl) {
  57213. this.getBody().setTpl(tpl);
  57214. },
  57215. updateRecord: function(record) {
  57216. var me = this,
  57217. dataview = me.dataview || this.getDataview(),
  57218. data = record && dataview.prepareData(record.getData(true), dataview.getStore().indexOf(record), record),
  57219. dataMap = me.getDataMap(),
  57220. body = this.getBody(),
  57221. disclosure = this.getDisclosure();
  57222. me._record = record;
  57223. if (dataMap) {
  57224. me.doMapData(dataMap, data, body);
  57225. } else if (body) {
  57226. body.updateData(data || null);
  57227. }
  57228. if (disclosure && record && dataview.getOnItemDisclosure()) {
  57229. var disclosureProperty = dataview.getDisclosureProperty();
  57230. disclosure[(data.hasOwnProperty(disclosureProperty) && data[disclosureProperty] === false) ? 'hide' : 'show']();
  57231. }
  57232. /**
  57233. * @event updatedata
  57234. * Fires whenever the data of the DataItem is updated.
  57235. * @param {Ext.dataview.component.DataItem} this The DataItem instance.
  57236. * @param {Object} newData The new data.
  57237. */
  57238. me.fireEvent('updatedata', me, data);
  57239. }
  57240. });
  57241. /**
  57242. * @private
  57243. */
  57244. Ext.define('Ext.util.TranslatableList', {
  57245. extend: 'Ext.util.translatable.Abstract',
  57246. config: {
  57247. items: []
  57248. },
  57249. applyItems: function(items) {
  57250. return Ext.Array.from(items);
  57251. },
  57252. doTranslate: function(x, y) {
  57253. var items = this.getItems(),
  57254. offset = 0,
  57255. i, ln, item, translateY;
  57256. for (i = 0, ln = items.length; i < ln; i++) {
  57257. item = items[i];
  57258. if (item && !item._list_hidden) {
  57259. translateY = y + offset;
  57260. offset += item.$height;
  57261. item.translate(0, translateY);
  57262. }
  57263. }
  57264. }
  57265. });
  57266. /**
  57267. * @private
  57268. */
  57269. Ext.define('Ext.util.PositionMap', {
  57270. config: {
  57271. minimumHeight: 50
  57272. },
  57273. constructor: function(config) {
  57274. this.map = [];
  57275. this.adjustments = {};
  57276. this.offset = 0;
  57277. this.initConfig(config);
  57278. },
  57279. populate: function(count, offset) {
  57280. var map = this.map = this.map || [],
  57281. minimumHeight = this.getMinimumHeight(),
  57282. i, previousIndex, ln;
  57283. // We add 1 item to the count so that we can get the height of the bottom item
  57284. count++;
  57285. map.length = count;
  57286. map[0] = 0;
  57287. for (i = offset + 1, ln = count - 1; i <= ln; i++) {
  57288. previousIndex = i - 1;
  57289. map[i] = map[previousIndex] + minimumHeight;
  57290. }
  57291. this.adjustments = {
  57292. indices: [],
  57293. heights: {}
  57294. };
  57295. this.offset = 0;
  57296. for (i = 1, ln = count - 1; i <= ln; i++) {
  57297. previousIndex = i - 1;
  57298. this.offset += map[i] - map[previousIndex] - minimumHeight;
  57299. }
  57300. },
  57301. setItemHeight: function(index, height) {
  57302. height = Math.max(height, this.getMinimumHeight());
  57303. if (height !== this.getItemHeight(index)) {
  57304. var adjustments = this.adjustments;
  57305. adjustments.indices.push(parseInt(index, 10));
  57306. adjustments.heights[index] = height;
  57307. }
  57308. },
  57309. update: function() {
  57310. var adjustments = this.adjustments,
  57311. indices = adjustments.indices,
  57312. heights = adjustments.heights,
  57313. map = this.map,
  57314. ln = indices.length,
  57315. minimumHeight = this.getMinimumHeight(),
  57316. difference = 0,
  57317. i, j, height, index, nextIndex, currentHeight;
  57318. if (!adjustments.indices.length) {
  57319. return false;
  57320. }
  57321. Ext.Array.sort(indices, function(a, b) {
  57322. return a - b;
  57323. });
  57324. for (i = 0; i < ln; i++) {
  57325. index = indices[i];
  57326. nextIndex = indices[i + 1] || map.length - 1;
  57327. currentHeight = (map[index + 1] !== undefined) ? (map[index + 1] - map[index] + difference) : minimumHeight;
  57328. height = heights[index];
  57329. difference += height - currentHeight;
  57330. for (j = index + 1; j <= nextIndex; j++) {
  57331. map[j] += difference;
  57332. }
  57333. }
  57334. this.offset += difference;
  57335. this.adjustments = {
  57336. indices: [],
  57337. heights: {}
  57338. };
  57339. return true;
  57340. },
  57341. getItemHeight: function(index) {
  57342. return this.map[index + 1] - this.map[index];
  57343. },
  57344. getTotalHeight: function() {
  57345. return ((this.map.length - 1) * this.getMinimumHeight()) + this.offset;
  57346. },
  57347. findIndex: function(pos) {
  57348. return this.map.length ? this.binarySearch(this.map, pos) : 0;
  57349. },
  57350. binarySearch: function(sorted, value) {
  57351. var start = 0,
  57352. end = sorted.length;
  57353. if (value < sorted[0]) {
  57354. return 0;
  57355. }
  57356. if (value > sorted[end - 1]) {
  57357. return end - 1;
  57358. }
  57359. while (start + 1 < end) {
  57360. var mid = (start + end) >> 1,
  57361. val = sorted[mid];
  57362. if (val == value) {
  57363. return mid;
  57364. } else if (val < value) {
  57365. start = mid;
  57366. } else {
  57367. end = mid;
  57368. }
  57369. }
  57370. return start;
  57371. }
  57372. });
  57373. /**
  57374. * @aside guide list
  57375. * @aside video list
  57376. *
  57377. * List is a custom styled DataView which allows Grouping, Indexing, Icons, and a Disclosure. See the
  57378. * [Guide](#!/guide/list) and [Video](#!/video/list) for more.
  57379. *
  57380. * @example miniphone preview
  57381. * Ext.create('Ext.List', {
  57382. * fullscreen: true,
  57383. * itemTpl: '{title}',
  57384. * data: [
  57385. * { title: 'Item 1' },
  57386. * { title: 'Item 2' },
  57387. * { title: 'Item 3' },
  57388. * { title: 'Item 4' }
  57389. * ]
  57390. * });
  57391. *
  57392. * A more advanced example showing a list of people groped by last name:
  57393. *
  57394. * @example miniphone preview
  57395. * Ext.define('Contact', {
  57396. * extend: 'Ext.data.Model',
  57397. * config: {
  57398. * fields: ['firstName', 'lastName']
  57399. * }
  57400. * });
  57401. *
  57402. * var store = Ext.create('Ext.data.Store', {
  57403. * model: 'Contact',
  57404. * sorters: 'lastName',
  57405. *
  57406. * grouper: {
  57407. * groupFn: function(record) {
  57408. * return record.get('lastName')[0];
  57409. * }
  57410. * },
  57411. *
  57412. * data: [
  57413. * { firstName: 'Tommy', lastName: 'Maintz' },
  57414. * { firstName: 'Rob', lastName: 'Dougan' },
  57415. * { firstName: 'Ed', lastName: 'Spencer' },
  57416. * { firstName: 'Jamie', lastName: 'Avins' },
  57417. * { firstName: 'Aaron', lastName: 'Conran' },
  57418. * { firstName: 'Dave', lastName: 'Kaneda' },
  57419. * { firstName: 'Jacky', lastName: 'Nguyen' },
  57420. * { firstName: 'Abraham', lastName: 'Elias' },
  57421. * { firstName: 'Jay', lastName: 'Robinson'},
  57422. * { firstName: 'Nigel', lastName: 'White' },
  57423. * { firstName: 'Don', lastName: 'Griffin' },
  57424. * { firstName: 'Nico', lastName: 'Ferrero' },
  57425. * { firstName: 'Jason', lastName: 'Johnston'}
  57426. * ]
  57427. * });
  57428. *
  57429. * Ext.create('Ext.List', {
  57430. * fullscreen: true,
  57431. * itemTpl: '<div class="contact">{firstName} <strong>{lastName}</strong></div>',
  57432. * store: store,
  57433. * grouped: true
  57434. * });
  57435. */
  57436. Ext.define('Ext.dataview.List', {
  57437. alternateClassName: 'Ext.List',
  57438. extend: 'Ext.dataview.DataView',
  57439. xtype: 'list',
  57440. mixins: ['Ext.mixin.Bindable'],
  57441. requires: [
  57442. 'Ext.dataview.IndexBar',
  57443. 'Ext.dataview.ListItemHeader',
  57444. 'Ext.dataview.component.ListItem',
  57445. 'Ext.util.TranslatableList',
  57446. 'Ext.util.PositionMap'
  57447. ],
  57448. /**
  57449. * @event disclose
  57450. * @preventable doDisclose
  57451. * Fires whenever a disclosure is handled
  57452. * @param {Ext.dataview.List} this The List instance
  57453. * @param {Ext.data.Model} record The record associated to the item
  57454. * @param {HTMLElement} target The element disclosed
  57455. * @param {Number} index The index of the item disclosed
  57456. * @param {Ext.EventObject} e The event object
  57457. */
  57458. config: {
  57459. /**
  57460. * @cfg layout
  57461. * Hide layout config in DataView. It only causes confusion.
  57462. * @accessor
  57463. * @private
  57464. */
  57465. layout: 'fit',
  57466. /**
  57467. * @cfg {Boolean/Object} indexBar
  57468. * `true` to render an alphabet IndexBar docked on the right.
  57469. * This can also be a config object that will be passed to {@link Ext.IndexBar}.
  57470. * @accessor
  57471. */
  57472. indexBar: false,
  57473. icon: null,
  57474. /**
  57475. * @cfg {Boolean} clearSelectionOnDeactivate
  57476. * `true` to clear any selections on the list when the list is deactivated.
  57477. * @removed 2.0.0
  57478. */
  57479. /**
  57480. * @cfg {Boolean} preventSelectionOnDisclose `true` to prevent the item selection when the user
  57481. * taps a disclose icon.
  57482. * @accessor
  57483. */
  57484. preventSelectionOnDisclose: true,
  57485. /**
  57486. * @cfg baseCls
  57487. * @inheritdoc
  57488. */
  57489. baseCls: Ext.baseCSSPrefix + 'list',
  57490. /**
  57491. * @cfg {Boolean} pinHeaders
  57492. * Whether or not to pin headers on top of item groups while scrolling for an iPhone native list experience.
  57493. * @accessor
  57494. */
  57495. pinHeaders: true,
  57496. /**
  57497. * @cfg {Boolean} grouped
  57498. * Whether or not to group items in the provided Store with a header for each item.
  57499. * @accessor
  57500. */
  57501. grouped: false,
  57502. /**
  57503. * @cfg {Boolean/Function/Object} onItemDisclosure
  57504. * `true` to display a disclosure icon on each list item.
  57505. * The list will still fire the disclose event, and the event can be stopped before itemtap.
  57506. * By setting this config to a function, the function passed will be called when the disclosure
  57507. * is tapped.
  57508. * Finally you can specify an object with a 'scope' and 'handler'
  57509. * property defined. This will also be bound to the tap event listener
  57510. * and is useful when you want to change the scope of the handler.
  57511. * @accessor
  57512. */
  57513. onItemDisclosure: null,
  57514. /**
  57515. * @cfg {String} disclosureProperty
  57516. * A property to check on each record to display the disclosure on a per record basis. This
  57517. * property must be false to prevent the disclosure from being displayed on the item.
  57518. * @accessor
  57519. */
  57520. disclosureProperty: 'disclosure',
  57521. /**
  57522. * @cfg {String} ui
  57523. * The style of this list. Available options are `normal` and `round`.
  57524. */
  57525. ui: 'normal',
  57526. /**
  57527. * @cfg {Boolean} useComponents
  57528. * Flag the use a component based DataView implementation. This allows the full use of components in the
  57529. * DataView at the cost of some performance.
  57530. *
  57531. * Checkout the [DataView Guide](#!/guide/dataview) for more information on using this configuration.
  57532. * @accessor
  57533. * @private
  57534. */
  57535. /**
  57536. * @cfg {Object} itemConfig
  57537. * A configuration object that is passed to every item created by a component based DataView. Because each
  57538. * item that a DataView renders is a Component, we can pass configuration options to each component to
  57539. * easily customize how each child component behaves.
  57540. * Note this is only used when useComponents is true.
  57541. * @accessor
  57542. * @private
  57543. */
  57544. /**
  57545. * @cfg {Number} maxItemCache
  57546. * Maintains a cache of reusable components when using a component based DataView. Improving performance at
  57547. * the cost of memory.
  57548. * Note this is currently only used when useComponents is true.
  57549. * @accessor
  57550. * @private
  57551. */
  57552. /**
  57553. * @cfg {String} defaultType
  57554. * The xtype used for the component based DataView. Defaults to dataitem.
  57555. * Note this is only used when useComponents is true.
  57556. * @accessor
  57557. */
  57558. defaultType: 'listitem',
  57559. /**
  57560. * @cfg {Object} itemMap
  57561. * @private
  57562. */
  57563. itemMap: {
  57564. minimumHeight: 47
  57565. },
  57566. /**
  57567. * @cfg {Boolean} variableHeights
  57568. * Whether or not this list contains items with variable heights. If you want to force the
  57569. * items in the list to have a fixed height, set the {@link #itemHeight} configuration.
  57570. * If you also variableHeights to false, the scrolling performance of the list will be
  57571. * improved.
  57572. */
  57573. variableHeights: true,
  57574. /**
  57575. * @cfg {Number} itemHeight
  57576. * This allows you to set the default item height and is used to roughly calculate the amount
  57577. * of items needed to fill the list. By default items are around 50px high. If you set this
  57578. * configuration in combination with setting the {@link #variableHeights} to false you
  57579. * can improve the scrolling speed
  57580. */
  57581. itemHeight: 47,
  57582. /**
  57583. * @cfg {Boolean} refreshHeightOnUpdate
  57584. * Set this to false if you make many updates to your list (like in an interval), but updates
  57585. * won't affect the item's height. Doing this will increase the performance of these updates.
  57586. * Note that if you have {@link #variableHeights} set to false, this configuration option has
  57587. * no effect.
  57588. */
  57589. refreshHeightOnUpdate: true,
  57590. scrollable: false
  57591. },
  57592. constructor: function(config) {
  57593. var me = this,
  57594. layout;
  57595. me.callParent(arguments);
  57596. if (Ext.os.is.Android4 && !Ext.browser.is.ChromeMobile) {
  57597. me.headerTranslateFn = Ext.Function.createThrottled(me.headerTranslateFn, 50, me);
  57598. }
  57599. //<debug>
  57600. layout = this.getLayout();
  57601. if (layout && !layout.isFit) {
  57602. Ext.Logger.error('The base layout for a DataView must always be a Fit Layout');
  57603. }
  57604. //</debug>
  57605. },
  57606. topItemIndex: 0,
  57607. topItemPosition: 0,
  57608. updateItemHeight: function(itemHeight) {
  57609. this.getItemMap().setMinimumHeight(itemHeight);
  57610. },
  57611. applyItemMap: function(itemMap) {
  57612. return Ext.factory(itemMap, Ext.util.PositionMap, this.getItemMap());
  57613. },
  57614. // apply to the selection model to maintain visual UI cues
  57615. // onItemTrigger: function(me, index, target, record, e) {
  57616. // if (!(this.getPreventSelectionOnDisclose() && Ext.fly(e.target).hasCls(this.getBaseCls() + '-disclosure'))) {
  57617. // this.callParent(arguments);
  57618. // }
  57619. // },
  57620. beforeInitialize: function() {
  57621. var me = this,
  57622. container;
  57623. me.listItems = [];
  57624. me.scrollDockItems = {
  57625. top: [],
  57626. bottom: []
  57627. };
  57628. container = me.container = me.add(new Ext.Container({
  57629. scrollable: {
  57630. scroller: {
  57631. autoRefresh: false,
  57632. direction: 'vertical',
  57633. translatable: {
  57634. xclass: 'Ext.util.TranslatableList'
  57635. }
  57636. }
  57637. }
  57638. }));
  57639. container.getScrollable().getScroller().getTranslatable().setItems(me.listItems);
  57640. // Tie List's scroller to its container's scroller
  57641. me.setScrollable(container.getScrollable());
  57642. me.scrollableBehavior = container.getScrollableBehavior();
  57643. },
  57644. initialize: function() {
  57645. var me = this,
  57646. container = me.container,
  57647. i, ln;
  57648. me.updatedItems = [];
  57649. me.headerMap = [];
  57650. me.on(me.getTriggerCtEvent(), me.onContainerTrigger, me);
  57651. me.on(me.getTriggerEvent(), me.onItemTrigger, me);
  57652. me.header = Ext.factory({
  57653. xclass: 'Ext.dataview.ListItemHeader',
  57654. html: '&nbsp;',
  57655. translatable: true,
  57656. role: 'globallistheader',
  57657. cls: ['x-list-header', 'x-list-header-swap']
  57658. });
  57659. me.container.innerElement.insertFirst(me.header.element);
  57660. me.headerTranslate = me.header.getTranslatable();
  57661. me.headerTranslate.translate(0, -10000);
  57662. if (!me.getGrouped()) {
  57663. me.updatePinHeaders(null);
  57664. }
  57665. container.element.on({
  57666. delegate: '.' + me.getBaseCls() + '-disclosure',
  57667. tap: 'handleItemDisclosure',
  57668. scope: me
  57669. });
  57670. container.element.on({
  57671. resize: 'onResize',
  57672. scope: me
  57673. });
  57674. // Android 2.x not a direct child
  57675. container.innerElement.on({
  57676. touchstart: 'onItemTouchStart',
  57677. touchend: 'onItemTouchEnd',
  57678. tap: 'onItemTap',
  57679. taphold: 'onItemTapHold',
  57680. singletap: 'onItemSingleTap',
  57681. doubletap: 'onItemDoubleTap',
  57682. swipe: 'onItemSwipe',
  57683. delegate: '.' + Ext.baseCSSPrefix + 'list-item-body',
  57684. scope: me
  57685. });
  57686. for (i = 0, ln = me.scrollDockItems.top.length; i < ln; i++) {
  57687. container.add(me.scrollDockItems.top[i]);
  57688. }
  57689. for (i = 0, ln = me.scrollDockItems.bottom.length; i < ln; i++) {
  57690. container.add(me.scrollDockItems.bottom[i]);
  57691. }
  57692. if (me.getStore()) {
  57693. me.refresh();
  57694. }
  57695. },
  57696. updateInline: function(newInline) {
  57697. var me = this;
  57698. me.callParent(arguments);
  57699. if (newInline) {
  57700. me.setOnItemDisclosure(false);
  57701. me.setIndexBar(false);
  57702. me.setGrouped(false);
  57703. }
  57704. },
  57705. applyIndexBar: function(indexBar) {
  57706. return Ext.factory(indexBar, Ext.dataview.IndexBar, this.getIndexBar());
  57707. },
  57708. updateIndexBar: function(indexBar) {
  57709. var me = this;
  57710. if (indexBar && me.getScrollable()) {
  57711. me.indexBarElement = me.getScrollableBehavior().getScrollView().getElement().appendChild(indexBar.renderElement);
  57712. indexBar.on({
  57713. index: 'onIndex',
  57714. scope: me
  57715. });
  57716. me.element.addCls(me.getBaseCls() + '-indexed');
  57717. }
  57718. },
  57719. updateGrouped: function(grouped) {
  57720. var me = this,
  57721. baseCls = this.getBaseCls(),
  57722. cls = baseCls + '-grouped',
  57723. unCls = baseCls + '-ungrouped';
  57724. if (grouped) {
  57725. me.addCls(cls);
  57726. me.removeCls(unCls);
  57727. me.updatePinHeaders(me.getPinHeaders());
  57728. }
  57729. else {
  57730. me.addCls(unCls);
  57731. me.removeCls(cls);
  57732. me.updatePinHeaders(null);
  57733. }
  57734. if (me.isPainted() && me.listItems.length) {
  57735. me.setItemsCount(me.listItems.length);
  57736. }
  57737. },
  57738. updatePinHeaders: function(pinnedHeaders) {
  57739. if (this.headerTranslate) {
  57740. this.headerTranslate.translate(0, -10000);
  57741. }
  57742. },
  57743. updateScrollerSize: function() {
  57744. var me = this,
  57745. totalHeight = me.getItemMap().getTotalHeight(),
  57746. scroller = me.container.getScrollable().getScroller();
  57747. if (totalHeight > 0) {
  57748. scroller.givenSize = totalHeight;
  57749. scroller.refresh();
  57750. }
  57751. },
  57752. onResize: function() {
  57753. var me = this,
  57754. container = me.container,
  57755. element = container.element,
  57756. minimumHeight = me.getItemMap().getMinimumHeight(),
  57757. containerSize;
  57758. if (!me.listItems.length) {
  57759. me.bind(container.getScrollable().getScroller().getTranslatable(), 'doTranslate', 'onTranslate');
  57760. }
  57761. me.containerSize = containerSize = element.getHeight();
  57762. me.setItemsCount(Math.ceil(containerSize / minimumHeight) + 1);
  57763. },
  57764. scrollDockHeightRefresh: function() {
  57765. var items = this.listItems,
  57766. scrollDockItems = this.scrollDockItems,
  57767. ln = items.length,
  57768. i, item;
  57769. for (i = 0; i < ln; i++) {
  57770. item = items[i];
  57771. if ((item.isFirst && scrollDockItems.top.length) || (item.isLast && scrollDockItems.bottom.length)) {
  57772. this.updatedItems.push(item);
  57773. }
  57774. }
  57775. this.refreshScroller();
  57776. },
  57777. headerTranslateFn: function(record, transY, headerTranslate) {
  57778. var headerString = this.getStore().getGroupString(record);
  57779. if (this.currentHeader !== headerString) {
  57780. this.currentHeader = headerString;
  57781. this.header.setHtml(headerString);
  57782. }
  57783. headerTranslate.translate(0, transY);
  57784. },
  57785. onTranslate: function(x, y, args) {
  57786. var me = this,
  57787. listItems = me.listItems,
  57788. itemsCount = listItems.length,
  57789. currentTopIndex = me.topItemIndex,
  57790. itemMap = me.getItemMap(),
  57791. store = me.getStore(),
  57792. storeCount = store.getCount(),
  57793. info = me.getListItemInfo(),
  57794. grouped = me.getGrouped(),
  57795. storeGroups = me.groups,
  57796. headerMap = me.headerMap,
  57797. headerTranslate = me.headerTranslate,
  57798. pinHeaders = me.getPinHeaders(),
  57799. maxIndex = storeCount - itemsCount + 1,
  57800. topIndex, changedCount, i, index, item,
  57801. closestHeader, record, pushedHeader, transY, element;
  57802. if (me.updatedItems.length) {
  57803. me.updateItemHeights();
  57804. }
  57805. me.topItemPosition = itemMap.findIndex(-y) || 0;
  57806. me.indexOffset = me.topItemIndex = topIndex = Math.max(0, Math.min(me.topItemPosition, maxIndex));
  57807. if (grouped && headerTranslate && storeGroups.length && pinHeaders) {
  57808. closestHeader = itemMap.binarySearch(headerMap, -y);
  57809. record = storeGroups[closestHeader].children[0];
  57810. if (record) {
  57811. pushedHeader = y + headerMap[closestHeader + 1] - me.headerHeight;
  57812. // Top of the list or above (hide the floating header offscreen)
  57813. if (y >= 0) {
  57814. transY = -10000;
  57815. }
  57816. // Scroll the floating header a bit
  57817. else if (pushedHeader < 0) {
  57818. transY = pushedHeader;
  57819. }
  57820. // Stick to the top of the screen
  57821. else {
  57822. transY = Math.max(0, y);
  57823. }
  57824. this.headerTranslateFn(record, transY, headerTranslate);
  57825. }
  57826. }
  57827. args[1] = (itemMap.map[topIndex] || 0) + y;
  57828. if (currentTopIndex !== topIndex && topIndex <= maxIndex) {
  57829. // Scroll up
  57830. if (currentTopIndex > topIndex) {
  57831. changedCount = Math.min(itemsCount, currentTopIndex - topIndex);
  57832. for (i = changedCount - 1; i >= 0; i--) {
  57833. item = listItems.pop();
  57834. listItems.unshift(item);
  57835. me.updateListItem(item, i + topIndex, info);
  57836. }
  57837. }
  57838. else {
  57839. // Scroll down
  57840. changedCount = Math.min(itemsCount, topIndex - currentTopIndex);
  57841. for (i = 0; i < changedCount; i++) {
  57842. item = listItems.shift();
  57843. listItems.push(item);
  57844. index = i + topIndex + itemsCount - changedCount;
  57845. me.updateListItem(item, index, info);
  57846. }
  57847. }
  57848. }
  57849. if (listItems.length && grouped && pinHeaders) {
  57850. if (me.headerIndices[topIndex]) {
  57851. element = listItems[0].getHeader().element;
  57852. if (y < itemMap.map[topIndex]) {
  57853. element.setVisibility(false);
  57854. }
  57855. else {
  57856. element.setVisibility(true);
  57857. }
  57858. }
  57859. for (i = 1; i <= changedCount; i++) {
  57860. if (listItems[i]) {
  57861. listItems[i].getHeader().element.setVisibility(true);
  57862. }
  57863. }
  57864. }
  57865. },
  57866. setItemsCount: function(itemsCount) {
  57867. var me = this,
  57868. listItems = me.listItems,
  57869. minimumHeight = me.getItemMap().getMinimumHeight(),
  57870. config = {
  57871. xtype: me.getDefaultType(),
  57872. itemConfig: me.getItemConfig(),
  57873. tpl: me.getItemTpl(),
  57874. minHeight: minimumHeight,
  57875. cls: me.getItemCls()
  57876. },
  57877. info = me.getListItemInfo(),
  57878. i, item;
  57879. for (i = 0; i < itemsCount; i++) {
  57880. // We begin by checking if we already have an item for this length
  57881. item = listItems[i];
  57882. // If we don't have an item yet at this index then create one
  57883. if (!item) {
  57884. item = Ext.factory(config);
  57885. item.dataview = me;
  57886. item.$height = minimumHeight;
  57887. me.container.doAdd(item);
  57888. listItems.push(item);
  57889. }
  57890. item.dataIndex = null;
  57891. if (info.store) {
  57892. me.updateListItem(item, i + me.topItemIndex, info);
  57893. }
  57894. }
  57895. me.updateScrollerSize();
  57896. },
  57897. getListItemInfo: function() {
  57898. var me = this,
  57899. baseCls = me.getBaseCls();
  57900. return {
  57901. store: me.getStore(),
  57902. grouped: me.getGrouped(),
  57903. baseCls: baseCls,
  57904. selectedCls: me.getSelectedCls(),
  57905. headerCls: baseCls + '-header-wrap',
  57906. footerCls: baseCls + '-footer-wrap',
  57907. firstCls: baseCls + '-item-first',
  57908. lastCls: baseCls + '-item-last',
  57909. itemMap: me.getItemMap(),
  57910. variableHeights: me.getVariableHeights(),
  57911. defaultItemHeight: me.getItemHeight()
  57912. };
  57913. },
  57914. updateListItem: function(item, index, info) {
  57915. var record = info.store.getAt(index);
  57916. if (this.isSelected(record)) {
  57917. item.addCls(info.selectedCls);
  57918. }
  57919. else {
  57920. item.removeCls(info.selectedCls);
  57921. }
  57922. item.removeCls([info.headerCls, info.footerCls, info.firstCls, info.lastCls]);
  57923. this.replaceItemContent(item, index, info)
  57924. },
  57925. taskRunner: function() {
  57926. delete this.intervalId;
  57927. if (this.scheduledTasks && this.scheduledTasks.length > 0) {
  57928. var task = this.scheduledTasks.shift();
  57929. this.doUpdateListItem(task.item, task.index, task.info);
  57930. if (this.scheduledTasks.length === 0 && this.getVariableHeights() && !this.container.getScrollable().getScroller().getTranslatable().isAnimating) {
  57931. this.refreshScroller();
  57932. } else if (this.scheduledTasks.length > 0) {
  57933. this.intervalId = requestAnimationFrame(Ext.Function.bind(this.taskRunner, this));
  57934. }
  57935. }
  57936. },
  57937. scheduledTasks: null,
  57938. replaceItemContent: function(item, index, info) {
  57939. var translatable = this.container.getScrollable().getScroller().getTranslatable();
  57940. // This falls apart when scrolling up. Turning off for now.
  57941. if (Ext.os.is.Android4
  57942. && !Ext.browser.is.Chrome
  57943. && !info.variableHeights
  57944. && !info.grouped
  57945. && translatable.isAnimating
  57946. && translatable.activeEasingY
  57947. && Math.abs(translatable.activeEasingY._startVelocity) > .75) {
  57948. if (!this.scheduledTasks) {
  57949. this.scheduledTasks = [];
  57950. }
  57951. for (var i = 0; i < this.scheduledTasks.length; i++) {
  57952. if (this.scheduledTasks[i].item === item) {
  57953. Ext.Array.remove(this.scheduledTasks, this.scheduledTasks[i]);
  57954. break;
  57955. }
  57956. }
  57957. this.scheduledTasks.push({
  57958. item: item,
  57959. index: index,
  57960. info: info
  57961. });
  57962. if (!this.intervalId) {
  57963. this.intervalId = requestAnimationFrame(Ext.Function.bind(this.taskRunner, this));
  57964. }
  57965. } else {
  57966. this.doUpdateListItem(item, index, info);
  57967. }
  57968. },
  57969. doUpdateListItem: function(item, index, info) {
  57970. var record = info.store.getAt(index),
  57971. headerIndices = this.headerIndices,
  57972. footerIndices = this.footerIndices,
  57973. headerItem = item.getHeader(),
  57974. scrollDockItems = this.scrollDockItems,
  57975. updatedItems = this.updatedItems,
  57976. itemHeight = info.itemMap.getItemHeight(index),
  57977. ln, i, scrollDockItem;
  57978. if (!record) {
  57979. item.setRecord(null);
  57980. item.translate(0, -10000);
  57981. item._list_hidden = true;
  57982. return;
  57983. }
  57984. item._list_hidden = false;
  57985. if (item.isFirst && scrollDockItems.top.length) {
  57986. for (i = 0, ln = scrollDockItems.top.length; i < ln; i++) {
  57987. scrollDockItem = scrollDockItems.top[i];
  57988. scrollDockItem.addCls(Ext.baseCSSPrefix + 'list-scrolldock-hidden');
  57989. item.remove(scrollDockItem, false);
  57990. }
  57991. item.isFirst = false;
  57992. }
  57993. if (item.isLast && scrollDockItems.bottom.length) {
  57994. for (i = 0, ln = scrollDockItems.bottom.length; i < ln; i++) {
  57995. scrollDockItem = scrollDockItems.bottom[i];
  57996. scrollDockItem.addCls(Ext.baseCSSPrefix + 'list-scrolldock-hidden');
  57997. item.remove(scrollDockItem, false);
  57998. }
  57999. item.isLast = false;
  58000. }
  58001. if (item.getRecord) {
  58002. if (item.dataIndex !== index) {
  58003. item.dataIndex = index;
  58004. this.fireEvent('itemindexchange', this, record, index, item);
  58005. }
  58006. if (item.getRecord() === record) {
  58007. item.updateRecord(record);
  58008. } else {
  58009. item.setRecord(record);
  58010. }
  58011. }
  58012. if (this.isSelected(record)) {
  58013. item.addCls(info.selectedCls);
  58014. }
  58015. else {
  58016. item.removeCls(info.selectedCls);
  58017. }
  58018. item.removeCls([info.headerCls, info.footerCls, info.firstCls, info.lastCls]);
  58019. if (info.grouped) {
  58020. if (headerIndices[index]) {
  58021. item.addCls(info.headerCls);
  58022. headerItem.setHtml(info.store.getGroupString(record));
  58023. headerItem.show();
  58024. headerItem.element.setVisibility(true);
  58025. // If this record is a group header, and the items height is still the default height
  58026. // we need to read the actual size of the item (including the header)
  58027. if (!info.variableHeights && itemHeight === info.defaultItemHeight) {
  58028. Ext.Array.include(updatedItems, item);
  58029. }
  58030. }
  58031. else {
  58032. headerItem.hide();
  58033. // If this record is not a header (anymore) and its height is unequal to the default item height
  58034. // it means the item must have gotten a different height because being a header before and now needs
  58035. // to become the default height again
  58036. if (!info.variableHeights && !footerIndices[index] && itemHeight !== info.defaultItemHeight) {
  58037. info.itemMap.setItemHeight(index, info.defaultItemHeight);
  58038. info.itemMap.update();
  58039. }
  58040. }
  58041. if (footerIndices[index]) {
  58042. item.addCls(info.footerCls);
  58043. // If this record is a footer and its height is still the same as the default item height, we have
  58044. // to make sure to read this items height to see if adding the foot cls effects its height
  58045. if (!info.variableHeights && itemHeight === info.defaultItemHeight) {
  58046. Ext.Array.include(updatedItems, item);
  58047. }
  58048. }
  58049. } else if (!info.variableHeights && itemHeight !== info.defaultItemHeight) {
  58050. // If this list is not grouped, the only thing that can change the height of an item
  58051. // can be scroll dock items. If an items height is not equal to the default item height
  58052. // it means it must have had scroll dock items. In this case we set the items height
  58053. // to become the default height again.
  58054. info.itemMap.setItemHeight(index, info.defaultItemHeight);
  58055. info.itemMap.update();
  58056. }
  58057. if (index === 0) {
  58058. item.isFirst = true;
  58059. item.addCls(info.firstCls);
  58060. if (!info.grouped) {
  58061. item.addCls(info.headerCls);
  58062. }
  58063. for (i = 0, ln = scrollDockItems.top.length; i < ln; i++) {
  58064. scrollDockItem = scrollDockItems.top[i];
  58065. item.insert(0, scrollDockItem);
  58066. scrollDockItem.removeCls(Ext.baseCSSPrefix + 'list-scrolldock-hidden');
  58067. }
  58068. // If an item gets scrolldock items inside of it, we need to always read the height
  58069. // in the next frame so we add it to the updatedItems array
  58070. if (ln && !info.variableHeights) {
  58071. Ext.Array.include(updatedItems, item);
  58072. }
  58073. }
  58074. if (index === info.store.getCount() - 1) {
  58075. item.isLast = true;
  58076. item.addCls(info.lastCls);
  58077. if (!info.grouped) {
  58078. item.addCls(info.footerCls);
  58079. }
  58080. for (i = 0, ln = scrollDockItems.bottom.length; i < ln; i++) {
  58081. scrollDockItem = scrollDockItems.bottom[i];
  58082. item.insert(0, scrollDockItem);
  58083. scrollDockItem.removeCls(Ext.baseCSSPrefix + 'list-scrolldock-hidden');
  58084. }
  58085. // If an item gets scrolldock items inside of it, we need to always read the height
  58086. // in the next frame so we add it to the updatedItems array
  58087. if (ln && !info.variableHeights) {
  58088. Ext.Array.include(updatedItems, item);
  58089. }
  58090. }
  58091. item.$height = info.itemMap.getItemHeight(index);
  58092. if (info.variableHeights) {
  58093. updatedItems.push(item);
  58094. }
  58095. },
  58096. updateItemHeights: function() {
  58097. if (!this.isPainted()) {
  58098. this.pendingHeightUpdate = true;
  58099. if (!this.pendingHeightUpdate) {
  58100. this.on('painted', this.updateItemHeights, this, {single: true});
  58101. }
  58102. return;
  58103. }
  58104. var updatedItems = this.updatedItems,
  58105. ln = updatedItems.length,
  58106. itemMap = this.getItemMap(),
  58107. scroller = this.container.getScrollable().getScroller(),
  58108. minimumHeight = itemMap.getMinimumHeight(),
  58109. headerIndices = this.headerIndices,
  58110. headerMap = this.headerMap,
  58111. translatable = scroller.getTranslatable(),
  58112. itemIndex, i, item, height;
  58113. this.pendingHeightUpdate = false;
  58114. // First we do all the reads
  58115. for (i = 0; i < ln; i++) {
  58116. item = updatedItems[i];
  58117. itemIndex = item.dataIndex;
  58118. // itemIndex may not be set yet if the store is still being loaded
  58119. if (itemIndex !== null) {
  58120. height = item.element.getFirstChild().getHeight();
  58121. height = Math.max(height, minimumHeight);
  58122. if (headerIndices && !this.headerHeight && headerIndices[itemIndex]) {
  58123. this.headerHeight = parseInt(item.getHeader().element.getHeight(), 10);
  58124. }
  58125. itemMap.setItemHeight(itemIndex, height);
  58126. }
  58127. }
  58128. itemMap.update();
  58129. height = itemMap.getTotalHeight();
  58130. headerMap.length = 0;
  58131. for (i in headerIndices) {
  58132. headerMap.push(itemMap.map[i]);
  58133. }
  58134. // Now do the dom writes
  58135. for (i = 0; i < ln; i++) {
  58136. item = updatedItems[i];
  58137. itemIndex = item.dataIndex;
  58138. item.$height = itemMap.getItemHeight(itemIndex);
  58139. }
  58140. if (height != scroller.givenSize) {
  58141. scroller.setSize(height);
  58142. scroller.refreshMaxPosition();
  58143. if (translatable.isAnimating) {
  58144. translatable.activeEasingY.setMinMomentumValue(-scroller.getMaxPosition().y);
  58145. }
  58146. }
  58147. this.updatedItems.length = 0;
  58148. },
  58149. /**
  58150. * Returns an item at the specified index.
  58151. * @param {Number} index Index of the item.
  58152. * @return {Ext.dom.Element/Ext.dataview.component.DataItem} item Item at the specified index.
  58153. */
  58154. getItemAt: function(index) {
  58155. var listItems = this.listItems,
  58156. ln = listItems.length,
  58157. i, listItem;
  58158. for (i = 0; i < ln; i++) {
  58159. listItem = listItems[i];
  58160. if (listItem.dataIndex === index) {
  58161. return listItem;
  58162. }
  58163. }
  58164. },
  58165. /**
  58166. * Returns an index for the specified item.
  58167. * @param {Number} item The item to locate.
  58168. * @return {Number} Index for the specified item.
  58169. */
  58170. getItemIndex: function(item) {
  58171. var index = item.dataIndex;
  58172. return (index === -1) ? index : this.indexOffset + index;
  58173. },
  58174. /**
  58175. * Returns an array of the current items in the DataView.
  58176. * @return {Ext.dom.Element[]/Ext.dataview.component.DataItem[]} Array of Items.
  58177. */
  58178. getViewItems: function() {
  58179. return this.listItems;
  58180. },
  58181. doRefresh: function(list) {
  58182. if (this.intervalId) {
  58183. cancelAnimationFrame(this.intervalId);
  58184. delete this.intervalId;
  58185. }
  58186. if (this.scheduledTasks) {
  58187. this.scheduledTasks.length = 0;
  58188. }
  58189. var me = this,
  58190. store = me.getStore(),
  58191. scrollable = me.container.getScrollable(),
  58192. scroller = scrollable && scrollable.getScroller(),
  58193. painted = me.isPainted(),
  58194. storeCount = store.getCount();
  58195. me.getItemMap().populate(storeCount, this.topItemPosition);
  58196. if (me.getGrouped()) {
  58197. me.findGroupHeaderIndices();
  58198. }
  58199. // This will refresh the items on the screen with the new data
  58200. if (me.listItems.length) {
  58201. me.setItemsCount(me.listItems.length);
  58202. if (painted) {
  58203. me.refreshScroller(scroller);
  58204. }
  58205. }
  58206. if (painted && this.getScrollToTopOnRefresh() && scroller && list) {
  58207. scroller.scrollToTop();
  58208. }
  58209. // No items, hide all the items from the collection.
  58210. if (storeCount < 1) {
  58211. me.onStoreClear();
  58212. return;
  58213. } else {
  58214. me.hideEmptyText();
  58215. }
  58216. },
  58217. findGroupHeaderIndices: function() {
  58218. var me = this,
  58219. store = me.getStore(),
  58220. storeLn = store.getCount(),
  58221. groups = store.getGroups(),
  58222. groupLn = groups.length,
  58223. headerIndices = me.headerIndices = {},
  58224. footerIndices = me.footerIndices = {},
  58225. i, previousIndex, firstGroupedRecord, storeIndex;
  58226. me.groups = groups;
  58227. for (i = 0; i < groupLn; i++) {
  58228. firstGroupedRecord = groups[i].children[0];
  58229. storeIndex = store.indexOf(firstGroupedRecord);
  58230. headerIndices[storeIndex] = true;
  58231. previousIndex = storeIndex - 1;
  58232. if (previousIndex) {
  58233. footerIndices[previousIndex] = true;
  58234. }
  58235. }
  58236. footerIndices[storeLn - 1] = true;
  58237. return headerIndices;
  58238. },
  58239. // Handling adds and removes like this is fine for now. It should not perform much slower then a dedicated solution
  58240. onStoreAdd: function() {
  58241. this.doRefresh();
  58242. },
  58243. onStoreRemove: function() {
  58244. this.doRefresh();
  58245. },
  58246. onStoreUpdate: function(store, record, newIndex, oldIndex) {
  58247. var me = this,
  58248. scroller = me.container.getScrollable().getScroller(),
  58249. item;
  58250. oldIndex = (typeof oldIndex === 'undefined') ? newIndex : oldIndex;
  58251. if (oldIndex !== newIndex) {
  58252. // Just refreshing the list here saves a lot of code and shouldnt be much slower
  58253. me.doRefresh();
  58254. }
  58255. else {
  58256. if (newIndex >= me.topItemIndex && newIndex < me.topItemIndex + me.listItems.length) {
  58257. item = me.getItemAt(newIndex);
  58258. me.doUpdateListItem(item, newIndex, me.getListItemInfo());
  58259. // Bypassing setter because sometimes we pass the same record (different data)
  58260. //me.updateListItem(me.getItemAt(newIndex), newIndex, me.getListItemInfo());
  58261. if (me.getVariableHeights() && me.getRefreshHeightOnUpdate()) {
  58262. me.updatedItems.push(item);
  58263. me.updateItemHeights();
  58264. me.refreshScroller(scroller);
  58265. }
  58266. }
  58267. }
  58268. },
  58269. /*
  58270. * @private
  58271. * This is to fix the variable heights again since the item height might have changed after the update
  58272. */
  58273. refreshScroller: function(scroller) {
  58274. if (!scroller) {
  58275. scroller = this.container.getScrollable().getScroller()
  58276. }
  58277. scroller.scrollTo(0, scroller.position.y + 1);
  58278. scroller.scrollTo(0, scroller.position.y - 1);
  58279. },
  58280. onStoreClear: function() {
  58281. if (this.headerTranslate) {
  58282. this.headerTranslate.translate(0, -10000);
  58283. }
  58284. this.showEmptyText();
  58285. },
  58286. onIndex: function(indexBar, index) {
  58287. var me = this,
  58288. key = index.toLowerCase(),
  58289. store = me.getStore(),
  58290. groups = store.getGroups(),
  58291. ln = groups.length,
  58292. scrollable = me.container.getScrollable(),
  58293. scroller, group, i, closest, id;
  58294. if (scrollable) {
  58295. scroller = scrollable.getScroller();
  58296. }
  58297. else {
  58298. return;
  58299. }
  58300. for (i = 0; i < ln; i++) {
  58301. group = groups[i];
  58302. id = group.name.toLowerCase();
  58303. if (id == key || id > key) {
  58304. closest = group;
  58305. break;
  58306. }
  58307. else {
  58308. closest = group;
  58309. }
  58310. }
  58311. if (scrollable && closest) {
  58312. index = store.indexOf(closest.children[0]);
  58313. //stop the scroller from scrolling
  58314. scroller.stopAnimation();
  58315. //make sure the new offsetTop is not out of bounds for the scroller
  58316. var containerSize = scroller.getContainerSize().y,
  58317. size = scroller.getSize().y,
  58318. maxOffset = size - containerSize,
  58319. offsetTop = me.getItemMap().map[index],
  58320. offset = (offsetTop > maxOffset) ? maxOffset : offsetTop;
  58321. // This is kind of hacky, but since there might be variable heights we have to render the frame
  58322. // twice. First to update all the content, then to read the heights and translate items accordingly
  58323. scroller.scrollTo(0, offset);
  58324. if (this.updatedItems.length > 0 && (!this.scheduledTasks || this.scheduledTasks.length === 0)) {
  58325. this.refreshScroller();
  58326. }
  58327. //scroller.scrollTo(0, offset);
  58328. }
  58329. },
  58330. applyOnItemDisclosure: function(config) {
  58331. if (Ext.isFunction(config)) {
  58332. return {
  58333. scope: this,
  58334. handler: config
  58335. };
  58336. }
  58337. return config;
  58338. },
  58339. handleItemDisclosure: function(e) {
  58340. var me = this,
  58341. item = Ext.getCmp(Ext.get(e.getTarget()).up('.x-list-item').id),
  58342. index = item.dataIndex,
  58343. record = me.getStore().getAt(index);
  58344. me.fireAction('disclose', [me, record, item, index, e], 'doDisclose');
  58345. },
  58346. doDisclose: function(me, record, item, index, e) {
  58347. var onItemDisclosure = me.getOnItemDisclosure();
  58348. if (onItemDisclosure && onItemDisclosure.handler) {
  58349. onItemDisclosure.handler.call(onItemDisclosure.scope || me, record, item, index, e);
  58350. }
  58351. },
  58352. updateItemCls: function(newCls, oldCls) {
  58353. var items = this.listItems,
  58354. ln = items.length,
  58355. i, item;
  58356. for (i = 0; i < ln; i++) {
  58357. item = items[i];
  58358. item.removeCls(oldCls);
  58359. item.addCls(newCls);
  58360. }
  58361. },
  58362. onItemTouchStart: function(e) {
  58363. this.container.innerElement.on({
  58364. touchmove: 'onItemTouchMove',
  58365. delegate: '.' + Ext.baseCSSPrefix + 'list-item-body',
  58366. single: true,
  58367. scope: this
  58368. });
  58369. this.callParent(this.parseEvent(e));
  58370. },
  58371. onItemTouchMove: function(e) {
  58372. this.callParent(this.parseEvent(e));
  58373. },
  58374. onItemTouchEnd: function(e) {
  58375. this.container.innerElement.un({
  58376. touchmove: 'onItemTouchMove',
  58377. delegate: '.' + Ext.baseCSSPrefix + 'list-item-body',
  58378. scope: this
  58379. });
  58380. this.callParent(this.parseEvent(e));
  58381. },
  58382. onItemTap: function(e) {
  58383. this.callParent(this.parseEvent(e));
  58384. },
  58385. onItemTapHold: function(e) {
  58386. this.callParent(this.parseEvent(e));
  58387. },
  58388. onItemSingleTap: function(e) {
  58389. this.callParent(this.parseEvent(e));
  58390. },
  58391. onItemDoubleTap: function(e) {
  58392. this.callParent(this.parseEvent(e));
  58393. },
  58394. onItemSwipe: function(e) {
  58395. this.callParent(this.parseEvent(e));
  58396. },
  58397. parseEvent: function(e) {
  58398. var me = this,
  58399. target = Ext.fly(e.getTarget()).findParent('.' + Ext.baseCSSPrefix + 'list-item', 8),
  58400. item = Ext.getCmp(target.id);
  58401. return [me, item, item.dataIndex, e];
  58402. },
  58403. onItemAdd: function(item) {
  58404. var me = this,
  58405. config = item.config;
  58406. if (config.scrollDock) {
  58407. if (config.scrollDock == 'bottom') {
  58408. me.scrollDockItems.bottom.push(item);
  58409. } else {
  58410. me.scrollDockItems.top.push(item);
  58411. }
  58412. item.addCls(Ext.baseCSSPrefix + 'list-scrolldock-hidden');
  58413. if (me.container) {
  58414. me.container.add(item);
  58415. }
  58416. } else {
  58417. me.callParent(arguments);
  58418. }
  58419. },
  58420. destroy: function() {
  58421. Ext.destroy(this.getIndexBar(), this.indexBarElement, this.header);
  58422. if (this.intervalId) {
  58423. cancelAnimationFrame(this.intervalId);
  58424. delete this.intervalId;
  58425. }
  58426. this.callParent();
  58427. }
  58428. });
  58429. /**
  58430. * NestedList provides a miller column interface to navigate between nested sets
  58431. * and provide a clean interface with limited screen real-estate.
  58432. *
  58433. * @example miniphone preview
  58434. * var data = {
  58435. * text: 'Groceries',
  58436. * items: [{
  58437. * text: 'Drinks',
  58438. * items: [{
  58439. * text: 'Water',
  58440. * items: [{
  58441. * text: 'Sparkling',
  58442. * leaf: true
  58443. * }, {
  58444. * text: 'Still',
  58445. * leaf: true
  58446. * }]
  58447. * }, {
  58448. * text: 'Coffee',
  58449. * leaf: true
  58450. * }, {
  58451. * text: 'Espresso',
  58452. * leaf: true
  58453. * }, {
  58454. * text: 'Redbull',
  58455. * leaf: true
  58456. * }, {
  58457. * text: 'Coke',
  58458. * leaf: true
  58459. * }, {
  58460. * text: 'Diet Coke',
  58461. * leaf: true
  58462. * }]
  58463. * }, {
  58464. * text: 'Fruit',
  58465. * items: [{
  58466. * text: 'Bananas',
  58467. * leaf: true
  58468. * }, {
  58469. * text: 'Lemon',
  58470. * leaf: true
  58471. * }]
  58472. * }, {
  58473. * text: 'Snacks',
  58474. * items: [{
  58475. * text: 'Nuts',
  58476. * leaf: true
  58477. * }, {
  58478. * text: 'Pretzels',
  58479. * leaf: true
  58480. * }, {
  58481. * text: 'Wasabi Peas',
  58482. * leaf: true
  58483. * }]
  58484. * }]
  58485. * };
  58486. *
  58487. * Ext.define('ListItem', {
  58488. * extend: 'Ext.data.Model',
  58489. * config: {
  58490. * fields: [{
  58491. * name: 'text',
  58492. * type: 'string'
  58493. * }]
  58494. * }
  58495. * });
  58496. *
  58497. * var store = Ext.create('Ext.data.TreeStore', {
  58498. * model: 'ListItem',
  58499. * defaultRootProperty: 'items',
  58500. * root: data
  58501. * });
  58502. *
  58503. * var nestedList = Ext.create('Ext.NestedList', {
  58504. * fullscreen: true,
  58505. * title: 'Groceries',
  58506. * displayField: 'text',
  58507. * store: store
  58508. * });
  58509. *
  58510. * @aside guide nested_list
  58511. * @aside example nested-list
  58512. * @aside example navigation-view
  58513. */
  58514. Ext.define('Ext.dataview.NestedList', {
  58515. alternateClassName: 'Ext.NestedList',
  58516. extend: 'Ext.Container',
  58517. xtype: 'nestedlist',
  58518. requires: [
  58519. 'Ext.List',
  58520. 'Ext.TitleBar',
  58521. 'Ext.Button',
  58522. 'Ext.XTemplate',
  58523. 'Ext.data.StoreManager',
  58524. 'Ext.data.NodeStore',
  58525. 'Ext.data.TreeStore'
  58526. ],
  58527. config: {
  58528. /**
  58529. * @cfg
  58530. * @inheritdoc
  58531. */
  58532. cls: Ext.baseCSSPrefix + 'nested-list',
  58533. /**
  58534. * @cfg {String/Object/Boolean} cardSwitchAnimation
  58535. * Animation to be used during transitions of cards.
  58536. * @removed 2.0.0 please use {@link Ext.layout.Card#animation}
  58537. */
  58538. /**
  58539. * @cfg {String} backText
  58540. * The label to display for the back button.
  58541. * @accessor
  58542. */
  58543. backText: 'Back',
  58544. /**
  58545. * @cfg {Boolean} useTitleAsBackText
  58546. * `true` to use title as a label for back button.
  58547. * @accessor
  58548. */
  58549. useTitleAsBackText: true,
  58550. /**
  58551. * @cfg {Boolean} updateTitleText
  58552. * Update the title with the currently selected category.
  58553. * @accessor
  58554. */
  58555. updateTitleText: true,
  58556. /**
  58557. * @cfg {String} displayField
  58558. * Display field to use when setting item text and title.
  58559. * This configuration is ignored when overriding {@link #getItemTextTpl} or
  58560. * {@link #getTitleTextTpl} for the item text or title.
  58561. * @accessor
  58562. */
  58563. displayField: 'text',
  58564. /**
  58565. * @cfg {String} loadingText
  58566. * Loading text to display when a subtree is loading.
  58567. * @accessor
  58568. */
  58569. loadingText: 'Loading...',
  58570. /**
  58571. * @cfg {String} emptyText
  58572. * Empty text to display when a subtree is empty.
  58573. * @accessor
  58574. */
  58575. emptyText: 'No items available.',
  58576. /**
  58577. * @cfg {Boolean/Function} onItemDisclosure
  58578. * Maps to the {@link Ext.List#onItemDisclosure} configuration for individual lists.
  58579. * @accessor
  58580. */
  58581. onItemDisclosure: false,
  58582. /**
  58583. * @cfg {Boolean} allowDeselect
  58584. * Set to `true` to allow the user to deselect leaf items via interaction.
  58585. * @accessor
  58586. */
  58587. allowDeselect: false,
  58588. /**
  58589. * @deprecated 2.0.0 Please set the {@link #toolbar} configuration to `false` instead
  58590. * @cfg {Boolean} useToolbar `true` to show the header toolbar.
  58591. * @accessor
  58592. */
  58593. useToolbar: null,
  58594. /**
  58595. * @cfg {Ext.Toolbar/Object/Boolean} toolbar
  58596. * The configuration to be used for the toolbar displayed in this nested list.
  58597. * @accessor
  58598. */
  58599. toolbar: {
  58600. docked: 'top',
  58601. xtype: 'titlebar',
  58602. ui: 'light',
  58603. inline: true
  58604. },
  58605. /**
  58606. * @cfg {String} title The title of the toolbar
  58607. * @accessor
  58608. */
  58609. title: '',
  58610. /**
  58611. * @cfg {String} layout
  58612. * @hide
  58613. * @accessor
  58614. */
  58615. layout: {
  58616. type: 'card',
  58617. animation: {
  58618. type: 'slide',
  58619. duration: 250,
  58620. direction: 'left'
  58621. }
  58622. },
  58623. /**
  58624. * @cfg {Ext.data.TreeStore/String} store The tree store to be used for this nested list.
  58625. */
  58626. store: null,
  58627. /**
  58628. * @cfg {Ext.Container} detailContainer The container of the `detailCard`.
  58629. * @accessor
  58630. */
  58631. detailContainer: undefined,
  58632. /**
  58633. * @cfg {Ext.Component} detailCard to provide a final card for leaf nodes.
  58634. * @accessor
  58635. */
  58636. detailCard: null,
  58637. /**
  58638. * @cfg {Object} backButton The configuration for the back button used in the nested list.
  58639. */
  58640. backButton: {
  58641. ui: 'back',
  58642. hidden: true
  58643. },
  58644. /**
  58645. * @cfg {Object} listConfig An optional config object which is merged with the default
  58646. * configuration used to create each nested list.
  58647. */
  58648. listConfig: null,
  58649. // @private
  58650. lastNode: null,
  58651. // @private
  58652. lastActiveList: null
  58653. },
  58654. /**
  58655. * @event itemtap
  58656. * Fires when a node is tapped on.
  58657. * @param {Ext.dataview.NestedList} this
  58658. * @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.
  58659. * @param {Number} index The index of the item tapped.
  58660. * @param {Ext.dom.Element} target The element tapped.
  58661. * @param {Ext.data.Record} record The record tapped.
  58662. * @param {Ext.event.Event} e The event object.
  58663. */
  58664. /**
  58665. * @event itemdoubletap
  58666. * Fires when a node is double tapped on.
  58667. * @param {Ext.dataview.NestedList} this
  58668. * @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.
  58669. * @param {Number} index The index of the item that was tapped.
  58670. * @param {Ext.dom.Element} target The element tapped.
  58671. * @param {Ext.data.Record} record The record tapped.
  58672. * @param {Ext.event.Event} e The event object.
  58673. */
  58674. /**
  58675. * @event containertap
  58676. * Fires when a tap occurs and it is not on a template node.
  58677. * @param {Ext.dataview.NestedList} this
  58678. * @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.
  58679. * @param {Ext.event.Event} e The raw event object.
  58680. */
  58681. /**
  58682. * @event selectionchange
  58683. * Fires when the selected nodes change.
  58684. * @param {Ext.dataview.NestedList} this
  58685. * @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.
  58686. * @param {Array} selections Array of the selected nodes.
  58687. */
  58688. /**
  58689. * @event beforeselectionchange
  58690. * Fires before a selection is made.
  58691. * @param {Ext.dataview.NestedList} this
  58692. * @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.
  58693. * @param {HTMLElement} node The node to be selected.
  58694. * @param {Array} selections Array of currently selected nodes.
  58695. * @deprecated 2.0.0 Please listen to the {@link #selectionchange} event with an order of `before` instead.
  58696. */
  58697. /**
  58698. * @event listchange
  58699. * Fires when the user taps a list item.
  58700. * @param {Ext.dataview.NestedList} this
  58701. * @param {Object} listitem The new active list.
  58702. */
  58703. /**
  58704. * @event leafitemtap
  58705. * Fires when the user taps a leaf list item.
  58706. * @param {Ext.dataview.NestedList} this
  58707. * @param {Ext.List} list The subList the item is on.
  58708. * @param {Number} index The index of the item tapped.
  58709. * @param {Ext.dom.Element} target The element tapped.
  58710. * @param {Ext.data.Record} record The record tapped.
  58711. * @param {Ext.event.Event} e The event.
  58712. */
  58713. /**
  58714. * @event back
  58715. * @preventable doBack
  58716. * Fires when the user taps Back.
  58717. * @param {Ext.dataview.NestedList} this
  58718. * @param {HTMLElement} node The node to be selected.
  58719. * @param {Ext.dataview.List} lastActiveList The Ext.dataview.List that was last active.
  58720. * @param {Boolean} detailCardActive Flag set if the detail card is currently active.
  58721. */
  58722. /**
  58723. * @event beforeload
  58724. * Fires before a request is made for a new data object.
  58725. * @param {Ext.dataview.NestedList} this
  58726. * @param {Ext.data.Store} store The store instance.
  58727. * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to
  58728. * load the Store.
  58729. */
  58730. /**
  58731. * @event load
  58732. * Fires whenever records have been loaded into the store.
  58733. * @param {Ext.dataview.NestedList} this
  58734. * @param {Ext.data.Store} store The store instance.
  58735. * @param {Ext.util.Grouper[]} records An array of records.
  58736. * @param {Boolean} successful `true` if the operation was successful.
  58737. * @param {Ext.data.Operation} operation The associated operation.
  58738. */
  58739. constructor: function (config) {
  58740. if (Ext.isObject(config)) {
  58741. if (config.getTitleTextTpl) {
  58742. this.getTitleTextTpl = config.getTitleTextTpl;
  58743. }
  58744. if (config.getItemTextTpl) {
  58745. this.getItemTextTpl = config.getItemTextTpl;
  58746. }
  58747. }
  58748. this.callParent(arguments);
  58749. },
  58750. onItemInteraction: function () {
  58751. if (this.isGoingTo) {
  58752. return false;
  58753. }
  58754. },
  58755. applyDetailContainer: function (config) {
  58756. if (!config) {
  58757. config = this;
  58758. }
  58759. return config;
  58760. },
  58761. updateDetailContainer: function (newContainer, oldContainer) {
  58762. if (newContainer) {
  58763. newContainer.onBefore('activeitemchange', 'onBeforeDetailContainerChange', this);
  58764. newContainer.onAfter('activeitemchange', 'onDetailContainerChange', this);
  58765. }
  58766. },
  58767. onBeforeDetailContainerChange: function () {
  58768. this.isGoingTo = true;
  58769. },
  58770. onDetailContainerChange: function () {
  58771. this.isGoingTo = false;
  58772. },
  58773. /**
  58774. * Called when an list item has been tapped.
  58775. * @param {Ext.List} list The subList the item is on.
  58776. * @param {Number} index The id of the item tapped.
  58777. * @param {Ext.Element} target The list item tapped.
  58778. * @param {Ext.data.Record} record The record which as tapped.
  58779. * @param {Ext.event.Event} e The event.
  58780. */
  58781. onItemTap: function (list, index, target, record, e) {
  58782. var me = this,
  58783. store = list.getStore(),
  58784. node = store.getAt(index);
  58785. me.fireEvent('itemtap', this, list, index, target, record, e);
  58786. if (node.isLeaf()) {
  58787. me.fireEvent('leafitemtap', this, list, index, target, record, e);
  58788. me.goToLeaf(node);
  58789. }
  58790. else {
  58791. this.goToNode(node);
  58792. }
  58793. },
  58794. onBeforeSelect: function () {
  58795. this.fireEvent.apply(this, [].concat('beforeselect', this, Array.prototype.slice.call(arguments)));
  58796. },
  58797. onContainerTap: function () {
  58798. this.fireEvent.apply(this, [].concat('containertap', this, Array.prototype.slice.call(arguments)));
  58799. },
  58800. onSelectionChange: function () {
  58801. this.fireEvent.apply(this, [].concat('selectionchange', this, Array.prototype.slice.call(arguments)));
  58802. },
  58803. onItemDoubleTap: function () {
  58804. this.fireEvent.apply(this, [].concat('itemdoubletap', this, Array.prototype.slice.call(arguments)));
  58805. },
  58806. onStoreBeforeLoad: function () {
  58807. var loadingText = this.getLoadingText(),
  58808. scrollable = this.getScrollable();
  58809. if (loadingText) {
  58810. this.setMasked({
  58811. xtype: 'loadmask',
  58812. message: loadingText
  58813. });
  58814. //disable scrolling while it is masked
  58815. if (scrollable) {
  58816. scrollable.getScroller().setDisabled(true);
  58817. }
  58818. }
  58819. this.fireEvent.apply(this, [].concat('beforeload', this, Array.prototype.slice.call(arguments)));
  58820. },
  58821. onStoreLoad: function (store, records, successful, operation) {
  58822. this.setMasked(false);
  58823. this.fireEvent.apply(this, [].concat('load', this, Array.prototype.slice.call(arguments)));
  58824. if (store.indexOf(this.getLastNode()) === -1) {
  58825. this.goToNode(store.getRoot());
  58826. }
  58827. },
  58828. /**
  58829. * Called when the backButton has been tapped.
  58830. */
  58831. onBackTap: function () {
  58832. var me = this,
  58833. node = me.getLastNode(),
  58834. detailCard = me.getDetailCard(),
  58835. detailCardActive = detailCard && me.getActiveItem() == detailCard,
  58836. lastActiveList = me.getLastActiveList();
  58837. this.fireAction('back', [this, node, lastActiveList, detailCardActive], 'doBack');
  58838. },
  58839. doBack: function (me, node, lastActiveList, detailCardActive) {
  58840. var layout = me.getLayout(),
  58841. animation = (layout) ? layout.getAnimation() : null;
  58842. if (detailCardActive && lastActiveList) {
  58843. if (animation) {
  58844. animation.setReverse(true);
  58845. }
  58846. me.setActiveItem(lastActiveList);
  58847. me.setLastNode(node.parentNode);
  58848. me.syncToolbar();
  58849. }
  58850. else {
  58851. this.goToNode(node.parentNode);
  58852. }
  58853. },
  58854. updateData: function (data) {
  58855. if (!this.getStore()) {
  58856. this.setStore(new Ext.data.TreeStore({
  58857. root: data
  58858. }));
  58859. }
  58860. },
  58861. applyStore: function (store) {
  58862. if (store) {
  58863. if (Ext.isString(store)) {
  58864. // store id
  58865. store = Ext.data.StoreManager.get(store);
  58866. } else {
  58867. // store instance or store config
  58868. if (!(store instanceof Ext.data.TreeStore)) {
  58869. store = Ext.factory(store, Ext.data.TreeStore, null);
  58870. }
  58871. }
  58872. // <debug>
  58873. if (!store) {
  58874. Ext.Logger.warn("The specified Store cannot be found", this);
  58875. }
  58876. //</debug>
  58877. }
  58878. return store;
  58879. },
  58880. storeListeners: {
  58881. rootchange: 'onStoreRootChange',
  58882. load: 'onStoreLoad',
  58883. beforeload: 'onStoreBeforeLoad'
  58884. },
  58885. updateStore: function (newStore, oldStore) {
  58886. var me = this,
  58887. listeners = this.storeListeners;
  58888. listeners.scope = me;
  58889. if (oldStore && Ext.isObject(oldStore) && oldStore.isStore) {
  58890. if (oldStore.autoDestroy) {
  58891. oldStore.destroy();
  58892. }
  58893. oldStore.un(listeners);
  58894. }
  58895. if (newStore) {
  58896. me.goToNode(newStore.getRoot());
  58897. newStore.on(listeners);
  58898. }
  58899. },
  58900. onStoreRootChange: function (store, node) {
  58901. this.goToNode(node);
  58902. },
  58903. applyBackButton: function (config) {
  58904. return Ext.factory(config, Ext.Button, this.getBackButton());
  58905. },
  58906. applyDetailCard: function (config, oldDetailCard) {
  58907. if (config === null) {
  58908. return Ext.factory(config, Ext.Component, oldDetailCard);
  58909. } else {
  58910. return Ext.factory(config, Ext.Component);
  58911. }
  58912. },
  58913. updateBackButton: function (newButton, oldButton) {
  58914. if (newButton) {
  58915. var me = this;
  58916. newButton.on('tap', me.onBackTap, me);
  58917. newButton.setText(me.getBackText());
  58918. me.getToolbar().insert(0, newButton);
  58919. }
  58920. else if (oldButton) {
  58921. oldButton.destroy();
  58922. }
  58923. },
  58924. applyToolbar: function (config) {
  58925. return Ext.factory(config, Ext.TitleBar, this.getToolbar());
  58926. },
  58927. updateToolbar: function (newToolbar, oldToolbar) {
  58928. var me = this;
  58929. if (newToolbar) {
  58930. newToolbar.setTitle(me.getTitle());
  58931. if (!newToolbar.getParent()) {
  58932. me.add(newToolbar);
  58933. }
  58934. }
  58935. else if (oldToolbar) {
  58936. oldToolbar.destroy();
  58937. }
  58938. },
  58939. updateUseToolbar: function (newUseToolbar, oldUseToolbar) {
  58940. if (!newUseToolbar) {
  58941. this.setToolbar(false);
  58942. }
  58943. },
  58944. updateTitle: function (newTitle) {
  58945. var me = this,
  58946. toolbar = me.getToolbar();
  58947. if (toolbar) {
  58948. if (me.getUpdateTitleText()) {
  58949. toolbar.setTitle(newTitle);
  58950. }
  58951. }
  58952. },
  58953. /**
  58954. * Override this method to provide custom template rendering of individual
  58955. * nodes. The template will receive all data within the Record and will also
  58956. * receive whether or not it is a leaf node.
  58957. * @param {Ext.data.Record} node
  58958. * @return {String}
  58959. */
  58960. getItemTextTpl: function (node) {
  58961. return '{' + this.getDisplayField() + '}';
  58962. },
  58963. /**
  58964. * Override this method to provide custom template rendering of titles/back
  58965. * buttons when {@link #useTitleAsBackText} is enabled.
  58966. * @param {Ext.data.Record} node
  58967. * @return {String}
  58968. */
  58969. getTitleTextTpl: function (node) {
  58970. return '{' + this.getDisplayField() + '}';
  58971. },
  58972. /**
  58973. * @private
  58974. */
  58975. renderTitleText: function (node, forBackButton) {
  58976. if (!node.titleTpl) {
  58977. node.titleTpl = Ext.create('Ext.XTemplate', this.getTitleTextTpl(node));
  58978. }
  58979. if (node.isRoot()) {
  58980. var initialTitle = this.getInitialConfig('title');
  58981. return (forBackButton && initialTitle === '') ? this.getInitialConfig('backText') : initialTitle;
  58982. }
  58983. return node.titleTpl.applyTemplate(node.data);
  58984. },
  58985. /**
  58986. * Method to handle going to a specific node within this nested list. Node must be part of the
  58987. * internal {@link #store}.
  58988. * @param {Ext.data.NodeInterface} node The specified node to navigate to.
  58989. */
  58990. goToNode: function (node) {
  58991. if (!node) {
  58992. return;
  58993. }
  58994. var me = this,
  58995. activeItem = me.getActiveItem(),
  58996. detailCard = me.getDetailCard(),
  58997. detailCardActive = detailCard && me.getActiveItem() == detailCard,
  58998. reverse = me.goToNodeReverseAnimation(node),
  58999. firstList = me.firstList,
  59000. secondList = me.secondList,
  59001. layout = me.getLayout(),
  59002. animation = (layout) ? layout.getAnimation() : null,
  59003. list;
  59004. //if the node is a leaf, throw an error
  59005. if (node.isLeaf()) {
  59006. throw new Error('goToNode: passed a node which is a leaf.');
  59007. }
  59008. //if we are currently at the passed node, do nothing.
  59009. if (node == me.getLastNode() && !detailCardActive) {
  59010. return;
  59011. }
  59012. if (detailCardActive) {
  59013. if (animation) {
  59014. animation.setReverse(true);
  59015. }
  59016. list = me.getLastActiveList();
  59017. list.getStore().setNode(node);
  59018. node.expand();
  59019. me.setActiveItem(list);
  59020. }
  59021. else {
  59022. if (firstList && secondList) {
  59023. //firstList and secondList have both been created
  59024. activeItem = me.getActiveItem();
  59025. me.setLastActiveList(activeItem);
  59026. list = (activeItem == firstList) ? secondList : firstList;
  59027. list.getStore().setNode(node);
  59028. node.expand();
  59029. if (animation) {
  59030. animation.setReverse(reverse);
  59031. }
  59032. me.setActiveItem(list);
  59033. list.deselectAll();
  59034. }
  59035. else if (firstList) {
  59036. //only firstList has been created
  59037. me.setLastActiveList(me.getActiveItem());
  59038. me.setActiveItem(me.getList(node));
  59039. me.secondList = me.getActiveItem();
  59040. }
  59041. else {
  59042. //no lists have been created
  59043. me.setActiveItem(me.getList(node));
  59044. me.firstList = me.getActiveItem();
  59045. }
  59046. }
  59047. me.fireEvent('listchange', this, me.getActiveItem());
  59048. me.setLastNode(node);
  59049. me.syncToolbar();
  59050. },
  59051. /**
  59052. * The leaf you want to navigate to. You should pass a node instance.
  59053. * @param {Ext.data.NodeInterface} node The specified node to navigate to.
  59054. */
  59055. goToLeaf: function (node) {
  59056. if (!node.isLeaf()) {
  59057. throw new Error('goToLeaf: passed a node which is not a leaf.');
  59058. }
  59059. var me = this,
  59060. card = me.getDetailCard(node),
  59061. container = me.getDetailContainer(),
  59062. sharedContainer = container == this,
  59063. layout = me.getLayout(),
  59064. animation = (layout) ? layout.getAnimation() : false;
  59065. if (card) {
  59066. if (container.getItems().indexOf(card) === -1) {
  59067. container.add(card);
  59068. }
  59069. if (sharedContainer) {
  59070. if (me.getActiveItem() instanceof Ext.dataview.List) {
  59071. me.setLastActiveList(me.getActiveItem());
  59072. }
  59073. me.setLastNode(node);
  59074. }
  59075. if (animation) {
  59076. animation.setReverse(false);
  59077. }
  59078. container.setActiveItem(card);
  59079. me.syncToolbar();
  59080. }
  59081. },
  59082. /**
  59083. * @private
  59084. * Method which updates the {@link #backButton} and {@link #toolbar} with the latest information from
  59085. * the current node.
  59086. */
  59087. syncToolbar: function (forceDetail) {
  59088. var me = this,
  59089. detailCard = me.getDetailCard(),
  59090. node = me.getLastNode(),
  59091. detailActive = forceDetail || (detailCard && (me.getActiveItem() == detailCard)),
  59092. parentNode = (detailActive) ? node : node.parentNode,
  59093. backButton = me.getBackButton();
  59094. //show/hide the backButton, and update the backButton text, if one exists
  59095. if (backButton) {
  59096. backButton[parentNode ? 'show' : 'hide']();
  59097. if (parentNode && me.getUseTitleAsBackText()) {
  59098. backButton.setText(me.renderTitleText(node.parentNode, true));
  59099. }
  59100. }
  59101. if (node) {
  59102. me.setTitle(me.renderTitleText(node));
  59103. }
  59104. },
  59105. updateBackText: function (newText) {
  59106. this.getBackButton().setText(newText);
  59107. },
  59108. /**
  59109. * @private
  59110. * Returns `true` if the passed node should have a reverse animation from the previous current node.
  59111. * @param {Ext.data.NodeInterface} node
  59112. */
  59113. goToNodeReverseAnimation: function (node) {
  59114. var me = this,
  59115. lastNode = me.getLastNode();
  59116. if (!lastNode) {
  59117. return false;
  59118. }
  59119. return (!lastNode.contains(node) && lastNode.isAncestor(node)) ? true : false;
  59120. },
  59121. /**
  59122. * @private
  59123. * Returns the list config for a specified node.
  59124. * @param {HTMLElement} node The node for the list config.
  59125. */
  59126. getList: function (node) {
  59127. var me = this,
  59128. nodeStore = Ext.create('Ext.data.NodeStore', {
  59129. recursive: false,
  59130. node: node,
  59131. rootVisible: false,
  59132. model: me.getStore().getModel()
  59133. });
  59134. node.expand();
  59135. return Ext.Object.merge({
  59136. xtype: 'list',
  59137. pressedDelay: 250,
  59138. autoDestroy: true,
  59139. store: nodeStore,
  59140. onItemDisclosure: me.getOnItemDisclosure(),
  59141. allowDeselect: me.getAllowDeselect(),
  59142. variableHeights: false,
  59143. listeners: [
  59144. { event: 'itemdoubletap', fn: 'onItemDoubleTap', scope: me },
  59145. { event: 'itemtap', fn: 'onItemInteraction', scope: me, order: 'before'},
  59146. { event: 'itemtouchstart', fn: 'onItemInteraction', scope: me, order: 'before'},
  59147. { event: 'itemtap', fn: 'onItemTap', scope: me },
  59148. { event: 'beforeselectionchange', fn: 'onBeforeSelect', scope: me },
  59149. { event: 'containertap', fn: 'onContainerTap', scope: me },
  59150. { event: 'selectionchange', fn: 'onSelectionChange', order: 'before', scope: me }
  59151. ],
  59152. itemTpl: '<span<tpl if="leaf == true"> class="x-list-item-leaf"</tpl>>' + me.getItemTextTpl(node) + '</span>'
  59153. }, this.getListConfig());
  59154. }
  59155. }, function () {
  59156. });
  59157. /**
  59158. * @private
  59159. */
  59160. Ext.define('Ext.dataview.element.List', {
  59161. extend: 'Ext.dataview.element.Container',
  59162. updateBaseCls: function(newBaseCls) {
  59163. var me = this;
  59164. me.itemClsShortCache = newBaseCls + '-item';
  59165. me.headerClsShortCache = newBaseCls + '-header';
  59166. me.headerClsCache = '.' + me.headerClsShortCache;
  59167. me.headerItemClsShortCache = newBaseCls + '-header-item';
  59168. me.footerClsShortCache = newBaseCls + '-footer-item';
  59169. me.footerClsCache = '.' + me.footerClsShortCache;
  59170. me.labelClsShortCache = newBaseCls + '-item-label';
  59171. me.labelClsCache = '.' + me.labelClsShortCache;
  59172. me.disclosureClsShortCache = newBaseCls + '-disclosure';
  59173. me.disclosureClsCache = '.' + me.disclosureClsShortCache;
  59174. me.iconClsShortCache = newBaseCls + '-icon';
  59175. me.iconClsCache = '.' + me.iconClsShortCache;
  59176. this.callParent(arguments);
  59177. },
  59178. hiddenDisplayCache: Ext.baseCSSPrefix + 'hidden-display',
  59179. getItemElementConfig: function(index, data) {
  59180. var me = this,
  59181. dataview = me.dataview,
  59182. itemCls = dataview.getItemCls(),
  59183. cls = me.itemClsShortCache,
  59184. config, iconSrc;
  59185. if (itemCls) {
  59186. cls += ' ' + itemCls;
  59187. }
  59188. config = {
  59189. cls: cls,
  59190. children: [{
  59191. cls: me.labelClsShortCache,
  59192. html: dataview.getItemTpl().apply(data)
  59193. }]
  59194. };
  59195. if (dataview.getIcon()) {
  59196. iconSrc = data.iconSrc;
  59197. config.children.push({
  59198. cls: me.iconClsShortCache,
  59199. style: 'background-image: ' + iconSrc ? 'url("' + newSrc + '")' : ''
  59200. });
  59201. }
  59202. if (dataview.getOnItemDisclosure()) {
  59203. config.children.push({
  59204. cls: me.disclosureClsShortCache + ' ' + ((data[dataview.getDisclosureProperty()] === false) ? me.hiddenDisplayCache : '')
  59205. });
  59206. }
  59207. return config;
  59208. },
  59209. updateListItem: function(record, item) {
  59210. var me = this,
  59211. dataview = me.dataview,
  59212. extItem = Ext.fly(item),
  59213. innerItem = extItem.down(me.labelClsCache, true),
  59214. data = dataview.prepareData(record.getData(true), dataview.getStore().indexOf(record), record),
  59215. disclosureProperty = dataview.getDisclosureProperty(),
  59216. hasDisclosureProperty = data && data.hasOwnProperty(disclosureProperty),
  59217. iconSrc = data && data.hasOwnProperty('iconSrc'),
  59218. disclosureEl, iconEl;
  59219. innerItem.innerHTML = dataview.getItemTpl().apply(data);
  59220. if (hasDisclosureProperty) {
  59221. disclosureEl = extItem.down(me.disclosureClsCache);
  59222. disclosureEl[data[disclosureProperty] === false ? 'addCls' : 'removeCls'](me.hiddenDisplayCache);
  59223. }
  59224. if (dataview.getIcon()) {
  59225. iconEl = extItem.down(me.iconClsCache, true);
  59226. iconEl.style.backgroundImage = iconSrc ? 'url("' + iconSrc + '")' : '';
  59227. }
  59228. },
  59229. doRemoveHeaders: function() {
  59230. var me = this,
  59231. headerClsShortCache = me.headerItemClsShortCache,
  59232. existingHeaders = me.element.query(me.headerClsCache),
  59233. existingHeadersLn = existingHeaders.length,
  59234. i = 0,
  59235. item;
  59236. for (; i < existingHeadersLn; i++) {
  59237. item = existingHeaders[i];
  59238. Ext.fly(item.parentNode).removeCls(headerClsShortCache);
  59239. Ext.get(item).destroy();
  59240. }
  59241. },
  59242. doRemoveFooterCls: function() {
  59243. var me = this,
  59244. footerClsShortCache = me.footerClsShortCache,
  59245. existingFooters = me.element.query(me.footerClsCache),
  59246. existingFootersLn = existingFooters.length,
  59247. i = 0;
  59248. for (; i < existingFootersLn; i++) {
  59249. Ext.fly(existingFooters[i]).removeCls(footerClsShortCache);
  59250. }
  59251. },
  59252. doAddHeader: function(item, html) {
  59253. item = Ext.fly(item);
  59254. if (html) {
  59255. item.insertFirst(Ext.Element.create({
  59256. cls: this.headerClsShortCache,
  59257. html: html
  59258. }));
  59259. }
  59260. item.addCls(this.headerItemClsShortCache);
  59261. },
  59262. destroy: function() {
  59263. this.doRemoveHeaders();
  59264. this.callParent();
  59265. }
  59266. });
  59267. /**
  59268. * @private
  59269. *
  59270. * This object handles communication between the WebView and Sencha's native shell.
  59271. * Currently it has two primary responsibilities:
  59272. *
  59273. * 1. Maintaining unique string ids for callback functions, together with their scope objects
  59274. * 2. Serializing given object data into HTTP GET request parameters
  59275. *
  59276. * As an example, to capture a photo from the device's camera, we use `Ext.device.Camera.capture()` like:
  59277. *
  59278. * Ext.device.Camera.capture(
  59279. * function(dataUri){
  59280. * // Do something with the base64-encoded `dataUri` string
  59281. * },
  59282. * function(errorMessage) {
  59283. *
  59284. * },
  59285. * callbackScope,
  59286. * {
  59287. * quality: 75,
  59288. * width: 500,
  59289. * height: 500
  59290. * }
  59291. * );
  59292. *
  59293. * Internally, `Ext.device.Communicator.send()` will then be invoked with the following argument:
  59294. *
  59295. * Ext.device.Communicator.send({
  59296. * command: 'Camera#capture',
  59297. * callbacks: {
  59298. * onSuccess: function() {
  59299. * // ...
  59300. * },
  59301. * onError: function() {
  59302. * // ...
  59303. * }
  59304. * },
  59305. * scope: callbackScope,
  59306. * quality: 75,
  59307. * width: 500,
  59308. * height: 500
  59309. * });
  59310. *
  59311. * Which will then be transformed into a HTTP GET request, sent to native shell's local
  59312. * HTTP server with the following parameters:
  59313. *
  59314. * ?quality=75&width=500&height=500&command=Camera%23capture&onSuccess=3&onError=5
  59315. *
  59316. * Notice that `onSuccess` and `onError` have been converted into string ids (`3` and `5`
  59317. * respectively) and maintained by `Ext.device.Communicator`.
  59318. *
  59319. * Whenever the requested operation finishes, `Ext.device.Communicator.invoke()` simply needs
  59320. * to be executed from the native shell with the corresponding ids given before. For example:
  59321. *
  59322. * Ext.device.Communicator.invoke('3', ['DATA_URI_OF_THE_CAPTURED_IMAGE_HERE']);
  59323. *
  59324. * will invoke the original `onSuccess` callback under the given scope. (`callbackScope`), with
  59325. * the first argument of 'DATA_URI_OF_THE_CAPTURED_IMAGE_HERE'
  59326. *
  59327. * Note that `Ext.device.Communicator` maintains the uniqueness of each function callback and
  59328. * its scope object. If subsequent calls to `Ext.device.Communicator.send()` have the same
  59329. * callback references, the same old ids will simply be reused, which guarantee the best possible
  59330. * performance for a large amount of repetitive calls.
  59331. */
  59332. Ext.define('Ext.device.communicator.Default', {
  59333. SERVER_URL: 'http://localhost:3000', // Change this to the correct server URL
  59334. callbackDataMap: {},
  59335. callbackIdMap: {},
  59336. idSeed: 0,
  59337. globalScopeId: '0',
  59338. generateId: function() {
  59339. return String(++this.idSeed);
  59340. },
  59341. getId: function(object) {
  59342. var id = object.$callbackId;
  59343. if (!id) {
  59344. object.$callbackId = id = this.generateId();
  59345. }
  59346. return id;
  59347. },
  59348. getCallbackId: function(callback, scope) {
  59349. var idMap = this.callbackIdMap,
  59350. dataMap = this.callbackDataMap,
  59351. id, scopeId, callbackId, data;
  59352. if (!scope) {
  59353. scopeId = this.globalScopeId;
  59354. }
  59355. else if (scope.isIdentifiable) {
  59356. scopeId = scope.getId();
  59357. }
  59358. else {
  59359. scopeId = this.getId(scope);
  59360. }
  59361. callbackId = this.getId(callback);
  59362. if (!idMap[scopeId]) {
  59363. idMap[scopeId] = {};
  59364. }
  59365. if (!idMap[scopeId][callbackId]) {
  59366. id = this.generateId();
  59367. data = {
  59368. callback: callback,
  59369. scope: scope
  59370. };
  59371. idMap[scopeId][callbackId] = id;
  59372. dataMap[id] = data;
  59373. }
  59374. return idMap[scopeId][callbackId];
  59375. },
  59376. getCallbackData: function(id) {
  59377. return this.callbackDataMap[id];
  59378. },
  59379. invoke: function(id, args) {
  59380. var data = this.getCallbackData(id);
  59381. data.callback.apply(data.scope, args);
  59382. },
  59383. send: function(args) {
  59384. var callbacks, scope, name, callback;
  59385. if (!args) {
  59386. args = {};
  59387. }
  59388. else if (args.callbacks) {
  59389. callbacks = args.callbacks;
  59390. scope = args.scope;
  59391. delete args.callbacks;
  59392. delete args.scope;
  59393. for (name in callbacks) {
  59394. if (callbacks.hasOwnProperty(name)) {
  59395. callback = callbacks[name];
  59396. if (typeof callback == 'function') {
  59397. args[name] = this.getCallbackId(callback, scope);
  59398. }
  59399. }
  59400. }
  59401. }
  59402. this.doSend(args);
  59403. },
  59404. doSend: function(args) {
  59405. var xhr = new XMLHttpRequest();
  59406. xhr.open('GET', this.SERVER_URL + '?' + Ext.Object.toQueryString(args) + '&_dc=' + new Date().getTime(), false);
  59407. // wrap the request in a try/catch block so we can check if any errors are thrown and attempt to call any
  59408. // failure/callback functions if defined
  59409. try {
  59410. xhr.send(null);
  59411. } catch(e) {
  59412. if (args.failure) {
  59413. this.invoke(args.failure);
  59414. } else if (args.callback) {
  59415. this.invoke(args.callback);
  59416. }
  59417. }
  59418. }
  59419. });
  59420. /**
  59421. * @private
  59422. */
  59423. Ext.define('Ext.device.communicator.Android', {
  59424. extend: 'Ext.device.communicator.Default',
  59425. doSend: function(args) {
  59426. window.Sencha.action(JSON.stringify(args));
  59427. }
  59428. });
  59429. /**
  59430. * @private
  59431. */
  59432. Ext.define('Ext.device.Communicator', {
  59433. requires: [
  59434. 'Ext.device.communicator.Default',
  59435. 'Ext.device.communicator.Android'
  59436. ],
  59437. singleton: true,
  59438. constructor: function() {
  59439. if (Ext.os.is.Android) {
  59440. return new Ext.device.communicator.Android();
  59441. }
  59442. return new Ext.device.communicator.Default();
  59443. }
  59444. });
  59445. /**
  59446. * @private
  59447. */
  59448. Ext.define('Ext.device.camera.Abstract', {
  59449. source: {
  59450. library: 0,
  59451. camera: 1,
  59452. album: 2
  59453. },
  59454. destination: {
  59455. data: 0, // Returns base64-encoded string
  59456. file: 1 // Returns file's URI
  59457. },
  59458. encoding: {
  59459. jpeg: 0,
  59460. jpg: 0,
  59461. png: 1
  59462. },
  59463. /**
  59464. * Allows you to capture a photo.
  59465. *
  59466. * @param {Object} options
  59467. * The options to use when taking a photo.
  59468. *
  59469. * @param {Function} options.success
  59470. * The success callback which is called when the photo has been taken.
  59471. *
  59472. * @param {String} options.success.image
  59473. * The image which was just taken, either a base64 encoded string or a URI depending on which
  59474. * option you chose (destination).
  59475. *
  59476. * @param {Function} options.failure
  59477. * The function which is called when something goes wrong.
  59478. *
  59479. * @param {Object} scope
  59480. * The scope in which to call the `success` and `failure` functions, if specified.
  59481. *
  59482. * @param {Number} options.quality
  59483. * The quality of the image which is returned in the callback. This should be a percentage.
  59484. *
  59485. * @param {String} options.source
  59486. * The source of where the image should be taken. Available options are:
  59487. *
  59488. * - **album** - prompts the user to choose an image from an album
  59489. * - **camera** - prompts the user to take a new photo
  59490. * - **library** - prompts the user to choose an image from the library
  59491. *
  59492. * @param {String} destination
  59493. * The destination of the image which is returned. Available options are:
  59494. *
  59495. * - **data** - returns a base64 encoded string
  59496. * - **file** - returns the file's URI
  59497. *
  59498. * @param {String} encoding
  59499. * The encoding of the returned image. Available options are:
  59500. *
  59501. * - **jpg**
  59502. * - **png**
  59503. *
  59504. * @param {Number} width
  59505. * The width of the image to return
  59506. *
  59507. * @param {Number} height
  59508. * The height of the image to return
  59509. */
  59510. capture: Ext.emptyFn
  59511. });
  59512. /**
  59513. * @private
  59514. */
  59515. Ext.define('Ext.device.camera.PhoneGap', {
  59516. extend: 'Ext.device.camera.Abstract',
  59517. capture: function(args) {
  59518. var onSuccess = args.success,
  59519. onError = args.failure,
  59520. scope = args.scope,
  59521. sources = this.source,
  59522. destinations = this.destination,
  59523. encodings = this.encoding,
  59524. source = args.source,
  59525. destination = args.destination,
  59526. encoding = args.encoding,
  59527. options = {};
  59528. if (scope) {
  59529. onSuccess = Ext.Function.bind(onSuccess, scope);
  59530. onError = Ext.Function.bind(onError, scope);
  59531. }
  59532. if (source !== undefined) {
  59533. options.sourceType = sources.hasOwnProperty(source) ? sources[source] : source;
  59534. }
  59535. if (destination !== undefined) {
  59536. options.destinationType = destinations.hasOwnProperty(destination) ? destinations[destination] : destination;
  59537. }
  59538. if (encoding !== undefined) {
  59539. options.encodingType = encodings.hasOwnProperty(encoding) ? encodings[encoding] : encoding;
  59540. }
  59541. if ('quality' in args) {
  59542. options.quality = args.quality;
  59543. }
  59544. if ('width' in args) {
  59545. options.targetWidth = args.width;
  59546. }
  59547. if ('height' in args) {
  59548. options.targetHeight = args.height;
  59549. }
  59550. try {
  59551. navigator.camera.getPicture(onSuccess, onError, options);
  59552. }
  59553. catch (e) {
  59554. alert(e);
  59555. }
  59556. }
  59557. });
  59558. /**
  59559. * @private
  59560. */
  59561. Ext.define('Ext.device.camera.Sencha', {
  59562. extend: 'Ext.device.camera.Abstract',
  59563. requires: [
  59564. 'Ext.device.Communicator'
  59565. ],
  59566. capture: function(options) {
  59567. var sources = this.source,
  59568. destinations = this.destination,
  59569. encodings = this.encoding,
  59570. source = options.source,
  59571. destination = options.destination,
  59572. encoding = options.encoding;
  59573. if (sources.hasOwnProperty(source)) {
  59574. source = sources[source];
  59575. }
  59576. if (destinations.hasOwnProperty(destination)) {
  59577. destination = destinations[destination];
  59578. }
  59579. if (encodings.hasOwnProperty(encoding)) {
  59580. encoding = encodings[encoding];
  59581. }
  59582. Ext.device.Communicator.send({
  59583. command: 'Camera#capture',
  59584. callbacks: {
  59585. success: options.success,
  59586. failure: options.failure
  59587. },
  59588. scope: options.scope,
  59589. quality: options.quality,
  59590. width: options.width,
  59591. height: options.height,
  59592. source: source,
  59593. destination: destination,
  59594. encoding: encoding
  59595. });
  59596. }
  59597. });
  59598. /**
  59599. * @private
  59600. */
  59601. Ext.define('Ext.device.camera.Simulator', {
  59602. extend: 'Ext.device.camera.Abstract',
  59603. config: {
  59604. samples: [
  59605. {
  59606. success: 'http://www.sencha.com/img/sencha-large.png'
  59607. }
  59608. ]
  59609. },
  59610. constructor: function(config) {
  59611. this.initConfig(config);
  59612. },
  59613. updateSamples: function(samples) {
  59614. this.sampleIndex = 0;
  59615. },
  59616. capture: function(options) {
  59617. var index = this.sampleIndex,
  59618. samples = this.getSamples(),
  59619. samplesCount = samples.length,
  59620. sample = samples[index],
  59621. scope = options.scope,
  59622. success = options.success,
  59623. failure = options.failure;
  59624. if ('success' in sample) {
  59625. if (success) {
  59626. success.call(scope, sample.success);
  59627. }
  59628. }
  59629. else {
  59630. if (failure) {
  59631. failure.call(scope, sample.failure);
  59632. }
  59633. }
  59634. if (++index > samplesCount - 1) {
  59635. index = 0;
  59636. }
  59637. this.sampleIndex = index;
  59638. }
  59639. });
  59640. /**
  59641. * This class allows you to use native APIs to take photos using the device camera.
  59642. *
  59643. * When this singleton is instantiated, it will automatically select the correct implementation depending on the
  59644. * current device:
  59645. *
  59646. * - Sencha Packager
  59647. * - PhoneGap
  59648. * - Simulator
  59649. *
  59650. * Both the Sencha Packager and PhoneGap implementations will use the native camera functionality to take or select
  59651. * a photo. The Simulator implementation will simply return fake images.
  59652. *
  59653. * ## Example
  59654. *
  59655. * You can use the {@link Ext.device.Camera#capture} function to take a photo:
  59656. *
  59657. * Ext.device.Camera.capture({
  59658. * success: function(image) {
  59659. * imageView.setSrc(image);
  59660. * },
  59661. * quality: 75,
  59662. * width: 200,
  59663. * height: 200,
  59664. * destination: 'data'
  59665. * });
  59666. *
  59667. * See the documentation for {@link Ext.device.Camera#capture} all available configurations.
  59668. *
  59669. * @mixins Ext.device.camera.Abstract
  59670. *
  59671. * @aside guide native_apis
  59672. */
  59673. Ext.define('Ext.device.Camera', {
  59674. singleton: true,
  59675. requires: [
  59676. 'Ext.device.Communicator',
  59677. 'Ext.device.camera.PhoneGap',
  59678. 'Ext.device.camera.Sencha',
  59679. 'Ext.device.camera.Simulator'
  59680. ],
  59681. constructor: function() {
  59682. var browserEnv = Ext.browser.is;
  59683. if (browserEnv.WebView) {
  59684. if (browserEnv.PhoneGap) {
  59685. return Ext.create('Ext.device.camera.PhoneGap');
  59686. }
  59687. else {
  59688. return Ext.create('Ext.device.camera.Sencha');
  59689. }
  59690. }
  59691. else {
  59692. return Ext.create('Ext.device.camera.Simulator');
  59693. }
  59694. }
  59695. });
  59696. /**
  59697. * @private
  59698. */
  59699. Ext.define('Ext.device.connection.Abstract', {
  59700. extend: 'Ext.Evented',
  59701. config: {
  59702. online: false,
  59703. type: null
  59704. },
  59705. /**
  59706. * @property {String} UNKNOWN
  59707. * Text label for a connection type.
  59708. */
  59709. UNKNOWN: 'Unknown connection',
  59710. /**
  59711. * @property {String} ETHERNET
  59712. * Text label for a connection type.
  59713. */
  59714. ETHERNET: 'Ethernet connection',
  59715. /**
  59716. * @property {String} WIFI
  59717. * Text label for a connection type.
  59718. */
  59719. WIFI: 'WiFi connection',
  59720. /**
  59721. * @property {String} CELL_2G
  59722. * Text label for a connection type.
  59723. */
  59724. CELL_2G: 'Cell 2G connection',
  59725. /**
  59726. * @property {String} CELL_3G
  59727. * Text label for a connection type.
  59728. */
  59729. CELL_3G: 'Cell 3G connection',
  59730. /**
  59731. * @property {String} CELL_4G
  59732. * Text label for a connection type.
  59733. */
  59734. CELL_4G: 'Cell 4G connection',
  59735. /**
  59736. * @property {String} NONE
  59737. * Text label for a connection type.
  59738. */
  59739. NONE: 'No network connection',
  59740. /**
  59741. * True if the device is currently online
  59742. * @return {Boolean} online
  59743. */
  59744. isOnline: function() {
  59745. return this.getOnline();
  59746. }
  59747. /**
  59748. * @method getType
  59749. * Returns the current connection type.
  59750. * @return {String} type
  59751. */
  59752. });
  59753. /**
  59754. * @private
  59755. */
  59756. Ext.define('Ext.device.connection.Sencha', {
  59757. extend: 'Ext.device.connection.Abstract',
  59758. /**
  59759. * @event onlinechange
  59760. * Fires when the connection status changes.
  59761. * @param {Boolean} online True if you are {@link Ext.device.Connection#isOnline online}
  59762. * @param {String} type The new online {@link Ext.device.Connection#getType type}
  59763. */
  59764. initialize: function() {
  59765. Ext.device.Communicator.send({
  59766. command: 'Connection#watch',
  59767. callbacks: {
  59768. callback: this.onConnectionChange
  59769. },
  59770. scope: this
  59771. });
  59772. },
  59773. onConnectionChange: function(e) {
  59774. this.setOnline(Boolean(e.online));
  59775. this.setType(this[e.type]);
  59776. this.fireEvent('onlinechange', this.getOnline(), this.getType());
  59777. }
  59778. });
  59779. /**
  59780. * @private
  59781. */
  59782. Ext.define('Ext.device.connection.PhoneGap', {
  59783. extend: 'Ext.device.connection.Abstract',
  59784. syncOnline: function() {
  59785. var type = navigator.network.connection.type;
  59786. this._type = type;
  59787. this._online = type != Connection.NONE;
  59788. },
  59789. getOnline: function() {
  59790. this.syncOnline();
  59791. return this._online;
  59792. },
  59793. getType: function() {
  59794. this.syncOnline();
  59795. return this._type;
  59796. }
  59797. });
  59798. /**
  59799. * @private
  59800. */
  59801. Ext.define('Ext.device.connection.Simulator', {
  59802. extend: 'Ext.device.connection.Abstract',
  59803. getOnline: function() {
  59804. this._online = navigator.onLine;
  59805. this._type = Ext.device.Connection.UNKNOWN;
  59806. return this._online;
  59807. }
  59808. });
  59809. /**
  59810. * This class is used to check if the current device is currently online or not. It has three different implementations:
  59811. *
  59812. * - Sencha Packager
  59813. * - PhoneGap
  59814. * - Simulator
  59815. *
  59816. * Both the Sencha Packager and PhoneGap implementations will use the native functionality to determine if the current
  59817. * device is online. The Simulator version will simply use `navigator.onLine`.
  59818. *
  59819. * When this singleton ({@link Ext.device.Connection}) is instantiated, it will automatically decide which version to
  59820. * use based on the current platform.
  59821. *
  59822. * ## Examples
  59823. *
  59824. * Determining if the current device is online:
  59825. *
  59826. * alert(Ext.device.Connection.isOnline());
  59827. *
  59828. * Checking the type of connection the device has:
  59829. *
  59830. * alert('Your connection type is: ' + Ext.device.Connection.getType());
  59831. *
  59832. * The available connection types are:
  59833. *
  59834. * - {@link Ext.device.Connection#UNKNOWN UNKNOWN} - Unknown connection
  59835. * - {@link Ext.device.Connection#ETHERNET ETHERNET} - Ethernet connection
  59836. * - {@link Ext.device.Connection#WIFI WIFI} - WiFi connection
  59837. * - {@link Ext.device.Connection#CELL_2G CELL_2G} - Cell 2G connection
  59838. * - {@link Ext.device.Connection#CELL_3G CELL_3G} - Cell 3G connection
  59839. * - {@link Ext.device.Connection#CELL_4G CELL_4G} - Cell 4G connection
  59840. * - {@link Ext.device.Connection#NONE NONE} - No network connection
  59841. *
  59842. * @mixins Ext.device.connection.Abstract
  59843. *
  59844. * @aside guide native_apis
  59845. */
  59846. Ext.define('Ext.device.Connection', {
  59847. singleton: true,
  59848. requires: [
  59849. 'Ext.device.Communicator',
  59850. 'Ext.device.connection.Sencha',
  59851. 'Ext.device.connection.PhoneGap',
  59852. 'Ext.device.connection.Simulator'
  59853. ],
  59854. /**
  59855. * @event onlinechange
  59856. * @inheritdoc Ext.device.connection.Sencha#onlinechange
  59857. */
  59858. constructor: function() {
  59859. var browserEnv = Ext.browser.is;
  59860. if (browserEnv.WebView) {
  59861. if (browserEnv.PhoneGap) {
  59862. return Ext.create('Ext.device.connection.PhoneGap');
  59863. }
  59864. else {
  59865. return Ext.create('Ext.device.connection.Sencha');
  59866. }
  59867. }
  59868. else {
  59869. return Ext.create('Ext.device.connection.Simulator');
  59870. }
  59871. }
  59872. });
  59873. /**
  59874. * @private
  59875. */
  59876. Ext.define('Ext.device.contacts.Abstract', {
  59877. extend: 'Ext.Evented',
  59878. config: {
  59879. /**
  59880. * @cfg {Boolean} includeImages
  59881. * True to include images when you get the contacts store. Please beware that this can be very slow.
  59882. */
  59883. includeImages: false
  59884. },
  59885. /**
  59886. * Returns an Array of contact objects.
  59887. * @return {Object[]} An array of contact objects.
  59888. */
  59889. getContacts: function(config) {
  59890. if (!this._store) {
  59891. this._store = [
  59892. {
  59893. first: 'Robert',
  59894. last: 'Dougan',
  59895. emails: {
  59896. work: 'rob@sencha.com'
  59897. }
  59898. },
  59899. {
  59900. first: 'Jamie',
  59901. last: 'Avins',
  59902. emails: {
  59903. work: 'jamie@sencha.com'
  59904. }
  59905. }
  59906. ];
  59907. }
  59908. config.success.call(config.scope || this, this._store);
  59909. },
  59910. /**
  59911. * Returns base64 encoded image thumbnail for a contact specified in config.id
  59912. * @return {String} base64 string
  59913. */
  59914. getThumbnail: function(config) {
  59915. config.callback.call(config.scope || this, "");
  59916. },
  59917. /**
  59918. * Returns localized, user readable label for a contact field (i.e. "Mobile", "Home")
  59919. * @return {String} user readable string
  59920. */
  59921. getLocalizedLabel: function(config) {
  59922. config.callback.call(config.scope || this, config.label.toUpperCase(), config.label);
  59923. }
  59924. });
  59925. /**
  59926. * @private
  59927. */
  59928. Ext.define('Ext.device.contacts.Sencha', {
  59929. extend: 'Ext.device.contacts.Abstract',
  59930. getContacts: function(config) {
  59931. var includeImages = this.getIncludeImages();
  59932. if (typeof config.includeImages != "undefined") {
  59933. includeImages = config.includeImages;
  59934. }
  59935. if (!config) {
  59936. Ext.Logger.warn('Ext.device.Contacts#getContacts: You must specify a `config` object.');
  59937. return false;
  59938. }
  59939. if (!config.success) {
  59940. Ext.Logger.warn('Ext.device.Contacts#getContacts: You must specify a `success` method.');
  59941. return false;
  59942. }
  59943. Ext.device.Communicator.send({
  59944. command: 'Contacts#all',
  59945. callbacks: {
  59946. success: function(contacts) {
  59947. config.success.call(config.scope || this, contacts);
  59948. },
  59949. failure: function() {
  59950. if (config.failure) {
  59951. config.failure.call(config.scope || this);
  59952. }
  59953. }
  59954. },
  59955. includeImages: includeImages,
  59956. scope: this
  59957. });
  59958. },
  59959. getThumbnail: function(config) {
  59960. if (!config || typeof config.id == "undefined") {
  59961. Ext.Logger.warn('Ext.device.Contacts#getThumbnail: You must specify an `id` of the contact.');
  59962. return false;
  59963. }
  59964. if (!config || !config.callback) {
  59965. Ext.Logger.warn('Ext.device.Contacts#getThumbnail: You must specify a `callback`.');
  59966. return false;
  59967. }
  59968. Ext.device.Communicator.send({
  59969. command: 'Contacts#getThumbnail',
  59970. callbacks: {
  59971. success: function(src) {
  59972. this.set('thumbnail', src);
  59973. if (config.callback) {
  59974. config.callback.call(config.scope || this, this);
  59975. }
  59976. }
  59977. },
  59978. id: id,
  59979. scope: this
  59980. });
  59981. },
  59982. getLocalizedLabel: function(config) {
  59983. if (!config || typeof config.label == "undefined") {
  59984. Ext.Logger.warn('Ext.device.Contacts#getLocalizedLabel: You must specify an `label` to be localized.');
  59985. return false;
  59986. }
  59987. if (!config || !config.callback) {
  59988. Ext.Logger.warn('Ext.device.Contacts#getLocalizedLabel: You must specify a `callback`.');
  59989. return false;
  59990. }
  59991. Ext.device.Communicator.send({
  59992. command: 'Contacts#getLocalizedLabel',
  59993. callbacks: {
  59994. callback: function(label) {
  59995. config.callback.call(config.scope || this, label, config.label);
  59996. }
  59997. },
  59998. label: config.label,
  59999. scope: this
  60000. });
  60001. }
  60002. });
  60003. /**
  60004. * This device API allows you to access a users contacts using a {@link Ext.data.Store}. This allows you to search, filter
  60005. * and sort through all the contacts using its methods.
  60006. *
  60007. * To use this API, all you need to do is require this class (`Ext.device.Contacts`) and then use `Ext.device.Contacts.getContacts()`
  60008. * to retrieve an array of contacts.
  60009. *
  60010. * **Please note that this will *only* work using the Sencha Native Packager.**
  60011. *
  60012. * # Example
  60013. *
  60014. * Ext.application({
  60015. * name: 'Sencha',
  60016. * requires: 'Ext.device.Contacts',
  60017. *
  60018. * launch: function() {
  60019. * Ext.Viewport.add({
  60020. * xtype: 'list',
  60021. * itemTpl: '{First} {Last}',
  60022. * store: {
  60023. * fields: ['First', 'Last'],
  60024. * data: Ext.device.Contacts.getContacts()
  60025. * }
  60026. * });
  60027. * }
  60028. * });
  60029. *
  60030. * @mixins Ext.device.contacts.Abstract
  60031. * @mixins Ext.device.contacts.Sencha
  60032. *
  60033. * @aside guide native_apis
  60034. */
  60035. Ext.define('Ext.device.Contacts', {
  60036. singleton: true,
  60037. requires: [
  60038. 'Ext.device.Communicator',
  60039. 'Ext.device.contacts.Sencha'
  60040. ],
  60041. constructor: function() {
  60042. var browserEnv = Ext.browser.is;
  60043. if (browserEnv.WebView && !browserEnv.PhoneGap) {
  60044. return Ext.create('Ext.device.contacts.Sencha');
  60045. } else {
  60046. return Ext.create('Ext.device.contacts.Abstract');
  60047. }
  60048. }
  60049. });
  60050. /**
  60051. * @private
  60052. */
  60053. Ext.define('Ext.device.device.Abstract', {
  60054. extend: 'Ext.EventedBase',
  60055. /**
  60056. * @event schemeupdate
  60057. * Event which is fired when your Sencha Native packaged application is opened from another application using a custom URL scheme.
  60058. *
  60059. * This event will only fire if the application was already open (in other words; `onReady` was already fired). This means you should check
  60060. * if {@link Ext.device.Device#scheme} is set in your Application `launch`/`onReady` method, and perform any needed changes for that URL (if defined).
  60061. * Then listen to this event for future changed.
  60062. *
  60063. * ## Example
  60064. *
  60065. * Ext.application({
  60066. * name: 'Sencha',
  60067. * requires: ['Ext.device.Device'],
  60068. * launch: function() {
  60069. * if (Ext.device.Device.scheme) {
  60070. * // the application was opened via another application. Do something:
  60071. * console.log('Applicaton opened via another application: ' + Ext.device.Device.scheme.url);
  60072. * }
  60073. *
  60074. * // Listen for future changes
  60075. * Ext.device.Device.on('schemeupdate', function(device, scheme) {
  60076. * // the application was launched, closed, and then launched another from another application
  60077. * // this means onReady wont be called again ('cause the application is already running in the
  60078. * // background) - but this event will be fired
  60079. * console.log('Applicated reopened via another application: ' + scheme.url);
  60080. * }, this);
  60081. * }
  60082. * });
  60083. *
  60084. * __Note:__ This currently only works with the Sencha Native Packager. If you attempt to listen to this event when packaged with
  60085. * PhoneGap or simply in the browser, it will never fire.**
  60086. *
  60087. * @param {Ext.device.Device} this The instance of Ext.device.Device
  60088. * @param {Object/Boolean} scheme The scheme information, if opened via another application
  60089. * @param {String} scheme.url The URL that was opened, if this application was opened via another application. Example: `sencha:`
  60090. * @param {String} scheme.sourceApplication The source application that opened this application. Example: `com.apple.safari`.
  60091. */
  60092. /**
  60093. * @property {String} name
  60094. * Returns the name of the current device. If the current device does not have a name (for example, in a browser), it will
  60095. * default to `not available`.
  60096. *
  60097. * alert('Device name: ' + Ext.device.Device.name);
  60098. */
  60099. name: 'not available',
  60100. /**
  60101. * @property {String} uuid
  60102. * Returns a unique identifier for the current device. If the current device does not have a unique identifier (for example,
  60103. * in a browser), it will default to `anonymous`.
  60104. *
  60105. * alert('Device UUID: ' + Ext.device.Device.uuid);
  60106. */
  60107. uuid: 'anonymous',
  60108. /**
  60109. * @property {String} platform
  60110. * The current platform the device is running on.
  60111. *
  60112. * alert('Device platform: ' + Ext.device.Device.platform);
  60113. */
  60114. platform: Ext.os.name,
  60115. /**
  60116. * @property {Object/Boolean} scheme
  60117. *
  60118. */
  60119. scheme: false,
  60120. /**
  60121. * Opens a specified URL. The URL can contain a custom URL Scheme for another app or service:
  60122. *
  60123. * // Safari
  60124. * Ext.device.Device.openURL('http://sencha.com');
  60125. *
  60126. * // Telephone
  60127. * Ext.device.Device.openURL('tel:6501231234');
  60128. *
  60129. * // SMS with a default number
  60130. * Ext.device.Device.openURL('sms:+12345678901');
  60131. *
  60132. * // Email client
  60133. * Ext.device.Device.openURL('mailto:rob@sencha.com');
  60134. *
  60135. * You can find a full list of available URL schemes here: [http://wiki.akosma.com/IPhone_URL_Schemes](http://wiki.akosma.com/IPhone_URL_Schemes).
  60136. *
  60137. * __Note:__ This currently only works on iOS using the Sencha Native Packager. Attempting to use this on PhoneGap, iOS Simulator
  60138. * or the browser will simply result in the current window location changing.**
  60139. *
  60140. * If successful, this will close the application (as another one opens).
  60141. *
  60142. * @param {String} url The URL to open
  60143. */
  60144. openURL: function(url) {
  60145. window.location = url;
  60146. }
  60147. });
  60148. /**
  60149. * @private
  60150. */
  60151. Ext.define('Ext.device.device.PhoneGap', {
  60152. extend: 'Ext.device.device.Abstract',
  60153. constructor: function() {
  60154. // We can't get the device details until the device is ready, so lets wait.
  60155. if (Ext.Viewport.isReady) {
  60156. this.onReady();
  60157. } else {
  60158. Ext.Viewport.on('ready', this.onReady, this, {single: true});
  60159. }
  60160. },
  60161. onReady: function() {
  60162. this.name = device.name;
  60163. this.uuid = device.uuid;
  60164. this.platform = device.platformName || Ext.os.name;
  60165. }
  60166. });
  60167. /**
  60168. * @private
  60169. */
  60170. Ext.define('Ext.device.device.Sencha', {
  60171. extend: 'Ext.device.device.Abstract',
  60172. constructor: function() {
  60173. this.name = device.name;
  60174. this.uuid = device.uuid;
  60175. this.platform = device.platformName || Ext.os.name;
  60176. this.initURL();
  60177. },
  60178. openURL: function(url) {
  60179. Ext.device.Communicator.send({
  60180. command: 'OpenURL#open',
  60181. url: url
  60182. });
  60183. },
  60184. /**
  60185. * @private
  60186. */
  60187. initURL: function() {
  60188. Ext.device.Communicator.send({
  60189. command: "OpenURL#watch",
  60190. callbacks: {
  60191. callback: this.updateURL
  60192. },
  60193. scope: this
  60194. });
  60195. },
  60196. /**
  60197. * @private
  60198. */
  60199. updateURL: function() {
  60200. this.scheme = device.scheme || false;
  60201. this.fireEvent('schemeupdate', this, this.scheme);
  60202. }
  60203. });
  60204. /**
  60205. * @private
  60206. */
  60207. Ext.define('Ext.device.device.Simulator', {
  60208. extend: 'Ext.device.device.Abstract'
  60209. });
  60210. /**
  60211. * Provides a cross device way to get information about the device your application is running on. There are 3 different implementations:
  60212. *
  60213. * - Sencha Packager
  60214. * - [PhoneGap](http://docs.phonegap.com/en/1.4.1/phonegap_device_device.md.html)
  60215. * - Simulator
  60216. *
  60217. * ## Examples
  60218. *
  60219. * #### Device Information
  60220. *
  60221. * Getting the device information:
  60222. *
  60223. * Ext.application({
  60224. * name: 'Sencha',
  60225. *
  60226. * // Remember that the Ext.device.Device class *must* be required
  60227. * requires: ['Ext.device.Device'],
  60228. *
  60229. * launch: function() {
  60230. * alert([
  60231. * 'Device name: ' + Ext.device.Device.name,
  60232. * 'Device platform: ' + Ext.device.Device.platform,
  60233. * 'Device UUID: ' + Ext.device.Device.uuid
  60234. * ].join('\n'));
  60235. * }
  60236. * });
  60237. *
  60238. * ### Custom Scheme URLs
  60239. *
  60240. * Using custom scheme URLs to application your application from other applications:
  60241. *
  60242. * Ext.application({
  60243. * name: 'Sencha',
  60244. * requires: ['Ext.device.Device'],
  60245. * launch: function() {
  60246. * if (Ext.device.Device.scheme) {
  60247. * // the application was opened via another application. Do something:
  60248. * alert('Applicaton pened via another application: ' + Ext.device.Device.scheme.url);
  60249. * }
  60250. *
  60251. * // Listen for future changes
  60252. * Ext.device.Device.on('schemeupdate', function(device, scheme) {
  60253. * // the application was launched, closed, and then launched another from another application
  60254. * // this means onReady wont be called again ('cause the application is already running in the
  60255. * // background) - but this event will be fired
  60256. * alert('Applicated reopened via another application: ' + scheme.url);
  60257. * }, this);
  60258. * }
  60259. * });
  60260. *
  60261. * Of course, you must add add the custom URLs you would like to use when packaging your application. You can do this by adding
  60262. * the following code into the `rawConfig` property inside your `package.json` file (Sencha Native Packager configuration file):
  60263. *
  60264. * {
  60265. * ...
  60266. * "rawConfig": "<key>CFBundleURLTypes</key><array><dict><key>CFBundleURLSchemes</key><array><string>sencha</string></array><key>CFBundleURLName</key><string>com.sencha.example</string></dict></array>"
  60267. * ...
  60268. * }
  60269. *
  60270. * You can change the available URL schemes and the application identifier above.
  60271. *
  60272. * You can then test it by packaging and installing the application onto a device/iOS Simulator, opening Safari and typing: `sencha:testing`.
  60273. * The application will launch and it will `alert` the URL you specified.
  60274. *
  60275. * **PLEASE NOTE: This currently only works with the Sencha Native Packager. If you attempt to listen to this event when packaged with
  60276. * PhoneGap or simply in the browser, it will not function.**
  60277. *
  60278. * @mixins Ext.device.device.Abstract
  60279. *
  60280. * @aside guide native_apis
  60281. */
  60282. Ext.define('Ext.device.Device', {
  60283. singleton: true,
  60284. requires: [
  60285. 'Ext.device.Communicator',
  60286. 'Ext.device.device.PhoneGap',
  60287. 'Ext.device.device.Sencha',
  60288. 'Ext.device.device.Simulator'
  60289. ],
  60290. constructor: function() {
  60291. var browserEnv = Ext.browser.is;
  60292. if (browserEnv.WebView) {
  60293. if (browserEnv.PhoneGap) {
  60294. return Ext.create('Ext.device.device.PhoneGap');
  60295. }
  60296. else {
  60297. return Ext.create('Ext.device.device.Sencha');
  60298. }
  60299. }
  60300. else {
  60301. return Ext.create('Ext.device.device.Simulator');
  60302. }
  60303. }
  60304. });
  60305. /**
  60306. * @private
  60307. */
  60308. Ext.define('Ext.device.geolocation.Abstract', {
  60309. config: {
  60310. /**
  60311. * @cfg {Number} maximumAge
  60312. * This option indicates that the application is willing to accept cached location information whose age
  60313. * is no greater than the specified time in milliseconds. If maximumAge is set to 0, an attempt to retrieve
  60314. * new location information is made immediately.
  60315. */
  60316. maximumAge: 0,
  60317. /**
  60318. * @cfg {Number} frequency The default frequency to get the current position when using {@link Ext.device.Geolocation#watchPosition}.
  60319. */
  60320. frequency: 10000,
  60321. /**
  60322. * @cfg {Boolean} allowHighAccuracy True to allow high accuracy when getting the current position.
  60323. */
  60324. allowHighAccuracy: false,
  60325. /**
  60326. * @cfg {Number} timeout
  60327. * The maximum number of milliseconds allowed to elapse between a location update operation.
  60328. */
  60329. timeout: Infinity
  60330. },
  60331. /**
  60332. * Attempts to get the current position of this device.
  60333. *
  60334. * Ext.device.Geolocation.getCurrentPosition({
  60335. * success: function(position) {
  60336. * console.log(position);
  60337. * },
  60338. * failure: function() {
  60339. * Ext.Msg.alert('Geolocation', 'Something went wrong!');
  60340. * }
  60341. * });
  60342. *
  60343. * *Note:* If you want to watch the current position, you could use {@link Ext.device.Geolocation#watchPosition} instead.
  60344. *
  60345. * @param {Object} config An object which contains the following config options:
  60346. *
  60347. * @param {Function} config.success
  60348. * The function to call when the location of the current device has been received.
  60349. *
  60350. * @param {Object} config.success.position
  60351. *
  60352. * @param {Function} config.failure
  60353. * The function that is called when something goes wrong.
  60354. *
  60355. * @param {Object} config.scope
  60356. * The scope of the `success` and `failure` functions.
  60357. *
  60358. * @param {Number} config.maximumAge
  60359. * The maximum age of a cached location. If you do not enter a value for this, the value of {@link #maximumAge}
  60360. * will be used.
  60361. *
  60362. * @param {Number} config.timeout
  60363. * The timeout for this request. If you do not specify a value, it will default to {@link #timeout}.
  60364. *
  60365. * @param {Boolean} config.allowHighAccuracy
  60366. * True to enable allow accuracy detection of the location of the current device. If you do not specify a value, it will
  60367. * default to {@link #allowHighAccuracy}.
  60368. */
  60369. getCurrentPosition: function(config) {
  60370. var defaultConfig = Ext.device.geolocation.Abstract.prototype.config;
  60371. config = Ext.applyIf(config, {
  60372. maximumAge: defaultConfig.maximumAge,
  60373. frequency: defaultConfig.frequency,
  60374. allowHighAccuracy: defaultConfig.allowHighAccuracy,
  60375. timeout: defaultConfig.timeout
  60376. });
  60377. // <debug>
  60378. if (!config.success) {
  60379. Ext.Logger.warn('You need to specify a `success` function for #getCurrentPosition');
  60380. }
  60381. // </debug>
  60382. return config;
  60383. },
  60384. /**
  60385. * Watches for the current position and calls the callback when successful depending on the specified {@link #frequency}.
  60386. *
  60387. * Ext.device.Geolocation.watchPosition({
  60388. * callback: function(position) {
  60389. * console.log(position);
  60390. * },
  60391. * failure: function() {
  60392. * Ext.Msg.alert('Geolocation', 'Something went wrong!');
  60393. * }
  60394. * });
  60395. *
  60396. * @param {Object} config An object which contains the following config options:
  60397. *
  60398. * @param {Function} config.callback
  60399. * The function to be called when the position has been updated.
  60400. *
  60401. * @param {Function} config.failure
  60402. * The function that is called when something goes wrong.
  60403. *
  60404. * @param {Object} config.scope
  60405. * The scope of the `success` and `failure` functions.
  60406. *
  60407. * @param {Boolean} config.frequency
  60408. * The frequency in which to call the supplied callback. Defaults to {@link #frequency} if you do not specify a value.
  60409. *
  60410. * @param {Boolean} config.allowHighAccuracy
  60411. * True to enable allow accuracy detection of the location of the current device. If you do not specify a value, it will
  60412. * default to {@link #allowHighAccuracy}.
  60413. */
  60414. watchPosition: function(config) {
  60415. var defaultConfig = Ext.device.geolocation.Abstract.prototype.config;
  60416. config = Ext.applyIf(config, {
  60417. maximumAge: defaultConfig.maximumAge,
  60418. frequency: defaultConfig.frequency,
  60419. allowHighAccuracy: defaultConfig.allowHighAccuracy,
  60420. timeout: defaultConfig.timeout
  60421. });
  60422. // <debug>
  60423. if (!config.callback) {
  60424. Ext.Logger.warn('You need to specify a `callback` function for #watchPosition');
  60425. }
  60426. // </debug>
  60427. return config;
  60428. },
  60429. /**
  60430. * If you are currently watching for the current position, this will stop that task.
  60431. */
  60432. clearWatch: function() {}
  60433. });
  60434. /**
  60435. * @private
  60436. */
  60437. Ext.define('Ext.device.geolocation.Sencha', {
  60438. extend: 'Ext.device.geolocation.Abstract',
  60439. getCurrentPosition: function(config) {
  60440. config = this.callParent([config]);
  60441. Ext.apply(config, {
  60442. command: 'Geolocation#getCurrentPosition',
  60443. callbacks: {
  60444. success: config.success,
  60445. failure: config.failure
  60446. }
  60447. });
  60448. Ext.applyIf(config, {
  60449. scope: this
  60450. });
  60451. delete config.success;
  60452. delete config.failure;
  60453. Ext.device.Communicator.send(config);
  60454. return config;
  60455. },
  60456. watchPosition: function(config) {
  60457. config = this.callParent([config]);
  60458. Ext.apply(config, {
  60459. command: 'Geolocation#watchPosition',
  60460. callbacks: {
  60461. success: config.callback,
  60462. failure: config.failure
  60463. }
  60464. });
  60465. Ext.applyIf(config, {
  60466. scope: this
  60467. });
  60468. delete config.callback;
  60469. delete config.failure;
  60470. Ext.device.Communicator.send(config);
  60471. return config;
  60472. },
  60473. clearWatch: function() {
  60474. Ext.device.Communicator.send({
  60475. command: 'Geolocation#clearWatch'
  60476. });
  60477. }
  60478. });
  60479. /**
  60480. * @private
  60481. */
  60482. Ext.define('Ext.device.geolocation.Simulator', {
  60483. extend: 'Ext.device.geolocation.Abstract',
  60484. requires: ['Ext.util.Geolocation'],
  60485. getCurrentPosition: function(config) {
  60486. config = this.callParent([config]);
  60487. Ext.apply(config, {
  60488. autoUpdate: false,
  60489. listeners: {
  60490. scope: this,
  60491. locationupdate: function(geolocation) {
  60492. if (config.success) {
  60493. config.success.call(config.scope || this, geolocation.position);
  60494. }
  60495. },
  60496. locationerror: function() {
  60497. if (config.failure) {
  60498. config.failure.call(config.scope || this);
  60499. }
  60500. }
  60501. }
  60502. });
  60503. this.geolocation = Ext.create('Ext.util.Geolocation', config);
  60504. this.geolocation.updateLocation();
  60505. return config;
  60506. },
  60507. watchPosition: function(config) {
  60508. config = this.callParent([config]);
  60509. Ext.apply(config, {
  60510. listeners: {
  60511. scope: this,
  60512. locationupdate: function(geolocation) {
  60513. if (config.callback) {
  60514. config.callback.call(config.scope || this, geolocation.position);
  60515. }
  60516. },
  60517. locationerror: function() {
  60518. if (config.failure) {
  60519. config.failure.call(config.scope || this);
  60520. }
  60521. }
  60522. }
  60523. });
  60524. this.geolocation = Ext.create('Ext.util.Geolocation', config);
  60525. return config;
  60526. },
  60527. clearWatch: function() {
  60528. if (this.geolocation) {
  60529. this.geolocation.destroy();
  60530. }
  60531. this.geolocation = null;
  60532. }
  60533. });
  60534. /**
  60535. * Provides access to the native Geolocation API when running on a device. There are three implementations of this API:
  60536. *
  60537. * - Sencha Packager
  60538. * - [PhoneGap](http://docs.phonegap.com/en/1.4.1/phonegap_device_device.md.html)
  60539. * - Browser
  60540. *
  60541. * This class will automatically select the correct implementation depending on the device your application is running on.
  60542. *
  60543. * ## Examples
  60544. *
  60545. * Getting the current location:
  60546. *
  60547. * Ext.device.Geolocation.getCurrentPosition({
  60548. * success: function(position) {
  60549. * console.log(position.coords);
  60550. * },
  60551. * failure: function() {
  60552. * console.log('something went wrong!');
  60553. * }
  60554. * });
  60555. *
  60556. * Watching the current location:
  60557. *
  60558. * Ext.device.Geolocation.watchPosition({
  60559. * frequency: 3000, // Update every 3 seconds
  60560. * callback: function(position) {
  60561. * console.log('Position updated!', position.coords);
  60562. * },
  60563. * failure: function() {
  60564. * console.log('something went wrong!');
  60565. * }
  60566. * });
  60567. *
  60568. * @mixins Ext.device.geolocation.Abstract
  60569. *
  60570. * @aside guide native_apis
  60571. */
  60572. Ext.define('Ext.device.Geolocation', {
  60573. singleton: true,
  60574. requires: [
  60575. 'Ext.device.Communicator',
  60576. 'Ext.device.geolocation.Sencha',
  60577. 'Ext.device.geolocation.Simulator'
  60578. ],
  60579. constructor: function() {
  60580. var browserEnv = Ext.browser.is;
  60581. if (browserEnv.WebView && browserEnv.Sencha) {
  60582. return Ext.create('Ext.device.geolocation.Sencha');
  60583. }
  60584. else {
  60585. return Ext.create('Ext.device.geolocation.Simulator');
  60586. }
  60587. }
  60588. });
  60589. /**
  60590. * @private
  60591. */
  60592. Ext.define('Ext.device.notification.Abstract', {
  60593. /**
  60594. * A simple way to show a notification.
  60595. *
  60596. * Ext.device.Notification.show({
  60597. * title: 'Verification',
  60598. * message: 'Is your email address is: test@sencha.com',
  60599. * buttons: Ext.MessageBox.OKCANCEL,
  60600. * callback: function(button) {
  60601. * if (button == "ok") {
  60602. * console.log('Verified');
  60603. * } else {
  60604. * console.log('Nope.');
  60605. * }
  60606. * }
  60607. * });
  60608. *
  60609. * @param {Object} config An object which contains the following config options:
  60610. *
  60611. * @param {String} config.title The title of the notification
  60612. *
  60613. * @param {String} config.message The message to be displayed on the notification
  60614. *
  60615. * @param {String/String[]} [config.buttons="OK"]
  60616. * The buttons to be displayed on the notification. It can be a string, which is the title of the button, or an array of multiple strings.
  60617. * Please not that you should not use more than 2 buttons, as they may not be displayed correct on all devices.
  60618. *
  60619. * @param {Function} config.callback
  60620. * A callback function which is called when the notification is dismissed by clicking on the configured buttons.
  60621. * @param {String} config.callback.buttonId The id of the button pressed, one of: 'ok', 'yes', 'no', 'cancel'.
  60622. *
  60623. * @param {Object} config.scope The scope of the callback function
  60624. */
  60625. show: function(config) {
  60626. if (!config.message) {
  60627. throw('[Ext.device.Notification#show] You passed no message');
  60628. }
  60629. if (!config.buttons) {
  60630. config.buttons = "OK";
  60631. }
  60632. if (!Ext.isArray(config.buttons)) {
  60633. config.buttons = [config.buttons];
  60634. }
  60635. if (!config.scope) {
  60636. config.scope = this;
  60637. }
  60638. return config;
  60639. },
  60640. /**
  60641. * Vibrates the device.
  60642. */
  60643. vibrate: Ext.emptyFn
  60644. });
  60645. /**
  60646. * @private
  60647. */
  60648. Ext.define('Ext.device.notification.PhoneGap', {
  60649. extend: 'Ext.device.notification.Abstract',
  60650. requires: ['Ext.device.Communicator'],
  60651. show: function() {
  60652. var config = this.callParent(arguments),
  60653. buttons = (config.buttons) ? config.buttons.join(',') : null,
  60654. onShowCallback = function(index) {
  60655. if (config.callback) {
  60656. config.callback.apply(config.scope, (config.buttons) ? [config.buttons[index - 1]].toLowerCase() : []);
  60657. }
  60658. };
  60659. // change Ext.MessageBox buttons into normal arrays
  60660. var ln = butons.length;
  60661. if (ln && typeof buttons[0] != "string") {
  60662. var newButtons = [],
  60663. i;
  60664. for (i = 0; i < ln; i++) {
  60665. newButtons.push(buttons[i].text);
  60666. }
  60667. buttons = newButtons;
  60668. }
  60669. navigator.notification.confirm(
  60670. config.message, // message
  60671. onShowCallback, // callback
  60672. config.title, // title
  60673. buttons // array of button names
  60674. );
  60675. },
  60676. vibrate: function() {
  60677. navigator.notification.vibrate(2000);
  60678. }
  60679. });
  60680. /**
  60681. * @private
  60682. */
  60683. Ext.define('Ext.device.notification.Sencha', {
  60684. extend: 'Ext.device.notification.Abstract',
  60685. requires: ['Ext.device.Communicator'],
  60686. show: function() {
  60687. var config = this.callParent(arguments);
  60688. Ext.device.Communicator.send({
  60689. command: 'Notification#show',
  60690. callbacks: {
  60691. callback: config.callback
  60692. },
  60693. scope : config.scope,
  60694. title : config.title,
  60695. message: config.message,
  60696. buttons: config.buttons.join(',') //@todo fix this
  60697. });
  60698. },
  60699. vibrate: function() {
  60700. Ext.device.Communicator.send({
  60701. command: 'Notification#vibrate'
  60702. });
  60703. }
  60704. });
  60705. /**
  60706. * @private
  60707. */
  60708. Ext.define('Ext.device.notification.Simulator', {
  60709. extend: 'Ext.device.notification.Abstract',
  60710. requires: ['Ext.MessageBox'],
  60711. // @private
  60712. msg: null,
  60713. show: function() {
  60714. var config = this.callParent(arguments),
  60715. buttons = [],
  60716. ln = config.buttons.length,
  60717. button, i, callback, msg;
  60718. //buttons
  60719. for (i = 0; i < ln; i++) {
  60720. button = config.buttons[i];
  60721. if (Ext.isString(button)) {
  60722. button = {
  60723. text: config.buttons[i],
  60724. itemId: config.buttons[i].toLowerCase()
  60725. };
  60726. }
  60727. buttons.push(button);
  60728. }
  60729. this.msg = Ext.create('Ext.MessageBox');
  60730. msg = this.msg;
  60731. callback = function(itemId) {
  60732. if (config.callback) {
  60733. config.callback.apply(config.scope, [itemId]);
  60734. }
  60735. };
  60736. this.msg.show({
  60737. title : config.title,
  60738. message: config.message,
  60739. scope : this.msg,
  60740. buttons: buttons,
  60741. fn : callback
  60742. });
  60743. },
  60744. vibrate: function() {
  60745. //nice animation to fake vibration
  60746. var animation = [
  60747. "@-webkit-keyframes vibrate{",
  60748. " from {",
  60749. " -webkit-transform: rotate(-2deg);",
  60750. " }",
  60751. " to{",
  60752. " -webkit-transform: rotate(2deg);",
  60753. " }",
  60754. "}",
  60755. "body {",
  60756. " -webkit-animation: vibrate 50ms linear 10 alternate;",
  60757. "}"
  60758. ];
  60759. var head = document.getElementsByTagName("head")[0];
  60760. var cssNode = document.createElement('style');
  60761. cssNode.innerHTML = animation.join('\n');
  60762. head.appendChild(cssNode);
  60763. setTimeout(function() {
  60764. head.removeChild(cssNode);
  60765. }, 400);
  60766. }
  60767. });
  60768. /**
  60769. * Provides a cross device way to show notifications. There are three different implementations:
  60770. *
  60771. * - Sencha Packager
  60772. * - PhoneGap
  60773. * - Simulator
  60774. *
  60775. * When this singleton is instantiated, it will automatically use the correct implementation depending on the current device.
  60776. *
  60777. * Both the Sencha Packager and PhoneGap versions will use the native implementations to display the notification. The
  60778. * Simulator implementation will use {@link Ext.MessageBox} for {@link #show} and a simply animation when you call {@link #vibrate}.
  60779. *
  60780. * ## Examples
  60781. *
  60782. * To show a simple notification:
  60783. *
  60784. * Ext.device.Notification.show({
  60785. * title: 'Verification',
  60786. * message: 'Is your email address: test@sencha.com',
  60787. * buttons: Ext.MessageBox.OKCANCEL,
  60788. * callback: function(button) {
  60789. * if (button === "ok") {
  60790. * console.log('Verified');
  60791. * } else {
  60792. * console.log('Nope');
  60793. * }
  60794. * }
  60795. * });
  60796. *
  60797. * To make the device vibrate:
  60798. *
  60799. * Ext.device.Notification.vibrate();
  60800. *
  60801. * @mixins Ext.device.notification.Abstract
  60802. *
  60803. * @aside guide native_apis
  60804. */
  60805. Ext.define('Ext.device.Notification', {
  60806. singleton: true,
  60807. requires: [
  60808. 'Ext.device.Communicator',
  60809. 'Ext.device.notification.PhoneGap',
  60810. 'Ext.device.notification.Sencha',
  60811. 'Ext.device.notification.Simulator'
  60812. ],
  60813. constructor: function() {
  60814. var browserEnv = Ext.browser.is;
  60815. if (browserEnv.WebView) {
  60816. if (browserEnv.PhoneGap) {
  60817. return Ext.create('Ext.device.notification.PhoneGap');
  60818. }
  60819. else {
  60820. return Ext.create('Ext.device.notification.Sencha');
  60821. }
  60822. }
  60823. else {
  60824. return Ext.create('Ext.device.notification.Simulator');
  60825. }
  60826. }
  60827. });
  60828. /**
  60829. * @private
  60830. */
  60831. Ext.define('Ext.device.orientation.Abstract', {
  60832. extend: 'Ext.EventedBase',
  60833. /**
  60834. * @event orientationchange
  60835. * Fires when the orientation has been changed on this device.
  60836. *
  60837. * Ext.device.Orientation.on({
  60838. * scope: this,
  60839. * orientationchange: function(e) {
  60840. * console.log('Alpha: ', e.alpha);
  60841. * console.log('Beta: ', e.beta);
  60842. * console.log('Gamma: ', e.gamma);
  60843. * }
  60844. * });
  60845. *
  60846. * @param {Object} event The event object
  60847. * @param {Object} event.alpha The alpha value of the orientation event
  60848. * @param {Object} event.beta The beta value of the orientation event
  60849. * @param {Object} event.gamma The gamma value of the orientation event
  60850. */
  60851. onDeviceOrientation: function(e) {
  60852. this.doFireEvent('orientationchange', [e]);
  60853. }
  60854. });
  60855. /**
  60856. * Provides the HTML5 implementation for the orientation API.
  60857. * @private
  60858. */
  60859. Ext.define('Ext.device.orientation.HTML5', {
  60860. extend: 'Ext.device.orientation.Abstract',
  60861. initialize: function() {
  60862. this.onDeviceOrientation = Ext.Function.bind(this.onDeviceOrientation, this);
  60863. window.addEventListener('deviceorientation', this.onDeviceOrientation, true);
  60864. }
  60865. });
  60866. /**
  60867. * @private
  60868. */
  60869. Ext.define('Ext.device.orientation.Sencha', {
  60870. extend: 'Ext.device.orientation.Abstract',
  60871. requires: [
  60872. 'Ext.device.Communicator'
  60873. ],
  60874. /**
  60875. * From the native shell, the callback needs to be invoked infinitely using a timer, ideally 50 times per second.
  60876. * The callback expects one event object argument, the format of which should looks like this:
  60877. *
  60878. * {
  60879. * alpha: 0,
  60880. * beta: 0,
  60881. * gamma: 0
  60882. * }
  60883. *
  60884. * Refer to [Safari DeviceOrientationEvent Class Reference][1] for more details.
  60885. *
  60886. * [1]: http://developer.apple.com/library/safari/#documentation/SafariDOMAdditions/Reference/DeviceOrientationEventClassRef/DeviceOrientationEvent/DeviceOrientationEvent.html
  60887. */
  60888. initialize: function() {
  60889. Ext.device.Communicator.send({
  60890. command: 'Orientation#watch',
  60891. callbacks: {
  60892. callback: this.onDeviceOrientation
  60893. },
  60894. scope: this
  60895. });
  60896. }
  60897. });
  60898. /**
  60899. * This class provides you with a cross platform way of listening to when the the orientation changes on the
  60900. * device your application is running on.
  60901. *
  60902. * The {@link Ext.device.Orientation#orientationchange orientationchange} event gets passes the `alpha`, `beta` and
  60903. * `gamma` values.
  60904. *
  60905. * You can find more information about these values and how to use them on the [W3C device orientation specification](http://dev.w3.org/geo/api/spec-source-orientation.html#deviceorientation).
  60906. *
  60907. * ## Example
  60908. *
  60909. * To listen to the device orientation, you can do the following:
  60910. *
  60911. * Ext.device.Orientation.on({
  60912. * scope: this,
  60913. * orientationchange: function(e) {
  60914. * console.log('Alpha: ', e.alpha);
  60915. * console.log('Beta: ', e.beta);
  60916. * console.log('Gamma: ', e.gamma);
  60917. * }
  60918. * });
  60919. *
  60920. * @mixins Ext.device.orientation.Abstract
  60921. *
  60922. * @aside guide native_apis
  60923. */
  60924. Ext.define('Ext.device.Orientation', {
  60925. singleton: true,
  60926. requires: [
  60927. 'Ext.device.Communicator',
  60928. 'Ext.device.orientation.HTML5',
  60929. 'Ext.device.orientation.Sencha'
  60930. ],
  60931. constructor: function() {
  60932. var browserEnv = Ext.browser.is;
  60933. if (browserEnv.Sencha) {
  60934. return Ext.create('Ext.device.orientation.Sencha');
  60935. }
  60936. else {
  60937. return Ext.create('Ext.device.orientation.HTML5');
  60938. }
  60939. }
  60940. });
  60941. /**
  60942. * @private
  60943. */
  60944. Ext.define('Ext.device.purchases.Sencha', {
  60945. /**
  60946. * Checks if the current user is able to make payments.
  60947. *
  60948. * ## Example
  60949. *
  60950. * Ext.device.Purchases.canMakePayments({
  60951. * success: function() {
  60952. * console.log('Yup! :)');
  60953. * },
  60954. * failure: function() {
  60955. * console.log('Nope! :(');
  60956. * }
  60957. * });
  60958. *
  60959. * @param {Object} config
  60960. * @param {Function} config.success
  60961. * @param {Function} config.failure
  60962. * @param {Object} config.scope
  60963. */
  60964. canMakePayments: function(config) {
  60965. if (!config.success) {
  60966. Ext.Logger.error('You must specify a `success` callback for `#canMakePayments` to work.');
  60967. return false;
  60968. }
  60969. if (!config.failure) {
  60970. Ext.Logger.error('You must specify a `failure` callback for `#canMakePayments` to work.');
  60971. return false;
  60972. }
  60973. Ext.device.Communicator.send({
  60974. command: 'Purchase#canMakePayments',
  60975. callbacks: {
  60976. success: config.success,
  60977. failure: config.failure
  60978. },
  60979. scope: config.scope || this
  60980. });
  60981. },
  60982. /**
  60983. * Returns a {@link Ext.data.Store} instance of all the available products.
  60984. *
  60985. * ## Example
  60986. *
  60987. * Ext.device.Purchases.getProducts({
  60988. * success: function(store) {
  60989. * console.log('Got the store! You have ' + store.getCount() + ' products.');
  60990. * },
  60991. * failure: function() {
  60992. * console.log('Oops. Looks like something went wrong.');
  60993. * }
  60994. * });
  60995. *
  60996. * @param {Object} config
  60997. * @param {Function} config.success
  60998. * @param {Ext.data.Store} config.success.store A store of products available to purchase.
  60999. * @param {Function} config.failure
  61000. * @param {Object} config.scope
  61001. */
  61002. getProducts: function(config) {
  61003. if (!config.success) {
  61004. Ext.Logger.error('You must specify a `success` callback for `#getProducts` to work.');
  61005. return false;
  61006. }
  61007. if (!config.failure) {
  61008. Ext.Logger.error('You must specify a `failure` callback for `#getProducts` to work.');
  61009. return false;
  61010. }
  61011. Ext.device.Communicator.send({
  61012. command: 'Purchase#getProducts',
  61013. callbacks: {
  61014. success: function(products) {
  61015. var store = Ext.create('Ext.data.Store', {
  61016. model: 'Ext.device.Purchases.Product',
  61017. data: products
  61018. });
  61019. config.success.call(config.scope || this, store);
  61020. },
  61021. failure: config.failure
  61022. },
  61023. scope: config.scope || this
  61024. });
  61025. },
  61026. /**
  61027. * Returns all purchases ever made by this user.
  61028. * @param {Object} config
  61029. * @param {Function} config.success
  61030. * @param {Array[]} config.success.purchases
  61031. * @param {Function} config.failure
  61032. * @param {Object} config.scope
  61033. */
  61034. getPurchases: function(config) {
  61035. if (!config.success) {
  61036. Ext.Logger.error('You must specify a `success` callback for `#getPurchases` to work.');
  61037. return false;
  61038. }
  61039. if (!config.failure) {
  61040. Ext.Logger.error('You must specify a `failure` callback for `#getPurchases` to work.');
  61041. return false;
  61042. }
  61043. Ext.device.Communicator.send({
  61044. command: 'Purchase#getPurchases',
  61045. callbacks: {
  61046. success: function(purchases) {
  61047. var array = [],
  61048. ln = purchases.length,
  61049. i;
  61050. for (i = 0; i < ln; i++) {
  61051. array.push({
  61052. productIdentifier: purchases[i]
  61053. });
  61054. }
  61055. var store = Ext.create('Ext.data.Store', {
  61056. model: 'Ext.device.Purchases.Purchase',
  61057. data: array
  61058. });
  61059. config.success.call(config.scope || this, store);
  61060. },
  61061. failure: function() {
  61062. config.failure.call(config.scope || this);
  61063. }
  61064. },
  61065. scope: config.scope || this
  61066. });
  61067. },
  61068. /**
  61069. * Returns all purchases that are currently pending.
  61070. * @param {Object} config
  61071. * @param {Function} config.success
  61072. * @param {Ext.data.Store} config.success.purchases
  61073. * @param {Function} config.failure
  61074. * @param {Object} config.scope
  61075. */
  61076. getPendingPurchases: function(config) {
  61077. if (!config.success) {
  61078. Ext.Logger.error('You must specify a `success` callback for `#getPendingPurchases` to work.');
  61079. return false;
  61080. }
  61081. if (!config.failure) {
  61082. Ext.Logger.error('You must specify a `failure` callback for `#getPendingPurchases` to work.');
  61083. return false;
  61084. }
  61085. Ext.device.Communicator.send({
  61086. command: 'Purchase#getPendingPurchases',
  61087. callbacks: {
  61088. success: function(purchases) {
  61089. var array = [],
  61090. ln = purchases.length,
  61091. i;
  61092. for (i = 0; i < ln; i++) {
  61093. array.push({
  61094. productIdentifier: purchases[i],
  61095. state: 'pending'
  61096. });
  61097. }
  61098. var store = Ext.create('Ext.data.Store', {
  61099. model: 'Ext.device.Purchases.Purchase',
  61100. data: array
  61101. });
  61102. config.success.call(config.scope || this, store);
  61103. },
  61104. failure: function() {
  61105. config.failure.call(config.scope || this);
  61106. }
  61107. },
  61108. scope: config.scope || this
  61109. });
  61110. }
  61111. }, function() {
  61112. /**
  61113. * The product model class which is uses when fetching available products using {@link Ext.device.Purchases#getProducts}.
  61114. */
  61115. Ext.define('Ext.device.Purchases.Product', {
  61116. extend: 'Ext.data.Model',
  61117. config: {
  61118. fields: [
  61119. 'localizeTitle',
  61120. 'price',
  61121. 'priceLocale',
  61122. 'localizedDescription',
  61123. 'productIdentifier'
  61124. ]
  61125. },
  61126. /**
  61127. * Will attempt to purchase this product.
  61128. *
  61129. * ## Example
  61130. *
  61131. * product.purchase({
  61132. * success: function() {
  61133. * console.log(product.get('title') + ' purchased!');
  61134. * },
  61135. * failure: function() {
  61136. * console.log('Something went wrong while trying to purchase ' + product.get('title'));
  61137. * }
  61138. * });
  61139. *
  61140. * @param {Object} config
  61141. * @param {Ext.data.Model/String} config.product
  61142. * @param {Function} config.success
  61143. * @param {Function} config.failure
  61144. */
  61145. purchase: function(config) {
  61146. if (!config.success) {
  61147. Ext.Logger.error('You must specify a `success` callback for `#product` to work.');
  61148. return false;
  61149. }
  61150. if (!config.failure) {
  61151. Ext.Logger.error('You must specify a `failure` callback for `#product` to work.');
  61152. return false;
  61153. }
  61154. Ext.device.Communicator.send({
  61155. command: 'Purchase#purchase',
  61156. callbacks: {
  61157. success: config.success,
  61158. failure: config.failure
  61159. },
  61160. identifier: this.get('productIdentifier'),
  61161. scope: config.scope || this
  61162. });
  61163. }
  61164. });
  61165. /**
  61166. *
  61167. */
  61168. Ext.define('Ext.device.Purchases.Purchase', {
  61169. extend: 'Ext.data.Model',
  61170. config: {
  61171. fields: [
  61172. 'productIdentifier',
  61173. 'state'
  61174. ]
  61175. },
  61176. /**
  61177. * Attempts to mark this purchase as complete
  61178. * @param {Object} config
  61179. * @param {Function} config.success
  61180. * @param {Function} config.failure
  61181. * @param {Object} config.scope
  61182. */
  61183. complete: function(config) {
  61184. var me = this;
  61185. if (!config.success) {
  61186. Ext.Logger.error('You must specify a `success` callback for `#complete` to work.');
  61187. return false;
  61188. }
  61189. if (!config.failure) {
  61190. Ext.Logger.error('You must specify a `failure` callback for `#complete` to work.');
  61191. return false;
  61192. }
  61193. if (this.get('state') != "pending") {
  61194. config.failure.call(config.scope || this, "purchase is not pending");
  61195. }
  61196. Ext.device.Communicator.send({
  61197. command: 'Purchase#completePurchase',
  61198. identifier: me.get('productIdentifier'),
  61199. callbacks: {
  61200. success: function() {
  61201. me.set('state', 'complete');
  61202. config.success.call(config.scope || this);
  61203. },
  61204. failure: function() {
  61205. me.set('state', 'pending');
  61206. config.failure.call(config.scope || this);
  61207. }
  61208. },
  61209. scope: config.scope || this
  61210. });
  61211. }
  61212. });
  61213. });
  61214. /**
  61215. *
  61216. *
  61217. * @mixins Ext.device.purchases.Sencha
  61218. *
  61219. * @aside guide native_apis
  61220. */
  61221. Ext.define('Ext.device.Purchases', {
  61222. singleton: true,
  61223. requires: [
  61224. 'Ext.device.purchases.Sencha'
  61225. ],
  61226. constructor: function() {
  61227. return Ext.create('Ext.device.purchases.Sencha');
  61228. }
  61229. });
  61230. /**
  61231. * @private
  61232. */
  61233. Ext.define('Ext.device.push.Abstract', {
  61234. /**
  61235. * @property
  61236. * Notification type: alert.
  61237. */
  61238. ALERT: 1,
  61239. /**
  61240. * @property
  61241. * Notification type: badge.
  61242. */
  61243. BADGE: 2,
  61244. /**
  61245. * @property
  61246. * Notification type: sound.
  61247. */
  61248. SOUND: 4,
  61249. /**
  61250. * @method getInitialConfig
  61251. * @hide
  61252. */
  61253. /**
  61254. * Registers a push notification.
  61255. *
  61256. * Ext.device.Push.register({
  61257. * type: Ext.device.Push.ALERT|Ext.device.Push.BADGE|Ext.device.Push.SOUND,
  61258. * success: function(token) {
  61259. * console.log('# Push notification registration successful:');
  61260. * console.log(' token: ' + token);
  61261. * },
  61262. * failure: function(error) {
  61263. * console.log('# Push notification registration unsuccessful:');
  61264. * console.log(' error: ' + error);
  61265. * },
  61266. * received: function(notifications) {
  61267. * console.log('# Push notification received:');
  61268. * console.log(' ' + JSON.stringify(notifications));
  61269. * }
  61270. * });
  61271. *
  61272. * @param {Object} config
  61273. * The configuration for to pass when registering this push notification service.
  61274. *
  61275. * @param {Number} config.type
  61276. * The type(s) of notifications to enable. Available options are:
  61277. *
  61278. * - {@link Ext.device.Push#ALERT}
  61279. * - {@link Ext.device.Push#BADGE}
  61280. * - {@link Ext.device.Push#SOUND}
  61281. *
  61282. * **Usage**
  61283. *
  61284. * Enable alerts and badges:
  61285. *
  61286. * Ext.device.Push.register({
  61287. * type: Ext.device.Push.ALERT|Ext.device.Push.BADGE
  61288. * // ...
  61289. * });
  61290. *
  61291. * Enable alerts, badges and sounds:
  61292. *
  61293. * Ext.device.Push.register({
  61294. * type: Ext.device.Push.ALERT|Ext.device.Push.BADGE|Ext.device.Push.SOUND
  61295. * // ...
  61296. * });
  61297. *
  61298. * Enable only sounds:
  61299. *
  61300. * Ext.device.Push.register({
  61301. * type: Ext.device.Push.SOUND
  61302. * // ...
  61303. * });
  61304. *
  61305. * @param {Function} config.success
  61306. * The callback to be called when registration is complete.
  61307. *
  61308. * @param {String} config.success.token
  61309. * A unique token for this push notification service.
  61310. *
  61311. * @param {Function} config.failure
  61312. * The callback to be called when registration fails.
  61313. *
  61314. * @param {String} config.failure.error
  61315. * The error message.
  61316. *
  61317. * @param {Function} config.received
  61318. * The callback to be called when a push notification is received on this device.
  61319. *
  61320. * @param {Object} config.received.notifications
  61321. * The notifications that have been received.
  61322. */
  61323. register: function(config) {
  61324. var me = this;
  61325. if (!config.received) {
  61326. Ext.Logger.error('Failed to pass a received callback. This is required.');
  61327. }
  61328. if (!config.type) {
  61329. Ext.Logger.error('Failed to pass a type. This is required.');
  61330. }
  61331. return {
  61332. success: function(token) {
  61333. me.onSuccess(token, config.success, config.scope || me);
  61334. },
  61335. failure: function(error) {
  61336. me.onFailure(error, config.failure, config.scope || me);
  61337. },
  61338. received: function(notifications) {
  61339. me.onReceived(notifications, config.received, config.scope || me);
  61340. },
  61341. type: config.type
  61342. };
  61343. },
  61344. onSuccess: function(token, callback, scope) {
  61345. if (callback) {
  61346. callback.call(scope, token);
  61347. }
  61348. },
  61349. onFailure: function(error, callback, scope) {
  61350. if (callback) {
  61351. callback.call(scope, error);
  61352. }
  61353. },
  61354. onReceived: function(notifications, callback, scope) {
  61355. if (callback) {
  61356. callback.call(scope, notifications);
  61357. }
  61358. }
  61359. });
  61360. /**
  61361. * @private
  61362. */
  61363. Ext.define('Ext.device.push.Sencha', {
  61364. extend: 'Ext.device.push.Abstract',
  61365. register: function() {
  61366. var config = this.callParent(arguments);
  61367. Ext.apply(config, {
  61368. command: 'PushNotification#Register',
  61369. callbacks: {
  61370. success: config.success,
  61371. failure: config.failure,
  61372. received: config.received
  61373. },
  61374. type: config.type
  61375. });
  61376. Ext.device.Communicator.send(config);
  61377. }
  61378. });
  61379. /**
  61380. * Provides a way to send push notifications to a device. Currently only available on iOS.
  61381. *
  61382. * # Example
  61383. *
  61384. * Ext.device.Push.register({
  61385. * type: Ext.device.Push.ALERT|Ext.device.Push.BADGE|Ext.device.Push.SOUND,
  61386. * success: function(token) {
  61387. * console.log('# Push notification registration successful:');
  61388. * console.log(' token: ' + token);
  61389. * },
  61390. * failure: function(error) {
  61391. * console.log('# Push notification registration unsuccessful:');
  61392. * console.log(' error: ' + error);
  61393. * },
  61394. * received: function(notifications) {
  61395. * console.log('# Push notification received:');
  61396. * console.log(' ' + JSON.stringify(notifications));
  61397. * }
  61398. * });
  61399. *
  61400. * @mixins Ext.device.push.Abstract
  61401. *
  61402. * @aside guide native_apis
  61403. */
  61404. Ext.define('Ext.device.Push', {
  61405. singleton: true,
  61406. requires: [
  61407. 'Ext.device.Communicator',
  61408. 'Ext.device.push.Sencha'
  61409. ],
  61410. constructor: function() {
  61411. var browserEnv = Ext.browser.is;
  61412. if (browserEnv.WebView) {
  61413. if (!browserEnv.PhoneGap) {
  61414. return Ext.create('Ext.device.push.Sencha');
  61415. }
  61416. else {
  61417. return Ext.create('Ext.device.push.Abstract');
  61418. }
  61419. }
  61420. else {
  61421. return Ext.create('Ext.device.push.Abstract');
  61422. }
  61423. }
  61424. });
  61425. /**
  61426. * @class Ext.direct.Event
  61427. * A base class for all Ext.direct events. An event is
  61428. * created after some kind of interaction with the server.
  61429. * The event class is essentially just a data structure
  61430. * to hold a Direct response.
  61431. */
  61432. Ext.define('Ext.direct.Event', {
  61433. alias: 'direct.event',
  61434. requires: ['Ext.direct.Manager'],
  61435. config: {
  61436. status: true,
  61437. /**
  61438. * @cfg {Object} data The raw data for this event.
  61439. * @accessor
  61440. */
  61441. data: null,
  61442. /**
  61443. * @cfg {String} name The name of this Event.
  61444. * @accessor
  61445. */
  61446. name: 'event',
  61447. xhr: null,
  61448. code: null,
  61449. message: '',
  61450. result: null,
  61451. transaction: null
  61452. },
  61453. constructor: function(config) {
  61454. this.initConfig(config)
  61455. }
  61456. });
  61457. /**
  61458. * @class Ext.direct.RemotingEvent
  61459. * An event that is fired when data is received from a
  61460. * {@link Ext.direct.RemotingProvider}. Contains a method to the
  61461. * related transaction for the direct request, see {@link #getTransaction}
  61462. */
  61463. Ext.define('Ext.direct.RemotingEvent', {
  61464. extend: 'Ext.direct.Event',
  61465. alias: 'direct.rpc',
  61466. config: {
  61467. name: 'remoting',
  61468. tid: null,
  61469. transaction: null
  61470. },
  61471. /**
  61472. * Get the transaction associated with this event.
  61473. * @return {Ext.direct.Transaction} The transaction
  61474. */
  61475. getTransaction: function() {
  61476. return this._transaction || Ext.direct.Manager.getTransaction(this.getTid());
  61477. }
  61478. });
  61479. /**
  61480. * @class Ext.direct.ExceptionEvent
  61481. * An event that is fired when an exception is received from a {@link Ext.direct.RemotingProvider}
  61482. */
  61483. Ext.define('Ext.direct.ExceptionEvent', {
  61484. extend: 'Ext.direct.RemotingEvent',
  61485. alias: 'direct.exception',
  61486. config: {
  61487. status: false,
  61488. name: 'exception',
  61489. error: null
  61490. }
  61491. });
  61492. /**
  61493. * Ext.direct.Provider is an abstract class meant to be extended.
  61494. *
  61495. * For example Ext JS implements the following subclasses:
  61496. *
  61497. * Provider
  61498. * |
  61499. * +---{@link Ext.direct.JsonProvider JsonProvider}
  61500. * |
  61501. * +---{@link Ext.direct.PollingProvider PollingProvider}
  61502. * |
  61503. * +---{@link Ext.direct.RemotingProvider RemotingProvider}
  61504. *
  61505. * @abstract
  61506. */
  61507. Ext.define('Ext.direct.Provider', {
  61508. alias: 'direct.provider',
  61509. mixins: {
  61510. observable: 'Ext.mixin.Observable'
  61511. },
  61512. config: {
  61513. /**
  61514. * @cfg {String} id
  61515. * The unique id of the provider (defaults to an auto-assigned id).
  61516. * You should assign an id if you need to be able to access the provider later and you do
  61517. * not have an object reference available, for example:
  61518. *
  61519. * Ext.direct.Manager.addProvider({
  61520. * type: 'polling',
  61521. * url: 'php/poll.php',
  61522. * id: 'poll-provider'
  61523. * });
  61524. * var p = {@link Ext.direct.Manager}.{@link Ext.direct.Manager#getProvider getProvider}('poll-provider');
  61525. * p.disconnect();
  61526. *
  61527. */
  61528. id: undefined
  61529. },
  61530. /**
  61531. * @event connect
  61532. * Fires when the Provider connects to the server-side
  61533. * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
  61534. */
  61535. /**
  61536. * @event disconnect
  61537. * Fires when the Provider disconnects from the server-side
  61538. * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
  61539. */
  61540. /**
  61541. * @event data
  61542. * Fires when the Provider receives data from the server-side
  61543. * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
  61544. * @param {Ext.direct.Event} e The Ext.direct.Event type that occurred.
  61545. */
  61546. /**
  61547. * @event exception
  61548. * Fires when the Provider receives an exception from the server-side
  61549. */
  61550. constructor : function(config){
  61551. this.initConfig(config);
  61552. },
  61553. applyId: function(id) {
  61554. if (id === undefined) {
  61555. id = this.getUniqueId();
  61556. }
  61557. return id;
  61558. },
  61559. /**
  61560. * Returns whether or not the server-side is currently connected.
  61561. * Abstract method for subclasses to implement.
  61562. * @return {Boolean}
  61563. */
  61564. isConnected: function() {
  61565. return false;
  61566. },
  61567. /**
  61568. * Abstract methods for subclasses to implement.
  61569. * @method
  61570. */
  61571. connect: Ext.emptyFn,
  61572. /**
  61573. * Abstract methods for subclasses to implement.
  61574. * @method
  61575. */
  61576. disconnect: Ext.emptyFn
  61577. });
  61578. /**
  61579. * @class Ext.direct.JsonProvider
  61580. *
  61581. * A base provider for communicating using JSON. This is an abstract class
  61582. * and should not be instanced directly.
  61583. * @abstract
  61584. */
  61585. Ext.define('Ext.direct.JsonProvider', {
  61586. extend: 'Ext.direct.Provider',
  61587. alias: 'direct.jsonprovider',
  61588. uses: ['Ext.direct.ExceptionEvent'],
  61589. /**
  61590. * Parse the JSON response.
  61591. * @private
  61592. * @param {Object} response The XHR response object.
  61593. * @return {Object} The data in the response.
  61594. */
  61595. parseResponse: function(response) {
  61596. if (!Ext.isEmpty(response.responseText)) {
  61597. if (Ext.isObject(response.responseText)) {
  61598. return response.responseText;
  61599. }
  61600. return Ext.decode(response.responseText);
  61601. }
  61602. return null;
  61603. },
  61604. /**
  61605. * Creates a set of events based on the XHR response.
  61606. * @private
  61607. * @param {Object} response The XHR response.
  61608. * @return {Ext.direct.Event[]} An array of {@link Ext.direct.Event} objects.
  61609. */
  61610. createEvents: function(response) {
  61611. var data = null,
  61612. events = [],
  61613. i = 0,
  61614. ln, event;
  61615. try {
  61616. data = this.parseResponse(response);
  61617. } catch(e) {
  61618. event = Ext.create('Ext.direct.ExceptionEvent', {
  61619. data: e,
  61620. xhr: response,
  61621. code: Ext.direct.Manager.exceptions.PARSE,
  61622. message: 'Error parsing json response: \n\n ' + data
  61623. });
  61624. return [event];
  61625. }
  61626. if (Ext.isArray(data)) {
  61627. for (ln = data.length; i < ln; ++i) {
  61628. events.push(this.createEvent(data[i]));
  61629. }
  61630. } else {
  61631. events.push(this.createEvent(data));
  61632. }
  61633. return events;
  61634. },
  61635. /**
  61636. * Create an event from a response object.
  61637. * @param {Object} response The XHR response object.
  61638. * @return {Ext.direct.Event} The event.
  61639. */
  61640. createEvent: function(response) {
  61641. return Ext.create('direct.' + response.type, response);
  61642. }
  61643. });
  61644. /**
  61645. * The DelayedTask class provides a convenient way to "buffer" the execution of a method,
  61646. * performing `setTimeout` where a new timeout cancels the old timeout. When called, the
  61647. * task will wait the specified time period before executing. If during that time period,
  61648. * the task is called again, the original call will be canceled. This continues so that
  61649. * the function is only called a single time for each iteration.
  61650. *
  61651. * This method is especially useful for things like detecting whether a user has finished
  61652. * typing in a text field. An example would be performing validation on a keypress. You can
  61653. * use this class to buffer the keypress events for a certain number of milliseconds, and
  61654. * perform only if they stop for that amount of time.
  61655. *
  61656. * Using {@link Ext.util.DelayedTask} is very simple:
  61657. *
  61658. * //create the delayed task instance with our callback
  61659. * var task = Ext.create('Ext.util.DelayedTask', function() {
  61660. * console.log('callback!');
  61661. * });
  61662. *
  61663. * task.delay(1500); //the callback function will now be called after 1500ms
  61664. *
  61665. * task.cancel(); //the callback function will never be called now, unless we call delay() again
  61666. *
  61667. * ## Example
  61668. *
  61669. * @example
  61670. * //create a textfield where we can listen to text
  61671. * var field = Ext.create('Ext.field.Text', {
  61672. * xtype: 'textfield',
  61673. * label: 'Length: 0'
  61674. * });
  61675. *
  61676. * //add the textfield into a fieldset
  61677. * Ext.Viewport.add({
  61678. * xtype: 'formpanel',
  61679. * items: [{
  61680. * xtype: 'fieldset',
  61681. * items: [field],
  61682. * instructions: 'Type into the field and watch the count go up after 500ms.'
  61683. * }]
  61684. * });
  61685. *
  61686. * //create our delayed task with a function that returns the fields length as the fields label
  61687. * var task = Ext.create('Ext.util.DelayedTask', function() {
  61688. * field.setLabel('Length: ' + field.getValue().length);
  61689. * });
  61690. *
  61691. * // Wait 500ms before calling our function. If the user presses another key
  61692. * // during that 500ms, it will be canceled and we'll wait another 500ms.
  61693. * field.on('keyup', function() {
  61694. * task.delay(500);
  61695. * });
  61696. *
  61697. * @constructor
  61698. * The parameters to this constructor serve as defaults and are not required.
  61699. * @param {Function} fn The default function to call.
  61700. * @param {Object} scope The default scope (The `this` reference) in which the function is called. If
  61701. * not specified, `this` will refer to the browser window.
  61702. * @param {Array} args The default Array of arguments.
  61703. */
  61704. Ext.define('Ext.util.DelayedTask', {
  61705. config: {
  61706. interval: null,
  61707. delay: null,
  61708. fn: null,
  61709. scope: null,
  61710. args: null
  61711. },
  61712. constructor: function(fn, scope, args) {
  61713. var config = {
  61714. fn: fn,
  61715. scope: scope,
  61716. args: args
  61717. };
  61718. this.initConfig(config);
  61719. },
  61720. /**
  61721. * Cancels any pending timeout and queues a new one.
  61722. * @param {Number} delay The milliseconds to delay
  61723. * @param {Function} newFn Overrides the original function passed when instantiated.
  61724. * @param {Object} newScope Overrides the original `scope` passed when instantiated. Remember that if no scope
  61725. * is specified, `this` will refer to the browser window.
  61726. * @param {Array} newArgs Overrides the original `args` passed when instantiated.
  61727. */
  61728. delay: function(delay, newFn, newScope, newArgs) {
  61729. var me = this;
  61730. //cancel any existing queued functions
  61731. me.cancel();
  61732. //set all the new configurations
  61733. me.setConfig({
  61734. delay: delay,
  61735. fn: newFn,
  61736. scope: newScope,
  61737. args: newArgs
  61738. });
  61739. //create the callback method for this delayed task
  61740. var call = function() {
  61741. me.getFn().apply(me.getScope(), me.getArgs() || []);
  61742. me.cancel();
  61743. };
  61744. me.setInterval(setInterval(call, me.getDelay()));
  61745. },
  61746. /**
  61747. * Cancel the last queued timeout
  61748. */
  61749. cancel: function() {
  61750. this.setInterval(null);
  61751. },
  61752. /**
  61753. * @private
  61754. * Clears the old interval
  61755. */
  61756. updateInterval: function(newInterval, oldInterval) {
  61757. if (oldInterval) {
  61758. clearInterval(oldInterval);
  61759. }
  61760. },
  61761. /**
  61762. * @private
  61763. * Changes the value into an array if it isn't one.
  61764. */
  61765. applyArgs: function(config) {
  61766. if (!Ext.isArray(config)) {
  61767. config = [config];
  61768. }
  61769. return config;
  61770. }
  61771. });
  61772. /**
  61773. * @class Ext.direct.PollingProvider
  61774. *
  61775. * Provides for repetitive polling of the server at distinct {@link #interval intervals}.
  61776. * The initial request for data originates from the client, and then is responded to by the
  61777. * server.
  61778. *
  61779. * All configurations for the PollingProvider should be generated by the server-side
  61780. * API portion of the Ext.Direct stack.
  61781. *
  61782. * An instance of PollingProvider may be created directly via the new keyword or by simply
  61783. * specifying `type = 'polling'`. For example:
  61784. *
  61785. * var pollA = Ext.create('Ext.direct.PollingProvider', {
  61786. * type:'polling',
  61787. * url: 'php/pollA.php'
  61788. * });
  61789. *
  61790. * Ext.direct.Manager.addProvider(pollA);
  61791. * pollA.disconnect();
  61792. *
  61793. * Ext.direct.Manager.addProvider({
  61794. * type:'polling',
  61795. * url: 'php/pollB.php',
  61796. * id: 'pollB-provider'
  61797. * });
  61798. *
  61799. * var pollB = Ext.direct.Manager.getProvider('pollB-provider');
  61800. */
  61801. Ext.define('Ext.direct.PollingProvider', {
  61802. extend: 'Ext.direct.JsonProvider',
  61803. alias: 'direct.pollingprovider',
  61804. uses: ['Ext.direct.ExceptionEvent'],
  61805. requires: ['Ext.Ajax', 'Ext.util.DelayedTask'],
  61806. config: {
  61807. /**
  61808. * @cfg {Number} interval
  61809. * How often to poll the server-side, in milliseconds.
  61810. */
  61811. interval: 3000,
  61812. /**
  61813. * @cfg {Object} baseParams
  61814. * An object containing properties which are to be sent as parameters on every polling request.
  61815. */
  61816. baseParams: null,
  61817. /**
  61818. * @cfg {String/Function} url
  61819. * The url which the PollingProvider should contact with each request. This can also be
  61820. * an imported {@link Ext.Direct} method which will accept the `{@link #baseParams}` as its only argument.
  61821. */
  61822. url: null
  61823. },
  61824. /**
  61825. * @event beforepoll
  61826. * Fired immediately before a poll takes place, an event handler can return `false`
  61827. * in order to cancel the poll.
  61828. * @param {Ext.direct.PollingProvider} this
  61829. */
  61830. /**
  61831. * @event poll
  61832. * This event has not yet been implemented.
  61833. * @param {Ext.direct.PollingProvider} this
  61834. */
  61835. /**
  61836. * @inheritdoc
  61837. */
  61838. isConnected: function() {
  61839. return !!this.pollTask;
  61840. },
  61841. /**
  61842. * Connect to the server-side and begin the polling process. To handle each
  61843. * response subscribe to the `data` event.
  61844. */
  61845. connect: function() {
  61846. var me = this,
  61847. url = me.getUrl(),
  61848. baseParams = me.getBaseParams();
  61849. if (url && !me.pollTask) {
  61850. me.pollTask = setInterval(function() {
  61851. if (me.fireEvent('beforepoll', me) !== false) {
  61852. if (Ext.isFunction(url)) {
  61853. url(baseParams);
  61854. } else {
  61855. Ext.Ajax.request({
  61856. url: url,
  61857. callback: me.onData,
  61858. scope: me,
  61859. params: baseParams
  61860. });
  61861. }
  61862. }
  61863. }, me.getInterval());
  61864. me.fireEvent('connect', me);
  61865. } else if (!url) {
  61866. //<debug>
  61867. Ext.Error.raise('Error initializing PollingProvider, no url configured.');
  61868. //</debug>
  61869. }
  61870. },
  61871. /**
  61872. * Disconnect from the server-side and stop the polling process. The `disconnect`
  61873. * event will be fired on a successful disconnect.
  61874. */
  61875. disconnect: function() {
  61876. var me = this;
  61877. if (me.pollTask) {
  61878. clearInterval(me.pollTask);
  61879. delete me.pollTask;
  61880. me.fireEvent('disconnect', me);
  61881. }
  61882. },
  61883. // @private
  61884. onData: function(opt, success, response) {
  61885. var me = this,
  61886. i = 0,
  61887. len,
  61888. events;
  61889. if (success) {
  61890. events = me.createEvents(response);
  61891. for (len = events.length; i < len; ++i) {
  61892. me.fireEvent('data', me, events[i]);
  61893. }
  61894. } else {
  61895. me.fireEvent('data', me, Ext.create('Ext.direct.ExceptionEvent', {
  61896. data: null,
  61897. code: Ext.direct.Manager.exceptions.TRANSPORT,
  61898. message: 'Unable to connect to the server.',
  61899. xhr: response
  61900. }));
  61901. }
  61902. }
  61903. });
  61904. /**
  61905. * Small utility class used internally to represent a Direct method.
  61906. * @class Ext.direct.RemotingMethod
  61907. * @private
  61908. */
  61909. Ext.define('Ext.direct.RemotingMethod', {
  61910. config: {
  61911. name: null,
  61912. params: null,
  61913. formHandler: null,
  61914. len: null,
  61915. ordered: true
  61916. },
  61917. constructor: function(config) {
  61918. this.initConfig(config);
  61919. },
  61920. applyParams: function(params) {
  61921. if (Ext.isNumber(params)) {
  61922. this.setLen(params);
  61923. } else if (Ext.isArray(params)) {
  61924. this.setOrdered(false);
  61925. var ln = params.length,
  61926. ret = [],
  61927. i, param, name;
  61928. for (i = 0; i < ln; i++) {
  61929. param = params[i];
  61930. name = Ext.isObject(param) ? param.name : param;
  61931. ret.push(name);
  61932. }
  61933. return ret;
  61934. }
  61935. },
  61936. getArgs: function(params, paramOrder, paramsAsHash) {
  61937. var args = [],
  61938. i, ln;
  61939. if (this.getOrdered()) {
  61940. if (this.getLen() > 0) {
  61941. // If a paramOrder was specified, add the params into the argument list in that order.
  61942. if (paramOrder) {
  61943. for (i = 0, ln = paramOrder.length; i < ln; i++) {
  61944. args.push(params[paramOrder[i]]);
  61945. }
  61946. } else if (paramsAsHash) {
  61947. // If paramsAsHash was specified, add all the params as a single object argument.
  61948. args.push(params);
  61949. }
  61950. }
  61951. } else {
  61952. args.push(params);
  61953. }
  61954. return args;
  61955. },
  61956. /**
  61957. * Takes the arguments for the Direct function and splits the arguments
  61958. * from the scope and the callback.
  61959. * @param {Array} args The arguments passed to the direct call
  61960. * @return {Object} An object with 3 properties, args, callback & scope.
  61961. */
  61962. getCallData: function(args) {
  61963. var me = this,
  61964. data = null,
  61965. len = me.getLen(),
  61966. params = me.getParams(),
  61967. callback, scope, name;
  61968. if (me.getOrdered()) {
  61969. callback = args[len];
  61970. scope = args[len + 1];
  61971. if (len !== 0) {
  61972. data = args.slice(0, len);
  61973. }
  61974. } else {
  61975. data = Ext.apply({}, args[0]);
  61976. callback = args[1];
  61977. scope = args[2];
  61978. for (name in data) {
  61979. if (data.hasOwnProperty(name)) {
  61980. if (!Ext.Array.contains(params, name)) {
  61981. delete data[name];
  61982. }
  61983. }
  61984. }
  61985. }
  61986. return {
  61987. data: data,
  61988. callback: callback,
  61989. scope: scope
  61990. };
  61991. }
  61992. });
  61993. /**
  61994. * Supporting Class for Ext.Direct (not intended to be used directly).
  61995. */
  61996. Ext.define('Ext.direct.Transaction', {
  61997. alias: 'direct.transaction',
  61998. alternateClassName: 'Ext.Direct.Transaction',
  61999. statics: {
  62000. TRANSACTION_ID: 0
  62001. },
  62002. config: {
  62003. id: undefined,
  62004. provider: null,
  62005. retryCount: 0,
  62006. args: null,
  62007. action: null,
  62008. method: null,
  62009. data: null,
  62010. callback: null,
  62011. form: null
  62012. },
  62013. constructor: function(config) {
  62014. this.initConfig(config);
  62015. },
  62016. applyId: function(id) {
  62017. if (id === undefined) {
  62018. id = ++this.self.TRANSACTION_ID;
  62019. }
  62020. return id;
  62021. },
  62022. updateId: function(id) {
  62023. this.id = this.tid = id;
  62024. },
  62025. getTid: function() {
  62026. return this.tid;
  62027. },
  62028. send: function(){
  62029. this.getProvider().queueTransaction(this);
  62030. },
  62031. retry: function(){
  62032. this.setRetryCount(this.getRetryCount() + 1);
  62033. this.send();
  62034. }
  62035. });
  62036. /**
  62037. * @class Ext.direct.RemotingProvider
  62038. *
  62039. * The {@link Ext.direct.RemotingProvider RemotingProvider} exposes access to
  62040. * server side methods on the client (a remote procedure call (RPC) type of
  62041. * connection where the client can initiate a procedure on the server).
  62042. *
  62043. * This allows for code to be organized in a fashion that is maintainable,
  62044. * while providing a clear path between client and server, something that is
  62045. * not always apparent when using URLs.
  62046. *
  62047. * To accomplish this the server-side needs to describe what classes and methods
  62048. * are available on the client-side. This configuration will typically be
  62049. * outputted by the server-side Ext.Direct stack when the API description is built.
  62050. */
  62051. Ext.define('Ext.direct.RemotingProvider', {
  62052. alias: 'direct.remotingprovider',
  62053. extend: 'Ext.direct.JsonProvider',
  62054. requires: [
  62055. 'Ext.util.MixedCollection',
  62056. 'Ext.util.DelayedTask',
  62057. 'Ext.direct.Transaction',
  62058. 'Ext.direct.RemotingMethod'
  62059. ],
  62060. config: {
  62061. /**
  62062. * @cfg {String/Object} namespace
  62063. * Namespace for the Remoting Provider (defaults to the browser global scope of _window_).
  62064. * Explicitly specify the namespace Object, or specify a String to have a
  62065. * {@link Ext#namespace namespace created} implicitly.
  62066. */
  62067. namespace: undefined,
  62068. /**
  62069. * @cfg {String} url (required) The url to connect to the {@link Ext.direct.Manager} server-side router.
  62070. */
  62071. url: null,
  62072. /**
  62073. * @cfg {String} enableUrlEncode
  62074. * Specify which param will hold the arguments for the method.
  62075. */
  62076. enableUrlEncode: null,
  62077. /**
  62078. * @cfg {Number/Boolean} enableBuffer
  62079. *
  62080. * `true` or `false` to enable or disable combining of method
  62081. * calls. If a number is specified this is the amount of time in milliseconds
  62082. * to wait before sending a batched request.
  62083. *
  62084. * Calls which are received within the specified timeframe will be
  62085. * concatenated together and sent in a single request, optimizing the
  62086. * application by reducing the amount of round trips that have to be made
  62087. * to the server.
  62088. */
  62089. enableBuffer: 10,
  62090. /**
  62091. * @cfg {Number} maxRetries
  62092. * Number of times to re-attempt delivery on failure of a call.
  62093. */
  62094. maxRetries: 1,
  62095. /**
  62096. * @cfg {Number} timeout
  62097. * The timeout to use for each request.
  62098. */
  62099. timeout: undefined,
  62100. /**
  62101. * @cfg {Object} actions
  62102. * Object literal defining the server side actions and methods. For example, if
  62103. * the Provider is configured with:
  62104. *
  62105. * actions: { // each property within the 'actions' object represents a server side Class
  62106. * // array of methods within each server side Class to be stubbed out on client
  62107. * TestAction: [{
  62108. * name: "doEcho",
  62109. * len: 1
  62110. * }, {
  62111. * "name": "multiply", // name of method
  62112. * "len": 2 // The number of parameters that will be used to create an
  62113. * // array of data to send to the server side function.
  62114. * // Ensure the server sends back a Number, not a String.
  62115. * }, {
  62116. * name: "doForm",
  62117. * formHandler: true, // direct the client to use specialized form handling method
  62118. * len: 1
  62119. * }]
  62120. * }
  62121. *
  62122. * __Note:__ A Store is not required, a server method can be called at any time.
  62123. * In the following example a **client side** handler is used to call the
  62124. * server side method "multiply" in the server-side "TestAction" Class:
  62125. *
  62126. * TestAction.multiply(
  62127. * 2, 4, // pass two arguments to server, so specify len=2
  62128. * // callback function after the server is called
  62129. * // result: the result returned by the server
  62130. * // e: Ext.direct.RemotingEvent object
  62131. * function(result, e) {
  62132. * var t = e.getTransaction();
  62133. * var action = t.action; // server side Class called
  62134. * var method = t.method; // server side method called
  62135. * if (e.getStatus()) {
  62136. * var answer = Ext.encode(result); // 8
  62137. * } else {
  62138. * var msg = e.getMessage(); // failure message
  62139. * }
  62140. * }
  62141. * );
  62142. *
  62143. * In the example above, the server side "multiply" function will be passed two
  62144. * arguments (2 and 4). The "multiply" method should return the value 8 which will be
  62145. * available as the `result` in the example above.
  62146. */
  62147. actions: {}
  62148. },
  62149. /**
  62150. * @event beforecall
  62151. * Fires immediately before the client-side sends off the RPC call.
  62152. * By returning `false` from an event handler you can prevent the call from
  62153. * executing.
  62154. * @param {Ext.direct.RemotingProvider} provider
  62155. * @param {Ext.direct.Transaction} transaction
  62156. * @param {Object} meta The meta data.
  62157. */
  62158. /**
  62159. * @event call
  62160. * Fires immediately after the request to the server-side is sent. This does
  62161. * NOT fire after the response has come back from the call.
  62162. * @param {Ext.direct.RemotingProvider} provider
  62163. * @param {Ext.direct.Transaction} transaction
  62164. * @param {Object} meta The meta data.
  62165. */
  62166. constructor : function(config) {
  62167. var me = this;
  62168. me.callParent(arguments);
  62169. me.transactions = Ext.create('Ext.util.Collection', function(item) {
  62170. return item.getId();
  62171. });
  62172. me.callBuffer = [];
  62173. },
  62174. applyNamespace: function(namespace) {
  62175. if (Ext.isString(namespace)) {
  62176. return Ext.ns(namespace);
  62177. }
  62178. return namespace || window;
  62179. },
  62180. /**
  62181. * Initialize the API
  62182. * @private
  62183. */
  62184. initAPI : function() {
  62185. var actions = this.getActions(),
  62186. namespace = this.getNamespace(),
  62187. action, cls, methods,
  62188. i, ln, method;
  62189. for (action in actions) {
  62190. if (actions.hasOwnProperty(action)) {
  62191. cls = namespace[action];
  62192. if (!cls) {
  62193. cls = namespace[action] = {};
  62194. }
  62195. methods = actions[action];
  62196. for (i = 0, ln = methods.length; i < ln; ++i) {
  62197. method = Ext.create('Ext.direct.RemotingMethod', methods[i]);
  62198. cls[method.getName()] = this.createHandler(action, method);
  62199. }
  62200. }
  62201. }
  62202. },
  62203. /**
  62204. * Create a handler function for a direct call.
  62205. * @private
  62206. * @param {String} action The action the call is for.
  62207. * @param {Object} method The details of the method.
  62208. * @return {Function} A JavaScript function that will kick off the call.
  62209. */
  62210. createHandler : function(action, method) {
  62211. var me = this,
  62212. handler;
  62213. if (!method.getFormHandler()) {
  62214. handler = function() {
  62215. me.configureRequest(action, method, Array.prototype.slice.call(arguments, 0));
  62216. };
  62217. } else {
  62218. handler = function(form, callback, scope) {
  62219. me.configureFormRequest(action, method, form, callback, scope);
  62220. };
  62221. }
  62222. handler.directCfg = {
  62223. action: action,
  62224. method: method
  62225. };
  62226. return handler;
  62227. },
  62228. // @inheritdoc
  62229. isConnected: function() {
  62230. return !!this.connected;
  62231. },
  62232. // @inheritdoc
  62233. connect: function() {
  62234. var me = this;
  62235. if (me.getUrl()) {
  62236. me.initAPI();
  62237. me.connected = true;
  62238. me.fireEvent('connect', me);
  62239. } else {
  62240. //<debug>
  62241. Ext.Error.raise('Error initializing RemotingProvider, no url configured.');
  62242. //</debug>
  62243. }
  62244. },
  62245. // @inheritdoc
  62246. disconnect: function() {
  62247. var me = this;
  62248. if (me.connected) {
  62249. me.connected = false;
  62250. me.fireEvent('disconnect', me);
  62251. }
  62252. },
  62253. /**
  62254. * Run any callbacks related to the transaction.
  62255. * @private
  62256. * @param {Ext.direct.Transaction} transaction The transaction
  62257. * @param {Ext.direct.Event} event The event
  62258. */
  62259. runCallback: function(transaction, event) {
  62260. var success = !!event.getStatus(),
  62261. functionName = success ? 'success' : 'failure',
  62262. callback = transaction && transaction.getCallback(),
  62263. result;
  62264. if (callback) {
  62265. // this doesnt make any sense. why do we have both result and data?
  62266. // result = Ext.isDefined(event.getResult()) ? event.result : event.data;
  62267. result = event.getResult();
  62268. if (Ext.isFunction(callback)) {
  62269. callback(result, event, success);
  62270. } else {
  62271. Ext.callback(callback[functionName], callback.scope, [result, event, success]);
  62272. Ext.callback(callback.callback, callback.scope, [result, event, success]);
  62273. }
  62274. }
  62275. },
  62276. /**
  62277. * React to the AJAX request being completed.
  62278. * @private
  62279. */
  62280. onData: function(options, success, response) {
  62281. var me = this,
  62282. i = 0,
  62283. ln, events, event,
  62284. transaction, transactions;
  62285. if (success) {
  62286. events = me.createEvents(response);
  62287. for (ln = events.length; i < ln; ++i) {
  62288. event = events[i];
  62289. transaction = me.getTransaction(event);
  62290. me.fireEvent('data', me, event);
  62291. if (transaction) {
  62292. me.runCallback(transaction, event, true);
  62293. Ext.direct.Manager.removeTransaction(transaction);
  62294. }
  62295. }
  62296. } else {
  62297. transactions = [].concat(options.transaction);
  62298. for (ln = transactions.length; i < ln; ++i) {
  62299. transaction = me.getTransaction(transactions[i]);
  62300. if (transaction && transaction.getRetryCount() < me.getMaxRetries()) {
  62301. transaction.retry();
  62302. } else {
  62303. event = Ext.create('Ext.direct.ExceptionEvent', {
  62304. data: null,
  62305. transaction: transaction,
  62306. code: Ext.direct.Manager.exceptions.TRANSPORT,
  62307. message: 'Unable to connect to the server.',
  62308. xhr: response
  62309. });
  62310. me.fireEvent('data', me, event);
  62311. if (transaction) {
  62312. me.runCallback(transaction, event, false);
  62313. Ext.direct.Manager.removeTransaction(transaction);
  62314. }
  62315. }
  62316. }
  62317. }
  62318. },
  62319. /**
  62320. * Get transaction from XHR options.
  62321. * @private
  62322. * @param {Object} options The options sent to the AJAX request.
  62323. * @return {Ext.direct.Transaction/null} The transaction, `null` if not found.
  62324. */
  62325. getTransaction: function(options) {
  62326. return options && options.getTid ? Ext.direct.Manager.getTransaction(options.getTid()) : null;
  62327. },
  62328. /**
  62329. * Configure a direct request.
  62330. * @private
  62331. * @param {String} action The action being executed.
  62332. * @param {Object} method The being executed.
  62333. */
  62334. configureRequest: function(action, method, args) {
  62335. var me = this,
  62336. callData = method.getCallData(args),
  62337. data = callData.data,
  62338. callback = callData.callback,
  62339. scope = callData.scope,
  62340. transaction;
  62341. transaction = Ext.create('Ext.direct.Transaction', {
  62342. provider: me,
  62343. args: args,
  62344. action: action,
  62345. method: method.getName(),
  62346. data: data,
  62347. callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback
  62348. });
  62349. if (me.fireEvent('beforecall', me, transaction, method) !== false) {
  62350. Ext.direct.Manager.addTransaction(transaction);
  62351. me.queueTransaction(transaction);
  62352. me.fireEvent('call', me, transaction, method);
  62353. }
  62354. },
  62355. /**
  62356. * Gets the AJAX call info for a transaction.
  62357. * @private
  62358. * @param {Ext.direct.Transaction} transaction The transaction.
  62359. * @return {Object} The call params.
  62360. */
  62361. getCallData: function(transaction) {
  62362. return {
  62363. action: transaction.getAction(),
  62364. method: transaction.getMethod(),
  62365. data: transaction.getData(),
  62366. type: 'rpc',
  62367. tid: transaction.getId()
  62368. };
  62369. },
  62370. /**
  62371. * Sends a request to the server.
  62372. * @private
  62373. * @param {Object/Array} data The data to send.
  62374. */
  62375. sendRequest : function(data) {
  62376. var me = this,
  62377. request = {
  62378. url: me.getUrl(),
  62379. callback: me.onData,
  62380. scope: me,
  62381. transaction: data,
  62382. timeout: me.getTimeout()
  62383. }, callData,
  62384. enableUrlEncode = me.getEnableUrlEncode(),
  62385. i = 0,
  62386. ln, params;
  62387. if (Ext.isArray(data)) {
  62388. callData = [];
  62389. for (ln = data.length; i < ln; ++i) {
  62390. callData.push(me.getCallData(data[i]));
  62391. }
  62392. } else {
  62393. callData = me.getCallData(data);
  62394. }
  62395. if (enableUrlEncode) {
  62396. params = {};
  62397. params[Ext.isString(enableUrlEncode) ? enableUrlEncode : 'data'] = Ext.encode(callData);
  62398. request.params = params;
  62399. } else {
  62400. request.jsonData = callData;
  62401. }
  62402. Ext.Ajax.request(request);
  62403. },
  62404. /**
  62405. * Add a new transaction to the queue.
  62406. * @private
  62407. * @param {Ext.direct.Transaction} transaction The transaction.
  62408. */
  62409. queueTransaction: function(transaction) {
  62410. var me = this,
  62411. enableBuffer = me.getEnableBuffer();
  62412. if (transaction.getForm()) {
  62413. me.sendFormRequest(transaction);
  62414. return;
  62415. }
  62416. me.callBuffer.push(transaction);
  62417. if (enableBuffer) {
  62418. if (!me.callTask) {
  62419. me.callTask = Ext.create('Ext.util.DelayedTask', me.combineAndSend, me);
  62420. }
  62421. me.callTask.delay(Ext.isNumber(enableBuffer) ? enableBuffer : 10);
  62422. } else {
  62423. me.combineAndSend();
  62424. }
  62425. },
  62426. /**
  62427. * Combine any buffered requests and send them off.
  62428. * @private
  62429. */
  62430. combineAndSend : function() {
  62431. var buffer = this.callBuffer,
  62432. ln = buffer.length;
  62433. if (ln > 0) {
  62434. this.sendRequest(ln == 1 ? buffer[0] : buffer);
  62435. this.callBuffer = [];
  62436. }
  62437. }
  62438. // /**
  62439. // * Configure a form submission request.
  62440. // * @private
  62441. // * @param {String} action The action being executed.
  62442. // * @param {Object} method The method being executed.
  62443. // * @param {HTMLElement} form The form being submitted.
  62444. // * @param {Function} callback (optional) A callback to run after the form submits.
  62445. // * @param {Object} scope (optional) A scope to execute the callback in.
  62446. // */
  62447. // configureFormRequest : function(action, method, form, callback, scope){
  62448. // var me = this,
  62449. // transaction = new Ext.direct.Transaction({
  62450. // provider: me,
  62451. // action: action,
  62452. // method: method.name,
  62453. // args: [form, callback, scope],
  62454. // callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback,
  62455. // isForm: true
  62456. // }),
  62457. // isUpload,
  62458. // params;
  62459. //
  62460. // if (me.fireEvent('beforecall', me, transaction, method) !== false) {
  62461. // Ext.direct.Manager.addTransaction(transaction);
  62462. // isUpload = String(form.getAttribute("enctype")).toLowerCase() == 'multipart/form-data';
  62463. //
  62464. // params = {
  62465. // extTID: transaction.id,
  62466. // extAction: action,
  62467. // extMethod: method.name,
  62468. // extType: 'rpc',
  62469. // extUpload: String(isUpload)
  62470. // };
  62471. //
  62472. // // change made from typeof callback check to callback.params
  62473. // // to support addl param passing in DirectSubmit EAC 6/2
  62474. // Ext.apply(transaction, {
  62475. // form: Ext.getDom(form),
  62476. // isUpload: isUpload,
  62477. // params: callback && Ext.isObject(callback.params) ? Ext.apply(params, callback.params) : params
  62478. // });
  62479. // me.fireEvent('call', me, transaction, method);
  62480. // me.sendFormRequest(transaction);
  62481. // }
  62482. // },
  62483. //
  62484. // /**
  62485. // * Sends a form request
  62486. // * @private
  62487. // * @param {Ext.direct.Transaction} transaction The transaction to send
  62488. // */
  62489. // sendFormRequest: function(transaction){
  62490. // Ext.Ajax.request({
  62491. // url: this.url,
  62492. // params: transaction.params,
  62493. // callback: this.onData,
  62494. // scope: this,
  62495. // form: transaction.form,
  62496. // isUpload: transaction.isUpload,
  62497. // transaction: transaction
  62498. // });
  62499. // }
  62500. });
  62501. /**
  62502. * This class encapsulates a _collection_ of DOM elements, providing methods to filter members, or to perform collective
  62503. * actions upon the whole set.
  62504. *
  62505. * Although they are not listed, this class supports all of the methods of {@link Ext.dom.Element}. The methods from
  62506. * these classes will be performed on all the elements in this collection.
  62507. *
  62508. * All methods return _this_ and can be chained.
  62509. *
  62510. * Usage:
  62511. *
  62512. * var els = Ext.select("#some-el div.some-class", true);
  62513. * // or select directly from an existing element
  62514. * var el = Ext.get('some-el');
  62515. * el.select('div.some-class', true);
  62516. *
  62517. * els.setWidth(100); // all elements become 100 width
  62518. * els.hide(true); // all elements fade out and hide
  62519. * // or
  62520. * els.setWidth(100).hide(true);
  62521. */
  62522. Ext.define('Ext.dom.CompositeElement', {
  62523. alternateClassName: 'Ext.CompositeElement',
  62524. extend: 'Ext.dom.CompositeElementLite',
  62525. // @private
  62526. getElement: function(el) {
  62527. // In this case just return it, since we already have a reference to it
  62528. return el;
  62529. },
  62530. // @private
  62531. transformElement: function(el) {
  62532. return Ext.get(el);
  62533. }
  62534. }, function() {
  62535. Ext.dom.Element.select = function(selector, unique, root) {
  62536. var elements;
  62537. if (typeof selector == "string") {
  62538. elements = Ext.dom.Element.selectorFunction(selector, root);
  62539. }
  62540. else if (selector.length !== undefined) {
  62541. elements = selector;
  62542. }
  62543. else {
  62544. //<debug>
  62545. throw new Error("[Ext.select] Invalid selector specified: " + selector);
  62546. //</debug>
  62547. }
  62548. return (unique === true) ? new Ext.CompositeElement(elements) : new Ext.CompositeElementLite(elements);
  62549. };
  62550. });
  62551. // Using @mixins to include all members of Ext.event.Touch
  62552. // into here to keep documentation simpler
  62553. /**
  62554. * @mixins Ext.event.Touch
  62555. *
  62556. * Just as {@link Ext.dom.Element} wraps around a native DOM node, {@link Ext.event.Event} wraps the browser's native
  62557. * event-object normalizing cross-browser differences such as mechanisms to stop event-propagation along with a method
  62558. * to prevent default actions from taking place.
  62559. *
  62560. * Here is a simple example of how you use it:
  62561. *
  62562. * @example preview
  62563. * Ext.Viewport.add({
  62564. * layout: 'fit',
  62565. * items: [
  62566. * {
  62567. * docked: 'top',
  62568. * xtype: 'toolbar',
  62569. * title: 'Ext.event.Event example!'
  62570. * },
  62571. * {
  62572. * id: 'logger',
  62573. * styleHtmlContent: true,
  62574. * html: 'Tap somewhere!',
  62575. * padding: 5
  62576. * }
  62577. * ]
  62578. * });
  62579. *
  62580. * Ext.Viewport.element.on({
  62581. * tap: function(e, node) {
  62582. * var string = '';
  62583. *
  62584. * string += 'You tapped at: <strong>{ x: ' + e.pageX + ', y: ' + e.pageY + ' }</strong> <i>(e.pageX & e.pageY)</i>';
  62585. * string += '<hr />';
  62586. * string += 'The HTMLElement you tapped has the className of: <strong>' + e.target.className + '</strong> <i>(e.target)</i>';
  62587. * string += '<hr />';
  62588. * string += 'The HTMLElement which has the listener has a className of: <strong>' + e.getTarget().className + '</strong> <i>(e.getTarget())</i>';
  62589. *
  62590. * Ext.getCmp('logger').setHtml(string);
  62591. * }
  62592. * });
  62593. *
  62594. * ## Recognizers
  62595. *
  62596. * Sencha Touch includes a bunch of default event recognizers to know when a user taps, swipes, etc.
  62597. *
  62598. * For a full list of default recognizers, and more information, please view the {@link Ext.event.recognizer.Recognizer} documentation.
  62599. */
  62600. Ext.define('Ext.event.Event', {
  62601. alternateClassName: 'Ext.EventObject',
  62602. isStopped: false,
  62603. set: function(name, value) {
  62604. if (arguments.length === 1 && typeof name != 'string') {
  62605. var info = name;
  62606. for (name in info) {
  62607. if (info.hasOwnProperty(name)) {
  62608. this[name] = info[name];
  62609. }
  62610. }
  62611. }
  62612. else {
  62613. this[name] = info[name];
  62614. }
  62615. },
  62616. /**
  62617. * Stop the event (`preventDefault` and `{@link #stopPropagation}`).
  62618. * @chainable
  62619. */
  62620. stopEvent: function() {
  62621. return this.stopPropagation();
  62622. },
  62623. /**
  62624. * Cancels bubbling of the event.
  62625. * @chainable
  62626. */
  62627. stopPropagation: function() {
  62628. this.isStopped = true;
  62629. return this;
  62630. }
  62631. });
  62632. /**
  62633. * @private
  62634. * @extends Object
  62635. * DOM event. This class really extends {@link Ext.event.Event}, but for documentation
  62636. * purposes it's members are listed inside {@link Ext.event.Event}.
  62637. */
  62638. Ext.define('Ext.event.Dom', {
  62639. extend: 'Ext.event.Event',
  62640. constructor: function(event) {
  62641. var target = event.target,
  62642. touches;
  62643. if (target && target.nodeType !== 1) {
  62644. target = target.parentNode;
  62645. }
  62646. touches = event.changedTouches;
  62647. if (touches) {
  62648. touches = touches[0];
  62649. this.pageX = touches.pageX;
  62650. this.pageY = touches.pageY;
  62651. }
  62652. else {
  62653. this.pageX = event.pageX;
  62654. this.pageY = event.pageY;
  62655. }
  62656. this.browserEvent = this.event = event;
  62657. this.target = this.delegatedTarget = target;
  62658. this.type = event.type;
  62659. this.timeStamp = this.time = event.timeStamp;
  62660. if (typeof this.time != 'number') {
  62661. this.time = new Date(this.time).getTime();
  62662. }
  62663. return this;
  62664. },
  62665. /**
  62666. * @property {Number} distance
  62667. * The distance of the event.
  62668. *
  62669. * **This is only available when the event type is `swipe` and `pinch`.**
  62670. */
  62671. /**
  62672. * @property {HTMLElement} target
  62673. * The target HTMLElement for this event. For example; if you are listening to a tap event and you tap on a `<div>` element,
  62674. * this will return that `<div>` element.
  62675. */
  62676. /**
  62677. * @property {Number} pageX The browsers x coordinate of the event.
  62678. */
  62679. /**
  62680. * @property {Number} pageY The browsers y coordinate of the event.
  62681. */
  62682. stopEvent: function() {
  62683. this.preventDefault();
  62684. return this.callParent();
  62685. },
  62686. /**
  62687. * Prevents the browsers default handling of the event.
  62688. */
  62689. preventDefault: function() {
  62690. this.browserEvent.preventDefault();
  62691. },
  62692. /**
  62693. * Gets the x coordinate of the event.
  62694. * @deprecated 2.0 Please use {@link #pageX} property directly.
  62695. * @return {Number}
  62696. */
  62697. getPageX: function() {
  62698. return this.browserEvent.pageX;
  62699. },
  62700. /**
  62701. * Gets the y coordinate of the event.
  62702. * @deprecated 2.0 Please use {@link #pageX} property directly.
  62703. * @return {Number}
  62704. */
  62705. getPageY: function() {
  62706. return this.browserEvent.pageY;
  62707. },
  62708. /**
  62709. * Gets the X and Y coordinates of the event.
  62710. * @deprecated 2.0 Please use the {@link #pageX} and {@link #pageY} properties directly.
  62711. * @return {Array}
  62712. */
  62713. getXY: function() {
  62714. if (!this.xy) {
  62715. this.xy = [this.getPageX(), this.getPageY()];
  62716. }
  62717. return this.xy;
  62718. },
  62719. /**
  62720. * Gets the target for the event. Unlike {@link #target}, this returns the main element for your event. So if you are
  62721. * listening to a tap event on Ext.Viewport.element, and you tap on an inner element of Ext.Viewport.element, this will
  62722. * return Ext.Viewport.element.
  62723. *
  62724. * If you want the element you tapped on, then use {@link #target}.
  62725. *
  62726. * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
  62727. * @param {Number/Mixed} [maxDepth=10||document.body] (optional) The max depth to
  62728. * search as a number or element (defaults to 10 || document.body)
  62729. * @param {Boolean} returnEl (optional) `true` to return a Ext.Element object instead of DOM node.
  62730. * @return {HTMLElement}
  62731. */
  62732. getTarget: function(selector, maxDepth, returnEl) {
  62733. if (arguments.length === 0) {
  62734. return this.delegatedTarget;
  62735. }
  62736. return selector ? Ext.fly(this.target).findParent(selector, maxDepth, returnEl) : (returnEl ? Ext.get(this.target) : this.target);
  62737. },
  62738. /**
  62739. * Returns the time of the event.
  62740. * @return {Date}
  62741. */
  62742. getTime: function() {
  62743. return this.time;
  62744. },
  62745. setDelegatedTarget: function(target) {
  62746. this.delegatedTarget = target;
  62747. },
  62748. makeUnpreventable: function() {
  62749. this.browserEvent.preventDefault = Ext.emptyFn;
  62750. }
  62751. });
  62752. /**
  62753. * @private
  62754. * Touch event.
  62755. */
  62756. Ext.define('Ext.event.Touch', {
  62757. extend: 'Ext.event.Dom',
  62758. requires: [
  62759. 'Ext.util.Point'
  62760. ],
  62761. constructor: function(event, info) {
  62762. if (info) {
  62763. this.set(info);
  62764. }
  62765. this.touchesMap = {};
  62766. this.changedTouches = this.cloneTouches(event.changedTouches);
  62767. this.touches = this.cloneTouches(event.touches);
  62768. this.targetTouches = this.cloneTouches(event.targetTouches);
  62769. return this.callParent([event]);
  62770. },
  62771. clone: function() {
  62772. return new this.self(this);
  62773. },
  62774. setTargets: function(targetsMap) {
  62775. this.doSetTargets(this.changedTouches, targetsMap);
  62776. this.doSetTargets(this.touches, targetsMap);
  62777. this.doSetTargets(this.targetTouches, targetsMap);
  62778. },
  62779. doSetTargets: function(touches, targetsMap) {
  62780. var i, ln, touch, identifier, targets;
  62781. for (i = 0,ln = touches.length; i < ln; i++) {
  62782. touch = touches[i];
  62783. identifier = touch.identifier;
  62784. targets = targetsMap[identifier];
  62785. if (targets) {
  62786. touch.targets = targets;
  62787. }
  62788. }
  62789. },
  62790. cloneTouches: function(touches) {
  62791. var map = this.touchesMap,
  62792. clone = [],
  62793. lastIdentifier = null,
  62794. i, ln, touch, identifier;
  62795. for (i = 0,ln = touches.length; i < ln; i++) {
  62796. touch = touches[i];
  62797. identifier = touch.identifier;
  62798. // A quick fix for a bug found in Bada 1.0 where all touches have
  62799. // idenfitier of 0
  62800. if (lastIdentifier !== null && identifier === lastIdentifier) {
  62801. identifier++;
  62802. }
  62803. lastIdentifier = identifier;
  62804. if (!map[identifier]) {
  62805. map[identifier] = {
  62806. pageX: touch.pageX,
  62807. pageY: touch.pageY,
  62808. identifier: identifier,
  62809. target: touch.target,
  62810. timeStamp: touch.timeStamp,
  62811. point: Ext.util.Point.fromTouch(touch),
  62812. targets: touch.targets
  62813. };
  62814. }
  62815. clone[i] = map[identifier];
  62816. }
  62817. return clone;
  62818. }
  62819. });
  62820. /**
  62821. * @private
  62822. */
  62823. Ext.define('Ext.event.publisher.ComponentDelegation', {
  62824. extend: 'Ext.event.publisher.Publisher',
  62825. requires: [
  62826. 'Ext.Component',
  62827. 'Ext.ComponentQuery'
  62828. ],
  62829. targetType: 'component',
  62830. optimizedSelectorRegex: /^#([\w\-]+)((?:[\s]*)>(?:[\s]*)|(?:\s*))([\w\-]+)$/i,
  62831. handledEvents: ['*'],
  62832. getSubscribers: function(eventName, createIfNotExist) {
  62833. var subscribers = this.subscribers,
  62834. eventSubscribers = subscribers[eventName];
  62835. if (!eventSubscribers && createIfNotExist) {
  62836. eventSubscribers = subscribers[eventName] = {
  62837. type: {
  62838. $length: 0
  62839. },
  62840. selector: [],
  62841. $length: 0
  62842. }
  62843. }
  62844. return eventSubscribers;
  62845. },
  62846. subscribe: function(target, eventName) {
  62847. // Ignore id-only selectors since they are already handled
  62848. if (this.idSelectorRegex.test(target)) {
  62849. return false;
  62850. }
  62851. var optimizedSelector = target.match(this.optimizedSelectorRegex),
  62852. subscribers = this.getSubscribers(eventName, true),
  62853. typeSubscribers = subscribers.type,
  62854. selectorSubscribers = subscribers.selector,
  62855. id, isDescendant, type, map, subMap;
  62856. if (optimizedSelector !== null) {
  62857. id = optimizedSelector[1];
  62858. isDescendant = optimizedSelector[2].indexOf('>') === -1;
  62859. type = optimizedSelector[3];
  62860. map = typeSubscribers[type];
  62861. if (!map) {
  62862. typeSubscribers[type] = map = {
  62863. descendents: {
  62864. $length: 0
  62865. },
  62866. children: {
  62867. $length: 0
  62868. },
  62869. $length: 0
  62870. }
  62871. }
  62872. subMap = isDescendant ? map.descendents : map.children;
  62873. if (subMap.hasOwnProperty(id)) {
  62874. subMap[id]++;
  62875. return true;
  62876. }
  62877. subMap[id] = 1;
  62878. subMap.$length++;
  62879. map.$length++;
  62880. typeSubscribers.$length++;
  62881. }
  62882. else {
  62883. if (selectorSubscribers.hasOwnProperty(target)) {
  62884. selectorSubscribers[target]++;
  62885. return true;
  62886. }
  62887. selectorSubscribers[target] = 1;
  62888. selectorSubscribers.push(target);
  62889. }
  62890. subscribers.$length++;
  62891. return true;
  62892. },
  62893. unsubscribe: function(target, eventName, all) {
  62894. var subscribers = this.getSubscribers(eventName);
  62895. if (!subscribers) {
  62896. return false;
  62897. }
  62898. var match = target.match(this.optimizedSelectorRegex),
  62899. typeSubscribers = subscribers.type,
  62900. selectorSubscribers = subscribers.selector,
  62901. id, isDescendant, type, map, subMap;
  62902. all = Boolean(all);
  62903. if (match !== null) {
  62904. id = match[1];
  62905. isDescendant = match[2].indexOf('>') === -1;
  62906. type = match[3];
  62907. map = typeSubscribers[type];
  62908. if (!map) {
  62909. return true;
  62910. }
  62911. subMap = isDescendant ? map.descendents : map.children;
  62912. if (!subMap.hasOwnProperty(id) || (!all && --subMap[id] > 0)) {
  62913. return true;
  62914. }
  62915. delete subMap[id];
  62916. subMap.$length--;
  62917. map.$length--;
  62918. typeSubscribers.$length--;
  62919. }
  62920. else {
  62921. if (!selectorSubscribers.hasOwnProperty(target) || (!all && --selectorSubscribers[target] > 0)) {
  62922. return true;
  62923. }
  62924. delete selectorSubscribers[target];
  62925. Ext.Array.remove(selectorSubscribers, target);
  62926. }
  62927. if (--subscribers.$length === 0) {
  62928. delete this.subscribers[eventName];
  62929. }
  62930. return true;
  62931. },
  62932. notify: function(target, eventName) {
  62933. var subscribers = this.getSubscribers(eventName),
  62934. id, component;
  62935. if (!subscribers || subscribers.$length === 0) {
  62936. return false;
  62937. }
  62938. id = target.substr(1);
  62939. component = Ext.ComponentManager.get(id);
  62940. if (component) {
  62941. this.dispatcher.doAddListener(this.targetType, target, eventName, 'publish', this, {
  62942. args: [eventName, component]
  62943. }, 'before');
  62944. }
  62945. },
  62946. matchesSelector: function(component, selector) {
  62947. return Ext.ComponentQuery.is(component, selector);
  62948. },
  62949. dispatch: function(target, eventName, args, connectedController) {
  62950. this.dispatcher.doDispatchEvent(this.targetType, target, eventName, args, null, connectedController);
  62951. },
  62952. publish: function(eventName, component) {
  62953. var subscribers = this.getSubscribers(eventName);
  62954. if (!subscribers) {
  62955. return;
  62956. }
  62957. var eventController = arguments[arguments.length - 1],
  62958. typeSubscribers = subscribers.type,
  62959. selectorSubscribers = subscribers.selector,
  62960. args = Array.prototype.slice.call(arguments, 2, -2),
  62961. types = component.xtypesChain,
  62962. descendentsSubscribers, childrenSubscribers,
  62963. parentId, ancestorIds, ancestorId, parentComponent,
  62964. selector,
  62965. i, ln, type, j, subLn;
  62966. for (i = 0, ln = types.length; i < ln; i++) {
  62967. type = types[i];
  62968. subscribers = typeSubscribers[type];
  62969. if (subscribers && subscribers.$length > 0) {
  62970. descendentsSubscribers = subscribers.descendents;
  62971. if (descendentsSubscribers.$length > 0) {
  62972. if (!ancestorIds) {
  62973. ancestorIds = component.getAncestorIds();
  62974. }
  62975. for (j = 0, subLn = ancestorIds.length; j < subLn; j++) {
  62976. ancestorId = ancestorIds[j];
  62977. if (descendentsSubscribers.hasOwnProperty(ancestorId)) {
  62978. this.dispatch('#' + ancestorId + ' ' + type, eventName, args, eventController);
  62979. }
  62980. }
  62981. }
  62982. childrenSubscribers = subscribers.children;
  62983. if (childrenSubscribers.$length > 0) {
  62984. if (!parentId) {
  62985. if (ancestorIds) {
  62986. parentId = ancestorIds[0];
  62987. }
  62988. else {
  62989. parentComponent = component.getParent();
  62990. if (parentComponent) {
  62991. parentId = parentComponent.getId();
  62992. }
  62993. }
  62994. }
  62995. if (parentId) {
  62996. if (childrenSubscribers.hasOwnProperty(parentId)) {
  62997. this.dispatch('#' + parentId + ' > ' + type, eventName, args, eventController);
  62998. }
  62999. }
  63000. }
  63001. }
  63002. }
  63003. ln = selectorSubscribers.length;
  63004. if (ln > 0) {
  63005. for (i = 0; i < ln; i++) {
  63006. selector = selectorSubscribers[i];
  63007. if (this.matchesSelector(component, selector)) {
  63008. this.dispatch(selector, eventName, args, eventController);
  63009. }
  63010. }
  63011. }
  63012. }
  63013. });
  63014. /**
  63015. * @private
  63016. */
  63017. Ext.define('Ext.event.publisher.ComponentPaint', {
  63018. extend: 'Ext.event.publisher.Publisher',
  63019. targetType: 'component',
  63020. handledEvents: ['erased'],
  63021. eventNames: {
  63022. painted: 'painted',
  63023. erased: 'erased'
  63024. },
  63025. constructor: function() {
  63026. this.callParent(arguments);
  63027. this.hiddenQueue = {};
  63028. this.renderedQueue = {};
  63029. },
  63030. getSubscribers: function(eventName, createIfNotExist) {
  63031. var subscribers = this.subscribers;
  63032. if (!subscribers.hasOwnProperty(eventName)) {
  63033. if (!createIfNotExist) {
  63034. return null;
  63035. }
  63036. subscribers[eventName] = {
  63037. $length: 0
  63038. };
  63039. }
  63040. return subscribers[eventName];
  63041. },
  63042. setDispatcher: function(dispatcher) {
  63043. var targetType = this.targetType;
  63044. dispatcher.doAddListener(targetType, '*', 'renderedchange', 'onBeforeComponentRenderedChange', this, null, 'before');
  63045. dispatcher.doAddListener(targetType, '*', 'hiddenchange', 'onBeforeComponentHiddenChange', this, null, 'before');
  63046. dispatcher.doAddListener(targetType, '*', 'renderedchange', 'onComponentRenderedChange', this, null, 'after');
  63047. dispatcher.doAddListener(targetType, '*', 'hiddenchange', 'onComponentHiddenChange', this, null, 'after');
  63048. return this.callParent(arguments);
  63049. },
  63050. subscribe: function(target, eventName) {
  63051. var match = target.match(this.idSelectorRegex),
  63052. subscribers,
  63053. id;
  63054. if (!match) {
  63055. return false;
  63056. }
  63057. id = match[1];
  63058. subscribers = this.getSubscribers(eventName, true);
  63059. if (subscribers.hasOwnProperty(id)) {
  63060. subscribers[id]++;
  63061. return true;
  63062. }
  63063. subscribers[id] = 1;
  63064. subscribers.$length++;
  63065. return true;
  63066. },
  63067. unsubscribe: function(target, eventName, all) {
  63068. var match = target.match(this.idSelectorRegex),
  63069. subscribers,
  63070. id;
  63071. if (!match || !(subscribers = this.getSubscribers(eventName))) {
  63072. return false;
  63073. }
  63074. id = match[1];
  63075. if (!subscribers.hasOwnProperty(id) || (!all && --subscribers[id] > 0)) {
  63076. return true;
  63077. }
  63078. delete subscribers[id];
  63079. if (--subscribers.$length === 0) {
  63080. delete this.subscribers[eventName];
  63081. }
  63082. return true;
  63083. },
  63084. onBeforeComponentRenderedChange: function(container, component, rendered) {
  63085. var eventNames = this.eventNames,
  63086. eventName = rendered ? eventNames.painted : eventNames.erased,
  63087. subscribers = this.getSubscribers(eventName),
  63088. queue;
  63089. if (subscribers && subscribers.$length > 0) {
  63090. this.renderedQueue[component.getId()] = queue = [];
  63091. this.publish(subscribers, component, eventName, queue);
  63092. }
  63093. },
  63094. onBeforeComponentHiddenChange: function(component, hidden) {
  63095. var eventNames = this.eventNames,
  63096. eventName = hidden ? eventNames.erased : eventNames.painted,
  63097. subscribers = this.getSubscribers(eventName),
  63098. queue;
  63099. if (subscribers && subscribers.$length > 0) {
  63100. this.hiddenQueue[component.getId()] = queue = [];
  63101. this.publish(subscribers, component, eventName, queue);
  63102. }
  63103. },
  63104. onComponentRenderedChange: function(container, component) {
  63105. var renderedQueue = this.renderedQueue,
  63106. id = component.getId(),
  63107. queue;
  63108. if (!renderedQueue.hasOwnProperty(id)) {
  63109. return;
  63110. }
  63111. queue = renderedQueue[id];
  63112. delete renderedQueue[id];
  63113. if (queue.length > 0) {
  63114. this.dispatchQueue(queue);
  63115. }
  63116. },
  63117. onComponentHiddenChange: function(component) {
  63118. var hiddenQueue = this.hiddenQueue,
  63119. id = component.getId(),
  63120. queue;
  63121. if (!hiddenQueue.hasOwnProperty(id)) {
  63122. return;
  63123. }
  63124. queue = hiddenQueue[id];
  63125. delete hiddenQueue[id];
  63126. if (queue.length > 0) {
  63127. this.dispatchQueue(queue);
  63128. }
  63129. },
  63130. dispatchQueue: function(dispatchingQueue) {
  63131. var dispatcher = this.dispatcher,
  63132. targetType = this.targetType,
  63133. eventNames = this.eventNames,
  63134. queue = dispatchingQueue.slice(),
  63135. ln = queue.length,
  63136. i, item, component, eventName, isPainted;
  63137. dispatchingQueue.length = 0;
  63138. if (ln > 0) {
  63139. for (i = 0; i < ln; i++) {
  63140. item = queue[i];
  63141. component = item.component;
  63142. eventName = item.eventName;
  63143. isPainted = component.isPainted();
  63144. if ((eventName === eventNames.painted && isPainted) || eventName === eventNames.erased && !isPainted) {
  63145. dispatcher.doDispatchEvent(targetType, '#' + item.id, eventName, [component]);
  63146. }
  63147. }
  63148. queue.length = 0;
  63149. }
  63150. },
  63151. publish: function(subscribers, component, eventName, dispatchingQueue) {
  63152. var id = component.getId(),
  63153. needsDispatching = false,
  63154. eventNames, items, i, ln, isPainted;
  63155. if (subscribers[id]) {
  63156. eventNames = this.eventNames;
  63157. isPainted = component.isPainted();
  63158. if ((eventName === eventNames.painted && !isPainted) || eventName === eventNames.erased && isPainted) {
  63159. needsDispatching = true;
  63160. }
  63161. else {
  63162. return this;
  63163. }
  63164. }
  63165. if (component.isContainer) {
  63166. items = component.getItems().items;
  63167. for (i = 0,ln = items.length; i < ln; i++) {
  63168. this.publish(subscribers, items[i], eventName, dispatchingQueue);
  63169. }
  63170. }
  63171. else if (component.isDecorator) {
  63172. this.publish(subscribers, component.getComponent(), eventName, dispatchingQueue);
  63173. }
  63174. if (needsDispatching) {
  63175. dispatchingQueue.push({
  63176. id: id,
  63177. eventName: eventName,
  63178. component: component
  63179. });
  63180. }
  63181. }
  63182. });
  63183. /**
  63184. * @private
  63185. */
  63186. Ext.define('Ext.event.publisher.ComponentSize', {
  63187. extend: 'Ext.event.publisher.Publisher',
  63188. requires: [
  63189. 'Ext.ComponentManager'
  63190. ],
  63191. targetType: 'component',
  63192. handledEvents: ['resize', 'innerresize'],
  63193. constructor: function() {
  63194. this.callParent(arguments);
  63195. this.sizeMonitors = {};
  63196. },
  63197. getSubscribers: function(target, createIfNotExist) {
  63198. var subscribers = this.subscribers;
  63199. if (!subscribers.hasOwnProperty(target)) {
  63200. if (!createIfNotExist) {
  63201. return null;
  63202. }
  63203. subscribers[target] = {
  63204. $length: 0
  63205. };
  63206. }
  63207. return subscribers[target];
  63208. },
  63209. subscribe: function(target, eventName) {
  63210. var match = target.match(this.idSelectorRegex),
  63211. sizeMonitors = this.sizeMonitors,
  63212. dispatcher = this.dispatcher,
  63213. targetType = this.targetType,
  63214. subscribers, component, id;
  63215. if (!match) {
  63216. return false;
  63217. }
  63218. id = match[1];
  63219. subscribers = this.getSubscribers(target, true);
  63220. subscribers.$length++;
  63221. if (subscribers.hasOwnProperty(eventName)) {
  63222. subscribers[eventName]++;
  63223. return true;
  63224. }
  63225. if (subscribers.$length === 1) {
  63226. dispatcher.addListener(targetType, target, 'painted', 'onComponentPainted', this, null, 'before');
  63227. }
  63228. component = Ext.ComponentManager.get(id);
  63229. //<debug error>
  63230. if (!component) {
  63231. Ext.Logger.error("Adding a listener to the 'resize' event of a non-existing component");
  63232. }
  63233. //</debug>
  63234. if (!sizeMonitors[target]) {
  63235. sizeMonitors[target] = {};
  63236. }
  63237. sizeMonitors[target][eventName] = new Ext.util.SizeMonitor({
  63238. element: eventName === 'resize' ? component.element : component.innerElement,
  63239. callback: this.onComponentSizeChange,
  63240. scope: this,
  63241. args: [this, target, eventName]
  63242. });
  63243. subscribers[eventName] = 1;
  63244. return true;
  63245. },
  63246. unsubscribe: function(target, eventName, all) {
  63247. var match = target.match(this.idSelectorRegex),
  63248. dispatcher = this.dispatcher,
  63249. targetType = this.targetType,
  63250. sizeMonitors = this.sizeMonitors,
  63251. subscribers,
  63252. id;
  63253. if (!match || !(subscribers = this.getSubscribers(target))) {
  63254. return false;
  63255. }
  63256. id = match[1];
  63257. if (!subscribers.hasOwnProperty(eventName) || (!all && --subscribers[eventName] > 0)) {
  63258. return true;
  63259. }
  63260. delete subscribers[eventName];
  63261. sizeMonitors[target][eventName].destroy();
  63262. delete sizeMonitors[target][eventName];
  63263. if (--subscribers.$length === 0) {
  63264. delete sizeMonitors[target];
  63265. delete this.subscribers[target];
  63266. dispatcher.removeListener(targetType, target, 'painted', 'onComponentPainted', this, 'before');
  63267. }
  63268. return true;
  63269. },
  63270. onComponentPainted: function(component) {
  63271. var target = component.getObservableId(),
  63272. sizeMonitors = this.sizeMonitors[target];
  63273. if (sizeMonitors.resize) {
  63274. sizeMonitors.resize.refresh();
  63275. }
  63276. if (sizeMonitors.innerresize) {
  63277. sizeMonitors.innerresize.refresh();
  63278. }
  63279. },
  63280. onComponentSizeChange: function(component, observableId, eventName) {
  63281. this.dispatcher.doDispatchEvent(this.targetType, observableId, eventName, [component]);
  63282. }
  63283. });
  63284. /**
  63285. * @private
  63286. */
  63287. Ext.define('Ext.event.publisher.Dom', {
  63288. extend: 'Ext.event.publisher.Publisher',
  63289. requires: [
  63290. 'Ext.env.Browser',
  63291. 'Ext.Element',
  63292. 'Ext.event.Dom'
  63293. ],
  63294. targetType: 'element',
  63295. idOrClassSelectorRegex: /^([#|\.])([\w\-]+)$/,
  63296. handledEvents: ['click', 'focus', 'blur', 'paste', 'input',
  63297. 'mousemove', 'mousedown', 'mouseup', 'mouseover', 'mouseout',
  63298. 'keyup', 'keydown', 'keypress', 'submit',
  63299. 'transitionend', 'animationstart', 'animationend'],
  63300. classNameSplitRegex: /\s+/,
  63301. SELECTOR_ALL: '*',
  63302. constructor: function() {
  63303. var eventNames = this.getHandledEvents(),
  63304. eventNameMap = {},
  63305. i, ln, eventName, vendorEventName;
  63306. this.doBubbleEventsMap = {
  63307. 'click': true,
  63308. 'submit': true,
  63309. 'mousedown': true,
  63310. 'mousemove': true,
  63311. 'mouseup': true,
  63312. 'mouseover': true,
  63313. 'mouseout': true,
  63314. 'transitionend': true
  63315. };
  63316. this.onEvent = Ext.Function.bind(this.onEvent, this);
  63317. for (i = 0,ln = eventNames.length; i < ln; i++) {
  63318. eventName = eventNames[i];
  63319. vendorEventName = this.getVendorEventName(eventName);
  63320. eventNameMap[vendorEventName] = eventName;
  63321. this.attachListener(vendorEventName);
  63322. }
  63323. this.eventNameMap = eventNameMap;
  63324. return this.callParent();
  63325. },
  63326. getSubscribers: function(eventName) {
  63327. var subscribers = this.subscribers,
  63328. eventSubscribers = subscribers[eventName];
  63329. if (!eventSubscribers) {
  63330. eventSubscribers = subscribers[eventName] = {
  63331. id: {
  63332. $length: 0
  63333. },
  63334. className: {
  63335. $length: 0
  63336. },
  63337. selector: [],
  63338. all: 0,
  63339. $length: 0
  63340. }
  63341. }
  63342. return eventSubscribers;
  63343. },
  63344. getVendorEventName: function(eventName) {
  63345. if (eventName === 'transitionend') {
  63346. eventName = Ext.browser.getVendorProperyName('transitionEnd');
  63347. }
  63348. else if (eventName === 'animationstart') {
  63349. eventName = Ext.browser.getVendorProperyName('animationStart');
  63350. }
  63351. else if (eventName === 'animationend') {
  63352. eventName = Ext.browser.getVendorProperyName('animationEnd');
  63353. }
  63354. return eventName;
  63355. },
  63356. attachListener: function(eventName) {
  63357. document.addEventListener(eventName, this.onEvent, !this.doesEventBubble(eventName));
  63358. return this;
  63359. },
  63360. removeListener: function(eventName) {
  63361. document.removeEventListener(eventName, this.onEvent, !this.doesEventBubble(eventName));
  63362. return this;
  63363. },
  63364. doesEventBubble: function(eventName) {
  63365. return !!this.doBubbleEventsMap[eventName];
  63366. },
  63367. subscribe: function(target, eventName) {
  63368. if (!this.handles(eventName)) {
  63369. return false;
  63370. }
  63371. var idOrClassSelectorMatch = target.match(this.idOrClassSelectorRegex),
  63372. subscribers = this.getSubscribers(eventName),
  63373. idSubscribers = subscribers.id,
  63374. classNameSubscribers = subscribers.className,
  63375. selectorSubscribers = subscribers.selector,
  63376. type, value;
  63377. if (idOrClassSelectorMatch !== null) {
  63378. type = idOrClassSelectorMatch[1];
  63379. value = idOrClassSelectorMatch[2];
  63380. if (type === '#') {
  63381. if (idSubscribers.hasOwnProperty(value)) {
  63382. idSubscribers[value]++;
  63383. return true;
  63384. }
  63385. idSubscribers[value] = 1;
  63386. idSubscribers.$length++;
  63387. }
  63388. else {
  63389. if (classNameSubscribers.hasOwnProperty(value)) {
  63390. classNameSubscribers[value]++;
  63391. return true;
  63392. }
  63393. classNameSubscribers[value] = 1;
  63394. classNameSubscribers.$length++;
  63395. }
  63396. }
  63397. else {
  63398. if (target === this.SELECTOR_ALL) {
  63399. subscribers.all++;
  63400. }
  63401. else {
  63402. if (selectorSubscribers.hasOwnProperty(target)) {
  63403. selectorSubscribers[target]++;
  63404. return true;
  63405. }
  63406. selectorSubscribers[target] = 1;
  63407. selectorSubscribers.push(target);
  63408. }
  63409. }
  63410. subscribers.$length++;
  63411. return true;
  63412. },
  63413. unsubscribe: function(target, eventName, all) {
  63414. if (!this.handles(eventName)) {
  63415. return false;
  63416. }
  63417. var idOrClassSelectorMatch = target.match(this.idOrClassSelectorRegex),
  63418. subscribers = this.getSubscribers(eventName),
  63419. idSubscribers = subscribers.id,
  63420. classNameSubscribers = subscribers.className,
  63421. selectorSubscribers = subscribers.selector,
  63422. type, value;
  63423. all = Boolean(all);
  63424. if (idOrClassSelectorMatch !== null) {
  63425. type = idOrClassSelectorMatch[1];
  63426. value = idOrClassSelectorMatch[2];
  63427. if (type === '#') {
  63428. if (!idSubscribers.hasOwnProperty(value) || (!all && --idSubscribers[value] > 0)) {
  63429. return true;
  63430. }
  63431. delete idSubscribers[value];
  63432. idSubscribers.$length--;
  63433. }
  63434. else {
  63435. if (!classNameSubscribers.hasOwnProperty(value) || (!all && --classNameSubscribers[value] > 0)) {
  63436. return true;
  63437. }
  63438. delete classNameSubscribers[value];
  63439. classNameSubscribers.$length--;
  63440. }
  63441. }
  63442. else {
  63443. if (target === this.SELECTOR_ALL) {
  63444. if (all) {
  63445. subscribers.all = 0;
  63446. }
  63447. else {
  63448. subscribers.all--;
  63449. }
  63450. }
  63451. else {
  63452. if (!selectorSubscribers.hasOwnProperty(target) || (!all && --selectorSubscribers[target] > 0)) {
  63453. return true;
  63454. }
  63455. delete selectorSubscribers[target];
  63456. Ext.Array.remove(selectorSubscribers, target);
  63457. }
  63458. }
  63459. subscribers.$length--;
  63460. return true;
  63461. },
  63462. getElementTarget: function(target) {
  63463. if (target.nodeType !== 1) {
  63464. target = target.parentNode;
  63465. if (!target || target.nodeType !== 1) {
  63466. return null;
  63467. }
  63468. }
  63469. return target;
  63470. },
  63471. getBubblingTargets: function(target) {
  63472. var targets = [];
  63473. if (!target) {
  63474. return targets;
  63475. }
  63476. do {
  63477. targets[targets.length] = target;
  63478. target = target.parentNode;
  63479. } while (target && target.nodeType === 1);
  63480. return targets;
  63481. },
  63482. dispatch: function(target, eventName, args) {
  63483. args.push(args[0].target);
  63484. this.callParent(arguments);
  63485. },
  63486. publish: function(eventName, targets, event) {
  63487. var subscribers = this.getSubscribers(eventName),
  63488. wildcardSubscribers;
  63489. if (subscribers.$length === 0 || !this.doPublish(subscribers, eventName, targets, event)) {
  63490. wildcardSubscribers = this.getSubscribers('*');
  63491. if (wildcardSubscribers.$length > 0) {
  63492. this.doPublish(wildcardSubscribers, eventName, targets, event);
  63493. }
  63494. }
  63495. return this;
  63496. },
  63497. doPublish: function(subscribers, eventName, targets, event) {
  63498. var idSubscribers = subscribers.id,
  63499. classNameSubscribers = subscribers.className,
  63500. selectorSubscribers = subscribers.selector,
  63501. hasIdSubscribers = idSubscribers.$length > 0,
  63502. hasClassNameSubscribers = classNameSubscribers.$length > 0,
  63503. hasSelectorSubscribers = selectorSubscribers.length > 0,
  63504. hasAllSubscribers = subscribers.all > 0,
  63505. isClassNameHandled = {},
  63506. args = [event],
  63507. hasDispatched = false,
  63508. classNameSplitRegex = this.classNameSplitRegex,
  63509. i, ln, j, subLn, target, id, className, classNames, selector;
  63510. for (i = 0,ln = targets.length; i < ln; i++) {
  63511. target = targets[i];
  63512. event.setDelegatedTarget(target);
  63513. if (hasIdSubscribers) {
  63514. id = target.id;
  63515. if (id) {
  63516. if (idSubscribers.hasOwnProperty(id)) {
  63517. hasDispatched = true;
  63518. this.dispatch('#' + id, eventName, args);
  63519. }
  63520. }
  63521. }
  63522. if (hasClassNameSubscribers) {
  63523. className = target.className;
  63524. if (className) {
  63525. classNames = className.split(classNameSplitRegex);
  63526. for (j = 0,subLn = classNames.length; j < subLn; j++) {
  63527. className = classNames[j];
  63528. if (!isClassNameHandled[className]) {
  63529. isClassNameHandled[className] = true;
  63530. if (classNameSubscribers.hasOwnProperty(className)) {
  63531. hasDispatched = true;
  63532. this.dispatch('.' + className, eventName, args);
  63533. }
  63534. }
  63535. }
  63536. }
  63537. }
  63538. // Stop propagation
  63539. if (event.isStopped) {
  63540. return hasDispatched;
  63541. }
  63542. }
  63543. if (hasAllSubscribers && !hasDispatched) {
  63544. event.setDelegatedTarget(event.browserEvent.target);
  63545. hasDispatched = true;
  63546. this.dispatch(this.SELECTOR_ALL, eventName, args);
  63547. if (event.isStopped) {
  63548. return hasDispatched;
  63549. }
  63550. }
  63551. if (hasSelectorSubscribers) {
  63552. for (j = 0,subLn = targets.length; j < subLn; j++) {
  63553. target = targets[j];
  63554. for (i = 0,ln = selectorSubscribers.length; i < ln; i++) {
  63555. selector = selectorSubscribers[i];
  63556. if (this.matchesSelector(target, selector)) {
  63557. event.setDelegatedTarget(target);
  63558. hasDispatched = true;
  63559. this.dispatch(selector, eventName, args);
  63560. }
  63561. if (event.isStopped) {
  63562. return hasDispatched;
  63563. }
  63564. }
  63565. }
  63566. }
  63567. return hasDispatched;
  63568. },
  63569. matchesSelector: function(element, selector) {
  63570. if ('webkitMatchesSelector' in element) {
  63571. return element.webkitMatchesSelector(selector);
  63572. }
  63573. return Ext.DomQuery.is(element, selector);
  63574. },
  63575. onEvent: function(e) {
  63576. var eventName = this.eventNameMap[e.type];
  63577. // Set the current frame start time to be the timestamp of the event.
  63578. Ext.frameStartTime = e.timeStamp;
  63579. if (!eventName || this.getSubscribersCount(eventName) === 0) {
  63580. return;
  63581. }
  63582. var target = this.getElementTarget(e.target),
  63583. targets;
  63584. if (!target) {
  63585. return;
  63586. }
  63587. if (this.doesEventBubble(eventName)) {
  63588. targets = this.getBubblingTargets(target);
  63589. }
  63590. else {
  63591. targets = [target];
  63592. }
  63593. this.publish(eventName, targets, new Ext.event.Dom(e));
  63594. },
  63595. //<debug>
  63596. hasSubscriber: function(target, eventName) {
  63597. if (!this.handles(eventName)) {
  63598. return false;
  63599. }
  63600. var match = target.match(this.idOrClassSelectorRegex),
  63601. subscribers = this.getSubscribers(eventName),
  63602. type, value;
  63603. if (match !== null) {
  63604. type = match[1];
  63605. value = match[2];
  63606. if (type === '#') {
  63607. return subscribers.id.hasOwnProperty(value);
  63608. }
  63609. else {
  63610. return subscribers.className.hasOwnProperty(value);
  63611. }
  63612. }
  63613. else {
  63614. return (subscribers.selector.hasOwnProperty(target) && Ext.Array.indexOf(subscribers.selector, target) !== -1);
  63615. }
  63616. return false;
  63617. },
  63618. //</debug>
  63619. getSubscribersCount: function(eventName) {
  63620. if (!this.handles(eventName)) {
  63621. return 0;
  63622. }
  63623. return this.getSubscribers(eventName).$length + this.getSubscribers('*').$length;
  63624. }
  63625. });
  63626. /**
  63627. * @private
  63628. */
  63629. Ext.define('Ext.util.paintmonitor.Abstract', {
  63630. config: {
  63631. element: null,
  63632. callback: Ext.emptyFn,
  63633. scope: null,
  63634. args: []
  63635. },
  63636. eventName: '',
  63637. monitorClass: '',
  63638. constructor: function(config) {
  63639. this.onElementPainted = Ext.Function.bind(this.onElementPainted, this);
  63640. this.initConfig(config);
  63641. },
  63642. bindListeners: function(bind) {
  63643. this.monitorElement[bind ? 'addEventListener' : 'removeEventListener'](this.eventName, this.onElementPainted, true);
  63644. },
  63645. applyElement: function(element) {
  63646. if (element) {
  63647. return Ext.get(element);
  63648. }
  63649. },
  63650. updateElement: function(element) {
  63651. this.monitorElement = Ext.Element.create({
  63652. classList: ['x-paint-monitor', this.monitorClass]
  63653. }, true);
  63654. element.appendChild(this.monitorElement);
  63655. element.addCls('x-paint-monitored');
  63656. this.bindListeners(true);
  63657. },
  63658. onElementPainted: function() {},
  63659. destroy: function() {
  63660. var monitorElement = this.monitorElement,
  63661. parentNode = monitorElement.parentNode,
  63662. element = this.getElement();
  63663. this.bindListeners(false);
  63664. delete this.monitorElement;
  63665. if (element && !element.isDestroyed) {
  63666. element.removeCls('x-paint-monitored');
  63667. delete this._element;
  63668. }
  63669. if (parentNode) {
  63670. parentNode.removeChild(monitorElement);
  63671. }
  63672. this.callSuper();
  63673. }
  63674. });
  63675. /**
  63676. * @private
  63677. */
  63678. Ext.define('Ext.util.paintmonitor.CssAnimation', {
  63679. extend: 'Ext.util.paintmonitor.Abstract',
  63680. eventName: 'webkitAnimationEnd',
  63681. monitorClass: 'cssanimation',
  63682. onElementPainted: function(e) {
  63683. if (e.animationName === 'x-paint-monitor-helper') {
  63684. this.getCallback().apply(this.getScope(), this.getArgs());
  63685. }
  63686. }
  63687. });
  63688. /**
  63689. * @private
  63690. */
  63691. Ext.define('Ext.util.paintmonitor.OverflowChange', {
  63692. extend: 'Ext.util.paintmonitor.Abstract',
  63693. eventName: 'overflowchanged',
  63694. monitorClass: 'overflowchange',
  63695. onElementPainted: function(e) {
  63696. this.getCallback().apply(this.getScope(), this.getArgs());
  63697. }
  63698. });
  63699. /**
  63700. *
  63701. */
  63702. Ext.define('Ext.util.PaintMonitor', {
  63703. requires: [
  63704. 'Ext.util.paintmonitor.CssAnimation',
  63705. 'Ext.util.paintmonitor.OverflowChange'
  63706. ],
  63707. constructor: function(config) {
  63708. if (Ext.browser.engineVersion.gtEq('536')) {
  63709. return new Ext.util.paintmonitor.OverflowChange(config);
  63710. }
  63711. else {
  63712. return new Ext.util.paintmonitor.CssAnimation(config);
  63713. }
  63714. }
  63715. });
  63716. /**
  63717. * @private
  63718. */
  63719. Ext.define('Ext.event.publisher.ElementPaint', {
  63720. extend: 'Ext.event.publisher.Publisher',
  63721. requires: [
  63722. 'Ext.util.PaintMonitor',
  63723. 'Ext.TaskQueue'
  63724. ],
  63725. targetType: 'element',
  63726. handledEvents: ['painted'],
  63727. constructor: function() {
  63728. this.monitors = {};
  63729. this.callSuper(arguments);
  63730. },
  63731. subscribe: function(target) {
  63732. var match = target.match(this.idSelectorRegex),
  63733. subscribers = this.subscribers,
  63734. id, element;
  63735. if (!match) {
  63736. return false;
  63737. }
  63738. id = match[1];
  63739. if (subscribers.hasOwnProperty(id)) {
  63740. subscribers[id]++;
  63741. return true;
  63742. }
  63743. subscribers[id] = 1;
  63744. element = Ext.get(id);
  63745. this.monitors[id] = new Ext.util.PaintMonitor({
  63746. element: element,
  63747. callback: this.onElementPainted,
  63748. scope: this,
  63749. args: [target, element]
  63750. });
  63751. return true;
  63752. },
  63753. unsubscribe: function(target, eventName, all) {
  63754. var match = target.match(this.idSelectorRegex),
  63755. subscribers = this.subscribers,
  63756. id;
  63757. if (!match) {
  63758. return false;
  63759. }
  63760. id = match[1];
  63761. if (!subscribers.hasOwnProperty(id) || (!all && --subscribers[id] > 0)) {
  63762. return true;
  63763. }
  63764. delete subscribers[id];
  63765. this.monitors[id].destroy();
  63766. delete this.monitors[id];
  63767. return true;
  63768. },
  63769. onElementPainted: function(target, element) {
  63770. Ext.TaskQueue.requestRead('dispatch', this, [target, 'painted', [element]]);
  63771. }
  63772. });
  63773. /**
  63774. *
  63775. */
  63776. Ext.define('Ext.mixin.Templatable', {
  63777. extend: 'Ext.mixin.Mixin',
  63778. mixinConfig: {
  63779. id: 'templatable'
  63780. },
  63781. referenceAttributeName: 'reference',
  63782. referenceSelector: '[reference]',
  63783. getElementConfig: function() {
  63784. return {
  63785. reference: 'element'
  63786. };
  63787. },
  63788. getElementTemplate: function() {
  63789. var elementTemplate = document.createDocumentFragment();
  63790. elementTemplate.appendChild(Ext.Element.create(this.getElementConfig(), true));
  63791. return elementTemplate;
  63792. },
  63793. initElement: function() {
  63794. var prototype = this.self.prototype;
  63795. prototype.elementTemplate = this.getElementTemplate();
  63796. prototype.initElement = prototype.doInitElement;
  63797. this.initElement.apply(this, arguments);
  63798. },
  63799. linkElement: function(reference, node) {
  63800. this.link(reference, node);
  63801. },
  63802. doInitElement: function() {
  63803. var referenceAttributeName = this.referenceAttributeName,
  63804. renderElement, referenceNodes, i, ln, referenceNode, reference;
  63805. renderElement = this.elementTemplate.cloneNode(true);
  63806. referenceNodes = renderElement.querySelectorAll(this.referenceSelector);
  63807. for (i = 0,ln = referenceNodes.length; i < ln; i++) {
  63808. referenceNode = referenceNodes[i];
  63809. reference = referenceNode.getAttribute(referenceAttributeName);
  63810. referenceNode.removeAttribute(referenceAttributeName);
  63811. this.linkElement(reference, referenceNode);
  63812. }
  63813. }
  63814. });
  63815. /**
  63816. * @private
  63817. */
  63818. Ext.define('Ext.util.sizemonitor.Abstract', {
  63819. mixins: ['Ext.mixin.Templatable'],
  63820. requires: [
  63821. 'Ext.TaskQueue'
  63822. ],
  63823. config: {
  63824. element: null,
  63825. callback: Ext.emptyFn,
  63826. scope: null,
  63827. args: []
  63828. },
  63829. width: 0,
  63830. height: 0,
  63831. contentWidth: 0,
  63832. contentHeight: 0,
  63833. constructor: function(config) {
  63834. this.refresh = Ext.Function.bind(this.refresh, this);
  63835. this.info = {
  63836. width: 0,
  63837. height: 0,
  63838. contentWidth: 0,
  63839. contentHeight: 0,
  63840. flag: 0
  63841. };
  63842. this.initElement();
  63843. this.initConfig(config);
  63844. this.bindListeners(true);
  63845. },
  63846. bindListeners: Ext.emptyFn,
  63847. applyElement: function(element) {
  63848. if (element) {
  63849. return Ext.get(element);
  63850. }
  63851. },
  63852. updateElement: function(element) {
  63853. element.append(this.detectorsContainer);
  63854. element.addCls('x-size-monitored');
  63855. },
  63856. applyArgs: function(args) {
  63857. return args.concat([this.info]);
  63858. },
  63859. refreshMonitors: Ext.emptyFn,
  63860. forceRefresh: function() {
  63861. Ext.TaskQueue.requestRead('refresh', this);
  63862. },
  63863. refreshSize: function() {
  63864. var element = this.getElement();
  63865. if (!element || element.isDestroyed) {
  63866. return false;
  63867. }
  63868. var width = element.getWidth(),
  63869. height = element.getHeight(),
  63870. contentElement = this.detectorsContainer,
  63871. contentWidth = contentElement.offsetWidth,
  63872. contentHeight = contentElement.offsetHeight,
  63873. currentContentWidth = this.contentWidth,
  63874. currentContentHeight = this.contentHeight,
  63875. info = this.info,
  63876. resized = false,
  63877. flag;
  63878. this.width = width;
  63879. this.height = height;
  63880. this.contentWidth = contentWidth;
  63881. this.contentHeight = contentHeight;
  63882. flag = ((currentContentWidth !== contentWidth ? 1 : 0) + (currentContentHeight !== contentHeight ? 2 : 0));
  63883. if (flag > 0) {
  63884. info.width = width;
  63885. info.height = height;
  63886. info.contentWidth = contentWidth;
  63887. info.contentHeight = contentHeight;
  63888. info.flag = flag;
  63889. resized = true;
  63890. this.getCallback().apply(this.getScope(), this.getArgs());
  63891. }
  63892. return resized;
  63893. },
  63894. refresh: function(force) {
  63895. if (this.refreshSize() || force) {
  63896. Ext.TaskQueue.requestWrite('refreshMonitors', this);
  63897. }
  63898. },
  63899. destroy: function() {
  63900. var element = this.getElement();
  63901. this.bindListeners(false);
  63902. if (element && !element.isDestroyed) {
  63903. element.removeCls('x-size-monitored');
  63904. }
  63905. delete this._element;
  63906. this.callSuper();
  63907. }
  63908. });
  63909. /**
  63910. * @private
  63911. */
  63912. Ext.define('Ext.util.sizemonitor.Scroll', {
  63913. extend: 'Ext.util.sizemonitor.Abstract',
  63914. getElementConfig: function() {
  63915. return {
  63916. reference: 'detectorsContainer',
  63917. classList: ['x-size-monitors', 'scroll'],
  63918. children: [
  63919. {
  63920. reference: 'expandMonitor',
  63921. className: 'expand'
  63922. },
  63923. {
  63924. reference: 'shrinkMonitor',
  63925. className: 'shrink'
  63926. }
  63927. ]
  63928. }
  63929. },
  63930. constructor: function(config) {
  63931. this.onScroll = Ext.Function.bind(this.onScroll, this);
  63932. this.callSuper(arguments);
  63933. },
  63934. bindListeners: function(bind) {
  63935. var method = bind ? 'addEventListener' : 'removeEventListener';
  63936. this.expandMonitor[method]('scroll', this.onScroll, true);
  63937. this.shrinkMonitor[method]('scroll', this.onScroll, true);
  63938. },
  63939. forceRefresh: function() {
  63940. Ext.TaskQueue.requestRead('refresh', this, [true]);
  63941. },
  63942. onScroll: function() {
  63943. Ext.TaskQueue.requestRead('refresh', this);
  63944. },
  63945. refreshMonitors: function() {
  63946. var expandMonitor = this.expandMonitor,
  63947. shrinkMonitor = this.shrinkMonitor,
  63948. end = 1000000;
  63949. if (expandMonitor && !expandMonitor.isDestroyed) {
  63950. expandMonitor.scrollLeft = end;
  63951. expandMonitor.scrollTop = end;
  63952. }
  63953. if (shrinkMonitor && !shrinkMonitor.isDestroyed) {
  63954. shrinkMonitor.scrollLeft = end;
  63955. shrinkMonitor.scrollTop = end;
  63956. }
  63957. }
  63958. });
  63959. /**
  63960. * @private
  63961. */
  63962. Ext.define('Ext.util.sizemonitor.OverflowChange', {
  63963. extend: 'Ext.util.sizemonitor.Abstract',
  63964. constructor: function(config) {
  63965. this.onExpand = Ext.Function.bind(this.onExpand, this);
  63966. this.onShrink = Ext.Function.bind(this.onShrink, this);
  63967. this.callSuper(arguments);
  63968. },
  63969. getElementConfig: function() {
  63970. return {
  63971. reference: 'detectorsContainer',
  63972. classList: ['x-size-monitors', 'overflowchanged'],
  63973. children: [
  63974. {
  63975. reference: 'expandMonitor',
  63976. className: 'expand',
  63977. children: [{
  63978. reference: 'expandHelper'
  63979. }]
  63980. },
  63981. {
  63982. reference: 'shrinkMonitor',
  63983. className: 'shrink',
  63984. children: [{
  63985. reference: 'shrinkHelper'
  63986. }]
  63987. }
  63988. ]
  63989. }
  63990. },
  63991. bindListeners: function(bind) {
  63992. var method = bind ? 'addEventListener' : 'removeEventListener';
  63993. this.expandMonitor[method]('overflowchanged', this.onExpand, true);
  63994. this.shrinkMonitor[method]('overflowchanged', this.onShrink, true);
  63995. },
  63996. onExpand: function(e) {
  63997. if (e.horizontalOverflow && e.verticalOverflow) {
  63998. return;
  63999. }
  64000. Ext.TaskQueue.requestRead('refresh', this);
  64001. },
  64002. onShrink: function(e) {
  64003. if (!e.horizontalOverflow && !e.verticalOverflow) {
  64004. return;
  64005. }
  64006. Ext.TaskQueue.requestRead('refresh', this);
  64007. },
  64008. refreshMonitors: function() {
  64009. var expandHelper = this.expandHelper,
  64010. shrinkHelper = this.shrinkHelper,
  64011. width = this.contentWidth,
  64012. height = this.contentHeight;
  64013. if (expandHelper && !expandHelper.isDestroyed) {
  64014. expandHelper.style.width = (width + 1) + 'px';
  64015. expandHelper.style.height = (height + 1) + 'px';
  64016. }
  64017. if (shrinkHelper && !shrinkHelper.isDestroyed) {
  64018. shrinkHelper.style.width = width + 'px';
  64019. shrinkHelper.style.height = height + 'px';
  64020. }
  64021. Ext.TaskQueue.requestRead('refresh', this);
  64022. }
  64023. });
  64024. /**
  64025. *
  64026. */
  64027. Ext.define('Ext.util.SizeMonitor', {
  64028. requires: [
  64029. 'Ext.util.sizemonitor.Scroll',
  64030. 'Ext.util.sizemonitor.OverflowChange'
  64031. ],
  64032. constructor: function(config) {
  64033. if (Ext.browser.engineVersion.gtEq('535')) {
  64034. return new Ext.util.sizemonitor.OverflowChange(config);
  64035. }
  64036. else {
  64037. return new Ext.util.sizemonitor.Scroll(config);
  64038. }
  64039. }
  64040. });
  64041. /**
  64042. * @private
  64043. */
  64044. Ext.define('Ext.event.publisher.ElementSize', {
  64045. extend: 'Ext.event.publisher.Publisher',
  64046. requires: [
  64047. 'Ext.util.SizeMonitor'
  64048. ],
  64049. targetType: 'element',
  64050. handledEvents: ['resize'],
  64051. constructor: function() {
  64052. this.monitors = {};
  64053. this.callSuper(arguments);
  64054. },
  64055. subscribe: function(target) {
  64056. var match = target.match(this.idSelectorRegex),
  64057. subscribers = this.subscribers,
  64058. id, element, sizeMonitor;
  64059. if (!match) {
  64060. return false;
  64061. }
  64062. id = match[1];
  64063. if (subscribers.hasOwnProperty(id)) {
  64064. subscribers[id]++;
  64065. return true;
  64066. }
  64067. subscribers[id] = 1;
  64068. element = Ext.get(id);
  64069. this.monitors[id] = sizeMonitor = new Ext.util.SizeMonitor({
  64070. element: element,
  64071. callback: this.onElementResize,
  64072. scope: this,
  64073. args: [target, element]
  64074. });
  64075. this.dispatcher.addListener('element', target, 'painted', 'forceRefresh', sizeMonitor);
  64076. return true;
  64077. },
  64078. unsubscribe: function(target, eventName, all) {
  64079. var match = target.match(this.idSelectorRegex),
  64080. subscribers = this.subscribers,
  64081. monitors = this.monitors,
  64082. id, sizeMonitor;
  64083. if (!match) {
  64084. return false;
  64085. }
  64086. id = match[1];
  64087. if (!subscribers.hasOwnProperty(id) || (!all && --subscribers[id] > 0)) {
  64088. return true;
  64089. }
  64090. delete subscribers[id];
  64091. sizeMonitor = monitors[id];
  64092. this.dispatcher.removeListener('element', target, 'painted', 'forceRefresh', sizeMonitor);
  64093. sizeMonitor.destroy();
  64094. delete monitors[id];
  64095. return true;
  64096. },
  64097. onElementResize: function(target, element, info) {
  64098. Ext.TaskQueue.requestRead('dispatch', this, [target, 'resize', [element, info]]);
  64099. }
  64100. });
  64101. /**
  64102. * @private
  64103. */
  64104. Ext.define('Ext.event.publisher.TouchGesture', {
  64105. extend: 'Ext.event.publisher.Dom',
  64106. requires: [
  64107. 'Ext.util.Point',
  64108. 'Ext.event.Touch'
  64109. ],
  64110. handledEvents: ['touchstart', 'touchmove', 'touchend', 'touchcancel'],
  64111. moveEventName: 'touchmove',
  64112. config: {
  64113. moveThrottle: 1,
  64114. buffering: {
  64115. enabled: false,
  64116. interval: 10
  64117. },
  64118. recognizers: {}
  64119. },
  64120. currentTouchesCount: 0,
  64121. constructor: function(config) {
  64122. this.processEvents = Ext.Function.bind(this.processEvents, this);
  64123. this.eventProcessors = {
  64124. touchstart: this.onTouchStart,
  64125. touchmove: this.onTouchMove,
  64126. touchend: this.onTouchEnd,
  64127. touchcancel: this.onTouchEnd
  64128. };
  64129. this.eventToRecognizerMap = {};
  64130. this.activeRecognizers = [];
  64131. this.currentRecognizers = [];
  64132. this.currentTargets = {};
  64133. this.currentTouches = {};
  64134. this.buffer = [];
  64135. this.initConfig(config);
  64136. return this.callParent();
  64137. },
  64138. applyBuffering: function(buffering) {
  64139. if (buffering.enabled === true) {
  64140. this.bufferTimer = setInterval(this.processEvents, buffering.interval);
  64141. }
  64142. else {
  64143. clearInterval(this.bufferTimer);
  64144. }
  64145. return buffering;
  64146. },
  64147. applyRecognizers: function(recognizers) {
  64148. var i, recognizer;
  64149. for (i in recognizers) {
  64150. if (recognizers.hasOwnProperty(i)) {
  64151. recognizer = recognizers[i];
  64152. if (recognizer) {
  64153. this.registerRecognizer(recognizer);
  64154. }
  64155. }
  64156. }
  64157. return recognizers;
  64158. },
  64159. handles: function(eventName) {
  64160. return this.callParent(arguments) || this.eventToRecognizerMap.hasOwnProperty(eventName);
  64161. },
  64162. doesEventBubble: function() {
  64163. // All touch events bubble
  64164. return true;
  64165. },
  64166. eventLogs: [],
  64167. onEvent: function(e) {
  64168. var buffering = this.getBuffering();
  64169. e = new Ext.event.Touch(e);
  64170. if (buffering.enabled) {
  64171. this.buffer.push(e);
  64172. }
  64173. else {
  64174. this.processEvent(e);
  64175. }
  64176. },
  64177. processEvents: function() {
  64178. var buffer = this.buffer,
  64179. ln = buffer.length,
  64180. moveEvents = [],
  64181. events, event, i;
  64182. if (ln > 0) {
  64183. events = buffer.slice(0);
  64184. buffer.length = 0;
  64185. for (i = 0; i < ln; i++) {
  64186. event = events[i];
  64187. if (event.type === this.moveEventName) {
  64188. moveEvents.push(event);
  64189. }
  64190. else {
  64191. if (moveEvents.length > 0) {
  64192. this.processEvent(this.mergeEvents(moveEvents));
  64193. moveEvents.length = 0;
  64194. }
  64195. this.processEvent(event);
  64196. }
  64197. }
  64198. if (moveEvents.length > 0) {
  64199. this.processEvent(this.mergeEvents(moveEvents));
  64200. moveEvents.length = 0;
  64201. }
  64202. }
  64203. },
  64204. mergeEvents: function(events) {
  64205. var changedTouchesLists = [],
  64206. ln = events.length,
  64207. i, event, targetEvent;
  64208. targetEvent = events[ln - 1];
  64209. if (ln === 1) {
  64210. return targetEvent;
  64211. }
  64212. for (i = 0; i < ln; i++) {
  64213. event = events[i];
  64214. changedTouchesLists.push(event.changedTouches);
  64215. }
  64216. targetEvent.changedTouches = this.mergeTouchLists(changedTouchesLists);
  64217. return targetEvent;
  64218. },
  64219. mergeTouchLists: function(touchLists) {
  64220. var touches = {},
  64221. list = [],
  64222. i, ln, touchList, j, subLn, touch, identifier;
  64223. for (i = 0,ln = touchLists.length; i < ln; i++) {
  64224. touchList = touchLists[i];
  64225. for (j = 0,subLn = touchList.length; j < subLn; j++) {
  64226. touch = touchList[j];
  64227. identifier = touch.identifier;
  64228. touches[identifier] = touch;
  64229. }
  64230. }
  64231. for (identifier in touches) {
  64232. if (touches.hasOwnProperty(identifier)) {
  64233. list.push(touches[identifier]);
  64234. }
  64235. }
  64236. return list;
  64237. },
  64238. registerRecognizer: function(recognizer) {
  64239. var map = this.eventToRecognizerMap,
  64240. activeRecognizers = this.activeRecognizers,
  64241. handledEvents = recognizer.getHandledEvents(),
  64242. i, ln, eventName;
  64243. recognizer.setOnRecognized(this.onRecognized);
  64244. recognizer.setCallbackScope(this);
  64245. for (i = 0,ln = handledEvents.length; i < ln; i++) {
  64246. eventName = handledEvents[i];
  64247. map[eventName] = recognizer;
  64248. }
  64249. activeRecognizers.push(recognizer);
  64250. return this;
  64251. },
  64252. onRecognized: function(eventName, e, touches, info) {
  64253. var targetGroups = [],
  64254. ln = touches.length,
  64255. targets, i, touch;
  64256. if (ln === 1) {
  64257. return this.publish(eventName, touches[0].targets, e, info);
  64258. }
  64259. for (i = 0; i < ln; i++) {
  64260. touch = touches[i];
  64261. targetGroups.push(touch.targets);
  64262. }
  64263. targets = this.getCommonTargets(targetGroups);
  64264. this.publish(eventName, targets, e, info);
  64265. },
  64266. publish: function(eventName, targets, event, info) {
  64267. event.set(info);
  64268. return this.callParent([eventName, targets, event]);
  64269. },
  64270. getCommonTargets: function(targetGroups) {
  64271. var firstTargetGroup = targetGroups[0],
  64272. ln = targetGroups.length;
  64273. if (ln === 1) {
  64274. return firstTargetGroup;
  64275. }
  64276. var commonTargets = [],
  64277. i = 1,
  64278. target, targets, j;
  64279. while (true) {
  64280. target = firstTargetGroup[firstTargetGroup.length - i];
  64281. if (!target) {
  64282. return commonTargets;
  64283. }
  64284. for (j = 1; j < ln; j++) {
  64285. targets = targetGroups[j];
  64286. if (targets[targets.length - i] !== target) {
  64287. return commonTargets;
  64288. }
  64289. }
  64290. commonTargets.unshift(target);
  64291. i++;
  64292. }
  64293. return commonTargets;
  64294. },
  64295. invokeRecognizers: function(methodName, e) {
  64296. var recognizers = this.activeRecognizers,
  64297. ln = recognizers.length,
  64298. i, recognizer;
  64299. if (methodName === 'onStart') {
  64300. for (i = 0; i < ln; i++) {
  64301. recognizers[i].isActive = true;
  64302. }
  64303. }
  64304. for (i = 0; i < ln; i++) {
  64305. recognizer = recognizers[i];
  64306. if (recognizer.isActive && recognizer[methodName].call(recognizer, e) === false) {
  64307. recognizer.isActive = false;
  64308. }
  64309. }
  64310. },
  64311. getActiveRecognizers: function() {
  64312. return this.activeRecognizers;
  64313. },
  64314. processEvent: function(e) {
  64315. this.eventProcessors[e.type].call(this, e);
  64316. },
  64317. onTouchStart: function(e) {
  64318. var currentTargets = this.currentTargets,
  64319. currentTouches = this.currentTouches,
  64320. currentTouchesCount = this.currentTouchesCount,
  64321. currentIdentifiers = {},
  64322. changedTouches = e.changedTouches,
  64323. changedTouchedLn = changedTouches.length,
  64324. touches = e.touches,
  64325. touchesLn = touches.length,
  64326. i, touch, identifier, fakeEndEvent;
  64327. currentTouchesCount += changedTouchedLn;
  64328. if (currentTouchesCount > touchesLn) {
  64329. for (i = 0; i < touchesLn; i++) {
  64330. touch = touches[i];
  64331. identifier = touch.identifier;
  64332. currentIdentifiers[identifier] = true;
  64333. }
  64334. if (!Ext.os.is.Android3 && !Ext.os.is.Android4) {
  64335. for (identifier in currentTouches) {
  64336. if (currentTouches.hasOwnProperty(identifier)) {
  64337. if (!currentIdentifiers[identifier]) {
  64338. currentTouchesCount--;
  64339. fakeEndEvent = e.clone();
  64340. touch = currentTouches[identifier];
  64341. touch.targets = this.getBubblingTargets(this.getElementTarget(touch.target));
  64342. fakeEndEvent.changedTouches = [touch];
  64343. this.onTouchEnd(fakeEndEvent);
  64344. }
  64345. }
  64346. }
  64347. }
  64348. // Fix for a bug found in Motorola Droid X (Gingerbread) and similar
  64349. // where there are 2 touchstarts but just one touchend
  64350. if (Ext.os.is.Android2 && currentTouchesCount > touchesLn) {
  64351. return;
  64352. }
  64353. }
  64354. for (i = 0; i < changedTouchedLn; i++) {
  64355. touch = changedTouches[i];
  64356. identifier = touch.identifier;
  64357. if (!currentTouches.hasOwnProperty(identifier)) {
  64358. this.currentTouchesCount++;
  64359. }
  64360. currentTouches[identifier] = touch;
  64361. currentTargets[identifier] = this.getBubblingTargets(this.getElementTarget(touch.target));
  64362. }
  64363. e.setTargets(currentTargets);
  64364. for (i = 0; i < changedTouchedLn; i++) {
  64365. touch = changedTouches[i];
  64366. this.publish('touchstart', touch.targets, e, {touch: touch});
  64367. }
  64368. if (!this.isStarted) {
  64369. this.isStarted = true;
  64370. this.invokeRecognizers('onStart', e);
  64371. }
  64372. this.invokeRecognizers('onTouchStart', e);
  64373. },
  64374. onTouchMove: function(e) {
  64375. if (!this.isStarted) {
  64376. return;
  64377. }
  64378. var currentTargets = this.currentTargets,
  64379. currentTouches = this.currentTouches,
  64380. moveThrottle = this.getMoveThrottle(),
  64381. changedTouches = e.changedTouches,
  64382. stillTouchesCount = 0,
  64383. i, ln, touch, point, oldPoint, identifier;
  64384. e.setTargets(currentTargets);
  64385. for (i = 0,ln = changedTouches.length; i < ln; i++) {
  64386. touch = changedTouches[i];
  64387. identifier = touch.identifier;
  64388. point = touch.point;
  64389. oldPoint = currentTouches[identifier].point;
  64390. if (moveThrottle && point.isCloseTo(oldPoint, moveThrottle)) {
  64391. stillTouchesCount++;
  64392. continue;
  64393. }
  64394. currentTouches[identifier] = touch;
  64395. this.publish('touchmove', touch.targets, e, {touch: touch});
  64396. }
  64397. if (stillTouchesCount < ln) {
  64398. this.invokeRecognizers('onTouchMove', e);
  64399. }
  64400. },
  64401. onTouchEnd: function(e) {
  64402. if (!this.isStarted) {
  64403. return;
  64404. }
  64405. var currentTargets = this.currentTargets,
  64406. currentTouches = this.currentTouches,
  64407. changedTouches = e.changedTouches,
  64408. ln = changedTouches.length,
  64409. isEnded, identifier, i, touch;
  64410. e.setTargets(currentTargets);
  64411. this.currentTouchesCount -= ln;
  64412. isEnded = (this.currentTouchesCount === 0);
  64413. if (isEnded) {
  64414. this.isStarted = false;
  64415. }
  64416. for (i = 0; i < ln; i++) {
  64417. touch = changedTouches[i];
  64418. identifier = touch.identifier;
  64419. delete currentTouches[identifier];
  64420. delete currentTargets[identifier];
  64421. this.publish('touchend', touch.targets, e, {touch: touch});
  64422. }
  64423. this.invokeRecognizers('onTouchEnd', e);
  64424. if (isEnded) {
  64425. this.invokeRecognizers('onEnd', e);
  64426. }
  64427. }
  64428. }, function() {
  64429. if (!Ext.feature.has.Touch) {
  64430. this.override({
  64431. moveEventName: 'mousemove',
  64432. map: {
  64433. mouseToTouch: {
  64434. mousedown: 'touchstart',
  64435. mousemove: 'touchmove',
  64436. mouseup: 'touchend'
  64437. },
  64438. touchToMouse: {
  64439. touchstart: 'mousedown',
  64440. touchmove: 'mousemove',
  64441. touchend: 'mouseup'
  64442. }
  64443. },
  64444. attachListener: function(eventName) {
  64445. eventName = this.map.touchToMouse[eventName];
  64446. if (!eventName) {
  64447. return;
  64448. }
  64449. return this.callOverridden([eventName]);
  64450. },
  64451. lastEventType: null,
  64452. onEvent: function(e) {
  64453. if ('button' in e && e.button !== 0) {
  64454. return;
  64455. }
  64456. var type = e.type,
  64457. touchList = [e];
  64458. // Temporary fix for a recent Chrome bugs where events don't seem to bubble up to document
  64459. // when the element is being animated
  64460. // with webkit-transition (2 mousedowns without any mouseup)
  64461. if (type === 'mousedown' && this.lastEventType && this.lastEventType !== 'mouseup') {
  64462. var fixedEvent = document.createEvent("MouseEvent");
  64463. fixedEvent.initMouseEvent('mouseup', e.bubbles, e.cancelable,
  64464. document.defaultView, e.detail, e.screenX, e.screenY, e.clientX,
  64465. e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.metaKey,
  64466. e.button, e.relatedTarget);
  64467. this.onEvent(fixedEvent);
  64468. }
  64469. if (type !== 'mousemove') {
  64470. this.lastEventType = type;
  64471. }
  64472. e.identifier = 1;
  64473. e.touches = (type !== 'mouseup') ? touchList : [];
  64474. e.targetTouches = (type !== 'mouseup') ? touchList : [];
  64475. e.changedTouches = touchList;
  64476. return this.callOverridden([e]);
  64477. },
  64478. processEvent: function(e) {
  64479. this.eventProcessors[this.map.mouseToTouch[e.type]].call(this, e);
  64480. }
  64481. });
  64482. }
  64483. });
  64484. /**
  64485. * A base class for all event recognizers in Sencha Touch.
  64486. *
  64487. * Sencha Touch, by default, includes various different {@link Ext.event.recognizer.Recognizer} subclasses to recognize
  64488. * events happening in your application.
  64489. *
  64490. * ## Default recognizers
  64491. *
  64492. * * {@link Ext.event.recognizer.Tap}
  64493. * * {@link Ext.event.recognizer.DoubleTap}
  64494. * * {@link Ext.event.recognizer.LongPress}
  64495. * * {@link Ext.event.recognizer.Drag}
  64496. * * {@link Ext.event.recognizer.HorizontalSwipe}
  64497. * * {@link Ext.event.recognizer.Pinch}
  64498. * * {@link Ext.event.recognizer.Rotate}
  64499. *
  64500. * ## Additional recognizers
  64501. *
  64502. * * {@link Ext.event.recognizer.VerticalSwipe}
  64503. *
  64504. * If you want to create custom recognizers, or disable recognizers in your Sencha Touch application, please refer to the
  64505. * documentation in {@link Ext#setup}.
  64506. *
  64507. * @private
  64508. */
  64509. Ext.define('Ext.event.recognizer.Recognizer', {
  64510. mixins: ['Ext.mixin.Identifiable'],
  64511. handledEvents: [],
  64512. config: {
  64513. onRecognized: Ext.emptyFn,
  64514. onFailed: Ext.emptyFn,
  64515. callbackScope: null
  64516. },
  64517. constructor: function(config) {
  64518. this.initConfig(config);
  64519. return this;
  64520. },
  64521. getHandledEvents: function() {
  64522. return this.handledEvents;
  64523. },
  64524. onStart: Ext.emptyFn,
  64525. onEnd: Ext.emptyFn,
  64526. fail: function() {
  64527. this.getOnFailed().apply(this.getCallbackScope(), arguments);
  64528. return false;
  64529. },
  64530. fire: function() {
  64531. this.getOnRecognized().apply(this.getCallbackScope(), arguments);
  64532. }
  64533. });
  64534. /**
  64535. * @private
  64536. */
  64537. Ext.define('Ext.event.recognizer.Touch', {
  64538. extend: 'Ext.event.recognizer.Recognizer',
  64539. onTouchStart: Ext.emptyFn,
  64540. onTouchMove: Ext.emptyFn,
  64541. onTouchEnd: Ext.emptyFn
  64542. });
  64543. /**
  64544. * @private
  64545. */
  64546. Ext.define('Ext.event.recognizer.SingleTouch', {
  64547. extend: 'Ext.event.recognizer.Touch',
  64548. inheritableStatics: {
  64549. NOT_SINGLE_TOUCH: 0x01,
  64550. TOUCH_MOVED: 0x02
  64551. },
  64552. onTouchStart: function(e) {
  64553. if (e.touches.length > 1) {
  64554. return this.fail(this.self.NOT_SINGLE_TOUCH);
  64555. }
  64556. }
  64557. });
  64558. /**
  64559. * A simple event recognizer which knows when you double tap.
  64560. *
  64561. * @private
  64562. */
  64563. Ext.define('Ext.event.recognizer.DoubleTap', {
  64564. extend: 'Ext.event.recognizer.SingleTouch',
  64565. inheritableStatics: {
  64566. DIFFERENT_TARGET: 0x03
  64567. },
  64568. config: {
  64569. maxDuration: 300
  64570. },
  64571. handledEvents: ['singletap', 'doubletap'],
  64572. /**
  64573. * @member Ext.dom.Element
  64574. * @event singletap
  64575. * Fires when there is a single tap.
  64576. * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
  64577. * @param {HTMLElement} node The target of the event.
  64578. * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
  64579. */
  64580. /**
  64581. * @member Ext.dom.Element
  64582. * @event doubletap
  64583. * Fires when there is a double tap.
  64584. * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
  64585. * @param {HTMLElement} node The target of the event.
  64586. * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
  64587. */
  64588. singleTapTimer: null,
  64589. startTime: 0,
  64590. lastTapTime: 0,
  64591. onTouchStart: function(e) {
  64592. if (this.callParent(arguments) === false) {
  64593. return false;
  64594. }
  64595. this.startTime = e.time;
  64596. clearTimeout(this.singleTapTimer);
  64597. },
  64598. onTouchMove: function() {
  64599. return this.fail(this.self.TOUCH_MOVED);
  64600. },
  64601. onEnd: function(e) {
  64602. var me = this,
  64603. maxDuration = this.getMaxDuration(),
  64604. touch = e.changedTouches[0],
  64605. time = e.time,
  64606. target = e.target,
  64607. lastTapTime = this.lastTapTime,
  64608. lastTarget = this.lastTarget,
  64609. duration;
  64610. this.lastTapTime = time;
  64611. this.lastTarget = target;
  64612. if (lastTapTime) {
  64613. duration = time - lastTapTime;
  64614. if (duration <= maxDuration) {
  64615. if (target !== lastTarget) {
  64616. return this.fail(this.self.DIFFERENT_TARGET);
  64617. }
  64618. this.lastTarget = null;
  64619. this.lastTapTime = 0;
  64620. this.fire('doubletap', e, [touch], {
  64621. touch: touch,
  64622. duration: duration
  64623. });
  64624. return;
  64625. }
  64626. }
  64627. if (time - this.startTime > maxDuration) {
  64628. this.fireSingleTap(e, touch);
  64629. }
  64630. else {
  64631. this.singleTapTimer = setTimeout(function() {
  64632. me.fireSingleTap(e, touch);
  64633. }, maxDuration);
  64634. }
  64635. },
  64636. fireSingleTap: function(e, touch) {
  64637. this.fire('singletap', e, [touch], {
  64638. touch: touch
  64639. });
  64640. }
  64641. });
  64642. /**
  64643. * A simple event recognizer which knows when you drag.
  64644. *
  64645. * @private
  64646. */
  64647. Ext.define('Ext.event.recognizer.Drag', {
  64648. extend: 'Ext.event.recognizer.SingleTouch',
  64649. isStarted: false,
  64650. startPoint: null,
  64651. previousPoint: null,
  64652. lastPoint: null,
  64653. handledEvents: ['dragstart', 'drag', 'dragend'],
  64654. /**
  64655. * @member Ext.dom.Element
  64656. * @event dragstart
  64657. * Fired once when a drag has started.
  64658. * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
  64659. * @param {HTMLElement} node The target of the event.
  64660. * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
  64661. */
  64662. /**
  64663. * @member Ext.dom.Element
  64664. * @event drag
  64665. * Fires continuously when there is dragging (the touch must move for this to be fired).
  64666. * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
  64667. * @param {HTMLElement} node The target of the event.
  64668. * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
  64669. */
  64670. /**
  64671. * @member Ext.dom.Element
  64672. * @event dragend
  64673. * Fires when a drag has ended.
  64674. * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
  64675. * @param {HTMLElement} node The target of the event.
  64676. * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
  64677. */
  64678. onTouchStart: function(e) {
  64679. var startTouches,
  64680. startTouch;
  64681. if (this.callParent(arguments) === false) {
  64682. if (this.isStarted && this.lastMoveEvent !== null) {
  64683. this.onTouchEnd(this.lastMoveEvent);
  64684. }
  64685. return false;
  64686. }
  64687. this.startTouches = startTouches = e.changedTouches;
  64688. this.startTouch = startTouch = startTouches[0];
  64689. this.startPoint = startTouch.point;
  64690. },
  64691. onTouchMove: function(e) {
  64692. var touches = e.changedTouches,
  64693. touch = touches[0],
  64694. point = touch.point,
  64695. time = e.time;
  64696. if (this.lastPoint) {
  64697. this.previousPoint = this.lastPoint;
  64698. }
  64699. if (this.lastTime) {
  64700. this.previousTime = this.lastTime;
  64701. }
  64702. this.lastTime = time;
  64703. this.lastPoint = point;
  64704. this.lastMoveEvent = e;
  64705. if (!this.isStarted) {
  64706. this.isStarted = true;
  64707. this.startTime = time;
  64708. this.previousTime = time;
  64709. this.previousPoint = this.startPoint;
  64710. this.fire('dragstart', e, this.startTouches, this.getInfo(e, this.startTouch));
  64711. }
  64712. else {
  64713. this.fire('drag', e, touches, this.getInfo(e, touch));
  64714. }
  64715. },
  64716. onTouchEnd: function(e) {
  64717. if (this.isStarted) {
  64718. var touches = e.changedTouches,
  64719. touch = touches[0],
  64720. point = touch.point;
  64721. this.isStarted = false;
  64722. this.lastPoint = point;
  64723. this.fire('dragend', e, touches, this.getInfo(e, touch));
  64724. this.startTime = 0;
  64725. this.previousTime = 0;
  64726. this.lastTime = 0;
  64727. this.startPoint = null;
  64728. this.previousPoint = null;
  64729. this.lastPoint = null;
  64730. this.lastMoveEvent = null;
  64731. }
  64732. },
  64733. getInfo: function(e, touch) {
  64734. var time = e.time,
  64735. startPoint = this.startPoint,
  64736. previousPoint = this.previousPoint,
  64737. startTime = this.startTime,
  64738. previousTime = this.previousTime,
  64739. point = this.lastPoint,
  64740. deltaX = point.x - startPoint.x,
  64741. deltaY = point.y - startPoint.y,
  64742. info = {
  64743. touch: touch,
  64744. startX: startPoint.x,
  64745. startY: startPoint.y,
  64746. previousX: previousPoint.x,
  64747. previousY: previousPoint.y,
  64748. pageX: point.x,
  64749. pageY: point.y,
  64750. deltaX: deltaX,
  64751. deltaY: deltaY,
  64752. absDeltaX: Math.abs(deltaX),
  64753. absDeltaY: Math.abs(deltaY),
  64754. previousDeltaX: point.x - previousPoint.x,
  64755. previousDeltaY: point.y - previousPoint.y,
  64756. time: time,
  64757. startTime: startTime,
  64758. previousTime: previousTime,
  64759. deltaTime: time - startTime,
  64760. previousDeltaTime: time - previousTime
  64761. };
  64762. return info;
  64763. }
  64764. });
  64765. /**
  64766. * A base class used for both {@link Ext.event.recognizer.VerticalSwipe} and {@link Ext.event.recognizer.HorizontalSwipe}
  64767. * event recognizers.
  64768. *
  64769. * @private
  64770. */
  64771. Ext.define('Ext.event.recognizer.Swipe', {
  64772. extend: 'Ext.event.recognizer.SingleTouch',
  64773. handledEvents: ['swipe'],
  64774. /**
  64775. * @member Ext.dom.Element
  64776. * @event swipe
  64777. * Fires when there is a swipe
  64778. * When listening to this, ensure you know about the {@link Ext.event.Event#direction} property in the `event` object.
  64779. * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
  64780. * @param {HTMLElement} node The target of the event.
  64781. * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
  64782. */
  64783. /**
  64784. * @property {Number} direction
  64785. * The direction of the swipe. Available options are:
  64786. *
  64787. * - up
  64788. * - down
  64789. * - left
  64790. * - right
  64791. *
  64792. * __Note:__ In order to recognize swiping up and down, you must enable the vertical swipe recognizer.
  64793. *
  64794. * **This is only available when the event type is `swipe`**
  64795. * @member Ext.event.Event
  64796. */
  64797. /**
  64798. * @property {Number} duration
  64799. * The duration of the swipe.
  64800. *
  64801. * **This is only available when the event type is `swipe`**
  64802. * @member Ext.event.Event
  64803. */
  64804. inheritableStatics: {
  64805. MAX_OFFSET_EXCEEDED: 0x10,
  64806. MAX_DURATION_EXCEEDED: 0x11,
  64807. DISTANCE_NOT_ENOUGH: 0x12
  64808. },
  64809. config: {
  64810. minDistance: 80,
  64811. maxOffset: 35,
  64812. maxDuration: 1000
  64813. },
  64814. onTouchStart: function(e) {
  64815. if (this.callParent(arguments) === false) {
  64816. return false;
  64817. }
  64818. var touch = e.changedTouches[0];
  64819. this.startTime = e.time;
  64820. this.isHorizontal = true;
  64821. this.isVertical = true;
  64822. this.startX = touch.pageX;
  64823. this.startY = touch.pageY;
  64824. },
  64825. onTouchMove: function(e) {
  64826. var touch = e.changedTouches[0],
  64827. x = touch.pageX,
  64828. y = touch.pageY,
  64829. absDeltaX = Math.abs(x - this.startX),
  64830. absDeltaY = Math.abs(y - this.startY),
  64831. time = e.time;
  64832. if (time - this.startTime > this.getMaxDuration()) {
  64833. return this.fail(this.self.MAX_DURATION_EXCEEDED);
  64834. }
  64835. if (this.isVertical && absDeltaX > this.getMaxOffset()) {
  64836. this.isVertical = false;
  64837. }
  64838. if (this.isHorizontal && absDeltaY > this.getMaxOffset()) {
  64839. this.isHorizontal = false;
  64840. }
  64841. if (!this.isHorizontal && !this.isVertical) {
  64842. return this.fail(this.self.MAX_OFFSET_EXCEEDED);
  64843. }
  64844. },
  64845. onTouchEnd: function(e) {
  64846. if (this.onTouchMove(e) === false) {
  64847. return false;
  64848. }
  64849. var touch = e.changedTouches[0],
  64850. x = touch.pageX,
  64851. y = touch.pageY,
  64852. deltaX = x - this.startX,
  64853. deltaY = y - this.startY,
  64854. absDeltaX = Math.abs(deltaX),
  64855. absDeltaY = Math.abs(deltaY),
  64856. minDistance = this.getMinDistance(),
  64857. duration = e.time - this.startTime,
  64858. direction, distance;
  64859. if (this.isVertical && absDeltaY < minDistance) {
  64860. this.isVertical = false;
  64861. }
  64862. if (this.isHorizontal && absDeltaX < minDistance) {
  64863. this.isHorizontal = false;
  64864. }
  64865. if (this.isHorizontal) {
  64866. direction = (deltaX < 0) ? 'left' : 'right';
  64867. distance = absDeltaX;
  64868. }
  64869. else if (this.isVertical) {
  64870. direction = (deltaY < 0) ? 'up' : 'down';
  64871. distance = absDeltaY;
  64872. }
  64873. else {
  64874. return this.fail(this.self.DISTANCE_NOT_ENOUGH);
  64875. }
  64876. this.fire('swipe', e, [touch], {
  64877. touch: touch,
  64878. direction: direction,
  64879. distance: distance,
  64880. duration: duration
  64881. });
  64882. }
  64883. });
  64884. /**
  64885. * A event recognizer created to recognize horizontal swipe movements.
  64886. *
  64887. * @private
  64888. */
  64889. Ext.define('Ext.event.recognizer.HorizontalSwipe', {
  64890. extend: 'Ext.event.recognizer.Swipe',
  64891. handledEvents: ['swipe'],
  64892. onTouchStart: function(e) {
  64893. if (this.callParent(arguments) === false) {
  64894. return false;
  64895. }
  64896. var touch = e.changedTouches[0];
  64897. this.startTime = e.time;
  64898. this.startX = touch.pageX;
  64899. this.startY = touch.pageY;
  64900. },
  64901. onTouchMove: function(e) {
  64902. var touch = e.changedTouches[0],
  64903. y = touch.pageY,
  64904. absDeltaY = Math.abs(y - this.startY),
  64905. time = e.time,
  64906. maxDuration = this.getMaxDuration(),
  64907. maxOffset = this.getMaxOffset();
  64908. if (time - this.startTime > maxDuration) {
  64909. return this.fail(this.self.MAX_DURATION_EXCEEDED);
  64910. }
  64911. if (absDeltaY > maxOffset) {
  64912. return this.fail(this.self.MAX_OFFSET_EXCEEDED);
  64913. }
  64914. },
  64915. onTouchEnd: function(e) {
  64916. if (this.onTouchMove(e) !== false) {
  64917. var touch = e.changedTouches[0],
  64918. x = touch.pageX,
  64919. deltaX = x - this.startX,
  64920. distance = Math.abs(deltaX),
  64921. duration = e.time - this.startTime,
  64922. minDistance = this.getMinDistance(),
  64923. direction;
  64924. if (distance < minDistance) {
  64925. return this.fail(this.self.DISTANCE_NOT_ENOUGH);
  64926. }
  64927. direction = (deltaX < 0) ? 'left' : 'right';
  64928. this.fire('swipe', e, [touch], {
  64929. touch: touch,
  64930. direction: direction,
  64931. distance: distance,
  64932. duration: duration
  64933. });
  64934. }
  64935. }
  64936. });
  64937. /**
  64938. * A event recognizer which knows when you tap and hold for more than 1 second.
  64939. *
  64940. * @private
  64941. */
  64942. Ext.define('Ext.event.recognizer.LongPress', {
  64943. extend: 'Ext.event.recognizer.SingleTouch',
  64944. inheritableStatics: {
  64945. DURATION_NOT_ENOUGH: 0x20
  64946. },
  64947. config: {
  64948. minDuration: 1000
  64949. },
  64950. handledEvents: ['longpress'],
  64951. /**
  64952. * @member Ext.dom.Element
  64953. * @event longpress
  64954. * Fires when you touch and hold still for more than 1 second.
  64955. * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
  64956. * @param {HTMLElement} node The target of the event.
  64957. * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
  64958. */
  64959. /**
  64960. * @member Ext.dom.Element
  64961. * @event taphold
  64962. * @inheritdoc Ext.dom.Element#longpress
  64963. */
  64964. fireLongPress: function(e) {
  64965. var touch = e.changedTouches[0];
  64966. this.fire('longpress', e, [touch], {
  64967. touch: touch,
  64968. duration: this.getMinDuration()
  64969. });
  64970. this.isLongPress = true;
  64971. },
  64972. onTouchStart: function(e) {
  64973. var me = this;
  64974. if (this.callParent(arguments) === false) {
  64975. return false;
  64976. }
  64977. this.isLongPress = false;
  64978. this.timer = setTimeout(function() {
  64979. me.fireLongPress(e);
  64980. }, this.getMinDuration());
  64981. },
  64982. onTouchMove: function() {
  64983. return this.fail(this.self.TOUCH_MOVED);
  64984. },
  64985. onTouchEnd: function() {
  64986. if (!this.isLongPress) {
  64987. return this.fail(this.self.DURATION_NOT_ENOUGH);
  64988. }
  64989. },
  64990. fail: function() {
  64991. clearTimeout(this.timer);
  64992. return this.callParent(arguments);
  64993. }
  64994. }, function() {
  64995. this.override({
  64996. handledEvents: ['longpress', 'taphold'],
  64997. fire: function(eventName) {
  64998. if (eventName === 'longpress') {
  64999. var args = Array.prototype.slice.call(arguments);
  65000. args[0] = 'taphold';
  65001. this.fire.apply(this, args);
  65002. }
  65003. return this.callOverridden(arguments);
  65004. }
  65005. });
  65006. });
  65007. /**
  65008. * @private
  65009. */
  65010. Ext.define('Ext.event.recognizer.MultiTouch', {
  65011. extend: 'Ext.event.recognizer.Touch',
  65012. requiredTouchesCount: 2,
  65013. isTracking: false,
  65014. isStarted: false,
  65015. onTouchStart: function(e) {
  65016. var requiredTouchesCount = this.requiredTouchesCount,
  65017. touches = e.touches,
  65018. touchesCount = touches.length;
  65019. if (touchesCount === requiredTouchesCount) {
  65020. this.start(e);
  65021. }
  65022. else if (touchesCount > requiredTouchesCount) {
  65023. this.end(e);
  65024. }
  65025. },
  65026. onTouchEnd: function(e) {
  65027. this.end(e);
  65028. },
  65029. start: function() {
  65030. if (!this.isTracking) {
  65031. this.isTracking = true;
  65032. this.isStarted = false;
  65033. }
  65034. },
  65035. end: function(e) {
  65036. if (this.isTracking) {
  65037. this.isTracking = false;
  65038. if (this.isStarted) {
  65039. this.isStarted = false;
  65040. this.fireEnd(e);
  65041. }
  65042. }
  65043. }
  65044. });
  65045. /**
  65046. * A event recognizer which knows when you pinch.
  65047. *
  65048. * @private
  65049. */
  65050. Ext.define('Ext.event.recognizer.Pinch', {
  65051. extend: 'Ext.event.recognizer.MultiTouch',
  65052. requiredTouchesCount: 2,
  65053. handledEvents: ['pinchstart', 'pinch', 'pinchend'],
  65054. /**
  65055. * @member Ext.dom.Element
  65056. * @event pinchstart
  65057. * Fired once when a pinch has started.
  65058. * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
  65059. * @param {HTMLElement} node The target of the event.
  65060. * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
  65061. */
  65062. /**
  65063. * @member Ext.dom.Element
  65064. * @event pinch
  65065. * Fires continuously when there is pinching (the touch must move for this to be fired).
  65066. * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
  65067. * @param {HTMLElement} node The target of the event.
  65068. * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
  65069. */
  65070. /**
  65071. * @member Ext.dom.Element
  65072. * @event pinchend
  65073. * Fires when a pinch has ended.
  65074. * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
  65075. * @param {HTMLElement} node The target of the event.
  65076. * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
  65077. */
  65078. /**
  65079. * @property {Number} scale
  65080. * The scape of a pinch event.
  65081. *
  65082. * **This is only available when the event type is `pinch`**
  65083. * @member Ext.event.Event
  65084. */
  65085. startDistance: 0,
  65086. lastTouches: null,
  65087. onTouchMove: function(e) {
  65088. if (!this.isTracking) {
  65089. return;
  65090. }
  65091. var touches = Array.prototype.slice.call(e.touches),
  65092. firstPoint, secondPoint, distance;
  65093. firstPoint = touches[0].point;
  65094. secondPoint = touches[1].point;
  65095. distance = firstPoint.getDistanceTo(secondPoint);
  65096. if (distance === 0) {
  65097. return;
  65098. }
  65099. if (!this.isStarted) {
  65100. this.isStarted = true;
  65101. this.startDistance = distance;
  65102. this.fire('pinchstart', e, touches, {
  65103. touches: touches,
  65104. distance: distance,
  65105. scale: 1
  65106. });
  65107. }
  65108. else {
  65109. this.fire('pinch', e, touches, {
  65110. touches: touches,
  65111. distance: distance,
  65112. scale: distance / this.startDistance
  65113. });
  65114. }
  65115. this.lastTouches = touches;
  65116. },
  65117. fireEnd: function(e) {
  65118. this.fire('pinchend', e, this.lastTouches);
  65119. },
  65120. fail: function() {
  65121. return this.callParent(arguments);
  65122. }
  65123. });
  65124. /**
  65125. * A simple event recognizer which knows when you rotate.
  65126. *
  65127. * @private
  65128. */
  65129. Ext.define('Ext.event.recognizer.Rotate', {
  65130. extend: 'Ext.event.recognizer.MultiTouch',
  65131. requiredTouchesCount: 2,
  65132. handledEvents: ['rotatestart', 'rotate', 'rotateend'],
  65133. /**
  65134. * @member Ext.dom.Element
  65135. * @event rotatestart
  65136. * Fired once when a rotation has started.
  65137. * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
  65138. * @param {HTMLElement} node The target of the event.
  65139. * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
  65140. */
  65141. /**
  65142. * @member Ext.dom.Element
  65143. * @event rotate
  65144. * Fires continuously when there is rotation (the touch must move for this to be fired).
  65145. * When listening to this, ensure you know about the {@link Ext.event.Event#angle} and {@link Ext.event.Event#rotation}
  65146. * properties in the `event` object.
  65147. * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
  65148. * @param {HTMLElement} node The target of the event.
  65149. * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
  65150. */
  65151. /**
  65152. * @member Ext.dom.Element
  65153. * @event rotateend
  65154. * Fires when a rotation event has ended.
  65155. * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
  65156. * @param {HTMLElement} node The target of the event.
  65157. * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
  65158. */
  65159. /**
  65160. * @property {Number} angle
  65161. * The angle of the rotation.
  65162. *
  65163. * **This is only available when the event type is `rotate`**
  65164. * @member Ext.event.Event
  65165. */
  65166. /**
  65167. * @property {Number} rotation
  65168. * A amount of rotation, since the start of the event.
  65169. *
  65170. * **This is only available when the event type is `rotate`**
  65171. * @member Ext.event.Event
  65172. */
  65173. startAngle: 0,
  65174. lastTouches: null,
  65175. lastAngle: null,
  65176. onTouchMove: function(e) {
  65177. if (!this.isTracking) {
  65178. return;
  65179. }
  65180. var touches = Array.prototype.slice.call(e.touches),
  65181. lastAngle = this.lastAngle,
  65182. firstPoint, secondPoint, angle, nextAngle, previousAngle, diff;
  65183. firstPoint = touches[0].point;
  65184. secondPoint = touches[1].point;
  65185. angle = firstPoint.getAngleTo(secondPoint);
  65186. if (lastAngle !== null) {
  65187. diff = Math.abs(lastAngle - angle);
  65188. nextAngle = angle + 360;
  65189. previousAngle = angle - 360;
  65190. if (Math.abs(nextAngle - lastAngle) < diff) {
  65191. angle = nextAngle;
  65192. }
  65193. else if (Math.abs(previousAngle - lastAngle) < diff) {
  65194. angle = previousAngle;
  65195. }
  65196. }
  65197. this.lastAngle = angle;
  65198. if (!this.isStarted) {
  65199. this.isStarted = true;
  65200. this.startAngle = angle;
  65201. this.fire('rotatestart', e, touches, {
  65202. touches: touches,
  65203. angle: angle,
  65204. rotation: 0
  65205. });
  65206. }
  65207. else {
  65208. this.fire('rotate', e, touches, {
  65209. touches: touches,
  65210. angle: angle,
  65211. rotation: angle - this.startAngle
  65212. });
  65213. }
  65214. this.lastTouches = touches;
  65215. },
  65216. fireEnd: function(e) {
  65217. this.lastAngle = null;
  65218. this.fire('rotateend', e, this.lastTouches);
  65219. }
  65220. });
  65221. /**
  65222. * A simple event recognizer which knows when you tap.
  65223. *
  65224. * @private
  65225. */
  65226. Ext.define('Ext.event.recognizer.Tap', {
  65227. handledEvents: ['tap'],
  65228. /**
  65229. * @member Ext.dom.Element
  65230. * @event tap
  65231. * Fires when you tap
  65232. * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
  65233. * @param {HTMLElement} node The target of the event.
  65234. * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
  65235. */
  65236. /**
  65237. * @member Ext.dom.Element
  65238. * @event touchstart
  65239. * Fires when touch starts.
  65240. * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
  65241. * @param {HTMLElement} node The target of the event.
  65242. * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
  65243. */
  65244. /**
  65245. * @member Ext.dom.Element
  65246. * @event tapstart
  65247. * @inheritdoc Ext.dom.Element#touchstart
  65248. * @deprecated 2.0.0 Please add listener to 'touchstart' event instead
  65249. */
  65250. /**
  65251. * @member Ext.dom.Element
  65252. * @event touchmove
  65253. * Fires when movement while touching.
  65254. * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
  65255. * @param {HTMLElement} node The target of the event.
  65256. * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
  65257. */
  65258. /**
  65259. * @member Ext.dom.Element
  65260. * @event tapcancel
  65261. * @inheritdoc Ext.dom.Element#touchmove
  65262. * @deprecated 2.0.0 Please add listener to 'touchmove' event instead
  65263. */
  65264. extend: 'Ext.event.recognizer.SingleTouch',
  65265. onTouchMove: function() {
  65266. return this.fail(this.self.TOUCH_MOVED);
  65267. },
  65268. onTouchEnd: function(e) {
  65269. var touch = e.changedTouches[0];
  65270. this.fire('tap', e, [touch]);
  65271. }
  65272. }, function() {
  65273. });
  65274. /**
  65275. * A event recognizer created to recognize vertical swipe movements.
  65276. *
  65277. * This is disabled by default in Sencha Touch as it has a performance impact when your application
  65278. * has vertical scrollers, plus, in most cases it is not very useful.
  65279. *
  65280. * If you wish to recognize vertical swipe movements in your application, please refer to the documentation of
  65281. * {@link Ext.event.recognizer.Recognizer} and {@link Ext#setup}.
  65282. *
  65283. * @private
  65284. */
  65285. Ext.define('Ext.event.recognizer.VerticalSwipe', {
  65286. extend: 'Ext.event.recognizer.Swipe',
  65287. onTouchStart: function(e) {
  65288. if (this.callParent(arguments) === false) {
  65289. return false;
  65290. }
  65291. var touch = e.changedTouches[0];
  65292. this.startTime = e.time;
  65293. this.startX = touch.pageX;
  65294. this.startY = touch.pageY;
  65295. },
  65296. onTouchMove: function(e) {
  65297. var touch = e.changedTouches[0],
  65298. x = touch.pageX,
  65299. absDeltaX = Math.abs(x - this.startX),
  65300. maxDuration = this.getMaxDuration(),
  65301. maxOffset = this.getMaxOffset(),
  65302. time = e.time;
  65303. if (time - this.startTime > maxDuration) {
  65304. return this.fail(this.self.MAX_DURATION_EXCEEDED);
  65305. }
  65306. if (absDeltaX > maxOffset) {
  65307. return this.fail(this.self.MAX_OFFSET_EXCEEDED);
  65308. }
  65309. },
  65310. onTouchEnd: function(e) {
  65311. if (this.onTouchMove(e) !== false) {
  65312. var touch = e.changedTouches[0],
  65313. y = touch.pageY,
  65314. deltaY = y - this.startY,
  65315. distance = Math.abs(deltaY),
  65316. duration = e.time - this.startTime,
  65317. minDistance = this.getMinDistance(),
  65318. direction;
  65319. if (distance < minDistance) {
  65320. return this.fail(this.self.DISTANCE_NOT_ENOUGH);
  65321. }
  65322. direction = (deltaY < 0) ? 'up' : 'down';
  65323. this.fire('swipe', e, [touch], {
  65324. touch: touch,
  65325. distance: distance,
  65326. duration: duration,
  65327. duration: duration
  65328. });
  65329. }
  65330. }
  65331. });
  65332. /**
  65333. * @aside guide forms
  65334. *
  65335. * The checkbox field is an enhanced version of the native browser checkbox and is great for enabling your user to
  65336. * choose one or more items from a set (for example choosing toppings for a pizza order). It works like any other
  65337. * {@link Ext.field.Field field} and is usually found in the context of a form:
  65338. *
  65339. * ## Example
  65340. *
  65341. * @example miniphone preview
  65342. * var form = Ext.create('Ext.form.Panel', {
  65343. * fullscreen: true,
  65344. * items: [
  65345. * {
  65346. * xtype: 'checkboxfield',
  65347. * name : 'tomato',
  65348. * label: 'Tomato',
  65349. * value: 'tomato',
  65350. * checked: true
  65351. * },
  65352. * {
  65353. * xtype: 'checkboxfield',
  65354. * name : 'salami',
  65355. * label: 'Salami'
  65356. * },
  65357. * {
  65358. * xtype: 'toolbar',
  65359. * docked: 'bottom',
  65360. * items: [
  65361. * { xtype: 'spacer' },
  65362. * {
  65363. * text: 'getValues',
  65364. * handler: function() {
  65365. * var form = Ext.ComponentQuery.query('formpanel')[0],
  65366. * values = form.getValues();
  65367. *
  65368. * Ext.Msg.alert(null,
  65369. * "Tomato: " + ((values.tomato) ? "yes" : "no") +
  65370. * "<br />Salami: " + ((values.salami) ? "yes" : "no")
  65371. * );
  65372. * }
  65373. * },
  65374. * { xtype: 'spacer' }
  65375. * ]
  65376. * }
  65377. * ]
  65378. * });
  65379. *
  65380. *
  65381. * The form above contains two check boxes - one for Tomato, one for Salami. We configured the Tomato checkbox to be
  65382. * checked immediately on load, and the Salami checkbox to be unchecked. We also specified an optional text
  65383. * {@link #value} that will be sent when we submit the form. We can get this value using the Form's
  65384. * {@link Ext.form.Panel#getValues getValues} function, or have it sent as part of the data that is sent when the
  65385. * form is submitted:
  65386. *
  65387. * form.getValues(); //contains a key called 'tomato' if the Tomato field is still checked
  65388. * form.submit(); //will send 'tomato' in the form submission data
  65389. *
  65390. */
  65391. Ext.define('Ext.field.Checkbox', {
  65392. extend: 'Ext.field.Field',
  65393. alternateClassName: 'Ext.form.Checkbox',
  65394. xtype: 'checkboxfield',
  65395. qsaLeftRe: /[\[]/g,
  65396. qsaRightRe: /[\]]/g,
  65397. isCheckbox: true,
  65398. /**
  65399. * @event change
  65400. * Fires just before the field blurs if the field value has changed.
  65401. * @param {Ext.field.Checkbox} this This field.
  65402. * @param {Boolean} newValue The new value.
  65403. * @param {Boolean} oldValue The original value.
  65404. */
  65405. /**
  65406. * @event check
  65407. * Fires when the checkbox is checked.
  65408. * @param {Ext.field.Checkbox} this This checkbox.
  65409. * @param {Ext.EventObject} e This event object.
  65410. */
  65411. /**
  65412. * @event uncheck
  65413. * Fires when the checkbox is unchecked.
  65414. * @param {Ext.field.Checkbox} this This checkbox.
  65415. * @param {Ext.EventObject} e This event object.
  65416. */
  65417. config: {
  65418. /**
  65419. * @cfg
  65420. * @inheritdoc
  65421. */
  65422. ui: 'checkbox',
  65423. /**
  65424. * @cfg {String} value The string value to submit if the item is in a checked state.
  65425. * @accessor
  65426. */
  65427. value: '',
  65428. /**
  65429. * @cfg {Boolean} checked `true` if the checkbox should render initially checked.
  65430. * @accessor
  65431. */
  65432. checked: false,
  65433. /**
  65434. * @cfg {Number} tabIndex
  65435. * @hide
  65436. */
  65437. tabIndex: -1,
  65438. /**
  65439. * @cfg
  65440. * @inheritdoc
  65441. */
  65442. component: {
  65443. xtype : 'input',
  65444. type : 'checkbox',
  65445. useMask : true,
  65446. cls : Ext.baseCSSPrefix + 'input-checkbox'
  65447. }
  65448. },
  65449. // @private
  65450. initialize: function() {
  65451. var me = this;
  65452. me.callParent();
  65453. me.getComponent().on({
  65454. scope: me,
  65455. order: 'before',
  65456. masktap: 'onMaskTap'
  65457. });
  65458. },
  65459. // @private
  65460. doInitValue: function() {
  65461. var me = this,
  65462. initialConfig = me.getInitialConfig();
  65463. // you can have a value or checked config, but checked get priority
  65464. if (initialConfig.hasOwnProperty('value')) {
  65465. me.originalState = initialConfig.value;
  65466. }
  65467. if (initialConfig.hasOwnProperty('checked')) {
  65468. me.originalState = initialConfig.checked;
  65469. }
  65470. me.callParent(arguments);
  65471. },
  65472. // @private
  65473. updateInputType: function(newInputType) {
  65474. var component = this.getComponent();
  65475. if (component) {
  65476. component.setType(newInputType);
  65477. }
  65478. },
  65479. // @private
  65480. updateName: function(newName) {
  65481. var component = this.getComponent();
  65482. if (component) {
  65483. component.setName(newName);
  65484. }
  65485. },
  65486. /**
  65487. * Returns the field checked value.
  65488. * @return {Mixed} The field value.
  65489. */
  65490. getChecked: function() {
  65491. // we need to get the latest value from the {@link #input} and then update the value
  65492. this._checked = this.getComponent().getChecked();
  65493. return this._checked;
  65494. },
  65495. /**
  65496. * Returns the submit value for the checkbox which can be used when submitting forms.
  65497. * @return {Boolean/String} value The value of {@link #value} or `true`, if {@link #checked}.
  65498. */
  65499. getSubmitValue: function() {
  65500. return (this.getChecked()) ? this._value || true : null;
  65501. },
  65502. setChecked: function(newChecked) {
  65503. this.updateChecked(newChecked);
  65504. this._checked = newChecked;
  65505. },
  65506. updateChecked: function(newChecked) {
  65507. this.getComponent().setChecked(newChecked);
  65508. // only call onChange (which fires events) if the component has been initialized
  65509. if (this.initialized) {
  65510. this.onChange();
  65511. }
  65512. },
  65513. // @private
  65514. onMaskTap: function(component, e) {
  65515. var me = this,
  65516. dom = component.input.dom;
  65517. if (me.getDisabled()) {
  65518. return false;
  65519. }
  65520. //we must manually update the input dom with the new checked value
  65521. dom.checked = !dom.checked;
  65522. me.onChange(e);
  65523. //return false so the mask does not disappear
  65524. return false;
  65525. },
  65526. /**
  65527. * Fires the `check` or `uncheck` event when the checked value of this component changes.
  65528. * @private
  65529. */
  65530. onChange: function(e) {
  65531. var me = this,
  65532. oldChecked = me._checked,
  65533. newChecked = me.getChecked();
  65534. // only fire the event when the value changes
  65535. if (oldChecked != newChecked) {
  65536. if (newChecked) {
  65537. me.fireEvent('check', me, e);
  65538. } else {
  65539. me.fireEvent('uncheck', me, e);
  65540. }
  65541. me.fireEvent('change', me, newChecked, oldChecked);
  65542. }
  65543. },
  65544. /**
  65545. * @method
  65546. * Method called when this {@link Ext.field.Checkbox} has been checked.
  65547. */
  65548. doChecked: Ext.emptyFn,
  65549. /**
  65550. * @method
  65551. * Method called when this {@link Ext.field.Checkbox} has been unchecked.
  65552. */
  65553. doUnChecked: Ext.emptyFn,
  65554. /**
  65555. * Returns the checked state of the checkbox.
  65556. * @return {Boolean} `true` if checked, `false` otherwise.
  65557. */
  65558. isChecked: function() {
  65559. return this.getChecked();
  65560. },
  65561. /**
  65562. * Set the checked state of the checkbox to `true`.
  65563. * @return {Ext.field.Checkbox} This checkbox.
  65564. */
  65565. check: function() {
  65566. return this.setChecked(true);
  65567. },
  65568. /**
  65569. * Set the checked state of the checkbox to `false`.
  65570. * @return {Ext.field.Checkbox} This checkbox.
  65571. */
  65572. uncheck: function() {
  65573. return this.setChecked(false);
  65574. },
  65575. getSameGroupFields: function() {
  65576. var component = this.up('formpanel') || this.up('fieldset'),
  65577. name = this.getName(),
  65578. replaceLeft = this.qsaLeftRe,
  65579. replaceRight = this.qsaRightRe,
  65580. components = [],
  65581. elements, element, i, ln;
  65582. if (!component) {
  65583. // <debug>
  65584. Ext.Logger.warn('Ext.field.Radio components must always be descendants of an Ext.form.Panel or Ext.form.FieldSet.');
  65585. // </debug>
  65586. component = Ext.Viewport;
  65587. }
  65588. // This is to handle ComponentQuery's lack of handling [name=foo[bar]] properly
  65589. name = name.replace(replaceLeft, '\\[');
  65590. name = name.replace(replaceRight, '\\]');
  65591. elements = Ext.query('[name=' + name + ']', component.element.dom);
  65592. ln = elements.length;
  65593. for (i = 0; i < ln; i++) {
  65594. element = elements[i];
  65595. element = Ext.fly(element).up('.x-field-' + element.getAttribute('type'));
  65596. if (element && element.id) {
  65597. components.push(Ext.getCmp(element.id));
  65598. }
  65599. }
  65600. return components;
  65601. },
  65602. /**
  65603. * Returns an array of values from the checkboxes in the group that are checked.
  65604. * @return {Array}
  65605. */
  65606. getGroupValues: function() {
  65607. var values = [];
  65608. this.getSameGroupFields().forEach(function(field) {
  65609. if (field.getChecked()) {
  65610. values.push(field.getValue());
  65611. }
  65612. });
  65613. return values;
  65614. },
  65615. /**
  65616. * Set the status of all matched checkboxes in the same group to checked.
  65617. * @param {Array} values An array of values.
  65618. * @return {Ext.field.Checkbox} This checkbox.
  65619. */
  65620. setGroupValues: function(values) {
  65621. this.getSameGroupFields().forEach(function(field) {
  65622. field.setChecked((values.indexOf(field.getValue()) !== -1));
  65623. });
  65624. return this;
  65625. },
  65626. /**
  65627. * Resets the status of all matched checkboxes in the same group to checked.
  65628. * @return {Ext.field.Checkbox} This checkbox.
  65629. */
  65630. resetGroupValues: function() {
  65631. this.getSameGroupFields().forEach(function(field) {
  65632. field.setChecked(field.originalState);
  65633. });
  65634. return this;
  65635. },
  65636. reset: function() {
  65637. this.setChecked(this.originalState);
  65638. return this;
  65639. }
  65640. });
  65641. /**
  65642. * @private
  65643. *
  65644. * A general {@link Ext.picker.Picker} slot class. Slots are used to organize multiple scrollable slots into
  65645. * a single {@link Ext.picker.Picker}.
  65646. *
  65647. * {
  65648. * name : 'limit_speed',
  65649. * title: 'Speed Limit',
  65650. * data : [
  65651. * {text: '50 KB/s', value: 50},
  65652. * {text: '100 KB/s', value: 100},
  65653. * {text: '200 KB/s', value: 200},
  65654. * {text: '300 KB/s', value: 300}
  65655. * ]
  65656. * }
  65657. *
  65658. * See the {@link Ext.picker.Picker} documentation on how to use slots.
  65659. */
  65660. Ext.define('Ext.picker.Slot', {
  65661. extend: 'Ext.dataview.DataView',
  65662. xtype : 'pickerslot',
  65663. alternateClassName: 'Ext.Picker.Slot',
  65664. requires: [
  65665. 'Ext.XTemplate',
  65666. 'Ext.data.Store',
  65667. 'Ext.Component',
  65668. 'Ext.data.StoreManager'
  65669. ],
  65670. /**
  65671. * @event slotpick
  65672. * Fires whenever an slot is picked
  65673. * @param {Ext.picker.Slot} this
  65674. * @param {Mixed} value The value of the pick
  65675. * @param {HTMLElement} node The node element of the pick
  65676. */
  65677. isSlot: true,
  65678. config: {
  65679. /**
  65680. * @cfg {String} title The title to use for this slot, or `null` for no title.
  65681. * @accessor
  65682. */
  65683. title: null,
  65684. /**
  65685. * @private
  65686. * @cfg {Boolean} showTitle
  65687. * @accessor
  65688. */
  65689. showTitle: true,
  65690. /**
  65691. * @private
  65692. * @cfg {String} cls The main component class
  65693. * @accessor
  65694. */
  65695. cls: Ext.baseCSSPrefix + 'picker-slot',
  65696. /**
  65697. * @cfg {String} name (required) The name of this slot.
  65698. * @accessor
  65699. */
  65700. name: null,
  65701. /**
  65702. * @cfg {Number} value The value of this slot
  65703. * @accessor
  65704. */
  65705. value: null,
  65706. /**
  65707. * @cfg {Number} flex
  65708. * @accessor
  65709. * @private
  65710. */
  65711. flex: 1,
  65712. /**
  65713. * @cfg {String} align The horizontal alignment of the slot's contents.
  65714. *
  65715. * Valid values are: "left", "center", and "right".
  65716. * @accessor
  65717. */
  65718. align: 'left',
  65719. /**
  65720. * @cfg {String} displayField The display field in the store.
  65721. * @accessor
  65722. */
  65723. displayField: 'text',
  65724. /**
  65725. * @cfg {String} valueField The value field in the store.
  65726. * @accessor
  65727. */
  65728. valueField: 'value',
  65729. /**
  65730. * @cfg {Object} scrollable
  65731. * @accessor
  65732. * @hide
  65733. */
  65734. scrollable: {
  65735. direction: 'vertical',
  65736. indicators: false,
  65737. momentumEasing: {
  65738. minVelocity: 2
  65739. },
  65740. slotSnapEasing: {
  65741. duration: 100
  65742. }
  65743. }
  65744. },
  65745. constructor: function() {
  65746. /**
  65747. * @property selectedIndex
  65748. * @type Number
  65749. * The current `selectedIndex` of the picker slot.
  65750. * @private
  65751. */
  65752. this.selectedIndex = 0;
  65753. /**
  65754. * @property picker
  65755. * @type Ext.picker.Picker
  65756. * A reference to the owner Picker.
  65757. * @private
  65758. */
  65759. this.callParent(arguments);
  65760. },
  65761. /**
  65762. * Sets the title for this dataview by creating element.
  65763. * @param {String} title
  65764. * @return {String}
  65765. */
  65766. applyTitle: function(title) {
  65767. //check if the title isnt defined
  65768. if (title) {
  65769. //create a new title element
  65770. title = Ext.create('Ext.Component', {
  65771. cls: Ext.baseCSSPrefix + 'picker-slot-title',
  65772. docked : 'top',
  65773. html : title
  65774. });
  65775. }
  65776. return title;
  65777. },
  65778. updateTitle: function(newTitle, oldTitle) {
  65779. if (newTitle) {
  65780. this.add(newTitle);
  65781. this.setupBar();
  65782. }
  65783. if (oldTitle) {
  65784. this.remove(oldTitle);
  65785. }
  65786. },
  65787. updateShowTitle: function(showTitle) {
  65788. var title = this.getTitle();
  65789. if (title) {
  65790. title[showTitle ? 'show' : 'hide']();
  65791. this.setupBar();
  65792. }
  65793. },
  65794. updateDisplayField: function(newDisplayField) {
  65795. this.setItemTpl('<div class="' + Ext.baseCSSPrefix + 'picker-item {cls} <tpl if="extra">' + Ext.baseCSSPrefix + 'picker-invalid</tpl>">{' + newDisplayField + '}</div>');
  65796. },
  65797. /**
  65798. * Updates the {@link #align} configuration
  65799. */
  65800. updateAlign: function(newAlign, oldAlign) {
  65801. var element = this.element;
  65802. element.addCls(Ext.baseCSSPrefix + 'picker-' + newAlign);
  65803. element.removeCls(Ext.baseCSSPrefix + 'picker-' + oldAlign);
  65804. },
  65805. /**
  65806. * Looks at the {@link #data} configuration and turns it into {@link #store}.
  65807. * @param {Object} data
  65808. * @return {Object}
  65809. */
  65810. applyData: function(data) {
  65811. var parsedData = [],
  65812. ln = data && data.length,
  65813. i, item, obj;
  65814. if (data && Ext.isArray(data) && ln) {
  65815. for (i = 0; i < ln; i++) {
  65816. item = data[i];
  65817. obj = {};
  65818. if (Ext.isArray(item)) {
  65819. obj[this.valueField] = item[0];
  65820. obj[this.displayField] = item[1];
  65821. }
  65822. else if (Ext.isString(item)) {
  65823. obj[this.valueField] = item;
  65824. obj[this.displayField] = item;
  65825. }
  65826. else if (Ext.isObject(item)) {
  65827. obj = item;
  65828. }
  65829. parsedData.push(obj);
  65830. }
  65831. }
  65832. return data;
  65833. },
  65834. updateData: function(data) {
  65835. this.setStore(Ext.create('Ext.data.Store', {
  65836. fields: ['text', 'value'],
  65837. data : data
  65838. }));
  65839. },
  65840. // @private
  65841. initialize: function() {
  65842. this.callParent();
  65843. var scroller = this.getScrollable().getScroller();
  65844. this.on({
  65845. scope: this,
  65846. painted: 'onPainted',
  65847. itemtap: 'doItemTap'
  65848. });
  65849. scroller.on({
  65850. scope: this,
  65851. scrollend: 'onScrollEnd'
  65852. });
  65853. },
  65854. // @private
  65855. onPainted: function() {
  65856. this.setupBar();
  65857. },
  65858. /**
  65859. * Returns an instance of the owner picker.
  65860. * @return {Object}
  65861. * @private
  65862. */
  65863. getPicker: function() {
  65864. if (!this.picker) {
  65865. this.picker = this.getParent();
  65866. }
  65867. return this.picker;
  65868. },
  65869. // @private
  65870. setupBar: function() {
  65871. if (!this.rendered) {
  65872. //if the component isnt rendered yet, there is no point in calculating the padding just eyt
  65873. return;
  65874. }
  65875. var element = this.element,
  65876. innerElement = this.innerElement,
  65877. picker = this.getPicker(),
  65878. bar = picker.bar,
  65879. value = this.getValue(),
  65880. showTitle = this.getShowTitle(),
  65881. title = this.getTitle(),
  65882. scrollable = this.getScrollable(),
  65883. scroller = scrollable.getScroller(),
  65884. titleHeight = 0,
  65885. barHeight, padding;
  65886. barHeight = bar.getHeight();
  65887. if (showTitle && title) {
  65888. titleHeight = title.element.getHeight();
  65889. }
  65890. padding = Math.ceil((element.getHeight() - titleHeight - barHeight) / 2);
  65891. innerElement.setStyle({
  65892. padding: padding + 'px 0 ' + (padding) + 'px'
  65893. });
  65894. scroller.refresh();
  65895. scroller.setSlotSnapSize(barHeight);
  65896. this.setValue(value);
  65897. },
  65898. // @private
  65899. doItemTap: function(list, index, item, e) {
  65900. var me = this;
  65901. me.selectedIndex = index;
  65902. me.selectedNode = item;
  65903. me.scrollToItem(item, true);
  65904. },
  65905. // @private
  65906. scrollToItem: function(item, animated) {
  65907. var y = item.getY(),
  65908. parentEl = item.parent(),
  65909. parentY = parentEl.getY(),
  65910. scrollView = this.getScrollable(),
  65911. scroller = scrollView.getScroller(),
  65912. difference;
  65913. difference = y - parentY;
  65914. scroller.scrollTo(0, difference, animated);
  65915. },
  65916. // @private
  65917. onScrollEnd: function(scroller, x, y) {
  65918. var me = this,
  65919. index = Math.round(y / me.picker.bar.getHeight()),
  65920. viewItems = me.getViewItems(),
  65921. item = viewItems[index];
  65922. if (item) {
  65923. me.selectedIndex = index;
  65924. me.selectedNode = item;
  65925. me.fireEvent('slotpick', me, me.getValue(), me.selectedNode);
  65926. }
  65927. },
  65928. /**
  65929. * Returns the value of this slot
  65930. * @private
  65931. */
  65932. getValue: function(useDom) {
  65933. var store = this.getStore(),
  65934. record, value;
  65935. if (!store) {
  65936. return;
  65937. }
  65938. if (!this.rendered || !useDom) {
  65939. return this._value;
  65940. }
  65941. //if the value is ever false, that means we do not want to return anything
  65942. if (this._value === false) {
  65943. return null;
  65944. }
  65945. record = store.getAt(this.selectedIndex);
  65946. value = record ? record.get(this.getValueField()) : null;
  65947. // this._value = value;
  65948. return value;
  65949. },
  65950. /**
  65951. * Sets the value of this slot
  65952. * @private
  65953. */
  65954. setValue: function(value) {
  65955. if (!Ext.isDefined(value)) {
  65956. return;
  65957. }
  65958. if (!this.rendered) {
  65959. //we don't want to call this until the slot has been rendered
  65960. this._value = value;
  65961. return;
  65962. }
  65963. var store = this.getStore(),
  65964. viewItems = this.getViewItems(),
  65965. valueField = this.getValueField(),
  65966. index, item;
  65967. index = store.findExact(valueField, value);
  65968. if (index != -1) {
  65969. item = Ext.get(viewItems[index]);
  65970. this.selectedIndex = index;
  65971. if (item) {
  65972. this.scrollToItem(item);
  65973. }
  65974. this._value = value;
  65975. }
  65976. },
  65977. /**
  65978. * Sets the value of this slot
  65979. * @private
  65980. */
  65981. setValueAnimated: function(value) {
  65982. if (!this.rendered) {
  65983. //we don't want to call this until the slot has been rendered
  65984. this._value = value;
  65985. return;
  65986. }
  65987. var store = this.getStore(),
  65988. viewItems = this.getViewItems(),
  65989. valueField = this.getValueField(),
  65990. index, item;
  65991. index = store.find(valueField, value);
  65992. if (index != -1) {
  65993. item = Ext.get(viewItems[index]);
  65994. this.selectedIndex = index;
  65995. if (item) {
  65996. this.scrollToItem(item, {
  65997. duration: 100
  65998. });
  65999. }
  66000. this._value = value;
  66001. }
  66002. }
  66003. });
  66004. /**
  66005. * @aside example pickers
  66006. * A general picker class. {@link Ext.picker.Slot}s are used to organize multiple scrollable slots into a single picker. {@link #slots} is
  66007. * the only necessary configuration.
  66008. *
  66009. * The {@link #slots} configuration with a few key values:
  66010. *
  66011. * - `name`: The name of the slot (will be the key when using {@link #getValues} in this {@link Ext.picker.Picker}).
  66012. * - `title`: The title of this slot (if {@link #useTitles} is set to `true`).
  66013. * - `data`/`store`: The data or store to use for this slot.
  66014. *
  66015. * Remember, {@link Ext.picker.Slot} class extends from {@link Ext.dataview.DataView}.
  66016. *
  66017. * ## Examples
  66018. *
  66019. * @example miniphone preview
  66020. * var picker = Ext.create('Ext.Picker', {
  66021. * slots: [
  66022. * {
  66023. * name : 'limit_speed',
  66024. * title: 'Speed',
  66025. * data : [
  66026. * {text: '50 KB/s', value: 50},
  66027. * {text: '100 KB/s', value: 100},
  66028. * {text: '200 KB/s', value: 200},
  66029. * {text: '300 KB/s', value: 300}
  66030. * ]
  66031. * }
  66032. * ]
  66033. * });
  66034. * Ext.Viewport.add(picker);
  66035. * picker.show();
  66036. *
  66037. * You can also customize the top toolbar on the {@link Ext.picker.Picker} by changing the {@link #doneButton} and {@link #cancelButton} configurations:
  66038. *
  66039. * @example miniphone preview
  66040. * var picker = Ext.create('Ext.Picker', {
  66041. * doneButton: 'I\'m done!',
  66042. * cancelButton: false,
  66043. * slots: [
  66044. * {
  66045. * name : 'limit_speed',
  66046. * title: 'Speed',
  66047. * data : [
  66048. * {text: '50 KB/s', value: 50},
  66049. * {text: '100 KB/s', value: 100},
  66050. * {text: '200 KB/s', value: 200},
  66051. * {text: '300 KB/s', value: 300}
  66052. * ]
  66053. * }
  66054. * ]
  66055. * });
  66056. * Ext.Viewport.add(picker);
  66057. * picker.show();
  66058. *
  66059. * Or by passing a custom {@link #toolbar} configuration:
  66060. *
  66061. * @example miniphone preview
  66062. * var picker = Ext.create('Ext.Picker', {
  66063. * doneButton: false,
  66064. * cancelButton: false,
  66065. * toolbar: {
  66066. * ui: 'light',
  66067. * title: 'My Picker!'
  66068. * },
  66069. * slots: [
  66070. * {
  66071. * name : 'limit_speed',
  66072. * title: 'Speed',
  66073. * data : [
  66074. * {text: '50 KB/s', value: 50},
  66075. * {text: '100 KB/s', value: 100},
  66076. * {text: '200 KB/s', value: 200},
  66077. * {text: '300 KB/s', value: 300}
  66078. * ]
  66079. * }
  66080. * ]
  66081. * });
  66082. * Ext.Viewport.add(picker);
  66083. * picker.show();
  66084. */
  66085. Ext.define('Ext.picker.Picker', {
  66086. extend: 'Ext.Sheet',
  66087. alias : 'widget.picker',
  66088. alternateClassName: 'Ext.Picker',
  66089. requires: ['Ext.picker.Slot', 'Ext.TitleBar', 'Ext.data.Model'],
  66090. isPicker: true,
  66091. /**
  66092. * @event pick
  66093. * Fired when a slot has been picked
  66094. * @param {Ext.Picker} this This Picker.
  66095. * @param {Object} The values of this picker's slots, in `{name:'value'}` format.
  66096. * @param {Ext.Picker.Slot} slot An instance of Ext.Picker.Slot that has been picked.
  66097. */
  66098. /**
  66099. * @event change
  66100. * Fired when the value of this picker has changed the Done button has been pressed.
  66101. * @param {Ext.picker.Picker} this This Picker.
  66102. * @param {Object} value The values of this picker's slots, in `{name:'value'}` format.
  66103. */
  66104. /**
  66105. * @event cancel
  66106. * Fired when the cancel button is tapped and the values are reverted back to
  66107. * what they were.
  66108. * @param {Ext.Picker} this This Picker.
  66109. */
  66110. config: {
  66111. /**
  66112. * @cfg
  66113. * @inheritdoc
  66114. */
  66115. cls: Ext.baseCSSPrefix + 'picker',
  66116. /**
  66117. * @cfg {String/Mixed} doneButton
  66118. * Can be either:
  66119. *
  66120. * - A {String} text to be used on the Done button.
  66121. * - An {Object} as config for {@link Ext.Button}.
  66122. * - `false` or `null` to hide it.
  66123. * @accessor
  66124. */
  66125. doneButton: true,
  66126. /**
  66127. * @cfg {String/Mixed} cancelButton
  66128. * Can be either:
  66129. *
  66130. * - A {String} text to be used on the Cancel button.
  66131. * - An {Object} as config for {@link Ext.Button}.
  66132. * - `false` or `null` to hide it.
  66133. * @accessor
  66134. */
  66135. cancelButton: true,
  66136. /**
  66137. * @cfg {Boolean} useTitles
  66138. * Generate a title header for each individual slot and use
  66139. * the title configuration of the slot.
  66140. * @accessor
  66141. */
  66142. useTitles: false,
  66143. /**
  66144. * @cfg {Array} slots
  66145. * An array of slot configurations.
  66146. *
  66147. * - `name` {String} - Name of the slot
  66148. * - `data` {Array} - An array of text/value pairs in the format `{text: 'myKey', value: 'myValue'}`
  66149. * - `title` {String} - Title of the slot. This is used in conjunction with `useTitles: true`.
  66150. *
  66151. * @accessor
  66152. */
  66153. slots: null,
  66154. /**
  66155. * @cfg {String/Number} value The value to initialize the picker with.
  66156. * @accessor
  66157. */
  66158. value: null,
  66159. /**
  66160. * @cfg {Number} height
  66161. * The height of the picker.
  66162. * @accessor
  66163. */
  66164. height: 220,
  66165. /**
  66166. * @cfg
  66167. * @inheritdoc
  66168. */
  66169. layout: {
  66170. type : 'hbox',
  66171. align: 'stretch'
  66172. },
  66173. /**
  66174. * @cfg
  66175. * @hide
  66176. */
  66177. centered: false,
  66178. /**
  66179. * @cfg
  66180. * @inheritdoc
  66181. */
  66182. left : 0,
  66183. /**
  66184. * @cfg
  66185. * @inheritdoc
  66186. */
  66187. right: 0,
  66188. /**
  66189. * @cfg
  66190. * @inheritdoc
  66191. */
  66192. bottom: 0,
  66193. // @private
  66194. defaultType: 'pickerslot',
  66195. /**
  66196. * @cfg {Ext.TitleBar/Ext.Toolbar/Object} toolbar
  66197. * The toolbar which contains the {@link #doneButton} and {@link #cancelButton} buttons.
  66198. * You can override this if you wish, and add your own configurations. Just ensure that you take into account
  66199. * the {@link #doneButton} and {@link #cancelButton} configurations.
  66200. *
  66201. * The default xtype is a {@link Ext.TitleBar}:
  66202. *
  66203. * toolbar: {
  66204. * items: [
  66205. * {
  66206. * xtype: 'button',
  66207. * text: 'Left',
  66208. * align: 'left'
  66209. * },
  66210. * {
  66211. * xtype: 'button',
  66212. * text: 'Right',
  66213. * align: 'left'
  66214. * }
  66215. * ]
  66216. * }
  66217. *
  66218. * Or to use a {@link Ext.Toolbar instead}:
  66219. *
  66220. * toolbar: {
  66221. * xtype: 'toolbar',
  66222. * items: [
  66223. * {
  66224. * xtype: 'button',
  66225. * text: 'Left'
  66226. * },
  66227. * {
  66228. * xtype: 'button',
  66229. * text: 'Left Two'
  66230. * }
  66231. * ]
  66232. * }
  66233. *
  66234. * @accessor
  66235. */
  66236. toolbar: true
  66237. },
  66238. initElement: function() {
  66239. this.callParent(arguments);
  66240. var me = this,
  66241. clsPrefix = Ext.baseCSSPrefix,
  66242. innerElement = this.innerElement;
  66243. //insert the mask, and the picker bar
  66244. this.mask = innerElement.createChild({
  66245. cls: clsPrefix + 'picker-mask'
  66246. });
  66247. this.bar = this.mask.createChild({
  66248. cls: clsPrefix + 'picker-bar'
  66249. });
  66250. me.on({
  66251. scope : this,
  66252. delegate: 'pickerslot',
  66253. slotpick: 'onSlotPick'
  66254. });
  66255. me.on({
  66256. scope: this,
  66257. show: 'onShow'
  66258. });
  66259. },
  66260. /**
  66261. * @private
  66262. */
  66263. applyToolbar: function(config) {
  66264. if (config === true) {
  66265. config = {};
  66266. }
  66267. Ext.applyIf(config, {
  66268. docked: 'top'
  66269. });
  66270. return Ext.factory(config, 'Ext.TitleBar', this.getToolbar());
  66271. },
  66272. /**
  66273. * @private
  66274. */
  66275. updateToolbar: function(newToolbar, oldToolbar) {
  66276. if (newToolbar) {
  66277. this.add(newToolbar);
  66278. }
  66279. if (oldToolbar) {
  66280. this.remove(oldToolbar);
  66281. }
  66282. },
  66283. /**
  66284. * Updates the {@link #doneButton} configuration. Will change it into a button when appropriate, or just update the text if needed.
  66285. * @param {Object} config
  66286. * @return {Object}
  66287. */
  66288. applyDoneButton: function(config) {
  66289. if (config) {
  66290. if (Ext.isBoolean(config)) {
  66291. config = {};
  66292. }
  66293. if (typeof config == "string") {
  66294. config = {
  66295. text: config
  66296. };
  66297. }
  66298. Ext.applyIf(config, {
  66299. ui: 'action',
  66300. align: 'right',
  66301. text: 'Done'
  66302. });
  66303. }
  66304. return Ext.factory(config, 'Ext.Button', this.getDoneButton());
  66305. },
  66306. updateDoneButton: function(newDoneButton, oldDoneButton) {
  66307. var toolbar = this.getToolbar();
  66308. if (newDoneButton) {
  66309. toolbar.add(newDoneButton);
  66310. newDoneButton.on('tap', this.onDoneButtonTap, this);
  66311. } else if (oldDoneButton) {
  66312. toolbar.remove(oldDoneButton);
  66313. }
  66314. },
  66315. /**
  66316. * Updates the {@link #cancelButton} configuration. Will change it into a button when appropriate, or just update the text if needed.
  66317. * @param {Object} config
  66318. * @return {Object}
  66319. */
  66320. applyCancelButton: function(config) {
  66321. if (config) {
  66322. if (Ext.isBoolean(config)) {
  66323. config = {};
  66324. }
  66325. if (typeof config == "string") {
  66326. config = {
  66327. text: config
  66328. };
  66329. }
  66330. Ext.applyIf(config, {
  66331. align: 'left',
  66332. text: 'Cancel'
  66333. });
  66334. }
  66335. return Ext.factory(config, 'Ext.Button', this.getCancelButton());
  66336. },
  66337. updateCancelButton: function(newCancelButton, oldCancelButton) {
  66338. var toolbar = this.getToolbar();
  66339. if (newCancelButton) {
  66340. toolbar.add(newCancelButton);
  66341. newCancelButton.on('tap', this.onCancelButtonTap, this);
  66342. } else if (oldCancelButton) {
  66343. toolbar.remove(oldCancelButton);
  66344. }
  66345. },
  66346. /**
  66347. * @private
  66348. */
  66349. updateUseTitles: function(useTitles) {
  66350. var innerItems = this.getInnerItems(),
  66351. ln = innerItems.length,
  66352. cls = Ext.baseCSSPrefix + 'use-titles',
  66353. i, innerItem;
  66354. //add a cls onto the picker
  66355. if (useTitles) {
  66356. this.addCls(cls);
  66357. } else {
  66358. this.removeCls(cls);
  66359. }
  66360. //show the time on each of the slots
  66361. for (i = 0; i < ln; i++) {
  66362. innerItem = innerItems[i];
  66363. if (innerItem.isSlot) {
  66364. innerItem.setShowTitle(useTitles);
  66365. }
  66366. }
  66367. },
  66368. applySlots: function(slots) {
  66369. //loop through each of the slots and add a reference to this picker
  66370. if (slots) {
  66371. var ln = slots.length,
  66372. i;
  66373. for (i = 0; i < ln; i++) {
  66374. slots[i].picker = this;
  66375. }
  66376. }
  66377. return slots;
  66378. },
  66379. /**
  66380. * Adds any new {@link #slots} to this picker, and removes existing {@link #slots}
  66381. * @private
  66382. */
  66383. updateSlots: function(newSlots) {
  66384. var bcss = Ext.baseCSSPrefix,
  66385. innerItems;
  66386. this.removeAll();
  66387. if (newSlots) {
  66388. this.add(newSlots);
  66389. }
  66390. innerItems = this.getInnerItems();
  66391. if (innerItems.length > 0) {
  66392. innerItems[0].addCls(bcss + 'first');
  66393. innerItems[innerItems.length - 1].addCls(bcss + 'last');
  66394. }
  66395. this.updateUseTitles(this.getUseTitles());
  66396. },
  66397. /**
  66398. * @private
  66399. * Called when the done button has been tapped.
  66400. */
  66401. onDoneButtonTap: function() {
  66402. var oldValue = this._value,
  66403. newValue = this.getValue(true);
  66404. if (newValue != oldValue) {
  66405. this.fireEvent('change', this, newValue);
  66406. }
  66407. this.hide();
  66408. },
  66409. /**
  66410. * @private
  66411. * Called when the cancel button has been tapped.
  66412. */
  66413. onCancelButtonTap: function() {
  66414. this.fireEvent('cancel', this);
  66415. this.hide();
  66416. },
  66417. /**
  66418. * @private
  66419. * Called when a slot has been picked.
  66420. */
  66421. onSlotPick: function(slot) {
  66422. this.fireEvent('pick', this, this.getValue(true), slot);
  66423. },
  66424. onShow: function() {
  66425. if (!this.isHidden()) {
  66426. this.setValue(this._value);
  66427. }
  66428. },
  66429. /**
  66430. * Sets the values of the pickers slots.
  66431. * @param {Object} values The values in a {name:'value'} format.
  66432. * @param {Boolean} animated `true` to animate setting the values.
  66433. * @return {Ext.Picker} this This picker.
  66434. */
  66435. setValue: function(values, animated) {
  66436. var me = this,
  66437. slots = me.getInnerItems(),
  66438. ln = slots.length,
  66439. key, slot, loopSlot, i, value;
  66440. if (!values) {
  66441. values = {};
  66442. for (i = 0; i < ln; i++) {
  66443. //set the value to false so the slot will return null when getValue is called
  66444. values[slots[i].config.name] = null;
  66445. }
  66446. }
  66447. for (key in values) {
  66448. slot = null;
  66449. value = values[key];
  66450. for (i = 0; i < slots.length; i++) {
  66451. loopSlot = slots[i];
  66452. if (loopSlot.config.name == key) {
  66453. slot = loopSlot;
  66454. break;
  66455. }
  66456. }
  66457. if (slot) {
  66458. if (animated) {
  66459. slot.setValueAnimated(value);
  66460. } else {
  66461. slot.setValue(value);
  66462. }
  66463. }
  66464. }
  66465. me._values = me._value = values;
  66466. return me;
  66467. },
  66468. setValueAnimated: function(values) {
  66469. this.setValue(values, true);
  66470. },
  66471. /**
  66472. * Returns the values of each of the pickers slots
  66473. * @return {Object} The values of the pickers slots
  66474. */
  66475. getValue: function(useDom) {
  66476. var values = {},
  66477. items = this.getItems().items,
  66478. ln = items.length,
  66479. item, i;
  66480. if (useDom) {
  66481. for (i = 0; i < ln; i++) {
  66482. item = items[i];
  66483. if (item && item.isSlot) {
  66484. values[item.getName()] = item.getValue(useDom);
  66485. }
  66486. }
  66487. this._values = values;
  66488. }
  66489. return this._values;
  66490. },
  66491. /**
  66492. * Returns the values of each of the pickers slots.
  66493. * @return {Object} The values of the pickers slots.
  66494. */
  66495. getValues: function() {
  66496. return this.getValue();
  66497. },
  66498. destroy: function() {
  66499. this.callParent();
  66500. Ext.destroy(this.mask, this.bar);
  66501. }
  66502. }, function() {
  66503. });
  66504. /**
  66505. * A date picker component which shows a Date Picker on the screen. This class extends from {@link Ext.picker.Picker}
  66506. * and {@link Ext.Sheet} so it is a popup.
  66507. *
  66508. * This component has no required configurations.
  66509. *
  66510. * ## Examples
  66511. *
  66512. * @example miniphone preview
  66513. * var datePicker = Ext.create('Ext.picker.Date');
  66514. * Ext.Viewport.add(datePicker);
  66515. * datePicker.show();
  66516. *
  66517. * You may want to adjust the {@link #yearFrom} and {@link #yearTo} properties:
  66518. *
  66519. * @example miniphone preview
  66520. * var datePicker = Ext.create('Ext.picker.Date', {
  66521. * yearFrom: 2000,
  66522. * yearTo : 2015
  66523. * });
  66524. * Ext.Viewport.add(datePicker);
  66525. * datePicker.show();
  66526. *
  66527. * You can set the value of the {@link Ext.picker.Date} to the current date using `new Date()`:
  66528. *
  66529. * @example miniphone preview
  66530. * var datePicker = Ext.create('Ext.picker.Date', {
  66531. * value: new Date()
  66532. * });
  66533. * Ext.Viewport.add(datePicker);
  66534. * datePicker.show();
  66535. *
  66536. * And you can hide the titles from each of the slots by using the {@link #useTitles} configuration:
  66537. *
  66538. * @example miniphone preview
  66539. * var datePicker = Ext.create('Ext.picker.Date', {
  66540. * useTitles: false
  66541. * });
  66542. * Ext.Viewport.add(datePicker);
  66543. * datePicker.show();
  66544. */
  66545. Ext.define('Ext.picker.Date', {
  66546. extend: 'Ext.picker.Picker',
  66547. xtype: 'datepicker',
  66548. alternateClassName: 'Ext.DatePicker',
  66549. requires: ['Ext.DateExtras'],
  66550. /**
  66551. * @event change
  66552. * Fired when the value of this picker has changed and the done button is pressed.
  66553. * @param {Ext.picker.Date} this This Picker
  66554. * @param {Date} value The date value
  66555. */
  66556. config: {
  66557. /**
  66558. * @cfg {Number} yearFrom
  66559. * The start year for the date picker. If {@link #yearFrom} is greater than
  66560. * {@link #yearTo} then the order of years will be reversed.
  66561. * @accessor
  66562. */
  66563. yearFrom: 1980,
  66564. /**
  66565. * @cfg {Number} [yearTo=new Date().getFullYear()]
  66566. * The last year for the date picker. If {@link #yearFrom} is greater than
  66567. * {@link #yearTo} then the order of years will be reversed.
  66568. * @accessor
  66569. */
  66570. yearTo: new Date().getFullYear(),
  66571. /**
  66572. * @cfg {String} monthText
  66573. * The label to show for the month column.
  66574. * @accessor
  66575. */
  66576. monthText: 'Month',
  66577. /**
  66578. * @cfg {String} dayText
  66579. * The label to show for the day column.
  66580. * @accessor
  66581. */
  66582. dayText: 'Day',
  66583. /**
  66584. * @cfg {String} yearText
  66585. * The label to show for the year column.
  66586. * @accessor
  66587. */
  66588. yearText: 'Year',
  66589. /**
  66590. * @cfg {Array} slotOrder
  66591. * An array of strings that specifies the order of the slots.
  66592. * @accessor
  66593. */
  66594. slotOrder: ['month', 'day', 'year']
  66595. /**
  66596. * @cfg {Object/Date} value
  66597. * Default value for the field and the internal {@link Ext.picker.Date} component. Accepts an object of 'year',
  66598. * 'month' and 'day' values, all of which should be numbers, or a {@link Date}.
  66599. *
  66600. * Examples:
  66601. *
  66602. * - `{year: 1989, day: 1, month: 5}` = 1st May 1989
  66603. * - `new Date()` = current date
  66604. * @accessor
  66605. */
  66606. /**
  66607. * @cfg {Array} slots
  66608. * @hide
  66609. * @accessor
  66610. */
  66611. },
  66612. initialize: function() {
  66613. this.callParent();
  66614. this.on({
  66615. scope: this,
  66616. delegate: '> slot',
  66617. slotpick: this.onSlotPick
  66618. });
  66619. this.on({
  66620. scope: this,
  66621. show: this.onSlotPick
  66622. });
  66623. },
  66624. setValue: function(value, animated) {
  66625. if (Ext.isDate(value)) {
  66626. value = {
  66627. day : value.getDate(),
  66628. month: value.getMonth() + 1,
  66629. year : value.getFullYear()
  66630. };
  66631. }
  66632. this.callParent([value, animated]);
  66633. },
  66634. getValue: function(useDom) {
  66635. var values = {},
  66636. items = this.getItems().items,
  66637. ln = items.length,
  66638. daysInMonth, day, month, year, item, i;
  66639. for (i = 0; i < ln; i++) {
  66640. item = items[i];
  66641. if (item instanceof Ext.picker.Slot) {
  66642. values[item.getName()] = item.getValue(useDom);
  66643. }
  66644. }
  66645. //if all the slots return null, we should not return a date
  66646. if (values.year === null && values.month === null && values.day === null) {
  66647. return null;
  66648. }
  66649. year = Ext.isNumber(values.year) ? values.year : 1;
  66650. month = Ext.isNumber(values.month) ? values.month : 1;
  66651. day = Ext.isNumber(values.day) ? values.day : 1;
  66652. if (month && year && month && day) {
  66653. daysInMonth = this.getDaysInMonth(month, year);
  66654. }
  66655. day = (daysInMonth) ? Math.min(day, daysInMonth): day;
  66656. return new Date(year, month - 1, day);
  66657. },
  66658. /**
  66659. * Updates the yearFrom configuration
  66660. */
  66661. updateYearFrom: function() {
  66662. if (this.initialized) {
  66663. this.createSlots();
  66664. }
  66665. },
  66666. /**
  66667. * Updates the yearTo configuration
  66668. */
  66669. updateYearTo: function() {
  66670. if (this.initialized) {
  66671. this.createSlots();
  66672. }
  66673. },
  66674. /**
  66675. * Updates the monthText configuration
  66676. */
  66677. updateMonthText: function(newMonthText, oldMonthText) {
  66678. var innerItems = this.getInnerItems,
  66679. ln = innerItems.length,
  66680. item, i;
  66681. //loop through each of the current items and set the title on the correct slice
  66682. if (this.initialized) {
  66683. for (i = 0; i < ln; i++) {
  66684. item = innerItems[i];
  66685. if ((typeof item.title == "string" && item.title == oldMonthText) || (item.title.html == oldMonthText)) {
  66686. item.setTitle(newMonthText);
  66687. }
  66688. }
  66689. }
  66690. },
  66691. /**
  66692. * Updates the {@link #dayText} configuration.
  66693. */
  66694. updateDayText: function(newDayText, oldDayText) {
  66695. var innerItems = this.getInnerItems,
  66696. ln = innerItems.length,
  66697. item, i;
  66698. //loop through each of the current items and set the title on the correct slice
  66699. if (this.initialized) {
  66700. for (i = 0; i < ln; i++) {
  66701. item = innerItems[i];
  66702. if ((typeof item.title == "string" && item.title == oldDayText) || (item.title.html == oldDayText)) {
  66703. item.setTitle(newDayText);
  66704. }
  66705. }
  66706. }
  66707. },
  66708. /**
  66709. * Updates the yearText configuration
  66710. */
  66711. updateYearText: function(yearText) {
  66712. var innerItems = this.getInnerItems,
  66713. ln = innerItems.length,
  66714. item, i;
  66715. //loop through each of the current items and set the title on the correct slice
  66716. if (this.initialized) {
  66717. for (i = 0; i < ln; i++) {
  66718. item = innerItems[i];
  66719. if (item.title == this.yearText) {
  66720. item.setTitle(yearText);
  66721. }
  66722. }
  66723. }
  66724. },
  66725. // @private
  66726. constructor: function() {
  66727. this.callParent(arguments);
  66728. this.createSlots();
  66729. },
  66730. /**
  66731. * Generates all slots for all years specified by this component, and then sets them on the component
  66732. * @private
  66733. */
  66734. createSlots: function() {
  66735. var me = this,
  66736. slotOrder = me.getSlotOrder(),
  66737. yearsFrom = me.getYearFrom(),
  66738. yearsTo = me.getYearTo(),
  66739. years = [],
  66740. days = [],
  66741. months = [],
  66742. reverse = yearsFrom > yearsTo,
  66743. ln, i, daysInMonth;
  66744. while (yearsFrom) {
  66745. years.push({
  66746. text : yearsFrom,
  66747. value : yearsFrom
  66748. });
  66749. if (yearsFrom === yearsTo) {
  66750. break;
  66751. }
  66752. if (reverse) {
  66753. yearsFrom--;
  66754. } else {
  66755. yearsFrom++;
  66756. }
  66757. }
  66758. daysInMonth = me.getDaysInMonth(1, new Date().getFullYear());
  66759. for (i = 0; i < daysInMonth; i++) {
  66760. days.push({
  66761. text : i + 1,
  66762. value : i + 1
  66763. });
  66764. }
  66765. for (i = 0, ln = Ext.Date.monthNames.length; i < ln; i++) {
  66766. months.push({
  66767. text : Ext.Date.monthNames[i],
  66768. value : i + 1
  66769. });
  66770. }
  66771. var slots = [];
  66772. slotOrder.forEach(function (item) {
  66773. slots.push(me.createSlot(item, days, months, years));
  66774. });
  66775. me.setSlots(slots);
  66776. },
  66777. /**
  66778. * Returns a slot config for a specified date.
  66779. * @private
  66780. */
  66781. createSlot: function(name, days, months, years) {
  66782. switch (name) {
  66783. case 'year':
  66784. return {
  66785. name: 'year',
  66786. align: 'center',
  66787. data: years,
  66788. title: this.getYearText(),
  66789. flex: 3
  66790. };
  66791. case 'month':
  66792. return {
  66793. name: name,
  66794. align: 'right',
  66795. data: months,
  66796. title: this.getMonthText(),
  66797. flex: 4
  66798. };
  66799. case 'day':
  66800. return {
  66801. name: 'day',
  66802. align: 'center',
  66803. data: days,
  66804. title: this.getDayText(),
  66805. flex: 2
  66806. };
  66807. }
  66808. },
  66809. onSlotPick: function() {
  66810. var value = this.getValue(true),
  66811. slot = this.getDaySlot(),
  66812. year = value.getFullYear(),
  66813. month = value.getMonth(),
  66814. days = [],
  66815. daysInMonth, i;
  66816. if (!value || !Ext.isDate(value) || !slot) {
  66817. return;
  66818. }
  66819. this.callParent();
  66820. //get the new days of the month for this new date
  66821. daysInMonth = this.getDaysInMonth(month + 1, year);
  66822. for (i = 0; i < daysInMonth; i++) {
  66823. days.push({
  66824. text: i + 1,
  66825. value: i + 1
  66826. });
  66827. }
  66828. // We don't need to update the slot days unless it has changed
  66829. if (slot.getData().length == days.length) {
  66830. return;
  66831. }
  66832. slot.setData(days);
  66833. // Now we have the correct amount of days for the day slot, lets update it
  66834. var store = slot.getStore(),
  66835. viewItems = slot.getViewItems(),
  66836. valueField = slot.getValueField(),
  66837. index, item;
  66838. index = store.find(valueField, value.getDate());
  66839. if (index == -1) {
  66840. return;
  66841. }
  66842. item = Ext.get(viewItems[index]);
  66843. slot.selectedIndex = index;
  66844. slot.scrollToItem(item);
  66845. // slot._value = value;
  66846. },
  66847. getDaySlot: function() {
  66848. var innerItems = this.getInnerItems(),
  66849. ln = innerItems.length,
  66850. i, slot;
  66851. if (this.daySlot) {
  66852. return this.daySlot;
  66853. }
  66854. for (i = 0; i < ln; i++) {
  66855. slot = innerItems[i];
  66856. if (slot.isSlot && slot.getName() == "day") {
  66857. this.daySlot = slot;
  66858. return slot;
  66859. }
  66860. }
  66861. return null;
  66862. },
  66863. // @private
  66864. getDaysInMonth: function(month, year) {
  66865. var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  66866. return month == 2 && this.isLeapYear(year) ? 29 : daysInMonth[month-1];
  66867. },
  66868. // @private
  66869. isLeapYear: function(year) {
  66870. return !!((year & 3) === 0 && (year % 100 || (year % 400 === 0 && year)));
  66871. },
  66872. onDoneButtonTap: function() {
  66873. var oldValue = this._value,
  66874. newValue = this.getValue(true),
  66875. testValue = newValue;
  66876. if (Ext.isDate(newValue)) {
  66877. testValue = newValue.toDateString();
  66878. }
  66879. if (Ext.isDate(oldValue)) {
  66880. oldValue = oldValue.toDateString();
  66881. }
  66882. if (testValue != oldValue) {
  66883. this.fireEvent('change', this, newValue);
  66884. }
  66885. this.hide();
  66886. }
  66887. });
  66888. /**
  66889. * @aside guide forms
  66890. *
  66891. * This is a specialized field which shows a {@link Ext.picker.Date} when tapped. If it has a predefined value,
  66892. * or a value is selected in the {@link Ext.picker.Date}, it will be displayed like a normal {@link Ext.field.Text}
  66893. * (but not selectable/changable).
  66894. *
  66895. * Ext.create('Ext.field.DatePicker', {
  66896. * label: 'Birthday',
  66897. * value: new Date()
  66898. * });
  66899. *
  66900. * {@link Ext.field.DatePicker} fields are very simple to implement, and have no required configurations.
  66901. *
  66902. * ## Examples
  66903. *
  66904. * It can be very useful to set a default {@link #value} configuration on {@link Ext.field.DatePicker} fields. In
  66905. * this example, we set the {@link #value} to be the current date. You can also use the {@link #setValue} method to
  66906. * update the value at any time.
  66907. *
  66908. * @example miniphone preview
  66909. * Ext.create('Ext.form.Panel', {
  66910. * fullscreen: true,
  66911. * items: [
  66912. * {
  66913. * xtype: 'fieldset',
  66914. * items: [
  66915. * {
  66916. * xtype: 'datepickerfield',
  66917. * label: 'Birthday',
  66918. * name: 'birthday',
  66919. * value: new Date()
  66920. * }
  66921. * ]
  66922. * },
  66923. * {
  66924. * xtype: 'toolbar',
  66925. * docked: 'bottom',
  66926. * items: [
  66927. * { xtype: 'spacer' },
  66928. * {
  66929. * text: 'setValue',
  66930. * handler: function() {
  66931. * var datePickerField = Ext.ComponentQuery.query('datepickerfield')[0];
  66932. *
  66933. * var randomNumber = function(from, to) {
  66934. * return Math.floor(Math.random() * (to - from + 1) + from);
  66935. * };
  66936. *
  66937. * datePickerField.setValue({
  66938. * month: randomNumber(0, 11),
  66939. * day : randomNumber(0, 28),
  66940. * year : randomNumber(1980, 2011)
  66941. * });
  66942. * }
  66943. * },
  66944. * { xtype: 'spacer' }
  66945. * ]
  66946. * }
  66947. * ]
  66948. * });
  66949. *
  66950. * When you need to retrieve the date from the {@link Ext.field.DatePicker}, you can either use the {@link #getValue} or
  66951. * {@link #getFormattedValue} methods:
  66952. *
  66953. * @example preview
  66954. * Ext.create('Ext.form.Panel', {
  66955. * fullscreen: true,
  66956. * items: [
  66957. * {
  66958. * xtype: 'fieldset',
  66959. * items: [
  66960. * {
  66961. * xtype: 'datepickerfield',
  66962. * label: 'Birthday',
  66963. * name: 'birthday',
  66964. * value: new Date()
  66965. * }
  66966. * ]
  66967. * },
  66968. * {
  66969. * xtype: 'toolbar',
  66970. * docked: 'bottom',
  66971. * items: [
  66972. * {
  66973. * text: 'getValue',
  66974. * handler: function() {
  66975. * var datePickerField = Ext.ComponentQuery.query('datepickerfield')[0];
  66976. * Ext.Msg.alert(null, datePickerField.getValue());
  66977. * }
  66978. * },
  66979. * { xtype: 'spacer' },
  66980. * {
  66981. * text: 'getFormattedValue',
  66982. * handler: function() {
  66983. * var datePickerField = Ext.ComponentQuery.query('datepickerfield')[0];
  66984. * Ext.Msg.alert(null, datePickerField.getFormattedValue());
  66985. * }
  66986. * }
  66987. * ]
  66988. * }
  66989. * ]
  66990. * });
  66991. *
  66992. *
  66993. */
  66994. Ext.define('Ext.field.DatePicker', {
  66995. extend: 'Ext.field.Text',
  66996. alternateClassName: 'Ext.form.DatePicker',
  66997. xtype: 'datepickerfield',
  66998. requires: [
  66999. 'Ext.picker.Date',
  67000. 'Ext.DateExtras'
  67001. ],
  67002. /**
  67003. * @event change
  67004. * Fires when a date is selected
  67005. * @param {Ext.field.DatePicker} this
  67006. * @param {Date} newDate The new date
  67007. * @param {Date} oldDate The old date
  67008. */
  67009. config: {
  67010. ui: 'select',
  67011. /**
  67012. * @cfg {Object/Ext.picker.Date} picker
  67013. * An object that is used when creating the internal {@link Ext.picker.Date} component or a direct instance of {@link Ext.picker.Date}.
  67014. * @accessor
  67015. */
  67016. picker: true,
  67017. /**
  67018. * @cfg {Boolean}
  67019. * @hide
  67020. * @accessor
  67021. */
  67022. clearIcon: false,
  67023. /**
  67024. * @cfg {Object/Date} value
  67025. * Default value for the field and the internal {@link Ext.picker.Date} component. Accepts an object of 'year',
  67026. * 'month' and 'day' values, all of which should be numbers, or a {@link Date}.
  67027. *
  67028. * Example: {year: 1989, day: 1, month: 5} = 1st May 1989 or new Date()
  67029. * @accessor
  67030. */
  67031. /**
  67032. * @cfg {Boolean} destroyPickerOnHide
  67033. * Whether or not to destroy the picker widget on hide. This save memory if it's not used frequently,
  67034. * but increase delay time on the next show due to re-instantiation.
  67035. * @accessor
  67036. */
  67037. destroyPickerOnHide: false,
  67038. /**
  67039. * @cfg {String} [dateFormat=Ext.util.Format.defaultDateFormat] The format to be used when displaying the date in this field.
  67040. * Accepts any valid date format. You can view formats over in the {@link Ext.Date} documentation.
  67041. */
  67042. dateFormat: null,
  67043. /**
  67044. * @cfg {Object}
  67045. * @hide
  67046. */
  67047. component: {
  67048. useMask: true
  67049. }
  67050. },
  67051. initialize: function() {
  67052. var me = this,
  67053. component = me.getComponent();
  67054. me.callParent();
  67055. component.on({
  67056. scope: me,
  67057. masktap: 'onMaskTap'
  67058. });
  67059. if (Ext.os.is.Android2) {
  67060. component.input.dom.disabled = true;
  67061. }
  67062. },
  67063. syncEmptyCls: Ext.emptyFn,
  67064. applyValue: function(value) {
  67065. if (!Ext.isDate(value) && !Ext.isObject(value)) {
  67066. return null;
  67067. }
  67068. if (Ext.isObject(value)) {
  67069. return new Date(value.year, value.month - 1, value.day);
  67070. }
  67071. return value;
  67072. },
  67073. updateValue: function(newValue, oldValue) {
  67074. var me = this,
  67075. picker = me._picker;
  67076. if (picker && picker.isPicker) {
  67077. picker.setValue(newValue);
  67078. }
  67079. // Ext.Date.format expects a Date
  67080. if (newValue !== null) {
  67081. me.getComponent().setValue(Ext.Date.format(newValue, me.getDateFormat() || Ext.util.Format.defaultDateFormat));
  67082. } else {
  67083. me.getComponent().setValue('');
  67084. }
  67085. if (newValue !== oldValue) {
  67086. me.fireEvent('change', me, newValue, oldValue);
  67087. }
  67088. },
  67089. /**
  67090. * Updates the date format in the field.
  67091. * @private
  67092. */
  67093. updateDateFormat: function(newDateFormat, oldDateFormat) {
  67094. var value = this.getValue();
  67095. if (newDateFormat != oldDateFormat && Ext.isDate(value)) {
  67096. this.getComponent().setValue(Ext.Date.format(value, newDateFormat || Ext.util.Format.defaultDateFormat));
  67097. }
  67098. },
  67099. /**
  67100. * Returns the {@link Date} value of this field.
  67101. * If you wanted a formated date
  67102. * @return {Date} The date selected
  67103. */
  67104. getValue: function() {
  67105. if (this._picker && this._picker instanceof Ext.picker.Date) {
  67106. return this._picker.getValue();
  67107. }
  67108. return this._value;
  67109. },
  67110. /**
  67111. * Returns the value of the field formatted using the specified format. If it is not specified, it will default to
  67112. * {@link #dateFormat} and then {@link Ext.util.Format#defaultDateFormat}.
  67113. * @param {String} format The format to be returned.
  67114. * @return {String} The formatted date.
  67115. */
  67116. getFormattedValue: function(format) {
  67117. var value = this.getValue();
  67118. return (Ext.isDate(value)) ? Ext.Date.format(value, format || this.getDateFormat() || Ext.util.Format.defaultDateFormat) : value;
  67119. },
  67120. applyPicker: function(picker, pickerInstance) {
  67121. if (pickerInstance && pickerInstance.isPicker) {
  67122. picker = pickerInstance.setConfig(picker);
  67123. }
  67124. return picker;
  67125. },
  67126. getPicker: function() {
  67127. var picker = this._picker,
  67128. value = this.getValue();
  67129. if (picker && !picker.isPicker) {
  67130. picker = Ext.factory(picker, Ext.picker.Date);
  67131. if (value != null) {
  67132. picker.setValue(value);
  67133. }
  67134. }
  67135. picker.on({
  67136. scope: this,
  67137. change: 'onPickerChange',
  67138. hide : 'onPickerHide'
  67139. });
  67140. Ext.Viewport.add(picker);
  67141. this._picker = picker;
  67142. return picker;
  67143. },
  67144. /**
  67145. * @private
  67146. * Listener to the tap event of the mask element. Shows the internal DatePicker component when the button has been tapped.
  67147. */
  67148. onMaskTap: function() {
  67149. if (this.getDisabled()) {
  67150. return false;
  67151. }
  67152. this.onFocus();
  67153. return false;
  67154. },
  67155. /**
  67156. * Called when the picker changes its value.
  67157. * @param {Ext.picker.Date} picker The date picker.
  67158. * @param {Object} value The new value from the date picker.
  67159. * @private
  67160. */
  67161. onPickerChange: function(picker, value) {
  67162. var me = this,
  67163. oldValue = me.getValue();
  67164. me.setValue(value);
  67165. me.fireEvent('select', me, value);
  67166. me.onChange(me, value, oldValue);
  67167. },
  67168. /**
  67169. * Override this or change event will be fired twice. change event is fired in updateValue
  67170. * for this field. TOUCH-2861
  67171. */
  67172. onChange: Ext.emptyFn,
  67173. /**
  67174. * Destroys the picker when it is hidden, if
  67175. * {@link Ext.field.DatePicker#destroyPickerOnHide destroyPickerOnHide} is set to `true`.
  67176. * @private
  67177. */
  67178. onPickerHide: function() {
  67179. var me = this,
  67180. picker = me.getPicker();
  67181. if (me.getDestroyPickerOnHide() && picker) {
  67182. picker.destroy();
  67183. me._picker = me.getInitialConfig().picker || true;
  67184. }
  67185. },
  67186. reset: function() {
  67187. this.setValue(this.originalValue);
  67188. },
  67189. onFocus: function(e) {
  67190. var component = this.getComponent();
  67191. this.fireEvent('focus', this, e);
  67192. if (Ext.os.is.Android4) {
  67193. component.input.dom.focus();
  67194. }
  67195. component.input.dom.blur();
  67196. if (this.getReadOnly()) {
  67197. return false;
  67198. }
  67199. this.isFocused = true;
  67200. this.getPicker().show();
  67201. },
  67202. // @private
  67203. destroy: function() {
  67204. var picker = this._picker;
  67205. if (picker && picker.isPicker) {
  67206. picker.destroy();
  67207. }
  67208. this.callParent(arguments);
  67209. }
  67210. });
  67211. /**
  67212. * @aside guide forms
  67213. *
  67214. * The Email field creates an HTML5 email input and is usually created inside a form. Because it creates an HTML email
  67215. * input field, most browsers will show a specialized virtual keyboard for email address input. Aside from that, the
  67216. * email field is just a normal text field. Here's an example of how to use it in a form:
  67217. *
  67218. * @example
  67219. * Ext.create('Ext.form.Panel', {
  67220. * fullscreen: true,
  67221. * items: [
  67222. * {
  67223. * xtype: 'fieldset',
  67224. * title: 'Register',
  67225. * items: [
  67226. * {
  67227. * xtype: 'emailfield',
  67228. * label: 'Email',
  67229. * name: 'email'
  67230. * },
  67231. * {
  67232. * xtype: 'passwordfield',
  67233. * label: 'Password',
  67234. * name: 'password'
  67235. * }
  67236. * ]
  67237. * }
  67238. * ]
  67239. * });
  67240. *
  67241. * Or on its own, outside of a form:
  67242. *
  67243. * Ext.create('Ext.field.Email', {
  67244. * label: 'Email address',
  67245. * value: 'prefilled@email.com'
  67246. * });
  67247. *
  67248. * Because email field inherits from {@link Ext.field.Text textfield} it gains all of the functionality that text fields
  67249. * provide, including getting and setting the value at runtime, validations and various events that are fired as the
  67250. * user interacts with the component. Check out the {@link Ext.field.Text} docs to see the additional functionality
  67251. * available.
  67252. */
  67253. Ext.define('Ext.field.Email', {
  67254. extend: 'Ext.field.Text',
  67255. alternateClassName: 'Ext.form.Email',
  67256. xtype: 'emailfield',
  67257. config: {
  67258. /**
  67259. * @cfg
  67260. * @inheritdoc
  67261. */
  67262. component: {
  67263. type: 'email'
  67264. },
  67265. /**
  67266. * @cfg
  67267. * @inheritdoc
  67268. */
  67269. autoCapitalize: false
  67270. }
  67271. });
  67272. /**
  67273. * @aside guide forms
  67274. *
  67275. * Hidden fields allow you to easily inject additional data into a {@link Ext.form.Panel form} without displaying
  67276. * additional fields on the screen. This is often useful for sending dynamic or previously collected data back to the
  67277. * server in the same request as the normal form submission. For example, here is how we might set up a form to send
  67278. * back a hidden userId field:
  67279. *
  67280. * @example
  67281. * var form = Ext.create('Ext.form.Panel', {
  67282. * fullscreen: true,
  67283. * items: [
  67284. * {
  67285. * xtype: 'fieldset',
  67286. * title: 'Enter your name',
  67287. * items: [
  67288. * {
  67289. * xtype: 'hiddenfield',
  67290. * name: 'userId',
  67291. * value: 123
  67292. * },
  67293. * {
  67294. * xtype: 'checkboxfield',
  67295. * label: 'Enable notifications',
  67296. * name: 'notifications'
  67297. * }
  67298. * ]
  67299. * }
  67300. * ]
  67301. * });
  67302. *
  67303. * In the form above we created two fields - a hidden field and a {@link Ext.field.Checkbox check box field}. Only the
  67304. * check box will be visible, but both fields will be submitted. Hidden fields cannot be tabbed to - they are removed
  67305. * from the tab index so when your user taps the next/previous field buttons the hidden field is skipped over.
  67306. *
  67307. * It's easy to read and update the value of a hidden field within a form. Using the example above, we can get a
  67308. * reference to the hidden field and then set it to a new value in 2 lines of code:
  67309. *
  67310. * var userId = form.down('hiddenfield')[0];
  67311. * userId.setValue(1234);
  67312. */
  67313. Ext.define('Ext.field.Hidden', {
  67314. extend: 'Ext.field.Text',
  67315. alternateClassName: 'Ext.form.Hidden',
  67316. xtype: 'hiddenfield',
  67317. config: {
  67318. /**
  67319. * @cfg
  67320. * @inheritdoc
  67321. */
  67322. component: {
  67323. xtype: 'input',
  67324. type : 'hidden'
  67325. },
  67326. /**
  67327. * @cfg
  67328. * @inheritdoc
  67329. */
  67330. ui: 'hidden',
  67331. /**
  67332. * @cfg hidden
  67333. * @hide
  67334. */
  67335. hidden: true,
  67336. /**
  67337. * @cfg {Number} tabIndex
  67338. * @hide
  67339. */
  67340. tabIndex: -1
  67341. }
  67342. });
  67343. /**
  67344. * @aside guide forms
  67345. *
  67346. * The Number field creates an HTML5 number input and is usually created inside a form. Because it creates an HTML
  67347. * number input field, most browsers will show a specialized virtual keyboard for entering numbers. The Number field
  67348. * only accepts numerical input and also provides additional spinner UI that increases or decreases the current value
  67349. * by a configured {@link #stepValue step value}. Here's how we might use one in a form:
  67350. *
  67351. * @example
  67352. * Ext.create('Ext.form.Panel', {
  67353. * fullscreen: true,
  67354. * items: [
  67355. * {
  67356. * xtype: 'fieldset',
  67357. * title: 'How old are you?',
  67358. * items: [
  67359. * {
  67360. * xtype: 'numberfield',
  67361. * label: 'Age',
  67362. * minValue: 18,
  67363. * maxValue: 150,
  67364. * name: 'age'
  67365. * }
  67366. * ]
  67367. * }
  67368. * ]
  67369. * });
  67370. *
  67371. * Or on its own, outside of a form:
  67372. *
  67373. * Ext.create('Ext.field.Number', {
  67374. * label: 'Age',
  67375. * value: '26'
  67376. * });
  67377. *
  67378. * ## minValue, maxValue and stepValue
  67379. *
  67380. * The {@link #minValue} and {@link #maxValue} configurations are self-explanatory and simply constrain the value
  67381. * entered to the range specified by the configured min and max values. The other option exposed by this component
  67382. * is {@link #stepValue}, which enables you to set how much the value changes every time the up and down spinners
  67383. * are tapped on. For example, to create a salary field that ticks up and down by $1,000 each tap we can do this:
  67384. *
  67385. * @example
  67386. * Ext.create('Ext.form.Panel', {
  67387. * fullscreen: true,
  67388. * items: [
  67389. * {
  67390. * xtype: 'fieldset',
  67391. * title: 'Are you rich yet?',
  67392. * items: [
  67393. * {
  67394. * xtype: 'numberfield',
  67395. * label: 'Salary',
  67396. * value: 30000,
  67397. * minValue: 25000,
  67398. * maxValue: 50000,
  67399. * stepValue: 1000
  67400. * }
  67401. * ]
  67402. * }
  67403. * ]
  67404. * });
  67405. *
  67406. * This creates a field that starts with a value of $30,000, steps up and down in $1,000 increments and will not go
  67407. * beneath $25,000 or above $50,000.
  67408. *
  67409. * Because number field inherits from {@link Ext.field.Text textfield} it gains all of the functionality that text
  67410. * fields provide, including getting and setting the value at runtime, validations and various events that are fired as
  67411. * the user interacts with the component. Check out the {@link Ext.field.Text} docs to see the additional functionality
  67412. * available.
  67413. */
  67414. Ext.define('Ext.field.Number', {
  67415. extend: 'Ext.field.Text',
  67416. xtype: 'numberfield',
  67417. alternateClassName: 'Ext.form.Number',
  67418. config: {
  67419. /**
  67420. * @cfg
  67421. * @inheritdoc
  67422. */
  67423. component: {
  67424. type: 'number'
  67425. },
  67426. /**
  67427. * @cfg
  67428. * @inheritdoc
  67429. */
  67430. ui: 'number'
  67431. },
  67432. proxyConfig: {
  67433. /**
  67434. * @cfg {Number} minValue The minimum value that this Number field can accept
  67435. * @accessor
  67436. */
  67437. minValue: null,
  67438. /**
  67439. * @cfg {Number} maxValue The maximum value that this Number field can accept
  67440. * @accessor
  67441. */
  67442. maxValue: null,
  67443. /**
  67444. * @cfg {Number} stepValue The amount by which the field is incremented or decremented each time the spinner is tapped.
  67445. * Defaults to undefined, which means that the field goes up or down by 1 each time the spinner is tapped
  67446. * @accessor
  67447. */
  67448. stepValue: null
  67449. },
  67450. applyValue: function(value) {
  67451. var minValue = this.getMinValue(),
  67452. maxValue = this.getMaxValue();
  67453. if (Ext.isNumber(minValue)) {
  67454. value = Math.max(value, minValue);
  67455. }
  67456. if (Ext.isNumber(maxValue)) {
  67457. value = Math.min(value, maxValue);
  67458. }
  67459. value = parseFloat(value);
  67460. return (isNaN(value)) ? '' : value;
  67461. },
  67462. getValue: function() {
  67463. var value = parseFloat(this.callParent(), 10);
  67464. return (isNaN(value)) ? null : value;
  67465. },
  67466. doClearIconTap: function(me, e) {
  67467. me.getComponent().setValue('');
  67468. me.getValue();
  67469. me.hideClearIcon();
  67470. }
  67471. });
  67472. /**
  67473. * @aside guide forms
  67474. *
  67475. * The Password field creates a password input and is usually created inside a form. Because it creates a password
  67476. * field, when the user enters text it will show up as stars. Aside from that, the password field is just a normal text
  67477. * field. Here's an example of how to use it in a form:
  67478. *
  67479. * @example
  67480. * Ext.create('Ext.form.Panel', {
  67481. * fullscreen: true,
  67482. * items: [
  67483. * {
  67484. * xtype: 'fieldset',
  67485. * title: 'Register',
  67486. * items: [
  67487. * {
  67488. * xtype: 'emailfield',
  67489. * label: 'Email',
  67490. * name: 'email'
  67491. * },
  67492. * {
  67493. * xtype: 'passwordfield',
  67494. * label: 'Password',
  67495. * name: 'password'
  67496. * }
  67497. * ]
  67498. * }
  67499. * ]
  67500. * });
  67501. *
  67502. * Or on its own, outside of a form:
  67503. *
  67504. * Ext.create('Ext.field.Password', {
  67505. * label: 'Password',
  67506. * value: 'existingPassword'
  67507. * });
  67508. *
  67509. * Because the password field inherits from {@link Ext.field.Text textfield} it gains all of the functionality that text
  67510. * fields provide, including getting and setting the value at runtime, validations and various events that are fired as
  67511. * the user interacts with the component. Check out the {@link Ext.field.Text} docs to see the additional functionality
  67512. * available.
  67513. */
  67514. Ext.define('Ext.field.Password', {
  67515. extend: 'Ext.field.Text',
  67516. xtype: 'passwordfield',
  67517. alternateClassName: 'Ext.form.Password',
  67518. config: {
  67519. /**
  67520. * @cfg
  67521. * @inheritdoc
  67522. */
  67523. autoCapitalize: false,
  67524. /**
  67525. * @cfg
  67526. * @inheritdoc
  67527. */
  67528. component: {
  67529. type: 'password'
  67530. }
  67531. }
  67532. });
  67533. /**
  67534. * @aside guide forms
  67535. *
  67536. * The radio field is an enhanced version of the native browser radio controls and is a good way of allowing your user
  67537. * to choose one option out of a selection of several (for example, choosing a favorite color):
  67538. *
  67539. * @example
  67540. * var form = Ext.create('Ext.form.Panel', {
  67541. * fullscreen: true,
  67542. * items: [
  67543. * {
  67544. * xtype: 'radiofield',
  67545. * name : 'color',
  67546. * value: 'red',
  67547. * label: 'Red',
  67548. * checked: true
  67549. * },
  67550. * {
  67551. * xtype: 'radiofield',
  67552. * name : 'color',
  67553. * value: 'green',
  67554. * label: 'Green'
  67555. * },
  67556. * {
  67557. * xtype: 'radiofield',
  67558. * name : 'color',
  67559. * value: 'blue',
  67560. * label: 'Blue'
  67561. * }
  67562. * ]
  67563. * });
  67564. *
  67565. * Above we created a simple form which allows the user to pick a color from the options red, green and blue. Because
  67566. * we gave each of the fields above the same {@link #name}, the radio field ensures that only one of them can be
  67567. * checked at a time. When we come to get the values out of the form again or submit it to the server, only 1 value
  67568. * will be sent for each group of radio fields with the same name:
  67569. *
  67570. * form.getValues(); //looks like {color: 'red'}
  67571. * form.submit(); //sends a single field back to the server (in this case color: red)
  67572. *
  67573. */
  67574. Ext.define('Ext.field.Radio', {
  67575. extend: 'Ext.field.Checkbox',
  67576. xtype: 'radiofield',
  67577. alternateClassName: 'Ext.form.Radio',
  67578. isRadio: true,
  67579. config: {
  67580. /**
  67581. * @cfg
  67582. * @inheritdoc
  67583. */
  67584. ui: 'radio',
  67585. /**
  67586. * @cfg
  67587. * @inheritdoc
  67588. */
  67589. component: {
  67590. type: 'radio',
  67591. cls: Ext.baseCSSPrefix + 'input-radio'
  67592. }
  67593. },
  67594. getValue: function() {
  67595. return (this._value) ? this._value : null;
  67596. },
  67597. setValue: function(value) {
  67598. this._value = value;
  67599. return this;
  67600. },
  67601. getSubmitValue: function() {
  67602. var value = this._value;
  67603. if (typeof value == "undefined" || value == null) {
  67604. value = true;
  67605. }
  67606. return (this.getChecked()) ? value : null;
  67607. },
  67608. updateChecked: function(newChecked) {
  67609. this.getComponent().setChecked(newChecked);
  67610. if (this.initialized) {
  67611. this.refreshGroupValues();
  67612. }
  67613. },
  67614. // @private
  67615. onMaskTap: function(component, e) {
  67616. var me = this,
  67617. dom = component.input.dom;
  67618. if (me.getDisabled()) {
  67619. return false;
  67620. }
  67621. if (!me.getChecked()) {
  67622. dom.checked = true;
  67623. }
  67624. me.refreshGroupValues();
  67625. //return false so the mask does not disappear
  67626. return false;
  67627. },
  67628. /**
  67629. * Returns the selected value if this radio is part of a group (other radio fields with the same name, in the same FormPanel),
  67630. * @return {String}
  67631. */
  67632. getGroupValue: function() {
  67633. var fields = this.getSameGroupFields(),
  67634. ln = fields.length,
  67635. i = 0,
  67636. field;
  67637. for (; i < ln; i++) {
  67638. field = fields[i];
  67639. if (field.getChecked()) {
  67640. return field.getValue();
  67641. }
  67642. }
  67643. return null;
  67644. },
  67645. /**
  67646. * Set the matched radio field's status (that has the same value as the given string) to checked.
  67647. * @param {String} value The value of the radio field to check.
  67648. * @return {Ext.field.Radio} The field that is checked.
  67649. */
  67650. setGroupValue: function(value) {
  67651. var fields = this.getSameGroupFields(),
  67652. ln = fields.length,
  67653. i = 0,
  67654. field;
  67655. for (; i < ln; i++) {
  67656. field = fields[i];
  67657. if (field.getValue() === value) {
  67658. field.setChecked(true);
  67659. return field;
  67660. }
  67661. }
  67662. },
  67663. /**
  67664. * Loops through each of the fields this radiofield is linked to (has the same name) and
  67665. * calls `onChange` on those fields so the appropriate event is fired.
  67666. * @private
  67667. */
  67668. refreshGroupValues: function() {
  67669. var fields = this.getSameGroupFields(),
  67670. ln = fields.length,
  67671. i = 0,
  67672. field;
  67673. for (; i < ln; i++) {
  67674. field = fields[i];
  67675. field.onChange();
  67676. }
  67677. }
  67678. });
  67679. /**
  67680. * @aside guide forms
  67681. *
  67682. * The Search field creates an HTML5 search input and is usually created inside a form. Because it creates an HTML
  67683. * search input type, the visual styling of this input is slightly different to normal text input controls (the corners
  67684. * are rounded), though the virtual keyboard displayed by the operating system is the standard keyboard control.
  67685. *
  67686. * As with all other form fields in Sencha Touch, the search field gains a "clear" button that appears whenever there
  67687. * is text entered into the form, and which removes that text when tapped.
  67688. *
  67689. * @example
  67690. * Ext.create('Ext.form.Panel', {
  67691. * fullscreen: true,
  67692. * items: [
  67693. * {
  67694. * xtype: 'fieldset',
  67695. * title: 'Search',
  67696. * items: [
  67697. * {
  67698. * xtype: 'searchfield',
  67699. * label: 'Query',
  67700. * name: 'query'
  67701. * }
  67702. * ]
  67703. * }
  67704. * ]
  67705. * });
  67706. *
  67707. * Or on its own, outside of a form:
  67708. *
  67709. * Ext.create('Ext.field.Search', {
  67710. * label: 'Search:',
  67711. * value: 'query'
  67712. * });
  67713. *
  67714. * Because search field inherits from {@link Ext.field.Text textfield} it gains all of the functionality that text
  67715. * fields provide, including getting and setting the value at runtime, validations and various events that are fired
  67716. * as the user interacts with the component. Check out the {@link Ext.field.Text} docs to see the additional
  67717. * functionality available.
  67718. */
  67719. Ext.define('Ext.field.Search', {
  67720. extend: 'Ext.field.Text',
  67721. xtype: 'searchfield',
  67722. alternateClassName: 'Ext.form.Search',
  67723. config: {
  67724. /**
  67725. * @cfg
  67726. * @inheritdoc
  67727. */
  67728. component: {
  67729. type: 'search'
  67730. },
  67731. /**
  67732. * @cfg
  67733. * @inheritdoc
  67734. */
  67735. ui: 'search'
  67736. }
  67737. });
  67738. /**
  67739. * @aside guide forms
  67740. *
  67741. * Simple Select field wrapper. Example usage:
  67742. *
  67743. * @example
  67744. * Ext.create('Ext.form.Panel', {
  67745. * fullscreen: true,
  67746. * items: [
  67747. * {
  67748. * xtype: 'fieldset',
  67749. * title: 'Select',
  67750. * items: [
  67751. * {
  67752. * xtype: 'selectfield',
  67753. * label: 'Choose one',
  67754. * options: [
  67755. * {text: 'First Option', value: 'first'},
  67756. * {text: 'Second Option', value: 'second'},
  67757. * {text: 'Third Option', value: 'third'}
  67758. * ]
  67759. * }
  67760. * ]
  67761. * }
  67762. * ]
  67763. * });
  67764. */
  67765. Ext.define('Ext.field.Select', {
  67766. extend: 'Ext.field.Text',
  67767. xtype: 'selectfield',
  67768. alternateClassName: 'Ext.form.Select',
  67769. requires: [
  67770. 'Ext.Panel',
  67771. 'Ext.picker.Picker',
  67772. 'Ext.data.Store',
  67773. 'Ext.data.StoreManager',
  67774. 'Ext.dataview.List'
  67775. ],
  67776. /**
  67777. * @event change
  67778. * Fires when an option selection has changed
  67779. * @param {Ext.field.Select} this
  67780. * @param {Mixed} newValue The new value
  67781. * @param {Mixed} oldValue The old value
  67782. */
  67783. /**
  67784. * @event focus
  67785. * Fires when this field receives input focus. This happens both when you tap on the field and when you focus on the field by using
  67786. * 'next' or 'tab' on a keyboard.
  67787. *
  67788. * Please note that this event is not very reliable on Android. For example, if your Select field is second in your form panel,
  67789. * you cannot use the Next button to get to this select field. This functionality works as expected on iOS.
  67790. * @param {Ext.field.Select} this This field
  67791. * @param {Ext.event.Event} e
  67792. */
  67793. config: {
  67794. /**
  67795. * @cfg
  67796. * @inheritdoc
  67797. */
  67798. ui: 'select',
  67799. /**
  67800. * @cfg {Boolean} useClearIcon
  67801. * @hide
  67802. */
  67803. /**
  67804. * @cfg {String/Number} valueField The underlying {@link Ext.data.Field#name data value name} (or numeric Array index) to bind to this
  67805. * Select control.
  67806. * @accessor
  67807. */
  67808. valueField: 'value',
  67809. /**
  67810. * @cfg {String/Number} displayField The underlying {@link Ext.data.Field#name data value name} (or numeric Array index) to bind to this
  67811. * Select control. This resolved value is the visibly rendered value of the available selection options.
  67812. * @accessor
  67813. */
  67814. displayField: 'text',
  67815. /**
  67816. * @cfg {Ext.data.Store/Object/String} store The store to provide selection options data.
  67817. * Either a Store instance, configuration object or store ID.
  67818. * @accessor
  67819. */
  67820. store: null,
  67821. /**
  67822. * @cfg {Array} options An array of select options.
  67823. *
  67824. * [
  67825. * {text: 'First Option', value: 'first'},
  67826. * {text: 'Second Option', value: 'second'},
  67827. * {text: 'Third Option', value: 'third'}
  67828. * ]
  67829. *
  67830. * __Note:__ Option object member names should correspond with defined {@link #valueField valueField} and {@link #displayField displayField} values.
  67831. * This config will be ignored if a {@link #store store} instance is provided.
  67832. * @accessor
  67833. */
  67834. options: null,
  67835. /**
  67836. * @cfg {String} hiddenName Specify a `hiddenName` if you're using the {@link Ext.form.Panel#standardSubmit standardSubmit} option.
  67837. * This name will be used to post the underlying value of the select to the server.
  67838. * @accessor
  67839. */
  67840. hiddenName: null,
  67841. /**
  67842. * @cfg {Object} component
  67843. * @accessor
  67844. * @hide
  67845. */
  67846. component: {
  67847. useMask: true
  67848. },
  67849. /**
  67850. * @cfg {Boolean} clearIcon
  67851. * @hide
  67852. * @accessor
  67853. */
  67854. clearIcon: false,
  67855. /**
  67856. * @cfg {String/Boolean} usePicker
  67857. * `true` if you want this component to always use a {@link Ext.picker.Picker}.
  67858. * `false` if you want it to use a popup overlay {@link Ext.List}.
  67859. * `auto` if you want to show a {@link Ext.picker.Picker} only on phones.
  67860. */
  67861. usePicker: 'auto',
  67862. /**
  67863. * @cfg {Boolean} autoSelect
  67864. * `true` to auto select the first value in the {@link #store} or {@link #options} when they are changed. Only happens when
  67865. * the {@link #value} is set to `null`.
  67866. */
  67867. autoSelect: true,
  67868. /**
  67869. * @cfg {Object} defaultPhonePickerConfig
  67870. * The default configuration for the picker component when you are on a phone.
  67871. */
  67872. defaultPhonePickerConfig: null,
  67873. /**
  67874. * @cfg {Object} defaultTabletPickerConfig
  67875. * The default configuration for the picker component when you are on a tablet.
  67876. */
  67877. defaultTabletPickerConfig: null,
  67878. /**
  67879. * @cfg
  67880. * @inheritdoc
  67881. */
  67882. name: 'picker'
  67883. },
  67884. // @private
  67885. initialize: function() {
  67886. var me = this,
  67887. component = me.getComponent();
  67888. me.callParent();
  67889. component.on({
  67890. scope: me,
  67891. masktap: 'onMaskTap'
  67892. });
  67893. if (Ext.os.is.Android2) {
  67894. component.input.dom.disabled = true;
  67895. }
  67896. },
  67897. /**
  67898. * @private
  67899. */
  67900. updateDefaultPhonePickerConfig: function(newConfig) {
  67901. var picker = this.picker;
  67902. if (picker) {
  67903. picker.setConfig(newConfig);
  67904. }
  67905. },
  67906. /**
  67907. * @private
  67908. */
  67909. updateDefaultTabletPickerConfig: function(newConfig) {
  67910. var listPanel = this.listPanel;
  67911. if (listPanel) {
  67912. listPanel.setConfig(newConfig);
  67913. }
  67914. },
  67915. /**
  67916. * @private
  67917. * Checks if the value is `auto`. If it is, it only uses the picker if the current device type
  67918. * is a phone.
  67919. */
  67920. applyUsePicker: function(usePicker) {
  67921. if (usePicker == "auto") {
  67922. usePicker = (Ext.os.deviceType == 'Phone');
  67923. }
  67924. return Boolean(usePicker);
  67925. },
  67926. syncEmptyCls: Ext.emptyFn,
  67927. /**
  67928. * @private
  67929. */
  67930. applyValue: function(value) {
  67931. var record = value,
  67932. index, store;
  67933. //we call this so that the options configruation gets intiailized, so that a store exists, and we can
  67934. //find the correct value
  67935. this.getOptions();
  67936. store = this.getStore();
  67937. if ((value != undefined && !value.isModel) && store) {
  67938. index = store.find(this.getValueField(), value, null, null, null, true);
  67939. if (index == -1) {
  67940. index = store.find(this.getDisplayField(), value, null, null, null, true);
  67941. }
  67942. record = store.getAt(index);
  67943. }
  67944. return record;
  67945. },
  67946. updateValue: function(newValue, oldValue) {
  67947. this.record = newValue;
  67948. this.callParent([(newValue && newValue.isModel) ? newValue.get(this.getDisplayField()) : '']);
  67949. },
  67950. getValue: function() {
  67951. var record = this.record;
  67952. return (record && record.isModel) ? record.get(this.getValueField()) : null;
  67953. },
  67954. /**
  67955. * Returns the current selected {@link Ext.data.Model record} instance selected in this field.
  67956. * @return {Ext.data.Model} the record.
  67957. */
  67958. getRecord: function() {
  67959. return this.record;
  67960. },
  67961. // @private
  67962. getPhonePicker: function() {
  67963. var config = this.getDefaultPhonePickerConfig();
  67964. if (!this.picker) {
  67965. this.picker = Ext.create('Ext.picker.Picker', Ext.apply({
  67966. slots: [{
  67967. align : 'center',
  67968. name : this.getName(),
  67969. valueField : this.getValueField(),
  67970. displayField: this.getDisplayField(),
  67971. value : this.getValue(),
  67972. store : this.getStore()
  67973. }],
  67974. listeners: {
  67975. change: this.onPickerChange,
  67976. scope: this
  67977. }
  67978. }, config));
  67979. }
  67980. return this.picker;
  67981. },
  67982. // @private
  67983. getTabletPicker: function() {
  67984. var config = this.getDefaultTabletPickerConfig();
  67985. if (!this.listPanel) {
  67986. this.listPanel = Ext.create('Ext.Panel', Ext.apply({
  67987. left: 0,
  67988. top: 0,
  67989. modal: true,
  67990. cls: Ext.baseCSSPrefix + 'select-overlay',
  67991. layout: 'fit',
  67992. hideOnMaskTap: true,
  67993. width: Ext.os.is.Phone ? '14em' : '18em',
  67994. height: Ext.os.is.Phone ? '12.5em' : '22em',
  67995. items: {
  67996. xtype: 'list',
  67997. store: this.getStore(),
  67998. itemTpl: '<span class="x-list-label">{' + this.getDisplayField() + ':htmlEncode}</span>',
  67999. listeners: {
  68000. select : this.onListSelect,
  68001. itemtap: this.onListTap,
  68002. scope : this
  68003. }
  68004. }
  68005. }, config));
  68006. }
  68007. return this.listPanel;
  68008. },
  68009. // @private
  68010. onMaskTap: function() {
  68011. if (this.getDisabled()) {
  68012. return false;
  68013. }
  68014. this.onFocus();
  68015. return false;
  68016. },
  68017. /**
  68018. * Shows the picker for the select field, whether that is a {@link Ext.picker.Picker} or a simple
  68019. * {@link Ext.List list}.
  68020. */
  68021. showPicker: function() {
  68022. var store = this.getStore();
  68023. //check if the store is empty, if it is, return
  68024. if (!store || store.getCount() === 0) {
  68025. return;
  68026. }
  68027. if (this.getReadOnly()) {
  68028. return;
  68029. }
  68030. this.isFocused = true;
  68031. if (this.getUsePicker()) {
  68032. var picker = this.getPhonePicker(),
  68033. name = this.getName(),
  68034. value = {};
  68035. value[name] = this.getValue();
  68036. picker.setValue(value);
  68037. if (!picker.getParent()) {
  68038. Ext.Viewport.add(picker);
  68039. }
  68040. picker.show();
  68041. } else {
  68042. var listPanel = this.getTabletPicker(),
  68043. list = listPanel.down('list'),
  68044. index, record;
  68045. store = list.getStore();
  68046. index = store.find(this.getValueField(), this.getValue(), null, null, null, true);
  68047. record = store.getAt((index == -1) ? 0 : index);
  68048. if (!listPanel.getParent()) {
  68049. Ext.Viewport.add(listPanel);
  68050. }
  68051. listPanel.showBy(this.getComponent());
  68052. list.select(record, null, true);
  68053. }
  68054. },
  68055. // @private
  68056. onListSelect: function(item, record) {
  68057. var me = this;
  68058. if (record) {
  68059. me.setValue(record);
  68060. }
  68061. },
  68062. onListTap: function() {
  68063. this.listPanel.hide({
  68064. type : 'fade',
  68065. out : true,
  68066. scope: this
  68067. });
  68068. },
  68069. // @private
  68070. onPickerChange: function(picker, value) {
  68071. var me = this,
  68072. newValue = value[me.getName()],
  68073. store = me.getStore(),
  68074. index = store.find(me.getValueField(), newValue, null, null, null, true),
  68075. record = store.getAt(index);
  68076. me.setValue(record);
  68077. },
  68078. onChange: function(component, newValue, oldValue) {
  68079. var me = this,
  68080. store = me.getStore(),
  68081. index = (store) ? store.find(me.getDisplayField(), oldValue) : -1,
  68082. valueField = me.getValueField(),
  68083. record = (store) ? store.getAt(index) : null;
  68084. oldValue = (record) ? record.get(valueField) : null;
  68085. me.fireEvent('change', me, me.getValue(), oldValue);
  68086. },
  68087. /**
  68088. * Updates the underlying `<options>` list with new values.
  68089. * @param {Array} options An array of options configurations to insert or append.
  68090. *
  68091. * selectBox.setOptions([
  68092. * {text: 'First Option', value: 'first'},
  68093. * {text: 'Second Option', value: 'second'},
  68094. * {text: 'Third Option', value: 'third'}
  68095. * ]).setValue('third');
  68096. *
  68097. * __Note:__ option object member names should correspond with defined {@link #valueField valueField} and
  68098. * {@link #displayField displayField} values.
  68099. * @return {Ext.field.Select} this
  68100. */
  68101. updateOptions: function(newOptions) {
  68102. var store = this.getStore();
  68103. if (!store) {
  68104. this.setStore(true);
  68105. store = this._store;
  68106. }
  68107. if (!newOptions) {
  68108. store.clearData();
  68109. }
  68110. else {
  68111. store.setData(newOptions);
  68112. this.onStoreDataChanged(store);
  68113. }
  68114. return this;
  68115. },
  68116. applyStore: function(store) {
  68117. if (store === true) {
  68118. store = Ext.create('Ext.data.Store', {
  68119. fields: [this.getValueField(), this.getDisplayField()],
  68120. autoDestroy: true
  68121. });
  68122. }
  68123. if (store) {
  68124. store = Ext.data.StoreManager.lookup(store);
  68125. store.on({
  68126. scope: this,
  68127. addrecords: 'onStoreDataChanged',
  68128. removerecords: 'onStoreDataChanged',
  68129. updaterecord: 'onStoreDataChanged',
  68130. refresh: 'onStoreDataChanged'
  68131. });
  68132. }
  68133. return store;
  68134. },
  68135. updateStore: function(newStore) {
  68136. if (newStore) {
  68137. this.onStoreDataChanged(newStore);
  68138. }
  68139. if (this.getUsePicker() && this.picker) {
  68140. this.picker.down('pickerslot').setStore(newStore);
  68141. } else if (this.listPanel) {
  68142. this.listPanel.down('dataview').setStore(newStore);
  68143. }
  68144. },
  68145. /**
  68146. * Called when the internal {@link #store}'s data has changed.
  68147. */
  68148. onStoreDataChanged: function(store) {
  68149. var initialConfig = this.getInitialConfig(),
  68150. value = this.getValue();
  68151. if (value || value == 0) {
  68152. this.updateValue(this.applyValue(value));
  68153. }
  68154. if (this.getValue() === null) {
  68155. if (initialConfig.hasOwnProperty('value')) {
  68156. this.setValue(initialConfig.value);
  68157. }
  68158. if (this.getValue() === null && this.getAutoSelect()) {
  68159. if (store.getCount() > 0) {
  68160. this.setValue(store.getAt(0));
  68161. }
  68162. }
  68163. }
  68164. },
  68165. /**
  68166. * @private
  68167. */
  68168. doSetDisabled: function(disabled) {
  68169. Ext.Component.prototype.doSetDisabled.apply(this, arguments);
  68170. },
  68171. /**
  68172. * @private
  68173. */
  68174. setDisabled: function() {
  68175. Ext.Component.prototype.setDisabled.apply(this, arguments);
  68176. },
  68177. /**
  68178. * Resets the Select field to the value of the first record in the store.
  68179. * @return {Ext.field.Select} this
  68180. * @chainable
  68181. */
  68182. reset: function() {
  68183. var store = this.getStore(),
  68184. record = (this.originalValue) ? this.originalValue : store.getAt(0);
  68185. if (store && record) {
  68186. this.setValue(record);
  68187. }
  68188. return this;
  68189. },
  68190. onFocus: function(e) {
  68191. var component = this.getComponent();
  68192. this.fireEvent('focus', this, e);
  68193. if (Ext.os.is.Android4) {
  68194. component.input.dom.focus();
  68195. }
  68196. component.input.dom.blur();
  68197. this.isFocused = true;
  68198. this.showPicker();
  68199. },
  68200. destroy: function () {
  68201. this.callParent(arguments);
  68202. var store = this.getStore();
  68203. if (store && store.getAutoDestroy()) {
  68204. Ext.destroy(store);
  68205. }
  68206. }
  68207. });
  68208. /**
  68209. * @private
  68210. * Utility class used by Ext.slider.Slider - should never need to be used directly.
  68211. */
  68212. Ext.define('Ext.slider.Thumb', {
  68213. extend: 'Ext.Component',
  68214. xtype : 'thumb',
  68215. config: {
  68216. /**
  68217. * @cfg
  68218. * @inheritdoc
  68219. */
  68220. baseCls: Ext.baseCSSPrefix + 'thumb',
  68221. /**
  68222. * @cfg
  68223. * @inheritdoc
  68224. */
  68225. draggable: {
  68226. direction: 'horizontal'
  68227. }
  68228. },
  68229. elementWidth: 0,
  68230. initialize: function() {
  68231. this.callParent();
  68232. this.getDraggable().onBefore({
  68233. dragstart: 'onDragStart',
  68234. drag: 'onDrag',
  68235. dragend: 'onDragEnd',
  68236. scope: this
  68237. });
  68238. this.element.on('resize', 'onElementResize', this);
  68239. },
  68240. onDragStart: function() {
  68241. if (this.isDisabled()) {
  68242. return false;
  68243. }
  68244. this.relayEvent(arguments);
  68245. },
  68246. onDrag: function() {
  68247. if (this.isDisabled()) {
  68248. return false;
  68249. }
  68250. this.relayEvent(arguments);
  68251. },
  68252. onDragEnd: function() {
  68253. if (this.isDisabled()) {
  68254. return false;
  68255. }
  68256. this.relayEvent(arguments);
  68257. },
  68258. onElementResize: function(element, info) {
  68259. this.elementWidth = info.width;
  68260. },
  68261. getElementWidth: function() {
  68262. return this.elementWidth;
  68263. }
  68264. });
  68265. /**
  68266. * Utility class used by Ext.field.Slider.
  68267. * @private
  68268. */
  68269. Ext.define('Ext.slider.Slider', {
  68270. extend: 'Ext.Container',
  68271. xtype: 'slider',
  68272. requires: [
  68273. 'Ext.slider.Thumb',
  68274. 'Ext.fx.easing.EaseOut'
  68275. ],
  68276. /**
  68277. * @event change
  68278. * Fires when the value changes
  68279. * @param {Ext.slider.Slider} this
  68280. * @param {Ext.slider.Thumb} thumb The thumb being changed
  68281. * @param {Number} newValue The new value
  68282. * @param {Number} oldValue The old value
  68283. */
  68284. /**
  68285. * @event dragstart
  68286. * Fires when the slider thumb starts a drag
  68287. * @param {Ext.slider.Slider} this
  68288. * @param {Ext.slider.Thumb} thumb The thumb being dragged
  68289. * @param {Array} value The start value
  68290. * @param {Ext.EventObject} e
  68291. */
  68292. /**
  68293. * @event drag
  68294. * Fires when the slider thumb starts a drag
  68295. * @param {Ext.slider.Slider} this
  68296. * @param {Ext.slider.Thumb} thumb The thumb being dragged
  68297. * @param {Ext.EventObject} e
  68298. */
  68299. /**
  68300. * @event dragend
  68301. * Fires when the slider thumb starts a drag
  68302. * @param {Ext.slider.Slider} this
  68303. * @param {Ext.slider.Thumb} thumb The thumb being dragged
  68304. * @param {Array} value The end value
  68305. * @param {Ext.EventObject} e
  68306. */
  68307. config: {
  68308. baseCls: 'x-slider',
  68309. /**
  68310. * @cfg {Object} thumbConfig The config object to factory {@link Ext.slider.Thumb} instances
  68311. * @accessor
  68312. */
  68313. thumbConfig: {
  68314. draggable: {
  68315. translatable: {
  68316. easingX: {
  68317. duration: 300,
  68318. type: 'ease-out'
  68319. }
  68320. }
  68321. }
  68322. },
  68323. /**
  68324. * @cfg {Number} increment The increment by which to snap each thumb when its value changes. Any thumb movement
  68325. * will be snapped to the nearest value that is a multiple of the increment (e.g. if increment is 10 and the user
  68326. * tries to move the thumb to 67, it will be snapped to 70 instead)
  68327. * @accessor
  68328. */
  68329. increment : 1,
  68330. /**
  68331. * @cfg {Number/Number[]} value The value(s) of this slider's thumbs. If you pass
  68332. * a number, it will assume you have just 1 thumb.
  68333. * @accessor
  68334. */
  68335. value: 0,
  68336. /**
  68337. * @cfg {Number} minValue The lowest value any thumb on this slider can be set to.
  68338. * @accessor
  68339. */
  68340. minValue: 0,
  68341. /**
  68342. * @cfg {Number} maxValue The highest value any thumb on this slider can be set to.
  68343. * @accessor
  68344. */
  68345. maxValue: 100,
  68346. /**
  68347. * @cfg {Boolean} allowThumbsOverlapping Whether or not to allow multiple thumbs to overlap each other.
  68348. * Setting this to true guarantees the ability to select every possible value in between {@link #minValue}
  68349. * and {@link #maxValue} that satisfies {@link #increment}
  68350. * @accessor
  68351. */
  68352. allowThumbsOverlapping: false,
  68353. /**
  68354. * @cfg {Boolean/Object} animation
  68355. * The animation to use when moving the slider. Possible properties are:
  68356. *
  68357. * - duration
  68358. * - easingX
  68359. * - easingY
  68360. *
  68361. * @accessor
  68362. */
  68363. animation: true,
  68364. /**
  68365. * Will make this field read only, meaning it cannot be changed with used interaction.
  68366. * @cfg {Boolean} readOnly
  68367. * @accessor
  68368. */
  68369. readOnly: false
  68370. },
  68371. /**
  68372. * @cfg {Number/Number[]} values Alias to {@link #value}
  68373. */
  68374. elementWidth: 0,
  68375. offsetValueRatio: 0,
  68376. activeThumb: null,
  68377. constructor: function(config) {
  68378. config = config || {};
  68379. if (config.hasOwnProperty('values')) {
  68380. config.value = config.values;
  68381. }
  68382. this.callParent([config]);
  68383. },
  68384. // @private
  68385. initialize: function() {
  68386. var element = this.element;
  68387. this.callParent();
  68388. element.on({
  68389. scope: this,
  68390. tap: 'onTap',
  68391. resize: 'onResize'
  68392. });
  68393. this.on({
  68394. scope: this,
  68395. delegate: '> thumb',
  68396. dragstart: 'onThumbDragStart',
  68397. drag: 'onThumbDrag',
  68398. dragend: 'onThumbDragEnd'
  68399. });
  68400. },
  68401. /**
  68402. * @private
  68403. */
  68404. factoryThumb: function() {
  68405. return Ext.factory(this.getThumbConfig(), Ext.slider.Thumb);
  68406. },
  68407. /**
  68408. * Returns the Thumb instances bound to this Slider
  68409. * @return {Ext.slider.Thumb[]} The thumb instances
  68410. */
  68411. getThumbs: function() {
  68412. return this.innerItems;
  68413. },
  68414. /**
  68415. * Returns the Thumb instance bound to this Slider
  68416. * @param {Number} [index=0] The index of Thumb to return.
  68417. * @return {Ext.slider.Thumb} The thumb instance
  68418. */
  68419. getThumb: function(index) {
  68420. if (typeof index != 'number') {
  68421. index = 0;
  68422. }
  68423. return this.innerItems[index];
  68424. },
  68425. refreshOffsetValueRatio: function() {
  68426. var valueRange = this.getMaxValue() - this.getMinValue(),
  68427. trackWidth = this.elementWidth - this.thumbWidth;
  68428. this.offsetValueRatio = trackWidth / valueRange;
  68429. },
  68430. onResize: function(element, info) {
  68431. var thumb = this.getThumb(0);
  68432. if (thumb) {
  68433. this.thumbWidth = thumb.getElementWidth();
  68434. }
  68435. this.elementWidth = info.width;
  68436. this.refresh();
  68437. },
  68438. refresh: function() {
  68439. this.refreshValue();
  68440. },
  68441. setActiveThumb: function(thumb) {
  68442. var oldActiveThumb = this.activeThumb;
  68443. if (oldActiveThumb && oldActiveThumb !== thumb) {
  68444. oldActiveThumb.setZIndex(null);
  68445. }
  68446. this.activeThumb = thumb;
  68447. thumb.setZIndex(2);
  68448. return this;
  68449. },
  68450. onThumbDragStart: function(thumb, e) {
  68451. if (e.absDeltaX <= e.absDeltaY || this.getReadOnly()) {
  68452. return false;
  68453. }
  68454. else {
  68455. e.stopPropagation();
  68456. }
  68457. if (this.getAllowThumbsOverlapping()) {
  68458. this.setActiveThumb(thumb);
  68459. }
  68460. this.dragStartValue = this.getValue()[this.getThumbIndex(thumb)];
  68461. this.fireEvent('dragstart', this, thumb, this.dragStartValue, e);
  68462. },
  68463. onThumbDrag: function(thumb, e, offsetX) {
  68464. var index = this.getThumbIndex(thumb),
  68465. offsetValueRatio = this.offsetValueRatio,
  68466. constrainedValue = this.constrainValue(this.getMinValue() + offsetX / offsetValueRatio);
  68467. e.stopPropagation();
  68468. this.setIndexValue(index, constrainedValue);
  68469. this.fireEvent('drag', this, thumb, this.getValue(), e);
  68470. return false;
  68471. },
  68472. setIndexValue: function(index, value, animation) {
  68473. var thumb = this.getThumb(index),
  68474. values = this.getValue(),
  68475. offsetValueRatio = this.offsetValueRatio,
  68476. draggable = thumb.getDraggable();
  68477. draggable.setOffset((value - this.getMinValue()) * offsetValueRatio, null, animation);
  68478. values[index] = value;
  68479. },
  68480. onThumbDragEnd: function(thumb, e) {
  68481. this.refreshThumbConstraints(thumb);
  68482. var index = this.getThumbIndex(thumb),
  68483. newValue = this.getValue()[index],
  68484. oldValue = this.dragStartValue;
  68485. this.fireEvent('dragend', this, thumb, this.getValue(), e);
  68486. if (oldValue !== newValue) {
  68487. this.fireEvent('change', this, thumb, newValue, oldValue);
  68488. }
  68489. },
  68490. getThumbIndex: function(thumb) {
  68491. return this.getThumbs().indexOf(thumb);
  68492. },
  68493. refreshThumbConstraints: function(thumb) {
  68494. var allowThumbsOverlapping = this.getAllowThumbsOverlapping(),
  68495. offsetX = thumb.getDraggable().getOffset().x,
  68496. thumbs = this.getThumbs(),
  68497. index = this.getThumbIndex(thumb),
  68498. previousThumb = thumbs[index - 1],
  68499. nextThumb = thumbs[index + 1],
  68500. thumbWidth = this.thumbWidth;
  68501. if (previousThumb) {
  68502. previousThumb.getDraggable().addExtraConstraint({
  68503. max: {
  68504. x: offsetX - ((allowThumbsOverlapping) ? 0 : thumbWidth)
  68505. }
  68506. });
  68507. }
  68508. if (nextThumb) {
  68509. nextThumb.getDraggable().addExtraConstraint({
  68510. min: {
  68511. x: offsetX + ((allowThumbsOverlapping) ? 0 : thumbWidth)
  68512. }
  68513. });
  68514. }
  68515. },
  68516. // @private
  68517. onTap: function(e) {
  68518. if (this.isDisabled()) {
  68519. return;
  68520. }
  68521. var targetElement = Ext.get(e.target);
  68522. if (!targetElement || targetElement.hasCls('x-thumb')) {
  68523. return;
  68524. }
  68525. var touchPointX = e.touch.point.x,
  68526. element = this.element,
  68527. elementX = element.getX(),
  68528. offset = touchPointX - elementX - (this.thumbWidth / 2),
  68529. value = this.constrainValue(this.getMinValue() + offset / this.offsetValueRatio),
  68530. values = this.getValue(),
  68531. minDistance = Infinity,
  68532. ln = values.length,
  68533. i, absDistance, testValue, closestIndex, oldValue, thumb;
  68534. if (ln === 1) {
  68535. closestIndex = 0;
  68536. }
  68537. else {
  68538. for (i = 0; i < ln; i++) {
  68539. testValue = values[i];
  68540. absDistance = Math.abs(testValue - value);
  68541. if (absDistance < minDistance) {
  68542. minDistance = absDistance;
  68543. closestIndex = i;
  68544. }
  68545. }
  68546. }
  68547. oldValue = values[closestIndex];
  68548. thumb = this.getThumb(closestIndex);
  68549. this.setIndexValue(closestIndex, value, this.getAnimation());
  68550. this.refreshThumbConstraints(thumb);
  68551. if (oldValue !== value) {
  68552. this.fireEvent('change', this, thumb, value, oldValue);
  68553. }
  68554. },
  68555. // @private
  68556. updateThumbs: function(newThumbs) {
  68557. this.add(newThumbs);
  68558. },
  68559. applyValue: function(value) {
  68560. var values = Ext.Array.from(value || 0),
  68561. filteredValues = [],
  68562. previousFilteredValue = this.getMinValue(),
  68563. filteredValue, i, ln;
  68564. for (i = 0,ln = values.length; i < ln; i++) {
  68565. filteredValue = this.constrainValue(values[i]);
  68566. if (filteredValue < previousFilteredValue) {
  68567. //<debug warn>
  68568. Ext.Logger.warn("Invalid values of '"+Ext.encode(values)+"', values at smaller indexes must " +
  68569. "be smaller than or equal to values at greater indexes");
  68570. //</debug>
  68571. filteredValue = previousFilteredValue;
  68572. }
  68573. filteredValues.push(filteredValue);
  68574. previousFilteredValue = filteredValue;
  68575. }
  68576. return filteredValues;
  68577. },
  68578. /**
  68579. * Updates the sliders thumbs with their new value(s)
  68580. */
  68581. updateValue: function(newValue, oldValue) {
  68582. var thumbs = this.getThumbs(),
  68583. ln = newValue.length,
  68584. minValue = this.getMinValue(),
  68585. offset = this.offsetValueRatio,
  68586. i;
  68587. this.setThumbsCount(ln);
  68588. for (i = 0; i < ln; i++) {
  68589. thumbs[i].getDraggable().setExtraConstraint(null).setOffset((newValue[i] - minValue) * offset);
  68590. }
  68591. for (i = 0; i < ln; i++) {
  68592. this.refreshThumbConstraints(thumbs[i]);
  68593. }
  68594. },
  68595. /**
  68596. * @private
  68597. */
  68598. refreshValue: function() {
  68599. this.refreshOffsetValueRatio();
  68600. this.setValue(this.getValue());
  68601. },
  68602. /**
  68603. * @private
  68604. * Takes a desired value of a thumb and returns the nearest snap value. e.g if minValue = 0, maxValue = 100, increment = 10 and we
  68605. * pass a value of 67 here, the returned value will be 70. The returned number is constrained within {@link #minValue} and {@link #maxValue},
  68606. * so in the above example 68 would be returned if {@link #maxValue} was set to 68.
  68607. * @param {Number} value The value to snap
  68608. * @return {Number} The snapped value
  68609. */
  68610. constrainValue: function(value) {
  68611. var me = this,
  68612. minValue = me.getMinValue(),
  68613. maxValue = me.getMaxValue(),
  68614. increment = me.getIncrement(),
  68615. remainder;
  68616. value = parseFloat(value);
  68617. if (isNaN(value)) {
  68618. value = minValue;
  68619. }
  68620. remainder = (value - minValue) % increment;
  68621. value -= remainder;
  68622. if (Math.abs(remainder) >= (increment / 2)) {
  68623. value += (remainder > 0) ? increment : -increment;
  68624. }
  68625. value = Math.max(minValue, value);
  68626. value = Math.min(maxValue, value);
  68627. return value;
  68628. },
  68629. setThumbsCount: function(count) {
  68630. var thumbs = this.getThumbs(),
  68631. thumbsCount = thumbs.length,
  68632. i, ln, thumb;
  68633. if (thumbsCount > count) {
  68634. for (i = 0,ln = thumbsCount - count; i < ln; i++) {
  68635. thumb = thumbs[thumbs.length - 1];
  68636. thumb.destroy();
  68637. }
  68638. }
  68639. else if (thumbsCount < count) {
  68640. for (i = 0,ln = count - thumbsCount; i < ln; i++) {
  68641. this.add(this.factoryThumb());
  68642. }
  68643. }
  68644. return this;
  68645. },
  68646. /**
  68647. * Convenience method. Calls {@link #setValue}.
  68648. */
  68649. setValues: function(value) {
  68650. this.setValue(value);
  68651. },
  68652. /**
  68653. * Convenience method. Calls {@link #getValue}.
  68654. * @return {Object}
  68655. */
  68656. getValues: function() {
  68657. return this.getValue();
  68658. },
  68659. /**
  68660. * Sets the {@link #increment} configuration.
  68661. * @param {Number} increment
  68662. * @return {Number}
  68663. */
  68664. applyIncrement: function(increment) {
  68665. if (increment === 0) {
  68666. increment = 1;
  68667. }
  68668. return Math.abs(increment);
  68669. },
  68670. // @private
  68671. updateAllowThumbsOverlapping: function(newValue, oldValue) {
  68672. if (typeof oldValue != 'undefined') {
  68673. this.refreshValue();
  68674. }
  68675. },
  68676. // @private
  68677. updateMinValue: function(newValue, oldValue) {
  68678. if (typeof oldValue != 'undefined') {
  68679. this.refreshValue();
  68680. }
  68681. },
  68682. // @private
  68683. updateMaxValue: function(newValue, oldValue) {
  68684. if (typeof oldValue != 'undefined') {
  68685. this.refreshValue();
  68686. }
  68687. },
  68688. // @private
  68689. updateIncrement: function(newValue, oldValue) {
  68690. if (typeof oldValue != 'undefined') {
  68691. this.refreshValue();
  68692. }
  68693. },
  68694. doSetDisabled: function(disabled) {
  68695. this.callParent(arguments);
  68696. var items = this.getItems().items,
  68697. ln = items.length,
  68698. i;
  68699. for (i = 0; i < ln; i++) {
  68700. items[i].setDisabled(disabled);
  68701. }
  68702. }
  68703. }, function() {
  68704. });
  68705. /**
  68706. * @aside guide forms
  68707. *
  68708. * The slider is a way to allow the user to select a value from a given numerical range. You might use it for choosing
  68709. * a percentage, combine two of them to get min and max values, or use three of them to specify the hex values for a
  68710. * color. Each slider contains a single 'thumb' that can be dragged along the slider's length to change the value.
  68711. * Sliders are equally useful inside {@link Ext.form.Panel forms} and standalone. Here's how to quickly create a
  68712. * slider in form, in this case enabling a user to choose a percentage:
  68713. *
  68714. * @example
  68715. * Ext.create('Ext.form.Panel', {
  68716. * fullscreen: true,
  68717. * items: [
  68718. * {
  68719. * xtype: 'sliderfield',
  68720. * label: 'Percentage',
  68721. * value: 50,
  68722. * minValue: 0,
  68723. * maxValue: 100
  68724. * }
  68725. * ]
  68726. * });
  68727. *
  68728. * In this case we set a starting value of 50%, and defined the min and max values to be 0 and 100 respectively, giving
  68729. * us a percentage slider. Because this is such a common use case, the defaults for {@link #minValue} and
  68730. * {@link #maxValue} are already set to 0 and 100 so in the example above they could be removed.
  68731. *
  68732. * It's often useful to render sliders outside the context of a form panel too. In this example we create a slider that
  68733. * allows a user to choose the waist measurement of a pair of jeans. Let's say the online store we're making this for
  68734. * sells jeans with waist sizes from 24 inches to 60 inches in 2 inch increments - here's how we might achieve that:
  68735. *
  68736. * @example
  68737. * Ext.create('Ext.form.Panel', {
  68738. * fullscreen: true,
  68739. * items: [
  68740. * {
  68741. * xtype: 'sliderfield',
  68742. * label: 'Waist Measurement',
  68743. * minValue: 24,
  68744. * maxValue: 60,
  68745. * increment: 2,
  68746. * value: 32
  68747. * }
  68748. * ]
  68749. * });
  68750. *
  68751. * Now that we've got our slider, we can ask it what value it currently has and listen to events that it fires. For
  68752. * example, if we wanted our app to show different images for different sizes, we can listen to the {@link #change}
  68753. * event to be informed whenever the slider is moved:
  68754. *
  68755. * slider.on('change', function(field, newValue) {
  68756. * if (newValue[0] > 40) {
  68757. * imgComponent.setSrc('large.png');
  68758. * } else {
  68759. * imgComponent.setSrc('small.png');
  68760. * }
  68761. * }, this);
  68762. *
  68763. * Here we listened to the {@link #change} event on the slider and updated the background image of an
  68764. * {@link Ext.Img image component} based on what size the user selected. Of course, you can use any logic inside your
  68765. * event listener.
  68766. */
  68767. Ext.define('Ext.field.Slider', {
  68768. extend : 'Ext.field.Field',
  68769. xtype : 'sliderfield',
  68770. requires: ['Ext.slider.Slider'],
  68771. alternateClassName: 'Ext.form.Slider',
  68772. /**
  68773. * @event change
  68774. * Fires when an option selection has changed.
  68775. * @param {Ext.field.Slider} me
  68776. * @param {Ext.slider.Slider} sl Slider Component.
  68777. * @param {Ext.slider.Thumb} thumb
  68778. * @param {Number} newValue The new value of this thumb.
  68779. * @param {Number} oldValue The old value of this thumb.
  68780. */
  68781. /**
  68782. * @event dragstart
  68783. * Fires when the slider thumb starts a drag operation.
  68784. * @param {Ext.field.Slider} this
  68785. * @param {Ext.slider.Slider} sl Slider Component.
  68786. * @param {Ext.slider.Thumb} thumb The thumb being dragged.
  68787. * @param {Array} value The start value.
  68788. * @param {Ext.EventObject} e
  68789. */
  68790. /**
  68791. * @event drag
  68792. * Fires when the slider thumb starts a drag operation.
  68793. * @param {Ext.field.Slider} this
  68794. * @param {Ext.slider.Slider} sl Slider Component.
  68795. * @param {Ext.slider.Thumb} thumb The thumb being dragged.
  68796. * @param {Ext.EventObject} e
  68797. */
  68798. /**
  68799. * @event dragend
  68800. * Fires when the slider thumb ends a drag operation.
  68801. * @param {Ext.field.Slider} this
  68802. * @param {Ext.slider.Slider} sl Slider Component.
  68803. * @param {Ext.slider.Thumb} thumb The thumb being dragged.
  68804. * @param {Array} value The end value.
  68805. * @param {Ext.EventObject} e
  68806. */
  68807. config: {
  68808. /**
  68809. * @cfg
  68810. * @inheritdoc
  68811. */
  68812. cls: Ext.baseCSSPrefix + 'slider-field',
  68813. /**
  68814. * @cfg
  68815. * @inheritdoc
  68816. */
  68817. tabIndex: -1,
  68818. /**
  68819. * Will make this field read only, meaning it cannot be changed with used interaction.
  68820. * @cfg {Boolean} readOnly
  68821. * @accessor
  68822. */
  68823. readOnly: false
  68824. },
  68825. proxyConfig: {
  68826. /**
  68827. * @inheritdoc Ext.slider.Slider#increment
  68828. * @cfg {Number} increment
  68829. * @accessor
  68830. */
  68831. increment : 1,
  68832. /**
  68833. * @inheritdoc Ext.slider.Slider#value
  68834. * @cfg {Number/Number[]} value
  68835. * @accessor
  68836. */
  68837. value: 0,
  68838. /**
  68839. * @inheritdoc Ext.slider.Slider#minValue
  68840. * @cfg {Number} minValue
  68841. * @accessor
  68842. */
  68843. minValue: 0,
  68844. /**
  68845. * @inheritdoc Ext.slider.Slider#maxValue
  68846. * @cfg {Number} maxValue
  68847. * @accessor
  68848. */
  68849. maxValue: 100
  68850. },
  68851. /**
  68852. * @inheritdoc Ext.slider.Slider#values
  68853. * @cfg {Number/Number[]} values
  68854. */
  68855. constructor: function(config) {
  68856. config = config || {};
  68857. if (config.hasOwnProperty('values')) {
  68858. config.value = config.values;
  68859. }
  68860. this.callParent([config]);
  68861. },
  68862. // @private
  68863. initialize: function() {
  68864. this.callParent();
  68865. this.getComponent().on({
  68866. scope: this,
  68867. change: 'onSliderChange',
  68868. dragstart: 'onSliderDragStart',
  68869. drag: 'onSliderDrag',
  68870. dragend: 'onSliderDragEnd'
  68871. });
  68872. },
  68873. // @private
  68874. applyComponent: function(config) {
  68875. return Ext.factory(config, Ext.slider.Slider);
  68876. },
  68877. onSliderChange: function() {
  68878. this.fireEvent.apply(this, [].concat('change', this, Array.prototype.slice.call(arguments)));
  68879. },
  68880. onSliderDragStart: function() {
  68881. this.fireEvent.apply(this, [].concat('dragstart', this, Array.prototype.slice.call(arguments)));
  68882. },
  68883. onSliderDrag: function() {
  68884. this.fireEvent.apply(this, [].concat('drag', this, Array.prototype.slice.call(arguments)));
  68885. },
  68886. onSliderDragEnd: function() {
  68887. this.fireEvent.apply(this, [].concat('dragend', this, Array.prototype.slice.call(arguments)));
  68888. },
  68889. /**
  68890. * Convenience method. Calls {@link #setValue}.
  68891. * @param {Object} value
  68892. */
  68893. setValues: function(value) {
  68894. this.setValue(value);
  68895. },
  68896. /**
  68897. * Convenience method. Calls {@link #getValue}
  68898. * @return {Object}
  68899. */
  68900. getValues: function() {
  68901. return this.getValue();
  68902. },
  68903. reset: function() {
  68904. var config = this.config,
  68905. initialValue = (this.config.hasOwnProperty('values')) ? config.values : config.value;
  68906. this.setValue(initialValue);
  68907. },
  68908. doSetDisabled: function(disabled) {
  68909. this.callParent(arguments);
  68910. this.getComponent().setDisabled(disabled);
  68911. },
  68912. updateReadOnly: function(newValue) {
  68913. this.getComponent().setReadOnly(newValue);
  68914. },
  68915. isDirty : function () {
  68916. if (this.getDisabled()) {
  68917. return false;
  68918. }
  68919. return this.getValue() !== this.originalValue;
  68920. }
  68921. });
  68922. /**
  68923. * A wrapper class which can be applied to any element. Fires a "tap" event while
  68924. * touching the device. The interval between firings may be specified in the config but
  68925. * defaults to 20 milliseconds.
  68926. */
  68927. Ext.define('Ext.util.TapRepeater', {
  68928. requires: ['Ext.DateExtras'],
  68929. mixins: {
  68930. observable: 'Ext.mixin.Observable'
  68931. },
  68932. /**
  68933. * @event touchstart
  68934. * Fires when the touch is started.
  68935. * @param {Ext.util.TapRepeater} this
  68936. * @param {Ext.event.Event} e
  68937. */
  68938. /**
  68939. * @event tap
  68940. * Fires on a specified interval during the time the element is pressed.
  68941. * @param {Ext.util.TapRepeater} this
  68942. * @param {Ext.event.Event} e
  68943. */
  68944. /**
  68945. * @event touchend
  68946. * Fires when the touch is ended.
  68947. * @param {Ext.util.TapRepeater} this
  68948. * @param {Ext.event.Event} e
  68949. */
  68950. config: {
  68951. el: null,
  68952. accelerate: true,
  68953. interval: 10,
  68954. delay: 250,
  68955. preventDefault: true,
  68956. stopDefault: false,
  68957. timer: 0,
  68958. pressCls: null
  68959. },
  68960. /**
  68961. * Creates new TapRepeater.
  68962. * @param {Mixed} el The element to listen on
  68963. * @param {Object} config
  68964. */
  68965. constructor: function(config) {
  68966. var me = this;
  68967. //<debug warn>
  68968. for (var configName in config) {
  68969. if (me.self.prototype.config && !(configName in me.self.prototype.config)) {
  68970. me[configName] = config[configName];
  68971. Ext.Logger.warn('Applied config as instance property: "' + configName + '"', me);
  68972. }
  68973. }
  68974. //</debug>
  68975. me.initConfig(config);
  68976. },
  68977. updateEl: function(newEl, oldEl) {
  68978. var eventCfg = {
  68979. touchstart: 'onTouchStart',
  68980. touchend: 'onTouchEnd',
  68981. tap: 'eventOptions',
  68982. scope: this
  68983. };
  68984. if (oldEl) {
  68985. oldEl.un(eventCfg)
  68986. }
  68987. newEl.on(eventCfg);
  68988. },
  68989. // @private
  68990. eventOptions: function(e) {
  68991. if (this.getPreventDefault()) {
  68992. e.preventDefault();
  68993. }
  68994. if (this.getStopDefault()) {
  68995. e.stopEvent();
  68996. }
  68997. },
  68998. // @private
  68999. destroy: function() {
  69000. this.clearListeners();
  69001. Ext.destroy(this.el);
  69002. },
  69003. // @private
  69004. onTouchStart: function(e) {
  69005. var me = this,
  69006. pressCls = me.getPressCls();
  69007. clearTimeout(me.getTimer());
  69008. if (pressCls) {
  69009. me.getEl().addCls(pressCls);
  69010. }
  69011. me.tapStartTime = new Date();
  69012. me.fireEvent('touchstart', me, e);
  69013. me.fireEvent('tap', me, e);
  69014. // Do not honor delay or interval if acceleration wanted.
  69015. if (me.getAccelerate()) {
  69016. me.delay = 400;
  69017. }
  69018. me.setTimer(Ext.defer(me.tap, me.getDelay() || me.getInterval(), me, [e]));
  69019. },
  69020. // @private
  69021. tap: function(e) {
  69022. var me = this;
  69023. me.fireEvent('tap', me, e);
  69024. me.setTimer(Ext.defer(me.tap, me.getAccelerate() ? me.easeOutExpo(Ext.Date.getElapsed(me.tapStartTime),
  69025. 400,
  69026. -390,
  69027. 12000) : me.getInterval(), me, [e]));
  69028. },
  69029. // Easing calculation
  69030. // @private
  69031. easeOutExpo: function(t, b, c, d) {
  69032. return (t == d) ? b + c : c * ( - Math.pow(2, -10 * t / d) + 1) + b;
  69033. },
  69034. // @private
  69035. onTouchEnd: function(e) {
  69036. var me = this;
  69037. clearTimeout(me.getTimer());
  69038. me.getEl().removeCls(me.getPressCls());
  69039. me.fireEvent('touchend', me, e);
  69040. }
  69041. });
  69042. /**
  69043. * @aside guide forms
  69044. *
  69045. * Wraps an HTML5 number field. Example usage:
  69046. *
  69047. * @example miniphone
  69048. * var spinner = Ext.create('Ext.field.Spinner', {
  69049. * label: 'Spinner Field',
  69050. * minValue: 0,
  69051. * maxValue: 100,
  69052. * increment: 2,
  69053. * cycle: true
  69054. * });
  69055. * Ext.Viewport.add({ xtype: 'container', items: [spinner] });
  69056. *
  69057. */
  69058. Ext.define('Ext.field.Spinner', {
  69059. extend: 'Ext.field.Number',
  69060. xtype: 'spinnerfield',
  69061. alternateClassName: 'Ext.form.Spinner',
  69062. requires: ['Ext.util.TapRepeater'],
  69063. /**
  69064. * @event spin
  69065. * Fires when the value is changed via either spinner buttons.
  69066. * @param {Ext.field.Spinner} this
  69067. * @param {Number} value
  69068. * @param {String} direction 'up' or 'down'.
  69069. */
  69070. /**
  69071. * @event spindown
  69072. * Fires when the value is changed via the spinner down button.
  69073. * @param {Ext.field.Spinner} this
  69074. * @param {Number} value
  69075. */
  69076. /**
  69077. * @event spinup
  69078. * Fires when the value is changed via the spinner up button.
  69079. * @param {Ext.field.Spinner} this
  69080. * @param {Number} value
  69081. */
  69082. /**
  69083. * @event change
  69084. * Fires just before the field blurs if the field value has changed.
  69085. * @param {Ext.field.Text} this This field.
  69086. * @param {Number} newValue The new value.
  69087. * @param {Number} oldValue The original value.
  69088. */
  69089. /**
  69090. * @event updatedata
  69091. * @hide
  69092. */
  69093. /**
  69094. * @event action
  69095. * @hide
  69096. */
  69097. config: {
  69098. /**
  69099. * @cfg
  69100. * @inheritdoc
  69101. */
  69102. cls: Ext.baseCSSPrefix + 'spinner',
  69103. /**
  69104. * @cfg {Number} [minValue=-infinity] The minimum allowed value.
  69105. * @accessor
  69106. */
  69107. minValue: Number.NEGATIVE_INFINITY,
  69108. /**
  69109. * @cfg {Number} [maxValue=infinity] The maximum allowed value.
  69110. * @accessor
  69111. */
  69112. maxValue: Number.MAX_VALUE,
  69113. /**
  69114. * @cfg {Number} stepValue Value that is added or subtracted from the current value when a spinner is used.
  69115. * @accessor
  69116. */
  69117. stepValue: 0.1,
  69118. /**
  69119. * @cfg {Boolean} accelerateOnTapHold True if autorepeating should start slowly and accelerate.
  69120. * @accessor
  69121. */
  69122. accelerateOnTapHold: true,
  69123. /**
  69124. * @cfg {Boolean} cycle When set to `true`, it will loop the values of a minimum or maximum is reached.
  69125. * If the maximum value is reached, the value will be set to the minimum.
  69126. * @accessor
  69127. */
  69128. cycle: false,
  69129. /**
  69130. * @cfg {Boolean} clearIcon
  69131. * @hide
  69132. * @accessor
  69133. */
  69134. clearIcon: false,
  69135. /**
  69136. * @cfg {Number} defaultValue The default value for this field when no value has been set.
  69137. * It is also used when the value is set to `NaN`.
  69138. */
  69139. defaultValue: 0,
  69140. /**
  69141. * @cfg {Number} tabIndex
  69142. * @hide
  69143. */
  69144. tabIndex: -1,
  69145. /**
  69146. * @cfg {Boolean} groupButtons
  69147. * `true` if you want to group the buttons to the right of the fields. `false` if you want the buttons
  69148. * to be at either side of the field.
  69149. */
  69150. groupButtons: true,
  69151. /**
  69152. * @cfg
  69153. * @inheritdoc
  69154. */
  69155. component: {
  69156. disabled: true
  69157. }
  69158. },
  69159. constructor: function() {
  69160. var me = this;
  69161. me.callParent(arguments);
  69162. if (!me.getValue()) {
  69163. me.suspendEvents();
  69164. me.setValue(me.getDefaultValue());
  69165. me.resumeEvents();
  69166. }
  69167. },
  69168. syncEmptyCls: Ext.emptyFn,
  69169. /**
  69170. * Updates the {@link #component} configuration
  69171. */
  69172. updateComponent: function(newComponent) {
  69173. this.callParent(arguments);
  69174. var innerElement = this.innerElement,
  69175. cls = this.getCls();
  69176. if (newComponent) {
  69177. this.spinDownButton = Ext.Element.create({
  69178. cls : cls + '-button ' + cls + '-button-down',
  69179. html: '-'
  69180. });
  69181. this.spinUpButton = Ext.Element.create({
  69182. cls : cls + '-button ' + cls + '-button-up',
  69183. html: '+'
  69184. });
  69185. this.downRepeater = this.createRepeater(this.spinDownButton, this.onSpinDown);
  69186. this.upRepeater = this.createRepeater(this.spinUpButton, this.onSpinUp);
  69187. }
  69188. },
  69189. updateGroupButtons: function(newGroupButtons, oldGroupButtons) {
  69190. var me = this,
  69191. innerElement = me.innerElement,
  69192. cls = me.getBaseCls() + '-grouped-buttons';
  69193. me.getComponent();
  69194. if (newGroupButtons != oldGroupButtons) {
  69195. if (newGroupButtons) {
  69196. this.addCls(cls);
  69197. innerElement.appendChild(me.spinDownButton);
  69198. innerElement.appendChild(me.spinUpButton);
  69199. } else {
  69200. this.removeCls(cls);
  69201. innerElement.insertFirst(me.spinDownButton);
  69202. innerElement.appendChild(me.spinUpButton);
  69203. }
  69204. }
  69205. },
  69206. applyValue: function(value) {
  69207. value = parseFloat(value);
  69208. if (isNaN(value) || value === null) {
  69209. value = this.getDefaultValue();
  69210. }
  69211. //round the value to 1 decimal
  69212. value = Math.round(value * 10) / 10;
  69213. return this.callParent([value]);
  69214. },
  69215. // @private
  69216. createRepeater: function(el, fn) {
  69217. var me = this,
  69218. repeater = Ext.create('Ext.util.TapRepeater', {
  69219. el: el,
  69220. accelerate: me.getAccelerateOnTapHold()
  69221. });
  69222. repeater.on({
  69223. tap: fn,
  69224. touchstart: 'onTouchStart',
  69225. touchend: 'onTouchEnd',
  69226. scope: me
  69227. });
  69228. return repeater;
  69229. },
  69230. // @private
  69231. onSpinDown: function() {
  69232. if (!this.getDisabled() && !this.getReadOnly()) {
  69233. this.spin(true);
  69234. }
  69235. },
  69236. // @private
  69237. onSpinUp: function() {
  69238. if (!this.getDisabled() && !this.getReadOnly()) {
  69239. this.spin(false);
  69240. }
  69241. },
  69242. // @private
  69243. onTouchStart: function(repeater) {
  69244. if (!this.getDisabled() && !this.getReadOnly()) {
  69245. repeater.getEl().addCls(Ext.baseCSSPrefix + 'button-pressed');
  69246. }
  69247. },
  69248. // @private
  69249. onTouchEnd: function(repeater) {
  69250. repeater.getEl().removeCls(Ext.baseCSSPrefix + 'button-pressed');
  69251. },
  69252. // @private
  69253. spin: function(down) {
  69254. var me = this,
  69255. originalValue = me.getValue(),
  69256. stepValue = me.getStepValue(),
  69257. direction = down ? 'down' : 'up',
  69258. minValue = me.getMinValue(),
  69259. maxValue = me.getMaxValue(),
  69260. value;
  69261. if (down) {
  69262. value = originalValue - stepValue;
  69263. }
  69264. else {
  69265. value = originalValue + stepValue;
  69266. }
  69267. //if cycle is true, then we need to check fi the value hasn't changed and we cycle the value
  69268. if (me.getCycle()) {
  69269. if (originalValue == minValue && value < minValue) {
  69270. value = maxValue;
  69271. }
  69272. if (originalValue == maxValue && value > maxValue) {
  69273. value = minValue;
  69274. }
  69275. }
  69276. me.setValue(value);
  69277. value = me.getValue();
  69278. me.fireEvent('spin', me, value, direction);
  69279. me.fireEvent('spin' + direction, me, value);
  69280. },
  69281. /**
  69282. * @private
  69283. */
  69284. doSetDisabled: function(disabled) {
  69285. Ext.Component.prototype.doSetDisabled.apply(this, arguments);
  69286. },
  69287. /**
  69288. * @private
  69289. */
  69290. setDisabled: function() {
  69291. Ext.Component.prototype.setDisabled.apply(this, arguments);
  69292. },
  69293. reset: function() {
  69294. this.setValue(this.getDefaultValue());
  69295. },
  69296. // @private
  69297. destroy: function() {
  69298. var me = this;
  69299. Ext.destroy(me.downRepeater, me.upRepeater, me.spinDownButton, me.spinUpButton);
  69300. me.callParent(arguments);
  69301. }
  69302. }, function() {
  69303. });
  69304. /**
  69305. * @private
  69306. */
  69307. Ext.define('Ext.slider.Toggle', {
  69308. extend: 'Ext.slider.Slider',
  69309. config: {
  69310. /**
  69311. * @cfg
  69312. * @inheritdoc
  69313. */
  69314. baseCls: 'x-toggle',
  69315. /**
  69316. * @cfg {String} minValueCls CSS class added to the field when toggled to its minValue
  69317. * @accessor
  69318. */
  69319. minValueCls: 'x-toggle-off',
  69320. /**
  69321. * @cfg {String} maxValueCls CSS class added to the field when toggled to its maxValue
  69322. * @accessor
  69323. */
  69324. maxValueCls: 'x-toggle-on'
  69325. },
  69326. initialize: function() {
  69327. this.callParent();
  69328. this.on({
  69329. change: 'onChange'
  69330. });
  69331. },
  69332. applyMinValue: function() {
  69333. return 0;
  69334. },
  69335. applyMaxValue: function() {
  69336. return 1;
  69337. },
  69338. applyIncrement: function() {
  69339. return 1;
  69340. },
  69341. updateMinValueCls: function(newCls, oldCls) {
  69342. var element = this.element;
  69343. if (oldCls && element.hasCls(oldCls)) {
  69344. element.replaceCls(oldCls, newCls);
  69345. }
  69346. },
  69347. updateMaxValueCls: function(newCls, oldCls) {
  69348. var element = this.element;
  69349. if (oldCls && element.hasCls(oldCls)) {
  69350. element.replaceCls(oldCls, newCls);
  69351. }
  69352. },
  69353. setValue: function(newValue, oldValue) {
  69354. this.callParent(arguments);
  69355. this.onChange(this, this.getThumbs()[0], newValue, oldValue);
  69356. },
  69357. onChange: function(me, thumb, newValue, oldValue) {
  69358. var isOn = newValue > 0,
  69359. onCls = me.getMaxValueCls(),
  69360. offCls = me.getMinValueCls();
  69361. this.element.addCls(isOn ? onCls : offCls);
  69362. this.element.removeCls(isOn ? offCls : onCls);
  69363. },
  69364. toggle: function() {
  69365. var value = this.getValue();
  69366. this.setValue((value == 1) ? 0 : 1);
  69367. return this;
  69368. },
  69369. onTap: function() {
  69370. if (this.isDisabled() || this.getReadOnly()) {
  69371. return;
  69372. }
  69373. var oldValue = this.getValue(),
  69374. newValue = (oldValue == 1) ? 0 : 1,
  69375. thumb = this.getThumb(0);
  69376. this.setIndexValue(0, newValue, this.getAnimation());
  69377. this.refreshThumbConstraints(thumb);
  69378. this.fireEvent('change', this, thumb, newValue, oldValue);
  69379. }
  69380. });
  69381. /**
  69382. * @aside guide forms
  69383. *
  69384. * Specialized {@link Ext.field.Slider} with a single thumb which only supports two {@link #value values}.
  69385. *
  69386. * ## Examples
  69387. *
  69388. * @example miniphone preview
  69389. * Ext.Viewport.add({
  69390. * xtype: 'togglefield',
  69391. * name: 'awesome',
  69392. * label: 'Are you awesome?',
  69393. * labelWidth: '40%'
  69394. * });
  69395. *
  69396. * Having a default value of 'toggled':
  69397. *
  69398. * @example miniphone preview
  69399. * Ext.Viewport.add({
  69400. * xtype: 'togglefield',
  69401. * name: 'awesome',
  69402. * value: 1,
  69403. * label: 'Are you awesome?',
  69404. * labelWidth: '40%'
  69405. * });
  69406. *
  69407. * And using the {@link #value} {@link #toggle} method:
  69408. *
  69409. * @example miniphone preview
  69410. * Ext.Viewport.add([
  69411. * {
  69412. * xtype: 'togglefield',
  69413. * name: 'awesome',
  69414. * value: 1,
  69415. * label: 'Are you awesome?',
  69416. * labelWidth: '40%'
  69417. * },
  69418. * {
  69419. * xtype: 'toolbar',
  69420. * docked: 'top',
  69421. * items: [
  69422. * {
  69423. * xtype: 'button',
  69424. * text: 'Toggle',
  69425. * flex: 1,
  69426. * handler: function() {
  69427. * Ext.ComponentQuery.query('togglefield')[0].toggle();
  69428. * }
  69429. * }
  69430. * ]
  69431. * }
  69432. * ]);
  69433. */
  69434. Ext.define('Ext.field.Toggle', {
  69435. extend: 'Ext.field.Slider',
  69436. xtype : 'togglefield',
  69437. alternateClassName: 'Ext.form.Toggle',
  69438. requires: ['Ext.slider.Toggle'],
  69439. config: {
  69440. /**
  69441. * @cfg
  69442. * @inheritdoc
  69443. */
  69444. cls: 'x-toggle-field'
  69445. },
  69446. /**
  69447. * @event change
  69448. * Fires when an option selection has changed.
  69449. *
  69450. * Ext.Viewport.add({
  69451. * xtype: 'togglefield',
  69452. * label: 'Event Example',
  69453. * listeners: {
  69454. * change: function(field, newValue) {
  69455. * console.log('Value of this toggle has changed:', (newValue) ? 'ON' : 'OFF');
  69456. * }
  69457. * }
  69458. * });
  69459. *
  69460. * @param {Ext.field.Toggle} me
  69461. * @param {Number} newValue the new value of this thumb
  69462. * @param {Number} oldValue the old value of this thumb
  69463. */
  69464. /**
  69465. * @event dragstart
  69466. * @hide
  69467. */
  69468. /**
  69469. * @event drag
  69470. * @hide
  69471. */
  69472. /**
  69473. * @event dragend
  69474. * @hide
  69475. */
  69476. proxyConfig: {
  69477. /**
  69478. * @cfg {String} minValueCls See {@link Ext.slider.Toggle#minValueCls}
  69479. * @accessor
  69480. */
  69481. minValueCls: 'x-toggle-off',
  69482. /**
  69483. * @cfg {String} maxValueCls See {@link Ext.slider.Toggle#maxValueCls}
  69484. * @accessor
  69485. */
  69486. maxValueCls: 'x-toggle-on'
  69487. },
  69488. // @private
  69489. applyComponent: function(config) {
  69490. return Ext.factory(config, Ext.slider.Toggle);
  69491. },
  69492. /**
  69493. * Sets the value of the toggle.
  69494. * @param {Number} value **1** for toggled, **0** for untoggled.
  69495. * @return {Object} this
  69496. */
  69497. setValue: function(newValue) {
  69498. if (newValue === true) {
  69499. newValue = 1;
  69500. }
  69501. var oldValue = this.getValue();
  69502. if (oldValue != newValue) {
  69503. this.getComponent().setValue(newValue);
  69504. this.fireEvent('change', this, newValue, oldValue);
  69505. }
  69506. return this;
  69507. },
  69508. getValue: function() {
  69509. return (this.getComponent().getValue() == 1) ? 1 : 0;
  69510. },
  69511. /**
  69512. * Toggles the value of this toggle field.
  69513. * @return {Object} this
  69514. */
  69515. toggle: function() {
  69516. // We call setValue directly so the change event can be fired
  69517. var value = this.getValue();
  69518. this.setValue((value == 1) ? 0 : 1);
  69519. return this;
  69520. }
  69521. });
  69522. /**
  69523. * @aside guide forms
  69524. *
  69525. * The Url field creates an HTML5 url input and is usually created inside a form. Because it creates an HTML url input
  69526. * field, most browsers will show a specialized virtual keyboard for web address input. Aside from that, the url field
  69527. * is just a normal text field. Here's an example of how to use it in a form:
  69528. *
  69529. * @example
  69530. * Ext.create('Ext.form.Panel', {
  69531. * fullscreen: true,
  69532. * items: [
  69533. * {
  69534. * xtype: 'fieldset',
  69535. * title: 'Add Bookmark',
  69536. * items: [
  69537. * {
  69538. * xtype: 'urlfield',
  69539. * label: 'Url',
  69540. * name: 'url'
  69541. * }
  69542. * ]
  69543. * }
  69544. * ]
  69545. * });
  69546. *
  69547. * Or on its own, outside of a form:
  69548. *
  69549. * Ext.create('Ext.field.Url', {
  69550. * label: 'Web address',
  69551. * value: 'http://sencha.com'
  69552. * });
  69553. *
  69554. * Because url field inherits from {@link Ext.field.Text textfield} it gains all of the functionality that text fields
  69555. * provide, including getting and setting the value at runtime, validations and various events that are fired as the
  69556. * user interacts with the component. Check out the {@link Ext.field.Text} docs to see the additional functionality
  69557. * available.
  69558. */
  69559. Ext.define('Ext.field.Url', {
  69560. extend: 'Ext.field.Text',
  69561. xtype: 'urlfield',
  69562. alternateClassName: 'Ext.form.Url',
  69563. config: {
  69564. /**
  69565. * @cfg
  69566. * @inheritdoc
  69567. */
  69568. autoCapitalize: false,
  69569. /**
  69570. * @cfg
  69571. * @inheritdoc
  69572. */
  69573. component: {
  69574. type: 'url'
  69575. }
  69576. }
  69577. });
  69578. /**
  69579. * @aside guide forms
  69580. * @aside example forms
  69581. * @aside example forms-toolbars
  69582. *
  69583. * A FieldSet is a great way to visually separate elements of a form. It's normally used when you have a form with
  69584. * fields that can be divided into groups - for example a customer's billing details in one fieldset and their shipping
  69585. * address in another. A fieldset can be used inside a form or on its own elsewhere in your app. Fieldsets can
  69586. * optionally have a title at the top and instructions at the bottom. Here's how we might create a FieldSet inside a
  69587. * form:
  69588. *
  69589. * @example
  69590. * Ext.create('Ext.form.Panel', {
  69591. * fullscreen: true,
  69592. * items: [
  69593. * {
  69594. * xtype: 'fieldset',
  69595. * title: 'About You',
  69596. * instructions: 'Tell us all about yourself',
  69597. * items: [
  69598. * {
  69599. * xtype: 'textfield',
  69600. * name : 'firstName',
  69601. * label: 'First Name'
  69602. * },
  69603. * {
  69604. * xtype: 'textfield',
  69605. * name : 'lastName',
  69606. * label: 'Last Name'
  69607. * }
  69608. * ]
  69609. * }
  69610. * ]
  69611. * });
  69612. *
  69613. * Above we created a {@link Ext.form.Panel form} with a fieldset that contains two text fields. In this case, all
  69614. * of the form fields are in the same fieldset, but for longer forms we may choose to use multiple fieldsets. We also
  69615. * configured a {@link #title} and {@link #instructions} to give the user more information on filling out the form if
  69616. * required.
  69617. */
  69618. Ext.define('Ext.form.FieldSet', {
  69619. extend : 'Ext.Container',
  69620. alias : 'widget.fieldset',
  69621. requires: ['Ext.Title'],
  69622. config: {
  69623. /**
  69624. * @cfg
  69625. * @inheritdoc
  69626. */
  69627. baseCls: Ext.baseCSSPrefix + 'form-fieldset',
  69628. /**
  69629. * @cfg {String} title
  69630. * Optional fieldset title, rendered just above the grouped fields.
  69631. *
  69632. * ## Example
  69633. *
  69634. * Ext.create('Ext.form.Fieldset', {
  69635. * fullscreen: true,
  69636. *
  69637. * title: 'Login',
  69638. *
  69639. * items: [{
  69640. * xtype: 'textfield',
  69641. * label: 'Email'
  69642. * }]
  69643. * });
  69644. *
  69645. * @accessor
  69646. */
  69647. title: null,
  69648. /**
  69649. * @cfg {String} instructions
  69650. * Optional fieldset instructions, rendered just below the grouped fields.
  69651. *
  69652. * ## Example
  69653. *
  69654. * Ext.create('Ext.form.Fieldset', {
  69655. * fullscreen: true,
  69656. *
  69657. * instructions: 'Please enter your email address.',
  69658. *
  69659. * items: [{
  69660. * xtype: 'textfield',
  69661. * label: 'Email'
  69662. * }]
  69663. * });
  69664. *
  69665. * @accessor
  69666. */
  69667. instructions: null
  69668. },
  69669. // @private
  69670. applyTitle: function(title) {
  69671. if (typeof title == 'string') {
  69672. title = {title: title};
  69673. }
  69674. Ext.applyIf(title, {
  69675. docked : 'top',
  69676. baseCls: this.getBaseCls() + '-title'
  69677. });
  69678. return Ext.factory(title, Ext.Title, this._title);
  69679. },
  69680. // @private
  69681. updateTitle: function(newTitle, oldTitle) {
  69682. if (newTitle) {
  69683. this.add(newTitle);
  69684. }
  69685. if (oldTitle) {
  69686. this.remove(oldTitle);
  69687. }
  69688. },
  69689. // @private
  69690. getTitle: function() {
  69691. var title = this._title;
  69692. if (title && title instanceof Ext.Title) {
  69693. return title.getTitle();
  69694. }
  69695. return title;
  69696. },
  69697. // @private
  69698. applyInstructions: function(instructions) {
  69699. if (typeof instructions == 'string') {
  69700. instructions = {title: instructions};
  69701. }
  69702. Ext.applyIf(instructions, {
  69703. docked : 'bottom',
  69704. baseCls: this.getBaseCls() + '-instructions'
  69705. });
  69706. return Ext.factory(instructions, Ext.Title, this._instructions);
  69707. },
  69708. // @private
  69709. updateInstructions: function(newInstructions, oldInstructions) {
  69710. if (newInstructions) {
  69711. this.add(newInstructions);
  69712. }
  69713. if (oldInstructions) {
  69714. this.remove(oldInstructions);
  69715. }
  69716. },
  69717. // @private
  69718. getInstructions: function() {
  69719. var instructions = this._instructions;
  69720. if (instructions && instructions instanceof Ext.Title) {
  69721. return instructions.getTitle();
  69722. }
  69723. return instructions;
  69724. },
  69725. /**
  69726. * A convenient method to disable all fields in this FieldSet
  69727. * @return {Ext.form.FieldSet} This FieldSet
  69728. */
  69729. doSetDisabled: function(newDisabled) {
  69730. this.getFieldsAsArray().forEach(function(field) {
  69731. field.setDisabled(newDisabled);
  69732. });
  69733. return this;
  69734. },
  69735. /**
  69736. * @private
  69737. */
  69738. getFieldsAsArray: function() {
  69739. var fields = [],
  69740. getFieldsFrom = function(item) {
  69741. if (item.isField) {
  69742. fields.push(item);
  69743. }
  69744. if (item.isContainer) {
  69745. item.getItems().each(getFieldsFrom);
  69746. }
  69747. };
  69748. this.getItems().each(getFieldsFrom);
  69749. return fields;
  69750. }
  69751. });
  69752. /**
  69753. * The Form panel presents a set of form fields and provides convenient ways to load and save data. Usually a form
  69754. * panel just contains the set of fields you want to display, ordered inside the items configuration like this:
  69755. *
  69756. * @example
  69757. * var form = Ext.create('Ext.form.Panel', {
  69758. * fullscreen: true,
  69759. * items: [
  69760. * {
  69761. * xtype: 'textfield',
  69762. * name: 'name',
  69763. * label: 'Name'
  69764. * },
  69765. * {
  69766. * xtype: 'emailfield',
  69767. * name: 'email',
  69768. * label: 'Email'
  69769. * },
  69770. * {
  69771. * xtype: 'passwordfield',
  69772. * name: 'password',
  69773. * label: 'Password'
  69774. * }
  69775. * ]
  69776. * });
  69777. *
  69778. * Here we just created a simple form panel which could be used as a registration form to sign up to your service. We
  69779. * added a plain {@link Ext.field.Text text field} for the user's Name, an {@link Ext.field.Email email field} and
  69780. * finally a {@link Ext.field.Password password field}. In each case we provided a {@link Ext.field.Field#name name}
  69781. * config on the field so that we can identify it later on when we load and save data on the form.
  69782. *
  69783. * ##Loading data
  69784. *
  69785. * Using the form we created above, we can load data into it in a few different ways, the easiest is to use
  69786. * {@link #setValues}:
  69787. *
  69788. * form.setValues({
  69789. * name: 'Ed',
  69790. * email: 'ed@sencha.com',
  69791. * password: 'secret'
  69792. * });
  69793. *
  69794. * It's also easy to load {@link Ext.data.Model Model} instances into a form - let's say we have a User model and want
  69795. * to load a particular instance into our form:
  69796. *
  69797. * Ext.define('MyApp.model.User', {
  69798. * extend: 'Ext.data.Model',
  69799. * config: {
  69800. * fields: ['name', 'email', 'password']
  69801. * }
  69802. * });
  69803. *
  69804. * var ed = Ext.create('MyApp.model.User', {
  69805. * name: 'Ed',
  69806. * email: 'ed@sencha.com',
  69807. * password: 'secret'
  69808. * });
  69809. *
  69810. * form.setRecord(ed);
  69811. *
  69812. * ##Retrieving form data
  69813. *
  69814. * Getting data out of the form panel is simple and is usually achieve via the {@link #getValues} method:
  69815. *
  69816. * var values = form.getValues();
  69817. *
  69818. * //values now looks like this:
  69819. * {
  69820. * name: 'Ed',
  69821. * email: 'ed@sencha.com',
  69822. * password: 'secret'
  69823. * }
  69824. *
  69825. * It's also possible to listen to the change events on individual fields to get more timely notification of changes
  69826. * that the user is making. Here we expand on the example above with the User model, updating the model as soon as
  69827. * any of the fields are changed:
  69828. *
  69829. * var form = Ext.create('Ext.form.Panel', {
  69830. * listeners: {
  69831. * '> field': {
  69832. * change: function(field, newValue, oldValue) {
  69833. * ed.set(field.getName(), newValue);
  69834. * }
  69835. * }
  69836. * },
  69837. * items: [
  69838. * {
  69839. * xtype: 'textfield',
  69840. * name: 'name',
  69841. * label: 'Name'
  69842. * },
  69843. * {
  69844. * xtype: 'emailfield',
  69845. * name: 'email',
  69846. * label: 'Email'
  69847. * },
  69848. * {
  69849. * xtype: 'passwordfield',
  69850. * name: 'password',
  69851. * label: 'Password'
  69852. * }
  69853. * ]
  69854. * });
  69855. *
  69856. * The above used a new capability of Sencha Touch 2.0, which enables you to specify listeners on child components of any
  69857. * container. In this case, we attached a listener to the {@link Ext.field.Text#change change} event of each form
  69858. * field that is a direct child of the form panel. Our listener gets the name of the field that fired the change event,
  69859. * and updates our {@link Ext.data.Model Model} instance with the new value. For example, changing the email field
  69860. * in the form will update the Model's email field.
  69861. *
  69862. * ##Submitting forms
  69863. *
  69864. * There are a few ways to submit form data. In our example above we have a Model instance that we have updated, giving
  69865. * us the option to use the Model's {@link Ext.data.Model#save save} method to persist the changes back to our server,
  69866. * without using a traditional form submission. Alternatively, we can send a normal browser form submit using the
  69867. * {@link #method} method:
  69868. *
  69869. * form.submit({
  69870. * url: 'url/to/submit/to',
  69871. * method: 'POST',
  69872. * success: function() {
  69873. * alert('form submitted successfully!');
  69874. * }
  69875. * });
  69876. *
  69877. * In this case we provided the `url` to submit the form to inside the submit call - alternatively you can just set the
  69878. * {@link #url} configuration when you create the form. We can specify other parameters (see {@link #method} for a
  69879. * full list), including callback functions for success and failure, which are called depending on whether or not the
  69880. * form submission was successful. These functions are usually used to take some action in your app after your data
  69881. * has been saved to the server side.
  69882. *
  69883. * @aside guide forms
  69884. * @aside example forms
  69885. * @aside example forms-toolbars
  69886. */
  69887. Ext.define('Ext.form.Panel', {
  69888. alternateClassName: 'Ext.form.FormPanel',
  69889. extend : 'Ext.Panel',
  69890. xtype : 'formpanel',
  69891. requires: ['Ext.XTemplate', 'Ext.field.Checkbox', 'Ext.Ajax'],
  69892. /**
  69893. * @event submit
  69894. * @preventable doSubmit
  69895. * Fires upon successful (Ajax-based) form submission.
  69896. * @param {Ext.form.Panel} this This FormPanel.
  69897. * @param {Object} result The result object as returned by the server.
  69898. * @param {Ext.EventObject} e The event object.
  69899. */
  69900. /**
  69901. * @event beforesubmit
  69902. * @preventable doBeforeSubmit
  69903. * Fires immediately preceding any Form submit action.
  69904. * Implementations may adjust submitted form values or options prior to execution.
  69905. * A return value of `false` from this listener will abort the submission
  69906. * attempt (regardless of `standardSubmit` configuration).
  69907. * @param {Ext.form.Panel} this This FormPanel.
  69908. * @param {Object} values A hash collection of the qualified form values about to be submitted.
  69909. * @param {Object} options Submission options hash (only available when `standardSubmit` is `false`).
  69910. */
  69911. /**
  69912. * @event exception
  69913. * Fires when either the Ajax HTTP request reports a failure OR the server returns a `success:false`
  69914. * response in the result payload.
  69915. * @param {Ext.form.Panel} this This FormPanel.
  69916. * @param {Object} result Either a failed Ext.data.Connection request object or a failed (logical) server.
  69917. * response payload.
  69918. */
  69919. config: {
  69920. /**
  69921. * @cfg {String} baseCls
  69922. * @inheritdoc
  69923. */
  69924. baseCls: Ext.baseCSSPrefix + 'form',
  69925. /**
  69926. * @cfg {Boolean} standardSubmit
  69927. * Whether or not we want to perform a standard form submit.
  69928. * @accessor
  69929. */
  69930. standardSubmit: false,
  69931. /**
  69932. * @cfg {String} url
  69933. * The default url for submit actions.
  69934. * @accessor
  69935. */
  69936. url: null,
  69937. /**
  69938. * @cfg {Object} baseParams
  69939. * Optional hash of params to be sent (when `standardSubmit` configuration is `false`) on every submit.
  69940. * @accessor
  69941. */
  69942. baseParams : null,
  69943. /**
  69944. * @cfg {Object} submitOnAction
  69945. * When this is set to `true`, the form will automatically submit itself whenever the `action`
  69946. * event fires on a field in this form. The action event usually fires whenever you press
  69947. * go or enter inside a textfield.
  69948. * @accessor
  69949. */
  69950. submitOnAction: false,
  69951. /**
  69952. * @cfg {Ext.data.Model} record The model instance of this form. Can by dynamically set at any time.
  69953. * @accessor
  69954. */
  69955. record: null,
  69956. /**
  69957. * @cfg {String} method
  69958. * The method which this form will be submitted. `post` or `get`.
  69959. */
  69960. method: 'post',
  69961. /**
  69962. * @cfg {Object} scrollable
  69963. * @inheritdoc
  69964. */
  69965. scrollable: {
  69966. translatable: {
  69967. translationMethod: 'scrollposition'
  69968. }
  69969. }
  69970. },
  69971. getElementConfig: function() {
  69972. var config = this.callParent();
  69973. config.tag = "form";
  69974. return config;
  69975. },
  69976. // @private
  69977. initialize: function() {
  69978. var me = this;
  69979. me.callParent();
  69980. me.element.on({
  69981. submit: 'onSubmit',
  69982. scope : me
  69983. });
  69984. },
  69985. updateRecord: function(newRecord) {
  69986. var fields, values, name;
  69987. if (newRecord && (fields = newRecord.fields)) {
  69988. values = this.getValues();
  69989. for (name in values) {
  69990. if (values.hasOwnProperty(name) && fields.containsKey(name)) {
  69991. newRecord.set(name, values[name]);
  69992. }
  69993. }
  69994. }
  69995. return this;
  69996. },
  69997. /**
  69998. * Loads matching fields from a model instance into this form.
  69999. * @param {Ext.data.Model} instance The model instance.
  70000. * @return {Ext.form.Panel} This form.
  70001. */
  70002. setRecord: function(record) {
  70003. var me = this;
  70004. if (record && record.data) {
  70005. me.setValues(record.data);
  70006. }
  70007. me._record = record;
  70008. return this;
  70009. },
  70010. // @private
  70011. onSubmit: function(e) {
  70012. var me = this;
  70013. if (e && !me.getStandardSubmit()) {
  70014. e.stopEvent();
  70015. } else {
  70016. this.submit();
  70017. }
  70018. },
  70019. updateSubmitOnAction: function(newSubmitOnAction) {
  70020. if (newSubmitOnAction) {
  70021. this.on({
  70022. action: 'onFieldAction',
  70023. scope: this
  70024. });
  70025. } else {
  70026. this.un({
  70027. action: 'onFieldAction',
  70028. scope: this
  70029. });
  70030. }
  70031. },
  70032. // @private
  70033. onFieldAction: function(field) {
  70034. if (this.getSubmitOnAction()) {
  70035. field.blur();
  70036. this.submit();
  70037. }
  70038. },
  70039. /**
  70040. * Performs a Ajax-based submission of form values (if `standardSubmit` is `false`) or otherwise
  70041. * executes a standard HTML Form submit action.
  70042. *
  70043. * @param {Object} options
  70044. * The configuration when submitting this form.
  70045. *
  70046. * @param {String} options.url
  70047. * The url for the action (defaults to the form's {@link #url}).
  70048. *
  70049. * @param {String} options.method
  70050. * The form method to use (defaults to the form's {@link #method}, or POST if not defined).
  70051. *
  70052. * @param {String/Object} params
  70053. * The params to pass when submitting this form (defaults to this forms {@link #baseParams}).
  70054. * Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.
  70055. *
  70056. * @param {Object} headers
  70057. * Request headers to set for the action.
  70058. *
  70059. * @param {Boolean} [autoAbort=false]
  70060. * `true` to abort any pending Ajax request prior to submission.
  70061. * __Note:__ Has no effect when `{@link #standardSubmit}` is enabled.
  70062. *
  70063. * @param {Boolean} [options.submitDisabled=false]
  70064. * `true` to submit all fields regardless of disabled state.
  70065. * __Note:__ Has no effect when `{@link #standardSubmit}` is enabled.
  70066. *
  70067. * @param {String/Object} [waitMsg]
  70068. * If specified, the value which is passed to the loading {@link #masked mask}. See {@link #masked} for
  70069. * more information.
  70070. *
  70071. * @param {Function} options.success
  70072. * The callback that will be invoked after a successful response. A response is successful if
  70073. * a response is received from the server and is a JSON object where the `success` property is set
  70074. * to `true`, `{"success": true}`.
  70075. *
  70076. * The function is passed the following parameters:
  70077. *
  70078. * @param {Ext.form.Panel} options.success.form
  70079. * The form that requested the action.
  70080. *
  70081. * @param {Ext.form.Panel} options.success.result
  70082. * The result object returned by the server as a result of the submit request.
  70083. *
  70084. * @param {Function} options.failure
  70085. * The callback that will be invoked after a failed transaction attempt.
  70086. *
  70087. * The function is passed the following parameters:
  70088. *
  70089. * @param {Ext.form.Panel} options.failure.form
  70090. * The {@link Ext.form.Panel} that requested the submit.
  70091. *
  70092. * @param {Ext.form.Panel} options.failure.result
  70093. * The failed response or result object returned by the server which performed the operation.
  70094. *
  70095. * @param {Object} options.scope
  70096. * The scope in which to call the callback functions (The `this` reference for the callback functions).
  70097. *
  70098. * @return {Ext.data.Connection} The request object.
  70099. */
  70100. submit: function(options) {
  70101. var me = this,
  70102. form = me.element.dom || {},
  70103. formValues;
  70104. options = Ext.apply({
  70105. url : me.getUrl() || form.action,
  70106. submit: false,
  70107. method : me.getMethod() || form.method || 'post',
  70108. autoAbort : false,
  70109. params : null,
  70110. waitMsg : null,
  70111. headers : null,
  70112. success : null,
  70113. failure : null
  70114. }, options || {});
  70115. formValues = me.getValues(me.getStandardSubmit() || !options.submitDisabled);
  70116. return me.fireAction('beforesubmit', [me, formValues, options], 'doBeforeSubmit');
  70117. },
  70118. doBeforeSubmit: function(me, formValues, options) {
  70119. var form = me.element.dom || {};
  70120. if (me.getStandardSubmit()) {
  70121. if (options.url && Ext.isEmpty(form.action)) {
  70122. form.action = options.url;
  70123. }
  70124. // Spinner fields must have their components enabled *before* submitting or else the value
  70125. // will not be posted.
  70126. var fields = this.query('spinnerfield'),
  70127. ln = fields.length,
  70128. i, field;
  70129. for (i = 0; i < ln; i++) {
  70130. field = fields[i];
  70131. if (!field.getDisabled()) {
  70132. field.getComponent().setDisabled(false);
  70133. }
  70134. }
  70135. form.method = (options.method || form.method).toLowerCase();
  70136. form.submit();
  70137. }
  70138. else {
  70139. if (options.waitMsg) {
  70140. me.setMasked(options.waitMsg);
  70141. }
  70142. return Ext.Ajax.request({
  70143. url: options.url,
  70144. method: options.method,
  70145. rawData: Ext.urlEncode(Ext.apply(
  70146. Ext.apply({}, me.getBaseParams() || {}),
  70147. options.params || {},
  70148. formValues
  70149. )),
  70150. autoAbort: options.autoAbort,
  70151. headers: Ext.apply(
  70152. {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
  70153. options.headers || {}
  70154. ),
  70155. scope: me,
  70156. callback: function(callbackOptions, success, response) {
  70157. var me = this,
  70158. responseText = response.responseText,
  70159. failureFn;
  70160. me.setMasked(false);
  70161. failureFn = function() {
  70162. if (Ext.isFunction(options.failure)) {
  70163. options.failure.call(options.scope || me, me, response, responseText);
  70164. }
  70165. me.fireEvent('exception', me, response);
  70166. };
  70167. if (success) {
  70168. response = Ext.decode(responseText);
  70169. success = !!response.success;
  70170. if (success) {
  70171. if (Ext.isFunction(options.success)) {
  70172. options.success.call(options.scope || me, me, response, responseText);
  70173. }
  70174. me.fireEvent('submit', me, response);
  70175. } else {
  70176. failureFn();
  70177. }
  70178. }
  70179. else {
  70180. failureFn();
  70181. }
  70182. }
  70183. });
  70184. }
  70185. },
  70186. /**
  70187. * Sets the values of form fields in bulk. Example usage:
  70188. *
  70189. * myForm.setValues({
  70190. * name: 'Ed',
  70191. * crazy: true,
  70192. * username: 'edspencer'
  70193. * });
  70194. *
  70195. * If there groups of checkbox fields with the same name, pass their values in an array. For example:
  70196. *
  70197. * myForm.setValues({
  70198. * name: 'Jacky',
  70199. * crazy: false,
  70200. * hobbies: [
  70201. * 'reading',
  70202. * 'cooking',
  70203. * 'gaming'
  70204. * ]
  70205. * });
  70206. *
  70207. * @param {Object} values field name => value mapping object.
  70208. * @return {Ext.form.Panel} This form.
  70209. */
  70210. setValues: function(values) {
  70211. var fields = this.getFields(),
  70212. name, field, value, ln, i, f;
  70213. values = values || {};
  70214. for (name in values) {
  70215. if (values.hasOwnProperty(name)) {
  70216. field = fields[name];
  70217. value = values[name];
  70218. if (field) {
  70219. // If there are multiple fields with the same name. Checkboxes, radio fields and maybe event just normal fields..
  70220. if (Ext.isArray(field)) {
  70221. ln = field.length;
  70222. // Loop through each of the fields
  70223. for (i = 0; i < ln; i++) {
  70224. f = field[i];
  70225. if (f.isRadio) {
  70226. // If it is a radio field just use setGroupValue which will handle all of the radio fields
  70227. f.setGroupValue(value);
  70228. break;
  70229. } else if (f.isCheckbox) {
  70230. if (Ext.isArray(value)) {
  70231. f.setChecked((value.indexOf(f._value) != -1));
  70232. } else {
  70233. f.setChecked((value == f._value));
  70234. }
  70235. } else {
  70236. // If it is a bunch of fields with the same name, check if the value is also an array, so we can map it
  70237. // to each field
  70238. if (Ext.isArray(value)) {
  70239. f.setValue(value[i]);
  70240. }
  70241. }
  70242. }
  70243. } else {
  70244. if (field.isRadio || field.isCheckbox) {
  70245. // If the field is a radio or a checkbox
  70246. field.setChecked(value);
  70247. } else {
  70248. // If just a normal field
  70249. field.setValue(value);
  70250. }
  70251. }
  70252. }
  70253. }
  70254. }
  70255. return this;
  70256. },
  70257. /**
  70258. * Returns an object containing the value of each field in the form, keyed to the field's name.
  70259. * For groups of checkbox fields with the same name, it will be arrays of values. For example:
  70260. *
  70261. * {
  70262. * name: "Jacky Nguyen", // From a TextField
  70263. * favorites: [
  70264. * 'pizza',
  70265. * 'noodle',
  70266. * 'cake'
  70267. * ]
  70268. * }
  70269. *
  70270. * @param {Boolean} enabled `true` to return only enabled fields.
  70271. * @param {Boolean} all `true` to return all fields even if they don't have a
  70272. * {@link Ext.field.Field#name name} configured.
  70273. * @return {Object} Object mapping field name to its value.
  70274. */
  70275. getValues: function(enabled, all) {
  70276. var fields = this.getFields(),
  70277. values = {},
  70278. isArray = Ext.isArray,
  70279. field, value, addValue, bucket, name, ln, i;
  70280. // Function which you give a field and a name, and it will add it into the values
  70281. // object accordingly
  70282. addValue = function(field, name) {
  70283. if (!all && (!name || name === 'null')) {
  70284. return;
  70285. }
  70286. if (field.isCheckbox) {
  70287. value = field.getSubmitValue();
  70288. } else {
  70289. value = field.getValue();
  70290. }
  70291. if (!(enabled && field.getDisabled())) {
  70292. // RadioField is a special case where the value returned is the fields valUE
  70293. // ONLY if it is checked
  70294. if (field.isRadio) {
  70295. if (field.isChecked()) {
  70296. values[name] = value;
  70297. }
  70298. } else {
  70299. // Check if the value already exists
  70300. bucket = values[name];
  70301. if (bucket) {
  70302. // if it does and it isn't an array, we need to make it into an array
  70303. // so we can push more
  70304. if (!isArray(bucket)) {
  70305. bucket = values[name] = [bucket];
  70306. }
  70307. // Check if it is an array
  70308. if (isArray(value)) {
  70309. // Concat it into the other values
  70310. bucket = values[name] = bucket.concat(value);
  70311. } else {
  70312. // If it isn't an array, just pushed more values
  70313. bucket.push(value);
  70314. }
  70315. } else {
  70316. values[name] = value;
  70317. }
  70318. }
  70319. }
  70320. };
  70321. // Loop through each of the fields, and add the values for those fields.
  70322. for (name in fields) {
  70323. if (fields.hasOwnProperty(name)) {
  70324. field = fields[name];
  70325. if (isArray(field)) {
  70326. ln = field.length;
  70327. for (i = 0; i < ln; i++) {
  70328. addValue(field[i], name);
  70329. }
  70330. } else {
  70331. addValue(field, name);
  70332. }
  70333. }
  70334. }
  70335. return values;
  70336. },
  70337. /**
  70338. * Resets all fields in the form back to their original values.
  70339. * @return {Ext.form.Panel} This form.
  70340. */
  70341. reset: function() {
  70342. this.getFieldsAsArray().forEach(function(field) {
  70343. field.reset();
  70344. });
  70345. return this;
  70346. },
  70347. /**
  70348. * A convenient method to disable all fields in this form.
  70349. * @return {Ext.form.Panel} This form.
  70350. */
  70351. doSetDisabled: function(newDisabled) {
  70352. this.getFieldsAsArray().forEach(function(field) {
  70353. field.setDisabled(newDisabled);
  70354. });
  70355. return this;
  70356. },
  70357. /**
  70358. * @private
  70359. */
  70360. getFieldsAsArray: function() {
  70361. var fields = [],
  70362. getFieldsFrom = function(item) {
  70363. if (item.isField) {
  70364. fields.push(item);
  70365. }
  70366. if (item.isContainer) {
  70367. item.getItems().each(getFieldsFrom);
  70368. }
  70369. };
  70370. this.getItems().each(getFieldsFrom);
  70371. return fields;
  70372. },
  70373. /**
  70374. * @private
  70375. * Returns all {@link Ext.field.Field field} instances inside this form.
  70376. * @param byName return only fields that match the given name, otherwise return all fields.
  70377. * @return {Object/Array} All field instances, mapped by field name; or an array if `byName` is passed.
  70378. */
  70379. getFields: function(byName) {
  70380. var fields = {},
  70381. itemName;
  70382. var getFieldsFrom = function(item) {
  70383. if (item.isField) {
  70384. itemName = item.getName();
  70385. if ((byName && itemName == byName) || typeof byName == 'undefined') {
  70386. if (fields.hasOwnProperty(itemName)) {
  70387. if (!Ext.isArray(fields[itemName])) {
  70388. fields[itemName] = [fields[itemName]];
  70389. }
  70390. fields[itemName].push(item);
  70391. } else {
  70392. fields[itemName] = item;
  70393. }
  70394. }
  70395. }
  70396. if (item.isContainer) {
  70397. item.items.each(getFieldsFrom);
  70398. }
  70399. };
  70400. this.getItems().each(getFieldsFrom);
  70401. return (byName) ? (fields[byName] || []) : fields;
  70402. },
  70403. /**
  70404. * Returns an array of fields in this formpanel.
  70405. * @return {Ext.field.Field[]} An array of fields in this form panel.
  70406. * @private
  70407. */
  70408. getFieldsArray: function() {
  70409. var fields = [];
  70410. var getFieldsFrom = function(item) {
  70411. if (item.isField) {
  70412. fields.push(item);
  70413. }
  70414. if (item.isContainer) {
  70415. item.items.each(getFieldsFrom);
  70416. }
  70417. };
  70418. this.items.each(getFieldsFrom);
  70419. return fields;
  70420. },
  70421. getFieldsFromItem: Ext.emptyFn,
  70422. /**
  70423. * Shows a generic/custom mask over a designated Element.
  70424. * @param {String/Object} cfg Either a string message or a configuration object supporting
  70425. * the following options:
  70426. *
  70427. * {
  70428. * message : 'Please Wait',
  70429. * cls : 'form-mask'
  70430. * }
  70431. *
  70432. * @param {Object} target
  70433. * @return {Ext.form.Panel} This form
  70434. * @deprecated 2.0.0 Please use {@link #setMasked} instead.
  70435. */
  70436. showMask: function(cfg, target) {
  70437. //<debug>
  70438. Ext.Logger.warn('showMask is now deprecated. Please use Ext.form.Panel#setMasked instead');
  70439. //</debug>
  70440. cfg = Ext.isObject(cfg) ? cfg.message : cfg;
  70441. if (cfg) {
  70442. this.setMasked({
  70443. xtype: 'loadmask',
  70444. message: cfg
  70445. });
  70446. } else {
  70447. this.setMasked(true);
  70448. }
  70449. return this;
  70450. },
  70451. /**
  70452. * Hides a previously shown wait mask (See {@link #showMask}).
  70453. * @return {Ext.form.Panel} this
  70454. * @deprecated 2.0.0 Please use {@link #unmask} or {@link #setMasked} instead.
  70455. */
  70456. hideMask: function() {
  70457. this.setMasked(false);
  70458. return this;
  70459. },
  70460. /**
  70461. * Returns the currently focused field
  70462. * @return {Ext.field.Field} The currently focused field, if one is focused or `null`.
  70463. * @private
  70464. */
  70465. getFocusedField: function() {
  70466. var fields = this.getFieldsArray(),
  70467. ln = fields.length,
  70468. field, i;
  70469. for (i = 0; i < ln; i++) {
  70470. field = fields[i];
  70471. if (field.isFocused) {
  70472. return field;
  70473. }
  70474. }
  70475. return null;
  70476. },
  70477. /**
  70478. * @private
  70479. * @return {Boolean/Ext.field.Field} The next field if one exists, or `false`.
  70480. * @private
  70481. */
  70482. getNextField: function() {
  70483. var fields = this.getFieldsArray(),
  70484. focusedField = this.getFocusedField(),
  70485. index;
  70486. if (focusedField) {
  70487. index = fields.indexOf(focusedField);
  70488. if (index !== fields.length - 1) {
  70489. index++;
  70490. return fields[index];
  70491. }
  70492. }
  70493. return false;
  70494. },
  70495. /**
  70496. * Tries to focus the next field in the form, if there is currently a focused field.
  70497. * @return {Boolean/Ext.field.Field} The next field that was focused, or `false`.
  70498. * @private
  70499. */
  70500. focusNextField: function() {
  70501. var field = this.getNextField();
  70502. if (field) {
  70503. field.focus();
  70504. return field;
  70505. }
  70506. return false;
  70507. },
  70508. /**
  70509. * @private
  70510. * @return {Boolean/Ext.field.Field} The next field if one exists, or `false`.
  70511. */
  70512. getPreviousField: function() {
  70513. var fields = this.getFieldsArray(),
  70514. focusedField = this.getFocusedField(),
  70515. index;
  70516. if (focusedField) {
  70517. index = fields.indexOf(focusedField);
  70518. if (index !== 0) {
  70519. index--;
  70520. return fields[index];
  70521. }
  70522. }
  70523. return false;
  70524. },
  70525. /**
  70526. * Tries to focus the previous field in the form, if there is currently a focused field.
  70527. * @return {Boolean/Ext.field.Field} The previous field that was focused, or `false`.
  70528. * @private
  70529. */
  70530. focusPreviousField: function() {
  70531. var field = this.getPreviousField();
  70532. if (field) {
  70533. field.focus();
  70534. return field;
  70535. }
  70536. return false;
  70537. }
  70538. }, function() {
  70539. });
  70540. /**
  70541. * @private
  70542. */
  70543. Ext.define('Ext.fx.Easing', {
  70544. requires: ['Ext.fx.easing.Linear'],
  70545. constructor: function(easing) {
  70546. return Ext.factory(easing, Ext.fx.easing.Linear, null, 'easing');
  70547. }
  70548. });
  70549. /**
  70550. * @private
  70551. */
  70552. Ext.define('Ext.fx.runner.Css', {
  70553. extend: 'Ext.Evented',
  70554. requires: [
  70555. 'Ext.fx.Animation'
  70556. ],
  70557. prefixedProperties: {
  70558. 'transform' : true,
  70559. 'transform-origin' : true,
  70560. 'perspective' : true,
  70561. 'transform-style' : true,
  70562. 'transition' : true,
  70563. 'transition-property' : true,
  70564. 'transition-duration' : true,
  70565. 'transition-timing-function': true,
  70566. 'transition-delay' : true,
  70567. 'animation' : true,
  70568. 'animation-name' : true,
  70569. 'animation-duration' : true,
  70570. 'animation-iteration-count' : true,
  70571. 'animation-direction' : true,
  70572. 'animation-timing-function' : true,
  70573. 'animation-delay' : true
  70574. },
  70575. lengthProperties: {
  70576. 'top' : true,
  70577. 'right' : true,
  70578. 'bottom' : true,
  70579. 'left' : true,
  70580. 'width' : true,
  70581. 'height' : true,
  70582. 'max-height' : true,
  70583. 'max-width' : true,
  70584. 'min-height' : true,
  70585. 'min-width' : true,
  70586. 'margin-bottom' : true,
  70587. 'margin-left' : true,
  70588. 'margin-right' : true,
  70589. 'margin-top' : true,
  70590. 'padding-bottom' : true,
  70591. 'padding-left' : true,
  70592. 'padding-right' : true,
  70593. 'padding-top' : true,
  70594. 'border-bottom-width': true,
  70595. 'border-left-width' : true,
  70596. 'border-right-width' : true,
  70597. 'border-spacing' : true,
  70598. 'border-top-width' : true,
  70599. 'border-width' : true,
  70600. 'outline-width' : true,
  70601. 'letter-spacing' : true,
  70602. 'line-height' : true,
  70603. 'text-indent' : true,
  70604. 'word-spacing' : true,
  70605. 'font-size' : true,
  70606. 'translate' : true,
  70607. 'translateX' : true,
  70608. 'translateY' : true,
  70609. 'translateZ' : true,
  70610. 'translate3d' : true
  70611. },
  70612. durationProperties: {
  70613. 'transition-duration' : true,
  70614. 'transition-delay' : true,
  70615. 'animation-duration' : true,
  70616. 'animation-delay' : true
  70617. },
  70618. angleProperties: {
  70619. rotate : true,
  70620. rotateX : true,
  70621. rotateY : true,
  70622. rotateZ : true,
  70623. skew : true,
  70624. skewX : true,
  70625. skewY : true
  70626. },
  70627. lengthUnitRegex: /([a-z%]*)$/,
  70628. DEFAULT_UNIT_LENGTH: 'px',
  70629. DEFAULT_UNIT_ANGLE: 'deg',
  70630. DEFAULT_UNIT_DURATION: 'ms',
  70631. formattedNameCache: {},
  70632. constructor: function() {
  70633. var supports3dTransform = Ext.feature.has.Css3dTransforms;
  70634. if (supports3dTransform) {
  70635. this.transformMethods = ['translateX', 'translateY', 'translateZ', 'rotate', 'rotateX', 'rotateY', 'rotateZ', 'skewX', 'skewY', 'scaleX', 'scaleY', 'scaleZ'];
  70636. }
  70637. else {
  70638. this.transformMethods = ['translateX', 'translateY', 'rotate', 'skewX', 'skewY', 'scaleX', 'scaleY'];
  70639. }
  70640. this.vendorPrefix = Ext.browser.getStyleDashPrefix();
  70641. this.ruleStylesCache = {};
  70642. return this;
  70643. },
  70644. getStyleSheet: function() {
  70645. var styleSheet = this.styleSheet,
  70646. styleElement, styleSheets;
  70647. if (!styleSheet) {
  70648. styleElement = document.createElement('style');
  70649. styleElement.type = 'text/css';
  70650. (document.head || document.getElementsByTagName('head')[0]).appendChild(styleElement);
  70651. styleSheets = document.styleSheets;
  70652. this.styleSheet = styleSheet = styleSheets[styleSheets.length - 1];
  70653. }
  70654. return styleSheet;
  70655. },
  70656. applyRules: function(selectors) {
  70657. var styleSheet = this.getStyleSheet(),
  70658. ruleStylesCache = this.ruleStylesCache,
  70659. rules = styleSheet.cssRules,
  70660. selector, properties, ruleStyle,
  70661. ruleStyleCache, rulesLength, name, value;
  70662. for (selector in selectors) {
  70663. properties = selectors[selector];
  70664. ruleStyle = ruleStylesCache[selector];
  70665. if (ruleStyle === undefined) {
  70666. rulesLength = rules.length;
  70667. styleSheet.insertRule(selector + '{}', rulesLength);
  70668. ruleStyle = ruleStylesCache[selector] = rules.item(rulesLength).style;
  70669. }
  70670. ruleStyleCache = ruleStyle.$cache;
  70671. if (!ruleStyleCache) {
  70672. ruleStyleCache = ruleStyle.$cache = {};
  70673. }
  70674. for (name in properties) {
  70675. value = this.formatValue(properties[name], name);
  70676. name = this.formatName(name);
  70677. if (ruleStyleCache[name] !== value) {
  70678. ruleStyleCache[name] = value;
  70679. // console.log(name + " " + value);
  70680. if (value === null) {
  70681. ruleStyle.removeProperty(name);
  70682. }
  70683. else {
  70684. ruleStyle.setProperty(name, value, 'important');
  70685. }
  70686. }
  70687. }
  70688. }
  70689. return this;
  70690. },
  70691. applyStyles: function(styles) {
  70692. var id, element, elementStyle, properties, name, value;
  70693. for (id in styles) {
  70694. // console.log("-> ["+id+"]", "APPLY======================");
  70695. element = document.getElementById(id);
  70696. if (!element) {
  70697. return this;
  70698. }
  70699. elementStyle = element.style;
  70700. properties = styles[id];
  70701. for (name in properties) {
  70702. value = this.formatValue(properties[name], name);
  70703. name = this.formatName(name);
  70704. // console.log("->-> ["+id+"]", name, value);
  70705. if (value === null) {
  70706. elementStyle.removeProperty(name);
  70707. }
  70708. else {
  70709. elementStyle.setProperty(name, value, 'important');
  70710. }
  70711. }
  70712. }
  70713. return this;
  70714. },
  70715. formatName: function(name) {
  70716. var cache = this.formattedNameCache,
  70717. formattedName = cache[name];
  70718. if (!formattedName) {
  70719. if (this.prefixedProperties[name]) {
  70720. formattedName = this.vendorPrefix + name;
  70721. }
  70722. else {
  70723. formattedName = name;
  70724. }
  70725. cache[name] = formattedName;
  70726. }
  70727. return formattedName;
  70728. },
  70729. formatValue: function(value, name) {
  70730. var type = typeof value,
  70731. lengthUnit = this.DEFAULT_UNIT_LENGTH,
  70732. transformMethods,
  70733. method, i, ln,
  70734. transformValues, values, unit;
  70735. if (type == 'string') {
  70736. if (this.lengthProperties[name]) {
  70737. unit = value.match(this.lengthUnitRegex)[1];
  70738. if (unit.length > 0) {
  70739. //<debug error>
  70740. if (unit !== lengthUnit) {
  70741. Ext.Logger.error("Length unit: '" + unit + "' in value: '" + value + "' of property: '" + name + "' is not " +
  70742. "valid for animation. Only 'px' is allowed");
  70743. }
  70744. //</debug>
  70745. }
  70746. else {
  70747. return value + lengthUnit;
  70748. }
  70749. }
  70750. return value;
  70751. }
  70752. else if (type == 'number') {
  70753. if (value == 0) {
  70754. return '0';
  70755. }
  70756. if (this.lengthProperties[name]) {
  70757. return value + lengthUnit;
  70758. }
  70759. if (this.angleProperties[name]) {
  70760. return value + this.DEFAULT_UNIT_ANGLE;
  70761. }
  70762. if (this.durationProperties[name]) {
  70763. return value + this.DEFAULT_UNIT_DURATION;
  70764. }
  70765. }
  70766. else if (name === 'transform') {
  70767. transformMethods = this.transformMethods;
  70768. transformValues = [];
  70769. for (i = 0,ln = transformMethods.length; i < ln; i++) {
  70770. method = transformMethods[i];
  70771. transformValues.push(method + '(' + this.formatValue(value[method], method) + ')');
  70772. }
  70773. return transformValues.join(' ');
  70774. }
  70775. else if (Ext.isArray(value)) {
  70776. values = [];
  70777. for (i = 0,ln = value.length; i < ln; i++) {
  70778. values.push(this.formatValue(value[i], name));
  70779. }
  70780. return (values.length > 0) ? values.join(', ') : 'none';
  70781. }
  70782. return value;
  70783. }
  70784. });
  70785. /**
  70786. * @author Jacky Nguyen <jacky@sencha.com>
  70787. * @private
  70788. */
  70789. Ext.define('Ext.fx.runner.CssTransition', {
  70790. extend: 'Ext.fx.runner.Css',
  70791. listenersAttached: false,
  70792. constructor: function() {
  70793. this.runningAnimationsData = {};
  70794. return this.callParent(arguments);
  70795. },
  70796. attachListeners: function() {
  70797. this.listenersAttached = true;
  70798. this.getEventDispatcher().addListener('element', '*', 'transitionend', 'onTransitionEnd', this);
  70799. },
  70800. onTransitionEnd: function(e) {
  70801. var target = e.target,
  70802. id = target.id;
  70803. if (id && this.runningAnimationsData.hasOwnProperty(id)) {
  70804. this.refreshRunningAnimationsData(Ext.get(target), [e.browserEvent.propertyName]);
  70805. }
  70806. },
  70807. onAnimationEnd: function(element, data, animation, isInterrupted, isReplaced) {
  70808. var id = element.getId(),
  70809. runningData = this.runningAnimationsData[id],
  70810. endRules = {},
  70811. endData = {},
  70812. runningNameMap, toPropertyNames, i, ln, name;
  70813. animation.un('stop', 'onAnimationStop', this);
  70814. if (runningData) {
  70815. runningNameMap = runningData.nameMap;
  70816. }
  70817. endRules[id] = endData;
  70818. if (data.onBeforeEnd) {
  70819. data.onBeforeEnd.call(data.scope || this, element, isInterrupted);
  70820. }
  70821. animation.fireEvent('animationbeforeend', animation, element, isInterrupted);
  70822. this.fireEvent('animationbeforeend', this, animation, element, isInterrupted);
  70823. if (isReplaced || (!isInterrupted && !data.preserveEndState)) {
  70824. toPropertyNames = data.toPropertyNames;
  70825. for (i = 0,ln = toPropertyNames.length; i < ln; i++) {
  70826. name = toPropertyNames[i];
  70827. if (runningNameMap && !runningNameMap.hasOwnProperty(name)) {
  70828. endData[name] = null;
  70829. }
  70830. }
  70831. }
  70832. if (data.after) {
  70833. Ext.merge(endData, data.after);
  70834. }
  70835. this.applyStyles(endRules);
  70836. if (data.onEnd) {
  70837. data.onEnd.call(data.scope || this, element, isInterrupted);
  70838. }
  70839. animation.fireEvent('animationend', animation, element, isInterrupted);
  70840. this.fireEvent('animationend', this, animation, element, isInterrupted);
  70841. },
  70842. onAllAnimationsEnd: function(element) {
  70843. var id = element.getId(),
  70844. endRules = {};
  70845. delete this.runningAnimationsData[id];
  70846. endRules[id] = {
  70847. 'transition-property': null,
  70848. 'transition-duration': null,
  70849. 'transition-timing-function': null,
  70850. 'transition-delay': null
  70851. };
  70852. this.applyStyles(endRules);
  70853. this.fireEvent('animationallend', this, element);
  70854. },
  70855. hasRunningAnimations: function(element) {
  70856. var id = element.getId(),
  70857. runningAnimationsData = this.runningAnimationsData;
  70858. return runningAnimationsData.hasOwnProperty(id) && runningAnimationsData[id].sessions.length > 0;
  70859. },
  70860. refreshRunningAnimationsData: function(element, propertyNames, interrupt, replace) {
  70861. var id = element.getId(),
  70862. runningAnimationsData = this.runningAnimationsData,
  70863. runningData = runningAnimationsData[id];
  70864. if (!runningData) {
  70865. return;
  70866. }
  70867. var nameMap = runningData.nameMap,
  70868. nameList = runningData.nameList,
  70869. sessions = runningData.sessions,
  70870. ln, j, subLn, name,
  70871. i, session, map, list,
  70872. hasCompletedSession = false;
  70873. interrupt = Boolean(interrupt);
  70874. replace = Boolean(replace);
  70875. if (!sessions) {
  70876. return this;
  70877. }
  70878. ln = sessions.length;
  70879. if (ln === 0) {
  70880. return this;
  70881. }
  70882. if (replace) {
  70883. runningData.nameMap = {};
  70884. nameList.length = 0;
  70885. for (i = 0; i < ln; i++) {
  70886. session = sessions[i];
  70887. this.onAnimationEnd(element, session.data, session.animation, interrupt, replace);
  70888. }
  70889. sessions.length = 0;
  70890. }
  70891. else {
  70892. for (i = 0; i < ln; i++) {
  70893. session = sessions[i];
  70894. map = session.map;
  70895. list = session.list;
  70896. for (j = 0,subLn = propertyNames.length; j < subLn; j++) {
  70897. name = propertyNames[j];
  70898. if (map[name]) {
  70899. delete map[name];
  70900. Ext.Array.remove(list, name);
  70901. session.length--;
  70902. if (--nameMap[name] == 0) {
  70903. delete nameMap[name];
  70904. Ext.Array.remove(nameList, name);
  70905. }
  70906. }
  70907. }
  70908. if (session.length == 0) {
  70909. sessions.splice(i, 1);
  70910. i--;
  70911. ln--;
  70912. hasCompletedSession = true;
  70913. this.onAnimationEnd(element, session.data, session.animation, interrupt);
  70914. }
  70915. }
  70916. }
  70917. if (!replace && !interrupt && sessions.length == 0 && hasCompletedSession) {
  70918. this.onAllAnimationsEnd(element);
  70919. }
  70920. },
  70921. getRunningData: function(id) {
  70922. var runningAnimationsData = this.runningAnimationsData;
  70923. if (!runningAnimationsData.hasOwnProperty(id)) {
  70924. runningAnimationsData[id] = {
  70925. nameMap: {},
  70926. nameList: [],
  70927. sessions: []
  70928. };
  70929. }
  70930. return runningAnimationsData[id];
  70931. },
  70932. getTestElement: function() {
  70933. var testElement = this.testElement,
  70934. iframe, iframeDocument, iframeStyle;
  70935. if (!testElement) {
  70936. iframe = document.createElement('iframe');
  70937. iframeStyle = iframe.style;
  70938. iframeStyle.setProperty('visibility', 'hidden', 'important');
  70939. iframeStyle.setProperty('width', '0px', 'important');
  70940. iframeStyle.setProperty('height', '0px', 'important');
  70941. iframeStyle.setProperty('position', 'absolute', 'important');
  70942. iframeStyle.setProperty('border', '0px', 'important');
  70943. iframeStyle.setProperty('zIndex', '-1000', 'important');
  70944. document.body.appendChild(iframe);
  70945. iframeDocument = iframe.contentDocument;
  70946. iframeDocument.open();
  70947. iframeDocument.writeln('</body>');
  70948. iframeDocument.close();
  70949. this.testElement = testElement = iframeDocument.createElement('div');
  70950. testElement.style.setProperty('position', 'absolute', '!important');
  70951. iframeDocument.body.appendChild(testElement);
  70952. this.testElementComputedStyle = window.getComputedStyle(testElement);
  70953. }
  70954. return testElement;
  70955. },
  70956. getCssStyleValue: function(name, value) {
  70957. var testElement = this.getTestElement(),
  70958. computedStyle = this.testElementComputedStyle,
  70959. style = testElement.style;
  70960. style.setProperty(name, value);
  70961. value = computedStyle.getPropertyValue(name);
  70962. style.removeProperty(name);
  70963. return value;
  70964. },
  70965. run: function(animations) {
  70966. var me = this,
  70967. isLengthPropertyMap = this.lengthProperties,
  70968. fromData = {},
  70969. toData = {},
  70970. data = {},
  70971. element, elementId, from, to, before,
  70972. fromPropertyNames, toPropertyNames,
  70973. doApplyTo, message,
  70974. runningData,
  70975. i, j, ln, animation, propertiesLength, sessionNameMap,
  70976. computedStyle, formattedName, name, toFormattedValue,
  70977. computedValue, fromFormattedValue, isLengthProperty,
  70978. runningNameMap, runningNameList, runningSessions, runningSession;
  70979. if (!this.listenersAttached) {
  70980. this.attachListeners();
  70981. }
  70982. animations = Ext.Array.from(animations);
  70983. for (i = 0,ln = animations.length; i < ln; i++) {
  70984. animation = animations[i];
  70985. animation = Ext.factory(animation, Ext.fx.Animation);
  70986. element = animation.getElement();
  70987. computedStyle = window.getComputedStyle(element.dom);
  70988. elementId = element.getId();
  70989. data = Ext.merge({}, animation.getData());
  70990. if (animation.onBeforeStart) {
  70991. animation.onBeforeStart.call(animation.scope || this, element);
  70992. }
  70993. animation.fireEvent('animationstart', animation);
  70994. this.fireEvent('animationstart', this, animation);
  70995. data[elementId] = data;
  70996. before = data.before;
  70997. from = data.from;
  70998. to = data.to;
  70999. data.fromPropertyNames = fromPropertyNames = [];
  71000. data.toPropertyNames = toPropertyNames = [];
  71001. for (name in to) {
  71002. if (to.hasOwnProperty(name)) {
  71003. to[name] = toFormattedValue = this.formatValue(to[name], name);
  71004. formattedName = this.formatName(name);
  71005. isLengthProperty = isLengthPropertyMap.hasOwnProperty(name);
  71006. if (!isLengthProperty) {
  71007. toFormattedValue = this.getCssStyleValue(formattedName, toFormattedValue);
  71008. }
  71009. if (from.hasOwnProperty(name)) {
  71010. from[name] = fromFormattedValue = this.formatValue(from[name], name);
  71011. if (!isLengthProperty) {
  71012. fromFormattedValue = this.getCssStyleValue(formattedName, fromFormattedValue);
  71013. }
  71014. if (toFormattedValue !== fromFormattedValue) {
  71015. fromPropertyNames.push(formattedName);
  71016. toPropertyNames.push(formattedName);
  71017. }
  71018. }
  71019. else {
  71020. computedValue = computedStyle.getPropertyValue(formattedName);
  71021. if (toFormattedValue !== computedValue) {
  71022. toPropertyNames.push(formattedName);
  71023. }
  71024. }
  71025. }
  71026. }
  71027. propertiesLength = toPropertyNames.length;
  71028. if (propertiesLength === 0) {
  71029. this.onAnimationEnd(element, data, animation);
  71030. continue;
  71031. }
  71032. runningData = this.getRunningData(elementId);
  71033. runningSessions = runningData.sessions;
  71034. if (runningSessions.length > 0) {
  71035. this.refreshRunningAnimationsData(
  71036. element, Ext.Array.merge(fromPropertyNames, toPropertyNames), true, data.replacePrevious
  71037. );
  71038. }
  71039. runningNameMap = runningData.nameMap;
  71040. runningNameList = runningData.nameList;
  71041. sessionNameMap = {};
  71042. for (j = 0; j < propertiesLength; j++) {
  71043. name = toPropertyNames[j];
  71044. sessionNameMap[name] = true;
  71045. if (!runningNameMap.hasOwnProperty(name)) {
  71046. runningNameMap[name] = 1;
  71047. runningNameList.push(name);
  71048. }
  71049. else {
  71050. runningNameMap[name]++;
  71051. }
  71052. }
  71053. runningSession = {
  71054. element: element,
  71055. map: sessionNameMap,
  71056. list: toPropertyNames.slice(),
  71057. length: propertiesLength,
  71058. data: data,
  71059. animation: animation
  71060. };
  71061. runningSessions.push(runningSession);
  71062. animation.on('stop', 'onAnimationStop', this);
  71063. fromData[elementId] = from = Ext.apply(Ext.Object.chain(before), from);
  71064. if (runningNameList.length > 0) {
  71065. fromPropertyNames = Ext.Array.difference(runningNameList, fromPropertyNames);
  71066. toPropertyNames = Ext.Array.merge(fromPropertyNames, toPropertyNames);
  71067. from['transition-property'] = fromPropertyNames;
  71068. }
  71069. toData[elementId] = to = Ext.Object.chain(to);
  71070. to['transition-property'] = toPropertyNames;
  71071. to['transition-duration'] = data.duration;
  71072. to['transition-timing-function'] = data.easing;
  71073. to['transition-delay'] = data.delay;
  71074. animation.startTime = Date.now();
  71075. }
  71076. message = this.$className;
  71077. this.applyStyles(fromData);
  71078. doApplyTo = function(e) {
  71079. if (e.data === message && e.source === window) {
  71080. window.removeEventListener('message', doApplyTo, false);
  71081. me.applyStyles(toData);
  71082. }
  71083. };
  71084. window.addEventListener('message', doApplyTo, false);
  71085. window.postMessage(message, '*');
  71086. },
  71087. onAnimationStop: function(animation) {
  71088. var runningAnimationsData = this.runningAnimationsData,
  71089. id, runningData, sessions, i, ln, session;
  71090. for (id in runningAnimationsData) {
  71091. if (runningAnimationsData.hasOwnProperty(id)) {
  71092. runningData = runningAnimationsData[id];
  71093. sessions = runningData.sessions;
  71094. for (i = 0,ln = sessions.length; i < ln; i++) {
  71095. session = sessions[i];
  71096. if (session.animation === animation) {
  71097. this.refreshRunningAnimationsData(session.element, session.list.slice(), false);
  71098. }
  71099. }
  71100. }
  71101. }
  71102. }
  71103. });
  71104. /**
  71105. * @class Ext.fx.Runner
  71106. * @private
  71107. */
  71108. Ext.define('Ext.fx.Runner', {
  71109. requires: [
  71110. 'Ext.fx.runner.CssTransition'
  71111. // 'Ext.fx.runner.CssAnimation'
  71112. ],
  71113. constructor: function() {
  71114. return new Ext.fx.runner.CssTransition();
  71115. }
  71116. });
  71117. /**
  71118. * @private
  71119. */
  71120. Ext.define('Ext.fx.animation.Cube', {
  71121. extend: 'Ext.fx.animation.Abstract',
  71122. alias: 'animation.cube',
  71123. config: {
  71124. /**
  71125. * @cfg
  71126. * @inheritdoc
  71127. */
  71128. before: {
  71129. // 'transform-style': 'preserve-3d'
  71130. },
  71131. after: {},
  71132. /**
  71133. * @cfg {String} direction The direction of which the slide animates
  71134. * @accessor
  71135. */
  71136. direction: 'right',
  71137. out: false
  71138. },
  71139. // getData: function() {
  71140. // var to = this.getTo(),
  71141. // from = this.getFrom(),
  71142. // out = this.getOut(),
  71143. // direction = this.getDirection(),
  71144. // el = this.getElement(),
  71145. // elW = el.getWidth(),
  71146. // elH = el.getHeight(),
  71147. // halfWidth = (elW / 2),
  71148. // halfHeight = (elH / 2),
  71149. // fromTransform = {},
  71150. // toTransform = {},
  71151. // originalFromTransform = {
  71152. // rotateY: 0,
  71153. // translateX: 0,
  71154. // translateZ: 0
  71155. // },
  71156. // originalToTransform = {
  71157. // rotateY: 90,
  71158. // translateX: halfWidth,
  71159. // translateZ: halfWidth
  71160. // },
  71161. // originalVerticalFromTransform = {
  71162. // rotateX: 0,
  71163. // translateY: 0,
  71164. // translateZ: 0
  71165. // },
  71166. // originalVerticalToTransform = {
  71167. // rotateX: 90,
  71168. // translateY: halfHeight,
  71169. // translateZ: halfHeight
  71170. // },
  71171. // tempTransform;
  71172. //
  71173. // if (direction == "left" || direction == "right") {
  71174. // if (out) {
  71175. // toTransform = originalToTransform;
  71176. // fromTransform = originalFromTransform;
  71177. // } else {
  71178. // toTransform = originalFromTransform;
  71179. // fromTransform = originalToTransform;
  71180. // fromTransform.rotateY *= -1;
  71181. // fromTransform.translateX *= -1;
  71182. // }
  71183. //
  71184. // if (direction === 'right') {
  71185. // tempTransform = fromTransform;
  71186. // fromTransform = toTransform;
  71187. // toTransform = tempTransform;
  71188. // }
  71189. // }
  71190. //
  71191. // if (direction == "up" || direction == "down") {
  71192. // if (out) {
  71193. // toTransform = originalVerticalFromTransform;
  71194. // fromTransform = {
  71195. // rotateX: -90,
  71196. // translateY: halfHeight,
  71197. // translateZ: halfHeight
  71198. // };
  71199. // } else {
  71200. // fromTransform = originalVerticalFromTransform;
  71201. // toTransform = {
  71202. // rotateX: 90,
  71203. // translateY: -halfHeight,
  71204. // translateZ: halfHeight
  71205. // };
  71206. // }
  71207. //
  71208. // if (direction == "up") {
  71209. // tempTransform = fromTransform;
  71210. // fromTransform = toTransform;
  71211. // toTransform = tempTransform;
  71212. // }
  71213. // }
  71214. //
  71215. // from.set('transform', fromTransform);
  71216. // to.set('transform', toTransform);
  71217. //
  71218. // return this.callParent(arguments);
  71219. // },
  71220. getData: function() {
  71221. var to = this.getTo(),
  71222. from = this.getFrom(),
  71223. before = this.getBefore(),
  71224. after = this.getAfter(),
  71225. out = this.getOut(),
  71226. direction = this.getDirection(),
  71227. el = this.getElement(),
  71228. elW = el.getWidth(),
  71229. elH = el.getHeight(),
  71230. origin = out ? '100% 100%' : '0% 0%',
  71231. fromOpacity = 1,
  71232. toOpacity = 1,
  71233. transformFrom = {
  71234. rotateY: 0,
  71235. translateZ: 0
  71236. },
  71237. transformTo = {
  71238. rotateY: 0,
  71239. translateZ: 0
  71240. };
  71241. if (direction == "left" || direction == "right") {
  71242. if (out) {
  71243. toOpacity = 0.5;
  71244. transformTo.translateZ = elW;
  71245. transformTo.rotateY = -90;
  71246. } else {
  71247. fromOpacity = 0.5;
  71248. transformFrom.translateZ = elW;
  71249. transformFrom.rotateY = 90;
  71250. }
  71251. }
  71252. before['transform-origin'] = origin;
  71253. after['transform-origin'] = null;
  71254. to.set('transform', transformTo);
  71255. from.set('transform', transformFrom);
  71256. from.set('opacity', fromOpacity);
  71257. to.set('opacity', toOpacity);
  71258. return this.callParent(arguments);
  71259. }
  71260. });
  71261. /**
  71262. * @private
  71263. */
  71264. Ext.define('Ext.fx.animation.Wipe', {
  71265. extend: 'Ext.fx.Animation',
  71266. alternateClassName: 'Ext.fx.animation.WipeIn',
  71267. config: {
  71268. /**
  71269. * @cfg
  71270. * @inheritdoc
  71271. */
  71272. easing: 'ease-out',
  71273. /**
  71274. * @cfg {String} direction The direction of which the slide animates
  71275. * @accessor
  71276. */
  71277. direction: 'right',
  71278. /**
  71279. * @cfg {Boolean} out True if you want to make this animation wipe out, instead of slide in.
  71280. * @accessor
  71281. */
  71282. out: false
  71283. },
  71284. refresh: function() {
  71285. var me = this,
  71286. el = me.getElement(),
  71287. elBox = el.dom.getBoundingClientRect(),
  71288. elWidth = elBox.width,
  71289. elHeight = elBox.height,
  71290. from = me.getFrom(),
  71291. to = me.getTo(),
  71292. out = me.getOut(),
  71293. direction = me.getDirection(),
  71294. maskFromX = 0,
  71295. maskFromY = 0,
  71296. maskToX = 0,
  71297. maskToY = 0,
  71298. mask, tmp;
  71299. switch (direction) {
  71300. case 'up':
  71301. if (out) {
  71302. mask = '-webkit-gradient(linear, left top, left bottom, from(#000), to(transparent), color-stop(33%, #000), color-stop(66%, transparent))';
  71303. maskFromY = elHeight * 3 + 'px';
  71304. maskToY = elHeight + 'px';
  71305. } else {
  71306. mask = '-webkit-gradient(linear, left top, left bottom, from(transparent), to(#000), color-stop(66%, #000), color-stop(33%, transparent))';
  71307. maskFromY = -elHeight * 2 + 'px';
  71308. maskToY = 0;
  71309. }
  71310. break;
  71311. case 'down':
  71312. if (out) {
  71313. mask = '-webkit-gradient(linear, left top, left bottom, from(transparent), to(#000), color-stop(66%, #000), color-stop(33%, transparent))';
  71314. maskFromY = -elHeight * 2 + 'px';
  71315. maskToY = 0;
  71316. } else {
  71317. mask = '-webkit-gradient(linear, left top, left bottom, from(#000), to(transparent), color-stop(33%, #000), color-stop(66%, transparent))';
  71318. maskFromY = elHeight * 3 + 'px';
  71319. maskToY = elHeight + 'px';
  71320. }
  71321. break;
  71322. case 'right':
  71323. if (out) {
  71324. mask = '-webkit-gradient(linear, right top, left top, from(#000), to(transparent), color-stop(33%, #000), color-stop(66%, transparent))';
  71325. maskFromX = -elWidth * 2 + 'px';
  71326. maskToX = 0;
  71327. } else {
  71328. mask = '-webkit-gradient(linear, right top, left top, from(transparent), to(#000), color-stop(66%, #000), color-stop(33%, transparent))';
  71329. maskToX = -elWidth * 2 + 'px';
  71330. }
  71331. break;
  71332. case 'left':
  71333. if (out) {
  71334. mask = '-webkit-gradient(linear, right top, left top, from(transparent), to(#000), color-stop(66%, #000), color-stop(33%, transparent))';
  71335. maskToX = -elWidth * 2 + 'px';
  71336. } else {
  71337. mask = '-webkit-gradient(linear, right top, left top, from(#000), to(transparent), color-stop(33%, #000), color-stop(66%, transparent))';
  71338. maskFromX = -elWidth * 2 + 'px';
  71339. maskToX = 0;
  71340. }
  71341. break;
  71342. }
  71343. if (!out) {
  71344. tmp = maskFromY;
  71345. maskFromY = maskToY;
  71346. maskToY = tmp;
  71347. tmp = maskFromX;
  71348. maskFromX = maskToX;
  71349. maskToX = tmp;
  71350. }
  71351. from.set('mask-image', mask);
  71352. from.set('mask-size', elWidth * 3 + 'px ' + elHeight * 3 + 'px');
  71353. from.set('mask-position-x', maskFromX);
  71354. from.set('mask-position-y', maskFromY);
  71355. to.set('mask-position-x', maskToX);
  71356. to.set('mask-position-y', maskToY);
  71357. // me.setEasing(out ? 'ease-in' : 'ease-out');
  71358. },
  71359. getData: function() {
  71360. this.refresh();
  71361. return this.callParent(arguments);
  71362. }
  71363. });
  71364. /**
  71365. * @private
  71366. */
  71367. Ext.define('Ext.fx.animation.WipeOut', {
  71368. extend: 'Ext.fx.animation.Wipe',
  71369. config: {
  71370. // @hide
  71371. out: true
  71372. }
  71373. });
  71374. /**
  71375. * @private
  71376. */
  71377. Ext.define('Ext.fx.easing.EaseIn', {
  71378. extend: 'Ext.fx.easing.Linear',
  71379. alias: 'easing.ease-in',
  71380. config: {
  71381. exponent: 4,
  71382. duration: 1500
  71383. },
  71384. getValue: function() {
  71385. var deltaTime = Ext.Date.now() - this.getStartTime(),
  71386. duration = this.getDuration(),
  71387. startValue = this.getStartValue(),
  71388. endValue = this.getEndValue(),
  71389. distance = this.distance,
  71390. theta = deltaTime / duration,
  71391. thetaEnd = Math.pow(theta, this.getExponent()),
  71392. currentValue = startValue + (thetaEnd * distance);
  71393. if (deltaTime >= duration) {
  71394. this.isEnded = true;
  71395. return endValue;
  71396. }
  71397. return currentValue;
  71398. }
  71399. });
  71400. /**
  71401. * @private
  71402. */
  71403. Ext.define('Ext.fx.layout.card.Cube', {
  71404. extend: 'Ext.fx.layout.card.Style',
  71405. alias: 'fx.layout.card.cube',
  71406. config: {
  71407. reverse: null,
  71408. inAnimation: {
  71409. type: 'cube'
  71410. },
  71411. outAnimation: {
  71412. type: 'cube',
  71413. out: true
  71414. }
  71415. }
  71416. });
  71417. /**
  71418. * @private
  71419. */
  71420. Ext.define('Ext.fx.layout.card.ScrollCover', {
  71421. extend: 'Ext.fx.layout.card.Scroll',
  71422. alias: 'fx.layout.card.scrollcover',
  71423. onActiveItemChange: function(cardLayout, inItem, outItem, options, controller) {
  71424. var containerElement, containerSize, xy, animConfig,
  71425. inTranslate, outTranslate;
  71426. this.lastController = controller;
  71427. this.inItem = inItem;
  71428. if (inItem && outItem) {
  71429. containerElement = this.getLayout().container.innerElement;
  71430. containerSize = containerElement.getSize();
  71431. xy = this.calculateXY(containerSize);
  71432. animConfig = {
  71433. easing: this.getEasing(),
  71434. duration: this.getDuration()
  71435. };
  71436. inItem.renderElement.dom.style.setProperty('visibility', 'hidden', '!important');
  71437. inTranslate = inItem.setTranslatable(true).getTranslatable();
  71438. outTranslate = outItem.setTranslatable(true).getTranslatable();
  71439. outTranslate.translate({ x: 0, y: 0});
  71440. // outItem.setTranslate(null);
  71441. inTranslate.translate({ x: xy.left, y: xy.top});
  71442. inTranslate.getWrapper().dom.style.setProperty('z-index', '100', '!important');
  71443. inItem.show();
  71444. inTranslate.on({
  71445. animationstart: 'onInAnimationStart',
  71446. animationend: 'onInAnimationEnd',
  71447. scope: this
  71448. });
  71449. inTranslate.translateAnimated({ x: 0, y: 0}, animConfig);
  71450. controller.pause();
  71451. }
  71452. },
  71453. onInAnimationStart: function() {
  71454. this.inItem.renderElement.dom.style.removeProperty('visibility');
  71455. },
  71456. onInAnimationEnd: function() {
  71457. this.inItem.getTranslatable().getWrapper().dom.style.removeProperty('z-index'); // Remove this when we can remove translatable
  71458. // this.inItem.setTranslatable(null);
  71459. this.lastController.resume();
  71460. }
  71461. });
  71462. /**
  71463. * @private
  71464. */
  71465. Ext.define('Ext.fx.layout.card.ScrollReveal', {
  71466. extend: 'Ext.fx.layout.card.Scroll',
  71467. alias: 'fx.layout.card.scrollreveal',
  71468. onActiveItemChange: function(cardLayout, inItem, outItem, options, controller) {
  71469. var containerElement, containerSize, xy, animConfig,
  71470. outTranslate, inTranslate;
  71471. this.lastController = controller;
  71472. this.outItem = outItem;
  71473. this.inItem = inItem;
  71474. if (inItem && outItem) {
  71475. containerElement = this.getLayout().container.innerElement;
  71476. containerSize = containerElement.getSize();
  71477. xy = this.calculateXY(containerSize);
  71478. animConfig = {
  71479. easing: this.getEasing(),
  71480. duration: this.getDuration()
  71481. };
  71482. outTranslate = outItem.setTranslatable(true).getTranslatable();
  71483. inTranslate = inItem.setTranslatable(true).getTranslatable();
  71484. outTranslate.getWrapper().dom.style.setProperty('z-index', '100', '!important');
  71485. outTranslate.translate({ x: 0, y: 0});
  71486. inTranslate.translate({ x: 0, y: 0});
  71487. inItem.show();
  71488. outTranslate.on({
  71489. animationend: 'onOutAnimationEnd',
  71490. scope: this
  71491. });
  71492. outTranslate.translateAnimated({ x: xy.x, y: xy.y}, animConfig);
  71493. controller.pause();
  71494. }
  71495. },
  71496. onOutAnimationEnd: function() {
  71497. this.outItem.getTranslatable().getWrapper().dom.style.removeProperty('z-index'); // Remove this when we can remove translatable
  71498. // this.outItem.setTranslatable(null);
  71499. this.lastController.resume();
  71500. }
  71501. });
  71502. /**
  71503. * @author Jacky Nguyen <jacky@sencha.com>
  71504. * @private
  71505. */
  71506. Ext.define('Ext.fx.runner.CssAnimation', {
  71507. extend: 'Ext.fx.runner.Css',
  71508. constructor: function() {
  71509. this.runningAnimationsMap = {};
  71510. this.elementEndStates = {};
  71511. this.animationElementMap = {};
  71512. this.keyframesRulesCache = {};
  71513. this.uniqueId = 0;
  71514. return this.callParent(arguments);
  71515. },
  71516. attachListeners: function() {
  71517. var eventDispatcher = this.getEventDispatcher();
  71518. this.listenersAttached = true;
  71519. eventDispatcher.addListener('element', '*', 'animationstart', 'onAnimationStart', this);
  71520. eventDispatcher.addListener('element', '*', 'animationend', 'onAnimationEnd', this);
  71521. },
  71522. onAnimationStart: function(e) {
  71523. var name = e.browserEvent.animationName,
  71524. elementId = this.animationElementMap[name],
  71525. animation = this.runningAnimationsMap[elementId][name],
  71526. elementEndStates = this.elementEndStates,
  71527. elementEndState = elementEndStates[elementId],
  71528. data = {};
  71529. console.log("START============= " + name);
  71530. if (elementEndState) {
  71531. delete elementEndStates[elementId];
  71532. data[elementId] = elementEndState;
  71533. this.applyStyles(data);
  71534. }
  71535. if (animation.before) {
  71536. data[elementId] = animation.before;
  71537. this.applyStyles(data);
  71538. }
  71539. },
  71540. onAnimationEnd: function(e) {
  71541. var element = e.target,
  71542. name = e.browserEvent.animationName,
  71543. animationElementMap = this.animationElementMap,
  71544. elementId = animationElementMap[name],
  71545. runningAnimationsMap = this.runningAnimationsMap,
  71546. runningAnimations = runningAnimationsMap[elementId],
  71547. animation = runningAnimations[name];
  71548. console.log("END============= " + name);
  71549. if (animation.onBeforeEnd) {
  71550. animation.onBeforeEnd.call(animation.scope || this, element);
  71551. }
  71552. if (animation.onEnd) {
  71553. animation.onEnd.call(animation.scope || this, element);
  71554. }
  71555. delete animationElementMap[name];
  71556. delete runningAnimations[name];
  71557. this.removeKeyframesRule(name);
  71558. },
  71559. generateAnimationId: function() {
  71560. return 'animation-' + (++this.uniqueId);
  71561. },
  71562. run: function(animations) {
  71563. var data = {},
  71564. elementEndStates = this.elementEndStates,
  71565. animationElementMap = this.animationElementMap,
  71566. runningAnimationsMap = this.runningAnimationsMap,
  71567. runningAnimations, states,
  71568. elementId, animationId, i, ln, animation,
  71569. name, runningAnimation,
  71570. names, durations, easings, delays, directions, iterations;
  71571. if (!this.listenersAttached) {
  71572. this.attachListeners();
  71573. }
  71574. animations = Ext.Array.from(animations);
  71575. for (i = 0,ln = animations.length; i < ln; i++) {
  71576. animation = animations[i];
  71577. animation = Ext.factory(animation, Ext.fx.Animation);
  71578. elementId = animation.getElement().getId();
  71579. animationId = animation.getName() || this.generateAnimationId();
  71580. animationElementMap[animationId] = elementId;
  71581. animation = animation.getData();
  71582. states = animation.states;
  71583. this.addKeyframesRule(animationId, states);
  71584. runningAnimations = runningAnimationsMap[elementId];
  71585. if (!runningAnimations) {
  71586. runningAnimations = runningAnimationsMap[elementId] = {};
  71587. }
  71588. runningAnimations[animationId] = animation;
  71589. names = [];
  71590. durations = [];
  71591. easings = [];
  71592. delays = [];
  71593. directions = [];
  71594. iterations = [];
  71595. for (name in runningAnimations) {
  71596. if (runningAnimations.hasOwnProperty(name)) {
  71597. runningAnimation = runningAnimations[name];
  71598. names.push(name);
  71599. durations.push(runningAnimation.duration);
  71600. easings.push(runningAnimation.easing);
  71601. delays.push(runningAnimation.delay);
  71602. directions.push(runningAnimation.direction);
  71603. iterations.push(runningAnimation.iteration);
  71604. }
  71605. }
  71606. data[elementId] = {
  71607. 'animation-name' : names,
  71608. 'animation-duration' : durations,
  71609. 'animation-timing-function' : easings,
  71610. 'animation-delay' : delays,
  71611. 'animation-direction' : directions,
  71612. 'animation-iteration-count' : iterations
  71613. };
  71614. // Ext.apply(data[elementId], animation.origin);
  71615. if (animation.preserveEndState) {
  71616. elementEndStates[elementId] = states['100%'];
  71617. }
  71618. }
  71619. this.applyStyles(data);
  71620. },
  71621. addKeyframesRule: function(name, keyframes) {
  71622. var percentage, properties,
  71623. keyframesRule,
  71624. styleSheet, rules, styles, rulesLength, key, value;
  71625. styleSheet = this.getStyleSheet();
  71626. rules = styleSheet.cssRules;
  71627. rulesLength = rules.length;
  71628. styleSheet.insertRule('@' + this.vendorPrefix + 'keyframes ' + name + '{}', rulesLength);
  71629. keyframesRule = rules[rulesLength];
  71630. for (percentage in keyframes) {
  71631. properties = keyframes[percentage];
  71632. rules = keyframesRule.cssRules;
  71633. rulesLength = rules.length;
  71634. styles = [];
  71635. for (key in properties) {
  71636. value = this.formatValue(properties[key], key);
  71637. key = this.formatName(key);
  71638. styles.push(key + ':' + value);
  71639. }
  71640. keyframesRule.insertRule(percentage + '{' + styles.join(';') + '}', rulesLength);
  71641. }
  71642. return this;
  71643. },
  71644. removeKeyframesRule: function(name) {
  71645. var styleSheet = this.getStyleSheet(),
  71646. rules = styleSheet.cssRules,
  71647. i, ln, rule;
  71648. for (i = 0,ln = rules.length; i < ln; i++) {
  71649. rule = rules[i];
  71650. if (rule.name === name) {
  71651. styleSheet.removeRule(i);
  71652. break;
  71653. }
  71654. }
  71655. return this;
  71656. }
  71657. });
  71658. //<feature logger>
  71659. Ext.define('Ext.log.Base', {
  71660. config: {},
  71661. constructor: function(config) {
  71662. this.initConfig(config);
  71663. return this;
  71664. }
  71665. });
  71666. //</feature>
  71667. //<feature logger>
  71668. /**
  71669. * @class Ext.Logger
  71670. * Logs messages to help with debugging.
  71671. *
  71672. * ## Example
  71673. *
  71674. * Ext.Logger.deprecate('This method is no longer supported.');
  71675. *
  71676. * @singleton
  71677. */
  71678. (function() {
  71679. var Logger = Ext.define('Ext.log.Logger', {
  71680. extend: 'Ext.log.Base',
  71681. statics: {
  71682. defaultPriority: 'info',
  71683. priorities: {
  71684. /**
  71685. * @method verbose
  71686. * Convenience method for {@link #log} with priority 'verbose'.
  71687. */
  71688. verbose: 0,
  71689. /**
  71690. * @method info
  71691. * Convenience method for {@link #log} with priority 'info'.
  71692. */
  71693. info: 1,
  71694. /**
  71695. * @method deprecate
  71696. * Convenience method for {@link #log} with priority 'deprecate'.
  71697. */
  71698. deprecate: 2,
  71699. /**
  71700. * @method warn
  71701. * Convenience method for {@link #log} with priority 'warn'.
  71702. */
  71703. warn: 3,
  71704. /**
  71705. * @method error
  71706. * Convenience method for {@link #log} with priority 'error'.
  71707. */
  71708. error: 4
  71709. }
  71710. },
  71711. config: {
  71712. enabled: true,
  71713. minPriority: 'deprecate',
  71714. writers: {}
  71715. },
  71716. /**
  71717. * Logs a message to help with debugging.
  71718. * @param {String} message Message to log.
  71719. * @param {Number} priority Priority of the log message.
  71720. */
  71721. log: function(message, priority, callerId) {
  71722. if (!this.getEnabled()) {
  71723. return this;
  71724. }
  71725. var statics = Logger,
  71726. priorities = statics.priorities,
  71727. priorityValue = priorities[priority],
  71728. caller = this.log.caller,
  71729. callerDisplayName = '',
  71730. writers = this.getWriters(),
  71731. event, i, originalCaller;
  71732. if (!priority) {
  71733. priority = 'info';
  71734. }
  71735. if (priorities[this.getMinPriority()] > priorityValue) {
  71736. return this;
  71737. }
  71738. if (!callerId) {
  71739. callerId = 1;
  71740. }
  71741. if (Ext.isArray(message)) {
  71742. message = message.join(" ");
  71743. }
  71744. else {
  71745. message = String(message);
  71746. }
  71747. if (typeof callerId == 'number') {
  71748. i = callerId;
  71749. do {
  71750. i--;
  71751. caller = caller.caller;
  71752. if (!caller) {
  71753. break;
  71754. }
  71755. if (!originalCaller) {
  71756. originalCaller = caller.caller;
  71757. }
  71758. if (i <= 0 && caller.displayName) {
  71759. break;
  71760. }
  71761. }
  71762. while (caller !== originalCaller);
  71763. callerDisplayName = Ext.getDisplayName(caller);
  71764. }
  71765. else {
  71766. caller = caller.caller;
  71767. callerDisplayName = Ext.getDisplayName(callerId) + '#' + caller.$name;
  71768. }
  71769. event = {
  71770. time: Ext.Date.now(),
  71771. priority: priorityValue,
  71772. priorityName: priority,
  71773. message: message,
  71774. caller: caller,
  71775. callerDisplayName: callerDisplayName
  71776. };
  71777. for (i in writers) {
  71778. if (writers.hasOwnProperty(i)) {
  71779. writers[i].write(Ext.merge({}, event));
  71780. }
  71781. }
  71782. return this;
  71783. }
  71784. }, function() {
  71785. Ext.Object.each(this.priorities, function(priority) {
  71786. this.override(priority, function(message, callerId) {
  71787. if (!callerId) {
  71788. callerId = 1;
  71789. }
  71790. if (typeof callerId == 'number') {
  71791. callerId += 1;
  71792. }
  71793. this.log(message, priority, callerId);
  71794. });
  71795. }, this);
  71796. });
  71797. })();
  71798. //</feature>
  71799. //<feature logger>
  71800. Ext.define('Ext.log.filter.Filter', {
  71801. extend: 'Ext.log.Base',
  71802. accept: function(event) {
  71803. return true;
  71804. }
  71805. });
  71806. //</feature>
  71807. //<feature logger>
  71808. Ext.define('Ext.log.filter.Priority', {
  71809. extend: 'Ext.log.filter.Filter',
  71810. config: {
  71811. minPriority: 1
  71812. },
  71813. accept: function(event) {
  71814. return event.priority >= this.getMinPriority();
  71815. }
  71816. });
  71817. //</feature>
  71818. //<feature logger>
  71819. Ext.define('Ext.log.formatter.Formatter', {
  71820. extend: 'Ext.log.Base',
  71821. config: {
  71822. messageFormat: "{message}"
  71823. },
  71824. format: function(event) {
  71825. return this.substitute(this.getMessageFormat(), event);
  71826. },
  71827. substitute: function(template, data) {
  71828. var name, value;
  71829. for (name in data) {
  71830. if (data.hasOwnProperty(name)) {
  71831. value = data[name];
  71832. template = template.replace(new RegExp("\\{" + name + "\\}", "g"), value);
  71833. }
  71834. }
  71835. return template;
  71836. }
  71837. });
  71838. //</feature>
  71839. //<feature logger>
  71840. Ext.define('Ext.log.formatter.Default', {
  71841. extend: 'Ext.log.formatter.Formatter',
  71842. config: {
  71843. messageFormat: "[{priorityName}][{callerDisplayName}] {message}"
  71844. },
  71845. format: function(event) {
  71846. var event = Ext.merge({}, event, {
  71847. priorityName: event.priorityName.toUpperCase()
  71848. });
  71849. return this.callParent([event]);
  71850. }
  71851. });
  71852. //</feature>
  71853. //<feature logger>
  71854. Ext.define('Ext.log.formatter.Identity', {
  71855. extend: 'Ext.log.formatter.Default',
  71856. config: {
  71857. messageFormat: "[{osIdentity}][{browserIdentity}][{timestamp}][{priorityName}][{callerDisplayName}] {message}"
  71858. },
  71859. format: function(event) {
  71860. event.timestamp = Ext.Date.toString();
  71861. event.browserIdentity = Ext.browser.name + ' ' + Ext.browser.version;
  71862. event.osIdentity = Ext.os.name + ' ' + Ext.os.version;
  71863. return this.callParent(arguments);
  71864. }
  71865. });
  71866. //</feature>
  71867. //<feature logger>
  71868. Ext.define('Ext.log.writer.Writer', {
  71869. extend: 'Ext.log.Base',
  71870. requires: ['Ext.log.formatter.Formatter'],
  71871. config: {
  71872. formatter: null,
  71873. filters: {}
  71874. },
  71875. constructor: function() {
  71876. this.activeFilters = [];
  71877. return this.callParent(arguments);
  71878. },
  71879. updateFilters: function(filters) {
  71880. var activeFilters = this.activeFilters,
  71881. i, filter;
  71882. activeFilters.length = 0;
  71883. for (i in filters) {
  71884. if (filters.hasOwnProperty(i)) {
  71885. filter = filters[i];
  71886. activeFilters.push(filter);
  71887. }
  71888. }
  71889. },
  71890. write: function(event) {
  71891. var filters = this.activeFilters,
  71892. formatter = this.getFormatter(),
  71893. i, ln, filter;
  71894. for (i = 0,ln = filters.length; i < ln; i++) {
  71895. filter = filters[i];
  71896. if (!filters[i].accept(event)) {
  71897. return this;
  71898. }
  71899. }
  71900. if (formatter) {
  71901. event.message = formatter.format(event);
  71902. }
  71903. this.doWrite(event);
  71904. return this;
  71905. },
  71906. // @private
  71907. doWrite: Ext.emptyFn
  71908. });
  71909. //</feature>
  71910. //<feature logger>
  71911. Ext.define('Ext.log.writer.Console', {
  71912. extend: 'Ext.log.writer.Writer',
  71913. config: {
  71914. throwOnErrors: true,
  71915. throwOnWarnings: false
  71916. },
  71917. doWrite: function(event) {
  71918. var message = event.message,
  71919. priority = event.priorityName,
  71920. consoleMethod;
  71921. if (priority === 'error' && this.getThrowOnErrors()) {
  71922. throw new Error(message);
  71923. }
  71924. if (typeof console !== 'undefined') {
  71925. consoleMethod = priority;
  71926. if (consoleMethod === 'deprecate') {
  71927. consoleMethod = 'warn';
  71928. }
  71929. if (consoleMethod === 'warn' && this.getThrowOnWarnings()) {
  71930. throw new Error(message);
  71931. }
  71932. if (!(consoleMethod in console)) {
  71933. consoleMethod = 'log';
  71934. }
  71935. console[consoleMethod](message);
  71936. }
  71937. }
  71938. });
  71939. //</feature>
  71940. //<feature logger>
  71941. Ext.define('Ext.log.writer.DocumentTitle', {
  71942. extend: 'Ext.log.writer.Writer',
  71943. doWrite: function(event) {
  71944. var message = event.message;
  71945. document.title = message;
  71946. }
  71947. });
  71948. //</feature>
  71949. //<feature logger>
  71950. Ext.define('Ext.log.writer.Remote', {
  71951. extend: 'Ext.log.writer.Writer',
  71952. requires: [
  71953. 'Ext.Ajax'
  71954. ],
  71955. config: {
  71956. batchSendDelay: 100,
  71957. onFailureRetryDelay: 500,
  71958. url: ''
  71959. },
  71960. isSending: false,
  71961. sendingTimer: null,
  71962. constructor: function() {
  71963. this.queue = [];
  71964. this.send = Ext.Function.bind(this.send, this);
  71965. return this.callParent(arguments);
  71966. },
  71967. doWrite: function(event) {
  71968. var queue = this.queue;
  71969. queue.push(event.message);
  71970. if (!this.isSending && this.sendingTimer === null) {
  71971. this.sendingTimer = setTimeout(this.send, this.getBatchSendDelay());
  71972. }
  71973. },
  71974. send: function() {
  71975. var queue = this.queue,
  71976. messages = queue.slice();
  71977. queue.length = 0;
  71978. this.sendingTimer = null;
  71979. if (messages.length > 0) {
  71980. this.doSend(messages);
  71981. }
  71982. },
  71983. doSend: function(messages) {
  71984. var me = this;
  71985. me.isSending = true;
  71986. Ext.Ajax.request({
  71987. url: me.getUrl(),
  71988. method: 'POST',
  71989. params: {
  71990. messages: messages.join("\n")
  71991. },
  71992. success: function(){
  71993. me.isSending = false;
  71994. me.send();
  71995. },
  71996. failure: function() {
  71997. setTimeout(function() {
  71998. me.doSend(messages);
  71999. }, me.getOnFailureRetryDelay());
  72000. },
  72001. scope: me
  72002. });
  72003. }
  72004. });
  72005. //</feature>
  72006. /**
  72007. * This component is used in {@link Ext.navigation.View} to control animations in the toolbar. You should never need to
  72008. * interact with the component directly, unless you are subclassing it.
  72009. * @private
  72010. * @author Robert Dougan <rob@sencha.com>
  72011. */
  72012. Ext.define('Ext.navigation.Bar', {
  72013. extend: 'Ext.TitleBar',
  72014. requires: [
  72015. 'Ext.Button',
  72016. 'Ext.Spacer'
  72017. ],
  72018. // @private
  72019. isToolbar: true,
  72020. config: {
  72021. /**
  72022. * @cfg
  72023. * @inheritdoc
  72024. */
  72025. baseCls: Ext.baseCSSPrefix + 'toolbar',
  72026. /**
  72027. * @cfg
  72028. * @inheritdoc
  72029. */
  72030. cls: Ext.baseCSSPrefix + 'navigation-bar',
  72031. /**
  72032. * @cfg {String} ui
  72033. * Style options for Toolbar. Either 'light' or 'dark'.
  72034. * @accessor
  72035. */
  72036. ui: 'dark',
  72037. /**
  72038. * @cfg {String} title
  72039. * The title of the toolbar. You should NEVER set this, it is used internally. You set the title of the
  72040. * navigation bar by giving a navigation views children a title configuration.
  72041. * @private
  72042. * @accessor
  72043. */
  72044. title: null,
  72045. /**
  72046. * @cfg
  72047. * @hide
  72048. * @accessor
  72049. */
  72050. defaultType: 'button',
  72051. /**
  72052. * @cfg
  72053. * @ignore
  72054. * @accessor
  72055. */
  72056. layout: {
  72057. type: 'hbox'
  72058. },
  72059. /**
  72060. * @cfg {Array/Object} items The child items to add to this NavigationBar. The {@link #cfg-defaultType} of
  72061. * a NavigationBar is {@link Ext.Button}, so you do not need to specify an `xtype` if you are adding
  72062. * buttons.
  72063. *
  72064. * You can also give items a `align` configuration which will align the item to the `left` or `right` of
  72065. * the NavigationBar.
  72066. * @hide
  72067. * @accessor
  72068. */
  72069. /**
  72070. * @cfg {String} defaultBackButtonText
  72071. * The text to be displayed on the back button if:
  72072. * a) The previous view does not have a title
  72073. * b) The {@link #useTitleForBackButtonText} configuration is true.
  72074. * @private
  72075. * @accessor
  72076. */
  72077. defaultBackButtonText: 'Back',
  72078. /**
  72079. * @cfg {Object} animation
  72080. * @private
  72081. * @accessor
  72082. */
  72083. animation: {
  72084. duration: 300
  72085. },
  72086. /**
  72087. * @cfg {Boolean} useTitleForBackButtonText
  72088. * Set to false if you always want to display the {@link #defaultBackButtonText} as the text
  72089. * on the back button. True if you want to use the previous views title.
  72090. * @private
  72091. * @accessor
  72092. */
  72093. useTitleForBackButtonText: null,
  72094. /**
  72095. * @cfg {Ext.navigation.View} view A reference to the navigation view this bar is linked to.
  72096. * @private
  72097. * @accessor
  72098. */
  72099. view: null,
  72100. /**
  72101. * @cfg {Boolean} androidAnimation Optionally enable CSS transforms on Android 2
  72102. * for NavigationBar animations. Note that this may cause flickering if the
  72103. * NavigationBar is hidden.
  72104. * @accessor
  72105. */
  72106. android2Transforms: false,
  72107. /**
  72108. * @cfg {Ext.Button/Object} backButton The configuration for the back button
  72109. * @private
  72110. * @accessor
  72111. */
  72112. backButton: {
  72113. align: 'left',
  72114. ui: 'back',
  72115. hidden: true
  72116. }
  72117. },
  72118. /**
  72119. * @event back
  72120. * Fires when the back button was tapped.
  72121. * @param {Ext.navigation.Bar} this This bar
  72122. */
  72123. constructor: function(config) {
  72124. config = config || {};
  72125. if (!config.items) {
  72126. config.items = [];
  72127. }
  72128. this.backButtonStack = [];
  72129. this.activeAnimations = [];
  72130. this.callParent([config]);
  72131. },
  72132. /**
  72133. * @private
  72134. */
  72135. applyBackButton: function(config) {
  72136. return Ext.factory(config, Ext.Button, this.getBackButton());
  72137. },
  72138. /**
  72139. * @private
  72140. */
  72141. updateBackButton: function(newBackButton, oldBackButton) {
  72142. if (oldBackButton) {
  72143. this.remove(oldBackButton);
  72144. }
  72145. if (newBackButton) {
  72146. this.add(newBackButton);
  72147. newBackButton.on({
  72148. scope: this,
  72149. tap: this.onBackButtonTap
  72150. });
  72151. }
  72152. },
  72153. onBackButtonTap: function() {
  72154. this.fireEvent('back', this);
  72155. },
  72156. /**
  72157. * @private
  72158. */
  72159. updateView: function(newView) {
  72160. var me = this,
  72161. backButton = me.getBackButton(),
  72162. innerItems, i, backButtonText, item, title;
  72163. me.getItems();
  72164. if (newView) {
  72165. //update the back button stack with the current inner items of the view
  72166. innerItems = newView.getInnerItems();
  72167. for (i = 0; i < innerItems.length; i++) {
  72168. item = innerItems[i];
  72169. title = (item.getTitle) ? item.getTitle() : item.config.title;
  72170. me.backButtonStack.push(title || '&nbsp;');
  72171. }
  72172. me.setTitle(me.getTitleText());
  72173. backButtonText = me.getBackButtonText();
  72174. if (backButtonText) {
  72175. backButton.setText(backButtonText);
  72176. backButton.show();
  72177. }
  72178. }
  72179. },
  72180. /**
  72181. * @private
  72182. */
  72183. onViewAdd: function(view, item) {
  72184. var me = this,
  72185. backButtonStack = me.backButtonStack,
  72186. hasPrevious, title;
  72187. me.endAnimation();
  72188. title = (item.getTitle) ? item.getTitle() : item.config.title;
  72189. backButtonStack.push(title || '&nbsp;');
  72190. hasPrevious = backButtonStack.length > 1;
  72191. me.doChangeView(view, hasPrevious, false);
  72192. },
  72193. /**
  72194. * @private
  72195. */
  72196. onViewRemove: function(view) {
  72197. var me = this,
  72198. backButtonStack = me.backButtonStack,
  72199. hasPrevious;
  72200. me.endAnimation();
  72201. backButtonStack.pop();
  72202. hasPrevious = backButtonStack.length > 1;
  72203. me.doChangeView(view, hasPrevious, true);
  72204. },
  72205. /**
  72206. * @private
  72207. */
  72208. doChangeView: function(view, hasPrevious, reverse) {
  72209. var me = this,
  72210. leftBox = me.leftBox,
  72211. leftBoxElement = leftBox.element,
  72212. titleComponent = me.titleComponent,
  72213. titleElement = titleComponent.element,
  72214. backButton = me.getBackButton(),
  72215. titleText = me.getTitleText(),
  72216. backButtonText = me.getBackButtonText(),
  72217. animation = me.getAnimation() && view.getLayout().getAnimation(),
  72218. animated = animation && animation.isAnimation && view.isPainted(),
  72219. properties, leftGhost, titleGhost, leftProps, titleProps;
  72220. if (animated) {
  72221. leftGhost = me.createProxy(leftBox.element);
  72222. leftBoxElement.setStyle('opacity', '0');
  72223. backButton.setText(backButtonText);
  72224. backButton[hasPrevious ? 'show' : 'hide']();
  72225. titleGhost = me.createProxy(titleComponent.element.getParent());
  72226. titleElement.setStyle('opacity', '0');
  72227. me.setTitle(titleText);
  72228. me.refreshTitlePosition();
  72229. properties = me.measureView(leftGhost, titleGhost, reverse);
  72230. leftProps = properties.left;
  72231. titleProps = properties.title;
  72232. me.isAnimating = true;
  72233. me.animate(leftBoxElement, leftProps.element);
  72234. me.animate(titleElement, titleProps.element, function() {
  72235. titleElement.setLeft(properties.titleLeft);
  72236. me.isAnimating = false;
  72237. });
  72238. if (Ext.os.is.Android2 && !this.getAndroid2Transforms()) {
  72239. leftGhost.ghost.destroy();
  72240. titleGhost.ghost.destroy();
  72241. }
  72242. else {
  72243. me.animate(leftGhost.ghost, leftProps.ghost);
  72244. me.animate(titleGhost.ghost, titleProps.ghost, function() {
  72245. leftGhost.ghost.destroy();
  72246. titleGhost.ghost.destroy();
  72247. });
  72248. }
  72249. }
  72250. else {
  72251. if (hasPrevious) {
  72252. backButton.setText(backButtonText);
  72253. backButton.show();
  72254. }
  72255. else {
  72256. backButton.hide();
  72257. }
  72258. me.setTitle(titleText);
  72259. }
  72260. },
  72261. /**
  72262. * Calculates and returns the position values needed for the back button when you are pushing a title.
  72263. * @private
  72264. */
  72265. measureView: function(oldLeft, oldTitle, reverse) {
  72266. var me = this,
  72267. barElement = me.element,
  72268. newLeftElement = me.leftBox.element,
  72269. titleElement = me.titleComponent.element,
  72270. minOffset = Math.min(barElement.getWidth() / 3, 200),
  72271. newLeftWidth = newLeftElement.getWidth(),
  72272. barX = barElement.getX(),
  72273. barWidth = barElement.getWidth(),
  72274. titleX = titleElement.getX(),
  72275. titleLeft = titleElement.getLeft(),
  72276. titleWidth = titleElement.getWidth(),
  72277. oldLeftX = oldLeft.x,
  72278. oldLeftWidth = oldLeft.width,
  72279. oldLeftLeft = oldLeft.left,
  72280. useLeft = Ext.os.is.Android2 && !this.getAndroid2Transforms(),
  72281. newOffset, oldOffset, leftAnims, titleAnims, omega, theta;
  72282. theta = barX - oldLeftX - oldLeftWidth;
  72283. if (reverse) {
  72284. newOffset = theta;
  72285. oldOffset = Math.min(titleX - oldLeftWidth, minOffset);
  72286. }
  72287. else {
  72288. oldOffset = theta;
  72289. newOffset = Math.min(titleX - barX, minOffset);
  72290. }
  72291. if (useLeft) {
  72292. leftAnims = {
  72293. element: {
  72294. from: {
  72295. left: newOffset,
  72296. opacity: 1
  72297. },
  72298. to: {
  72299. left: 0,
  72300. opacity: 1
  72301. }
  72302. }
  72303. };
  72304. }
  72305. else {
  72306. leftAnims = {
  72307. element: {
  72308. from: {
  72309. transform: {
  72310. translateX: newOffset
  72311. },
  72312. opacity: 0
  72313. },
  72314. to: {
  72315. transform: {
  72316. translateX: 0
  72317. },
  72318. opacity: 1
  72319. }
  72320. },
  72321. ghost: {
  72322. to: {
  72323. transform: {
  72324. translateX: oldOffset
  72325. },
  72326. opacity: 0
  72327. }
  72328. }
  72329. };
  72330. }
  72331. theta = barX - titleX + newLeftWidth;
  72332. if ((oldLeftLeft + titleWidth) > titleX) {
  72333. omega = barX - titleX - titleWidth;
  72334. }
  72335. if (reverse) {
  72336. titleElement.setLeft(0);
  72337. oldOffset = barX + barWidth;
  72338. if (omega !== undefined) {
  72339. newOffset = omega;
  72340. }
  72341. else {
  72342. newOffset = theta;
  72343. }
  72344. }
  72345. else {
  72346. newOffset = barWidth - titleX;
  72347. if (omega !== undefined) {
  72348. oldOffset = omega;
  72349. }
  72350. else {
  72351. oldOffset = theta;
  72352. }
  72353. }
  72354. if (useLeft) {
  72355. titleAnims = {
  72356. element: {
  72357. from: {
  72358. left: newOffset,
  72359. opacity: 1
  72360. },
  72361. to: {
  72362. left: titleLeft,
  72363. opacity: 1
  72364. }
  72365. }
  72366. };
  72367. }
  72368. else {
  72369. titleAnims = {
  72370. element: {
  72371. from: {
  72372. transform: {
  72373. translateX: newOffset
  72374. },
  72375. opacity: 0
  72376. },
  72377. to: {
  72378. transform: {
  72379. translateX: titleLeft
  72380. },
  72381. opacity: 1
  72382. }
  72383. },
  72384. ghost: {
  72385. to: {
  72386. transform: {
  72387. translateX: oldOffset
  72388. },
  72389. opacity: 0
  72390. }
  72391. }
  72392. };
  72393. }
  72394. return {
  72395. left: leftAnims,
  72396. title: titleAnims,
  72397. titleLeft: titleLeft
  72398. };
  72399. },
  72400. /**
  72401. * Helper method used to animate elements.
  72402. * You pass it an element, objects for the from and to positions an option onEnd callback called when the animation is over.
  72403. * Normally this method is passed configurations returned from the methods such as #measureTitle(true) etc.
  72404. * It is called from the #pushLeftBoxAnimated, #pushTitleAnimated, #popBackButtonAnimated and #popTitleAnimated
  72405. * methods.
  72406. *
  72407. * If the current device is Android, it will use top/left to animate.
  72408. * If it is anything else, it will use transform.
  72409. * @private
  72410. */
  72411. animate: function(element, config, callback) {
  72412. var me = this,
  72413. animation;
  72414. //reset the left of the element
  72415. element.setLeft(0);
  72416. config = Ext.apply(config, {
  72417. element: element,
  72418. easing: 'ease-in-out',
  72419. duration: me.getAnimation().duration || 250,
  72420. preserveEndState: true
  72421. });
  72422. animation = new Ext.fx.Animation(config);
  72423. animation.on('animationend', function() {
  72424. if (callback) {
  72425. callback.call(me);
  72426. }
  72427. }, me);
  72428. Ext.Animator.run(animation);
  72429. me.activeAnimations.push(animation);
  72430. },
  72431. endAnimation: function() {
  72432. var activeAnimations = this.activeAnimations,
  72433. animation, i, ln;
  72434. if (activeAnimations) {
  72435. ln = activeAnimations.length;
  72436. for (i = 0; i < ln; i++) {
  72437. animation = activeAnimations[i];
  72438. if (animation.isAnimating) {
  72439. animation.stopAnimation();
  72440. }
  72441. else {
  72442. animation.destroy();
  72443. }
  72444. }
  72445. this.activeAnimations = [];
  72446. }
  72447. },
  72448. refreshTitlePosition: function() {
  72449. if (!this.isAnimating) {
  72450. this.callParent();
  72451. }
  72452. },
  72453. /**
  72454. * Returns the text needed for the current back button at anytime.
  72455. * @private
  72456. */
  72457. getBackButtonText: function() {
  72458. var text = this.backButtonStack[this.backButtonStack.length - 2],
  72459. useTitleForBackButtonText = this.getUseTitleForBackButtonText();
  72460. if (!useTitleForBackButtonText) {
  72461. if (text) {
  72462. text = this.getDefaultBackButtonText();
  72463. }
  72464. }
  72465. return text;
  72466. },
  72467. /**
  72468. * Returns the text needed for the current title at anytime.
  72469. * @private
  72470. */
  72471. getTitleText: function() {
  72472. return this.backButtonStack[this.backButtonStack.length - 1];
  72473. },
  72474. /**
  72475. * Handles removing back button stacks from this bar
  72476. * @private
  72477. */
  72478. beforePop: function(count) {
  72479. count--;
  72480. for (var i = 0; i < count; i++) {
  72481. this.backButtonStack.pop();
  72482. }
  72483. },
  72484. /**
  72485. * We override the hidden method because we don't want to remove it from the view using display:none. Instead we just position it off
  72486. * the screen, much like the navigation bar proxy. This means that all animations, pushing, popping etc. all still work when if you hide/show
  72487. * this bar at any time.
  72488. * @private
  72489. */
  72490. doSetHidden: function(hidden) {
  72491. if (!hidden) {
  72492. this.element.setStyle({
  72493. position: 'relative',
  72494. top: 'auto',
  72495. left: 'auto',
  72496. width: 'auto'
  72497. });
  72498. } else {
  72499. this.element.setStyle({
  72500. position: 'absolute',
  72501. top: '-1000px',
  72502. left: '-1000px',
  72503. width: this.element.getWidth() + 'px'
  72504. });
  72505. }
  72506. },
  72507. /**
  72508. * Creates a proxy element of the passed element, and positions it in the same position, using absolute positioning.
  72509. * The createNavigationBarProxy method uses this to create proxies of the backButton and the title elements.
  72510. * @private
  72511. */
  72512. createProxy: function(element) {
  72513. var ghost, x, y, left, width;
  72514. ghost = element.dom.cloneNode(true);
  72515. ghost.id = element.id + '-proxy';
  72516. //insert it into the toolbar
  72517. element.getParent().dom.appendChild(ghost);
  72518. //set the x/y
  72519. ghost = Ext.get(ghost);
  72520. x = element.getX();
  72521. y = element.getY();
  72522. left = element.getLeft();
  72523. width = element.getWidth();
  72524. ghost.setStyle('position', 'absolute');
  72525. ghost.setX(x);
  72526. ghost.setY(y);
  72527. ghost.setHeight(element.getHeight());
  72528. ghost.setWidth(width);
  72529. return {
  72530. x: x,
  72531. y: y,
  72532. left: left,
  72533. width: width,
  72534. ghost: ghost
  72535. };
  72536. }
  72537. });
  72538. /**
  72539. * @author Robert Dougan <rob@sencha.com>
  72540. *
  72541. * NavigationView is basically a {@link Ext.Container} with a {@link Ext.layout.Card card} layout, so only one view
  72542. * can be visible at a time. However, NavigationView also adds extra functionality on top of this to allow
  72543. * you to `push` and `pop` views at any time. When you do this, your NavigationView will automatically animate
  72544. * between your current active view, and the new view you want to `push`, or the previous view you want to `pop`.
  72545. *
  72546. * Using the NavigationView is very simple. Here is a basic example of it in action:
  72547. *
  72548. * @example
  72549. * var view = Ext.create('Ext.NavigationView', {
  72550. * fullscreen: true,
  72551. *
  72552. * items: [{
  72553. * title: 'First',
  72554. * items: [{
  72555. * xtype: 'button',
  72556. * text: 'Push a new view!',
  72557. * handler: function() {
  72558. * // use the push() method to push another view. It works much like
  72559. * // add() or setActiveItem(). it accepts a view instance, or you can give it
  72560. * // a view config.
  72561. * view.push({
  72562. * title: 'Second',
  72563. * html: 'Second view!'
  72564. * });
  72565. * }
  72566. * }]
  72567. * }]
  72568. * });
  72569. *
  72570. * Now, here comes the fun part: you can push any view/item into the NavigationView, at any time, and it will
  72571. * automatically handle the animations between the two views, including adding a back button (if necessary)
  72572. * and showing the new title.
  72573. *
  72574. * view.push({
  72575. * title: 'A new view',
  72576. * html: 'Some new content'
  72577. * });
  72578. *
  72579. * As you can see, it is as simple as calling the {@link #method-push} method, with a new view (instance or object). Done.
  72580. *
  72581. * You can also `pop` a view at any time. This will remove the top-most view from the NavigationView, and animate back
  72582. * to the previous view. You can do this using the {@link #method-pop} method (which requires no arguments).
  72583. *
  72584. * view.pop();
  72585. *
  72586. * @aside guide navigation_view
  72587. */
  72588. Ext.define('Ext.navigation.View', {
  72589. extend: 'Ext.Container',
  72590. alternateClassName: 'Ext.NavigationView',
  72591. xtype: 'navigationview',
  72592. requires: ['Ext.navigation.Bar'],
  72593. config: {
  72594. /**
  72595. * @cfg
  72596. * @inheritdoc
  72597. */
  72598. baseCls: Ext.baseCSSPrefix + 'navigationview',
  72599. /**
  72600. * @cfg {Boolean/Object} navigationBar
  72601. * The NavigationBar used in this navigation view. It defaults to be docked to the top.
  72602. *
  72603. * You can just pass in a normal object if you want to customize the NavigationBar. For example:
  72604. *
  72605. * navigationBar: {
  72606. * ui: 'dark',
  72607. * docked: 'bottom'
  72608. * }
  72609. *
  72610. * You **cannot** specify a *title* property in this configuration. The title of the navigationBar is taken
  72611. * from the configuration of this views children:
  72612. *
  72613. * view.push({
  72614. * title: 'This views title which will be shown in the navigation bar',
  72615. * html: 'Some HTML'
  72616. * });
  72617. *
  72618. * @accessor
  72619. */
  72620. navigationBar: {
  72621. docked: 'top'
  72622. },
  72623. /**
  72624. * @cfg {String} defaultBackButtonText
  72625. * The text to be displayed on the back button if:
  72626. *
  72627. * - The previous view does not have a title.
  72628. * - The {@link #useTitleForBackButtonText} configuration is `true`.
  72629. * @accessor
  72630. */
  72631. defaultBackButtonText: 'Back',
  72632. /**
  72633. * @cfg {Boolean} useTitleForBackButtonText
  72634. * Set to `false` if you always want to display the {@link #defaultBackButtonText} as the text
  72635. * on the back button. `true` if you want to use the previous views title.
  72636. * @accessor
  72637. */
  72638. useTitleForBackButtonText: false,
  72639. /**
  72640. * @cfg {Array/Object} items The child items to add to this NavigationView. This is usually an array of Component
  72641. * configurations or instances, for example:
  72642. *
  72643. * Ext.create('Ext.Container', {
  72644. * items: [
  72645. * {
  72646. * xtype: 'panel',
  72647. * title: 'My title',
  72648. * html: 'This is an item'
  72649. * }
  72650. * ]
  72651. * });
  72652. *
  72653. * If you want a title to be displayed in the {@link #navigationBar}, you must specify a `title` configuration in your
  72654. * view, like above.
  72655. *
  72656. * __Note:__ Only one view will be visible at a time. If you want to change to another view, use the {@link #method-push} or
  72657. * {@link #setActiveItem} methods.
  72658. * @accessor
  72659. */
  72660. /**
  72661. * @cfg
  72662. * @hide
  72663. */
  72664. layout: {
  72665. type: 'card',
  72666. animation: {
  72667. duration: 300,
  72668. easing: 'ease-out',
  72669. type: 'slide',
  72670. direction: 'left'
  72671. }
  72672. }
  72673. // See https://sencha.jira.com/browse/TOUCH-1568
  72674. // If you do, add to #navigationBar config docs:
  72675. //
  72676. // If you want to add a button on the right of the NavigationBar,
  72677. // use the {@link #rightButton} configuration.
  72678. },
  72679. /**
  72680. * @event push
  72681. * Fires when a view is pushed into this navigation view
  72682. * @param {Ext.navigation.View} this The component instance
  72683. * @param {Mixed} view The view that has been pushed
  72684. */
  72685. /**
  72686. * @event pop
  72687. * Fires when a view is popped from this navigation view
  72688. * @param {Ext.navigation.View} this The component instance
  72689. * @param {Mixed} view The view that has been popped
  72690. */
  72691. /**
  72692. * @event back
  72693. * Fires when the back button in the navigation view was tapped.
  72694. * @param {Ext.navigation.View} this The component instance\
  72695. */
  72696. // @private
  72697. initialize: function() {
  72698. var me = this,
  72699. navBar = me.getNavigationBar();
  72700. //add a listener onto the back button in the navigationbar
  72701. navBar.on({
  72702. back: me.onBackButtonTap,
  72703. scope: me
  72704. });
  72705. me.relayEvents(navBar, 'rightbuttontap');
  72706. me.relayEvents(me, {
  72707. add: 'push',
  72708. remove: 'pop'
  72709. });
  72710. //<debug>
  72711. var layout = me.getLayout();
  72712. if (layout && !layout.isCard) {
  72713. Ext.Logger.error('The base layout for a NavigationView must always be a Card Layout');
  72714. }
  72715. //</debug>
  72716. },
  72717. /**
  72718. * @private
  72719. */
  72720. applyLayout: function(config) {
  72721. config = config || {};
  72722. return config;
  72723. },
  72724. /**
  72725. * @private
  72726. * Called when the user taps on the back button
  72727. */
  72728. onBackButtonTap: function() {
  72729. this.pop();
  72730. this.fireEvent('back', this);
  72731. },
  72732. /**
  72733. * Pushes a new view into this navigation view using the default animation that this view has.
  72734. * @param {Object} view The view to push.
  72735. * @return {Ext.Component} The new item you just pushed.
  72736. */
  72737. push: function(view) {
  72738. return this.add(view);
  72739. },
  72740. /**
  72741. * Removes the current active view from the stack and sets the previous view using the default animation
  72742. * of this view. You can also pass a {@link Ext.ComponentQuery} selector to target what inner item to pop to.
  72743. * @param {Number} count The number of views you want to pop.
  72744. * @return {Ext.Component} The new active item.
  72745. */
  72746. pop: function(count) {
  72747. if (this.beforePop(count)) {
  72748. return this.doPop();
  72749. }
  72750. },
  72751. /**
  72752. * @private
  72753. * Calculates whether it needs to remove any items from the stack when you are popping more than 1
  72754. * item. If it does, it removes those views from the stack and returns `true`.
  72755. * @return {Boolean} `true` if it has removed views.
  72756. */
  72757. beforePop: function(count) {
  72758. var me = this,
  72759. innerItems = me.getInnerItems();
  72760. if (Ext.isString(count) || Ext.isObject(count)) {
  72761. var last = innerItems.length - 1,
  72762. i;
  72763. for (i = last; i >= 0; i--) {
  72764. if ((Ext.isString(count) && Ext.ComponentQuery.is(innerItems[i], count)) || (Ext.isObject(count) && count == innerItems[i])) {
  72765. count = last - i;
  72766. break;
  72767. }
  72768. }
  72769. if (!Ext.isNumber(count)) {
  72770. return false;
  72771. }
  72772. }
  72773. var ln = innerItems.length,
  72774. toRemove;
  72775. //default to 1 pop
  72776. if (!Ext.isNumber(count) || count < 1) {
  72777. count = 1;
  72778. }
  72779. //check if we are trying to remove more items than we have
  72780. count = Math.min(count, ln - 1);
  72781. if (count) {
  72782. //we need to reset the backButtonStack in the navigation bar
  72783. me.getNavigationBar().beforePop(count);
  72784. //get the items we need to remove from the view and remove theme
  72785. toRemove = innerItems.splice(-count, count - 1);
  72786. for (i = 0; i < toRemove.length; i++) {
  72787. this.remove(toRemove[i]);
  72788. }
  72789. return true;
  72790. }
  72791. return false;
  72792. },
  72793. /**
  72794. * @private
  72795. */
  72796. doPop: function() {
  72797. var me = this,
  72798. innerItems = this.getInnerItems();
  72799. //set the new active item to be the new last item of the stack
  72800. me.remove(innerItems[innerItems.length - 1]);
  72801. return this.getActiveItem();
  72802. },
  72803. /**
  72804. * Returns the previous item, if one exists.
  72805. * @return {Mixed} The previous view
  72806. */
  72807. getPreviousItem: function() {
  72808. var innerItems = this.getInnerItems();
  72809. return innerItems[innerItems.length - 2];
  72810. },
  72811. /**
  72812. * Updates the backbutton text accordingly in the {@link #navigationBar}
  72813. * @private
  72814. */
  72815. updateUseTitleForBackButtonText: function(useTitleForBackButtonText) {
  72816. var navigationBar = this.getNavigationBar();
  72817. if (navigationBar) {
  72818. navigationBar.setUseTitleForBackButtonText(useTitleForBackButtonText);
  72819. }
  72820. },
  72821. /**
  72822. * Updates the backbutton text accordingly in the {@link #navigationBar}
  72823. * @private
  72824. */
  72825. updateDefaultBackButtonText: function(defaultBackButtonText) {
  72826. var navigationBar = this.getNavigationBar();
  72827. if (navigationBar) {
  72828. navigationBar.setDefaultBackButtonText(defaultBackButtonText);
  72829. }
  72830. },
  72831. // @private
  72832. applyNavigationBar: function(config) {
  72833. if (!config) {
  72834. config = {
  72835. hidden: true,
  72836. docked: 'top'
  72837. };
  72838. }
  72839. if (config.title) {
  72840. delete config.title;
  72841. //<debug>
  72842. Ext.Logger.warn("Ext.navigation.View: The 'navigationBar' configuration does not accept a 'title' property. You " +
  72843. "set the title of the navigationBar by giving this navigation view's children a 'title' property.");
  72844. //</debug>
  72845. }
  72846. config.view = this;
  72847. config.useTitleForBackButtonText = this.getUseTitleForBackButtonText();
  72848. return Ext.factory(config, Ext.navigation.Bar, this.getNavigationBar());
  72849. },
  72850. // @private
  72851. updateNavigationBar: function(newNavigationBar, oldNavigationBar) {
  72852. if (oldNavigationBar) {
  72853. this.remove(oldNavigationBar, true);
  72854. }
  72855. if (newNavigationBar) {
  72856. this.add(newNavigationBar);
  72857. }
  72858. },
  72859. /**
  72860. * @private
  72861. */
  72862. applyActiveItem: function(activeItem, currentActiveItem) {
  72863. var me = this,
  72864. innerItems = me.getInnerItems();
  72865. // Make sure the items are already initialized
  72866. me.getItems();
  72867. // If we are not initialzed yet, we should set the active item to the last item in the stack
  72868. if (!me.initialized) {
  72869. activeItem = innerItems.length - 1;
  72870. }
  72871. return this.callParent([activeItem, currentActiveItem]);
  72872. },
  72873. doResetActiveItem: function(innerIndex) {
  72874. var me = this,
  72875. innerItems = me.getInnerItems(),
  72876. animation = me.getLayout().getAnimation();
  72877. if (innerIndex > 0) {
  72878. if (animation && animation.isAnimation) {
  72879. animation.setReverse(true);
  72880. }
  72881. me.setActiveItem(innerIndex - 1);
  72882. me.getNavigationBar().onViewRemove(me, innerItems[innerIndex], innerIndex);
  72883. }
  72884. },
  72885. /**
  72886. * @private
  72887. */
  72888. doRemove: function() {
  72889. var animation = this.getLayout().getAnimation();
  72890. if (animation && animation.isAnimation) {
  72891. animation.setReverse(false);
  72892. }
  72893. this.callParent(arguments);
  72894. },
  72895. /**
  72896. * @private
  72897. */
  72898. onItemAdd: function(item, index) {
  72899. this.doItemLayoutAdd(item, index);
  72900. if (!this.isItemsInitializing && item.isInnerItem()) {
  72901. this.setActiveItem(item);
  72902. this.getNavigationBar().onViewAdd(this, item, index);
  72903. }
  72904. if (this.initialized) {
  72905. this.fireEvent('add', this, item, index);
  72906. }
  72907. },
  72908. /**
  72909. * Resets the view by removing all items between the first and last item.
  72910. * @return {Ext.Component} The view that is now active
  72911. */
  72912. reset: function() {
  72913. return this.pop(this.getInnerItems().length);
  72914. }
  72915. });
  72916. /**
  72917. * Adds a Load More button at the bottom of the list. When the user presses this button,
  72918. * the next page of data will be loaded into the store and appended to the List.
  72919. *
  72920. * By specifying `{@link #autoPaging}: true`, an 'infinite scroll' effect can be achieved,
  72921. * i.e., the next page of content will load automatically when the user scrolls to the
  72922. * bottom of the list.
  72923. *
  72924. * ## Example
  72925. *
  72926. * Ext.create('Ext.dataview.List', {
  72927. *
  72928. * store: Ext.create('TweetStore'),
  72929. *
  72930. * plugins: [
  72931. * {
  72932. * xclass: 'Ext.plugin.ListPaging',
  72933. * autoPaging: true
  72934. * }
  72935. * ],
  72936. *
  72937. * itemTpl: [
  72938. * '<img src="{profile_image_url}" />',
  72939. * '<div class="tweet">{text}</div>'
  72940. * ]
  72941. * });
  72942. */
  72943. Ext.define('Ext.plugin.ListPaging', {
  72944. extend: 'Ext.Component',
  72945. alias: 'plugin.listpaging',
  72946. config: {
  72947. /**
  72948. * @cfg {Boolean} autoPaging
  72949. * True to automatically load the next page when you scroll to the bottom of the list.
  72950. */
  72951. autoPaging: false,
  72952. /**
  72953. * @cfg {String} loadMoreText The text used as the label of the Load More button.
  72954. */
  72955. loadMoreText: 'Load More...',
  72956. /**
  72957. * @cfg {String} noMoreRecordsText The text used as the label of the Load More button when the Store's
  72958. * {@link Ext.data.Store#totalCount totalCount} indicates that all of the records available on the server are
  72959. * already loaded
  72960. */
  72961. noMoreRecordsText: 'No More Records',
  72962. /**
  72963. * @private
  72964. * @cfg {String} loadTpl The template used to render the load more text
  72965. */
  72966. loadTpl: [
  72967. '<div class="{cssPrefix}loading-spinner" style="font-size: 180%; margin: 10px auto;">',
  72968. '<span class="{cssPrefix}loading-top"></span>',
  72969. '<span class="{cssPrefix}loading-right"></span>',
  72970. '<span class="{cssPrefix}loading-bottom"></span>',
  72971. '<span class="{cssPrefix}loading-left"></span>',
  72972. '</div>',
  72973. '<div class="{cssPrefix}list-paging-msg">{message}</div>'
  72974. ].join(''),
  72975. /**
  72976. * @cfg {Object} loadMoreCmp
  72977. * @private
  72978. */
  72979. loadMoreCmp: {
  72980. xtype: 'component',
  72981. baseCls: Ext.baseCSSPrefix + 'list-paging',
  72982. scrollDock: 'bottom',
  72983. docked: 'bottom',
  72984. hidden: true
  72985. },
  72986. /**
  72987. * @private
  72988. * @cfg {Boolean} loadMoreCmpAdded Indicates whether or not the load more component has been added to the List
  72989. * yet.
  72990. */
  72991. loadMoreCmpAdded: false,
  72992. /**
  72993. * @private
  72994. * @cfg {String} loadingCls The CSS class that is added to the {@link #loadMoreCmp} while the Store is loading
  72995. */
  72996. loadingCls: Ext.baseCSSPrefix + 'loading',
  72997. /**
  72998. * @private
  72999. * @cfg {Ext.List} list Local reference to the List this plugin is bound to
  73000. */
  73001. list: null,
  73002. /**
  73003. * @private
  73004. * @cfg {Ext.scroll.Scroller} scroller Local reference to the List's Scroller
  73005. */
  73006. scroller: null,
  73007. /**
  73008. * @private
  73009. * @cfg {Boolean} loading True if the plugin has initiated a Store load that has not yet completed
  73010. */
  73011. loading: false
  73012. },
  73013. /**
  73014. * @private
  73015. * Sets up all of the references the plugin needs
  73016. */
  73017. init: function(list) {
  73018. var scroller = list.getScrollable().getScroller(),
  73019. store = list.getStore();
  73020. this.setList(list);
  73021. this.setScroller(scroller);
  73022. this.bindStore(list.getStore());
  73023. list.setScrollToTopOnRefresh(false);
  73024. this.addLoadMoreCmp();
  73025. // We provide our own load mask so if the Store is autoLoading already disable the List's mask straight away,
  73026. // otherwise if the Store loads later allow the mask to show once then remove it thereafter
  73027. if (store) {
  73028. this.disableDataViewMask(store);
  73029. }
  73030. // The List's Store could change at any time so make sure we are informed when that happens
  73031. list.updateStore = Ext.Function.createInterceptor(list.updateStore, this.bindStore, this);
  73032. if (this.getAutoPaging()) {
  73033. scroller.on({
  73034. scrollend: this.onScrollEnd,
  73035. scope: this
  73036. });
  73037. }
  73038. },
  73039. /**
  73040. * @private
  73041. */
  73042. bindStore: function(newStore, oldStore) {
  73043. if (oldStore) {
  73044. oldStore.un({
  73045. beforeload: this.onStoreBeforeLoad,
  73046. load: this.onStoreLoad,
  73047. scope: this
  73048. });
  73049. }
  73050. if (newStore) {
  73051. newStore.on({
  73052. beforeload: this.onStoreBeforeLoad,
  73053. load: this.onStoreLoad,
  73054. scope: this
  73055. });
  73056. }
  73057. },
  73058. /**
  73059. * @private
  73060. * Removes the List/DataView's loading mask because we show our own in the plugin. The logic here disables the
  73061. * loading mask immediately if the store is autoloading. If it's not autoloading, allow the mask to show the first
  73062. * time the Store loads, then disable it and use the plugin's loading spinner.
  73063. * @param {Ext.data.Store} store The store that is bound to the DataView
  73064. */
  73065. disableDataViewMask: function(store) {
  73066. var list = this.getList();
  73067. if (store.isAutoLoading()) {
  73068. list.setLoadingText(null);
  73069. } else {
  73070. store.on({
  73071. load: {
  73072. single: true,
  73073. fn: function() {
  73074. list.setLoadingText(null);
  73075. }
  73076. }
  73077. });
  73078. }
  73079. },
  73080. /**
  73081. * @private
  73082. */
  73083. applyLoadTpl: function(config) {
  73084. return (Ext.isObject(config) && config.isTemplate) ? config : new Ext.XTemplate(config);
  73085. },
  73086. /**
  73087. * @private
  73088. */
  73089. applyLoadMoreCmp: function(config) {
  73090. config = Ext.merge(config, {
  73091. html: this.getLoadTpl().apply({
  73092. cssPrefix: Ext.baseCSSPrefix,
  73093. message: this.getLoadMoreText()
  73094. }),
  73095. listeners: {
  73096. tap: {
  73097. fn: this.loadNextPage,
  73098. scope: this,
  73099. element: 'element'
  73100. }
  73101. }
  73102. });
  73103. return Ext.factory(config, Ext.Component, this.getLoadMoreCmp());
  73104. },
  73105. /**
  73106. * @private
  73107. * If we're using autoPaging and detect that the user has scrolled to the bottom, kick off loading of the next page
  73108. */
  73109. onScrollEnd: function(scroller, x, y) {
  73110. if (!this.getLoading() && y >= scroller.maxPosition.y) {
  73111. this.loadNextPage();
  73112. }
  73113. },
  73114. /**
  73115. * @private
  73116. * Makes sure we add/remove the loading CSS class while the Store is loading
  73117. */
  73118. updateLoading: function(isLoading) {
  73119. var loadMoreCmp = this.getLoadMoreCmp(),
  73120. loadMoreCls = this.getLoadingCls();
  73121. if (isLoading) {
  73122. loadMoreCmp.addCls(loadMoreCls);
  73123. } else {
  73124. loadMoreCmp.removeCls(loadMoreCls);
  73125. }
  73126. },
  73127. /**
  73128. * @private
  73129. * If the Store is just about to load but it's currently empty, we hide the load more button because this is
  73130. * usually an outcome of setting a new Store on the List so we don't want the load more button to flash while
  73131. * the new Store loads
  73132. */
  73133. onStoreBeforeLoad: function(store) {
  73134. if (store.getCount() === 0) {
  73135. this.getLoadMoreCmp().hide();
  73136. }
  73137. },
  73138. /**
  73139. * @private
  73140. */
  73141. onStoreLoad: function(store) {
  73142. var loadCmp = this.getLoadMoreCmp(),
  73143. template = this.getLoadTpl(),
  73144. message = this.storeFullyLoaded() ? this.getNoMoreRecordsText() : this.getLoadMoreText();
  73145. if (store.getCount()) {
  73146. loadCmp.show();
  73147. this.getList().scrollDockHeightRefresh();
  73148. }
  73149. this.setLoading(false);
  73150. //if we've reached the end of the data set, switch to the noMoreRecordsText
  73151. loadCmp.setHtml(template.apply({
  73152. cssPrefix: Ext.baseCSSPrefix,
  73153. message: message
  73154. }));
  73155. },
  73156. /**
  73157. * @private
  73158. * Because the attached List's inner list element is rendered after our init function is called,
  73159. * we need to dynamically add the loadMoreCmp later. This does this once and caches the result.
  73160. */
  73161. addLoadMoreCmp: function() {
  73162. var list = this.getList(),
  73163. cmp = this.getLoadMoreCmp();
  73164. if (!this.getLoadMoreCmpAdded()) {
  73165. list.add(cmp);
  73166. /**
  73167. * @event loadmorecmpadded Fired when the Load More component is added to the list. Fires on the List.
  73168. * @param {Ext.plugin.ListPaging} this The list paging plugin
  73169. * @param {Ext.List} list The list
  73170. */
  73171. list.fireEvent('loadmorecmpadded', this, list);
  73172. this.setLoadMoreCmpAdded(true);
  73173. }
  73174. return cmp;
  73175. },
  73176. /**
  73177. * @private
  73178. * Returns true if the Store is detected as being fully loaded, or the server did not return a total count, which
  73179. * means we're in 'infinite' mode
  73180. * @return {Boolean}
  73181. */
  73182. storeFullyLoaded: function() {
  73183. var store = this.getList().getStore(),
  73184. total = store.getTotalCount();
  73185. return total !== null ? store.getTotalCount() <= (store.currentPage * store.getPageSize()) : false;
  73186. },
  73187. /**
  73188. * @private
  73189. */
  73190. loadNextPage: function() {
  73191. var me = this;
  73192. if (!me.storeFullyLoaded()) {
  73193. me.setLoading(true);
  73194. me.getList().getStore().nextPage({ addRecords: true });
  73195. }
  73196. }
  73197. });
  73198. /**
  73199. * This plugin adds pull to refresh functionality to the List.
  73200. *
  73201. * ## Example
  73202. *
  73203. * @example
  73204. * var store = Ext.create('Ext.data.Store', {
  73205. * fields: ['name', 'img', 'text'],
  73206. * data: [
  73207. * {
  73208. * name: 'rdougan',
  73209. * img: 'http://a0.twimg.com/profile_images/1261180556/171265_10150129602722922_727937921_7778997_8387690_o_reasonably_small.jpg',
  73210. * text: 'JavaScript development'
  73211. * }
  73212. * ]
  73213. * });
  73214. *
  73215. * Ext.create('Ext.dataview.List', {
  73216. * fullscreen: true,
  73217. *
  73218. * store: store,
  73219. *
  73220. * plugins: [
  73221. * {
  73222. * xclass: 'Ext.plugin.PullRefresh',
  73223. * pullRefreshText: 'Pull down for more new Tweets!'
  73224. * }
  73225. * ],
  73226. *
  73227. * itemTpl: [
  73228. * '<img src="{img}" alt="{name} photo" />',
  73229. * '<div class="tweet"><b>{name}:</b> {text}</div>'
  73230. * ]
  73231. * });
  73232. */
  73233. Ext.define('Ext.plugin.PullRefresh', {
  73234. extend: 'Ext.Component',
  73235. alias: 'plugin.pullrefresh',
  73236. requires: ['Ext.DateExtras'],
  73237. config: {
  73238. /**
  73239. * @cfg {Ext.dataview.List} list
  73240. * The list to which this PullRefresh plugin is connected.
  73241. * This will usually by set automatically when configuring the list with this plugin.
  73242. * @accessor
  73243. */
  73244. list: null,
  73245. /**
  73246. * @cfg {String} pullRefreshText The text that will be shown while you are pulling down.
  73247. * @accessor
  73248. */
  73249. pullRefreshText: 'Pull down to refresh...',
  73250. /**
  73251. * @cfg {String} releaseRefreshText The text that will be shown after you have pulled down enough to show the release message.
  73252. * @accessor
  73253. */
  73254. releaseRefreshText: 'Release to refresh...',
  73255. /**
  73256. * @cfg {String} lastUpdatedText The text to be shown in front of the last updated time.
  73257. * @accessor
  73258. */
  73259. lastUpdatedText: 'Last Updated:',
  73260. /**
  73261. * @cfg {String} loadingText The text that will be shown while the list is refreshing.
  73262. * @accessor
  73263. */
  73264. loadingText: 'Loading...',
  73265. /**
  73266. * @cfg {Number} snappingAnimationDuration The duration for snapping back animation after the data has been refreshed
  73267. * @accessor
  73268. */
  73269. snappingAnimationDuration: 150,
  73270. /**
  73271. * @cfg {Function} refreshFn The function that will be called to refresh the list.
  73272. * If this is not defined, the store's load function will be called.
  73273. * The refresh function gets called with a reference to this plugin instance.
  73274. * @accessor
  73275. */
  73276. refreshFn: null,
  73277. /**
  73278. * @cfg {Ext.XTemplate/String/Array} pullTpl The template being used for the pull to refresh markup.
  73279. * @accessor
  73280. */
  73281. pullTpl: [
  73282. '<div class="x-list-pullrefresh">',
  73283. '<div class="x-list-pullrefresh-arrow"></div>',
  73284. '<div class="x-loading-spinner">',
  73285. '<span class="x-loading-top"></span>',
  73286. '<span class="x-loading-right"></span>',
  73287. '<span class="x-loading-bottom"></span>',
  73288. '<span class="x-loading-left"></span>',
  73289. '</div>',
  73290. '<div class="x-list-pullrefresh-wrap">',
  73291. '<h3 class="x-list-pullrefresh-message">{message}</h3>',
  73292. '<div class="x-list-pullrefresh-updated">{lastUpdatedText}&nbsp;{lastUpdated:date("m/d/Y h:iA")}</div>',
  73293. '</div>',
  73294. '</div>'
  73295. ].join(''),
  73296. translatable: true
  73297. },
  73298. isRefreshing: false,
  73299. currentViewState: '',
  73300. initialize: function() {
  73301. this.callParent();
  73302. this.on({
  73303. painted: 'onPainted',
  73304. scope: this
  73305. });
  73306. },
  73307. init: function(list) {
  73308. var me = this;
  73309. me.setList(list);
  73310. me.initScrollable();
  73311. },
  73312. initScrollable: function() {
  73313. var me = this,
  73314. list = me.getList(),
  73315. store = list.getStore(),
  73316. pullTpl = me.getPullTpl(),
  73317. element = me.element,
  73318. scrollable = list.getScrollable(),
  73319. scroller;
  73320. if (!scrollable) {
  73321. return;
  73322. }
  73323. scroller = scrollable.getScroller();
  73324. me.lastUpdated = new Date();
  73325. list.container.insert(0, me);
  73326. // We provide our own load mask so if the Store is autoLoading already disable the List's mask straight away,
  73327. // otherwise if the Store loads later allow the mask to show once then remove it thereafter
  73328. if (store) {
  73329. if (store.isAutoLoading()) {
  73330. list.setLoadingText(null);
  73331. } else {
  73332. store.on({
  73333. load: {
  73334. single: true,
  73335. fn: function() {
  73336. list.setLoadingText(null);
  73337. }
  73338. }
  73339. });
  73340. }
  73341. }
  73342. pullTpl.overwrite(element, {
  73343. message: me.getPullRefreshText(),
  73344. lastUpdatedText: me.getLastUpdatedText(),
  73345. lastUpdated: me.lastUpdated
  73346. }, true);
  73347. me.loadingElement = element.getFirstChild();
  73348. me.messageEl = element.down('.x-list-pullrefresh-message');
  73349. me.updatedEl = element.down('.x-list-pullrefresh-updated');
  73350. me.maxScroller = scroller.getMaxPosition();
  73351. scroller.on({
  73352. maxpositionchange: me.setMaxScroller,
  73353. scroll: me.onScrollChange,
  73354. scope: me
  73355. });
  73356. },
  73357. onScrollableChange: function() {
  73358. this.initScrollable();
  73359. },
  73360. updateList: function(newList, oldList) {
  73361. var me = this;
  73362. if (newList && newList != oldList) {
  73363. newList.on({
  73364. order: 'after',
  73365. scrollablechange: me.onScrollableChange,
  73366. scope: me
  73367. });
  73368. } else if (oldList) {
  73369. oldList.un({
  73370. order: 'after',
  73371. scrollablechange: me.onScrollableChange,
  73372. scope: me
  73373. });
  73374. }
  73375. },
  73376. /**
  73377. * @private
  73378. * Attempts to load the newest posts via the attached List's Store's Proxy
  73379. */
  73380. fetchLatest: function() {
  73381. var store = this.getList().getStore(),
  73382. proxy = store.getProxy(),
  73383. operation;
  73384. operation = Ext.create('Ext.data.Operation', {
  73385. page: 1,
  73386. start: 0,
  73387. model: store.getModel(),
  73388. limit: store.getPageSize(),
  73389. action: 'read',
  73390. filters: store.getRemoteFilter() ? store.getFilters() : []
  73391. });
  73392. proxy.read(operation, this.onLatestFetched, this);
  73393. },
  73394. /**
  73395. * @private
  73396. * Called after fetchLatest has finished grabbing data. Matches any returned records against what is already in the
  73397. * Store. If there is an overlap, updates the existing records with the new data and inserts the new items at the
  73398. * front of the Store. If there is no overlap, insert the new records anyway and record that there's a break in the
  73399. * timeline between the new and the old records.
  73400. */
  73401. onLatestFetched: function(operation) {
  73402. var store = this.getList().getStore(),
  73403. oldRecords = store.getData(),
  73404. newRecords = operation.getRecords(),
  73405. length = newRecords.length,
  73406. toInsert = [],
  73407. newRecord, oldRecord, i;
  73408. for (i = 0; i < length; i++) {
  73409. newRecord = newRecords[i];
  73410. oldRecord = oldRecords.getByKey(newRecord.getId());
  73411. if (oldRecord) {
  73412. oldRecord.set(newRecord.getData());
  73413. } else {
  73414. toInsert.push(newRecord);
  73415. }
  73416. oldRecord = undefined;
  73417. }
  73418. store.insert(0, toInsert);
  73419. },
  73420. onPainted: function() {
  73421. this.pullHeight = this.loadingElement.getHeight();
  73422. },
  73423. setMaxScroller: function(scroller, position) {
  73424. this.maxScroller = position;
  73425. },
  73426. onScrollChange: function(scroller, x, y) {
  73427. if (y < 0) {
  73428. this.onBounceTop(y);
  73429. }
  73430. if (y > this.maxScroller.y) {
  73431. this.onBounceBottom(y);
  73432. }
  73433. },
  73434. /**
  73435. * @private
  73436. */
  73437. applyPullTpl: function(config) {
  73438. return (Ext.isObject(config) && config.isTemplate) ? config : new Ext.XTemplate(config);
  73439. },
  73440. onBounceTop: function(y) {
  73441. var me = this,
  73442. pullHeight = me.pullHeight,
  73443. list = me.getList(),
  73444. scroller = list.getScrollable().getScroller();
  73445. if (!me.isReleased) {
  73446. if (!pullHeight) {
  73447. me.onPainted();
  73448. pullHeight = me.pullHeight;
  73449. }
  73450. if (!me.isRefreshing && -y >= pullHeight + 10) {
  73451. me.isRefreshing = true;
  73452. me.setViewState('release');
  73453. scroller.getContainer().onBefore({
  73454. dragend: 'onScrollerDragEnd',
  73455. single: true,
  73456. scope: me
  73457. });
  73458. }
  73459. else if (me.isRefreshing && -y < pullHeight + 10) {
  73460. me.isRefreshing = false;
  73461. me.setViewState('pull');
  73462. }
  73463. }
  73464. me.getTranslatable().translate(0, -y);
  73465. },
  73466. onScrollerDragEnd: function() {
  73467. var me = this;
  73468. if (me.isRefreshing) {
  73469. var list = me.getList(),
  73470. scroller = list.getScrollable().getScroller();
  73471. scroller.minPosition.y = -me.pullHeight;
  73472. scroller.on({
  73473. scrollend: 'loadStore',
  73474. single: true,
  73475. scope: me
  73476. });
  73477. me.isReleased = true;
  73478. }
  73479. },
  73480. loadStore: function() {
  73481. var me = this,
  73482. list = me.getList(),
  73483. scroller = list.getScrollable().getScroller();
  73484. me.setViewState('loading');
  73485. me.isReleased = false;
  73486. Ext.defer(function() {
  73487. scroller.on({
  73488. scrollend: function() {
  73489. if (me.getRefreshFn()) {
  73490. me.getRefreshFn().call(me, me);
  73491. } else {
  73492. me.fetchLatest();
  73493. }
  73494. me.resetRefreshState();
  73495. },
  73496. delay: 100,
  73497. single: true,
  73498. scope: me
  73499. });
  73500. scroller.minPosition.y = 0;
  73501. scroller.scrollTo(null, 0, true);
  73502. }, 500, me);
  73503. },
  73504. onBounceBottom: Ext.emptyFn,
  73505. setViewState: function(state) {
  73506. var me = this,
  73507. prefix = Ext.baseCSSPrefix,
  73508. messageEl = me.messageEl,
  73509. loadingElement = me.loadingElement;
  73510. if (state === me.currentViewState) {
  73511. return me;
  73512. }
  73513. me.currentViewState = state;
  73514. if (messageEl && loadingElement) {
  73515. switch (state) {
  73516. case 'pull':
  73517. messageEl.setHtml(me.getPullRefreshText());
  73518. loadingElement.removeCls([prefix + 'list-pullrefresh-release', prefix + 'list-pullrefresh-loading']);
  73519. break;
  73520. case 'release':
  73521. messageEl.setHtml(me.getReleaseRefreshText());
  73522. loadingElement.addCls(prefix + 'list-pullrefresh-release');
  73523. break;
  73524. case 'loading':
  73525. messageEl.setHtml(me.getLoadingText());
  73526. loadingElement.addCls(prefix + 'list-pullrefresh-loading');
  73527. break;
  73528. }
  73529. }
  73530. return me;
  73531. },
  73532. resetRefreshState: function() {
  73533. var me = this;
  73534. me.isRefreshing = false;
  73535. me.lastUpdated = new Date();
  73536. me.setViewState('pull');
  73537. me.updatedEl.setHtml(this.getLastUpdatedText() + '&nbsp;' + Ext.util.Format.date(me.lastUpdated, "m/d/Y h:iA"));
  73538. }
  73539. });
  73540. /**
  73541. * Used in the {@link Ext.tab.Bar} component. This shouldn't be used directly, instead use
  73542. * {@link Ext.tab.Bar} or {@link Ext.tab.Panel}.
  73543. * @private
  73544. */
  73545. Ext.define('Ext.tab.Tab', {
  73546. extend: 'Ext.Button',
  73547. xtype: 'tab',
  73548. alternateClassName: 'Ext.Tab',
  73549. // @private
  73550. isTab: true,
  73551. config: {
  73552. /**
  73553. * @cfg
  73554. * @inheritdoc
  73555. */
  73556. baseCls: Ext.baseCSSPrefix + 'tab',
  73557. /**
  73558. * @cfg {String} pressedCls
  73559. * The CSS class to be applied to a Tab when it is pressed.
  73560. * Providing your own CSS for this class enables you to customize the pressed state.
  73561. * @accessor
  73562. */
  73563. pressedCls: Ext.baseCSSPrefix + 'tab-pressed',
  73564. /**
  73565. * @cfg {String} activeCls
  73566. * The CSS class to be applied to a Tab when it is active.
  73567. * Providing your own CSS for this class enables you to customize the active state.
  73568. * @accessor
  73569. */
  73570. activeCls: Ext.baseCSSPrefix + 'tab-active',
  73571. /**
  73572. * @cfg {Boolean} active
  73573. * Set this to `true` to have the tab be active by default.
  73574. * @accessor
  73575. */
  73576. active: false,
  73577. /**
  73578. * @cfg {String} title
  73579. * The title of the card that this tab is bound to.
  73580. * @accessor
  73581. */
  73582. title: '&nbsp;'
  73583. },
  73584. // We need to override this so the `iconElement` is properly hidden using visibility
  73585. // when we render it.
  73586. template: [
  73587. {
  73588. tag: 'span',
  73589. reference: 'badgeElement',
  73590. hidden: true
  73591. },
  73592. {
  73593. tag: 'span',
  73594. className: Ext.baseCSSPrefix + 'button-icon',
  73595. reference: 'iconElement',
  73596. style: 'visibility: hidden !important'
  73597. },
  73598. {
  73599. tag: 'span',
  73600. reference: 'textElement',
  73601. hidden: true
  73602. }
  73603. ],
  73604. updateIconCls : function(newCls, oldCls) {
  73605. this.callParent([newCls, oldCls]);
  73606. if (oldCls) {
  73607. this.removeCls('x-tab-icon');
  73608. }
  73609. if (newCls) {
  73610. this.addCls('x-tab-icon');
  73611. }
  73612. },
  73613. /**
  73614. * @event activate
  73615. * Fires when a tab is activated
  73616. * @param {Ext.tab.Tab} this
  73617. */
  73618. /**
  73619. * @event deactivate
  73620. * Fires when a tab is deactivated
  73621. * @param {Ext.tab.Tab} this
  73622. */
  73623. updateTitle: function(title) {
  73624. this.setText(title);
  73625. },
  73626. hideIconElement: function() {
  73627. this.iconElement.dom.style.setProperty('visibility', 'hidden', '!important');
  73628. },
  73629. showIconElement: function() {
  73630. this.iconElement.dom.style.setProperty('visibility', 'visible', '!important');
  73631. },
  73632. updateActive: function(active, oldActive) {
  73633. var activeCls = this.getActiveCls();
  73634. if (active && !oldActive) {
  73635. this.element.addCls(activeCls);
  73636. this.fireEvent('activate', this);
  73637. } else if (oldActive) {
  73638. this.element.removeCls(activeCls);
  73639. this.fireEvent('deactivate', this);
  73640. }
  73641. }
  73642. }, function() {
  73643. this.override({
  73644. activate: function() {
  73645. this.setActive(true);
  73646. },
  73647. deactivate: function() {
  73648. this.setActive(false);
  73649. }
  73650. });
  73651. });
  73652. /**
  73653. * Ext.tab.Bar is used internally by {@link Ext.tab.Panel} to create the bar of tabs that appears at the top of the tab
  73654. * panel. It's unusual to use it directly, instead see the {@link Ext.tab.Panel tab panel docs} for usage instructions.
  73655. *
  73656. * Used in the {@link Ext.tab.Panel} component to display {@link Ext.tab.Tab} components.
  73657. *
  73658. * @private
  73659. */
  73660. Ext.define('Ext.tab.Bar', {
  73661. extend: 'Ext.Toolbar',
  73662. alternateClassName: 'Ext.TabBar',
  73663. xtype : 'tabbar',
  73664. requires: ['Ext.tab.Tab'],
  73665. config: {
  73666. /**
  73667. * @cfg
  73668. * @inheritdoc
  73669. */
  73670. baseCls: Ext.baseCSSPrefix + 'tabbar',
  73671. // @private
  73672. defaultType: 'tab',
  73673. // @private
  73674. layout: {
  73675. type: 'hbox',
  73676. align: 'middle'
  73677. }
  73678. },
  73679. eventedConfig: {
  73680. /**
  73681. * @cfg {Number/String/Ext.Component} activeTab
  73682. * The initially activated tab. Can be specified as numeric index,
  73683. * component ID or as the component instance itself.
  73684. * @accessor
  73685. * @evented
  73686. */
  73687. activeTab: null
  73688. },
  73689. /**
  73690. * @event tabchange
  73691. * Fired when active tab changes.
  73692. * @param {Ext.tab.Bar} this
  73693. * @param {Ext.tab.Tab} newTab The new Tab
  73694. * @param {Ext.tab.Tab} oldTab The old Tab
  73695. */
  73696. initialize: function() {
  73697. var me = this;
  73698. me.callParent();
  73699. me.on({
  73700. tap: 'onTabTap',
  73701. delegate: '> tab',
  73702. scope : me
  73703. });
  73704. },
  73705. // @private
  73706. onTabTap: function(tab) {
  73707. this.setActiveTab(tab);
  73708. },
  73709. /**
  73710. * @private
  73711. */
  73712. applyActiveTab: function(newActiveTab, oldActiveTab) {
  73713. if (!newActiveTab && newActiveTab !== 0) {
  73714. return;
  73715. }
  73716. var newTabInstance = this.parseActiveTab(newActiveTab);
  73717. if (!newTabInstance) {
  73718. // <debug warn>
  73719. if (oldActiveTab) {
  73720. Ext.Logger.warn('Trying to set a non-existent activeTab');
  73721. }
  73722. // </debug>
  73723. return;
  73724. }
  73725. return newTabInstance;
  73726. },
  73727. /**
  73728. * @private
  73729. * Default pack to center when docked to the bottom, otherwise default pack to left
  73730. */
  73731. doSetDocked: function(newDocked) {
  73732. var layout = this.getLayout(),
  73733. initialConfig = this.getInitialConfig(),
  73734. pack;
  73735. if (!initialConfig.layout || !initialConfig.layout.pack) {
  73736. pack = (newDocked == 'bottom') ? 'center' : 'left';
  73737. //layout isn't guaranteed to be instantiated so must test
  73738. if (layout.isLayout) {
  73739. layout.setPack(pack);
  73740. } else {
  73741. layout.pack = (layout && layout.pack) ? layout.pack : pack;
  73742. }
  73743. }
  73744. },
  73745. /**
  73746. * @private
  73747. * Sets the active tab
  73748. */
  73749. doSetActiveTab: function(newTab, oldTab) {
  73750. if (newTab) {
  73751. newTab.setActive(true);
  73752. }
  73753. //Check if the parent is present, if not it is destroyed
  73754. if (oldTab && oldTab.parent) {
  73755. oldTab.setActive(false);
  73756. }
  73757. },
  73758. /**
  73759. * @private
  73760. * Parses the active tab, which can be a number or string
  73761. */
  73762. parseActiveTab: function(tab) {
  73763. //we need to call getItems to initialize the items, otherwise they will not exist yet.
  73764. if (typeof tab == 'number') {
  73765. return this.getInnerItems()[tab];
  73766. }
  73767. else if (typeof tab == 'string') {
  73768. tab = Ext.getCmp(tab);
  73769. }
  73770. return tab;
  73771. }
  73772. });
  73773. /**
  73774. * @aside guide tabs
  73775. * @aside video tabs-toolbars
  73776. * @aside example tabs
  73777. * @aside example tabs-bottom
  73778. *
  73779. * Tab Panels are a great way to allow the user to switch between several pages that are all full screen. Each
  73780. * Component in the Tab Panel gets its own Tab, which shows the Component when tapped on. Tabs can be positioned at
  73781. * the top or the bottom of the Tab Panel, and can optionally accept title and icon configurations.
  73782. *
  73783. * Here's how we can set up a simple Tab Panel with tabs at the bottom. Use the controls at the top left of the example
  73784. * to toggle between code mode and live preview mode (you can also edit the code and see your changes in the live
  73785. * preview):
  73786. *
  73787. * @example miniphone preview
  73788. * Ext.create('Ext.TabPanel', {
  73789. * fullscreen: true,
  73790. * tabBarPosition: 'bottom',
  73791. *
  73792. * defaults: {
  73793. * styleHtmlContent: true
  73794. * },
  73795. *
  73796. * items: [
  73797. * {
  73798. * title: 'Home',
  73799. * iconCls: 'home',
  73800. * html: 'Home Screen'
  73801. * },
  73802. * {
  73803. * title: 'Contact',
  73804. * iconCls: 'user',
  73805. * html: 'Contact Screen'
  73806. * }
  73807. * ]
  73808. * });
  73809. * One tab was created for each of the {@link Ext.Panel panels} defined in the items array. Each tab automatically uses
  73810. * the title and icon defined on the item configuration, and switches to that item when tapped on. We can also position
  73811. * the tab bar at the top, which makes our Tab Panel look like this:
  73812. *
  73813. * @example miniphone preview
  73814. * Ext.create('Ext.TabPanel', {
  73815. * fullscreen: true,
  73816. *
  73817. * defaults: {
  73818. * styleHtmlContent: true
  73819. * },
  73820. *
  73821. * items: [
  73822. * {
  73823. * title: 'Home',
  73824. * html: 'Home Screen'
  73825. * },
  73826. * {
  73827. * title: 'Contact',
  73828. * html: 'Contact Screen'
  73829. * }
  73830. * ]
  73831. * });
  73832. *
  73833. */
  73834. Ext.define('Ext.tab.Panel', {
  73835. extend: 'Ext.Container',
  73836. xtype : 'tabpanel',
  73837. alternateClassName: 'Ext.TabPanel',
  73838. requires: ['Ext.tab.Bar'],
  73839. config: {
  73840. /**
  73841. * @cfg {String} ui
  73842. * Sets the UI of this component.
  73843. * Available values are: `light` and `dark`.
  73844. * @accessor
  73845. */
  73846. ui: 'dark',
  73847. /**
  73848. * @cfg {Object} tabBar
  73849. * An Ext.tab.Bar configuration.
  73850. * @accessor
  73851. */
  73852. tabBar: true,
  73853. /**
  73854. * @cfg {String} tabBarPosition
  73855. * The docked position for the {@link #tabBar} instance.
  73856. * Possible values are 'top' and 'bottom'.
  73857. * @accessor
  73858. */
  73859. tabBarPosition: 'top',
  73860. /**
  73861. * @cfg layout
  73862. * @inheritdoc
  73863. */
  73864. layout: {
  73865. type: 'card',
  73866. animation: {
  73867. type: 'slide',
  73868. direction: 'left'
  73869. }
  73870. },
  73871. /**
  73872. * @cfg cls
  73873. * @inheritdoc
  73874. */
  73875. cls: Ext.baseCSSPrefix + 'tabpanel'
  73876. /**
  73877. * @cfg {Boolean/String/Object} scrollable
  73878. * @accessor
  73879. * @hide
  73880. */
  73881. /**
  73882. * @cfg {Boolean/String/Object} scroll
  73883. * @hide
  73884. */
  73885. },
  73886. delegateListeners: {
  73887. delegate: '> component',
  73888. centeredchange: 'onItemCenteredChange',
  73889. dockedchange: 'onItemDockedChange',
  73890. floatingchange: 'onItemFloatingChange',
  73891. disabledchange: 'onItemDisabledChange'
  73892. },
  73893. initialize: function() {
  73894. this.callParent();
  73895. this.on({
  73896. order: 'before',
  73897. activetabchange: 'doTabChange',
  73898. delegate: '> tabbar',
  73899. scope : this
  73900. });
  73901. },
  73902. /**
  73903. * Tab panels should not be scrollable. Instead, you should add scrollable to any item that
  73904. * you want to scroll.
  73905. * @private
  73906. */
  73907. applyScrollable: function() {
  73908. return false;
  73909. },
  73910. /**
  73911. * Updates the Ui for this component and the {@link #tabBar}.
  73912. */
  73913. updateUi: function(newUi, oldUi) {
  73914. this.callParent(arguments);
  73915. if (this.initialized) {
  73916. this.getTabBar().setUi(newUi);
  73917. }
  73918. },
  73919. /**
  73920. * @private
  73921. */
  73922. doSetActiveItem: function(newActiveItem, oldActiveItem) {
  73923. if (newActiveItem) {
  73924. var items = this.getInnerItems(),
  73925. oldIndex = items.indexOf(oldActiveItem),
  73926. newIndex = items.indexOf(newActiveItem),
  73927. reverse = oldIndex > newIndex,
  73928. animation = this.getLayout().getAnimation(),
  73929. tabBar = this.getTabBar(),
  73930. oldTab = tabBar.parseActiveTab(oldIndex),
  73931. newTab = tabBar.parseActiveTab(newIndex);
  73932. if (animation && animation.setReverse) {
  73933. animation.setReverse(reverse);
  73934. }
  73935. this.callParent(arguments);
  73936. if (newIndex != -1) {
  73937. this.forcedChange = true;
  73938. tabBar.setActiveTab(newIndex);
  73939. this.forcedChange = false;
  73940. if (oldTab) {
  73941. oldTab.setActive(false);
  73942. }
  73943. if (newTab) {
  73944. newTab.setActive(true);
  73945. }
  73946. }
  73947. }
  73948. },
  73949. /**
  73950. * Updates this container with the new active item.
  73951. * @param {Object} tabBar
  73952. * @param {Object} newTab
  73953. * @return {Boolean}
  73954. */
  73955. doTabChange: function(tabBar, newTab) {
  73956. var oldActiveItem = this.getActiveItem(),
  73957. newActiveItem;
  73958. this.setActiveItem(tabBar.indexOf(newTab));
  73959. newActiveItem = this.getActiveItem();
  73960. return this.forcedChange || oldActiveItem !== newActiveItem;
  73961. },
  73962. /**
  73963. * Creates a new {@link Ext.tab.Bar} instance using {@link Ext#factory}.
  73964. * @param {Object} config
  73965. * @return {Object}
  73966. * @private
  73967. */
  73968. applyTabBar: function(config) {
  73969. if (config === true) {
  73970. config = {};
  73971. }
  73972. if (config) {
  73973. Ext.applyIf(config, {
  73974. ui: this.getUi(),
  73975. docked: this.getTabBarPosition()
  73976. });
  73977. }
  73978. return Ext.factory(config, Ext.tab.Bar, this.getTabBar());
  73979. },
  73980. /**
  73981. * Adds the new {@link Ext.tab.Bar} instance into this container.
  73982. * @private
  73983. */
  73984. updateTabBar: function(newTabBar) {
  73985. if (newTabBar) {
  73986. this.add(newTabBar);
  73987. this.setTabBarPosition(newTabBar.getDocked());
  73988. }
  73989. },
  73990. /**
  73991. * Updates the docked position of the {@link #tabBar}.
  73992. * @private
  73993. */
  73994. updateTabBarPosition: function(position) {
  73995. var tabBar = this.getTabBar();
  73996. if (tabBar) {
  73997. tabBar.setDocked(position);
  73998. }
  73999. },
  74000. onItemAdd: function(card) {
  74001. var me = this;
  74002. if (!card.isInnerItem()) {
  74003. return me.callParent(arguments);
  74004. }
  74005. var tabBar = me.getTabBar(),
  74006. initialConfig = card.getInitialConfig(),
  74007. tabConfig = initialConfig.tab || {},
  74008. tabTitle = (card.getTitle) ? card.getTitle() : initialConfig.title,
  74009. tabIconCls = (card.getIconCls) ? card.getIconCls() : initialConfig.iconCls,
  74010. tabHidden = (card.getHidden) ? card.getHidden() : initialConfig.hidden,
  74011. tabDisabled = (card.getDisabled) ? card.getDisabled() : initialConfig.disabled,
  74012. tabBadgeText = (card.getBadgeText) ? card.getBadgeText() : initialConfig.badgeText,
  74013. innerItems = me.getInnerItems(),
  74014. index = innerItems.indexOf(card),
  74015. tabs = tabBar.getItems(),
  74016. activeTab = tabBar.getActiveTab(),
  74017. currentTabInstance = (tabs.length >= innerItems.length) && tabs.getAt(index),
  74018. tabInstance;
  74019. if (tabTitle && !tabConfig.title) {
  74020. tabConfig.title = tabTitle;
  74021. }
  74022. if (tabIconCls && !tabConfig.iconCls) {
  74023. tabConfig.iconCls = tabIconCls;
  74024. }
  74025. if (tabHidden && !tabConfig.hidden) {
  74026. tabConfig.hidden = tabHidden;
  74027. }
  74028. if (tabDisabled && !tabConfig.disabled) {
  74029. tabConfig.disabled = tabDisabled;
  74030. }
  74031. if (tabBadgeText && !tabConfig.badgeText) {
  74032. tabConfig.badgeText = tabBadgeText;
  74033. }
  74034. //<debug warn>
  74035. if (!currentTabInstance && !tabConfig.title && !tabConfig.iconCls) {
  74036. if (!tabConfig.title && !tabConfig.iconCls) {
  74037. Ext.Logger.error('Adding a card to a tab container without specifying any tab configuration');
  74038. }
  74039. }
  74040. //</debug>
  74041. tabInstance = Ext.factory(tabConfig, Ext.tab.Tab, currentTabInstance);
  74042. if (!currentTabInstance) {
  74043. tabBar.insert(index, tabInstance);
  74044. }
  74045. card.tab = tabInstance;
  74046. me.callParent(arguments);
  74047. if (!activeTab && activeTab !== 0) {
  74048. tabBar.setActiveTab(tabBar.getActiveItem());
  74049. }
  74050. },
  74051. /**
  74052. * If an item gets enabled/disabled and it has an tab, we should also enable/disable that tab
  74053. * @private
  74054. */
  74055. onItemDisabledChange: function(item, newDisabled) {
  74056. if (item && item.tab) {
  74057. item.tab.setDisabled(newDisabled);
  74058. }
  74059. },
  74060. // @private
  74061. onItemRemove: function(item, index) {
  74062. this.getTabBar().remove(item.tab, this.getAutoDestroy());
  74063. this.callParent(arguments);
  74064. }
  74065. }, function() {
  74066. });
  74067. Ext.define('Ext.table.Cell', {
  74068. extend: 'Ext.Container',
  74069. xtype: 'tablecell',
  74070. config: {
  74071. baseCls: 'x-table-cell'
  74072. },
  74073. getElementConfig: function() {
  74074. var config = this.callParent();
  74075. config.children.length = 0;
  74076. return config;
  74077. }
  74078. });
  74079. Ext.define('Ext.table.Row', {
  74080. extend: 'Ext.table.Cell',
  74081. xtype: 'tablerow',
  74082. config: {
  74083. baseCls: 'x-table-row',
  74084. defaultType: 'tablecell'
  74085. }
  74086. });
  74087. Ext.define('Ext.table.Table', {
  74088. extend: 'Ext.Container',
  74089. requires: ['Ext.table.Row'],
  74090. xtype: 'table',
  74091. config: {
  74092. baseCls: 'x-table',
  74093. defaultType: 'tablerow'
  74094. },
  74095. cachedConfig: {
  74096. fixedLayout: false
  74097. },
  74098. fixedLayoutCls: 'x-table-fixed',
  74099. updateFixedLayout: function(fixedLayout) {
  74100. this.innerElement[fixedLayout ? 'addCls' : 'removeCls'](this.fixedLayoutCls);
  74101. }
  74102. });
  74103. /**
  74104. *
  74105. */
  74106. Ext.define('Ext.util.Droppable', {
  74107. mixins: {
  74108. observable: 'Ext.mixin.Observable'
  74109. },
  74110. config: {
  74111. /**
  74112. * @cfg
  74113. * @inheritdoc
  74114. */
  74115. baseCls: Ext.baseCSSPrefix + 'droppable'
  74116. },
  74117. /**
  74118. * @cfg {String} activeCls
  74119. * The CSS added to a Droppable when a Draggable in the same group is being
  74120. * dragged.
  74121. */
  74122. activeCls: Ext.baseCSSPrefix + 'drop-active',
  74123. /**
  74124. * @cfg {String} invalidCls
  74125. * The CSS class to add to the droppable when dragging a draggable that is
  74126. * not in the same group.
  74127. */
  74128. invalidCls: Ext.baseCSSPrefix + 'drop-invalid',
  74129. /**
  74130. * @cfg {String} hoverCls
  74131. * The CSS class to add to the droppable when hovering over a valid drop.
  74132. */
  74133. hoverCls: Ext.baseCSSPrefix + 'drop-hover',
  74134. /**
  74135. * @cfg {String} validDropMode
  74136. * Determines when a drop is considered 'valid' whether it simply need to
  74137. * intersect the region or if it needs to be contained within the region.
  74138. * Valid values are: 'intersects' or 'contains'
  74139. */
  74140. validDropMode: 'intersect',
  74141. /**
  74142. * @cfg {Boolean} disabled
  74143. */
  74144. disabled: false,
  74145. /**
  74146. * @cfg {String} group
  74147. * Draggable and Droppable objects can participate in a group which are
  74148. * capable of interacting.
  74149. */
  74150. group: 'base',
  74151. // not yet implemented
  74152. tolerance: null,
  74153. // @private
  74154. monitoring: false,
  74155. /**
  74156. * Creates new Droppable.
  74157. * @param {Mixed} el String, HtmlElement or Ext.Element representing an
  74158. * element on the page.
  74159. * @param {Object} config Configuration options for this class.
  74160. */
  74161. constructor: function(el, config) {
  74162. var me = this;
  74163. config = config || {};
  74164. Ext.apply(me, config);
  74165. /**
  74166. * @event dropactivate
  74167. * @param {Ext.util.Droppable} this
  74168. * @param {Ext.util.Draggable} draggable
  74169. * @param {Ext.event.Event} e
  74170. */
  74171. /**
  74172. * @event dropdeactivate
  74173. * @param {Ext.util.Droppable} this
  74174. * @param {Ext.util.Draggable} draggable
  74175. * @param {Ext.event.Event} e
  74176. */
  74177. /**
  74178. * @event dropenter
  74179. * @param {Ext.util.Droppable} this
  74180. * @param {Ext.util.Draggable} draggable
  74181. * @param {Ext.event.Event} e
  74182. */
  74183. /**
  74184. * @event dropleave
  74185. * @param {Ext.util.Droppable} this
  74186. * @param {Ext.util.Draggable} draggable
  74187. * @param {Ext.event.Event} e
  74188. */
  74189. /**
  74190. * @event drop
  74191. * @param {Ext.util.Droppable} this
  74192. * @param {Ext.util.Draggable} draggable
  74193. * @param {Ext.event.Event} e
  74194. */
  74195. me.el = Ext.get(el);
  74196. me.callParent();
  74197. me.mixins.observable.constructor.call(me);
  74198. if (!me.disabled) {
  74199. me.enable();
  74200. }
  74201. me.el.addCls(me.baseCls);
  74202. },
  74203. // @private
  74204. onDragStart: function(draggable, e) {
  74205. if (draggable.group === this.group) {
  74206. this.monitoring = true;
  74207. this.el.addCls(this.activeCls);
  74208. this.region = this.el.getPageBox(true);
  74209. draggable.on({
  74210. drag: this.onDrag,
  74211. beforedragend: this.onBeforeDragEnd,
  74212. dragend: this.onDragEnd,
  74213. scope: this
  74214. });
  74215. if (this.isDragOver(draggable)) {
  74216. this.setCanDrop(true, draggable, e);
  74217. }
  74218. this.fireEvent('dropactivate', this, draggable, e);
  74219. }
  74220. else {
  74221. draggable.on({
  74222. dragend: function() {
  74223. this.el.removeCls(this.invalidCls);
  74224. },
  74225. scope: this,
  74226. single: true
  74227. });
  74228. this.el.addCls(this.invalidCls);
  74229. }
  74230. },
  74231. // @private
  74232. isDragOver: function(draggable, region) {
  74233. return this.region[this.validDropMode](draggable.region);
  74234. },
  74235. // @private
  74236. onDrag: function(draggable, e) {
  74237. this.setCanDrop(this.isDragOver(draggable), draggable, e);
  74238. },
  74239. // @private
  74240. setCanDrop: function(canDrop, draggable, e) {
  74241. if (canDrop && !this.canDrop) {
  74242. this.canDrop = true;
  74243. this.el.addCls(this.hoverCls);
  74244. this.fireEvent('dropenter', this, draggable, e);
  74245. }
  74246. else if (!canDrop && this.canDrop) {
  74247. this.canDrop = false;
  74248. this.el.removeCls(this.hoverCls);
  74249. this.fireEvent('dropleave', this, draggable, e);
  74250. }
  74251. },
  74252. // @private
  74253. onBeforeDragEnd: function(draggable, e) {
  74254. draggable.cancelRevert = this.canDrop;
  74255. },
  74256. // @private
  74257. onDragEnd: function(draggable, e) {
  74258. this.monitoring = false;
  74259. this.el.removeCls(this.activeCls);
  74260. draggable.un({
  74261. drag: this.onDrag,
  74262. beforedragend: this.onBeforeDragEnd,
  74263. dragend: this.onDragEnd,
  74264. scope: this
  74265. });
  74266. if (this.canDrop) {
  74267. this.canDrop = false;
  74268. this.el.removeCls(this.hoverCls);
  74269. this.fireEvent('drop', this, draggable, e);
  74270. }
  74271. this.fireEvent('dropdeactivate', this, draggable, e);
  74272. },
  74273. /**
  74274. * Enable the Droppable target.
  74275. * This is invoked immediately after constructing a Droppable if the
  74276. * disabled parameter is NOT set to true.
  74277. */
  74278. enable: function() {
  74279. if (!this.mgr) {
  74280. this.mgr = Ext.util.Observable.observe(Ext.util.Draggable);
  74281. }
  74282. this.mgr.on({
  74283. dragstart: this.onDragStart,
  74284. scope: this
  74285. });
  74286. this.disabled = false;
  74287. },
  74288. /**
  74289. * Disable the Droppable target.
  74290. */
  74291. disable: function() {
  74292. this.mgr.un({
  74293. dragstart: this.onDragStart,
  74294. scope: this
  74295. });
  74296. this.disabled = true;
  74297. },
  74298. /**
  74299. * Method to determine whether this Component is currently disabled.
  74300. * @return {Boolean} the disabled state of this Component.
  74301. */
  74302. isDisabled: function() {
  74303. return this.disabled;
  74304. },
  74305. /**
  74306. * Method to determine whether this Droppable is currently monitoring drag operations of Draggables.
  74307. * @return {Boolean} the monitoring state of this Droppable
  74308. */
  74309. isMonitoring: function() {
  74310. return this.monitoring;
  74311. }
  74312. });
  74313. /*
  74314. http://www.JSON.org/json2.js
  74315. 2010-03-20
  74316. Public Domain.
  74317. NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
  74318. See http://www.JSON.org/js.html
  74319. This code should be minified before deployment.
  74320. See http://javascript.crockford.com/jsmin.html
  74321. USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
  74322. NOT CONTROL.
  74323. This file creates a global JSON object containing two methods: stringify
  74324. and parse.
  74325. JSON.stringify(value, replacer, space)
  74326. value any JavaScript value, usually an object or array.
  74327. replacer an optional parameter that determines how object
  74328. values are stringified for objects. It can be a
  74329. function or an array of strings.
  74330. space an optional parameter that specifies the indentation
  74331. of nested structures. If it is omitted, the text will
  74332. be packed without extra whitespace. If it is a number,
  74333. it will specify the number of spaces to indent at each
  74334. level. If it is a string (such as '\t' or '&nbsp;'),
  74335. it contains the characters used to indent at each level.
  74336. This method produces a JSON text from a JavaScript value.
  74337. When an object value is found, if the object contains a toJSON
  74338. method, its toJSON method will be called and the result will be
  74339. stringified. A toJSON method does not serialize: it returns the
  74340. value represented by the name/value pair that should be serialized,
  74341. or undefined if nothing should be serialized. The toJSON method
  74342. will be passed the key associated with the value, and this will be
  74343. bound to the value
  74344. For example, this would serialize Dates as ISO strings.
  74345. Date.prototype.toJSON = function (key) {
  74346. function f(n) {
  74347. // Format integers to have at least two digits.
  74348. return n < 10 ? '0' + n : n;
  74349. }
  74350. return this.getUTCFullYear() + '-' +
  74351. f(this.getUTCMonth() + 1) + '-' +
  74352. f(this.getUTCDate()) + 'T' +
  74353. f(this.getUTCHours()) + ':' +
  74354. f(this.getUTCMinutes()) + ':' +
  74355. f(this.getUTCSeconds()) + 'Z';
  74356. };
  74357. You can provide an optional replacer method. It will be passed the
  74358. key and value of each member, with this bound to the containing
  74359. object. The value that is returned from your method will be
  74360. serialized. If your method returns undefined, then the member will
  74361. be excluded from the serialization.
  74362. If the replacer parameter is an array of strings, then it will be
  74363. used to select the members to be serialized. It filters the results
  74364. such that only members with keys listed in the replacer array are
  74365. stringified.
  74366. Values that do not have JSON representations, such as undefined or
  74367. functions, will not be serialized. Such values in objects will be
  74368. dropped; in arrays they will be replaced with null. You can use
  74369. a replacer function to replace those with JSON values.
  74370. JSON.stringify(undefined) returns undefined.
  74371. The optional space parameter produces a stringification of the
  74372. value that is filled with line breaks and indentation to make it
  74373. easier to read.
  74374. If the space parameter is a non-empty string, then that string will
  74375. be used for indentation. If the space parameter is a number, then
  74376. the indentation will be that many spaces.
  74377. Example:
  74378. text = JSON.stringify(['e', {pluribus: 'unum'}]);
  74379. // text is '["e",{"pluribus":"unum"}]'
  74380. text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
  74381. // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
  74382. text = JSON.stringify([new Date()], function (key, value) {
  74383. return this[key] instanceof Date ?
  74384. 'Date(' + this[key] + ')' : value;
  74385. });
  74386. // text is '["Date(---current time---)"]'
  74387. JSON.parse(text, reviver)
  74388. This method parses a JSON text to produce an object or array.
  74389. It can throw a SyntaxError exception.
  74390. The optional reviver parameter is a function that can filter and
  74391. transform the results. It receives each of the keys and values,
  74392. and its return value is used instead of the original value.
  74393. If it returns what it received, then the structure is not modified.
  74394. If it returns undefined then the member is deleted.
  74395. Example:
  74396. // Parse the text. Values that look like ISO date strings will
  74397. // be converted to Date objects.
  74398. myData = JSON.parse(text, function (key, value) {
  74399. var a;
  74400. if (typeof value === 'string') {
  74401. a =
  74402. /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
  74403. if (a) {
  74404. return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
  74405. +a[5], +a[6]));
  74406. }
  74407. }
  74408. return value;
  74409. });
  74410. myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
  74411. var d;
  74412. if (typeof value === 'string' &&
  74413. value.slice(0, 5) === 'Date(' &&
  74414. value.slice(-1) === ')') {
  74415. d = new Date(value.slice(5, -1));
  74416. if (d) {
  74417. return d;
  74418. }
  74419. }
  74420. return value;
  74421. });
  74422. This is a reference implementation. You are free to copy, modify, or
  74423. redistribute.
  74424. */
  74425. /*jslint evil: true, strict: false */
  74426. /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
  74427. call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
  74428. getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
  74429. lastIndex, length, parse, prototype, push, replace, slice, stringify,
  74430. test, toJSON, toString, valueOf
  74431. */
  74432. // Create a JSON object only if one does not already exist. We create the
  74433. // methods in a closure to avoid creating global variables.
  74434. if (!this.JSON) {
  74435. this.JSON = {};
  74436. }
  74437. (function () {
  74438. function f(n) {
  74439. // Format integers to have at least two digits.
  74440. return n < 10 ? '0' + n : n;
  74441. }
  74442. if (typeof Date.prototype.toJSON !== 'function') {
  74443. Date.prototype.toJSON = function (key) {
  74444. return isFinite(this.valueOf()) ?
  74445. this.getUTCFullYear() + '-' +
  74446. f(this.getUTCMonth() + 1) + '-' +
  74447. f(this.getUTCDate()) + 'T' +
  74448. f(this.getUTCHours()) + ':' +
  74449. f(this.getUTCMinutes()) + ':' +
  74450. f(this.getUTCSeconds()) + 'Z' : null;
  74451. };
  74452. String.prototype.toJSON =
  74453. Number.prototype.toJSON =
  74454. Boolean.prototype.toJSON = function (key) {
  74455. return this.valueOf();
  74456. };
  74457. }
  74458. var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
  74459. escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
  74460. gap,
  74461. indent,
  74462. meta = { // table of character substitutions
  74463. '\b': '\\b',
  74464. '\t': '\\t',
  74465. '\n': '\\n',
  74466. '\f': '\\f',
  74467. '\r': '\\r',
  74468. '"' : '\\"',
  74469. '\\': '\\\\'
  74470. },
  74471. rep;
  74472. function quote(string) {
  74473. // If the string contains no control characters, no quote characters, and no
  74474. // backslash characters, then we can safely slap some quotes around it.
  74475. // Otherwise we must also replace the offending characters with safe escape
  74476. // sequences.
  74477. escapable.lastIndex = 0;
  74478. return escapable.test(string) ?
  74479. '"' + string.replace(escapable, function (a) {
  74480. var c = meta[a];
  74481. return typeof c === 'string' ? c :
  74482. '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
  74483. }) + '"' :
  74484. '"' + string + '"';
  74485. }
  74486. function str(key, holder) {
  74487. // Produce a string from holder[key].
  74488. var i, // The loop counter.
  74489. k, // The member key.
  74490. v, // The member value.
  74491. length,
  74492. mind = gap,
  74493. partial,
  74494. value = holder[key];
  74495. // If the value has a toJSON method, call it to obtain a replacement value.
  74496. if (value && typeof value === 'object' &&
  74497. typeof value.toJSON === 'function') {
  74498. value = value.toJSON(key);
  74499. }
  74500. // If we were called with a replacer function, then call the replacer to
  74501. // obtain a replacement value.
  74502. if (typeof rep === 'function') {
  74503. value = rep.call(holder, key, value);
  74504. }
  74505. // What happens next depends on the value's type.
  74506. switch (typeof value) {
  74507. case 'string':
  74508. return quote(value);
  74509. case 'number':
  74510. // JSON numbers must be finite. Encode non-finite numbers as null.
  74511. return isFinite(value) ? String(value) : 'null';
  74512. case 'boolean':
  74513. case 'null':
  74514. // If the value is a boolean or null, convert it to a string. Note:
  74515. // typeof null does not produce 'null'. The case is included here in
  74516. // the remote chance that this gets fixed someday.
  74517. return String(value);
  74518. // If the type is 'object', we might be dealing with an object or an array or
  74519. // null.
  74520. case 'object':
  74521. // Due to a specification blunder in ECMAScript, typeof null is 'object',
  74522. // so watch out for that case.
  74523. if (!value) {
  74524. return 'null';
  74525. }
  74526. // Make an array to hold the partial results of stringifying this object value.
  74527. gap += indent;
  74528. partial = [];
  74529. // Is the value an array?
  74530. if (Object.prototype.toString.apply(value) === '[object Array]') {
  74531. // The value is an array. Stringify every element. Use null as a placeholder
  74532. // for non-JSON values.
  74533. length = value.length;
  74534. for (i = 0; i < length; i += 1) {
  74535. partial[i] = str(i, value) || 'null';
  74536. }
  74537. // Join all of the elements together, separated with commas, and wrap them in
  74538. // brackets.
  74539. v = partial.length === 0 ? '[]' :
  74540. gap ? '[\n' + gap +
  74541. partial.join(',\n' + gap) + '\n' +
  74542. mind + ']' :
  74543. '[' + partial.join(',') + ']';
  74544. gap = mind;
  74545. return v;
  74546. }
  74547. // If the replacer is an array, use it to select the members to be stringified.
  74548. if (rep && typeof rep === 'object') {
  74549. length = rep.length;
  74550. for (i = 0; i < length; i += 1) {
  74551. k = rep[i];
  74552. if (typeof k === 'string') {
  74553. v = str(k, value);
  74554. if (v) {
  74555. partial.push(quote(k) + (gap ? ': ' : ':') + v);
  74556. }
  74557. }
  74558. }
  74559. } else {
  74560. // Otherwise, iterate through all of the keys in the object.
  74561. for (k in value) {
  74562. if (Object.hasOwnProperty.call(value, k)) {
  74563. v = str(k, value);
  74564. if (v) {
  74565. partial.push(quote(k) + (gap ? ': ' : ':') + v);
  74566. }
  74567. }
  74568. }
  74569. }
  74570. // Join all of the member texts together, separated with commas,
  74571. // and wrap them in braces.
  74572. v = partial.length === 0 ? '{}' :
  74573. gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
  74574. mind + '}' : '{' + partial.join(',') + '}';
  74575. gap = mind;
  74576. return v;
  74577. }
  74578. return v;
  74579. }
  74580. // If the JSON object does not yet have a stringify method, give it one.
  74581. if (typeof JSON.stringify !== 'function') {
  74582. JSON.stringify = function (value, replacer, space) {
  74583. // The stringify method takes a value and an optional replacer, and an optional
  74584. // space parameter, and returns a JSON text. The replacer can be a function
  74585. // that can replace values, or an array of strings that will select the keys.
  74586. // A default replacer method can be provided. Use of the space parameter can
  74587. // produce text that is more easily readable.
  74588. var i;
  74589. gap = '';
  74590. indent = '';
  74591. // If the space parameter is a number, make an indent string containing that
  74592. // many spaces.
  74593. if (typeof space === 'number') {
  74594. for (i = 0; i < space; i += 1) {
  74595. indent += ' ';
  74596. }
  74597. // If the space parameter is a string, it will be used as the indent string.
  74598. } else if (typeof space === 'string') {
  74599. indent = space;
  74600. }
  74601. // If there is a replacer, it must be a function or an array.
  74602. // Otherwise, throw an error.
  74603. rep = replacer;
  74604. if (replacer && typeof replacer !== 'function' &&
  74605. (typeof replacer !== 'object' ||
  74606. typeof replacer.length !== 'number')) {
  74607. throw new Error('JSON.stringify');
  74608. }
  74609. // Make a fake root object containing our value under the key of ''.
  74610. // Return the result of stringifying the value.
  74611. return str('', {'': value});
  74612. };
  74613. }
  74614. // If the JSON object does not yet have a parse method, give it one.
  74615. if (typeof JSON.parse !== 'function') {
  74616. JSON.parse = function (text, reviver) {
  74617. // The parse method takes a text and an optional reviver function, and returns
  74618. // a JavaScript value if the text is a valid JSON text.
  74619. var j;
  74620. function walk(holder, key) {
  74621. // The walk method is used to recursively walk the resulting structure so
  74622. // that modifications can be made.
  74623. var k, v, value = holder[key];
  74624. if (value && typeof value === 'object') {
  74625. for (k in value) {
  74626. if (Object.hasOwnProperty.call(value, k)) {
  74627. v = walk(value, k);
  74628. if (v !== undefined) {
  74629. value[k] = v;
  74630. } else {
  74631. delete value[k];
  74632. }
  74633. }
  74634. }
  74635. }
  74636. return reviver.call(holder, key, value);
  74637. }
  74638. // Parsing happens in four stages. In the first stage, we replace certain
  74639. // Unicode characters with escape sequences. JavaScript handles many characters
  74640. // incorrectly, either silently deleting them, or treating them as line endings.
  74641. text = String(text);
  74642. cx.lastIndex = 0;
  74643. if (cx.test(text)) {
  74644. text = text.replace(cx, function (a) {
  74645. return '\\u' +
  74646. ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
  74647. });
  74648. }
  74649. // In the second stage, we run the text against regular expressions that look
  74650. // for non-JSON patterns. We are especially concerned with '()' and 'new'
  74651. // because they can cause invocation, and '=' because it can cause mutation.
  74652. // But just to be safe, we want to reject all unexpected forms.
  74653. // We split the second stage into 4 regexp operations in order to work around
  74654. // crippling inefficiencies in IE's and Safari's regexp engines. First we
  74655. // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
  74656. // replace all simple value tokens with ']' characters. Third, we delete all
  74657. // open brackets that follow a colon or comma or that begin the text. Finally,
  74658. // we look to see that the remaining characters are only whitespace or ']' or
  74659. // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
  74660. if (/^[\],:{}\s]*$/.
  74661. test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
  74662. replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
  74663. replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
  74664. // In the third stage we use the eval function to compile the text into a
  74665. // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
  74666. // in JavaScript: it can begin a block or an object literal. We wrap the text
  74667. // in parens to eliminate the ambiguity.
  74668. j = eval('(' + text + ')');
  74669. // In the optional fourth stage, we recursively walk the new structure, passing
  74670. // each name/value pair to a reviver function for possible transformation.
  74671. return typeof reviver === 'function' ?
  74672. walk({'': j}, '') : j;
  74673. }
  74674. // If the text is not JSON parseable, then a SyntaxError is thrown.
  74675. throw new SyntaxError('JSON.parse');
  74676. };
  74677. }
  74678. }());
  74679. /**
  74680. * @class Ext.util.JSON
  74681. * Modified version of Douglas Crockford"s json.js that doesn"t
  74682. * mess with the Object prototype
  74683. * http://www.json.org/js.html
  74684. * @singleton
  74685. * @ignore
  74686. */
  74687. Ext.util.JSON = {
  74688. encode: function(o) {
  74689. return JSON.stringify(o);
  74690. },
  74691. decode: function(s) {
  74692. return JSON.parse(s);
  74693. }
  74694. };
  74695. /**
  74696. * Shorthand for {@link Ext.util.JSON#encode}
  74697. * @param {Mixed} o The variable to encode
  74698. * @return {String} The JSON string
  74699. * @member Ext
  74700. * @method encode
  74701. * @ignore
  74702. */
  74703. Ext.encode = Ext.util.JSON.encode;
  74704. /**
  74705. * Shorthand for {@link Ext.util.JSON#decode}
  74706. * @param {String} json The JSON string
  74707. * @param {Boolean} safe (optional) Whether to return null or throw an exception if the JSON is invalid.
  74708. * @return {Object} The resulting object
  74709. * @member Ext
  74710. * @method decode
  74711. * @ignore
  74712. */
  74713. Ext.decode = Ext.util.JSON.decode;
  74714. /**
  74715. * @class Ext.util.translatable.CssPosition
  74716. * @private
  74717. */
  74718. Ext.define('Ext.util.translatable.CssPosition', {
  74719. extend: 'Ext.util.translatable.Dom',
  74720. doTranslate: function(x, y) {
  74721. var domStyle = this.getElement().dom.style;
  74722. if (typeof x == 'number') {
  74723. domStyle.left = x + 'px';
  74724. }
  74725. if (typeof y == 'number') {
  74726. domStyle.top = y + 'px';
  74727. }
  74728. },
  74729. destroy: function() {
  74730. var domStyle = this.getElement().dom.style;
  74731. domStyle.left = null;
  74732. domStyle.top = null;
  74733. this.callParent(arguments);
  74734. }
  74735. });
  74736. Ext.define('Ext.ux.Faker', {
  74737. config: {
  74738. names: ['Ed Spencer', 'Tommy Maintz', 'Rob Dougan', 'Jamie Avins', 'Jacky Nguyen'],
  74739. emails: ['ed@sencha.com', 'tommy@sencha.com', 'rob@sencha.com', 'jamie@sencha.com', 'jacky@sencha.com'],
  74740. lorem: [
  74741. "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus eget neque nec sem semper cursus. Fusce ",
  74742. "molestie nibh nec ligula gravida et porta enim luctus. Curabitur id accumsan dolor. Vestibulum ultricies ",
  74743. "vehicula erat vel elementum. Mauris urna odio, dignissim sit amet molestie sit amet, sodales vel metus. Ut eu ",
  74744. "volutpat nulla. Morbi ut est sed eros egestas gravida quis eget eros. Proin sit amet massa nunc. Proin congue ",
  74745. "mollis mollis. Morbi sollicitudin nisl at diam placerat eu dignissim magna rutrum.\n",
  74746. "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Phasellus eu ",
  74747. "vestibulum lectus. Fusce a eros metus. Vivamus vel aliquet neque. Ut eu purus ipsum. Nullam id leo hendrerit ",
  74748. "augue imperdiet malesuada ac eget velit. Quisque congue turpis eget ante mollis ut sollicitudin massa dapibus. ",
  74749. "Sed magna dolor, dictum sit amet aliquam eu, ultricies sit amet diam. Fusce tempor porta tellus vitae ",
  74750. "pulvinar. Aenean velit ligula, fermentum non imperdiet et, suscipit sed libero. Aliquam ac ligula ut dui ",
  74751. "pharetra dictum vel vel nunc. Phasellus semper, ligula id tristique ullamcorper, tortor diam mollis erat, sed ",
  74752. "feugiat nisl nisi sit amet sem. Maecenas nec mi vitae ligula malesuada pellentesque.\n",
  74753. "Quisque diam velit, suscipit sit amet ornare eu, congue sed quam. Integer rhoncus luctus mi, sed pulvinar ",
  74754. "lectus lobortis non. Sed egestas orci nec elit sagittis eu condimentum massa volutpat. Fusce blandit congue ",
  74755. "enim venenatis lacinia. Donec enim sapien, sollicitudin at placerat non, vehicula ut nisi. Aliquam volutpat ",
  74756. "metus sit amet lacus condimentum fermentum. Aliquam congue scelerisque leo ut tristique."
  74757. ].join(""),
  74758. subjects: [
  74759. "Order more widgets",
  74760. "You're crazy",
  74761. "Jacky is not his real name",
  74762. "Why am I here?",
  74763. "This is totally broken",
  74764. "When do we ship?",
  74765. "Top Secret",
  74766. "There's always money in the banana stand"
  74767. ]
  74768. },
  74769. oneOf: function(set) {
  74770. return set[Math.floor(Math.random() * set.length)];
  74771. },
  74772. name: function() {
  74773. return this.oneOf(this.getNames());
  74774. },
  74775. email: function() {
  74776. return this.oneOf(this.getSubjects());
  74777. },
  74778. subject: function() {
  74779. return this.oneOf(this.getSubjects());
  74780. },
  74781. lorem: function(paragraphs) {
  74782. var lorem = this.getLorem();
  74783. if (paragraphs) {
  74784. return lorem.split("\n").slice(0, paragraphs).join("\n");
  74785. } else {
  74786. return lorem;
  74787. }
  74788. }
  74789. });
  74790. Ext.define('Ext.ux.auth.Session', {
  74791. constructor: function(credentials) {
  74792. credentials = {
  74793. username: 'ed',
  74794. password: 'secret'
  74795. }
  74796. },
  74797. validate: function(options) {
  74798. options = {
  74799. success: function(session) {
  74800. },
  74801. failure: function(session) {
  74802. },
  74803. callback: function(session) {
  74804. },
  74805. scope: me
  74806. }
  74807. },
  74808. destroy: function() {
  74809. }
  74810. });
  74811. Ext.define('Ext.ux.auth.model.Session', {
  74812. fields: ['username', 'created_at', 'expires_at'],
  74813. validate: function() {
  74814. },
  74815. destroy: function() {
  74816. }
  74817. });
  74818. /**
  74819. * @private
  74820. * Base class for iOS and Android viewports.
  74821. */
  74822. Ext.define('Ext.viewport.Default', {
  74823. extend: 'Ext.Container',
  74824. xtype: 'viewport',
  74825. PORTRAIT: 'portrait',
  74826. LANDSCAPE: 'landscape',
  74827. requires: [
  74828. 'Ext.LoadMask',
  74829. 'Ext.layout.Card'
  74830. ],
  74831. /**
  74832. * @event ready
  74833. * Fires when the Viewport is in the DOM and ready.
  74834. * @param {Ext.Viewport} this
  74835. */
  74836. /**
  74837. * @event maximize
  74838. * Fires when the Viewport is maximized.
  74839. * @param {Ext.Viewport} this
  74840. */
  74841. /**
  74842. * @event orientationchange
  74843. * Fires when the Viewport orientation has changed.
  74844. * @param {Ext.Viewport} this
  74845. * @param {String} newOrientation The new orientation.
  74846. * @param {Number} width The width of the Viewport.
  74847. * @param {Number} height The height of the Viewport.
  74848. */
  74849. config: {
  74850. /**
  74851. * @cfg {Boolean} autoMaximize
  74852. * Whether or not to always automatically maximize the viewport on first load and all subsequent orientation changes.
  74853. *
  74854. * This is set to `false` by default for a number of reasons:
  74855. *
  74856. * - Orientation change performance is drastically reduced when this is enabled, on all devices.
  74857. * - On some devices (mostly Android) this can sometimes cause issues when the default browser zoom setting is changed.
  74858. * - When wrapping your phone in a native shell, you may get a blank screen.
  74859. * - When bookmarked to the homescreen (iOS), you may get a blank screen.
  74860. *
  74861. * @accessor
  74862. */
  74863. autoMaximize: false,
  74864. /**
  74865. * @private
  74866. */
  74867. autoBlurInput: true,
  74868. /**
  74869. * @cfg {Boolean} preventPanning
  74870. * Whether or not to always prevent default panning behavior of the
  74871. * browser's viewport.
  74872. * @accessor
  74873. */
  74874. preventPanning: true,
  74875. /**
  74876. * @cfg {Boolean} preventZooming
  74877. * `true` to attempt to stop zooming when you double tap on the screen on mobile devices,
  74878. * typically HTC devices with HTC Sense UI.
  74879. * @accessor
  74880. */
  74881. preventZooming: false,
  74882. /**
  74883. * @cfg
  74884. * @private
  74885. */
  74886. autoRender: true,
  74887. /**
  74888. * @cfg {Object/String} layout Configuration for this Container's layout. Example:
  74889. *
  74890. * Ext.create('Ext.Container', {
  74891. * layout: {
  74892. * type: 'hbox',
  74893. * align: 'middle'
  74894. * },
  74895. * items: [
  74896. * {
  74897. * xtype: 'panel',
  74898. * flex: 1,
  74899. * style: 'background-color: red;'
  74900. * },
  74901. * {
  74902. * xtype: 'panel',
  74903. * flex: 2,
  74904. * style: 'background-color: green'
  74905. * }
  74906. * ]
  74907. * });
  74908. *
  74909. * See the [layouts guide](#!/guides/layouts) for more information.
  74910. *
  74911. * @accessor
  74912. */
  74913. layout: 'card',
  74914. /**
  74915. * @cfg
  74916. * @private
  74917. */
  74918. width: '100%',
  74919. /**
  74920. * @cfg
  74921. * @private
  74922. */
  74923. height: '100%',
  74924. useBodyElement: true
  74925. },
  74926. /**
  74927. * @property {Boolean} isReady
  74928. * `true` if the DOM is ready.
  74929. */
  74930. isReady: false,
  74931. isViewport: true,
  74932. isMaximizing: false,
  74933. id: 'ext-viewport',
  74934. isInputRegex: /^(input|textarea|select|a)$/i,
  74935. focusedElement: null,
  74936. /**
  74937. * @private
  74938. */
  74939. fullscreenItemCls: Ext.baseCSSPrefix + 'fullscreen',
  74940. constructor: function(config) {
  74941. var bind = Ext.Function.bind;
  74942. this.doPreventPanning = bind(this.doPreventPanning, this);
  74943. this.doPreventZooming = bind(this.doPreventZooming, this);
  74944. this.doBlurInput = bind(this.doBlurInput, this);
  74945. this.maximizeOnEvents = ['ready', 'orientationchange'];
  74946. this.orientation = this.determineOrientation();
  74947. this.windowWidth = this.getWindowWidth();
  74948. this.windowHeight = this.getWindowHeight();
  74949. this.windowOuterHeight = this.getWindowOuterHeight();
  74950. if (!this.stretchHeights) {
  74951. this.stretchHeights = {};
  74952. }
  74953. this.callParent([config]);
  74954. // Android is handled separately
  74955. if (!Ext.os.is.Android || Ext.browser.name == 'ChromeMobile') {
  74956. if (this.supportsOrientation()) {
  74957. this.addWindowListener('orientationchange', bind(this.onOrientationChange, this));
  74958. }
  74959. else {
  74960. this.addWindowListener('resize', bind(this.onResize, this));
  74961. }
  74962. }
  74963. document.addEventListener('focus', bind(this.onElementFocus, this), true);
  74964. document.addEventListener('blur', bind(this.onElementBlur, this), true);
  74965. Ext.onDocumentReady(this.onDomReady, this);
  74966. this.on('ready', this.onReady, this, {single: true});
  74967. this.getEventDispatcher().addListener('component', '*', 'fullscreen', 'onItemFullscreenChange', this);
  74968. return this;
  74969. },
  74970. onDomReady: function() {
  74971. this.isReady = true;
  74972. this.updateSize();
  74973. this.fireEvent('ready', this);
  74974. },
  74975. onReady: function() {
  74976. if (this.getAutoRender()) {
  74977. this.render();
  74978. }
  74979. },
  74980. onElementFocus: function(e) {
  74981. this.focusedElement = e.target;
  74982. },
  74983. onElementBlur: function() {
  74984. this.focusedElement = null;
  74985. },
  74986. render: function() {
  74987. if (!this.rendered) {
  74988. var body = Ext.getBody(),
  74989. clsPrefix = Ext.baseCSSPrefix,
  74990. classList = [],
  74991. osEnv = Ext.os,
  74992. osName = osEnv.name.toLowerCase(),
  74993. browserName = Ext.browser.name.toLowerCase(),
  74994. osMajorVersion = osEnv.version.getMajor(),
  74995. orientation = this.getOrientation();
  74996. this.renderTo(body);
  74997. classList.push(clsPrefix + osEnv.deviceType.toLowerCase());
  74998. if (osEnv.is.iPad) {
  74999. classList.push(clsPrefix + 'ipad');
  75000. }
  75001. classList.push(clsPrefix + osName);
  75002. classList.push(clsPrefix + browserName);
  75003. if (osMajorVersion) {
  75004. classList.push(clsPrefix + osName + '-' + osMajorVersion);
  75005. }
  75006. if (osEnv.is.BlackBerry) {
  75007. classList.push(clsPrefix + 'bb');
  75008. }
  75009. if (Ext.browser.is.Standalone) {
  75010. classList.push(clsPrefix + 'standalone');
  75011. }
  75012. classList.push(clsPrefix + orientation);
  75013. body.addCls(classList);
  75014. }
  75015. },
  75016. applyAutoBlurInput: function(autoBlurInput) {
  75017. var touchstart = (Ext.feature.has.Touch) ? 'touchstart' : 'mousedown';
  75018. if (autoBlurInput) {
  75019. this.addWindowListener(touchstart, this.doBlurInput, false);
  75020. }
  75021. else {
  75022. this.removeWindowListener(touchstart, this.doBlurInput, false);
  75023. }
  75024. return autoBlurInput;
  75025. },
  75026. applyAutoMaximize: function(autoMaximize) {
  75027. if (Ext.browser.is.WebView) {
  75028. autoMaximize = false;
  75029. }
  75030. if (autoMaximize) {
  75031. this.on('ready', 'doAutoMaximizeOnReady', this, { single: true });
  75032. this.on('orientationchange', 'doAutoMaximizeOnOrientationChange', this);
  75033. }
  75034. else {
  75035. this.un('ready', 'doAutoMaximizeOnReady', this);
  75036. this.un('orientationchange', 'doAutoMaximizeOnOrientationChange', this);
  75037. }
  75038. return autoMaximize;
  75039. },
  75040. applyPreventPanning: function(preventPanning) {
  75041. if (preventPanning) {
  75042. this.addWindowListener('touchmove', this.doPreventPanning, false);
  75043. }
  75044. else {
  75045. this.removeWindowListener('touchmove', this.doPreventPanning, false);
  75046. }
  75047. return preventPanning;
  75048. },
  75049. applyPreventZooming: function(preventZooming) {
  75050. var touchstart = (Ext.feature.has.Touch) ? 'touchstart' : 'mousedown';
  75051. if (preventZooming) {
  75052. this.addWindowListener(touchstart, this.doPreventZooming, false);
  75053. }
  75054. else {
  75055. this.removeWindowListener(touchstart, this.doPreventZooming, false);
  75056. }
  75057. return preventZooming;
  75058. },
  75059. doAutoMaximizeOnReady: function() {
  75060. var controller = arguments[arguments.length - 1];
  75061. controller.pause();
  75062. this.isMaximizing = true;
  75063. this.on('maximize', function() {
  75064. this.isMaximizing = false;
  75065. this.updateSize();
  75066. controller.resume();
  75067. this.fireEvent('ready', this);
  75068. }, this, { single: true });
  75069. this.maximize();
  75070. },
  75071. doAutoMaximizeOnOrientationChange: function() {
  75072. var controller = arguments[arguments.length - 1],
  75073. firingArguments = controller.firingArguments;
  75074. controller.pause();
  75075. this.isMaximizing = true;
  75076. this.on('maximize', function() {
  75077. this.isMaximizing = false;
  75078. this.updateSize();
  75079. firingArguments[2] = this.windowWidth;
  75080. firingArguments[3] = this.windowHeight;
  75081. controller.resume();
  75082. }, this, { single: true });
  75083. this.maximize();
  75084. },
  75085. doBlurInput: function(e) {
  75086. var target = e.target,
  75087. focusedElement = this.focusedElement;
  75088. if (focusedElement && !this.isInputRegex.test(target.tagName)) {
  75089. delete this.focusedElement;
  75090. focusedElement.blur();
  75091. }
  75092. },
  75093. doPreventPanning: function(e) {
  75094. e.preventDefault();
  75095. },
  75096. doPreventZooming: function(e) {
  75097. // Don't prevent right mouse event
  75098. if ('button' in e && e.button !== 0) {
  75099. return;
  75100. }
  75101. var target = e.target;
  75102. if (target && target.nodeType === 1 && !this.isInputRegex.test(target.tagName)) {
  75103. e.preventDefault();
  75104. }
  75105. },
  75106. addWindowListener: function(eventName, fn, capturing) {
  75107. window.addEventListener(eventName, fn, Boolean(capturing));
  75108. },
  75109. removeWindowListener: function(eventName, fn, capturing) {
  75110. window.removeEventListener(eventName, fn, Boolean(capturing));
  75111. },
  75112. doAddListener: function(eventName, fn, scope, options) {
  75113. if (eventName === 'ready' && this.isReady && !this.isMaximizing) {
  75114. fn.call(scope);
  75115. return this;
  75116. }
  75117. return this.callSuper(arguments);
  75118. },
  75119. supportsOrientation: function() {
  75120. return Ext.feature.has.Orientation;
  75121. },
  75122. onResize: function() {
  75123. var oldWidth = this.windowWidth,
  75124. oldHeight = this.windowHeight,
  75125. width = this.getWindowWidth(),
  75126. height = this.getWindowHeight(),
  75127. currentOrientation = this.getOrientation(),
  75128. newOrientation = this.determineOrientation();
  75129. // Determine orientation change via resize. BOTH width AND height much change, otherwise
  75130. // this is a keyboard popping up.
  75131. if ((oldWidth !== width && oldHeight !== height) && currentOrientation !== newOrientation) {
  75132. this.fireOrientationChangeEvent(newOrientation, currentOrientation);
  75133. }
  75134. },
  75135. onOrientationChange: function() {
  75136. var currentOrientation = this.getOrientation(),
  75137. newOrientation = this.determineOrientation();
  75138. if (newOrientation !== currentOrientation) {
  75139. this.fireOrientationChangeEvent(newOrientation, currentOrientation);
  75140. }
  75141. },
  75142. fireOrientationChangeEvent: function(newOrientation, oldOrientation) {
  75143. var clsPrefix = Ext.baseCSSPrefix;
  75144. Ext.getBody().replaceCls(clsPrefix + oldOrientation, clsPrefix + newOrientation);
  75145. this.orientation = newOrientation;
  75146. this.updateSize();
  75147. this.fireEvent('orientationchange', this, newOrientation, this.windowWidth, this.windowHeight);
  75148. },
  75149. updateSize: function(width, height) {
  75150. this.windowWidth = width !== undefined ? width : this.getWindowWidth();
  75151. this.windowHeight = height !== undefined ? height : this.getWindowHeight();
  75152. return this;
  75153. },
  75154. waitUntil: function(condition, onSatisfied, onTimeout, delay, timeoutDuration) {
  75155. if (!delay) {
  75156. delay = 50;
  75157. }
  75158. if (!timeoutDuration) {
  75159. timeoutDuration = 2000;
  75160. }
  75161. var scope = this,
  75162. elapse = 0;
  75163. setTimeout(function repeat() {
  75164. elapse += delay;
  75165. if (condition.call(scope) === true) {
  75166. if (onSatisfied) {
  75167. onSatisfied.call(scope);
  75168. }
  75169. }
  75170. else {
  75171. if (elapse >= timeoutDuration) {
  75172. if (onTimeout) {
  75173. onTimeout.call(scope);
  75174. }
  75175. }
  75176. else {
  75177. setTimeout(repeat, delay);
  75178. }
  75179. }
  75180. }, delay);
  75181. },
  75182. maximize: function() {
  75183. this.fireMaximizeEvent();
  75184. },
  75185. fireMaximizeEvent: function() {
  75186. this.updateSize();
  75187. this.fireEvent('maximize', this);
  75188. },
  75189. doSetHeight: function(height) {
  75190. Ext.getBody().setHeight(height);
  75191. this.callParent(arguments);
  75192. },
  75193. doSetWidth: function(width) {
  75194. Ext.getBody().setWidth(width);
  75195. this.callParent(arguments);
  75196. },
  75197. scrollToTop: function() {
  75198. window.scrollTo(0, -1);
  75199. },
  75200. /**
  75201. * Retrieves the document width.
  75202. * @return {Number} width in pixels.
  75203. */
  75204. getWindowWidth: function() {
  75205. return window.innerWidth;
  75206. },
  75207. /**
  75208. * Retrieves the document height.
  75209. * @return {Number} height in pixels.
  75210. */
  75211. getWindowHeight: function() {
  75212. return window.innerHeight;
  75213. },
  75214. getWindowOuterHeight: function() {
  75215. return window.outerHeight;
  75216. },
  75217. getWindowOrientation: function() {
  75218. return window.orientation;
  75219. },
  75220. /**
  75221. * Returns the current orientation.
  75222. * @return {String} `portrait` or `landscape`
  75223. */
  75224. getOrientation: function() {
  75225. return this.orientation;
  75226. },
  75227. getSize: function() {
  75228. return {
  75229. width: this.windowWidth,
  75230. height: this.windowHeight
  75231. };
  75232. },
  75233. determineOrientation: function() {
  75234. var portrait = this.PORTRAIT,
  75235. landscape = this.LANDSCAPE;
  75236. if (this.supportsOrientation()) {
  75237. if (this.getWindowOrientation() % 180 === 0) {
  75238. return portrait;
  75239. }
  75240. return landscape;
  75241. }
  75242. else {
  75243. if (this.getWindowHeight() >= this.getWindowWidth()) {
  75244. return portrait;
  75245. }
  75246. return landscape;
  75247. }
  75248. },
  75249. onItemFullscreenChange: function(item) {
  75250. item.addCls(this.fullscreenItemCls);
  75251. this.add(item);
  75252. }
  75253. });
  75254. /**
  75255. * @private
  75256. * Android version of viewport.
  75257. */
  75258. Ext.define('Ext.viewport.Android', {
  75259. extend: 'Ext.viewport.Default',
  75260. constructor: function() {
  75261. this.on('orientationchange', 'doFireOrientationChangeEvent', this, { prepend: true });
  75262. this.on('orientationchange', 'hideKeyboardIfNeeded', this, { prepend: true });
  75263. this.callParent(arguments);
  75264. this.addWindowListener('resize', Ext.Function.bind(this.onResize, this));
  75265. },
  75266. getDummyInput: function() {
  75267. var input = this.dummyInput,
  75268. focusedElement = this.focusedElement,
  75269. box = Ext.fly(focusedElement).getPageBox();
  75270. if (!input) {
  75271. this.dummyInput = input = document.createElement('input');
  75272. input.style.position = 'absolute';
  75273. input.style.opacity = '0';
  75274. document.body.appendChild(input);
  75275. }
  75276. input.style.left = box.left + 'px';
  75277. input.style.top = box.top + 'px';
  75278. input.style.display = '';
  75279. return input;
  75280. },
  75281. doBlurInput: function(e) {
  75282. var target = e.target,
  75283. focusedElement = this.focusedElement,
  75284. dummy;
  75285. if (focusedElement && !this.isInputRegex.test(target.tagName)) {
  75286. dummy = this.getDummyInput();
  75287. delete this.focusedElement;
  75288. dummy.focus();
  75289. setTimeout(function() {
  75290. dummy.style.display = 'none';
  75291. }, 100);
  75292. }
  75293. },
  75294. hideKeyboardIfNeeded: function() {
  75295. var eventController = arguments[arguments.length - 1],
  75296. focusedElement = this.focusedElement;
  75297. if (focusedElement) {
  75298. delete this.focusedElement;
  75299. eventController.pause();
  75300. if (Ext.os.version.lt('4')) {
  75301. focusedElement.style.display = 'none';
  75302. }
  75303. else {
  75304. focusedElement.blur();
  75305. }
  75306. setTimeout(function() {
  75307. focusedElement.style.display = '';
  75308. eventController.resume();
  75309. }, 1000);
  75310. }
  75311. },
  75312. doFireOrientationChangeEvent: function() {
  75313. var eventController = arguments[arguments.length - 1];
  75314. this.orientationChanging = true;
  75315. eventController.pause();
  75316. this.waitUntil(function() {
  75317. return this.getWindowOuterHeight() !== this.windowOuterHeight;
  75318. }, function() {
  75319. this.windowOuterHeight = this.getWindowOuterHeight();
  75320. this.updateSize();
  75321. eventController.firingArguments[2] = this.windowWidth;
  75322. eventController.firingArguments[3] = this.windowHeight;
  75323. eventController.resume();
  75324. this.orientationChanging = false;
  75325. }, function() {
  75326. //<debug error>
  75327. Ext.Logger.error("Timeout waiting for viewport's outerHeight to change before firing orientationchange", this);
  75328. //</debug>
  75329. });
  75330. return this;
  75331. },
  75332. applyAutoMaximize: function(autoMaximize) {
  75333. autoMaximize = this.callParent(arguments);
  75334. this.on('add', 'fixSize', this, { single: true });
  75335. if (!autoMaximize) {
  75336. this.on('ready', 'fixSize', this, { single: true });
  75337. this.onAfter('orientationchange', 'doFixSize', this, { buffer: 100 });
  75338. }
  75339. else {
  75340. this.un('ready', 'fixSize', this);
  75341. this.unAfter('orientationchange', 'doFixSize', this);
  75342. }
  75343. },
  75344. fixSize: function() {
  75345. this.doFixSize();
  75346. },
  75347. doFixSize: function() {
  75348. this.setHeight(this.getWindowHeight());
  75349. },
  75350. determineOrientation: function() {
  75351. return (this.getWindowHeight() >= this.getWindowWidth()) ? this.PORTRAIT : this.LANDSCAPE;
  75352. },
  75353. getActualWindowOuterHeight: function() {
  75354. return Math.round(this.getWindowOuterHeight() / window.devicePixelRatio);
  75355. },
  75356. maximize: function() {
  75357. var stretchHeights = this.stretchHeights,
  75358. orientation = this.orientation,
  75359. height;
  75360. height = stretchHeights[orientation];
  75361. if (!height) {
  75362. stretchHeights[orientation] = height = this.getActualWindowOuterHeight();
  75363. }
  75364. if (!this.addressBarHeight) {
  75365. this.addressBarHeight = height - this.getWindowHeight();
  75366. }
  75367. this.setHeight(height);
  75368. var isHeightMaximized = Ext.Function.bind(this.isHeightMaximized, this, [height]);
  75369. this.scrollToTop();
  75370. this.waitUntil(isHeightMaximized, this.fireMaximizeEvent, this.fireMaximizeEvent);
  75371. },
  75372. isHeightMaximized: function(height) {
  75373. this.scrollToTop();
  75374. return this.getWindowHeight() === height;
  75375. }
  75376. }, function() {
  75377. if (!Ext.os.is.Android) {
  75378. return;
  75379. }
  75380. var version = Ext.os.version,
  75381. userAgent = Ext.browser.userAgent,
  75382. // These Android devices have a nasty bug which causes JavaScript timers to be completely frozen
  75383. // when the browser's viewport is being panned.
  75384. isBuggy = /(htc|desire|incredible|ADR6300)/i.test(userAgent) && version.lt('2.3');
  75385. if (isBuggy) {
  75386. this.override({
  75387. constructor: function(config) {
  75388. if (!config) {
  75389. config = {};
  75390. }
  75391. config.autoMaximize = false;
  75392. this.watchDogTick = Ext.Function.bind(this.watchDogTick, this);
  75393. setInterval(this.watchDogTick, 1000);
  75394. return this.callParent([config]);
  75395. },
  75396. watchDogTick: function() {
  75397. this.watchDogLastTick = Ext.Date.now();
  75398. },
  75399. doPreventPanning: function() {
  75400. var now = Ext.Date.now(),
  75401. lastTick = this.watchDogLastTick,
  75402. deltaTime = now - lastTick;
  75403. // Timers are frozen
  75404. if (deltaTime >= 2000) {
  75405. return;
  75406. }
  75407. return this.callParent(arguments);
  75408. },
  75409. doPreventZooming: function() {
  75410. var now = Ext.Date.now(),
  75411. lastTick = this.watchDogLastTick,
  75412. deltaTime = now - lastTick;
  75413. // Timers are frozen
  75414. if (deltaTime >= 2000) {
  75415. return;
  75416. }
  75417. return this.callParent(arguments);
  75418. }
  75419. });
  75420. }
  75421. if (version.match('2')) {
  75422. this.override({
  75423. onReady: function() {
  75424. this.addWindowListener('resize', Ext.Function.bind(this.onWindowResize, this));
  75425. this.callParent(arguments);
  75426. },
  75427. scrollToTop: function() {
  75428. document.body.scrollTop = 100;
  75429. },
  75430. onWindowResize: function() {
  75431. var oldWidth = this.windowWidth,
  75432. oldHeight = this.windowHeight,
  75433. width = this.getWindowWidth(),
  75434. height = this.getWindowHeight();
  75435. if (this.getAutoMaximize() && !this.isMaximizing && !this.orientationChanging
  75436. && window.scrollY === 0
  75437. && oldWidth === width
  75438. && height < oldHeight
  75439. && ((height >= oldHeight - this.addressBarHeight) || !this.focusedElement)) {
  75440. this.scrollToTop();
  75441. }
  75442. },
  75443. fixSize: function() {
  75444. var orientation = this.getOrientation(),
  75445. outerHeight = window.outerHeight,
  75446. outerWidth = window.outerWidth,
  75447. actualOuterHeight;
  75448. // On some Android 2 devices such as the Kindle Fire, outerWidth and outerHeight are reported wrongly
  75449. // when navigating from another page that has larger size.
  75450. if (orientation === 'landscape' && (outerHeight < outerWidth)
  75451. || orientation === 'portrait' && (outerHeight >= outerWidth)) {
  75452. actualOuterHeight = this.getActualWindowOuterHeight();
  75453. }
  75454. else {
  75455. actualOuterHeight = this.getWindowHeight();
  75456. }
  75457. this.waitUntil(function() {
  75458. return actualOuterHeight > this.getWindowHeight();
  75459. }, this.doFixSize, this.doFixSize, 50, 1000);
  75460. }
  75461. });
  75462. }
  75463. else if (version.gtEq('3.1')) {
  75464. this.override({
  75465. isHeightMaximized: function(height) {
  75466. this.scrollToTop();
  75467. return this.getWindowHeight() === height - 1;
  75468. }
  75469. });
  75470. }
  75471. else if (version.match('3')) {
  75472. this.override({
  75473. isHeightMaximized: function() {
  75474. this.scrollToTop();
  75475. return true;
  75476. }
  75477. })
  75478. }
  75479. if (version.gtEq('4')) {
  75480. this.override({
  75481. doBlurInput: Ext.emptyFn
  75482. });
  75483. }
  75484. });
  75485. /**
  75486. * @private
  75487. * iOS version of viewport.
  75488. */
  75489. Ext.define('Ext.viewport.Ios', {
  75490. extend: 'Ext.viewport.Default',
  75491. isFullscreen: function() {
  75492. return this.isHomeScreen();
  75493. },
  75494. isHomeScreen: function() {
  75495. return window.navigator.standalone === true;
  75496. },
  75497. constructor: function() {
  75498. this.callParent(arguments);
  75499. if (this.getAutoMaximize() && !this.isFullscreen()) {
  75500. this.addWindowListener('touchstart', Ext.Function.bind(this.onTouchStart, this));
  75501. }
  75502. },
  75503. maximize: function() {
  75504. if (this.isFullscreen()) {
  75505. return this.callParent();
  75506. }
  75507. var stretchHeights = this.stretchHeights,
  75508. orientation = this.orientation,
  75509. currentHeight = this.getWindowHeight(),
  75510. height = stretchHeights[orientation];
  75511. if (window.scrollY > 0) {
  75512. this.scrollToTop();
  75513. if (!height) {
  75514. stretchHeights[orientation] = height = this.getWindowHeight();
  75515. }
  75516. this.setHeight(height);
  75517. this.fireMaximizeEvent();
  75518. }
  75519. else {
  75520. if (!height) {
  75521. height = this.getScreenHeight();
  75522. }
  75523. this.setHeight(height);
  75524. this.waitUntil(function() {
  75525. this.scrollToTop();
  75526. return currentHeight !== this.getWindowHeight();
  75527. }, function() {
  75528. if (!stretchHeights[orientation]) {
  75529. height = stretchHeights[orientation] = this.getWindowHeight();
  75530. this.setHeight(height);
  75531. }
  75532. this.fireMaximizeEvent();
  75533. }, function() {
  75534. //<debug error>
  75535. Ext.Logger.error("Timeout waiting for window.innerHeight to change", this);
  75536. //</debug>
  75537. height = stretchHeights[orientation] = this.getWindowHeight();
  75538. this.setHeight(height);
  75539. this.fireMaximizeEvent();
  75540. }, 50, 1000);
  75541. }
  75542. },
  75543. getScreenHeight: function() {
  75544. return window.screen[this.orientation === this.PORTRAIT ? 'height' : 'width'];
  75545. },
  75546. onElementFocus: function() {
  75547. if (this.getAutoMaximize() && !this.isFullscreen()) {
  75548. clearTimeout(this.scrollToTopTimer);
  75549. }
  75550. this.callParent(arguments);
  75551. },
  75552. onElementBlur: function() {
  75553. if (this.getAutoMaximize() && !this.isFullscreen()) {
  75554. this.scrollToTopTimer = setTimeout(this.scrollToTop, 500);
  75555. }
  75556. this.callParent(arguments);
  75557. },
  75558. onTouchStart: function() {
  75559. if (this.focusedElement === null) {
  75560. this.scrollToTop();
  75561. }
  75562. },
  75563. scrollToTop: function() {
  75564. window.scrollTo(0, 0);
  75565. }
  75566. }, function() {
  75567. if (!Ext.os.is.iOS) {
  75568. return;
  75569. }
  75570. if (Ext.os.version.lt('3.2')) {
  75571. this.override({
  75572. constructor: function() {
  75573. var stretchHeights = this.stretchHeights = {};
  75574. stretchHeights[this.PORTRAIT] = 416;
  75575. stretchHeights[this.LANDSCAPE] = 268;
  75576. return this.callOverridden(arguments);
  75577. }
  75578. });
  75579. }
  75580. if (Ext.os.version.lt('5')) {
  75581. this.override({
  75582. fieldMaskClsTest: '-field-mask',
  75583. doPreventZooming: function(e) {
  75584. var target = e.target;
  75585. if (target && target.nodeType === 1 &&
  75586. !this.isInputRegex.test(target.tagName) &&
  75587. target.className.indexOf(this.fieldMaskClsTest) == -1) {
  75588. e.preventDefault();
  75589. }
  75590. }
  75591. });
  75592. }
  75593. if (Ext.os.is.iPad) {
  75594. this.override({
  75595. isFullscreen: function() {
  75596. return true;
  75597. }
  75598. });
  75599. }
  75600. });
  75601. /**
  75602. * This class acts as a factory for environment-specific viewport implementations.
  75603. *
  75604. * Please refer to the {@link Ext.Viewport} documentation about using the global instance.
  75605. * @private
  75606. */
  75607. Ext.define('Ext.viewport.Viewport', {
  75608. requires: [
  75609. 'Ext.viewport.Ios',
  75610. 'Ext.viewport.Android'
  75611. ],
  75612. constructor: function(config) {
  75613. var osName = Ext.os.name,
  75614. viewportName, viewport;
  75615. switch (osName) {
  75616. case 'Android':
  75617. viewportName = (Ext.browser.name == 'ChromeMobile') ? 'Default' : 'Android';
  75618. break;
  75619. case 'iOS':
  75620. viewportName = 'Ios';
  75621. break;
  75622. default:
  75623. viewportName = 'Default';
  75624. }
  75625. viewport = Ext.create('Ext.viewport.' + viewportName, config);
  75626. return viewport;
  75627. }
  75628. });
  75629. // Docs for the singleton instance created by above factory:
  75630. /**
  75631. * @class Ext.Viewport
  75632. * @extends Ext.viewport.Default
  75633. * @singleton
  75634. *
  75635. * Ext.Viewport is a instance created when you use {@link Ext#setup}. Because {@link Ext.Viewport} extends from
  75636. * {@link Ext.Container}, it has as {@link #layout} (which defaults to {@link Ext.layout.Card}). This means you
  75637. * can add items to it at any time, from anywhere in your code. The {@link Ext.Viewport} {@link #cfg-fullscreen}
  75638. * configuration is `true` by default, so it will take up your whole screen.
  75639. *
  75640. * @example raw
  75641. * Ext.setup({
  75642. * onReady: function() {
  75643. * Ext.Viewport.add({
  75644. * xtype: 'container',
  75645. * html: 'My new container!'
  75646. * });
  75647. * }
  75648. * });
  75649. *
  75650. * If you want to customize anything about this {@link Ext.Viewport} instance, you can do so by adding a property
  75651. * called `viewport` into your {@link Ext#setup} object:
  75652. *
  75653. * @example raw
  75654. * Ext.setup({
  75655. * viewport: {
  75656. * layout: 'vbox'
  75657. * },
  75658. * onReady: function() {
  75659. * //do something
  75660. * }
  75661. * });
  75662. *
  75663. * **Note** if you use {@link Ext#onReady}, this instance of {@link Ext.Viewport} will **not** be created. Though, in most cases,
  75664. * you should **not** use {@link Ext#onReady}.
  75665. */