123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976149771497814979149801498114982149831498414985149861498714988149891499014991149921499314994149951499614997149981499915000150011500215003150041500515006150071500815009150101501115012150131501415015150161501715018150191502015021150221502315024150251502615027150281502915030150311503215033150341503515036150371503815039150401504115042150431504415045150461504715048150491505015051150521505315054150551505615057150581505915060150611506215063150641506515066150671506815069150701507115072150731507415075150761507715078150791508015081150821508315084150851508615087150881508915090150911509215093150941509515096150971509815099151001510115102151031510415105151061510715108151091511015111151121511315114151151511615117151181511915120151211512215123151241512515126151271512815129151301513115132151331513415135151361513715138151391514015141151421514315144151451514615147151481514915150151511515215153151541515515156151571515815159151601516115162151631516415165151661516715168151691517015171151721517315174151751517615177151781517915180151811518215183151841518515186151871518815189151901519115192151931519415195151961519715198151991520015201152021520315204152051520615207152081520915210152111521215213152141521515216152171521815219152201522115222152231522415225152261522715228152291523015231152321523315234152351523615237152381523915240152411524215243152441524515246152471524815249152501525115252152531525415255152561525715258152591526015261152621526315264152651526615267152681526915270152711527215273152741527515276152771527815279152801528115282152831528415285152861528715288152891529015291152921529315294152951529615297152981529915300153011530215303153041530515306153071530815309153101531115312153131531415315153161531715318153191532015321153221532315324153251532615327153281532915330153311533215333153341533515336153371533815339153401534115342153431534415345153461534715348153491535015351153521535315354153551535615357153581535915360153611536215363153641536515366153671536815369153701537115372153731537415375153761537715378153791538015381153821538315384153851538615387153881538915390153911539215393153941539515396153971539815399154001540115402154031540415405154061540715408154091541015411154121541315414154151541615417154181541915420154211542215423154241542515426154271542815429154301543115432154331543415435154361543715438154391544015441154421544315444154451544615447154481544915450154511545215453154541545515456154571545815459154601546115462154631546415465154661546715468154691547015471154721547315474154751547615477154781547915480154811548215483154841548515486154871548815489154901549115492154931549415495154961549715498154991550015501155021550315504155051550615507155081550915510155111551215513155141551515516155171551815519155201552115522155231552415525155261552715528155291553015531155321553315534155351553615537155381553915540155411554215543155441554515546155471554815549155501555115552155531555415555155561555715558155591556015561155621556315564155651556615567155681556915570155711557215573155741557515576155771557815579155801558115582155831558415585155861558715588155891559015591155921559315594155951559615597155981559915600156011560215603156041560515606156071560815609156101561115612156131561415615156161561715618156191562015621156221562315624156251562615627156281562915630156311563215633156341563515636156371563815639156401564115642156431564415645156461564715648156491565015651156521565315654156551565615657156581565915660156611566215663156641566515666156671566815669156701567115672156731567415675156761567715678156791568015681156821568315684156851568615687156881568915690156911569215693156941569515696156971569815699157001570115702157031570415705157061570715708157091571015711157121571315714157151571615717157181571915720157211572215723157241572515726157271572815729157301573115732157331573415735157361573715738157391574015741157421574315744157451574615747157481574915750157511575215753157541575515756157571575815759157601576115762157631576415765157661576715768157691577015771157721577315774157751577615777157781577915780157811578215783157841578515786157871578815789157901579115792157931579415795157961579715798157991580015801158021580315804158051580615807158081580915810158111581215813158141581515816158171581815819158201582115822158231582415825158261582715828158291583015831158321583315834158351583615837158381583915840158411584215843158441584515846158471584815849158501585115852158531585415855158561585715858158591586015861158621586315864158651586615867158681586915870158711587215873158741587515876158771587815879158801588115882158831588415885158861588715888158891589015891158921589315894158951589615897158981589915900159011590215903159041590515906159071590815909159101591115912159131591415915159161591715918159191592015921159221592315924159251592615927159281592915930159311593215933159341593515936159371593815939159401594115942159431594415945159461594715948159491595015951159521595315954159551595615957159581595915960159611596215963159641596515966159671596815969159701597115972159731597415975159761597715978159791598015981159821598315984159851598615987159881598915990159911599215993159941599515996159971599815999160001600116002160031600416005160061600716008160091601016011160121601316014160151601616017160181601916020160211602216023160241602516026160271602816029160301603116032160331603416035160361603716038160391604016041160421604316044160451604616047160481604916050160511605216053160541605516056160571605816059160601606116062160631606416065160661606716068160691607016071160721607316074160751607616077160781607916080160811608216083160841608516086160871608816089160901609116092160931609416095160961609716098160991610016101161021610316104161051610616107161081610916110161111611216113161141611516116161171611816119161201612116122161231612416125161261612716128161291613016131161321613316134161351613616137161381613916140161411614216143161441614516146161471614816149161501615116152161531615416155161561615716158161591616016161161621616316164161651616616167161681616916170161711617216173161741617516176161771617816179161801618116182161831618416185161861618716188161891619016191161921619316194161951619616197161981619916200162011620216203162041620516206162071620816209162101621116212162131621416215162161621716218162191622016221162221622316224162251622616227162281622916230162311623216233162341623516236162371623816239162401624116242162431624416245162461624716248162491625016251162521625316254162551625616257162581625916260162611626216263162641626516266162671626816269162701627116272162731627416275162761627716278162791628016281162821628316284162851628616287162881628916290162911629216293162941629516296162971629816299163001630116302163031630416305163061630716308163091631016311163121631316314163151631616317163181631916320163211632216323163241632516326163271632816329163301633116332163331633416335163361633716338163391634016341163421634316344163451634616347163481634916350163511635216353163541635516356163571635816359163601636116362163631636416365163661636716368163691637016371163721637316374163751637616377163781637916380163811638216383163841638516386163871638816389163901639116392163931639416395163961639716398163991640016401164021640316404164051640616407164081640916410164111641216413164141641516416164171641816419164201642116422164231642416425164261642716428164291643016431164321643316434164351643616437164381643916440164411644216443164441644516446164471644816449164501645116452164531645416455164561645716458164591646016461164621646316464164651646616467164681646916470164711647216473164741647516476164771647816479164801648116482164831648416485164861648716488164891649016491164921649316494164951649616497164981649916500165011650216503165041650516506165071650816509165101651116512165131651416515165161651716518165191652016521165221652316524165251652616527165281652916530165311653216533165341653516536165371653816539165401654116542165431654416545165461654716548165491655016551165521655316554165551655616557165581655916560165611656216563165641656516566165671656816569165701657116572165731657416575165761657716578165791658016581165821658316584165851658616587165881658916590165911659216593165941659516596165971659816599166001660116602166031660416605166061660716608166091661016611166121661316614166151661616617166181661916620166211662216623166241662516626166271662816629166301663116632166331663416635166361663716638166391664016641166421664316644166451664616647166481664916650166511665216653166541665516656166571665816659166601666116662166631666416665166661666716668166691667016671166721667316674166751667616677166781667916680166811668216683166841668516686166871668816689166901669116692166931669416695166961669716698166991670016701167021670316704167051670616707167081670916710167111671216713167141671516716167171671816719167201672116722167231672416725167261672716728167291673016731167321673316734167351673616737167381673916740167411674216743167441674516746167471674816749167501675116752167531675416755167561675716758167591676016761167621676316764167651676616767167681676916770167711677216773167741677516776167771677816779167801678116782167831678416785167861678716788167891679016791167921679316794167951679616797167981679916800168011680216803168041680516806168071680816809168101681116812168131681416815168161681716818168191682016821168221682316824168251682616827168281682916830168311683216833168341683516836168371683816839168401684116842168431684416845168461684716848168491685016851168521685316854168551685616857168581685916860168611686216863168641686516866168671686816869168701687116872168731687416875168761687716878168791688016881168821688316884168851688616887168881688916890168911689216893168941689516896168971689816899169001690116902169031690416905169061690716908169091691016911169121691316914169151691616917169181691916920169211692216923169241692516926169271692816929169301693116932169331693416935169361693716938169391694016941169421694316944169451694616947169481694916950169511695216953169541695516956169571695816959169601696116962169631696416965169661696716968169691697016971169721697316974169751697616977169781697916980169811698216983169841698516986169871698816989169901699116992169931699416995169961699716998169991700017001170021700317004170051700617007170081700917010170111701217013170141701517016170171701817019170201702117022170231702417025170261702717028170291703017031170321703317034170351703617037170381703917040170411704217043170441704517046170471704817049170501705117052170531705417055170561705717058170591706017061170621706317064170651706617067170681706917070170711707217073170741707517076170771707817079170801708117082170831708417085170861708717088170891709017091170921709317094170951709617097170981709917100171011710217103171041710517106171071710817109171101711117112171131711417115171161711717118171191712017121171221712317124171251712617127171281712917130171311713217133171341713517136171371713817139171401714117142171431714417145171461714717148171491715017151171521715317154171551715617157171581715917160171611716217163171641716517166171671716817169171701717117172171731717417175171761717717178171791718017181171821718317184171851718617187171881718917190171911719217193171941719517196171971719817199172001720117202172031720417205172061720717208172091721017211172121721317214172151721617217172181721917220172211722217223172241722517226172271722817229172301723117232172331723417235172361723717238172391724017241172421724317244172451724617247172481724917250172511725217253172541725517256172571725817259172601726117262172631726417265172661726717268172691727017271172721727317274172751727617277172781727917280172811728217283172841728517286172871728817289172901729117292172931729417295172961729717298172991730017301173021730317304173051730617307173081730917310173111731217313173141731517316173171731817319173201732117322173231732417325173261732717328173291733017331173321733317334173351733617337173381733917340173411734217343173441734517346173471734817349173501735117352173531735417355173561735717358173591736017361173621736317364173651736617367173681736917370173711737217373173741737517376173771737817379173801738117382173831738417385173861738717388173891739017391173921739317394173951739617397173981739917400174011740217403174041740517406174071740817409174101741117412174131741417415174161741717418174191742017421174221742317424174251742617427174281742917430174311743217433174341743517436174371743817439174401744117442174431744417445174461744717448174491745017451174521745317454174551745617457174581745917460174611746217463174641746517466174671746817469174701747117472174731747417475174761747717478174791748017481174821748317484174851748617487174881748917490174911749217493174941749517496174971749817499175001750117502175031750417505175061750717508175091751017511175121751317514175151751617517175181751917520175211752217523175241752517526175271752817529175301753117532175331753417535175361753717538175391754017541175421754317544175451754617547175481754917550175511755217553175541755517556175571755817559175601756117562175631756417565175661756717568175691757017571175721757317574175751757617577175781757917580175811758217583175841758517586175871758817589175901759117592175931759417595175961759717598175991760017601176021760317604176051760617607176081760917610176111761217613176141761517616176171761817619176201762117622176231762417625176261762717628176291763017631176321763317634176351763617637176381763917640176411764217643176441764517646176471764817649176501765117652176531765417655176561765717658176591766017661176621766317664176651766617667176681766917670176711767217673176741767517676176771767817679176801768117682176831768417685176861768717688176891769017691176921769317694176951769617697176981769917700177011770217703177041770517706177071770817709177101771117712177131771417715177161771717718177191772017721177221772317724177251772617727177281772917730177311773217733177341773517736177371773817739177401774117742177431774417745177461774717748177491775017751177521775317754177551775617757177581775917760177611776217763177641776517766177671776817769177701777117772177731777417775177761777717778177791778017781177821778317784177851778617787177881778917790177911779217793177941779517796177971779817799178001780117802178031780417805178061780717808178091781017811178121781317814178151781617817178181781917820178211782217823178241782517826178271782817829178301783117832178331783417835178361783717838178391784017841178421784317844178451784617847178481784917850178511785217853178541785517856178571785817859178601786117862178631786417865178661786717868178691787017871178721787317874178751787617877178781787917880178811788217883178841788517886178871788817889178901789117892178931789417895178961789717898178991790017901179021790317904179051790617907179081790917910179111791217913179141791517916179171791817919179201792117922179231792417925179261792717928179291793017931179321793317934179351793617937179381793917940179411794217943179441794517946179471794817949179501795117952179531795417955179561795717958179591796017961179621796317964179651796617967179681796917970179711797217973179741797517976179771797817979179801798117982179831798417985179861798717988179891799017991179921799317994179951799617997179981799918000180011800218003180041800518006180071800818009180101801118012180131801418015180161801718018180191802018021180221802318024180251802618027180281802918030180311803218033180341803518036180371803818039180401804118042180431804418045180461804718048180491805018051180521805318054180551805618057180581805918060180611806218063180641806518066180671806818069180701807118072180731807418075180761807718078180791808018081180821808318084180851808618087180881808918090180911809218093180941809518096180971809818099181001810118102181031810418105181061810718108181091811018111181121811318114181151811618117181181811918120181211812218123181241812518126181271812818129181301813118132181331813418135181361813718138181391814018141181421814318144181451814618147181481814918150181511815218153181541815518156181571815818159181601816118162181631816418165181661816718168181691817018171181721817318174181751817618177181781817918180181811818218183181841818518186181871818818189181901819118192181931819418195181961819718198181991820018201182021820318204182051820618207182081820918210182111821218213182141821518216182171821818219182201822118222182231822418225182261822718228182291823018231182321823318234182351823618237182381823918240182411824218243182441824518246182471824818249182501825118252182531825418255182561825718258182591826018261182621826318264182651826618267182681826918270182711827218273182741827518276182771827818279182801828118282182831828418285182861828718288182891829018291182921829318294182951829618297182981829918300183011830218303183041830518306183071830818309183101831118312183131831418315183161831718318183191832018321183221832318324183251832618327183281832918330183311833218333183341833518336183371833818339183401834118342183431834418345183461834718348183491835018351183521835318354183551835618357183581835918360183611836218363183641836518366183671836818369183701837118372183731837418375183761837718378183791838018381183821838318384183851838618387183881838918390183911839218393183941839518396183971839818399184001840118402184031840418405184061840718408184091841018411184121841318414184151841618417184181841918420184211842218423184241842518426184271842818429184301843118432184331843418435184361843718438184391844018441184421844318444184451844618447184481844918450184511845218453184541845518456184571845818459184601846118462184631846418465184661846718468184691847018471184721847318474184751847618477184781847918480184811848218483184841848518486184871848818489184901849118492184931849418495184961849718498184991850018501185021850318504185051850618507185081850918510185111851218513185141851518516185171851818519185201852118522185231852418525185261852718528185291853018531185321853318534185351853618537185381853918540185411854218543185441854518546185471854818549185501855118552185531855418555185561855718558185591856018561185621856318564185651856618567185681856918570185711857218573185741857518576185771857818579185801858118582185831858418585185861858718588185891859018591185921859318594185951859618597185981859918600186011860218603186041860518606186071860818609186101861118612186131861418615186161861718618186191862018621186221862318624186251862618627186281862918630186311863218633186341863518636186371863818639186401864118642186431864418645186461864718648186491865018651186521865318654186551865618657186581865918660186611866218663186641866518666186671866818669186701867118672186731867418675186761867718678186791868018681186821868318684186851868618687186881868918690186911869218693186941869518696186971869818699187001870118702187031870418705187061870718708187091871018711187121871318714187151871618717187181871918720187211872218723187241872518726187271872818729187301873118732187331873418735187361873718738187391874018741187421874318744187451874618747187481874918750187511875218753187541875518756187571875818759187601876118762187631876418765187661876718768187691877018771187721877318774187751877618777187781877918780187811878218783187841878518786187871878818789187901879118792187931879418795187961879718798187991880018801188021880318804188051880618807188081880918810188111881218813188141881518816188171881818819188201882118822188231882418825188261882718828188291883018831188321883318834188351883618837188381883918840188411884218843188441884518846188471884818849188501885118852188531885418855188561885718858188591886018861188621886318864188651886618867188681886918870188711887218873188741887518876188771887818879188801888118882188831888418885188861888718888188891889018891188921889318894188951889618897188981889918900189011890218903189041890518906189071890818909189101891118912189131891418915189161891718918189191892018921189221892318924189251892618927189281892918930189311893218933189341893518936189371893818939189401894118942189431894418945189461894718948189491895018951189521895318954189551895618957189581895918960189611896218963189641896518966189671896818969189701897118972189731897418975189761897718978189791898018981189821898318984189851898618987189881898918990189911899218993189941899518996189971899818999190001900119002190031900419005190061900719008190091901019011190121901319014190151901619017190181901919020190211902219023190241902519026190271902819029190301903119032190331903419035190361903719038190391904019041190421904319044190451904619047190481904919050190511905219053190541905519056190571905819059190601906119062190631906419065190661906719068190691907019071190721907319074190751907619077190781907919080190811908219083190841908519086190871908819089190901909119092190931909419095190961909719098190991910019101191021910319104191051910619107191081910919110191111911219113191141911519116191171911819119191201912119122191231912419125191261912719128191291913019131191321913319134191351913619137191381913919140191411914219143191441914519146191471914819149191501915119152191531915419155191561915719158191591916019161191621916319164191651916619167191681916919170191711917219173191741917519176191771917819179191801918119182191831918419185191861918719188191891919019191191921919319194191951919619197191981919919200192011920219203192041920519206192071920819209192101921119212192131921419215192161921719218192191922019221192221922319224192251922619227192281922919230192311923219233192341923519236192371923819239192401924119242192431924419245192461924719248192491925019251192521925319254192551925619257192581925919260192611926219263192641926519266192671926819269192701927119272192731927419275192761927719278192791928019281192821928319284192851928619287192881928919290192911929219293192941929519296192971929819299193001930119302193031930419305193061930719308193091931019311193121931319314193151931619317193181931919320193211932219323193241932519326193271932819329193301933119332193331933419335193361933719338193391934019341193421934319344193451934619347193481934919350193511935219353193541935519356193571935819359193601936119362193631936419365193661936719368193691937019371193721937319374193751937619377193781937919380193811938219383193841938519386193871938819389193901939119392193931939419395193961939719398193991940019401194021940319404194051940619407194081940919410194111941219413194141941519416194171941819419194201942119422194231942419425194261942719428194291943019431194321943319434194351943619437194381943919440194411944219443194441944519446194471944819449194501945119452194531945419455194561945719458194591946019461194621946319464194651946619467194681946919470194711947219473194741947519476194771947819479194801948119482194831948419485194861948719488194891949019491194921949319494194951949619497194981949919500195011950219503195041950519506195071950819509195101951119512195131951419515195161951719518195191952019521195221952319524195251952619527195281952919530195311953219533195341953519536195371953819539195401954119542195431954419545195461954719548195491955019551195521955319554195551955619557195581955919560195611956219563195641956519566195671956819569195701957119572195731957419575195761957719578195791958019581195821958319584195851958619587195881958919590195911959219593195941959519596195971959819599196001960119602196031960419605196061960719608196091961019611196121961319614196151961619617196181961919620196211962219623196241962519626196271962819629196301963119632196331963419635196361963719638196391964019641196421964319644196451964619647196481964919650196511965219653196541965519656196571965819659196601966119662196631966419665196661966719668196691967019671196721967319674196751967619677196781967919680196811968219683196841968519686196871968819689196901969119692196931969419695196961969719698196991970019701197021970319704197051970619707197081970919710197111971219713197141971519716197171971819719197201972119722197231972419725197261972719728197291973019731197321973319734197351973619737197381973919740197411974219743197441974519746197471974819749197501975119752197531975419755197561975719758197591976019761197621976319764197651976619767197681976919770197711977219773197741977519776197771977819779197801978119782197831978419785197861978719788197891979019791197921979319794197951979619797197981979919800198011980219803198041980519806198071980819809198101981119812198131981419815198161981719818198191982019821198221982319824198251982619827198281982919830198311983219833198341983519836198371983819839198401984119842198431984419845198461984719848198491985019851198521985319854198551985619857198581985919860198611986219863198641986519866198671986819869198701987119872198731987419875198761987719878198791988019881198821988319884198851988619887198881988919890198911989219893198941989519896198971989819899199001990119902199031990419905199061990719908199091991019911199121991319914199151991619917199181991919920199211992219923199241992519926199271992819929199301993119932199331993419935199361993719938199391994019941199421994319944199451994619947199481994919950199511995219953199541995519956199571995819959199601996119962199631996419965199661996719968199691997019971199721997319974199751997619977199781997919980199811998219983199841998519986199871998819989199901999119992199931999419995199961999719998199992000020001200022000320004200052000620007200082000920010200112001220013200142001520016200172001820019200202002120022200232002420025200262002720028200292003020031200322003320034200352003620037200382003920040200412004220043200442004520046200472004820049200502005120052200532005420055200562005720058200592006020061200622006320064200652006620067200682006920070200712007220073200742007520076200772007820079200802008120082200832008420085200862008720088200892009020091200922009320094200952009620097200982009920100201012010220103201042010520106201072010820109201102011120112201132011420115201162011720118201192012020121201222012320124201252012620127201282012920130201312013220133201342013520136201372013820139201402014120142201432014420145201462014720148201492015020151201522015320154201552015620157201582015920160201612016220163201642016520166201672016820169201702017120172201732017420175201762017720178201792018020181201822018320184201852018620187201882018920190201912019220193201942019520196201972019820199202002020120202202032020420205202062020720208202092021020211202122021320214202152021620217202182021920220202212022220223202242022520226202272022820229202302023120232202332023420235202362023720238202392024020241202422024320244202452024620247202482024920250202512025220253202542025520256202572025820259202602026120262202632026420265202662026720268202692027020271202722027320274202752027620277202782027920280202812028220283202842028520286202872028820289202902029120292202932029420295202962029720298202992030020301203022030320304203052030620307203082030920310203112031220313203142031520316203172031820319203202032120322203232032420325203262032720328203292033020331203322033320334203352033620337203382033920340203412034220343203442034520346203472034820349203502035120352203532035420355203562035720358203592036020361203622036320364203652036620367203682036920370203712037220373203742037520376203772037820379203802038120382203832038420385203862038720388203892039020391203922039320394203952039620397203982039920400204012040220403204042040520406204072040820409204102041120412204132041420415204162041720418204192042020421204222042320424204252042620427204282042920430204312043220433204342043520436204372043820439204402044120442204432044420445204462044720448204492045020451204522045320454204552045620457204582045920460204612046220463204642046520466204672046820469204702047120472204732047420475204762047720478204792048020481204822048320484204852048620487204882048920490204912049220493204942049520496204972049820499205002050120502205032050420505205062050720508205092051020511205122051320514205152051620517205182051920520205212052220523205242052520526205272052820529205302053120532205332053420535205362053720538205392054020541205422054320544205452054620547205482054920550205512055220553205542055520556205572055820559205602056120562205632056420565205662056720568205692057020571205722057320574205752057620577205782057920580205812058220583205842058520586205872058820589205902059120592205932059420595205962059720598205992060020601206022060320604206052060620607206082060920610206112061220613206142061520616206172061820619206202062120622206232062420625206262062720628206292063020631206322063320634206352063620637206382063920640206412064220643206442064520646206472064820649206502065120652206532065420655206562065720658206592066020661206622066320664206652066620667206682066920670206712067220673206742067520676206772067820679206802068120682206832068420685206862068720688206892069020691206922069320694206952069620697206982069920700207012070220703207042070520706207072070820709207102071120712207132071420715207162071720718207192072020721207222072320724207252072620727207282072920730207312073220733207342073520736207372073820739207402074120742207432074420745207462074720748207492075020751207522075320754207552075620757207582075920760207612076220763207642076520766207672076820769207702077120772207732077420775207762077720778207792078020781207822078320784207852078620787207882078920790207912079220793207942079520796207972079820799208002080120802208032080420805208062080720808208092081020811208122081320814208152081620817208182081920820208212082220823208242082520826208272082820829208302083120832208332083420835208362083720838208392084020841208422084320844208452084620847208482084920850208512085220853208542085520856208572085820859208602086120862208632086420865208662086720868208692087020871208722087320874208752087620877208782087920880208812088220883208842088520886208872088820889208902089120892208932089420895208962089720898208992090020901209022090320904209052090620907209082090920910209112091220913209142091520916209172091820919209202092120922209232092420925209262092720928209292093020931209322093320934209352093620937209382093920940209412094220943209442094520946209472094820949209502095120952209532095420955209562095720958209592096020961209622096320964209652096620967209682096920970209712097220973209742097520976209772097820979209802098120982209832098420985209862098720988209892099020991209922099320994209952099620997209982099921000210012100221003210042100521006210072100821009210102101121012210132101421015210162101721018210192102021021210222102321024210252102621027210282102921030210312103221033210342103521036210372103821039210402104121042210432104421045210462104721048210492105021051210522105321054210552105621057210582105921060210612106221063210642106521066210672106821069210702107121072210732107421075210762107721078210792108021081210822108321084210852108621087210882108921090210912109221093210942109521096210972109821099211002110121102211032110421105211062110721108211092111021111211122111321114211152111621117211182111921120211212112221123211242112521126211272112821129211302113121132211332113421135211362113721138211392114021141211422114321144211452114621147211482114921150211512115221153211542115521156211572115821159211602116121162211632116421165211662116721168211692117021171211722117321174211752117621177211782117921180211812118221183211842118521186211872118821189211902119121192211932119421195211962119721198211992120021201212022120321204212052120621207212082120921210212112121221213212142121521216212172121821219212202122121222212232122421225212262122721228212292123021231212322123321234212352123621237212382123921240212412124221243212442124521246212472124821249212502125121252212532125421255212562125721258212592126021261212622126321264212652126621267212682126921270212712127221273212742127521276212772127821279212802128121282212832128421285212862128721288212892129021291212922129321294212952129621297212982129921300213012130221303213042130521306213072130821309213102131121312213132131421315213162131721318213192132021321213222132321324213252132621327213282132921330213312133221333213342133521336213372133821339213402134121342213432134421345213462134721348213492135021351213522135321354213552135621357213582135921360213612136221363213642136521366213672136821369213702137121372213732137421375213762137721378213792138021381213822138321384213852138621387213882138921390213912139221393213942139521396213972139821399214002140121402214032140421405214062140721408214092141021411214122141321414214152141621417214182141921420214212142221423214242142521426214272142821429214302143121432214332143421435214362143721438214392144021441214422144321444214452144621447214482144921450214512145221453214542145521456214572145821459214602146121462214632146421465214662146721468214692147021471214722147321474214752147621477214782147921480214812148221483214842148521486214872148821489214902149121492214932149421495214962149721498214992150021501215022150321504215052150621507215082150921510215112151221513215142151521516215172151821519215202152121522215232152421525215262152721528215292153021531215322153321534215352153621537215382153921540215412154221543215442154521546215472154821549215502155121552215532155421555215562155721558215592156021561215622156321564215652156621567215682156921570215712157221573215742157521576215772157821579215802158121582215832158421585215862158721588215892159021591215922159321594215952159621597215982159921600216012160221603216042160521606216072160821609216102161121612216132161421615216162161721618216192162021621216222162321624216252162621627216282162921630216312163221633216342163521636216372163821639216402164121642216432164421645216462164721648216492165021651216522165321654216552165621657216582165921660216612166221663216642166521666216672166821669216702167121672216732167421675216762167721678216792168021681216822168321684216852168621687216882168921690216912169221693216942169521696216972169821699217002170121702217032170421705217062170721708217092171021711217122171321714217152171621717217182171921720217212172221723217242172521726217272172821729217302173121732217332173421735217362173721738217392174021741217422174321744217452174621747217482174921750217512175221753217542175521756217572175821759217602176121762217632176421765217662176721768217692177021771217722177321774217752177621777217782177921780217812178221783217842178521786217872178821789217902179121792217932179421795217962179721798217992180021801218022180321804218052180621807218082180921810218112181221813218142181521816218172181821819218202182121822218232182421825218262182721828218292183021831218322183321834218352183621837218382183921840218412184221843218442184521846218472184821849218502185121852218532185421855218562185721858218592186021861218622186321864218652186621867218682186921870218712187221873218742187521876218772187821879218802188121882218832188421885218862188721888218892189021891218922189321894218952189621897218982189921900219012190221903219042190521906219072190821909219102191121912219132191421915219162191721918219192192021921219222192321924219252192621927219282192921930219312193221933219342193521936219372193821939219402194121942219432194421945219462194721948219492195021951219522195321954219552195621957219582195921960219612196221963219642196521966219672196821969219702197121972219732197421975219762197721978219792198021981219822198321984219852198621987219882198921990219912199221993219942199521996219972199821999220002200122002220032200422005220062200722008220092201022011220122201322014220152201622017220182201922020220212202222023220242202522026220272202822029220302203122032220332203422035220362203722038220392204022041220422204322044220452204622047220482204922050220512205222053220542205522056220572205822059220602206122062220632206422065220662206722068220692207022071220722207322074220752207622077220782207922080220812208222083220842208522086220872208822089220902209122092220932209422095220962209722098220992210022101221022210322104221052210622107221082210922110221112211222113221142211522116221172211822119221202212122122221232212422125221262212722128221292213022131221322213322134221352213622137221382213922140221412214222143221442214522146221472214822149221502215122152221532215422155221562215722158221592216022161221622216322164221652216622167221682216922170221712217222173221742217522176221772217822179221802218122182221832218422185221862218722188221892219022191221922219322194221952219622197221982219922200222012220222203222042220522206222072220822209222102221122212222132221422215222162221722218222192222022221222222222322224222252222622227222282222922230222312223222233222342223522236222372223822239222402224122242222432224422245222462224722248222492225022251222522225322254222552225622257222582225922260222612226222263222642226522266222672226822269222702227122272222732227422275222762227722278222792228022281222822228322284222852228622287222882228922290222912229222293222942229522296222972229822299223002230122302223032230422305223062230722308223092231022311223122231322314223152231622317223182231922320223212232222323223242232522326223272232822329223302233122332223332233422335223362233722338223392234022341223422234322344223452234622347223482234922350223512235222353223542235522356223572235822359223602236122362223632236422365223662236722368223692237022371223722237322374223752237622377223782237922380223812238222383223842238522386223872238822389223902239122392223932239422395223962239722398223992240022401224022240322404224052240622407224082240922410224112241222413224142241522416224172241822419224202242122422224232242422425224262242722428224292243022431224322243322434224352243622437224382243922440224412244222443224442244522446224472244822449224502245122452224532245422455224562245722458224592246022461224622246322464224652246622467224682246922470224712247222473224742247522476224772247822479224802248122482224832248422485224862248722488224892249022491224922249322494224952249622497224982249922500225012250222503225042250522506225072250822509225102251122512225132251422515225162251722518225192252022521225222252322524225252252622527225282252922530225312253222533225342253522536225372253822539225402254122542225432254422545225462254722548225492255022551225522255322554225552255622557225582255922560225612256222563225642256522566225672256822569225702257122572225732257422575225762257722578225792258022581225822258322584225852258622587225882258922590225912259222593225942259522596225972259822599226002260122602226032260422605226062260722608226092261022611226122261322614226152261622617226182261922620226212262222623226242262522626226272262822629226302263122632226332263422635226362263722638226392264022641226422264322644226452264622647226482264922650226512265222653226542265522656226572265822659226602266122662226632266422665226662266722668226692267022671226722267322674226752267622677226782267922680226812268222683226842268522686226872268822689226902269122692226932269422695226962269722698226992270022701227022270322704227052270622707227082270922710227112271222713227142271522716227172271822719227202272122722227232272422725227262272722728227292273022731227322273322734227352273622737227382273922740227412274222743227442274522746227472274822749227502275122752227532275422755227562275722758227592276022761227622276322764227652276622767227682276922770227712277222773227742277522776227772277822779227802278122782227832278422785227862278722788227892279022791227922279322794227952279622797227982279922800228012280222803228042280522806228072280822809228102281122812228132281422815228162281722818228192282022821228222282322824228252282622827228282282922830228312283222833228342283522836228372283822839228402284122842228432284422845228462284722848228492285022851228522285322854228552285622857228582285922860228612286222863228642286522866228672286822869228702287122872228732287422875228762287722878228792288022881228822288322884228852288622887228882288922890228912289222893228942289522896228972289822899229002290122902229032290422905229062290722908229092291022911229122291322914229152291622917229182291922920229212292222923229242292522926229272292822929229302293122932229332293422935229362293722938229392294022941229422294322944229452294622947229482294922950229512295222953229542295522956229572295822959229602296122962229632296422965229662296722968229692297022971229722297322974229752297622977229782297922980229812298222983229842298522986229872298822989229902299122992229932299422995229962299722998229992300023001230022300323004230052300623007230082300923010230112301223013230142301523016230172301823019230202302123022230232302423025230262302723028230292303023031230322303323034230352303623037230382303923040230412304223043230442304523046230472304823049230502305123052230532305423055230562305723058230592306023061230622306323064230652306623067230682306923070230712307223073230742307523076230772307823079230802308123082230832308423085230862308723088230892309023091230922309323094230952309623097230982309923100231012310223103231042310523106231072310823109231102311123112231132311423115231162311723118231192312023121231222312323124231252312623127231282312923130231312313223133231342313523136231372313823139231402314123142231432314423145231462314723148231492315023151231522315323154231552315623157231582315923160231612316223163231642316523166231672316823169231702317123172231732317423175231762317723178231792318023181231822318323184231852318623187231882318923190231912319223193231942319523196231972319823199232002320123202232032320423205232062320723208232092321023211232122321323214232152321623217232182321923220232212322223223232242322523226232272322823229232302323123232232332323423235232362323723238232392324023241232422324323244232452324623247232482324923250232512325223253232542325523256232572325823259232602326123262232632326423265232662326723268232692327023271232722327323274232752327623277232782327923280232812328223283232842328523286232872328823289232902329123292232932329423295232962329723298232992330023301233022330323304233052330623307233082330923310233112331223313233142331523316233172331823319233202332123322233232332423325233262332723328233292333023331233322333323334233352333623337233382333923340233412334223343233442334523346233472334823349233502335123352233532335423355233562335723358233592336023361233622336323364233652336623367233682336923370233712337223373233742337523376233772337823379233802338123382233832338423385233862338723388233892339023391233922339323394233952339623397233982339923400234012340223403234042340523406234072340823409234102341123412234132341423415234162341723418234192342023421234222342323424234252342623427234282342923430234312343223433234342343523436234372343823439234402344123442234432344423445234462344723448234492345023451234522345323454234552345623457234582345923460234612346223463234642346523466234672346823469234702347123472234732347423475234762347723478234792348023481234822348323484234852348623487234882348923490234912349223493234942349523496234972349823499235002350123502235032350423505235062350723508235092351023511235122351323514235152351623517235182351923520235212352223523235242352523526235272352823529235302353123532235332353423535235362353723538235392354023541235422354323544235452354623547235482354923550235512355223553235542355523556235572355823559235602356123562235632356423565235662356723568235692357023571235722357323574235752357623577235782357923580235812358223583235842358523586235872358823589235902359123592235932359423595235962359723598235992360023601236022360323604236052360623607236082360923610236112361223613236142361523616236172361823619236202362123622236232362423625236262362723628236292363023631236322363323634236352363623637236382363923640236412364223643236442364523646236472364823649236502365123652236532365423655236562365723658236592366023661236622366323664236652366623667236682366923670236712367223673236742367523676236772367823679236802368123682236832368423685236862368723688236892369023691236922369323694236952369623697236982369923700237012370223703237042370523706237072370823709237102371123712237132371423715237162371723718237192372023721237222372323724237252372623727237282372923730237312373223733237342373523736237372373823739237402374123742237432374423745237462374723748237492375023751237522375323754237552375623757237582375923760237612376223763237642376523766237672376823769237702377123772237732377423775237762377723778237792378023781237822378323784237852378623787237882378923790237912379223793237942379523796237972379823799238002380123802238032380423805238062380723808238092381023811238122381323814238152381623817238182381923820238212382223823238242382523826238272382823829238302383123832238332383423835238362383723838238392384023841238422384323844238452384623847238482384923850238512385223853238542385523856238572385823859238602386123862238632386423865238662386723868238692387023871238722387323874238752387623877238782387923880238812388223883238842388523886238872388823889238902389123892238932389423895238962389723898238992390023901239022390323904239052390623907239082390923910239112391223913239142391523916239172391823919239202392123922239232392423925239262392723928239292393023931239322393323934239352393623937239382393923940239412394223943239442394523946239472394823949239502395123952239532395423955239562395723958239592396023961239622396323964239652396623967239682396923970239712397223973239742397523976239772397823979239802398123982239832398423985239862398723988239892399023991239922399323994239952399623997239982399924000240012400224003240042400524006240072400824009240102401124012240132401424015240162401724018240192402024021240222402324024240252402624027240282402924030240312403224033240342403524036240372403824039240402404124042240432404424045240462404724048240492405024051240522405324054240552405624057240582405924060240612406224063240642406524066240672406824069240702407124072240732407424075240762407724078240792408024081240822408324084240852408624087240882408924090240912409224093240942409524096240972409824099241002410124102241032410424105241062410724108241092411024111241122411324114241152411624117241182411924120241212412224123241242412524126241272412824129241302413124132241332413424135241362413724138241392414024141241422414324144241452414624147241482414924150241512415224153241542415524156241572415824159241602416124162241632416424165241662416724168241692417024171241722417324174241752417624177241782417924180241812418224183241842418524186241872418824189241902419124192241932419424195241962419724198241992420024201242022420324204242052420624207242082420924210242112421224213242142421524216242172421824219242202422124222242232422424225242262422724228242292423024231242322423324234242352423624237242382423924240242412424224243242442424524246242472424824249242502425124252242532425424255242562425724258242592426024261242622426324264242652426624267242682426924270242712427224273242742427524276242772427824279242802428124282242832428424285242862428724288242892429024291242922429324294242952429624297242982429924300243012430224303243042430524306243072430824309243102431124312243132431424315243162431724318243192432024321243222432324324243252432624327243282432924330243312433224333243342433524336243372433824339243402434124342243432434424345243462434724348243492435024351243522435324354243552435624357243582435924360243612436224363243642436524366243672436824369243702437124372243732437424375243762437724378243792438024381243822438324384243852438624387243882438924390243912439224393243942439524396243972439824399244002440124402244032440424405244062440724408244092441024411244122441324414244152441624417244182441924420244212442224423244242442524426244272442824429244302443124432244332443424435244362443724438244392444024441244422444324444244452444624447244482444924450244512445224453244542445524456244572445824459244602446124462244632446424465244662446724468244692447024471244722447324474244752447624477244782447924480244812448224483244842448524486244872448824489244902449124492244932449424495244962449724498244992450024501245022450324504245052450624507245082450924510245112451224513245142451524516245172451824519245202452124522245232452424525245262452724528245292453024531245322453324534245352453624537245382453924540245412454224543245442454524546245472454824549245502455124552245532455424555245562455724558245592456024561245622456324564245652456624567245682456924570245712457224573245742457524576245772457824579245802458124582245832458424585245862458724588245892459024591245922459324594245952459624597245982459924600246012460224603246042460524606246072460824609246102461124612246132461424615246162461724618246192462024621246222462324624246252462624627246282462924630246312463224633246342463524636246372463824639246402464124642246432464424645246462464724648246492465024651246522465324654246552465624657246582465924660246612466224663246642466524666246672466824669246702467124672246732467424675246762467724678246792468024681246822468324684246852468624687246882468924690246912469224693246942469524696246972469824699247002470124702247032470424705247062470724708247092471024711247122471324714247152471624717247182471924720247212472224723247242472524726247272472824729247302473124732247332473424735247362473724738247392474024741247422474324744247452474624747247482474924750247512475224753247542475524756247572475824759247602476124762247632476424765247662476724768247692477024771247722477324774247752477624777247782477924780247812478224783247842478524786247872478824789247902479124792247932479424795247962479724798247992480024801248022480324804248052480624807248082480924810248112481224813248142481524816248172481824819248202482124822248232482424825248262482724828248292483024831248322483324834248352483624837248382483924840248412484224843248442484524846248472484824849248502485124852248532485424855248562485724858248592486024861248622486324864248652486624867248682486924870248712487224873248742487524876248772487824879248802488124882248832488424885248862488724888248892489024891248922489324894248952489624897248982489924900249012490224903249042490524906249072490824909249102491124912249132491424915249162491724918249192492024921249222492324924249252492624927249282492924930249312493224933249342493524936249372493824939249402494124942249432494424945249462494724948249492495024951249522495324954249552495624957249582495924960249612496224963249642496524966249672496824969249702497124972249732497424975249762497724978249792498024981249822498324984249852498624987249882498924990249912499224993249942499524996249972499824999250002500125002250032500425005250062500725008250092501025011250122501325014250152501625017250182501925020250212502225023250242502525026250272502825029250302503125032250332503425035250362503725038250392504025041250422504325044250452504625047250482504925050250512505225053250542505525056250572505825059250602506125062250632506425065250662506725068250692507025071250722507325074250752507625077250782507925080250812508225083250842508525086250872508825089250902509125092250932509425095250962509725098250992510025101251022510325104251052510625107251082510925110251112511225113251142511525116251172511825119251202512125122251232512425125251262512725128251292513025131251322513325134251352513625137251382513925140251412514225143251442514525146251472514825149251502515125152251532515425155251562515725158251592516025161251622516325164251652516625167251682516925170251712517225173251742517525176251772517825179251802518125182251832518425185251862518725188251892519025191251922519325194251952519625197251982519925200252012520225203252042520525206252072520825209252102521125212252132521425215252162521725218252192522025221252222522325224252252522625227252282522925230252312523225233252342523525236252372523825239252402524125242252432524425245252462524725248252492525025251252522525325254252552525625257252582525925260252612526225263252642526525266252672526825269252702527125272252732527425275252762527725278252792528025281252822528325284252852528625287252882528925290252912529225293252942529525296252972529825299253002530125302253032530425305253062530725308253092531025311253122531325314253152531625317253182531925320253212532225323253242532525326253272532825329253302533125332253332533425335253362533725338253392534025341253422534325344253452534625347253482534925350253512535225353253542535525356253572535825359253602536125362253632536425365253662536725368253692537025371253722537325374253752537625377253782537925380253812538225383253842538525386253872538825389253902539125392253932539425395253962539725398253992540025401254022540325404254052540625407254082540925410254112541225413254142541525416254172541825419254202542125422254232542425425254262542725428254292543025431254322543325434254352543625437254382543925440254412544225443254442544525446254472544825449254502545125452254532545425455254562545725458254592546025461254622546325464254652546625467254682546925470254712547225473254742547525476254772547825479254802548125482254832548425485254862548725488254892549025491254922549325494254952549625497254982549925500255012550225503255042550525506255072550825509255102551125512255132551425515255162551725518255192552025521255222552325524255252552625527255282552925530255312553225533255342553525536255372553825539255402554125542255432554425545255462554725548255492555025551255522555325554255552555625557255582555925560255612556225563255642556525566255672556825569255702557125572255732557425575255762557725578255792558025581255822558325584255852558625587255882558925590255912559225593255942559525596255972559825599256002560125602256032560425605256062560725608256092561025611256122561325614256152561625617256182561925620256212562225623256242562525626256272562825629256302563125632256332563425635256362563725638256392564025641256422564325644256452564625647256482564925650256512565225653256542565525656256572565825659256602566125662256632566425665256662566725668256692567025671256722567325674256752567625677256782567925680256812568225683256842568525686256872568825689256902569125692256932569425695256962569725698256992570025701257022570325704257052570625707257082570925710257112571225713257142571525716257172571825719257202572125722257232572425725257262572725728257292573025731257322573325734257352573625737257382573925740257412574225743257442574525746257472574825749257502575125752257532575425755257562575725758257592576025761257622576325764257652576625767257682576925770257712577225773257742577525776257772577825779257802578125782257832578425785257862578725788257892579025791257922579325794257952579625797257982579925800258012580225803258042580525806258072580825809258102581125812258132581425815258162581725818258192582025821258222582325824258252582625827258282582925830258312583225833258342583525836258372583825839258402584125842258432584425845258462584725848258492585025851258522585325854258552585625857258582585925860258612586225863258642586525866258672586825869258702587125872258732587425875258762587725878258792588025881258822588325884258852588625887258882588925890258912589225893258942589525896258972589825899259002590125902259032590425905259062590725908259092591025911259122591325914259152591625917259182591925920259212592225923259242592525926259272592825929259302593125932259332593425935259362593725938259392594025941259422594325944259452594625947259482594925950259512595225953259542595525956259572595825959259602596125962259632596425965259662596725968259692597025971259722597325974259752597625977259782597925980259812598225983259842598525986259872598825989259902599125992259932599425995259962599725998259992600026001260022600326004260052600626007260082600926010260112601226013260142601526016260172601826019260202602126022260232602426025260262602726028260292603026031260322603326034260352603626037260382603926040260412604226043260442604526046260472604826049260502605126052260532605426055260562605726058260592606026061260622606326064260652606626067260682606926070260712607226073260742607526076260772607826079260802608126082260832608426085260862608726088260892609026091260922609326094260952609626097260982609926100261012610226103261042610526106261072610826109261102611126112261132611426115261162611726118261192612026121261222612326124261252612626127261282612926130261312613226133261342613526136261372613826139261402614126142261432614426145261462614726148261492615026151261522615326154261552615626157261582615926160261612616226163261642616526166261672616826169261702617126172261732617426175261762617726178261792618026181261822618326184261852618626187261882618926190261912619226193261942619526196261972619826199262002620126202262032620426205262062620726208262092621026211262122621326214262152621626217262182621926220262212622226223262242622526226262272622826229262302623126232262332623426235262362623726238262392624026241262422624326244262452624626247262482624926250262512625226253262542625526256262572625826259262602626126262262632626426265262662626726268262692627026271262722627326274262752627626277262782627926280262812628226283262842628526286262872628826289262902629126292262932629426295262962629726298262992630026301263022630326304263052630626307263082630926310263112631226313263142631526316263172631826319263202632126322263232632426325263262632726328263292633026331263322633326334263352633626337263382633926340263412634226343263442634526346263472634826349263502635126352263532635426355263562635726358263592636026361263622636326364263652636626367263682636926370263712637226373263742637526376263772637826379263802638126382263832638426385263862638726388263892639026391263922639326394263952639626397263982639926400264012640226403264042640526406264072640826409264102641126412264132641426415264162641726418264192642026421264222642326424264252642626427264282642926430264312643226433264342643526436264372643826439264402644126442264432644426445264462644726448264492645026451264522645326454264552645626457264582645926460264612646226463264642646526466264672646826469264702647126472264732647426475264762647726478264792648026481264822648326484264852648626487264882648926490264912649226493264942649526496264972649826499265002650126502265032650426505265062650726508265092651026511265122651326514265152651626517265182651926520265212652226523265242652526526265272652826529265302653126532265332653426535265362653726538265392654026541265422654326544265452654626547265482654926550265512655226553265542655526556265572655826559265602656126562265632656426565265662656726568265692657026571265722657326574265752657626577265782657926580265812658226583265842658526586265872658826589265902659126592265932659426595265962659726598265992660026601266022660326604266052660626607266082660926610266112661226613266142661526616266172661826619266202662126622266232662426625266262662726628266292663026631266322663326634266352663626637266382663926640266412664226643266442664526646266472664826649266502665126652266532665426655266562665726658266592666026661266622666326664266652666626667266682666926670266712667226673266742667526676266772667826679266802668126682266832668426685266862668726688266892669026691266922669326694266952669626697266982669926700267012670226703267042670526706267072670826709267102671126712267132671426715267162671726718267192672026721267222672326724267252672626727267282672926730267312673226733267342673526736267372673826739267402674126742267432674426745267462674726748267492675026751267522675326754267552675626757267582675926760267612676226763267642676526766267672676826769267702677126772267732677426775267762677726778267792678026781267822678326784267852678626787267882678926790267912679226793267942679526796267972679826799268002680126802268032680426805268062680726808268092681026811268122681326814268152681626817268182681926820268212682226823268242682526826268272682826829268302683126832268332683426835268362683726838268392684026841268422684326844268452684626847268482684926850268512685226853268542685526856268572685826859268602686126862268632686426865268662686726868268692687026871268722687326874268752687626877268782687926880268812688226883268842688526886268872688826889268902689126892268932689426895268962689726898268992690026901269022690326904269052690626907269082690926910269112691226913269142691526916269172691826919269202692126922269232692426925269262692726928269292693026931269322693326934269352693626937269382693926940269412694226943269442694526946269472694826949269502695126952269532695426955269562695726958269592696026961269622696326964269652696626967269682696926970269712697226973269742697526976269772697826979269802698126982269832698426985269862698726988269892699026991269922699326994269952699626997269982699927000270012700227003270042700527006270072700827009270102701127012270132701427015270162701727018270192702027021270222702327024270252702627027270282702927030270312703227033270342703527036270372703827039270402704127042270432704427045270462704727048270492705027051270522705327054270552705627057270582705927060270612706227063270642706527066270672706827069270702707127072270732707427075270762707727078270792708027081270822708327084270852708627087270882708927090270912709227093270942709527096270972709827099271002710127102271032710427105271062710727108271092711027111271122711327114271152711627117271182711927120271212712227123271242712527126271272712827129271302713127132271332713427135271362713727138271392714027141271422714327144271452714627147271482714927150271512715227153271542715527156271572715827159271602716127162271632716427165271662716727168271692717027171271722717327174271752717627177271782717927180271812718227183271842718527186271872718827189271902719127192271932719427195271962719727198271992720027201272022720327204272052720627207272082720927210272112721227213272142721527216272172721827219272202722127222272232722427225272262722727228272292723027231272322723327234272352723627237272382723927240272412724227243272442724527246272472724827249272502725127252272532725427255272562725727258272592726027261272622726327264272652726627267272682726927270272712727227273272742727527276272772727827279272802728127282272832728427285272862728727288272892729027291272922729327294272952729627297272982729927300273012730227303273042730527306273072730827309273102731127312273132731427315273162731727318273192732027321273222732327324273252732627327273282732927330273312733227333273342733527336273372733827339273402734127342273432734427345273462734727348273492735027351273522735327354273552735627357273582735927360273612736227363273642736527366273672736827369273702737127372273732737427375273762737727378273792738027381273822738327384273852738627387273882738927390273912739227393273942739527396273972739827399274002740127402274032740427405274062740727408274092741027411274122741327414274152741627417274182741927420274212742227423274242742527426274272742827429274302743127432274332743427435274362743727438274392744027441274422744327444274452744627447274482744927450274512745227453274542745527456274572745827459274602746127462274632746427465274662746727468274692747027471274722747327474274752747627477274782747927480274812748227483274842748527486274872748827489274902749127492274932749427495274962749727498274992750027501275022750327504275052750627507275082750927510275112751227513275142751527516275172751827519275202752127522275232752427525275262752727528275292753027531275322753327534275352753627537275382753927540275412754227543275442754527546275472754827549275502755127552275532755427555275562755727558275592756027561275622756327564275652756627567275682756927570275712757227573275742757527576275772757827579275802758127582275832758427585275862758727588275892759027591275922759327594275952759627597275982759927600276012760227603276042760527606276072760827609276102761127612276132761427615276162761727618276192762027621276222762327624276252762627627276282762927630276312763227633276342763527636276372763827639276402764127642276432764427645276462764727648276492765027651276522765327654276552765627657276582765927660276612766227663276642766527666276672766827669276702767127672276732767427675276762767727678276792768027681276822768327684276852768627687276882768927690276912769227693276942769527696276972769827699277002770127702277032770427705277062770727708277092771027711277122771327714277152771627717277182771927720277212772227723277242772527726277272772827729277302773127732277332773427735277362773727738277392774027741277422774327744277452774627747277482774927750277512775227753277542775527756277572775827759277602776127762277632776427765277662776727768277692777027771277722777327774277752777627777277782777927780277812778227783277842778527786277872778827789277902779127792277932779427795277962779727798277992780027801278022780327804278052780627807278082780927810278112781227813278142781527816278172781827819278202782127822278232782427825278262782727828278292783027831278322783327834278352783627837278382783927840278412784227843278442784527846278472784827849278502785127852278532785427855278562785727858278592786027861278622786327864278652786627867278682786927870278712787227873278742787527876278772787827879278802788127882278832788427885278862788727888278892789027891278922789327894278952789627897278982789927900279012790227903279042790527906279072790827909279102791127912279132791427915279162791727918279192792027921279222792327924279252792627927279282792927930279312793227933279342793527936279372793827939279402794127942279432794427945279462794727948279492795027951279522795327954279552795627957279582795927960279612796227963279642796527966279672796827969279702797127972279732797427975279762797727978279792798027981279822798327984279852798627987279882798927990279912799227993279942799527996279972799827999280002800128002280032800428005280062800728008280092801028011280122801328014280152801628017280182801928020280212802228023280242802528026280272802828029280302803128032280332803428035280362803728038280392804028041280422804328044280452804628047280482804928050280512805228053280542805528056280572805828059280602806128062280632806428065280662806728068280692807028071280722807328074280752807628077280782807928080280812808228083280842808528086280872808828089280902809128092280932809428095280962809728098280992810028101281022810328104281052810628107281082810928110281112811228113281142811528116281172811828119281202812128122281232812428125281262812728128281292813028131281322813328134281352813628137281382813928140281412814228143281442814528146281472814828149281502815128152281532815428155281562815728158281592816028161281622816328164281652816628167281682816928170281712817228173281742817528176281772817828179281802818128182281832818428185281862818728188281892819028191281922819328194281952819628197281982819928200282012820228203282042820528206282072820828209282102821128212282132821428215282162821728218282192822028221282222822328224282252822628227282282822928230282312823228233282342823528236282372823828239282402824128242282432824428245282462824728248282492825028251282522825328254282552825628257282582825928260282612826228263282642826528266282672826828269282702827128272282732827428275282762827728278282792828028281282822828328284282852828628287282882828928290282912829228293282942829528296282972829828299283002830128302283032830428305283062830728308283092831028311283122831328314283152831628317283182831928320283212832228323283242832528326283272832828329283302833128332283332833428335283362833728338283392834028341283422834328344283452834628347283482834928350283512835228353283542835528356283572835828359283602836128362283632836428365283662836728368283692837028371283722837328374283752837628377283782837928380283812838228383283842838528386283872838828389283902839128392283932839428395283962839728398283992840028401284022840328404284052840628407284082840928410284112841228413284142841528416284172841828419284202842128422284232842428425284262842728428284292843028431284322843328434284352843628437284382843928440284412844228443284442844528446284472844828449284502845128452284532845428455284562845728458284592846028461284622846328464284652846628467284682846928470284712847228473284742847528476284772847828479284802848128482284832848428485284862848728488284892849028491284922849328494284952849628497284982849928500285012850228503285042850528506285072850828509285102851128512285132851428515285162851728518285192852028521285222852328524285252852628527285282852928530285312853228533285342853528536285372853828539285402854128542285432854428545285462854728548285492855028551285522855328554285552855628557285582855928560285612856228563285642856528566285672856828569285702857128572285732857428575285762857728578285792858028581285822858328584285852858628587285882858928590285912859228593285942859528596285972859828599286002860128602286032860428605286062860728608286092861028611286122861328614286152861628617286182861928620286212862228623286242862528626286272862828629286302863128632286332863428635286362863728638286392864028641286422864328644286452864628647286482864928650286512865228653286542865528656286572865828659286602866128662286632866428665286662866728668286692867028671286722867328674286752867628677286782867928680286812868228683286842868528686286872868828689286902869128692286932869428695286962869728698286992870028701287022870328704287052870628707287082870928710287112871228713287142871528716287172871828719287202872128722287232872428725287262872728728287292873028731287322873328734287352873628737287382873928740287412874228743287442874528746287472874828749287502875128752287532875428755287562875728758287592876028761287622876328764287652876628767287682876928770287712877228773287742877528776287772877828779287802878128782287832878428785287862878728788287892879028791287922879328794287952879628797287982879928800288012880228803288042880528806288072880828809288102881128812288132881428815288162881728818288192882028821288222882328824288252882628827288282882928830288312883228833288342883528836288372883828839288402884128842288432884428845288462884728848288492885028851288522885328854288552885628857288582885928860288612886228863288642886528866288672886828869288702887128872288732887428875288762887728878288792888028881288822888328884288852888628887288882888928890288912889228893288942889528896288972889828899289002890128902289032890428905289062890728908289092891028911289122891328914289152891628917289182891928920289212892228923289242892528926289272892828929289302893128932289332893428935289362893728938289392894028941289422894328944289452894628947289482894928950289512895228953289542895528956289572895828959289602896128962289632896428965289662896728968289692897028971289722897328974289752897628977289782897928980289812898228983289842898528986289872898828989289902899128992289932899428995289962899728998289992900029001290022900329004290052900629007290082900929010290112901229013290142901529016290172901829019290202902129022290232902429025290262902729028290292903029031290322903329034290352903629037290382903929040290412904229043290442904529046290472904829049290502905129052290532905429055290562905729058290592906029061290622906329064290652906629067290682906929070290712907229073290742907529076290772907829079290802908129082290832908429085290862908729088290892909029091290922909329094290952909629097290982909929100291012910229103291042910529106291072910829109291102911129112291132911429115291162911729118291192912029121291222912329124291252912629127291282912929130291312913229133291342913529136291372913829139291402914129142291432914429145291462914729148291492915029151291522915329154291552915629157291582915929160291612916229163291642916529166291672916829169291702917129172291732917429175291762917729178291792918029181291822918329184291852918629187291882918929190291912919229193291942919529196291972919829199292002920129202292032920429205292062920729208292092921029211292122921329214292152921629217292182921929220292212922229223292242922529226292272922829229292302923129232292332923429235292362923729238292392924029241292422924329244292452924629247292482924929250292512925229253292542925529256292572925829259292602926129262292632926429265292662926729268292692927029271292722927329274292752927629277292782927929280292812928229283292842928529286292872928829289292902929129292292932929429295292962929729298292992930029301293022930329304293052930629307293082930929310293112931229313293142931529316293172931829319293202932129322293232932429325293262932729328293292933029331293322933329334293352933629337293382933929340293412934229343293442934529346293472934829349293502935129352293532935429355293562935729358293592936029361293622936329364293652936629367293682936929370293712937229373293742937529376293772937829379293802938129382293832938429385293862938729388293892939029391293922939329394293952939629397293982939929400294012940229403294042940529406294072940829409294102941129412294132941429415294162941729418294192942029421294222942329424294252942629427294282942929430294312943229433294342943529436294372943829439294402944129442294432944429445294462944729448294492945029451294522945329454294552945629457294582945929460294612946229463294642946529466294672946829469294702947129472294732947429475294762947729478294792948029481294822948329484294852948629487294882948929490294912949229493294942949529496294972949829499295002950129502295032950429505295062950729508295092951029511295122951329514295152951629517295182951929520295212952229523295242952529526295272952829529295302953129532295332953429535295362953729538295392954029541295422954329544295452954629547295482954929550295512955229553295542955529556295572955829559295602956129562295632956429565295662956729568295692957029571295722957329574295752957629577295782957929580295812958229583295842958529586295872958829589295902959129592295932959429595295962959729598295992960029601296022960329604296052960629607296082960929610296112961229613296142961529616296172961829619296202962129622296232962429625296262962729628296292963029631296322963329634296352963629637296382963929640296412964229643296442964529646296472964829649296502965129652296532965429655296562965729658296592966029661296622966329664296652966629667296682966929670296712967229673296742967529676296772967829679296802968129682296832968429685296862968729688296892969029691296922969329694296952969629697296982969929700297012970229703297042970529706297072970829709297102971129712297132971429715297162971729718297192972029721297222972329724297252972629727297282972929730297312973229733297342973529736297372973829739297402974129742297432974429745297462974729748297492975029751297522975329754297552975629757297582975929760297612976229763297642976529766297672976829769297702977129772297732977429775297762977729778297792978029781297822978329784297852978629787297882978929790297912979229793297942979529796297972979829799298002980129802298032980429805298062980729808298092981029811298122981329814298152981629817298182981929820298212982229823298242982529826298272982829829298302983129832298332983429835298362983729838298392984029841298422984329844298452984629847298482984929850298512985229853298542985529856298572985829859298602986129862298632986429865298662986729868298692987029871298722987329874298752987629877298782987929880298812988229883298842988529886298872988829889298902989129892298932989429895298962989729898298992990029901299022990329904299052990629907299082990929910299112991229913299142991529916299172991829919299202992129922299232992429925299262992729928299292993029931299322993329934299352993629937299382993929940299412994229943299442994529946299472994829949299502995129952299532995429955299562995729958299592996029961299622996329964299652996629967299682996929970299712997229973299742997529976299772997829979299802998129982299832998429985299862998729988299892999029991299922999329994299952999629997299982999930000300013000230003300043000530006300073000830009300103001130012300133001430015300163001730018300193002030021300223002330024300253002630027300283002930030300313003230033300343003530036300373003830039300403004130042300433004430045300463004730048300493005030051300523005330054300553005630057300583005930060300613006230063300643006530066300673006830069300703007130072300733007430075300763007730078300793008030081300823008330084300853008630087300883008930090300913009230093300943009530096300973009830099301003010130102301033010430105301063010730108301093011030111301123011330114301153011630117301183011930120301213012230123301243012530126301273012830129301303013130132301333013430135301363013730138301393014030141301423014330144301453014630147301483014930150301513015230153301543015530156301573015830159301603016130162301633016430165301663016730168301693017030171301723017330174301753017630177301783017930180301813018230183301843018530186301873018830189301903019130192301933019430195301963019730198301993020030201302023020330204302053020630207302083020930210302113021230213302143021530216302173021830219302203022130222302233022430225302263022730228302293023030231302323023330234302353023630237302383023930240302413024230243302443024530246302473024830249302503025130252302533025430255302563025730258302593026030261302623026330264302653026630267302683026930270302713027230273302743027530276302773027830279302803028130282302833028430285302863028730288302893029030291302923029330294302953029630297302983029930300303013030230303303043030530306303073030830309303103031130312303133031430315303163031730318303193032030321303223032330324303253032630327303283032930330303313033230333303343033530336303373033830339303403034130342303433034430345303463034730348303493035030351303523035330354303553035630357303583035930360303613036230363303643036530366303673036830369303703037130372303733037430375303763037730378303793038030381303823038330384303853038630387303883038930390303913039230393303943039530396303973039830399304003040130402304033040430405304063040730408304093041030411304123041330414304153041630417304183041930420304213042230423304243042530426304273042830429304303043130432304333043430435304363043730438304393044030441304423044330444304453044630447304483044930450304513045230453304543045530456304573045830459304603046130462304633046430465304663046730468304693047030471304723047330474304753047630477304783047930480304813048230483304843048530486304873048830489304903049130492304933049430495304963049730498304993050030501305023050330504305053050630507305083050930510305113051230513305143051530516305173051830519305203052130522305233052430525305263052730528305293053030531305323053330534305353053630537305383053930540305413054230543305443054530546305473054830549305503055130552305533055430555305563055730558305593056030561305623056330564305653056630567305683056930570305713057230573305743057530576305773057830579305803058130582305833058430585305863058730588305893059030591305923059330594305953059630597305983059930600306013060230603306043060530606306073060830609306103061130612306133061430615306163061730618306193062030621306223062330624306253062630627306283062930630306313063230633306343063530636306373063830639306403064130642306433064430645306463064730648306493065030651306523065330654306553065630657306583065930660306613066230663306643066530666306673066830669306703067130672306733067430675306763067730678306793068030681306823068330684306853068630687306883068930690306913069230693306943069530696306973069830699307003070130702307033070430705307063070730708307093071030711307123071330714307153071630717307183071930720307213072230723307243072530726307273072830729307303073130732307333073430735307363073730738307393074030741307423074330744307453074630747307483074930750307513075230753307543075530756307573075830759307603076130762307633076430765307663076730768307693077030771307723077330774307753077630777307783077930780307813078230783307843078530786307873078830789307903079130792307933079430795307963079730798307993080030801308023080330804308053080630807308083080930810308113081230813308143081530816308173081830819308203082130822308233082430825308263082730828308293083030831308323083330834308353083630837308383083930840308413084230843308443084530846308473084830849308503085130852308533085430855308563085730858308593086030861308623086330864308653086630867308683086930870308713087230873308743087530876308773087830879308803088130882308833088430885308863088730888308893089030891308923089330894308953089630897308983089930900309013090230903309043090530906309073090830909309103091130912309133091430915309163091730918309193092030921309223092330924309253092630927309283092930930309313093230933309343093530936309373093830939309403094130942309433094430945309463094730948309493095030951309523095330954309553095630957309583095930960309613096230963309643096530966309673096830969309703097130972309733097430975309763097730978309793098030981309823098330984309853098630987309883098930990309913099230993309943099530996309973099830999310003100131002310033100431005310063100731008310093101031011310123101331014310153101631017310183101931020310213102231023310243102531026310273102831029310303103131032310333103431035310363103731038310393104031041310423104331044310453104631047310483104931050310513105231053310543105531056310573105831059310603106131062310633106431065310663106731068310693107031071310723107331074310753107631077310783107931080310813108231083310843108531086310873108831089310903109131092310933109431095310963109731098310993110031101311023110331104311053110631107311083110931110311113111231113311143111531116311173111831119311203112131122311233112431125311263112731128311293113031131311323113331134311353113631137311383113931140311413114231143311443114531146311473114831149311503115131152311533115431155311563115731158311593116031161311623116331164311653116631167311683116931170311713117231173311743117531176311773117831179311803118131182311833118431185311863118731188311893119031191311923119331194311953119631197311983119931200312013120231203312043120531206312073120831209312103121131212312133121431215312163121731218312193122031221312223122331224312253122631227312283122931230312313123231233312343123531236312373123831239312403124131242312433124431245312463124731248312493125031251312523125331254312553125631257312583125931260312613126231263312643126531266312673126831269312703127131272312733127431275312763127731278312793128031281312823128331284312853128631287312883128931290312913129231293312943129531296312973129831299313003130131302313033130431305313063130731308313093131031311313123131331314313153131631317313183131931320313213132231323313243132531326313273132831329313303133131332313333133431335313363133731338313393134031341313423134331344313453134631347313483134931350313513135231353313543135531356313573135831359313603136131362313633136431365313663136731368313693137031371313723137331374313753137631377313783137931380313813138231383313843138531386313873138831389313903139131392313933139431395313963139731398313993140031401314023140331404314053140631407314083140931410314113141231413314143141531416314173141831419314203142131422314233142431425314263142731428314293143031431314323143331434314353143631437314383143931440314413144231443314443144531446314473144831449314503145131452314533145431455314563145731458314593146031461314623146331464314653146631467314683146931470314713147231473314743147531476314773147831479314803148131482314833148431485314863148731488314893149031491314923149331494314953149631497314983149931500315013150231503315043150531506315073150831509315103151131512315133151431515315163151731518315193152031521315223152331524315253152631527315283152931530315313153231533315343153531536315373153831539315403154131542315433154431545315463154731548315493155031551315523155331554315553155631557315583155931560315613156231563315643156531566315673156831569315703157131572315733157431575315763157731578315793158031581315823158331584315853158631587315883158931590315913159231593315943159531596315973159831599316003160131602316033160431605316063160731608316093161031611316123161331614316153161631617316183161931620316213162231623316243162531626316273162831629316303163131632316333163431635316363163731638316393164031641316423164331644316453164631647316483164931650316513165231653316543165531656316573165831659316603166131662316633166431665316663166731668316693167031671316723167331674316753167631677316783167931680316813168231683316843168531686316873168831689316903169131692316933169431695316963169731698316993170031701317023170331704317053170631707317083170931710317113171231713317143171531716317173171831719317203172131722317233172431725317263172731728317293173031731317323173331734317353173631737317383173931740317413174231743317443174531746317473174831749317503175131752317533175431755317563175731758317593176031761317623176331764317653176631767317683176931770317713177231773317743177531776317773177831779317803178131782317833178431785317863178731788317893179031791317923179331794317953179631797317983179931800318013180231803318043180531806318073180831809318103181131812318133181431815318163181731818318193182031821318223182331824318253182631827318283182931830318313183231833318343183531836318373183831839318403184131842318433184431845318463184731848318493185031851318523185331854318553185631857318583185931860318613186231863318643186531866318673186831869318703187131872318733187431875318763187731878318793188031881318823188331884318853188631887318883188931890318913189231893318943189531896318973189831899319003190131902319033190431905319063190731908319093191031911319123191331914319153191631917319183191931920319213192231923319243192531926319273192831929319303193131932319333193431935319363193731938319393194031941319423194331944319453194631947319483194931950319513195231953319543195531956319573195831959319603196131962319633196431965319663196731968319693197031971319723197331974319753197631977319783197931980319813198231983319843198531986319873198831989319903199131992319933199431995319963199731998319993200032001320023200332004320053200632007320083200932010320113201232013320143201532016320173201832019320203202132022320233202432025320263202732028320293203032031320323203332034320353203632037320383203932040320413204232043320443204532046320473204832049320503205132052320533205432055320563205732058320593206032061320623206332064320653206632067320683206932070320713207232073320743207532076320773207832079320803208132082320833208432085320863208732088320893209032091320923209332094320953209632097320983209932100321013210232103321043210532106321073210832109321103211132112321133211432115321163211732118321193212032121321223212332124321253212632127321283212932130321313213232133321343213532136321373213832139321403214132142321433214432145321463214732148321493215032151321523215332154321553215632157321583215932160321613216232163321643216532166321673216832169321703217132172321733217432175321763217732178321793218032181321823218332184321853218632187321883218932190321913219232193321943219532196321973219832199322003220132202322033220432205322063220732208322093221032211322123221332214322153221632217322183221932220322213222232223322243222532226322273222832229322303223132232322333223432235322363223732238322393224032241322423224332244322453224632247322483224932250322513225232253322543225532256322573225832259322603226132262322633226432265322663226732268322693227032271322723227332274322753227632277322783227932280322813228232283322843228532286322873228832289322903229132292322933229432295322963229732298322993230032301323023230332304323053230632307323083230932310323113231232313323143231532316323173231832319323203232132322323233232432325323263232732328323293233032331323323233332334323353233632337323383233932340323413234232343323443234532346323473234832349323503235132352323533235432355323563235732358323593236032361323623236332364323653236632367323683236932370323713237232373323743237532376323773237832379323803238132382323833238432385323863238732388323893239032391323923239332394323953239632397323983239932400324013240232403324043240532406324073240832409324103241132412324133241432415324163241732418324193242032421324223242332424324253242632427324283242932430324313243232433324343243532436324373243832439324403244132442324433244432445324463244732448324493245032451324523245332454324553245632457324583245932460324613246232463324643246532466324673246832469324703247132472324733247432475324763247732478324793248032481324823248332484324853248632487324883248932490324913249232493324943249532496324973249832499325003250132502325033250432505325063250732508325093251032511325123251332514325153251632517325183251932520325213252232523325243252532526325273252832529325303253132532325333253432535325363253732538325393254032541325423254332544325453254632547325483254932550325513255232553325543255532556325573255832559325603256132562325633256432565325663256732568325693257032571325723257332574325753257632577325783257932580325813258232583325843258532586325873258832589325903259132592325933259432595325963259732598325993260032601326023260332604326053260632607326083260932610326113261232613326143261532616326173261832619326203262132622326233262432625326263262732628326293263032631326323263332634326353263632637326383263932640326413264232643326443264532646326473264832649326503265132652326533265432655326563265732658326593266032661326623266332664326653266632667326683266932670326713267232673326743267532676326773267832679326803268132682326833268432685326863268732688326893269032691326923269332694326953269632697326983269932700327013270232703327043270532706327073270832709327103271132712327133271432715327163271732718327193272032721327223272332724327253272632727327283272932730327313273232733327343273532736327373273832739327403274132742327433274432745327463274732748327493275032751327523275332754327553275632757327583275932760327613276232763327643276532766327673276832769327703277132772327733277432775327763277732778327793278032781327823278332784327853278632787327883278932790327913279232793327943279532796327973279832799328003280132802328033280432805328063280732808328093281032811328123281332814328153281632817328183281932820328213282232823328243282532826328273282832829328303283132832328333283432835328363283732838328393284032841328423284332844328453284632847328483284932850328513285232853328543285532856328573285832859328603286132862328633286432865328663286732868328693287032871328723287332874328753287632877328783287932880328813288232883328843288532886328873288832889328903289132892328933289432895328963289732898328993290032901329023290332904329053290632907329083290932910329113291232913329143291532916329173291832919329203292132922329233292432925329263292732928329293293032931329323293332934329353293632937329383293932940329413294232943329443294532946329473294832949329503295132952329533295432955329563295732958329593296032961329623296332964329653296632967329683296932970329713297232973329743297532976329773297832979329803298132982329833298432985329863298732988329893299032991329923299332994329953299632997329983299933000330013300233003330043300533006330073300833009330103301133012330133301433015330163301733018330193302033021330223302333024330253302633027330283302933030330313303233033330343303533036330373303833039330403304133042330433304433045330463304733048330493305033051330523305333054330553305633057330583305933060330613306233063330643306533066330673306833069330703307133072330733307433075330763307733078330793308033081330823308333084330853308633087330883308933090330913309233093330943309533096330973309833099331003310133102331033310433105331063310733108331093311033111331123311333114331153311633117331183311933120331213312233123331243312533126331273312833129331303313133132331333313433135331363313733138331393314033141331423314333144331453314633147331483314933150331513315233153331543315533156331573315833159331603316133162331633316433165331663316733168331693317033171331723317333174331753317633177331783317933180331813318233183331843318533186331873318833189331903319133192331933319433195331963319733198331993320033201332023320333204332053320633207332083320933210332113321233213332143321533216332173321833219332203322133222332233322433225332263322733228332293323033231332323323333234332353323633237332383323933240332413324233243332443324533246332473324833249332503325133252332533325433255332563325733258332593326033261332623326333264332653326633267332683326933270332713327233273332743327533276332773327833279332803328133282332833328433285332863328733288332893329033291332923329333294332953329633297332983329933300333013330233303333043330533306333073330833309333103331133312333133331433315333163331733318333193332033321333223332333324333253332633327333283332933330333313333233333333343333533336333373333833339333403334133342333433334433345333463334733348333493335033351333523335333354333553335633357333583335933360333613336233363333643336533366333673336833369333703337133372333733337433375333763337733378333793338033381333823338333384333853338633387333883338933390333913339233393333943339533396333973339833399334003340133402334033340433405334063340733408334093341033411334123341333414334153341633417334183341933420334213342233423334243342533426334273342833429334303343133432334333343433435334363343733438334393344033441334423344333444334453344633447334483344933450334513345233453334543345533456334573345833459334603346133462334633346433465334663346733468334693347033471334723347333474334753347633477334783347933480334813348233483334843348533486334873348833489334903349133492334933349433495334963349733498334993350033501335023350333504335053350633507335083350933510335113351233513335143351533516335173351833519335203352133522335233352433525335263352733528335293353033531335323353333534335353353633537335383353933540335413354233543335443354533546335473354833549335503355133552335533355433555335563355733558335593356033561335623356333564335653356633567335683356933570335713357233573335743357533576335773357833579335803358133582335833358433585335863358733588335893359033591335923359333594335953359633597335983359933600336013360233603336043360533606336073360833609336103361133612336133361433615336163361733618336193362033621336223362333624336253362633627336283362933630336313363233633336343363533636336373363833639336403364133642336433364433645336463364733648336493365033651336523365333654336553365633657336583365933660336613366233663336643366533666336673366833669336703367133672336733367433675336763367733678336793368033681336823368333684336853368633687336883368933690336913369233693336943369533696336973369833699337003370133702337033370433705337063370733708337093371033711337123371333714337153371633717337183371933720337213372233723337243372533726337273372833729337303373133732337333373433735337363373733738337393374033741337423374333744337453374633747337483374933750337513375233753337543375533756337573375833759337603376133762337633376433765337663376733768337693377033771337723377333774337753377633777337783377933780337813378233783337843378533786337873378833789337903379133792337933379433795337963379733798337993380033801338023380333804338053380633807338083380933810338113381233813338143381533816338173381833819338203382133822338233382433825338263382733828338293383033831338323383333834338353383633837338383383933840338413384233843338443384533846338473384833849338503385133852338533385433855338563385733858338593386033861338623386333864338653386633867338683386933870338713387233873338743387533876338773387833879338803388133882338833388433885338863388733888338893389033891338923389333894338953389633897338983389933900339013390233903339043390533906339073390833909339103391133912339133391433915339163391733918339193392033921339223392333924339253392633927339283392933930339313393233933339343393533936339373393833939339403394133942339433394433945339463394733948339493395033951339523395333954339553395633957339583395933960339613396233963339643396533966339673396833969339703397133972339733397433975339763397733978339793398033981339823398333984339853398633987339883398933990339913399233993339943399533996339973399833999340003400134002340033400434005340063400734008340093401034011340123401334014340153401634017340183401934020340213402234023340243402534026340273402834029340303403134032340333403434035340363403734038340393404034041340423404334044340453404634047340483404934050340513405234053340543405534056340573405834059340603406134062340633406434065340663406734068340693407034071340723407334074340753407634077340783407934080340813408234083340843408534086340873408834089340903409134092340933409434095340963409734098340993410034101341023410334104341053410634107341083410934110341113411234113341143411534116341173411834119341203412134122341233412434125341263412734128341293413034131341323413334134341353413634137341383413934140341413414234143341443414534146341473414834149341503415134152341533415434155341563415734158341593416034161341623416334164341653416634167341683416934170341713417234173341743417534176341773417834179341803418134182341833418434185341863418734188341893419034191341923419334194341953419634197341983419934200342013420234203342043420534206342073420834209342103421134212342133421434215342163421734218342193422034221342223422334224342253422634227342283422934230342313423234233342343423534236342373423834239342403424134242342433424434245342463424734248342493425034251342523425334254342553425634257342583425934260342613426234263342643426534266342673426834269342703427134272342733427434275342763427734278342793428034281342823428334284342853428634287342883428934290342913429234293342943429534296342973429834299343003430134302343033430434305343063430734308343093431034311343123431334314343153431634317343183431934320343213432234323343243432534326343273432834329343303433134332343333433434335343363433734338343393434034341343423434334344343453434634347343483434934350343513435234353343543435534356343573435834359343603436134362343633436434365343663436734368343693437034371343723437334374343753437634377343783437934380343813438234383343843438534386343873438834389343903439134392343933439434395343963439734398343993440034401344023440334404344053440634407344083440934410344113441234413344143441534416344173441834419344203442134422344233442434425344263442734428344293443034431344323443334434344353443634437344383443934440344413444234443344443444534446344473444834449344503445134452344533445434455344563445734458344593446034461344623446334464344653446634467344683446934470344713447234473344743447534476344773447834479344803448134482344833448434485344863448734488344893449034491344923449334494344953449634497344983449934500345013450234503345043450534506345073450834509345103451134512345133451434515345163451734518345193452034521345223452334524345253452634527345283452934530345313453234533345343453534536345373453834539345403454134542345433454434545345463454734548345493455034551345523455334554345553455634557345583455934560345613456234563345643456534566345673456834569345703457134572345733457434575345763457734578345793458034581345823458334584345853458634587345883458934590345913459234593345943459534596345973459834599346003460134602346033460434605346063460734608346093461034611346123461334614346153461634617346183461934620346213462234623346243462534626346273462834629346303463134632346333463434635346363463734638346393464034641346423464334644346453464634647346483464934650346513465234653346543465534656346573465834659346603466134662346633466434665346663466734668346693467034671346723467334674346753467634677346783467934680346813468234683346843468534686346873468834689346903469134692346933469434695346963469734698346993470034701347023470334704347053470634707347083470934710347113471234713347143471534716347173471834719347203472134722347233472434725347263472734728347293473034731347323473334734347353473634737347383473934740347413474234743347443474534746347473474834749347503475134752347533475434755347563475734758347593476034761347623476334764347653476634767347683476934770347713477234773347743477534776347773477834779347803478134782347833478434785347863478734788347893479034791347923479334794347953479634797347983479934800348013480234803348043480534806348073480834809348103481134812348133481434815348163481734818348193482034821348223482334824348253482634827348283482934830348313483234833348343483534836348373483834839348403484134842348433484434845348463484734848348493485034851348523485334854348553485634857348583485934860348613486234863348643486534866348673486834869348703487134872348733487434875348763487734878348793488034881348823488334884348853488634887348883488934890348913489234893348943489534896348973489834899349003490134902349033490434905349063490734908349093491034911349123491334914349153491634917349183491934920349213492234923349243492534926349273492834929349303493134932349333493434935349363493734938349393494034941349423494334944349453494634947349483494934950349513495234953349543495534956349573495834959349603496134962349633496434965349663496734968349693497034971349723497334974349753497634977349783497934980349813498234983349843498534986349873498834989349903499134992349933499434995349963499734998349993500035001350023500335004350053500635007350083500935010350113501235013350143501535016350173501835019350203502135022350233502435025350263502735028350293503035031350323503335034350353503635037350383503935040350413504235043350443504535046350473504835049350503505135052350533505435055350563505735058350593506035061350623506335064350653506635067350683506935070350713507235073350743507535076350773507835079350803508135082350833508435085350863508735088350893509035091350923509335094350953509635097350983509935100351013510235103351043510535106351073510835109351103511135112351133511435115351163511735118351193512035121351223512335124351253512635127351283512935130351313513235133351343513535136351373513835139351403514135142351433514435145351463514735148351493515035151351523515335154351553515635157351583515935160351613516235163351643516535166351673516835169351703517135172351733517435175351763517735178351793518035181351823518335184351853518635187351883518935190351913519235193351943519535196351973519835199352003520135202352033520435205352063520735208352093521035211352123521335214352153521635217352183521935220352213522235223352243522535226352273522835229352303523135232352333523435235352363523735238352393524035241352423524335244352453524635247352483524935250352513525235253352543525535256352573525835259352603526135262352633526435265352663526735268352693527035271352723527335274352753527635277352783527935280352813528235283352843528535286352873528835289352903529135292352933529435295352963529735298352993530035301353023530335304353053530635307353083530935310353113531235313353143531535316353173531835319353203532135322353233532435325353263532735328353293533035331353323533335334353353533635337353383533935340353413534235343353443534535346353473534835349353503535135352353533535435355353563535735358353593536035361353623536335364353653536635367353683536935370353713537235373353743537535376353773537835379353803538135382353833538435385353863538735388353893539035391353923539335394353953539635397353983539935400354013540235403354043540535406354073540835409354103541135412354133541435415354163541735418354193542035421354223542335424354253542635427354283542935430354313543235433354343543535436354373543835439354403544135442354433544435445354463544735448354493545035451354523545335454354553545635457354583545935460354613546235463354643546535466354673546835469354703547135472354733547435475354763547735478354793548035481354823548335484354853548635487354883548935490354913549235493354943549535496354973549835499355003550135502355033550435505355063550735508355093551035511355123551335514355153551635517355183551935520355213552235523355243552535526355273552835529355303553135532355333553435535355363553735538355393554035541355423554335544355453554635547355483554935550355513555235553355543555535556355573555835559355603556135562355633556435565355663556735568355693557035571355723557335574355753557635577355783557935580355813558235583355843558535586355873558835589355903559135592355933559435595355963559735598355993560035601356023560335604356053560635607356083560935610356113561235613356143561535616356173561835619356203562135622356233562435625356263562735628356293563035631356323563335634356353563635637356383563935640356413564235643356443564535646356473564835649356503565135652356533565435655356563565735658356593566035661356623566335664356653566635667356683566935670356713567235673356743567535676356773567835679356803568135682356833568435685356863568735688356893569035691356923569335694356953569635697356983569935700357013570235703357043570535706357073570835709357103571135712357133571435715357163571735718357193572035721357223572335724357253572635727357283572935730357313573235733357343573535736357373573835739357403574135742357433574435745357463574735748357493575035751357523575335754357553575635757357583575935760357613576235763357643576535766357673576835769357703577135772357733577435775357763577735778357793578035781357823578335784357853578635787357883578935790357913579235793357943579535796357973579835799358003580135802358033580435805358063580735808358093581035811358123581335814358153581635817358183581935820358213582235823358243582535826358273582835829358303583135832358333583435835358363583735838358393584035841358423584335844358453584635847358483584935850358513585235853358543585535856358573585835859358603586135862358633586435865358663586735868358693587035871358723587335874358753587635877358783587935880358813588235883358843588535886358873588835889358903589135892358933589435895358963589735898358993590035901359023590335904359053590635907359083590935910359113591235913359143591535916359173591835919359203592135922359233592435925359263592735928359293593035931359323593335934359353593635937359383593935940359413594235943359443594535946359473594835949359503595135952359533595435955359563595735958359593596035961359623596335964359653596635967359683596935970359713597235973359743597535976359773597835979359803598135982359833598435985359863598735988359893599035991359923599335994359953599635997359983599936000360013600236003360043600536006360073600836009360103601136012360133601436015360163601736018360193602036021360223602336024360253602636027360283602936030360313603236033360343603536036360373603836039360403604136042360433604436045360463604736048360493605036051360523605336054360553605636057360583605936060360613606236063360643606536066360673606836069360703607136072360733607436075360763607736078360793608036081360823608336084360853608636087360883608936090360913609236093360943609536096360973609836099361003610136102361033610436105361063610736108361093611036111361123611336114361153611636117361183611936120361213612236123361243612536126361273612836129361303613136132361333613436135361363613736138361393614036141361423614336144361453614636147361483614936150361513615236153361543615536156361573615836159361603616136162361633616436165361663616736168361693617036171361723617336174361753617636177361783617936180361813618236183361843618536186361873618836189361903619136192361933619436195361963619736198361993620036201362023620336204362053620636207362083620936210362113621236213362143621536216362173621836219362203622136222362233622436225362263622736228362293623036231362323623336234362353623636237362383623936240362413624236243362443624536246362473624836249362503625136252362533625436255362563625736258362593626036261362623626336264362653626636267362683626936270362713627236273362743627536276362773627836279362803628136282362833628436285362863628736288362893629036291362923629336294362953629636297362983629936300363013630236303363043630536306363073630836309363103631136312363133631436315363163631736318363193632036321363223632336324363253632636327363283632936330363313633236333363343633536336363373633836339363403634136342363433634436345363463634736348363493635036351363523635336354363553635636357363583635936360363613636236363363643636536366363673636836369363703637136372363733637436375363763637736378363793638036381363823638336384363853638636387363883638936390363913639236393363943639536396363973639836399364003640136402364033640436405364063640736408364093641036411364123641336414364153641636417364183641936420364213642236423364243642536426364273642836429364303643136432364333643436435364363643736438364393644036441364423644336444364453644636447364483644936450364513645236453364543645536456364573645836459364603646136462364633646436465364663646736468364693647036471364723647336474364753647636477364783647936480364813648236483364843648536486364873648836489364903649136492364933649436495364963649736498364993650036501365023650336504365053650636507365083650936510365113651236513365143651536516365173651836519365203652136522365233652436525365263652736528365293653036531365323653336534365353653636537365383653936540365413654236543365443654536546365473654836549365503655136552365533655436555365563655736558365593656036561365623656336564365653656636567365683656936570365713657236573365743657536576365773657836579365803658136582365833658436585365863658736588365893659036591365923659336594365953659636597365983659936600366013660236603366043660536606366073660836609366103661136612366133661436615366163661736618366193662036621366223662336624366253662636627366283662936630366313663236633366343663536636366373663836639366403664136642366433664436645366463664736648366493665036651366523665336654366553665636657366583665936660366613666236663366643666536666366673666836669366703667136672366733667436675366763667736678366793668036681366823668336684366853668636687366883668936690366913669236693366943669536696366973669836699367003670136702367033670436705367063670736708367093671036711367123671336714367153671636717367183671936720367213672236723367243672536726367273672836729367303673136732367333673436735367363673736738367393674036741367423674336744367453674636747367483674936750367513675236753367543675536756367573675836759367603676136762367633676436765367663676736768367693677036771367723677336774367753677636777367783677936780367813678236783367843678536786367873678836789367903679136792367933679436795367963679736798367993680036801368023680336804368053680636807368083680936810368113681236813368143681536816368173681836819368203682136822368233682436825368263682736828368293683036831368323683336834368353683636837368383683936840368413684236843368443684536846368473684836849368503685136852368533685436855368563685736858368593686036861368623686336864368653686636867368683686936870368713687236873368743687536876368773687836879368803688136882368833688436885368863688736888368893689036891368923689336894368953689636897368983689936900369013690236903369043690536906369073690836909369103691136912369133691436915369163691736918369193692036921369223692336924369253692636927369283692936930369313693236933369343693536936369373693836939369403694136942369433694436945369463694736948369493695036951369523695336954369553695636957369583695936960369613696236963369643696536966369673696836969369703697136972369733697436975369763697736978369793698036981369823698336984369853698636987369883698936990369913699236993369943699536996369973699836999370003700137002370033700437005370063700737008370093701037011370123701337014370153701637017370183701937020370213702237023370243702537026370273702837029370303703137032370333703437035370363703737038370393704037041370423704337044370453704637047370483704937050370513705237053370543705537056370573705837059370603706137062370633706437065370663706737068370693707037071370723707337074370753707637077370783707937080370813708237083370843708537086370873708837089370903709137092370933709437095370963709737098370993710037101371023710337104371053710637107371083710937110371113711237113371143711537116371173711837119371203712137122371233712437125371263712737128371293713037131371323713337134371353713637137371383713937140371413714237143371443714537146371473714837149371503715137152371533715437155371563715737158371593716037161371623716337164371653716637167371683716937170371713717237173371743717537176371773717837179371803718137182371833718437185371863718737188371893719037191371923719337194371953719637197371983719937200372013720237203372043720537206372073720837209372103721137212372133721437215372163721737218372193722037221372223722337224372253722637227372283722937230372313723237233372343723537236372373723837239372403724137242372433724437245372463724737248372493725037251372523725337254372553725637257372583725937260372613726237263372643726537266372673726837269372703727137272372733727437275372763727737278372793728037281372823728337284372853728637287372883728937290372913729237293372943729537296372973729837299373003730137302373033730437305373063730737308373093731037311373123731337314373153731637317373183731937320373213732237323373243732537326373273732837329373303733137332373333733437335373363733737338373393734037341373423734337344373453734637347373483734937350373513735237353373543735537356373573735837359373603736137362373633736437365373663736737368373693737037371373723737337374373753737637377373783737937380373813738237383373843738537386373873738837389373903739137392373933739437395373963739737398373993740037401374023740337404374053740637407374083740937410374113741237413374143741537416374173741837419374203742137422374233742437425374263742737428374293743037431374323743337434374353743637437374383743937440374413744237443374443744537446374473744837449374503745137452374533745437455374563745737458374593746037461374623746337464374653746637467374683746937470374713747237473374743747537476374773747837479374803748137482374833748437485374863748737488374893749037491374923749337494374953749637497374983749937500375013750237503375043750537506375073750837509375103751137512375133751437515375163751737518375193752037521375223752337524375253752637527375283752937530375313753237533375343753537536375373753837539375403754137542375433754437545375463754737548375493755037551375523755337554375553755637557375583755937560375613756237563375643756537566375673756837569375703757137572375733757437575375763757737578375793758037581375823758337584375853758637587375883758937590375913759237593375943759537596375973759837599376003760137602376033760437605376063760737608376093761037611376123761337614376153761637617376183761937620376213762237623376243762537626376273762837629376303763137632376333763437635376363763737638376393764037641376423764337644376453764637647376483764937650376513765237653376543765537656376573765837659376603766137662376633766437665376663766737668376693767037671376723767337674376753767637677376783767937680376813768237683376843768537686376873768837689376903769137692376933769437695376963769737698376993770037701377023770337704377053770637707377083770937710377113771237713377143771537716377173771837719377203772137722377233772437725377263772737728377293773037731377323773337734377353773637737377383773937740377413774237743377443774537746377473774837749377503775137752377533775437755377563775737758377593776037761377623776337764377653776637767377683776937770377713777237773377743777537776377773777837779377803778137782377833778437785377863778737788377893779037791377923779337794377953779637797377983779937800378013780237803378043780537806378073780837809378103781137812378133781437815378163781737818378193782037821378223782337824378253782637827378283782937830378313783237833378343783537836378373783837839378403784137842378433784437845378463784737848378493785037851378523785337854378553785637857378583785937860378613786237863378643786537866378673786837869378703787137872378733787437875378763787737878378793788037881378823788337884378853788637887378883788937890378913789237893378943789537896378973789837899379003790137902379033790437905379063790737908379093791037911379123791337914379153791637917379183791937920379213792237923379243792537926379273792837929379303793137932379333793437935379363793737938379393794037941379423794337944379453794637947379483794937950379513795237953379543795537956379573795837959379603796137962379633796437965379663796737968379693797037971379723797337974379753797637977379783797937980379813798237983379843798537986379873798837989379903799137992379933799437995379963799737998379993800038001380023800338004380053800638007380083800938010380113801238013380143801538016380173801838019380203802138022380233802438025380263802738028380293803038031380323803338034380353803638037380383803938040380413804238043380443804538046380473804838049380503805138052380533805438055380563805738058380593806038061380623806338064380653806638067380683806938070380713807238073380743807538076380773807838079380803808138082380833808438085380863808738088380893809038091380923809338094380953809638097380983809938100381013810238103381043810538106381073810838109381103811138112381133811438115381163811738118381193812038121381223812338124381253812638127381283812938130381313813238133381343813538136381373813838139381403814138142381433814438145381463814738148381493815038151381523815338154381553815638157381583815938160381613816238163381643816538166381673816838169381703817138172381733817438175381763817738178381793818038181381823818338184381853818638187381883818938190381913819238193381943819538196381973819838199382003820138202382033820438205382063820738208382093821038211382123821338214382153821638217382183821938220382213822238223382243822538226382273822838229382303823138232382333823438235382363823738238382393824038241382423824338244382453824638247382483824938250382513825238253382543825538256382573825838259382603826138262382633826438265382663826738268382693827038271382723827338274382753827638277382783827938280382813828238283382843828538286382873828838289382903829138292382933829438295382963829738298382993830038301383023830338304383053830638307383083830938310383113831238313383143831538316383173831838319383203832138322383233832438325383263832738328383293833038331383323833338334383353833638337383383833938340383413834238343383443834538346383473834838349383503835138352383533835438355383563835738358383593836038361383623836338364383653836638367383683836938370383713837238373383743837538376383773837838379383803838138382383833838438385383863838738388383893839038391383923839338394383953839638397383983839938400384013840238403384043840538406384073840838409384103841138412384133841438415384163841738418384193842038421384223842338424384253842638427384283842938430384313843238433384343843538436384373843838439384403844138442384433844438445384463844738448384493845038451384523845338454384553845638457384583845938460384613846238463384643846538466384673846838469384703847138472384733847438475384763847738478384793848038481384823848338484384853848638487384883848938490384913849238493384943849538496384973849838499385003850138502385033850438505385063850738508385093851038511385123851338514385153851638517385183851938520385213852238523385243852538526385273852838529385303853138532385333853438535385363853738538385393854038541385423854338544385453854638547385483854938550385513855238553385543855538556385573855838559385603856138562385633856438565385663856738568385693857038571385723857338574385753857638577385783857938580385813858238583385843858538586385873858838589385903859138592385933859438595385963859738598385993860038601386023860338604386053860638607386083860938610386113861238613386143861538616386173861838619386203862138622386233862438625386263862738628386293863038631386323863338634386353863638637386383863938640386413864238643386443864538646386473864838649386503865138652386533865438655386563865738658386593866038661386623866338664386653866638667386683866938670386713867238673386743867538676386773867838679386803868138682386833868438685386863868738688386893869038691386923869338694386953869638697386983869938700387013870238703387043870538706387073870838709387103871138712387133871438715387163871738718387193872038721387223872338724387253872638727387283872938730387313873238733387343873538736387373873838739387403874138742387433874438745387463874738748387493875038751387523875338754387553875638757387583875938760387613876238763387643876538766387673876838769387703877138772387733877438775387763877738778387793878038781387823878338784387853878638787387883878938790387913879238793387943879538796387973879838799388003880138802388033880438805388063880738808388093881038811388123881338814388153881638817388183881938820388213882238823388243882538826388273882838829388303883138832388333883438835388363883738838388393884038841388423884338844388453884638847388483884938850388513885238853388543885538856388573885838859388603886138862388633886438865388663886738868388693887038871388723887338874388753887638877388783887938880388813888238883388843888538886388873888838889388903889138892388933889438895388963889738898388993890038901389023890338904389053890638907389083890938910389113891238913389143891538916389173891838919389203892138922389233892438925389263892738928389293893038931389323893338934389353893638937389383893938940389413894238943389443894538946389473894838949389503895138952389533895438955389563895738958389593896038961389623896338964389653896638967389683896938970389713897238973389743897538976389773897838979389803898138982389833898438985389863898738988389893899038991389923899338994389953899638997389983899939000390013900239003390043900539006390073900839009390103901139012390133901439015390163901739018390193902039021390223902339024390253902639027390283902939030390313903239033390343903539036390373903839039390403904139042390433904439045390463904739048390493905039051390523905339054390553905639057390583905939060390613906239063390643906539066390673906839069390703907139072390733907439075390763907739078390793908039081390823908339084390853908639087390883908939090390913909239093390943909539096390973909839099391003910139102391033910439105391063910739108391093911039111391123911339114391153911639117391183911939120391213912239123391243912539126391273912839129391303913139132391333913439135391363913739138391393914039141391423914339144391453914639147391483914939150391513915239153391543915539156391573915839159391603916139162391633916439165391663916739168391693917039171391723917339174391753917639177391783917939180391813918239183391843918539186391873918839189391903919139192391933919439195391963919739198391993920039201392023920339204392053920639207392083920939210392113921239213392143921539216392173921839219392203922139222392233922439225392263922739228392293923039231392323923339234392353923639237392383923939240392413924239243392443924539246392473924839249392503925139252392533925439255392563925739258392593926039261392623926339264392653926639267392683926939270392713927239273392743927539276392773927839279392803928139282392833928439285392863928739288392893929039291392923929339294392953929639297392983929939300393013930239303393043930539306393073930839309393103931139312393133931439315393163931739318393193932039321393223932339324393253932639327393283932939330393313933239333393343933539336393373933839339393403934139342393433934439345393463934739348393493935039351393523935339354393553935639357393583935939360393613936239363393643936539366393673936839369393703937139372393733937439375393763937739378393793938039381393823938339384393853938639387393883938939390393913939239393393943939539396393973939839399394003940139402394033940439405394063940739408394093941039411394123941339414394153941639417394183941939420394213942239423394243942539426394273942839429394303943139432394333943439435394363943739438394393944039441394423944339444394453944639447394483944939450394513945239453394543945539456394573945839459394603946139462394633946439465394663946739468394693947039471394723947339474394753947639477394783947939480394813948239483394843948539486394873948839489394903949139492394933949439495394963949739498394993950039501395023950339504395053950639507395083950939510395113951239513395143951539516395173951839519395203952139522395233952439525395263952739528395293953039531395323953339534395353953639537395383953939540395413954239543395443954539546395473954839549395503955139552395533955439555395563955739558395593956039561395623956339564395653956639567395683956939570395713957239573395743957539576395773957839579395803958139582395833958439585395863958739588395893959039591395923959339594395953959639597395983959939600396013960239603396043960539606396073960839609396103961139612396133961439615396163961739618396193962039621396223962339624396253962639627396283962939630396313963239633396343963539636396373963839639396403964139642396433964439645396463964739648396493965039651396523965339654396553965639657396583965939660396613966239663396643966539666396673966839669396703967139672396733967439675396763967739678396793968039681396823968339684396853968639687396883968939690396913969239693396943969539696396973969839699397003970139702397033970439705397063970739708397093971039711397123971339714397153971639717397183971939720397213972239723397243972539726397273972839729397303973139732397333973439735397363973739738397393974039741397423974339744397453974639747397483974939750397513975239753397543975539756397573975839759397603976139762397633976439765397663976739768397693977039771397723977339774397753977639777397783977939780397813978239783397843978539786397873978839789397903979139792397933979439795397963979739798397993980039801398023980339804398053980639807398083980939810398113981239813398143981539816398173981839819398203982139822398233982439825398263982739828398293983039831398323983339834398353983639837398383983939840398413984239843398443984539846398473984839849398503985139852398533985439855398563985739858398593986039861398623986339864398653986639867398683986939870398713987239873398743987539876398773987839879398803988139882398833988439885398863988739888398893989039891398923989339894398953989639897398983989939900399013990239903399043990539906399073990839909399103991139912399133991439915399163991739918399193992039921399223992339924399253992639927399283992939930399313993239933399343993539936399373993839939399403994139942399433994439945399463994739948399493995039951399523995339954399553995639957399583995939960399613996239963399643996539966399673996839969399703997139972399733997439975399763997739978399793998039981399823998339984399853998639987399883998939990399913999239993399943999539996399973999839999400004000140002400034000440005400064000740008400094001040011400124001340014400154001640017400184001940020400214002240023400244002540026400274002840029400304003140032400334003440035400364003740038400394004040041400424004340044400454004640047400484004940050400514005240053400544005540056400574005840059400604006140062400634006440065400664006740068400694007040071400724007340074400754007640077400784007940080400814008240083400844008540086400874008840089400904009140092400934009440095400964009740098400994010040101401024010340104401054010640107401084010940110401114011240113401144011540116401174011840119401204012140122401234012440125401264012740128401294013040131401324013340134401354013640137401384013940140401414014240143401444014540146401474014840149401504015140152401534015440155401564015740158401594016040161401624016340164401654016640167401684016940170401714017240173401744017540176401774017840179401804018140182401834018440185401864018740188401894019040191401924019340194401954019640197401984019940200402014020240203402044020540206402074020840209402104021140212402134021440215402164021740218402194022040221402224022340224402254022640227402284022940230402314023240233402344023540236402374023840239402404024140242402434024440245402464024740248402494025040251402524025340254402554025640257402584025940260402614026240263402644026540266402674026840269402704027140272402734027440275402764027740278402794028040281402824028340284402854028640287402884028940290402914029240293402944029540296402974029840299403004030140302403034030440305403064030740308403094031040311403124031340314403154031640317403184031940320403214032240323403244032540326403274032840329403304033140332403334033440335403364033740338403394034040341403424034340344403454034640347403484034940350403514035240353403544035540356403574035840359403604036140362403634036440365403664036740368403694037040371403724037340374403754037640377403784037940380403814038240383403844038540386403874038840389403904039140392403934039440395403964039740398403994040040401404024040340404404054040640407404084040940410404114041240413404144041540416404174041840419404204042140422404234042440425404264042740428404294043040431404324043340434404354043640437404384043940440404414044240443404444044540446404474044840449404504045140452404534045440455404564045740458404594046040461404624046340464404654046640467404684046940470404714047240473404744047540476404774047840479404804048140482404834048440485404864048740488404894049040491404924049340494404954049640497404984049940500405014050240503405044050540506405074050840509405104051140512405134051440515405164051740518405194052040521405224052340524405254052640527405284052940530405314053240533405344053540536405374053840539405404054140542405434054440545405464054740548405494055040551405524055340554405554055640557405584055940560405614056240563405644056540566405674056840569405704057140572405734057440575405764057740578405794058040581405824058340584405854058640587405884058940590405914059240593405944059540596405974059840599406004060140602406034060440605406064060740608406094061040611406124061340614406154061640617406184061940620406214062240623406244062540626406274062840629406304063140632406334063440635406364063740638406394064040641406424064340644406454064640647406484064940650406514065240653406544065540656406574065840659406604066140662406634066440665406664066740668406694067040671406724067340674406754067640677406784067940680406814068240683406844068540686406874068840689406904069140692406934069440695406964069740698406994070040701407024070340704407054070640707407084070940710407114071240713407144071540716407174071840719407204072140722407234072440725407264072740728407294073040731407324073340734407354073640737407384073940740407414074240743407444074540746407474074840749407504075140752407534075440755407564075740758407594076040761407624076340764407654076640767407684076940770407714077240773407744077540776407774077840779407804078140782407834078440785407864078740788407894079040791407924079340794407954079640797407984079940800408014080240803408044080540806408074080840809408104081140812408134081440815408164081740818408194082040821408224082340824408254082640827408284082940830408314083240833408344083540836408374083840839408404084140842408434084440845408464084740848408494085040851408524085340854408554085640857408584085940860408614086240863408644086540866408674086840869408704087140872408734087440875408764087740878408794088040881408824088340884408854088640887408884088940890408914089240893408944089540896408974089840899409004090140902409034090440905409064090740908409094091040911409124091340914409154091640917409184091940920409214092240923409244092540926409274092840929409304093140932409334093440935409364093740938409394094040941409424094340944409454094640947409484094940950409514095240953409544095540956409574095840959409604096140962409634096440965409664096740968409694097040971409724097340974409754097640977409784097940980409814098240983409844098540986409874098840989409904099140992409934099440995409964099740998409994100041001410024100341004410054100641007410084100941010410114101241013410144101541016410174101841019410204102141022410234102441025410264102741028410294103041031410324103341034410354103641037410384103941040410414104241043410444104541046410474104841049410504105141052410534105441055410564105741058410594106041061410624106341064410654106641067410684106941070410714107241073410744107541076410774107841079410804108141082410834108441085410864108741088410894109041091410924109341094410954109641097410984109941100411014110241103411044110541106411074110841109411104111141112411134111441115411164111741118411194112041121411224112341124411254112641127411284112941130411314113241133411344113541136411374113841139411404114141142411434114441145411464114741148411494115041151411524115341154411554115641157411584115941160411614116241163411644116541166411674116841169411704117141172411734117441175411764117741178411794118041181411824118341184411854118641187411884118941190411914119241193411944119541196411974119841199412004120141202412034120441205412064120741208412094121041211412124121341214412154121641217412184121941220412214122241223412244122541226412274122841229412304123141232412334123441235412364123741238412394124041241412424124341244412454124641247412484124941250412514125241253412544125541256412574125841259412604126141262412634126441265412664126741268412694127041271412724127341274412754127641277412784127941280412814128241283412844128541286412874128841289412904129141292412934129441295412964129741298412994130041301413024130341304413054130641307413084130941310413114131241313413144131541316413174131841319413204132141322413234132441325413264132741328413294133041331413324133341334413354133641337413384133941340413414134241343413444134541346413474134841349413504135141352413534135441355413564135741358413594136041361413624136341364413654136641367413684136941370413714137241373413744137541376413774137841379413804138141382413834138441385413864138741388413894139041391413924139341394413954139641397413984139941400414014140241403414044140541406414074140841409414104141141412414134141441415414164141741418414194142041421414224142341424414254142641427414284142941430414314143241433414344143541436414374143841439414404144141442414434144441445414464144741448414494145041451414524145341454414554145641457414584145941460414614146241463414644146541466414674146841469414704147141472414734147441475414764147741478414794148041481414824148341484414854148641487414884148941490414914149241493414944149541496414974149841499415004150141502415034150441505415064150741508415094151041511415124151341514415154151641517415184151941520415214152241523415244152541526415274152841529415304153141532415334153441535415364153741538415394154041541415424154341544415454154641547415484154941550415514155241553415544155541556415574155841559415604156141562415634156441565415664156741568415694157041571415724157341574415754157641577415784157941580415814158241583415844158541586415874158841589415904159141592415934159441595415964159741598415994160041601416024160341604416054160641607416084160941610416114161241613416144161541616416174161841619416204162141622416234162441625416264162741628416294163041631416324163341634416354163641637416384163941640416414164241643416444164541646416474164841649416504165141652416534165441655416564165741658416594166041661416624166341664416654166641667416684166941670416714167241673416744167541676416774167841679416804168141682416834168441685416864168741688416894169041691416924169341694416954169641697416984169941700417014170241703417044170541706417074170841709417104171141712417134171441715417164171741718417194172041721417224172341724417254172641727417284172941730417314173241733417344173541736417374173841739417404174141742417434174441745417464174741748417494175041751417524175341754417554175641757417584175941760417614176241763417644176541766417674176841769417704177141772417734177441775417764177741778417794178041781417824178341784417854178641787417884178941790417914179241793417944179541796417974179841799418004180141802418034180441805418064180741808418094181041811418124181341814418154181641817418184181941820418214182241823418244182541826418274182841829418304183141832418334183441835418364183741838418394184041841418424184341844418454184641847418484184941850418514185241853418544185541856418574185841859418604186141862418634186441865418664186741868418694187041871418724187341874418754187641877418784187941880418814188241883418844188541886418874188841889418904189141892418934189441895418964189741898418994190041901419024190341904419054190641907419084190941910419114191241913419144191541916419174191841919419204192141922419234192441925419264192741928419294193041931419324193341934419354193641937419384193941940419414194241943419444194541946419474194841949419504195141952419534195441955419564195741958419594196041961419624196341964419654196641967419684196941970419714197241973419744197541976419774197841979419804198141982419834198441985419864198741988419894199041991419924199341994419954199641997419984199942000420014200242003420044200542006420074200842009420104201142012420134201442015420164201742018420194202042021420224202342024420254202642027420284202942030420314203242033420344203542036420374203842039420404204142042420434204442045420464204742048420494205042051420524205342054420554205642057420584205942060420614206242063420644206542066420674206842069420704207142072420734207442075420764207742078420794208042081420824208342084420854208642087420884208942090420914209242093420944209542096420974209842099421004210142102421034210442105421064210742108421094211042111421124211342114421154211642117421184211942120421214212242123421244212542126421274212842129421304213142132421334213442135421364213742138421394214042141421424214342144421454214642147421484214942150421514215242153421544215542156421574215842159421604216142162421634216442165421664216742168421694217042171421724217342174421754217642177421784217942180421814218242183421844218542186421874218842189421904219142192421934219442195421964219742198421994220042201422024220342204422054220642207422084220942210422114221242213422144221542216422174221842219422204222142222422234222442225422264222742228422294223042231422324223342234422354223642237422384223942240422414224242243422444224542246422474224842249422504225142252422534225442255422564225742258422594226042261422624226342264422654226642267422684226942270422714227242273422744227542276422774227842279422804228142282422834228442285422864228742288422894229042291422924229342294422954229642297422984229942300423014230242303423044230542306423074230842309423104231142312423134231442315423164231742318423194232042321423224232342324423254232642327423284232942330423314233242333423344233542336423374233842339423404234142342423434234442345423464234742348423494235042351423524235342354423554235642357423584235942360423614236242363423644236542366423674236842369423704237142372423734237442375423764237742378423794238042381423824238342384423854238642387423884238942390423914239242393423944239542396423974239842399424004240142402424034240442405424064240742408424094241042411424124241342414424154241642417424184241942420424214242242423424244242542426424274242842429424304243142432424334243442435424364243742438424394244042441424424244342444424454244642447424484244942450424514245242453424544245542456424574245842459424604246142462424634246442465424664246742468424694247042471424724247342474424754247642477424784247942480424814248242483424844248542486424874248842489424904249142492424934249442495424964249742498424994250042501425024250342504425054250642507425084250942510425114251242513425144251542516425174251842519425204252142522425234252442525425264252742528425294253042531425324253342534425354253642537425384253942540425414254242543425444254542546425474254842549425504255142552425534255442555425564255742558425594256042561425624256342564425654256642567425684256942570425714257242573425744257542576425774257842579425804258142582425834258442585425864258742588425894259042591425924259342594425954259642597425984259942600426014260242603426044260542606426074260842609426104261142612426134261442615426164261742618426194262042621426224262342624426254262642627426284262942630426314263242633426344263542636426374263842639426404264142642426434264442645426464264742648426494265042651426524265342654426554265642657426584265942660426614266242663426644266542666426674266842669426704267142672426734267442675426764267742678426794268042681426824268342684426854268642687426884268942690426914269242693426944269542696426974269842699427004270142702427034270442705427064270742708427094271042711427124271342714427154271642717427184271942720427214272242723427244272542726427274272842729427304273142732427334273442735427364273742738427394274042741427424274342744427454274642747427484274942750427514275242753427544275542756427574275842759427604276142762427634276442765427664276742768427694277042771427724277342774427754277642777427784277942780427814278242783427844278542786427874278842789427904279142792427934279442795427964279742798427994280042801428024280342804428054280642807428084280942810428114281242813428144281542816428174281842819428204282142822428234282442825428264282742828428294283042831428324283342834428354283642837428384283942840428414284242843428444284542846428474284842849428504285142852428534285442855428564285742858428594286042861428624286342864428654286642867428684286942870428714287242873428744287542876428774287842879428804288142882428834288442885428864288742888428894289042891428924289342894428954289642897428984289942900429014290242903429044290542906429074290842909429104291142912429134291442915429164291742918429194292042921429224292342924429254292642927429284292942930429314293242933429344293542936429374293842939429404294142942429434294442945429464294742948429494295042951429524295342954429554295642957429584295942960429614296242963429644296542966429674296842969429704297142972429734297442975429764297742978429794298042981429824298342984429854298642987429884298942990429914299242993429944299542996429974299842999430004300143002430034300443005430064300743008430094301043011430124301343014430154301643017430184301943020430214302243023430244302543026430274302843029430304303143032430334303443035430364303743038430394304043041430424304343044430454304643047430484304943050430514305243053430544305543056430574305843059430604306143062430634306443065430664306743068430694307043071430724307343074430754307643077430784307943080430814308243083430844308543086430874308843089430904309143092430934309443095430964309743098430994310043101431024310343104431054310643107431084310943110431114311243113431144311543116431174311843119431204312143122431234312443125431264312743128431294313043131431324313343134431354313643137431384313943140431414314243143431444314543146431474314843149431504315143152431534315443155431564315743158431594316043161431624316343164431654316643167431684316943170431714317243173431744317543176431774317843179431804318143182431834318443185431864318743188431894319043191431924319343194431954319643197431984319943200432014320243203432044320543206432074320843209432104321143212432134321443215432164321743218432194322043221432224322343224432254322643227432284322943230432314323243233432344323543236432374323843239432404324143242432434324443245432464324743248432494325043251432524325343254432554325643257432584325943260432614326243263432644326543266432674326843269432704327143272432734327443275432764327743278432794328043281432824328343284432854328643287432884328943290432914329243293432944329543296432974329843299433004330143302433034330443305433064330743308433094331043311433124331343314433154331643317433184331943320433214332243323433244332543326433274332843329433304333143332433334333443335433364333743338433394334043341433424334343344433454334643347433484334943350433514335243353433544335543356433574335843359433604336143362433634336443365433664336743368433694337043371433724337343374433754337643377433784337943380433814338243383433844338543386433874338843389433904339143392433934339443395433964339743398433994340043401434024340343404434054340643407434084340943410434114341243413434144341543416434174341843419434204342143422434234342443425434264342743428434294343043431434324343343434434354343643437434384343943440434414344243443434444344543446434474344843449434504345143452434534345443455434564345743458434594346043461434624346343464434654346643467434684346943470434714347243473434744347543476434774347843479434804348143482434834348443485434864348743488434894349043491434924349343494434954349643497434984349943500435014350243503435044350543506435074350843509435104351143512435134351443515435164351743518435194352043521435224352343524435254352643527435284352943530435314353243533435344353543536435374353843539435404354143542435434354443545435464354743548435494355043551435524355343554435554355643557435584355943560435614356243563435644356543566435674356843569435704357143572435734357443575435764357743578435794358043581435824358343584435854358643587435884358943590435914359243593435944359543596435974359843599436004360143602436034360443605436064360743608436094361043611436124361343614436154361643617436184361943620436214362243623436244362543626436274362843629436304363143632436334363443635436364363743638436394364043641436424364343644436454364643647436484364943650436514365243653436544365543656436574365843659436604366143662436634366443665436664366743668436694367043671436724367343674436754367643677436784367943680436814368243683436844368543686436874368843689436904369143692436934369443695436964369743698436994370043701437024370343704437054370643707437084370943710437114371243713437144371543716437174371843719437204372143722437234372443725437264372743728437294373043731437324373343734437354373643737437384373943740437414374243743437444374543746437474374843749437504375143752437534375443755437564375743758437594376043761437624376343764437654376643767437684376943770437714377243773437744377543776437774377843779437804378143782437834378443785437864378743788437894379043791437924379343794437954379643797437984379943800438014380243803438044380543806438074380843809438104381143812438134381443815438164381743818438194382043821438224382343824438254382643827438284382943830438314383243833438344383543836438374383843839438404384143842438434384443845438464384743848438494385043851438524385343854438554385643857438584385943860438614386243863438644386543866438674386843869438704387143872438734387443875438764387743878438794388043881438824388343884438854388643887438884388943890438914389243893438944389543896438974389843899439004390143902439034390443905439064390743908439094391043911439124391343914439154391643917439184391943920439214392243923439244392543926439274392843929439304393143932439334393443935439364393743938439394394043941439424394343944439454394643947439484394943950439514395243953439544395543956439574395843959439604396143962439634396443965439664396743968439694397043971439724397343974439754397643977439784397943980439814398243983439844398543986439874398843989439904399143992439934399443995439964399743998439994400044001440024400344004440054400644007440084400944010440114401244013440144401544016440174401844019440204402144022440234402444025440264402744028440294403044031440324403344034440354403644037440384403944040440414404244043440444404544046440474404844049440504405144052440534405444055440564405744058440594406044061440624406344064440654406644067440684406944070440714407244073440744407544076440774407844079440804408144082440834408444085440864408744088440894409044091440924409344094440954409644097440984409944100441014410244103441044410544106441074410844109441104411144112441134411444115441164411744118441194412044121441224412344124441254412644127441284412944130441314413244133441344413544136441374413844139441404414144142441434414444145441464414744148441494415044151441524415344154441554415644157441584415944160441614416244163441644416544166441674416844169441704417144172441734417444175441764417744178441794418044181441824418344184441854418644187441884418944190441914419244193441944419544196441974419844199442004420144202442034420444205442064420744208442094421044211442124421344214442154421644217442184421944220442214422244223442244422544226442274422844229442304423144232442334423444235442364423744238442394424044241442424424344244442454424644247442484424944250442514425244253442544425544256442574425844259442604426144262442634426444265442664426744268442694427044271442724427344274442754427644277442784427944280442814428244283442844428544286442874428844289442904429144292442934429444295442964429744298442994430044301443024430344304443054430644307443084430944310443114431244313443144431544316443174431844319443204432144322443234432444325443264432744328443294433044331443324433344334443354433644337443384433944340443414434244343443444434544346443474434844349443504435144352443534435444355443564435744358443594436044361443624436344364443654436644367443684436944370443714437244373443744437544376443774437844379443804438144382443834438444385443864438744388443894439044391443924439344394443954439644397443984439944400444014440244403444044440544406444074440844409444104441144412444134441444415444164441744418444194442044421444224442344424444254442644427444284442944430444314443244433444344443544436444374443844439444404444144442444434444444445444464444744448444494445044451444524445344454444554445644457444584445944460444614446244463444644446544466444674446844469444704447144472444734447444475444764447744478444794448044481444824448344484444854448644487444884448944490444914449244493444944449544496444974449844499445004450144502445034450444505445064450744508445094451044511445124451344514445154451644517445184451944520445214452244523445244452544526445274452844529445304453144532445334453444535445364453744538445394454044541445424454344544445454454644547445484454944550445514455244553445544455544556445574455844559445604456144562445634456444565445664456744568445694457044571445724457344574445754457644577445784457944580445814458244583445844458544586445874458844589445904459144592445934459444595445964459744598445994460044601446024460344604446054460644607446084460944610446114461244613446144461544616446174461844619446204462144622446234462444625446264462744628446294463044631446324463344634446354463644637446384463944640446414464244643446444464544646446474464844649446504465144652446534465444655446564465744658446594466044661446624466344664446654466644667446684466944670446714467244673446744467544676446774467844679446804468144682446834468444685446864468744688446894469044691446924469344694446954469644697446984469944700447014470244703447044470544706447074470844709447104471144712447134471444715447164471744718447194472044721447224472344724447254472644727447284472944730447314473244733447344473544736447374473844739447404474144742447434474444745447464474744748447494475044751447524475344754447554475644757447584475944760447614476244763447644476544766447674476844769447704477144772447734477444775447764477744778447794478044781447824478344784447854478644787447884478944790447914479244793447944479544796447974479844799448004480144802448034480444805448064480744808448094481044811448124481344814448154481644817448184481944820448214482244823448244482544826448274482844829448304483144832448334483444835448364483744838448394484044841448424484344844448454484644847448484484944850448514485244853448544485544856448574485844859448604486144862448634486444865448664486744868448694487044871448724487344874448754487644877448784487944880448814488244883448844488544886448874488844889448904489144892448934489444895448964489744898448994490044901449024490344904449054490644907449084490944910449114491244913449144491544916449174491844919449204492144922449234492444925449264492744928449294493044931449324493344934449354493644937449384493944940449414494244943449444494544946449474494844949449504495144952449534495444955449564495744958449594496044961449624496344964449654496644967449684496944970449714497244973449744497544976449774497844979449804498144982449834498444985449864498744988449894499044991449924499344994449954499644997449984499945000450014500245003450044500545006450074500845009450104501145012450134501445015450164501745018450194502045021450224502345024450254502645027450284502945030450314503245033450344503545036450374503845039450404504145042450434504445045450464504745048450494505045051450524505345054450554505645057450584505945060450614506245063450644506545066450674506845069450704507145072450734507445075450764507745078450794508045081450824508345084450854508645087450884508945090450914509245093450944509545096450974509845099451004510145102451034510445105451064510745108451094511045111451124511345114451154511645117451184511945120451214512245123451244512545126451274512845129451304513145132451334513445135451364513745138451394514045141451424514345144451454514645147451484514945150451514515245153451544515545156451574515845159451604516145162451634516445165451664516745168451694517045171451724517345174451754517645177451784517945180451814518245183451844518545186451874518845189451904519145192451934519445195451964519745198451994520045201452024520345204452054520645207452084520945210452114521245213452144521545216452174521845219452204522145222452234522445225452264522745228452294523045231452324523345234452354523645237452384523945240452414524245243452444524545246452474524845249452504525145252452534525445255452564525745258452594526045261452624526345264452654526645267452684526945270452714527245273452744527545276452774527845279452804528145282452834528445285452864528745288452894529045291452924529345294452954529645297452984529945300453014530245303453044530545306453074530845309453104531145312453134531445315453164531745318453194532045321453224532345324453254532645327453284532945330453314533245333453344533545336453374533845339453404534145342453434534445345453464534745348453494535045351453524535345354453554535645357453584535945360453614536245363453644536545366453674536845369453704537145372453734537445375453764537745378453794538045381453824538345384453854538645387453884538945390453914539245393453944539545396453974539845399454004540145402454034540445405454064540745408454094541045411454124541345414454154541645417454184541945420454214542245423454244542545426454274542845429454304543145432454334543445435454364543745438454394544045441454424544345444454454544645447454484544945450454514545245453454544545545456454574545845459454604546145462454634546445465454664546745468454694547045471454724547345474454754547645477454784547945480454814548245483454844548545486454874548845489454904549145492454934549445495454964549745498454994550045501455024550345504455054550645507455084550945510455114551245513455144551545516455174551845519455204552145522455234552445525455264552745528455294553045531455324553345534455354553645537455384553945540455414554245543455444554545546455474554845549455504555145552455534555445555455564555745558455594556045561455624556345564455654556645567455684556945570455714557245573455744557545576455774557845579455804558145582455834558445585455864558745588455894559045591455924559345594455954559645597455984559945600456014560245603456044560545606456074560845609456104561145612456134561445615456164561745618456194562045621456224562345624456254562645627456284562945630456314563245633456344563545636456374563845639456404564145642456434564445645456464564745648456494565045651456524565345654456554565645657456584565945660456614566245663456644566545666456674566845669456704567145672456734567445675456764567745678456794568045681456824568345684456854568645687456884568945690456914569245693456944569545696456974569845699457004570145702457034570445705457064570745708457094571045711457124571345714457154571645717457184571945720457214572245723457244572545726457274572845729457304573145732457334573445735457364573745738457394574045741457424574345744457454574645747457484574945750457514575245753457544575545756457574575845759457604576145762457634576445765457664576745768457694577045771457724577345774457754577645777457784577945780457814578245783457844578545786457874578845789457904579145792457934579445795457964579745798457994580045801458024580345804458054580645807458084580945810458114581245813458144581545816458174581845819458204582145822458234582445825458264582745828458294583045831458324583345834458354583645837458384583945840458414584245843458444584545846458474584845849458504585145852458534585445855458564585745858458594586045861458624586345864458654586645867458684586945870458714587245873458744587545876458774587845879458804588145882458834588445885458864588745888458894589045891458924589345894458954589645897458984589945900459014590245903459044590545906459074590845909459104591145912459134591445915459164591745918459194592045921459224592345924459254592645927459284592945930459314593245933459344593545936459374593845939459404594145942459434594445945459464594745948459494595045951459524595345954459554595645957459584595945960459614596245963459644596545966459674596845969459704597145972459734597445975459764597745978459794598045981459824598345984459854598645987459884598945990459914599245993459944599545996459974599845999460004600146002460034600446005460064600746008460094601046011460124601346014460154601646017460184601946020460214602246023460244602546026460274602846029460304603146032460334603446035460364603746038460394604046041460424604346044460454604646047460484604946050460514605246053460544605546056460574605846059460604606146062460634606446065460664606746068460694607046071460724607346074460754607646077460784607946080460814608246083460844608546086460874608846089460904609146092460934609446095460964609746098460994610046101461024610346104461054610646107461084610946110461114611246113461144611546116461174611846119461204612146122461234612446125461264612746128461294613046131461324613346134461354613646137461384613946140461414614246143461444614546146461474614846149461504615146152461534615446155461564615746158461594616046161461624616346164461654616646167461684616946170461714617246173461744617546176461774617846179461804618146182461834618446185461864618746188461894619046191461924619346194461954619646197461984619946200462014620246203462044620546206462074620846209462104621146212462134621446215462164621746218462194622046221462224622346224462254622646227462284622946230462314623246233462344623546236462374623846239462404624146242462434624446245462464624746248462494625046251462524625346254462554625646257462584625946260462614626246263462644626546266462674626846269462704627146272462734627446275462764627746278462794628046281462824628346284462854628646287462884628946290462914629246293462944629546296462974629846299463004630146302463034630446305463064630746308463094631046311463124631346314463154631646317463184631946320463214632246323463244632546326463274632846329463304633146332463334633446335463364633746338463394634046341463424634346344463454634646347463484634946350463514635246353463544635546356463574635846359463604636146362463634636446365463664636746368463694637046371463724637346374463754637646377463784637946380463814638246383463844638546386463874638846389463904639146392463934639446395463964639746398463994640046401464024640346404464054640646407464084640946410464114641246413464144641546416464174641846419464204642146422464234642446425464264642746428464294643046431464324643346434464354643646437464384643946440464414644246443464444644546446464474644846449464504645146452464534645446455464564645746458464594646046461464624646346464464654646646467464684646946470464714647246473464744647546476464774647846479464804648146482464834648446485464864648746488464894649046491464924649346494464954649646497464984649946500465014650246503465044650546506465074650846509465104651146512465134651446515465164651746518465194652046521465224652346524465254652646527465284652946530465314653246533465344653546536465374653846539465404654146542465434654446545465464654746548465494655046551465524655346554465554655646557465584655946560465614656246563465644656546566465674656846569465704657146572465734657446575465764657746578465794658046581465824658346584465854658646587465884658946590465914659246593465944659546596465974659846599466004660146602466034660446605466064660746608466094661046611466124661346614466154661646617466184661946620466214662246623466244662546626466274662846629466304663146632466334663446635466364663746638466394664046641466424664346644466454664646647466484664946650466514665246653466544665546656466574665846659466604666146662466634666446665466664666746668466694667046671466724667346674466754667646677466784667946680466814668246683466844668546686466874668846689466904669146692466934669446695466964669746698466994670046701467024670346704467054670646707467084670946710467114671246713467144671546716467174671846719467204672146722467234672446725467264672746728467294673046731467324673346734467354673646737467384673946740467414674246743467444674546746467474674846749467504675146752467534675446755467564675746758467594676046761467624676346764467654676646767467684676946770467714677246773467744677546776467774677846779467804678146782467834678446785467864678746788467894679046791467924679346794467954679646797467984679946800468014680246803468044680546806468074680846809468104681146812468134681446815468164681746818468194682046821468224682346824468254682646827468284682946830468314683246833468344683546836468374683846839468404684146842468434684446845468464684746848468494685046851468524685346854468554685646857468584685946860468614686246863468644686546866468674686846869468704687146872468734687446875468764687746878468794688046881468824688346884468854688646887468884688946890468914689246893468944689546896468974689846899469004690146902469034690446905469064690746908469094691046911469124691346914469154691646917469184691946920469214692246923469244692546926469274692846929469304693146932469334693446935469364693746938469394694046941469424694346944469454694646947469484694946950469514695246953469544695546956469574695846959469604696146962469634696446965469664696746968469694697046971469724697346974469754697646977469784697946980469814698246983469844698546986469874698846989469904699146992469934699446995469964699746998469994700047001470024700347004470054700647007470084700947010470114701247013470144701547016470174701847019470204702147022470234702447025470264702747028470294703047031470324703347034470354703647037470384703947040470414704247043470444704547046470474704847049470504705147052470534705447055470564705747058470594706047061470624706347064470654706647067470684706947070470714707247073470744707547076470774707847079470804708147082470834708447085470864708747088470894709047091470924709347094470954709647097470984709947100471014710247103471044710547106471074710847109471104711147112471134711447115471164711747118471194712047121471224712347124471254712647127471284712947130471314713247133471344713547136471374713847139471404714147142471434714447145471464714747148471494715047151471524715347154471554715647157471584715947160471614716247163471644716547166471674716847169471704717147172471734717447175471764717747178471794718047181471824718347184471854718647187471884718947190471914719247193471944719547196471974719847199472004720147202472034720447205472064720747208472094721047211472124721347214472154721647217472184721947220472214722247223472244722547226472274722847229472304723147232472334723447235472364723747238472394724047241472424724347244472454724647247472484724947250472514725247253472544725547256472574725847259472604726147262472634726447265472664726747268472694727047271472724727347274472754727647277472784727947280472814728247283472844728547286472874728847289472904729147292472934729447295472964729747298472994730047301473024730347304473054730647307473084730947310473114731247313473144731547316473174731847319473204732147322473234732447325473264732747328473294733047331473324733347334473354733647337473384733947340473414734247343473444734547346473474734847349473504735147352473534735447355473564735747358473594736047361473624736347364473654736647367473684736947370473714737247373473744737547376473774737847379473804738147382473834738447385473864738747388473894739047391473924739347394473954739647397473984739947400474014740247403474044740547406474074740847409474104741147412474134741447415474164741747418474194742047421474224742347424474254742647427474284742947430474314743247433474344743547436474374743847439474404744147442474434744447445474464744747448474494745047451474524745347454474554745647457474584745947460474614746247463474644746547466474674746847469474704747147472474734747447475474764747747478474794748047481474824748347484474854748647487474884748947490474914749247493474944749547496474974749847499475004750147502475034750447505475064750747508475094751047511475124751347514475154751647517475184751947520475214752247523475244752547526475274752847529475304753147532475334753447535475364753747538475394754047541475424754347544475454754647547475484754947550475514755247553475544755547556475574755847559475604756147562475634756447565475664756747568475694757047571475724757347574475754757647577475784757947580475814758247583475844758547586475874758847589475904759147592475934759447595475964759747598475994760047601476024760347604476054760647607476084760947610476114761247613476144761547616476174761847619476204762147622476234762447625476264762747628476294763047631476324763347634476354763647637476384763947640476414764247643476444764547646476474764847649476504765147652476534765447655476564765747658476594766047661476624766347664476654766647667476684766947670476714767247673476744767547676476774767847679476804768147682476834768447685476864768747688476894769047691476924769347694476954769647697476984769947700477014770247703477044770547706477074770847709477104771147712477134771447715477164771747718477194772047721477224772347724477254772647727477284772947730477314773247733477344773547736477374773847739477404774147742477434774447745477464774747748477494775047751477524775347754477554775647757477584775947760477614776247763477644776547766477674776847769477704777147772477734777447775477764777747778477794778047781477824778347784477854778647787477884778947790477914779247793477944779547796477974779847799478004780147802478034780447805478064780747808478094781047811478124781347814478154781647817478184781947820478214782247823478244782547826478274782847829478304783147832478334783447835478364783747838478394784047841478424784347844478454784647847478484784947850478514785247853478544785547856478574785847859478604786147862478634786447865478664786747868478694787047871478724787347874478754787647877478784787947880478814788247883478844788547886478874788847889478904789147892478934789447895478964789747898478994790047901479024790347904479054790647907479084790947910479114791247913479144791547916479174791847919479204792147922479234792447925479264792747928479294793047931479324793347934479354793647937479384793947940479414794247943479444794547946479474794847949479504795147952479534795447955479564795747958479594796047961479624796347964479654796647967479684796947970479714797247973479744797547976479774797847979479804798147982479834798447985479864798747988479894799047991479924799347994479954799647997479984799948000480014800248003480044800548006480074800848009480104801148012480134801448015480164801748018480194802048021480224802348024480254802648027480284802948030480314803248033480344803548036480374803848039480404804148042480434804448045480464804748048480494805048051480524805348054480554805648057480584805948060480614806248063480644806548066480674806848069480704807148072480734807448075480764807748078480794808048081480824808348084480854808648087480884808948090480914809248093480944809548096480974809848099481004810148102481034810448105481064810748108481094811048111481124811348114481154811648117481184811948120481214812248123481244812548126481274812848129481304813148132481334813448135481364813748138481394814048141481424814348144481454814648147481484814948150481514815248153481544815548156481574815848159481604816148162481634816448165481664816748168481694817048171481724817348174481754817648177481784817948180481814818248183481844818548186481874818848189481904819148192481934819448195481964819748198481994820048201482024820348204482054820648207482084820948210482114821248213482144821548216482174821848219482204822148222482234822448225482264822748228482294823048231482324823348234482354823648237482384823948240482414824248243482444824548246482474824848249482504825148252482534825448255482564825748258482594826048261482624826348264482654826648267482684826948270482714827248273482744827548276482774827848279482804828148282482834828448285482864828748288482894829048291482924829348294482954829648297482984829948300483014830248303483044830548306483074830848309483104831148312483134831448315483164831748318483194832048321483224832348324483254832648327483284832948330483314833248333483344833548336483374833848339483404834148342483434834448345483464834748348483494835048351483524835348354483554835648357483584835948360483614836248363483644836548366483674836848369483704837148372483734837448375483764837748378483794838048381483824838348384483854838648387483884838948390483914839248393483944839548396483974839848399484004840148402484034840448405484064840748408484094841048411484124841348414484154841648417484184841948420484214842248423484244842548426484274842848429484304843148432484334843448435484364843748438484394844048441484424844348444484454844648447484484844948450484514845248453484544845548456484574845848459484604846148462484634846448465484664846748468484694847048471484724847348474484754847648477484784847948480484814848248483484844848548486484874848848489484904849148492484934849448495484964849748498484994850048501485024850348504485054850648507485084850948510485114851248513485144851548516485174851848519485204852148522485234852448525485264852748528485294853048531485324853348534485354853648537485384853948540485414854248543485444854548546485474854848549485504855148552485534855448555485564855748558485594856048561485624856348564485654856648567485684856948570485714857248573485744857548576485774857848579485804858148582485834858448585485864858748588485894859048591485924859348594485954859648597485984859948600486014860248603486044860548606486074860848609486104861148612486134861448615486164861748618486194862048621486224862348624486254862648627486284862948630486314863248633486344863548636486374863848639486404864148642486434864448645486464864748648486494865048651486524865348654486554865648657486584865948660486614866248663486644866548666486674866848669486704867148672486734867448675486764867748678486794868048681486824868348684486854868648687486884868948690486914869248693486944869548696486974869848699487004870148702487034870448705487064870748708487094871048711487124871348714487154871648717487184871948720487214872248723487244872548726487274872848729487304873148732487334873448735487364873748738487394874048741487424874348744487454874648747487484874948750487514875248753487544875548756487574875848759487604876148762487634876448765487664876748768487694877048771487724877348774487754877648777487784877948780487814878248783487844878548786487874878848789487904879148792487934879448795487964879748798487994880048801488024880348804488054880648807488084880948810488114881248813488144881548816488174881848819488204882148822488234882448825488264882748828488294883048831488324883348834488354883648837488384883948840488414884248843488444884548846488474884848849488504885148852488534885448855488564885748858488594886048861488624886348864488654886648867488684886948870488714887248873488744887548876488774887848879488804888148882488834888448885488864888748888488894889048891488924889348894488954889648897488984889948900489014890248903489044890548906489074890848909489104891148912489134891448915489164891748918489194892048921489224892348924489254892648927489284892948930489314893248933489344893548936489374893848939489404894148942489434894448945489464894748948489494895048951489524895348954489554895648957489584895948960489614896248963489644896548966489674896848969489704897148972489734897448975489764897748978489794898048981489824898348984489854898648987489884898948990489914899248993489944899548996489974899848999490004900149002490034900449005490064900749008490094901049011490124901349014490154901649017490184901949020490214902249023490244902549026490274902849029490304903149032490334903449035490364903749038490394904049041490424904349044490454904649047490484904949050490514905249053490544905549056490574905849059490604906149062490634906449065490664906749068490694907049071490724907349074490754907649077490784907949080490814908249083490844908549086490874908849089490904909149092490934909449095490964909749098490994910049101491024910349104491054910649107491084910949110491114911249113491144911549116491174911849119491204912149122491234912449125491264912749128491294913049131491324913349134491354913649137491384913949140491414914249143491444914549146491474914849149491504915149152491534915449155491564915749158491594916049161491624916349164491654916649167491684916949170491714917249173491744917549176491774917849179491804918149182491834918449185491864918749188491894919049191491924919349194491954919649197491984919949200492014920249203492044920549206492074920849209492104921149212492134921449215492164921749218492194922049221492224922349224492254922649227492284922949230492314923249233492344923549236492374923849239492404924149242492434924449245492464924749248492494925049251492524925349254492554925649257492584925949260492614926249263492644926549266492674926849269492704927149272492734927449275492764927749278492794928049281492824928349284492854928649287492884928949290492914929249293492944929549296492974929849299493004930149302493034930449305493064930749308493094931049311493124931349314493154931649317493184931949320493214932249323493244932549326493274932849329493304933149332493334933449335493364933749338493394934049341493424934349344493454934649347493484934949350493514935249353493544935549356493574935849359493604936149362493634936449365493664936749368493694937049371493724937349374493754937649377493784937949380493814938249383493844938549386493874938849389493904939149392493934939449395493964939749398493994940049401494024940349404494054940649407494084940949410494114941249413494144941549416494174941849419494204942149422494234942449425494264942749428494294943049431494324943349434494354943649437494384943949440494414944249443494444944549446494474944849449494504945149452494534945449455494564945749458494594946049461494624946349464494654946649467494684946949470494714947249473494744947549476494774947849479494804948149482494834948449485494864948749488494894949049491494924949349494494954949649497494984949949500495014950249503495044950549506495074950849509495104951149512495134951449515495164951749518495194952049521495224952349524495254952649527495284952949530495314953249533495344953549536495374953849539495404954149542495434954449545495464954749548495494955049551495524955349554495554955649557495584955949560495614956249563495644956549566495674956849569495704957149572495734957449575495764957749578495794958049581495824958349584495854958649587495884958949590495914959249593495944959549596495974959849599496004960149602496034960449605496064960749608496094961049611496124961349614496154961649617496184961949620496214962249623496244962549626496274962849629496304963149632496334963449635496364963749638496394964049641496424964349644496454964649647496484964949650496514965249653496544965549656496574965849659496604966149662496634966449665496664966749668496694967049671496724967349674496754967649677496784967949680496814968249683496844968549686496874968849689496904969149692496934969449695496964969749698496994970049701497024970349704497054970649707497084970949710497114971249713497144971549716497174971849719497204972149722497234972449725497264972749728497294973049731497324973349734497354973649737497384973949740497414974249743497444974549746497474974849749497504975149752497534975449755497564975749758497594976049761497624976349764497654976649767497684976949770497714977249773497744977549776497774977849779497804978149782497834978449785497864978749788497894979049791497924979349794497954979649797497984979949800498014980249803498044980549806498074980849809498104981149812498134981449815498164981749818498194982049821498224982349824498254982649827498284982949830498314983249833498344983549836498374983849839498404984149842498434984449845498464984749848498494985049851498524985349854498554985649857498584985949860498614986249863498644986549866498674986849869498704987149872498734987449875498764987749878498794988049881498824988349884498854988649887498884988949890498914989249893498944989549896498974989849899499004990149902499034990449905499064990749908499094991049911499124991349914499154991649917499184991949920499214992249923499244992549926499274992849929499304993149932499334993449935499364993749938499394994049941499424994349944499454994649947499484994949950499514995249953499544995549956499574995849959499604996149962499634996449965499664996749968499694997049971499724997349974499754997649977499784997949980499814998249983499844998549986499874998849989499904999149992499934999449995499964999749998499995000050001500025000350004500055000650007500085000950010500115001250013500145001550016500175001850019500205002150022500235002450025500265002750028500295003050031500325003350034500355003650037500385003950040500415004250043500445004550046500475004850049500505005150052500535005450055500565005750058500595006050061500625006350064500655006650067500685006950070500715007250073500745007550076500775007850079500805008150082500835008450085500865008750088500895009050091500925009350094500955009650097500985009950100501015010250103501045010550106501075010850109501105011150112501135011450115501165011750118501195012050121501225012350124501255012650127501285012950130501315013250133501345013550136501375013850139501405014150142501435014450145501465014750148501495015050151501525015350154501555015650157501585015950160501615016250163501645016550166501675016850169501705017150172501735017450175501765017750178501795018050181501825018350184501855018650187501885018950190501915019250193501945019550196501975019850199502005020150202502035020450205502065020750208502095021050211502125021350214502155021650217502185021950220502215022250223502245022550226502275022850229502305023150232502335023450235502365023750238502395024050241502425024350244502455024650247502485024950250502515025250253502545025550256502575025850259502605026150262502635026450265502665026750268502695027050271502725027350274502755027650277502785027950280502815028250283502845028550286502875028850289502905029150292502935029450295502965029750298502995030050301503025030350304503055030650307503085030950310503115031250313503145031550316503175031850319503205032150322503235032450325503265032750328503295033050331503325033350334503355033650337503385033950340503415034250343503445034550346503475034850349503505035150352503535035450355503565035750358503595036050361503625036350364503655036650367503685036950370503715037250373503745037550376503775037850379503805038150382503835038450385503865038750388503895039050391503925039350394503955039650397503985039950400504015040250403504045040550406504075040850409504105041150412504135041450415504165041750418504195042050421504225042350424504255042650427504285042950430504315043250433504345043550436504375043850439504405044150442504435044450445504465044750448504495045050451504525045350454504555045650457504585045950460504615046250463504645046550466504675046850469504705047150472504735047450475504765047750478504795048050481504825048350484504855048650487504885048950490504915049250493504945049550496504975049850499505005050150502505035050450505505065050750508505095051050511505125051350514505155051650517505185051950520505215052250523505245052550526505275052850529505305053150532505335053450535505365053750538505395054050541505425054350544505455054650547505485054950550505515055250553505545055550556505575055850559505605056150562505635056450565505665056750568505695057050571505725057350574505755057650577505785057950580505815058250583505845058550586505875058850589505905059150592505935059450595505965059750598505995060050601506025060350604506055060650607506085060950610506115061250613506145061550616506175061850619506205062150622506235062450625506265062750628506295063050631506325063350634506355063650637506385063950640506415064250643506445064550646506475064850649506505065150652506535065450655506565065750658506595066050661506625066350664506655066650667506685066950670506715067250673506745067550676506775067850679506805068150682506835068450685506865068750688506895069050691506925069350694506955069650697506985069950700507015070250703507045070550706507075070850709507105071150712507135071450715507165071750718507195072050721507225072350724507255072650727507285072950730507315073250733507345073550736507375073850739507405074150742507435074450745507465074750748507495075050751507525075350754507555075650757507585075950760507615076250763507645076550766507675076850769507705077150772507735077450775507765077750778507795078050781507825078350784507855078650787507885078950790507915079250793507945079550796507975079850799508005080150802508035080450805508065080750808508095081050811508125081350814508155081650817508185081950820508215082250823508245082550826508275082850829508305083150832508335083450835508365083750838508395084050841508425084350844508455084650847508485084950850508515085250853508545085550856508575085850859508605086150862508635086450865508665086750868508695087050871508725087350874508755087650877508785087950880508815088250883508845088550886508875088850889508905089150892508935089450895508965089750898508995090050901509025090350904509055090650907509085090950910509115091250913509145091550916509175091850919509205092150922509235092450925509265092750928509295093050931509325093350934509355093650937509385093950940509415094250943509445094550946509475094850949509505095150952509535095450955509565095750958509595096050961509625096350964509655096650967509685096950970509715097250973509745097550976509775097850979509805098150982509835098450985509865098750988509895099050991509925099350994509955099650997509985099951000510015100251003510045100551006510075100851009510105101151012510135101451015510165101751018510195102051021510225102351024510255102651027510285102951030510315103251033510345103551036510375103851039510405104151042510435104451045510465104751048510495105051051510525105351054510555105651057510585105951060510615106251063510645106551066510675106851069510705107151072510735107451075510765107751078510795108051081510825108351084510855108651087510885108951090510915109251093510945109551096510975109851099511005110151102511035110451105511065110751108511095111051111511125111351114511155111651117511185111951120511215112251123511245112551126511275112851129511305113151132511335113451135511365113751138511395114051141511425114351144511455114651147511485114951150511515115251153511545115551156511575115851159511605116151162511635116451165511665116751168511695117051171511725117351174511755117651177511785117951180511815118251183511845118551186511875118851189511905119151192511935119451195511965119751198511995120051201512025120351204512055120651207512085120951210512115121251213512145121551216512175121851219512205122151222512235122451225512265122751228512295123051231512325123351234512355123651237512385123951240512415124251243512445124551246512475124851249512505125151252512535125451255512565125751258512595126051261512625126351264512655126651267512685126951270512715127251273512745127551276512775127851279512805128151282512835128451285512865128751288512895129051291512925129351294512955129651297512985129951300513015130251303513045130551306513075130851309513105131151312513135131451315513165131751318513195132051321513225132351324513255132651327513285132951330513315133251333513345133551336513375133851339513405134151342513435134451345513465134751348513495135051351513525135351354513555135651357513585135951360513615136251363513645136551366513675136851369513705137151372513735137451375513765137751378513795138051381513825138351384513855138651387513885138951390513915139251393513945139551396513975139851399514005140151402514035140451405514065140751408514095141051411514125141351414514155141651417514185141951420514215142251423514245142551426514275142851429514305143151432514335143451435514365143751438514395144051441514425144351444514455144651447514485144951450514515145251453514545145551456514575145851459514605146151462514635146451465514665146751468514695147051471514725147351474514755147651477514785147951480514815148251483514845148551486514875148851489514905149151492514935149451495514965149751498514995150051501515025150351504515055150651507515085150951510515115151251513515145151551516515175151851519515205152151522515235152451525515265152751528515295153051531515325153351534515355153651537515385153951540515415154251543515445154551546515475154851549515505155151552515535155451555515565155751558515595156051561515625156351564515655156651567515685156951570515715157251573515745157551576515775157851579515805158151582515835158451585515865158751588515895159051591515925159351594515955159651597515985159951600516015160251603516045160551606516075160851609516105161151612516135161451615516165161751618516195162051621516225162351624516255162651627516285162951630516315163251633516345163551636516375163851639516405164151642516435164451645516465164751648516495165051651516525165351654516555165651657516585165951660516615166251663516645166551666516675166851669516705167151672516735167451675516765167751678516795168051681516825168351684516855168651687516885168951690516915169251693516945169551696516975169851699517005170151702517035170451705517065170751708517095171051711517125171351714517155171651717517185171951720517215172251723517245172551726517275172851729517305173151732517335173451735517365173751738517395174051741517425174351744517455174651747517485174951750517515175251753517545175551756517575175851759517605176151762517635176451765517665176751768517695177051771517725177351774517755177651777517785177951780517815178251783517845178551786517875178851789517905179151792517935179451795517965179751798517995180051801518025180351804518055180651807518085180951810518115181251813518145181551816518175181851819518205182151822518235182451825518265182751828518295183051831518325183351834518355183651837518385183951840518415184251843518445184551846518475184851849518505185151852518535185451855518565185751858518595186051861518625186351864518655186651867518685186951870518715187251873518745187551876518775187851879518805188151882518835188451885518865188751888518895189051891518925189351894518955189651897518985189951900519015190251903519045190551906519075190851909519105191151912519135191451915519165191751918519195192051921519225192351924519255192651927519285192951930519315193251933519345193551936519375193851939519405194151942519435194451945519465194751948519495195051951519525195351954519555195651957519585195951960519615196251963519645196551966519675196851969519705197151972519735197451975519765197751978519795198051981519825198351984519855198651987519885198951990519915199251993519945199551996519975199851999520005200152002520035200452005520065200752008520095201052011520125201352014520155201652017520185201952020520215202252023520245202552026520275202852029520305203152032520335203452035520365203752038520395204052041520425204352044520455204652047520485204952050520515205252053520545205552056520575205852059520605206152062520635206452065520665206752068520695207052071520725207352074520755207652077520785207952080520815208252083520845208552086520875208852089520905209152092520935209452095520965209752098520995210052101521025210352104521055210652107521085210952110521115211252113521145211552116521175211852119521205212152122521235212452125521265212752128521295213052131521325213352134521355213652137521385213952140521415214252143521445214552146521475214852149521505215152152521535215452155521565215752158521595216052161521625216352164521655216652167521685216952170521715217252173521745217552176521775217852179521805218152182521835218452185521865218752188521895219052191521925219352194521955219652197521985219952200522015220252203522045220552206522075220852209522105221152212522135221452215522165221752218522195222052221522225222352224522255222652227522285222952230522315223252233522345223552236522375223852239522405224152242522435224452245522465224752248522495225052251522525225352254522555225652257522585225952260522615226252263522645226552266522675226852269522705227152272522735227452275522765227752278522795228052281522825228352284522855228652287522885228952290522915229252293522945229552296522975229852299523005230152302523035230452305523065230752308523095231052311523125231352314523155231652317523185231952320523215232252323523245232552326523275232852329523305233152332523335233452335523365233752338523395234052341523425234352344523455234652347523485234952350523515235252353523545235552356523575235852359523605236152362523635236452365523665236752368523695237052371523725237352374523755237652377523785237952380523815238252383523845238552386523875238852389523905239152392523935239452395523965239752398523995240052401524025240352404524055240652407524085240952410524115241252413524145241552416524175241852419524205242152422524235242452425524265242752428524295243052431524325243352434524355243652437524385243952440524415244252443524445244552446524475244852449524505245152452524535245452455524565245752458524595246052461524625246352464524655246652467524685246952470524715247252473524745247552476524775247852479524805248152482524835248452485524865248752488524895249052491524925249352494524955249652497524985249952500525015250252503525045250552506525075250852509525105251152512525135251452515525165251752518525195252052521525225252352524525255252652527525285252952530525315253252533525345253552536525375253852539525405254152542525435254452545525465254752548525495255052551525525255352554525555255652557525585255952560525615256252563525645256552566525675256852569525705257152572525735257452575525765257752578525795258052581525825258352584525855258652587525885258952590525915259252593525945259552596525975259852599526005260152602526035260452605526065260752608526095261052611526125261352614526155261652617526185261952620526215262252623526245262552626526275262852629526305263152632526335263452635526365263752638526395264052641526425264352644526455264652647526485264952650526515265252653526545265552656526575265852659526605266152662526635266452665526665266752668526695267052671526725267352674526755267652677526785267952680526815268252683526845268552686526875268852689526905269152692526935269452695526965269752698526995270052701527025270352704527055270652707527085270952710527115271252713527145271552716527175271852719527205272152722527235272452725527265272752728527295273052731527325273352734527355273652737527385273952740527415274252743527445274552746527475274852749527505275152752527535275452755527565275752758527595276052761527625276352764527655276652767527685276952770527715277252773527745277552776527775277852779527805278152782527835278452785527865278752788527895279052791527925279352794527955279652797527985279952800528015280252803528045280552806528075280852809528105281152812528135281452815528165281752818528195282052821528225282352824528255282652827528285282952830528315283252833528345283552836528375283852839528405284152842528435284452845528465284752848528495285052851528525285352854528555285652857528585285952860528615286252863528645286552866528675286852869528705287152872528735287452875528765287752878528795288052881528825288352884528855288652887528885288952890528915289252893528945289552896528975289852899529005290152902529035290452905529065290752908529095291052911529125291352914529155291652917529185291952920529215292252923529245292552926529275292852929529305293152932529335293452935529365293752938529395294052941529425294352944529455294652947529485294952950529515295252953529545295552956529575295852959529605296152962529635296452965529665296752968529695297052971529725297352974529755297652977529785297952980529815298252983529845298552986529875298852989529905299152992529935299452995529965299752998529995300053001530025300353004530055300653007530085300953010530115301253013530145301553016530175301853019530205302153022530235302453025530265302753028530295303053031530325303353034530355303653037530385303953040530415304253043530445304553046530475304853049530505305153052530535305453055530565305753058530595306053061530625306353064530655306653067530685306953070530715307253073530745307553076530775307853079530805308153082530835308453085530865308753088530895309053091530925309353094530955309653097530985309953100531015310253103531045310553106531075310853109531105311153112531135311453115531165311753118531195312053121531225312353124531255312653127531285312953130531315313253133531345313553136531375313853139531405314153142531435314453145531465314753148531495315053151531525315353154531555315653157531585315953160531615316253163531645316553166531675316853169531705317153172531735317453175531765317753178531795318053181531825318353184531855318653187531885318953190531915319253193531945319553196531975319853199532005320153202532035320453205532065320753208532095321053211532125321353214532155321653217532185321953220532215322253223532245322553226532275322853229532305323153232532335323453235532365323753238532395324053241532425324353244532455324653247532485324953250532515325253253532545325553256532575325853259532605326153262532635326453265532665326753268532695327053271532725327353274532755327653277532785327953280532815328253283532845328553286532875328853289532905329153292532935329453295532965329753298532995330053301533025330353304533055330653307533085330953310533115331253313533145331553316533175331853319533205332153322533235332453325533265332753328533295333053331533325333353334533355333653337533385333953340533415334253343533445334553346533475334853349533505335153352533535335453355533565335753358533595336053361533625336353364533655336653367533685336953370533715337253373533745337553376533775337853379533805338153382533835338453385533865338753388533895339053391533925339353394533955339653397533985339953400534015340253403534045340553406534075340853409534105341153412534135341453415534165341753418534195342053421534225342353424534255342653427534285342953430534315343253433534345343553436534375343853439534405344153442534435344453445534465344753448534495345053451534525345353454534555345653457534585345953460534615346253463534645346553466534675346853469534705347153472534735347453475534765347753478534795348053481534825348353484534855348653487534885348953490534915349253493534945349553496534975349853499535005350153502535035350453505535065350753508535095351053511535125351353514535155351653517535185351953520535215352253523535245352553526535275352853529535305353153532535335353453535535365353753538535395354053541535425354353544535455354653547535485354953550535515355253553535545355553556535575355853559535605356153562535635356453565535665356753568535695357053571535725357353574535755357653577535785357953580535815358253583535845358553586535875358853589535905359153592535935359453595535965359753598535995360053601536025360353604536055360653607536085360953610536115361253613536145361553616536175361853619536205362153622536235362453625536265362753628536295363053631536325363353634536355363653637536385363953640536415364253643536445364553646536475364853649536505365153652536535365453655536565365753658536595366053661536625366353664536655366653667536685366953670536715367253673536745367553676536775367853679536805368153682536835368453685536865368753688536895369053691536925369353694536955369653697536985369953700537015370253703537045370553706537075370853709537105371153712537135371453715537165371753718537195372053721537225372353724537255372653727537285372953730537315373253733537345373553736537375373853739537405374153742537435374453745537465374753748537495375053751537525375353754537555375653757537585375953760537615376253763537645376553766537675376853769537705377153772537735377453775537765377753778537795378053781537825378353784537855378653787537885378953790537915379253793537945379553796537975379853799538005380153802538035380453805538065380753808538095381053811538125381353814538155381653817538185381953820538215382253823538245382553826538275382853829538305383153832538335383453835538365383753838538395384053841538425384353844538455384653847538485384953850538515385253853538545385553856538575385853859538605386153862538635386453865538665386753868538695387053871538725387353874538755387653877538785387953880538815388253883538845388553886538875388853889538905389153892538935389453895538965389753898538995390053901539025390353904539055390653907539085390953910539115391253913539145391553916539175391853919539205392153922539235392453925539265392753928539295393053931539325393353934539355393653937539385393953940539415394253943539445394553946539475394853949539505395153952539535395453955539565395753958539595396053961539625396353964539655396653967539685396953970539715397253973539745397553976539775397853979539805398153982539835398453985539865398753988539895399053991539925399353994539955399653997539985399954000540015400254003540045400554006540075400854009540105401154012540135401454015540165401754018540195402054021540225402354024540255402654027540285402954030540315403254033540345403554036540375403854039540405404154042540435404454045540465404754048540495405054051540525405354054540555405654057540585405954060540615406254063540645406554066540675406854069540705407154072540735407454075540765407754078540795408054081540825408354084540855408654087540885408954090540915409254093540945409554096540975409854099541005410154102541035410454105541065410754108541095411054111541125411354114541155411654117541185411954120541215412254123541245412554126541275412854129541305413154132541335413454135541365413754138541395414054141541425414354144541455414654147541485414954150541515415254153541545415554156541575415854159541605416154162541635416454165541665416754168541695417054171541725417354174541755417654177541785417954180541815418254183541845418554186541875418854189541905419154192541935419454195541965419754198541995420054201542025420354204542055420654207542085420954210542115421254213542145421554216542175421854219542205422154222542235422454225542265422754228542295423054231542325423354234542355423654237542385423954240542415424254243542445424554246542475424854249542505425154252542535425454255542565425754258542595426054261542625426354264542655426654267542685426954270542715427254273542745427554276542775427854279542805428154282542835428454285542865428754288542895429054291542925429354294542955429654297542985429954300543015430254303543045430554306543075430854309543105431154312543135431454315543165431754318543195432054321543225432354324543255432654327543285432954330543315433254333543345433554336543375433854339543405434154342543435434454345543465434754348543495435054351543525435354354543555435654357543585435954360543615436254363543645436554366543675436854369543705437154372543735437454375543765437754378543795438054381543825438354384543855438654387543885438954390543915439254393543945439554396543975439854399544005440154402544035440454405544065440754408544095441054411544125441354414544155441654417544185441954420544215442254423544245442554426544275442854429544305443154432544335443454435544365443754438544395444054441544425444354444544455444654447544485444954450544515445254453544545445554456544575445854459544605446154462544635446454465544665446754468544695447054471544725447354474544755447654477544785447954480544815448254483544845448554486544875448854489544905449154492544935449454495544965449754498544995450054501545025450354504545055450654507545085450954510545115451254513545145451554516545175451854519545205452154522545235452454525545265452754528545295453054531545325453354534545355453654537545385453954540545415454254543545445454554546545475454854549545505455154552545535455454555545565455754558545595456054561545625456354564545655456654567545685456954570545715457254573545745457554576545775457854579545805458154582545835458454585545865458754588545895459054591545925459354594545955459654597545985459954600546015460254603546045460554606546075460854609546105461154612546135461454615546165461754618546195462054621546225462354624546255462654627546285462954630546315463254633546345463554636546375463854639546405464154642546435464454645546465464754648546495465054651546525465354654546555465654657546585465954660546615466254663546645466554666546675466854669546705467154672546735467454675546765467754678546795468054681546825468354684546855468654687546885468954690546915469254693546945469554696546975469854699547005470154702547035470454705547065470754708547095471054711547125471354714547155471654717547185471954720547215472254723547245472554726547275472854729547305473154732547335473454735547365473754738547395474054741547425474354744547455474654747547485474954750547515475254753547545475554756547575475854759547605476154762547635476454765547665476754768547695477054771547725477354774547755477654777547785477954780547815478254783547845478554786547875478854789547905479154792547935479454795547965479754798547995480054801548025480354804548055480654807548085480954810548115481254813548145481554816548175481854819548205482154822548235482454825548265482754828548295483054831548325483354834548355483654837548385483954840548415484254843548445484554846548475484854849548505485154852548535485454855548565485754858548595486054861548625486354864548655486654867548685486954870548715487254873548745487554876548775487854879548805488154882548835488454885548865488754888548895489054891548925489354894548955489654897548985489954900549015490254903549045490554906549075490854909549105491154912549135491454915549165491754918549195492054921549225492354924549255492654927549285492954930549315493254933549345493554936549375493854939549405494154942549435494454945549465494754948549495495054951549525495354954549555495654957549585495954960549615496254963549645496554966549675496854969549705497154972549735497454975549765497754978549795498054981549825498354984549855498654987549885498954990549915499254993549945499554996549975499854999550005500155002550035500455005550065500755008550095501055011550125501355014550155501655017550185501955020550215502255023550245502555026550275502855029550305503155032550335503455035550365503755038550395504055041550425504355044550455504655047550485504955050550515505255053550545505555056550575505855059550605506155062550635506455065550665506755068550695507055071550725507355074550755507655077550785507955080550815508255083550845508555086550875508855089550905509155092550935509455095550965509755098550995510055101551025510355104551055510655107551085510955110551115511255113551145511555116551175511855119551205512155122551235512455125551265512755128551295513055131551325513355134551355513655137551385513955140551415514255143551445514555146551475514855149551505515155152551535515455155551565515755158551595516055161551625516355164551655516655167551685516955170551715517255173551745517555176551775517855179551805518155182551835518455185551865518755188551895519055191551925519355194551955519655197551985519955200552015520255203552045520555206552075520855209552105521155212552135521455215552165521755218552195522055221552225522355224552255522655227552285522955230552315523255233552345523555236552375523855239552405524155242552435524455245552465524755248552495525055251552525525355254552555525655257552585525955260552615526255263552645526555266552675526855269552705527155272552735527455275552765527755278552795528055281552825528355284552855528655287552885528955290552915529255293552945529555296552975529855299553005530155302553035530455305553065530755308553095531055311553125531355314553155531655317553185531955320553215532255323553245532555326553275532855329553305533155332553335533455335553365533755338553395534055341553425534355344553455534655347553485534955350553515535255353553545535555356553575535855359553605536155362553635536455365553665536755368553695537055371553725537355374553755537655377553785537955380553815538255383553845538555386553875538855389553905539155392553935539455395553965539755398553995540055401554025540355404554055540655407554085540955410554115541255413554145541555416554175541855419554205542155422554235542455425554265542755428554295543055431554325543355434554355543655437554385543955440554415544255443554445544555446554475544855449554505545155452554535545455455554565545755458554595546055461554625546355464554655546655467554685546955470554715547255473554745547555476554775547855479554805548155482554835548455485554865548755488554895549055491554925549355494554955549655497554985549955500555015550255503555045550555506555075550855509555105551155512555135551455515555165551755518555195552055521555225552355524555255552655527555285552955530555315553255533555345553555536555375553855539555405554155542555435554455545555465554755548555495555055551555525555355554555555555655557555585555955560555615556255563555645556555566555675556855569555705557155572555735557455575555765557755578555795558055581555825558355584555855558655587555885558955590555915559255593555945559555596555975559855599556005560155602556035560455605556065560755608556095561055611556125561355614556155561655617556185561955620556215562255623556245562555626556275562855629556305563155632556335563455635556365563755638556395564055641556425564355644556455564655647556485564955650556515565255653556545565555656556575565855659556605566155662556635566455665556665566755668556695567055671556725567355674556755567655677556785567955680556815568255683556845568555686556875568855689556905569155692556935569455695556965569755698556995570055701557025570355704557055570655707557085570955710557115571255713557145571555716557175571855719557205572155722557235572455725557265572755728557295573055731557325573355734557355573655737557385573955740557415574255743557445574555746557475574855749557505575155752557535575455755557565575755758557595576055761557625576355764557655576655767557685576955770557715577255773557745577555776557775577855779557805578155782557835578455785557865578755788557895579055791557925579355794557955579655797557985579955800558015580255803558045580555806558075580855809558105581155812558135581455815558165581755818558195582055821558225582355824558255582655827558285582955830558315583255833558345583555836558375583855839558405584155842558435584455845558465584755848558495585055851558525585355854558555585655857558585585955860558615586255863558645586555866558675586855869558705587155872558735587455875558765587755878558795588055881558825588355884558855588655887558885588955890558915589255893558945589555896558975589855899559005590155902559035590455905559065590755908559095591055911559125591355914559155591655917559185591955920559215592255923559245592555926559275592855929559305593155932559335593455935559365593755938559395594055941559425594355944559455594655947559485594955950559515595255953559545595555956559575595855959559605596155962559635596455965559665596755968559695597055971559725597355974559755597655977559785597955980559815598255983559845598555986559875598855989559905599155992559935599455995559965599755998559995600056001560025600356004560055600656007560085600956010560115601256013560145601556016560175601856019560205602156022560235602456025560265602756028560295603056031560325603356034560355603656037560385603956040560415604256043560445604556046560475604856049560505605156052560535605456055560565605756058560595606056061560625606356064560655606656067560685606956070560715607256073560745607556076560775607856079560805608156082560835608456085560865608756088560895609056091560925609356094560955609656097560985609956100561015610256103561045610556106561075610856109561105611156112561135611456115561165611756118561195612056121561225612356124561255612656127561285612956130561315613256133561345613556136561375613856139561405614156142561435614456145561465614756148561495615056151561525615356154561555615656157561585615956160561615616256163561645616556166561675616856169561705617156172561735617456175561765617756178561795618056181561825618356184561855618656187561885618956190561915619256193561945619556196561975619856199562005620156202562035620456205562065620756208562095621056211562125621356214562155621656217562185621956220562215622256223562245622556226562275622856229562305623156232562335623456235562365623756238562395624056241562425624356244562455624656247562485624956250562515625256253562545625556256562575625856259562605626156262562635626456265562665626756268562695627056271562725627356274562755627656277562785627956280562815628256283562845628556286562875628856289562905629156292562935629456295562965629756298562995630056301563025630356304563055630656307563085630956310563115631256313563145631556316563175631856319563205632156322563235632456325563265632756328563295633056331563325633356334563355633656337563385633956340563415634256343563445634556346563475634856349563505635156352563535635456355563565635756358563595636056361563625636356364563655636656367563685636956370563715637256373563745637556376563775637856379563805638156382563835638456385563865638756388563895639056391563925639356394563955639656397563985639956400564015640256403564045640556406564075640856409564105641156412564135641456415564165641756418564195642056421564225642356424564255642656427564285642956430564315643256433564345643556436564375643856439564405644156442564435644456445564465644756448564495645056451564525645356454564555645656457564585645956460564615646256463564645646556466564675646856469564705647156472564735647456475564765647756478564795648056481564825648356484564855648656487564885648956490564915649256493564945649556496564975649856499565005650156502565035650456505565065650756508565095651056511565125651356514565155651656517565185651956520565215652256523565245652556526565275652856529565305653156532565335653456535565365653756538565395654056541565425654356544565455654656547565485654956550565515655256553565545655556556565575655856559565605656156562565635656456565565665656756568565695657056571565725657356574565755657656577565785657956580565815658256583565845658556586565875658856589565905659156592565935659456595565965659756598565995660056601566025660356604566055660656607566085660956610566115661256613566145661556616566175661856619566205662156622566235662456625566265662756628566295663056631566325663356634566355663656637566385663956640566415664256643566445664556646566475664856649566505665156652566535665456655566565665756658566595666056661566625666356664566655666656667566685666956670566715667256673566745667556676566775667856679566805668156682566835668456685566865668756688566895669056691566925669356694566955669656697566985669956700567015670256703567045670556706567075670856709567105671156712567135671456715567165671756718567195672056721567225672356724567255672656727567285672956730567315673256733567345673556736567375673856739567405674156742567435674456745567465674756748567495675056751567525675356754567555675656757567585675956760567615676256763567645676556766567675676856769567705677156772567735677456775567765677756778567795678056781567825678356784567855678656787567885678956790567915679256793567945679556796567975679856799568005680156802568035680456805568065680756808568095681056811568125681356814568155681656817568185681956820568215682256823568245682556826568275682856829568305683156832568335683456835568365683756838568395684056841568425684356844568455684656847568485684956850568515685256853568545685556856568575685856859568605686156862568635686456865568665686756868568695687056871568725687356874568755687656877568785687956880568815688256883568845688556886568875688856889568905689156892568935689456895568965689756898568995690056901569025690356904569055690656907569085690956910569115691256913569145691556916569175691856919569205692156922569235692456925569265692756928569295693056931569325693356934569355693656937569385693956940569415694256943569445694556946569475694856949569505695156952569535695456955569565695756958569595696056961569625696356964569655696656967569685696956970569715697256973569745697556976569775697856979569805698156982569835698456985569865698756988569895699056991569925699356994569955699656997569985699957000570015700257003570045700557006570075700857009570105701157012570135701457015570165701757018570195702057021570225702357024570255702657027570285702957030570315703257033570345703557036570375703857039570405704157042570435704457045570465704757048570495705057051570525705357054570555705657057570585705957060570615706257063570645706557066570675706857069570705707157072570735707457075570765707757078570795708057081570825708357084570855708657087570885708957090570915709257093570945709557096570975709857099571005710157102571035710457105571065710757108571095711057111571125711357114571155711657117571185711957120571215712257123571245712557126571275712857129571305713157132571335713457135571365713757138571395714057141571425714357144571455714657147571485714957150571515715257153571545715557156571575715857159571605716157162571635716457165571665716757168571695717057171571725717357174571755717657177571785717957180571815718257183571845718557186571875718857189571905719157192571935719457195571965719757198571995720057201572025720357204572055720657207572085720957210572115721257213572145721557216572175721857219572205722157222572235722457225572265722757228572295723057231572325723357234572355723657237572385723957240572415724257243572445724557246572475724857249572505725157252572535725457255572565725757258572595726057261572625726357264572655726657267572685726957270572715727257273572745727557276572775727857279572805728157282572835728457285572865728757288572895729057291572925729357294572955729657297572985729957300573015730257303573045730557306573075730857309573105731157312573135731457315573165731757318573195732057321573225732357324573255732657327573285732957330573315733257333573345733557336573375733857339573405734157342573435734457345573465734757348573495735057351573525735357354573555735657357573585735957360573615736257363573645736557366573675736857369573705737157372573735737457375573765737757378573795738057381573825738357384573855738657387573885738957390573915739257393573945739557396573975739857399574005740157402574035740457405574065740757408574095741057411574125741357414574155741657417574185741957420574215742257423574245742557426574275742857429574305743157432574335743457435574365743757438574395744057441574425744357444574455744657447574485744957450574515745257453574545745557456574575745857459574605746157462574635746457465574665746757468574695747057471574725747357474574755747657477574785747957480574815748257483574845748557486574875748857489574905749157492574935749457495574965749757498574995750057501575025750357504575055750657507575085750957510575115751257513575145751557516575175751857519575205752157522575235752457525575265752757528575295753057531575325753357534575355753657537575385753957540575415754257543575445754557546575475754857549575505755157552575535755457555575565755757558575595756057561575625756357564575655756657567575685756957570575715757257573575745757557576575775757857579575805758157582575835758457585575865758757588575895759057591575925759357594575955759657597575985759957600576015760257603576045760557606576075760857609576105761157612576135761457615576165761757618576195762057621576225762357624576255762657627576285762957630576315763257633576345763557636576375763857639576405764157642576435764457645576465764757648576495765057651576525765357654576555765657657576585765957660576615766257663576645766557666576675766857669576705767157672576735767457675576765767757678576795768057681576825768357684576855768657687576885768957690576915769257693576945769557696576975769857699577005770157702577035770457705577065770757708577095771057711577125771357714577155771657717577185771957720577215772257723577245772557726577275772857729577305773157732577335773457735577365773757738577395774057741577425774357744577455774657747577485774957750577515775257753577545775557756577575775857759577605776157762577635776457765577665776757768577695777057771577725777357774577755777657777577785777957780577815778257783577845778557786577875778857789577905779157792577935779457795577965779757798577995780057801578025780357804578055780657807578085780957810578115781257813578145781557816578175781857819578205782157822578235782457825578265782757828578295783057831578325783357834578355783657837578385783957840578415784257843578445784557846578475784857849578505785157852578535785457855578565785757858578595786057861578625786357864578655786657867578685786957870578715787257873578745787557876578775787857879578805788157882578835788457885578865788757888578895789057891578925789357894578955789657897578985789957900579015790257903579045790557906579075790857909579105791157912579135791457915579165791757918579195792057921579225792357924579255792657927579285792957930579315793257933579345793557936579375793857939579405794157942579435794457945579465794757948579495795057951579525795357954579555795657957579585795957960579615796257963579645796557966579675796857969579705797157972579735797457975579765797757978579795798057981579825798357984579855798657987579885798957990579915799257993579945799557996579975799857999580005800158002580035800458005580065800758008580095801058011580125801358014580155801658017580185801958020580215802258023580245802558026580275802858029580305803158032580335803458035580365803758038580395804058041580425804358044580455804658047580485804958050580515805258053580545805558056580575805858059580605806158062580635806458065580665806758068580695807058071580725807358074580755807658077580785807958080580815808258083580845808558086580875808858089580905809158092580935809458095580965809758098580995810058101581025810358104581055810658107581085810958110581115811258113581145811558116581175811858119581205812158122581235812458125581265812758128581295813058131581325813358134581355813658137581385813958140581415814258143581445814558146581475814858149581505815158152581535815458155581565815758158581595816058161581625816358164581655816658167581685816958170581715817258173581745817558176581775817858179581805818158182581835818458185581865818758188581895819058191581925819358194581955819658197581985819958200582015820258203582045820558206582075820858209582105821158212582135821458215582165821758218582195822058221582225822358224582255822658227582285822958230582315823258233582345823558236582375823858239582405824158242582435824458245582465824758248582495825058251582525825358254582555825658257582585825958260582615826258263582645826558266582675826858269582705827158272582735827458275582765827758278582795828058281582825828358284582855828658287582885828958290582915829258293582945829558296582975829858299583005830158302583035830458305583065830758308583095831058311583125831358314583155831658317583185831958320583215832258323583245832558326583275832858329583305833158332583335833458335583365833758338583395834058341583425834358344583455834658347583485834958350583515835258353583545835558356583575835858359583605836158362583635836458365583665836758368583695837058371583725837358374583755837658377583785837958380583815838258383583845838558386583875838858389583905839158392583935839458395583965839758398583995840058401584025840358404584055840658407584085840958410584115841258413584145841558416584175841858419584205842158422584235842458425584265842758428584295843058431584325843358434584355843658437584385843958440584415844258443584445844558446584475844858449584505845158452584535845458455584565845758458584595846058461584625846358464584655846658467584685846958470584715847258473584745847558476584775847858479584805848158482584835848458485584865848758488584895849058491584925849358494584955849658497584985849958500585015850258503585045850558506585075850858509585105851158512585135851458515585165851758518585195852058521585225852358524585255852658527585285852958530585315853258533585345853558536585375853858539585405854158542585435854458545585465854758548585495855058551585525855358554585555855658557585585855958560585615856258563585645856558566585675856858569585705857158572585735857458575585765857758578585795858058581585825858358584585855858658587585885858958590585915859258593585945859558596585975859858599586005860158602586035860458605586065860758608586095861058611586125861358614586155861658617586185861958620586215862258623586245862558626586275862858629586305863158632586335863458635586365863758638586395864058641586425864358644586455864658647586485864958650586515865258653586545865558656586575865858659586605866158662586635866458665586665866758668586695867058671586725867358674586755867658677586785867958680586815868258683586845868558686586875868858689586905869158692586935869458695586965869758698586995870058701587025870358704587055870658707587085870958710587115871258713587145871558716587175871858719587205872158722587235872458725587265872758728587295873058731587325873358734587355873658737587385873958740587415874258743587445874558746587475874858749587505875158752587535875458755587565875758758587595876058761587625876358764587655876658767587685876958770587715877258773587745877558776587775877858779587805878158782587835878458785587865878758788587895879058791587925879358794587955879658797587985879958800588015880258803588045880558806588075880858809588105881158812588135881458815588165881758818588195882058821588225882358824588255882658827588285882958830588315883258833588345883558836588375883858839588405884158842588435884458845588465884758848588495885058851588525885358854588555885658857588585885958860588615886258863588645886558866588675886858869588705887158872588735887458875588765887758878588795888058881588825888358884588855888658887588885888958890588915889258893588945889558896588975889858899589005890158902589035890458905589065890758908589095891058911589125891358914589155891658917589185891958920589215892258923589245892558926589275892858929589305893158932589335893458935589365893758938589395894058941589425894358944589455894658947589485894958950589515895258953589545895558956589575895858959589605896158962589635896458965589665896758968589695897058971589725897358974589755897658977589785897958980589815898258983589845898558986589875898858989589905899158992589935899458995589965899758998589995900059001590025900359004590055900659007590085900959010590115901259013590145901559016590175901859019590205902159022590235902459025590265902759028590295903059031590325903359034590355903659037590385903959040590415904259043590445904559046590475904859049590505905159052590535905459055590565905759058590595906059061590625906359064590655906659067590685906959070590715907259073590745907559076590775907859079590805908159082590835908459085590865908759088590895909059091590925909359094590955909659097590985909959100591015910259103591045910559106591075910859109591105911159112591135911459115591165911759118591195912059121591225912359124591255912659127591285912959130591315913259133591345913559136591375913859139591405914159142591435914459145591465914759148591495915059151591525915359154591555915659157591585915959160591615916259163591645916559166591675916859169591705917159172591735917459175591765917759178591795918059181591825918359184591855918659187591885918959190591915919259193591945919559196591975919859199592005920159202592035920459205592065920759208592095921059211592125921359214592155921659217592185921959220592215922259223592245922559226592275922859229592305923159232592335923459235592365923759238592395924059241592425924359244592455924659247592485924959250592515925259253592545925559256592575925859259592605926159262592635926459265592665926759268592695927059271592725927359274592755927659277592785927959280592815928259283592845928559286592875928859289592905929159292592935929459295592965929759298592995930059301593025930359304593055930659307593085930959310593115931259313593145931559316593175931859319593205932159322593235932459325593265932759328593295933059331593325933359334593355933659337593385933959340593415934259343593445934559346593475934859349593505935159352593535935459355593565935759358593595936059361593625936359364593655936659367593685936959370593715937259373593745937559376593775937859379593805938159382593835938459385593865938759388593895939059391593925939359394593955939659397593985939959400594015940259403594045940559406594075940859409594105941159412594135941459415594165941759418594195942059421594225942359424594255942659427594285942959430594315943259433594345943559436594375943859439594405944159442594435944459445594465944759448594495945059451594525945359454594555945659457594585945959460594615946259463594645946559466594675946859469594705947159472594735947459475594765947759478594795948059481594825948359484594855948659487594885948959490594915949259493594945949559496594975949859499595005950159502595035950459505595065950759508595095951059511595125951359514595155951659517595185951959520595215952259523595245952559526595275952859529595305953159532595335953459535595365953759538595395954059541595425954359544595455954659547595485954959550595515955259553595545955559556595575955859559595605956159562595635956459565595665956759568595695957059571595725957359574595755957659577595785957959580595815958259583595845958559586595875958859589595905959159592595935959459595595965959759598595995960059601596025960359604596055960659607596085960959610596115961259613596145961559616596175961859619596205962159622596235962459625596265962759628596295963059631596325963359634596355963659637596385963959640596415964259643596445964559646596475964859649596505965159652596535965459655596565965759658596595966059661596625966359664596655966659667596685966959670596715967259673596745967559676596775967859679596805968159682596835968459685596865968759688596895969059691596925969359694596955969659697596985969959700597015970259703597045970559706597075970859709597105971159712597135971459715597165971759718597195972059721597225972359724597255972659727597285972959730597315973259733597345973559736597375973859739597405974159742597435974459745597465974759748597495975059751597525975359754597555975659757597585975959760597615976259763597645976559766597675976859769597705977159772597735977459775597765977759778597795978059781597825978359784597855978659787597885978959790597915979259793597945979559796597975979859799598005980159802598035980459805598065980759808598095981059811598125981359814598155981659817598185981959820598215982259823598245982559826598275982859829598305983159832598335983459835598365983759838598395984059841598425984359844598455984659847598485984959850598515985259853598545985559856598575985859859598605986159862598635986459865598665986759868598695987059871598725987359874598755987659877598785987959880598815988259883598845988559886598875988859889598905989159892598935989459895598965989759898598995990059901599025990359904599055990659907599085990959910599115991259913599145991559916599175991859919599205992159922599235992459925599265992759928599295993059931599325993359934599355993659937599385993959940599415994259943599445994559946599475994859949599505995159952599535995459955599565995759958599595996059961599625996359964599655996659967599685996959970599715997259973599745997559976599775997859979599805998159982599835998459985599865998759988599895999059991599925999359994599955999659997599985999960000600016000260003600046000560006600076000860009600106001160012600136001460015600166001760018600196002060021600226002360024600256002660027600286002960030600316003260033600346003560036600376003860039600406004160042600436004460045600466004760048600496005060051600526005360054600556005660057600586005960060600616006260063600646006560066600676006860069600706007160072600736007460075600766007760078600796008060081600826008360084600856008660087600886008960090600916009260093600946009560096600976009860099601006010160102601036010460105601066010760108601096011060111601126011360114601156011660117601186011960120601216012260123601246012560126601276012860129601306013160132601336013460135601366013760138601396014060141601426014360144601456014660147601486014960150601516015260153601546015560156601576015860159601606016160162601636016460165601666016760168601696017060171601726017360174601756017660177601786017960180601816018260183601846018560186601876018860189601906019160192601936019460195601966019760198601996020060201602026020360204602056020660207602086020960210602116021260213602146021560216602176021860219602206022160222602236022460225602266022760228602296023060231602326023360234602356023660237602386023960240602416024260243602446024560246602476024860249602506025160252602536025460255602566025760258602596026060261602626026360264602656026660267602686026960270602716027260273602746027560276602776027860279602806028160282602836028460285602866028760288602896029060291602926029360294602956029660297602986029960300603016030260303603046030560306603076030860309603106031160312603136031460315603166031760318603196032060321603226032360324603256032660327603286032960330603316033260333603346033560336603376033860339603406034160342603436034460345603466034760348603496035060351603526035360354603556035660357603586035960360603616036260363603646036560366603676036860369603706037160372603736037460375603766037760378603796038060381603826038360384603856038660387603886038960390603916039260393603946039560396603976039860399604006040160402604036040460405604066040760408604096041060411604126041360414604156041660417604186041960420604216042260423604246042560426604276042860429604306043160432604336043460435604366043760438604396044060441604426044360444604456044660447604486044960450604516045260453604546045560456604576045860459604606046160462604636046460465604666046760468604696047060471604726047360474604756047660477604786047960480604816048260483604846048560486604876048860489604906049160492604936049460495604966049760498604996050060501605026050360504605056050660507605086050960510605116051260513605146051560516605176051860519605206052160522605236052460525605266052760528605296053060531605326053360534605356053660537605386053960540605416054260543605446054560546605476054860549605506055160552605536055460555605566055760558605596056060561605626056360564605656056660567605686056960570605716057260573605746057560576605776057860579605806058160582605836058460585605866058760588605896059060591605926059360594605956059660597605986059960600606016060260603606046060560606606076060860609606106061160612606136061460615606166061760618606196062060621606226062360624606256062660627606286062960630606316063260633606346063560636606376063860639606406064160642606436064460645606466064760648606496065060651606526065360654606556065660657606586065960660606616066260663606646066560666606676066860669606706067160672606736067460675606766067760678606796068060681606826068360684606856068660687606886068960690606916069260693606946069560696606976069860699607006070160702607036070460705607066070760708607096071060711607126071360714607156071660717607186071960720607216072260723607246072560726607276072860729607306073160732607336073460735607366073760738607396074060741607426074360744607456074660747607486074960750607516075260753607546075560756607576075860759607606076160762607636076460765607666076760768607696077060771607726077360774607756077660777607786077960780607816078260783607846078560786607876078860789607906079160792607936079460795607966079760798607996080060801608026080360804608056080660807608086080960810608116081260813608146081560816608176081860819608206082160822608236082460825608266082760828608296083060831608326083360834608356083660837608386083960840608416084260843608446084560846608476084860849608506085160852608536085460855608566085760858608596086060861608626086360864608656086660867608686086960870608716087260873608746087560876608776087860879608806088160882608836088460885608866088760888608896089060891608926089360894608956089660897608986089960900609016090260903609046090560906609076090860909609106091160912609136091460915609166091760918609196092060921609226092360924609256092660927609286092960930609316093260933609346093560936609376093860939609406094160942609436094460945609466094760948609496095060951609526095360954609556095660957609586095960960609616096260963609646096560966609676096860969609706097160972609736097460975609766097760978609796098060981609826098360984609856098660987609886098960990609916099260993609946099560996609976099860999610006100161002610036100461005610066100761008610096101061011610126101361014610156101661017610186101961020610216102261023610246102561026610276102861029610306103161032610336103461035610366103761038610396104061041610426104361044610456104661047610486104961050610516105261053610546105561056610576105861059610606106161062610636106461065610666106761068610696107061071610726107361074610756107661077610786107961080610816108261083610846108561086610876108861089610906109161092610936109461095610966109761098610996110061101611026110361104611056110661107611086110961110611116111261113611146111561116611176111861119611206112161122611236112461125611266112761128611296113061131611326113361134611356113661137611386113961140611416114261143611446114561146611476114861149611506115161152611536115461155611566115761158611596116061161611626116361164611656116661167611686116961170611716117261173611746117561176611776117861179611806118161182611836118461185611866118761188611896119061191611926119361194611956119661197611986119961200612016120261203612046120561206612076120861209612106121161212612136121461215612166121761218612196122061221612226122361224612256122661227612286122961230612316123261233612346123561236612376123861239612406124161242612436124461245612466124761248612496125061251612526125361254612556125661257612586125961260612616126261263612646126561266612676126861269612706127161272612736127461275612766127761278612796128061281612826128361284612856128661287612886128961290612916129261293612946129561296612976129861299613006130161302613036130461305613066130761308613096131061311613126131361314613156131661317613186131961320613216132261323613246132561326613276132861329613306133161332613336133461335613366133761338613396134061341613426134361344613456134661347613486134961350613516135261353613546135561356613576135861359613606136161362613636136461365613666136761368613696137061371613726137361374613756137661377613786137961380613816138261383613846138561386613876138861389613906139161392613936139461395613966139761398613996140061401614026140361404614056140661407614086140961410614116141261413614146141561416614176141861419614206142161422614236142461425614266142761428614296143061431614326143361434614356143661437614386143961440614416144261443614446144561446614476144861449614506145161452614536145461455614566145761458614596146061461614626146361464614656146661467614686146961470614716147261473614746147561476614776147861479614806148161482614836148461485614866148761488614896149061491614926149361494614956149661497614986149961500615016150261503615046150561506615076150861509615106151161512615136151461515615166151761518615196152061521615226152361524615256152661527615286152961530615316153261533615346153561536615376153861539615406154161542615436154461545615466154761548615496155061551615526155361554615556155661557615586155961560615616156261563615646156561566615676156861569615706157161572615736157461575615766157761578615796158061581615826158361584615856158661587615886158961590615916159261593615946159561596615976159861599616006160161602616036160461605616066160761608616096161061611616126161361614616156161661617616186161961620616216162261623616246162561626616276162861629616306163161632616336163461635616366163761638616396164061641616426164361644616456164661647616486164961650616516165261653616546165561656616576165861659616606166161662616636166461665616666166761668616696167061671616726167361674616756167661677616786167961680616816168261683616846168561686616876168861689616906169161692616936169461695616966169761698616996170061701617026170361704617056170661707617086170961710617116171261713617146171561716617176171861719617206172161722617236172461725617266172761728617296173061731617326173361734617356173661737617386173961740617416174261743617446174561746617476174861749617506175161752617536175461755617566175761758617596176061761617626176361764617656176661767617686176961770617716177261773617746177561776617776177861779617806178161782617836178461785617866178761788617896179061791617926179361794617956179661797617986179961800618016180261803618046180561806618076180861809618106181161812618136181461815618166181761818618196182061821618226182361824618256182661827618286182961830618316183261833618346183561836618376183861839618406184161842618436184461845618466184761848618496185061851618526185361854618556185661857618586185961860618616186261863618646186561866618676186861869618706187161872618736187461875618766187761878618796188061881618826188361884618856188661887618886188961890618916189261893618946189561896618976189861899619006190161902619036190461905619066190761908619096191061911619126191361914619156191661917619186191961920619216192261923619246192561926619276192861929619306193161932619336193461935619366193761938619396194061941619426194361944619456194661947619486194961950619516195261953619546195561956619576195861959619606196161962619636196461965619666196761968619696197061971619726197361974619756197661977619786197961980619816198261983619846198561986619876198861989619906199161992619936199461995619966199761998619996200062001620026200362004620056200662007620086200962010620116201262013620146201562016620176201862019620206202162022620236202462025620266202762028620296203062031620326203362034620356203662037620386203962040620416204262043620446204562046620476204862049620506205162052620536205462055620566205762058620596206062061620626206362064620656206662067620686206962070620716207262073620746207562076620776207862079620806208162082620836208462085620866208762088620896209062091620926209362094620956209662097620986209962100621016210262103621046210562106621076210862109621106211162112621136211462115621166211762118621196212062121621226212362124621256212662127621286212962130621316213262133621346213562136621376213862139621406214162142621436214462145621466214762148621496215062151621526215362154621556215662157621586215962160621616216262163621646216562166621676216862169621706217162172621736217462175621766217762178621796218062181621826218362184621856218662187621886218962190621916219262193621946219562196621976219862199622006220162202622036220462205622066220762208622096221062211622126221362214622156221662217622186221962220622216222262223622246222562226622276222862229622306223162232622336223462235622366223762238622396224062241622426224362244622456224662247622486224962250622516225262253622546225562256622576225862259622606226162262622636226462265622666226762268622696227062271622726227362274622756227662277622786227962280622816228262283622846228562286622876228862289622906229162292622936229462295622966229762298622996230062301623026230362304623056230662307623086230962310623116231262313623146231562316623176231862319623206232162322623236232462325623266232762328623296233062331623326233362334623356233662337623386233962340623416234262343623446234562346623476234862349623506235162352623536235462355623566235762358623596236062361623626236362364623656236662367623686236962370623716237262373623746237562376623776237862379623806238162382623836238462385623866238762388623896239062391623926239362394623956239662397623986239962400624016240262403624046240562406624076240862409624106241162412624136241462415624166241762418624196242062421624226242362424624256242662427624286242962430624316243262433624346243562436624376243862439624406244162442624436244462445624466244762448624496245062451624526245362454624556245662457624586245962460624616246262463624646246562466624676246862469624706247162472624736247462475624766247762478624796248062481624826248362484624856248662487624886248962490624916249262493624946249562496624976249862499625006250162502625036250462505625066250762508625096251062511625126251362514625156251662517625186251962520625216252262523625246252562526625276252862529625306253162532625336253462535625366253762538625396254062541625426254362544625456254662547625486254962550625516255262553625546255562556625576255862559625606256162562625636256462565625666256762568625696257062571625726257362574625756257662577625786257962580625816258262583625846258562586625876258862589625906259162592625936259462595625966259762598625996260062601626026260362604626056260662607626086260962610626116261262613626146261562616626176261862619626206262162622626236262462625626266262762628626296263062631626326263362634626356263662637626386263962640626416264262643626446264562646626476264862649626506265162652626536265462655626566265762658626596266062661626626266362664626656266662667626686266962670626716267262673626746267562676626776267862679626806268162682626836268462685626866268762688626896269062691626926269362694626956269662697626986269962700627016270262703627046270562706627076270862709627106271162712627136271462715627166271762718627196272062721627226272362724627256272662727627286272962730627316273262733627346273562736627376273862739627406274162742627436274462745627466274762748627496275062751627526275362754627556275662757627586275962760627616276262763627646276562766627676276862769627706277162772627736277462775627766277762778627796278062781627826278362784627856278662787627886278962790627916279262793627946279562796627976279862799628006280162802628036280462805628066280762808628096281062811628126281362814628156281662817628186281962820628216282262823628246282562826628276282862829628306283162832628336283462835628366283762838628396284062841628426284362844628456284662847628486284962850628516285262853628546285562856628576285862859628606286162862628636286462865628666286762868628696287062871628726287362874628756287662877628786287962880628816288262883628846288562886628876288862889628906289162892628936289462895628966289762898628996290062901629026290362904629056290662907629086290962910629116291262913629146291562916629176291862919629206292162922629236292462925629266292762928629296293062931629326293362934629356293662937629386293962940629416294262943629446294562946629476294862949629506295162952629536295462955629566295762958629596296062961629626296362964629656296662967629686296962970629716297262973629746297562976629776297862979629806298162982629836298462985629866298762988629896299062991629926299362994629956299662997629986299963000630016300263003630046300563006630076300863009630106301163012630136301463015630166301763018630196302063021630226302363024630256302663027630286302963030630316303263033630346303563036630376303863039630406304163042630436304463045630466304763048630496305063051630526305363054630556305663057630586305963060630616306263063630646306563066630676306863069630706307163072630736307463075630766307763078630796308063081630826308363084630856308663087630886308963090630916309263093630946309563096630976309863099631006310163102631036310463105631066310763108631096311063111631126311363114631156311663117631186311963120631216312263123631246312563126631276312863129631306313163132631336313463135631366313763138631396314063141631426314363144631456314663147631486314963150631516315263153631546315563156631576315863159631606316163162631636316463165631666316763168631696317063171631726317363174631756317663177631786317963180631816318263183631846318563186631876318863189631906319163192631936319463195631966319763198631996320063201632026320363204632056320663207632086320963210632116321263213632146321563216632176321863219632206322163222632236322463225632266322763228632296323063231632326323363234632356323663237632386323963240632416324263243632446324563246632476324863249632506325163252632536325463255632566325763258632596326063261632626326363264632656326663267632686326963270632716327263273632746327563276632776327863279632806328163282632836328463285632866328763288632896329063291632926329363294632956329663297632986329963300633016330263303633046330563306633076330863309633106331163312633136331463315633166331763318633196332063321633226332363324633256332663327633286332963330633316333263333633346333563336633376333863339633406334163342633436334463345633466334763348633496335063351633526335363354633556335663357633586335963360633616336263363633646336563366633676336863369633706337163372633736337463375633766337763378633796338063381633826338363384633856338663387633886338963390633916339263393633946339563396633976339863399634006340163402634036340463405634066340763408634096341063411634126341363414634156341663417634186341963420634216342263423634246342563426634276342863429634306343163432634336343463435634366343763438634396344063441634426344363444634456344663447634486344963450634516345263453634546345563456634576345863459634606346163462634636346463465634666346763468634696347063471634726347363474634756347663477634786347963480634816348263483634846348563486634876348863489634906349163492634936349463495634966349763498634996350063501635026350363504635056350663507635086350963510635116351263513635146351563516635176351863519635206352163522635236352463525635266352763528635296353063531635326353363534635356353663537635386353963540635416354263543635446354563546635476354863549635506355163552635536355463555635566355763558635596356063561635626356363564635656356663567635686356963570635716357263573635746357563576635776357863579635806358163582635836358463585635866358763588635896359063591635926359363594635956359663597635986359963600636016360263603636046360563606636076360863609636106361163612636136361463615636166361763618636196362063621636226362363624636256362663627636286362963630636316363263633636346363563636636376363863639636406364163642636436364463645636466364763648636496365063651636526365363654636556365663657636586365963660636616366263663636646366563666636676366863669636706367163672636736367463675636766367763678636796368063681636826368363684636856368663687636886368963690636916369263693636946369563696636976369863699637006370163702637036370463705637066370763708637096371063711637126371363714637156371663717637186371963720637216372263723637246372563726637276372863729637306373163732637336373463735637366373763738637396374063741637426374363744637456374663747637486374963750637516375263753637546375563756637576375863759637606376163762637636376463765637666376763768637696377063771637726377363774637756377663777637786377963780637816378263783637846378563786637876378863789637906379163792637936379463795637966379763798637996380063801638026380363804638056380663807638086380963810638116381263813638146381563816638176381863819638206382163822638236382463825638266382763828638296383063831638326383363834638356383663837638386383963840638416384263843638446384563846638476384863849638506385163852638536385463855638566385763858638596386063861638626386363864638656386663867638686386963870638716387263873638746387563876638776387863879638806388163882638836388463885638866388763888638896389063891638926389363894638956389663897638986389963900639016390263903639046390563906639076390863909639106391163912639136391463915639166391763918639196392063921639226392363924639256392663927639286392963930639316393263933639346393563936639376393863939639406394163942639436394463945639466394763948639496395063951639526395363954639556395663957639586395963960639616396263963639646396563966639676396863969639706397163972639736397463975639766397763978639796398063981639826398363984639856398663987639886398963990639916399263993639946399563996639976399863999640006400164002640036400464005640066400764008640096401064011640126401364014640156401664017640186401964020640216402264023640246402564026640276402864029640306403164032640336403464035640366403764038640396404064041640426404364044640456404664047640486404964050640516405264053640546405564056640576405864059640606406164062640636406464065640666406764068640696407064071640726407364074640756407664077640786407964080640816408264083640846408564086640876408864089640906409164092640936409464095640966409764098640996410064101641026410364104641056410664107641086410964110641116411264113641146411564116641176411864119641206412164122641236412464125641266412764128641296413064131641326413364134641356413664137641386413964140641416414264143641446414564146641476414864149641506415164152641536415464155641566415764158641596416064161641626416364164641656416664167641686416964170641716417264173641746417564176641776417864179641806418164182641836418464185641866418764188641896419064191641926419364194641956419664197641986419964200642016420264203642046420564206642076420864209642106421164212642136421464215642166421764218642196422064221642226422364224642256422664227642286422964230642316423264233642346423564236642376423864239642406424164242642436424464245642466424764248642496425064251642526425364254642556425664257642586425964260642616426264263642646426564266642676426864269642706427164272642736427464275642766427764278642796428064281642826428364284642856428664287642886428964290642916429264293642946429564296642976429864299643006430164302643036430464305643066430764308643096431064311643126431364314643156431664317643186431964320643216432264323643246432564326643276432864329643306433164332643336433464335643366433764338643396434064341643426434364344643456434664347643486434964350643516435264353643546435564356643576435864359643606436164362643636436464365643666436764368643696437064371643726437364374643756437664377643786437964380643816438264383643846438564386643876438864389643906439164392643936439464395643966439764398643996440064401644026440364404644056440664407644086440964410644116441264413644146441564416644176441864419644206442164422644236442464425644266442764428644296443064431644326443364434644356443664437644386443964440644416444264443644446444564446644476444864449644506445164452644536445464455644566445764458644596446064461644626446364464644656446664467644686446964470644716447264473644746447564476644776447864479644806448164482644836448464485644866448764488644896449064491644926449364494644956449664497644986449964500645016450264503645046450564506645076450864509645106451164512645136451464515645166451764518645196452064521645226452364524645256452664527645286452964530645316453264533645346453564536645376453864539645406454164542645436454464545645466454764548645496455064551645526455364554645556455664557645586455964560645616456264563645646456564566645676456864569645706457164572645736457464575645766457764578645796458064581645826458364584645856458664587645886458964590645916459264593645946459564596645976459864599646006460164602646036460464605646066460764608646096461064611646126461364614646156461664617646186461964620646216462264623646246462564626646276462864629646306463164632646336463464635646366463764638646396464064641646426464364644646456464664647646486464964650646516465264653646546465564656646576465864659646606466164662646636466464665646666466764668646696467064671646726467364674646756467664677646786467964680646816468264683646846468564686646876468864689646906469164692646936469464695646966469764698646996470064701647026470364704647056470664707647086470964710647116471264713647146471564716647176471864719647206472164722647236472464725647266472764728647296473064731647326473364734647356473664737647386473964740647416474264743647446474564746647476474864749647506475164752647536475464755647566475764758647596476064761647626476364764647656476664767647686476964770647716477264773647746477564776647776477864779647806478164782647836478464785647866478764788647896479064791647926479364794647956479664797647986479964800648016480264803648046480564806648076480864809648106481164812648136481464815648166481764818648196482064821648226482364824648256482664827648286482964830648316483264833648346483564836648376483864839648406484164842648436484464845648466484764848648496485064851648526485364854648556485664857648586485964860648616486264863648646486564866648676486864869648706487164872648736487464875648766487764878648796488064881648826488364884648856488664887648886488964890648916489264893648946489564896648976489864899649006490164902649036490464905649066490764908649096491064911649126491364914649156491664917649186491964920649216492264923649246492564926649276492864929649306493164932649336493464935649366493764938649396494064941649426494364944649456494664947649486494964950649516495264953649546495564956649576495864959649606496164962649636496464965649666496764968649696497064971649726497364974649756497664977649786497964980649816498264983649846498564986649876498864989649906499164992649936499464995649966499764998649996500065001650026500365004650056500665007650086500965010650116501265013650146501565016650176501865019650206502165022650236502465025650266502765028650296503065031650326503365034650356503665037650386503965040650416504265043650446504565046650476504865049650506505165052650536505465055650566505765058650596506065061650626506365064650656506665067650686506965070650716507265073650746507565076650776507865079650806508165082650836508465085650866508765088650896509065091650926509365094650956509665097650986509965100651016510265103651046510565106651076510865109651106511165112651136511465115651166511765118651196512065121651226512365124651256512665127651286512965130651316513265133651346513565136651376513865139651406514165142651436514465145651466514765148651496515065151651526515365154651556515665157651586515965160651616516265163651646516565166651676516865169651706517165172651736517465175651766517765178651796518065181651826518365184651856518665187651886518965190651916519265193651946519565196651976519865199652006520165202652036520465205652066520765208652096521065211652126521365214652156521665217652186521965220652216522265223652246522565226652276522865229652306523165232652336523465235652366523765238652396524065241652426524365244652456524665247652486524965250652516525265253652546525565256652576525865259652606526165262652636526465265652666526765268652696527065271652726527365274652756527665277652786527965280652816528265283652846528565286652876528865289652906529165292652936529465295652966529765298652996530065301653026530365304653056530665307653086530965310653116531265313653146531565316653176531865319653206532165322653236532465325653266532765328653296533065331653326533365334653356533665337653386533965340653416534265343653446534565346653476534865349653506535165352653536535465355653566535765358653596536065361653626536365364653656536665367653686536965370653716537265373653746537565376653776537865379653806538165382653836538465385653866538765388653896539065391653926539365394653956539665397653986539965400654016540265403654046540565406654076540865409654106541165412654136541465415654166541765418654196542065421654226542365424654256542665427654286542965430654316543265433654346543565436654376543865439654406544165442654436544465445654466544765448654496545065451654526545365454654556545665457654586545965460654616546265463654646546565466654676546865469654706547165472654736547465475654766547765478654796548065481654826548365484654856548665487654886548965490654916549265493654946549565496654976549865499655006550165502655036550465505655066550765508655096551065511655126551365514655156551665517655186551965520655216552265523655246552565526655276552865529655306553165532655336553465535655366553765538655396554065541655426554365544655456554665547655486554965550655516555265553655546555565556655576555865559655606556165562655636556465565655666556765568655696557065571655726557365574655756557665577655786557965580655816558265583655846558565586655876558865589655906559165592655936559465595655966559765598655996560065601656026560365604656056560665607656086560965610656116561265613656146561565616656176561865619656206562165622656236562465625656266562765628656296563065631656326563365634656356563665637656386563965640656416564265643656446564565646656476564865649656506565165652656536565465655656566565765658656596566065661656626566365664656656566665667656686566965670656716567265673656746567565676656776567865679656806568165682656836568465685656866568765688656896569065691656926569365694656956569665697656986569965700657016570265703657046570565706657076570865709657106571165712657136571465715657166571765718657196572065721657226572365724657256572665727657286572965730657316573265733657346573565736657376573865739657406574165742657436574465745657466574765748657496575065751657526575365754657556575665757657586575965760657616576265763657646576565766657676576865769657706577165772657736577465775657766577765778657796578065781657826578365784657856578665787657886578965790657916579265793657946579565796657976579865799658006580165802658036580465805658066580765808658096581065811658126581365814658156581665817658186581965820658216582265823658246582565826658276582865829658306583165832658336583465835658366583765838658396584065841658426584365844658456584665847658486584965850658516585265853658546585565856658576585865859658606586165862658636586465865658666586765868658696587065871658726587365874658756587665877658786587965880658816588265883658846588565886658876588865889658906589165892658936589465895658966589765898658996590065901659026590365904659056590665907659086590965910659116591265913659146591565916659176591865919659206592165922659236592465925659266592765928659296593065931659326593365934659356593665937659386593965940659416594265943659446594565946659476594865949659506595165952659536595465955659566595765958659596596065961659626596365964659656596665967659686596965970659716597265973659746597565976659776597865979659806598165982659836598465985659866598765988659896599065991659926599365994659956599665997659986599966000660016600266003660046600566006660076600866009660106601166012660136601466015660166601766018660196602066021660226602366024660256602666027660286602966030660316603266033660346603566036660376603866039660406604166042660436604466045660466604766048660496605066051660526605366054660556605666057660586605966060660616606266063660646606566066660676606866069660706607166072660736607466075660766607766078660796608066081660826608366084660856608666087660886608966090660916609266093660946609566096660976609866099661006610166102661036610466105661066610766108661096611066111661126611366114661156611666117661186611966120661216612266123661246612566126661276612866129661306613166132661336613466135661366613766138661396614066141661426614366144661456614666147661486614966150661516615266153661546615566156661576615866159661606616166162661636616466165661666616766168661696617066171661726617366174661756617666177661786617966180661816618266183661846618566186661876618866189661906619166192661936619466195661966619766198661996620066201662026620366204662056620666207662086620966210662116621266213662146621566216662176621866219662206622166222662236622466225662266622766228662296623066231662326623366234662356623666237662386623966240662416624266243662446624566246662476624866249662506625166252662536625466255662566625766258662596626066261662626626366264662656626666267662686626966270662716627266273662746627566276662776627866279662806628166282662836628466285662866628766288662896629066291662926629366294662956629666297662986629966300663016630266303663046630566306663076630866309663106631166312663136631466315663166631766318663196632066321663226632366324663256632666327663286632966330663316633266333663346633566336663376633866339663406634166342663436634466345663466634766348663496635066351663526635366354663556635666357663586635966360663616636266363663646636566366663676636866369663706637166372663736637466375663766637766378663796638066381663826638366384663856638666387663886638966390663916639266393663946639566396663976639866399664006640166402664036640466405664066640766408664096641066411664126641366414664156641666417664186641966420664216642266423664246642566426664276642866429664306643166432664336643466435664366643766438664396644066441664426644366444664456644666447664486644966450664516645266453664546645566456664576645866459664606646166462664636646466465664666646766468664696647066471664726647366474664756647666477664786647966480664816648266483664846648566486664876648866489664906649166492664936649466495664966649766498664996650066501665026650366504665056650666507665086650966510665116651266513665146651566516665176651866519665206652166522665236652466525665266652766528665296653066531665326653366534665356653666537665386653966540665416654266543665446654566546665476654866549665506655166552665536655466555665566655766558665596656066561665626656366564665656656666567665686656966570665716657266573665746657566576665776657866579665806658166582665836658466585665866658766588665896659066591665926659366594665956659666597665986659966600666016660266603666046660566606666076660866609666106661166612666136661466615666166661766618666196662066621666226662366624666256662666627666286662966630666316663266633666346663566636666376663866639666406664166642666436664466645666466664766648666496665066651666526665366654666556665666657666586665966660666616666266663666646666566666666676666866669666706667166672666736667466675666766667766678666796668066681666826668366684666856668666687666886668966690666916669266693666946669566696666976669866699667006670166702667036670466705667066670766708667096671066711667126671366714667156671666717667186671966720667216672266723667246672566726667276672866729667306673166732667336673466735667366673766738667396674066741667426674366744667456674666747667486674966750667516675266753667546675566756667576675866759667606676166762667636676466765667666676766768667696677066771667726677366774667756677666777667786677966780667816678266783667846678566786667876678866789667906679166792667936679466795667966679766798667996680066801668026680366804668056680666807668086680966810668116681266813668146681566816668176681866819668206682166822668236682466825668266682766828668296683066831668326683366834668356683666837668386683966840668416684266843668446684566846668476684866849668506685166852668536685466855668566685766858668596686066861668626686366864668656686666867668686686966870668716687266873668746687566876668776687866879668806688166882668836688466885668866688766888668896689066891668926689366894668956689666897668986689966900669016690266903669046690566906669076690866909669106691166912669136691466915669166691766918669196692066921669226692366924669256692666927669286692966930669316693266933669346693566936669376693866939669406694166942669436694466945669466694766948669496695066951669526695366954669556695666957669586695966960669616696266963669646696566966669676696866969669706697166972669736697466975669766697766978669796698066981669826698366984669856698666987669886698966990669916699266993669946699566996669976699866999670006700167002670036700467005670066700767008670096701067011670126701367014670156701667017670186701967020670216702267023670246702567026670276702867029670306703167032670336703467035670366703767038670396704067041670426704367044670456704667047670486704967050670516705267053670546705567056670576705867059670606706167062670636706467065670666706767068670696707067071670726707367074670756707667077670786707967080670816708267083670846708567086670876708867089670906709167092670936709467095670966709767098670996710067101671026710367104671056710667107671086710967110671116711267113671146711567116671176711867119671206712167122671236712467125671266712767128671296713067131671326713367134671356713667137671386713967140671416714267143671446714567146671476714867149671506715167152671536715467155671566715767158671596716067161671626716367164671656716667167671686716967170671716717267173671746717567176671776717867179671806718167182671836718467185671866718767188671896719067191671926719367194671956719667197671986719967200672016720267203672046720567206672076720867209672106721167212672136721467215672166721767218672196722067221672226722367224672256722667227672286722967230672316723267233672346723567236672376723867239672406724167242672436724467245672466724767248672496725067251672526725367254672556725667257672586725967260672616726267263672646726567266672676726867269672706727167272672736727467275672766727767278672796728067281672826728367284672856728667287672886728967290672916729267293672946729567296672976729867299673006730167302673036730467305673066730767308673096731067311673126731367314673156731667317673186731967320673216732267323673246732567326673276732867329673306733167332673336733467335673366733767338673396734067341673426734367344673456734667347673486734967350673516735267353673546735567356673576735867359673606736167362673636736467365673666736767368673696737067371673726737367374673756737667377673786737967380673816738267383673846738567386673876738867389673906739167392673936739467395673966739767398673996740067401674026740367404674056740667407674086740967410674116741267413674146741567416674176741867419674206742167422674236742467425674266742767428674296743067431674326743367434674356743667437674386743967440674416744267443674446744567446674476744867449674506745167452674536745467455674566745767458674596746067461674626746367464674656746667467674686746967470674716747267473674746747567476674776747867479674806748167482674836748467485674866748767488674896749067491674926749367494674956749667497674986749967500675016750267503675046750567506675076750867509675106751167512675136751467515675166751767518675196752067521675226752367524675256752667527675286752967530675316753267533675346753567536675376753867539675406754167542675436754467545675466754767548675496755067551675526755367554675556755667557675586755967560675616756267563675646756567566675676756867569675706757167572675736757467575675766757767578675796758067581675826758367584675856758667587675886758967590675916759267593675946759567596675976759867599676006760167602676036760467605676066760767608676096761067611676126761367614676156761667617676186761967620676216762267623676246762567626676276762867629676306763167632676336763467635676366763767638676396764067641676426764367644676456764667647676486764967650676516765267653676546765567656676576765867659676606766167662676636766467665676666766767668676696767067671676726767367674676756767667677676786767967680676816768267683676846768567686676876768867689676906769167692676936769467695676966769767698676996770067701677026770367704677056770667707677086770967710677116771267713677146771567716677176771867719677206772167722677236772467725677266772767728677296773067731677326773367734677356773667737677386773967740677416774267743677446774567746677476774867749677506775167752677536775467755677566775767758677596776067761677626776367764677656776667767677686776967770677716777267773677746777567776677776777867779677806778167782677836778467785677866778767788677896779067791677926779367794677956779667797677986779967800678016780267803678046780567806678076780867809678106781167812678136781467815678166781767818678196782067821678226782367824678256782667827678286782967830678316783267833678346783567836678376783867839678406784167842678436784467845678466784767848678496785067851678526785367854678556785667857678586785967860678616786267863678646786567866678676786867869678706787167872678736787467875678766787767878678796788067881678826788367884678856788667887678886788967890678916789267893678946789567896678976789867899679006790167902679036790467905679066790767908679096791067911679126791367914679156791667917679186791967920679216792267923679246792567926679276792867929679306793167932679336793467935679366793767938679396794067941679426794367944679456794667947679486794967950679516795267953679546795567956679576795867959679606796167962679636796467965679666796767968679696797067971679726797367974679756797667977679786797967980679816798267983679846798567986679876798867989679906799167992679936799467995679966799767998679996800068001680026800368004680056800668007680086800968010680116801268013680146801568016680176801868019680206802168022680236802468025680266802768028680296803068031680326803368034680356803668037680386803968040680416804268043680446804568046680476804868049680506805168052680536805468055680566805768058680596806068061680626806368064680656806668067680686806968070680716807268073680746807568076680776807868079680806808168082680836808468085680866808768088680896809068091680926809368094680956809668097680986809968100681016810268103681046810568106681076810868109681106811168112681136811468115681166811768118681196812068121681226812368124681256812668127681286812968130681316813268133681346813568136681376813868139681406814168142681436814468145681466814768148681496815068151681526815368154681556815668157681586815968160681616816268163681646816568166681676816868169681706817168172681736817468175681766817768178681796818068181681826818368184681856818668187681886818968190681916819268193681946819568196681976819868199682006820168202682036820468205682066820768208682096821068211682126821368214682156821668217682186821968220682216822268223682246822568226682276822868229682306823168232682336823468235682366823768238682396824068241682426824368244682456824668247682486824968250682516825268253682546825568256682576825868259682606826168262682636826468265682666826768268682696827068271682726827368274682756827668277682786827968280682816828268283682846828568286682876828868289682906829168292682936829468295682966829768298682996830068301683026830368304683056830668307683086830968310683116831268313683146831568316683176831868319683206832168322683236832468325683266832768328683296833068331683326833368334683356833668337683386833968340683416834268343683446834568346683476834868349683506835168352683536835468355683566835768358683596836068361683626836368364683656836668367683686836968370683716837268373683746837568376683776837868379683806838168382683836838468385683866838768388683896839068391683926839368394683956839668397683986839968400684016840268403684046840568406684076840868409684106841168412684136841468415684166841768418684196842068421684226842368424684256842668427684286842968430684316843268433684346843568436684376843868439684406844168442684436844468445684466844768448684496845068451684526845368454684556845668457684586845968460684616846268463684646846568466684676846868469684706847168472684736847468475684766847768478684796848068481684826848368484684856848668487684886848968490684916849268493684946849568496684976849868499685006850168502685036850468505685066850768508685096851068511685126851368514685156851668517685186851968520685216852268523685246852568526685276852868529685306853168532685336853468535685366853768538685396854068541685426854368544685456854668547685486854968550685516855268553685546855568556685576855868559685606856168562685636856468565685666856768568685696857068571685726857368574685756857668577685786857968580685816858268583685846858568586685876858868589685906859168592685936859468595685966859768598685996860068601686026860368604686056860668607686086860968610686116861268613686146861568616686176861868619686206862168622686236862468625686266862768628686296863068631686326863368634686356863668637686386863968640686416864268643686446864568646686476864868649686506865168652686536865468655686566865768658686596866068661686626866368664686656866668667686686866968670686716867268673686746867568676686776867868679686806868168682686836868468685686866868768688686896869068691686926869368694686956869668697686986869968700687016870268703687046870568706687076870868709687106871168712687136871468715687166871768718687196872068721687226872368724687256872668727687286872968730687316873268733687346873568736687376873868739687406874168742687436874468745687466874768748687496875068751687526875368754687556875668757687586875968760687616876268763687646876568766687676876868769687706877168772687736877468775687766877768778687796878068781687826878368784687856878668787687886878968790687916879268793687946879568796687976879868799688006880168802688036880468805688066880768808688096881068811688126881368814688156881668817688186881968820688216882268823688246882568826688276882868829688306883168832688336883468835688366883768838688396884068841688426884368844688456884668847688486884968850688516885268853688546885568856688576885868859688606886168862688636886468865688666886768868688696887068871688726887368874688756887668877688786887968880688816888268883688846888568886688876888868889688906889168892688936889468895688966889768898688996890068901689026890368904689056890668907689086890968910689116891268913689146891568916689176891868919689206892168922689236892468925689266892768928689296893068931689326893368934689356893668937689386893968940689416894268943689446894568946689476894868949689506895168952689536895468955689566895768958689596896068961689626896368964689656896668967689686896968970689716897268973689746897568976689776897868979689806898168982689836898468985689866898768988689896899068991689926899368994689956899668997689986899969000690016900269003690046900569006690076900869009690106901169012690136901469015690166901769018690196902069021690226902369024690256902669027690286902969030690316903269033690346903569036690376903869039690406904169042690436904469045690466904769048690496905069051690526905369054690556905669057690586905969060690616906269063690646906569066690676906869069690706907169072690736907469075690766907769078690796908069081690826908369084690856908669087690886908969090690916909269093690946909569096690976909869099691006910169102691036910469105691066910769108691096911069111691126911369114691156911669117691186911969120691216912269123691246912569126691276912869129691306913169132691336913469135691366913769138691396914069141691426914369144691456914669147691486914969150691516915269153691546915569156691576915869159691606916169162691636916469165691666916769168691696917069171691726917369174691756917669177691786917969180691816918269183691846918569186691876918869189691906919169192691936919469195691966919769198691996920069201692026920369204692056920669207692086920969210692116921269213692146921569216692176921869219692206922169222692236922469225692266922769228692296923069231692326923369234692356923669237692386923969240692416924269243692446924569246692476924869249692506925169252692536925469255692566925769258692596926069261692626926369264692656926669267692686926969270692716927269273692746927569276692776927869279692806928169282692836928469285692866928769288692896929069291692926929369294692956929669297692986929969300693016930269303693046930569306693076930869309693106931169312693136931469315693166931769318693196932069321693226932369324693256932669327693286932969330693316933269333693346933569336693376933869339693406934169342693436934469345693466934769348693496935069351693526935369354693556935669357693586935969360693616936269363693646936569366693676936869369693706937169372693736937469375693766937769378693796938069381693826938369384693856938669387693886938969390693916939269393693946939569396693976939869399694006940169402694036940469405694066940769408694096941069411694126941369414694156941669417694186941969420694216942269423694246942569426694276942869429694306943169432694336943469435694366943769438694396944069441694426944369444694456944669447694486944969450694516945269453694546945569456694576945869459694606946169462694636946469465694666946769468694696947069471694726947369474694756947669477694786947969480694816948269483694846948569486694876948869489694906949169492694936949469495694966949769498694996950069501695026950369504695056950669507695086950969510695116951269513695146951569516695176951869519695206952169522695236952469525695266952769528695296953069531695326953369534695356953669537695386953969540695416954269543695446954569546695476954869549695506955169552695536955469555695566955769558695596956069561695626956369564695656956669567695686956969570695716957269573695746957569576695776957869579695806958169582695836958469585695866958769588695896959069591695926959369594695956959669597695986959969600696016960269603696046960569606696076960869609696106961169612696136961469615696166961769618696196962069621696226962369624696256962669627696286962969630696316963269633696346963569636696376963869639696406964169642696436964469645696466964769648696496965069651696526965369654696556965669657696586965969660696616966269663696646966569666696676966869669696706967169672696736967469675696766967769678696796968069681696826968369684696856968669687696886968969690696916969269693696946969569696696976969869699697006970169702697036970469705697066970769708697096971069711697126971369714697156971669717697186971969720697216972269723697246972569726697276972869729697306973169732697336973469735697366973769738697396974069741697426974369744697456974669747697486974969750697516975269753697546975569756697576975869759697606976169762697636976469765697666976769768697696977069771697726977369774697756977669777697786977969780697816978269783697846978569786697876978869789697906979169792697936979469795697966979769798697996980069801698026980369804698056980669807698086980969810698116981269813698146981569816698176981869819698206982169822698236982469825698266982769828698296983069831698326983369834698356983669837698386983969840698416984269843698446984569846698476984869849698506985169852698536985469855698566985769858698596986069861698626986369864698656986669867698686986969870698716987269873698746987569876698776987869879698806988169882698836988469885698866988769888698896989069891698926989369894698956989669897698986989969900699016990269903699046990569906699076990869909699106991169912699136991469915699166991769918699196992069921699226992369924699256992669927699286992969930699316993269933699346993569936699376993869939699406994169942699436994469945699466994769948699496995069951699526995369954699556995669957699586995969960699616996269963699646996569966699676996869969699706997169972699736997469975699766997769978699796998069981699826998369984699856998669987699886998969990699916999269993699946999569996699976999869999700007000170002700037000470005700067000770008700097001070011700127001370014700157001670017700187001970020700217002270023700247002570026700277002870029700307003170032700337003470035700367003770038700397004070041700427004370044700457004670047700487004970050700517005270053700547005570056700577005870059700607006170062700637006470065700667006770068700697007070071700727007370074700757007670077700787007970080700817008270083700847008570086700877008870089700907009170092700937009470095700967009770098700997010070101701027010370104701057010670107701087010970110701117011270113701147011570116701177011870119701207012170122701237012470125701267012770128701297013070131701327013370134701357013670137701387013970140701417014270143701447014570146701477014870149701507015170152701537015470155701567015770158701597016070161701627016370164701657016670167701687016970170701717017270173701747017570176701777017870179701807018170182701837018470185701867018770188701897019070191701927019370194701957019670197701987019970200702017020270203702047020570206702077020870209702107021170212702137021470215702167021770218702197022070221702227022370224702257022670227702287022970230702317023270233702347023570236702377023870239702407024170242702437024470245702467024770248702497025070251702527025370254702557025670257702587025970260702617026270263702647026570266702677026870269702707027170272702737027470275702767027770278702797028070281702827028370284702857028670287702887028970290702917029270293702947029570296702977029870299703007030170302703037030470305703067030770308703097031070311703127031370314703157031670317703187031970320703217032270323703247032570326703277032870329703307033170332703337033470335703367033770338703397034070341703427034370344703457034670347703487034970350703517035270353703547035570356703577035870359703607036170362703637036470365703667036770368703697037070371703727037370374703757037670377703787037970380703817038270383703847038570386703877038870389703907039170392703937039470395703967039770398703997040070401704027040370404704057040670407704087040970410704117041270413704147041570416704177041870419704207042170422704237042470425704267042770428704297043070431704327043370434704357043670437704387043970440704417044270443704447044570446704477044870449704507045170452704537045470455704567045770458704597046070461704627046370464704657046670467704687046970470704717047270473704747047570476704777047870479704807048170482704837048470485704867048770488704897049070491704927049370494704957049670497704987049970500705017050270503705047050570506705077050870509705107051170512705137051470515705167051770518705197052070521705227052370524705257052670527705287052970530705317053270533705347053570536705377053870539705407054170542705437054470545705467054770548705497055070551705527055370554705557055670557705587055970560705617056270563705647056570566705677056870569705707057170572705737057470575705767057770578705797058070581705827058370584705857058670587705887058970590705917059270593705947059570596705977059870599706007060170602706037060470605706067060770608706097061070611706127061370614706157061670617706187061970620706217062270623706247062570626706277062870629706307063170632706337063470635706367063770638706397064070641706427064370644706457064670647706487064970650706517065270653706547065570656706577065870659706607066170662706637066470665706667066770668706697067070671706727067370674706757067670677706787067970680706817068270683706847068570686706877068870689706907069170692706937069470695706967069770698706997070070701707027070370704707057070670707707087070970710707117071270713707147071570716707177071870719707207072170722707237072470725707267072770728707297073070731707327073370734707357073670737707387073970740707417074270743707447074570746707477074870749707507075170752707537075470755707567075770758707597076070761707627076370764707657076670767707687076970770707717077270773707747077570776707777077870779707807078170782707837078470785707867078770788707897079070791707927079370794707957079670797707987079970800708017080270803708047080570806708077080870809708107081170812708137081470815708167081770818708197082070821708227082370824708257082670827708287082970830708317083270833708347083570836708377083870839708407084170842708437084470845708467084770848708497085070851708527085370854708557085670857708587085970860708617086270863708647086570866708677086870869708707087170872708737087470875708767087770878708797088070881708827088370884708857088670887708887088970890708917089270893708947089570896708977089870899709007090170902709037090470905709067090770908709097091070911709127091370914709157091670917709187091970920709217092270923709247092570926709277092870929709307093170932709337093470935709367093770938709397094070941709427094370944709457094670947709487094970950709517095270953709547095570956709577095870959709607096170962709637096470965709667096770968709697097070971709727097370974709757097670977709787097970980709817098270983709847098570986709877098870989709907099170992709937099470995709967099770998709997100071001710027100371004710057100671007710087100971010710117101271013710147101571016710177101871019710207102171022710237102471025710267102771028710297103071031710327103371034710357103671037710387103971040710417104271043710447104571046710477104871049710507105171052710537105471055710567105771058710597106071061710627106371064710657106671067710687106971070710717107271073710747107571076710777107871079710807108171082710837108471085710867108771088710897109071091710927109371094710957109671097710987109971100711017110271103711047110571106711077110871109711107111171112711137111471115711167111771118711197112071121711227112371124711257112671127711287112971130711317113271133711347113571136711377113871139711407114171142711437114471145711467114771148711497115071151711527115371154711557115671157711587115971160711617116271163711647116571166711677116871169711707117171172711737117471175711767117771178711797118071181711827118371184711857118671187711887118971190711917119271193711947119571196711977119871199712007120171202712037120471205712067120771208712097121071211712127121371214712157121671217712187121971220712217122271223712247122571226712277122871229712307123171232712337123471235712367123771238712397124071241712427124371244712457124671247712487124971250712517125271253712547125571256712577125871259712607126171262712637126471265712667126771268712697127071271712727127371274712757127671277712787127971280712817128271283712847128571286712877128871289712907129171292712937129471295712967129771298712997130071301713027130371304713057130671307713087130971310713117131271313713147131571316713177131871319713207132171322713237132471325713267132771328713297133071331713327133371334713357133671337713387133971340713417134271343713447134571346713477134871349713507135171352713537135471355713567135771358713597136071361713627136371364713657136671367713687136971370713717137271373713747137571376713777137871379713807138171382713837138471385713867138771388713897139071391713927139371394713957139671397713987139971400714017140271403714047140571406714077140871409714107141171412714137141471415714167141771418714197142071421714227142371424714257142671427714287142971430714317143271433714347143571436714377143871439714407144171442714437144471445714467144771448714497145071451714527145371454714557145671457714587145971460714617146271463714647146571466714677146871469714707147171472714737147471475714767147771478714797148071481714827148371484714857148671487714887148971490714917149271493714947149571496714977149871499715007150171502715037150471505715067150771508715097151071511715127151371514715157151671517715187151971520715217152271523715247152571526715277152871529715307153171532715337153471535715367153771538715397154071541715427154371544715457154671547715487154971550715517155271553715547155571556715577155871559715607156171562715637156471565715667156771568715697157071571715727157371574715757157671577715787157971580715817158271583715847158571586715877158871589715907159171592715937159471595715967159771598715997160071601716027160371604716057160671607716087160971610716117161271613716147161571616716177161871619716207162171622716237162471625716267162771628716297163071631716327163371634716357163671637716387163971640716417164271643716447164571646716477164871649716507165171652716537165471655716567165771658716597166071661716627166371664716657166671667716687166971670716717167271673716747167571676716777167871679716807168171682716837168471685716867168771688716897169071691716927169371694716957169671697716987169971700717017170271703717047170571706717077170871709717107171171712717137171471715717167171771718717197172071721717227172371724717257172671727717287172971730717317173271733717347173571736717377173871739717407174171742717437174471745717467174771748717497175071751717527175371754717557175671757717587175971760717617176271763717647176571766717677176871769717707177171772717737177471775717767177771778717797178071781717827178371784717857178671787717887178971790717917179271793717947179571796717977179871799718007180171802718037180471805718067180771808718097181071811718127181371814718157181671817718187181971820718217182271823718247182571826718277182871829718307183171832718337183471835718367183771838718397184071841718427184371844718457184671847718487184971850718517185271853718547185571856718577185871859718607186171862718637186471865718667186771868718697187071871718727187371874718757187671877718787187971880718817188271883718847188571886718877188871889718907189171892718937189471895718967189771898718997190071901719027190371904719057190671907719087190971910719117191271913719147191571916719177191871919719207192171922719237192471925719267192771928719297193071931719327193371934719357193671937719387193971940719417194271943719447194571946719477194871949719507195171952719537195471955719567195771958719597196071961719627196371964719657196671967719687196971970719717197271973719747197571976719777197871979719807198171982719837198471985719867198771988719897199071991719927199371994719957199671997719987199972000720017200272003720047200572006720077200872009720107201172012720137201472015720167201772018720197202072021720227202372024720257202672027720287202972030720317203272033720347203572036720377203872039720407204172042720437204472045720467204772048720497205072051720527205372054720557205672057720587205972060720617206272063720647206572066720677206872069720707207172072720737207472075720767207772078720797208072081720827208372084720857208672087720887208972090720917209272093720947209572096720977209872099721007210172102721037210472105721067210772108721097211072111721127211372114721157211672117721187211972120721217212272123721247212572126721277212872129721307213172132721337213472135721367213772138721397214072141721427214372144721457214672147721487214972150721517215272153721547215572156721577215872159721607216172162721637216472165721667216772168721697217072171721727217372174721757217672177721787217972180721817218272183721847218572186721877218872189721907219172192721937219472195721967219772198721997220072201722027220372204722057220672207722087220972210722117221272213722147221572216722177221872219722207222172222722237222472225722267222772228722297223072231722327223372234722357223672237722387223972240722417224272243722447224572246722477224872249722507225172252722537225472255722567225772258722597226072261722627226372264722657226672267722687226972270722717227272273722747227572276722777227872279722807228172282722837228472285722867228772288722897229072291722927229372294722957229672297722987229972300723017230272303723047230572306723077230872309723107231172312723137231472315723167231772318723197232072321723227232372324723257232672327723287232972330723317233272333723347233572336723377233872339723407234172342723437234472345723467234772348723497235072351723527235372354723557235672357723587235972360723617236272363723647236572366723677236872369723707237172372723737237472375723767237772378723797238072381723827238372384723857238672387723887238972390723917239272393723947239572396723977239872399724007240172402724037240472405724067240772408724097241072411724127241372414724157241672417724187241972420724217242272423724247242572426724277242872429724307243172432724337243472435724367243772438724397244072441724427244372444724457244672447724487244972450724517245272453724547245572456724577245872459724607246172462724637246472465724667246772468724697247072471724727247372474724757247672477724787247972480724817248272483724847248572486724877248872489724907249172492724937249472495724967249772498724997250072501725027250372504725057250672507725087250972510725117251272513725147251572516725177251872519725207252172522725237252472525725267252772528725297253072531725327253372534725357253672537725387253972540725417254272543725447254572546725477254872549725507255172552725537255472555725567255772558725597256072561725627256372564725657256672567725687256972570725717257272573725747257572576725777257872579725807258172582725837258472585725867258772588725897259072591725927259372594725957259672597725987259972600726017260272603726047260572606726077260872609726107261172612726137261472615726167261772618726197262072621726227262372624726257262672627726287262972630726317263272633726347263572636726377263872639726407264172642726437264472645726467264772648726497265072651726527265372654726557265672657726587265972660726617266272663726647266572666726677266872669726707267172672726737267472675726767267772678726797268072681726827268372684726857268672687726887268972690726917269272693726947269572696726977269872699727007270172702727037270472705727067270772708727097271072711727127271372714727157271672717727187271972720727217272272723727247272572726727277272872729727307273172732727337273472735727367273772738727397274072741727427274372744727457274672747727487274972750727517275272753727547275572756727577275872759727607276172762727637276472765727667276772768727697277072771727727277372774727757277672777727787277972780727817278272783727847278572786727877278872789727907279172792727937279472795727967279772798727997280072801728027280372804728057280672807728087280972810728117281272813728147281572816728177281872819728207282172822728237282472825728267282772828728297283072831728327283372834728357283672837728387283972840728417284272843728447284572846728477284872849728507285172852728537285472855728567285772858728597286072861728627286372864728657286672867728687286972870728717287272873728747287572876728777287872879728807288172882728837288472885728867288772888728897289072891728927289372894728957289672897728987289972900729017290272903729047290572906729077290872909729107291172912729137291472915729167291772918729197292072921729227292372924729257292672927729287292972930729317293272933729347293572936729377293872939729407294172942729437294472945729467294772948729497295072951729527295372954729557295672957729587295972960729617296272963729647296572966729677296872969729707297172972729737297472975729767297772978729797298072981729827298372984729857298672987729887298972990729917299272993729947299572996729977299872999730007300173002730037300473005730067300773008730097301073011730127301373014730157301673017730187301973020730217302273023730247302573026730277302873029730307303173032730337303473035730367303773038730397304073041730427304373044730457304673047730487304973050730517305273053730547305573056730577305873059730607306173062730637306473065730667306773068730697307073071730727307373074730757307673077730787307973080730817308273083730847308573086730877308873089730907309173092730937309473095730967309773098730997310073101731027310373104731057310673107731087310973110731117311273113731147311573116731177311873119731207312173122731237312473125731267312773128731297313073131731327313373134731357313673137731387313973140731417314273143731447314573146731477314873149731507315173152731537315473155731567315773158731597316073161731627316373164731657316673167731687316973170731717317273173731747317573176731777317873179731807318173182731837318473185731867318773188731897319073191731927319373194731957319673197731987319973200732017320273203732047320573206732077320873209732107321173212732137321473215732167321773218732197322073221732227322373224732257322673227732287322973230732317323273233732347323573236732377323873239732407324173242732437324473245732467324773248732497325073251732527325373254732557325673257732587325973260732617326273263732647326573266732677326873269732707327173272732737327473275732767327773278732797328073281732827328373284732857328673287732887328973290732917329273293732947329573296732977329873299733007330173302733037330473305733067330773308733097331073311733127331373314733157331673317733187331973320733217332273323733247332573326733277332873329733307333173332733337333473335733367333773338733397334073341733427334373344733457334673347733487334973350733517335273353733547335573356733577335873359733607336173362733637336473365733667336773368733697337073371733727337373374733757337673377733787337973380733817338273383733847338573386733877338873389733907339173392733937339473395733967339773398733997340073401734027340373404734057340673407734087340973410734117341273413734147341573416734177341873419734207342173422734237342473425734267342773428734297343073431734327343373434734357343673437734387343973440734417344273443734447344573446734477344873449734507345173452734537345473455734567345773458734597346073461734627346373464734657346673467734687346973470734717347273473734747347573476734777347873479734807348173482734837348473485734867348773488734897349073491734927349373494734957349673497734987349973500735017350273503735047350573506735077350873509735107351173512735137351473515735167351773518735197352073521735227352373524735257352673527735287352973530735317353273533735347353573536735377353873539735407354173542735437354473545735467354773548735497355073551735527355373554735557355673557735587355973560735617356273563735647356573566735677356873569735707357173572735737357473575735767357773578735797358073581735827358373584735857358673587735887358973590735917359273593735947359573596735977359873599736007360173602736037360473605736067360773608736097361073611736127361373614736157361673617736187361973620736217362273623736247362573626736277362873629736307363173632736337363473635736367363773638736397364073641736427364373644736457364673647736487364973650736517365273653736547365573656736577365873659736607366173662736637366473665736667366773668736697367073671736727367373674736757367673677736787367973680736817368273683736847368573686736877368873689736907369173692736937369473695736967369773698736997370073701737027370373704737057370673707737087370973710737117371273713737147371573716737177371873719737207372173722737237372473725737267372773728737297373073731737327373373734737357373673737737387373973740737417374273743737447374573746737477374873749737507375173752737537375473755737567375773758737597376073761737627376373764737657376673767737687376973770737717377273773737747377573776737777377873779737807378173782737837378473785737867378773788737897379073791737927379373794737957379673797737987379973800738017380273803738047380573806738077380873809738107381173812738137381473815738167381773818738197382073821738227382373824738257382673827738287382973830738317383273833738347383573836738377383873839738407384173842738437384473845738467384773848738497385073851738527385373854738557385673857738587385973860738617386273863738647386573866738677386873869738707387173872738737387473875738767387773878738797388073881738827388373884738857388673887738887388973890738917389273893738947389573896738977389873899739007390173902739037390473905739067390773908739097391073911739127391373914739157391673917739187391973920739217392273923739247392573926739277392873929739307393173932739337393473935739367393773938739397394073941739427394373944739457394673947739487394973950739517395273953739547395573956739577395873959739607396173962739637396473965739667396773968739697397073971739727397373974739757397673977739787397973980739817398273983739847398573986739877398873989739907399173992739937399473995739967399773998739997400074001740027400374004740057400674007740087400974010740117401274013740147401574016740177401874019740207402174022740237402474025740267402774028740297403074031740327403374034740357403674037740387403974040740417404274043740447404574046740477404874049740507405174052740537405474055740567405774058740597406074061740627406374064740657406674067740687406974070740717407274073740747407574076740777407874079740807408174082740837408474085740867408774088740897409074091740927409374094740957409674097740987409974100741017410274103741047410574106741077410874109741107411174112741137411474115741167411774118741197412074121741227412374124741257412674127741287412974130741317413274133741347413574136741377413874139741407414174142741437414474145741467414774148741497415074151741527415374154741557415674157741587415974160741617416274163741647416574166741677416874169741707417174172741737417474175741767417774178741797418074181741827418374184741857418674187741887418974190741917419274193741947419574196741977419874199742007420174202742037420474205742067420774208742097421074211742127421374214742157421674217742187421974220742217422274223742247422574226742277422874229742307423174232742337423474235742367423774238742397424074241742427424374244742457424674247742487424974250742517425274253742547425574256742577425874259742607426174262742637426474265742667426774268742697427074271742727427374274742757427674277742787427974280742817428274283742847428574286742877428874289742907429174292742937429474295742967429774298742997430074301743027430374304743057430674307743087430974310743117431274313743147431574316743177431874319743207432174322743237432474325743267432774328743297433074331743327433374334743357433674337743387433974340743417434274343743447434574346743477434874349743507435174352743537435474355743567435774358743597436074361743627436374364743657436674367743687436974370743717437274373743747437574376743777437874379743807438174382743837438474385743867438774388743897439074391743927439374394743957439674397743987439974400744017440274403744047440574406744077440874409744107441174412744137441474415744167441774418744197442074421744227442374424744257442674427744287442974430744317443274433744347443574436744377443874439744407444174442744437444474445744467444774448744497445074451744527445374454744557445674457744587445974460744617446274463744647446574466744677446874469744707447174472744737447474475744767447774478744797448074481744827448374484744857448674487744887448974490744917449274493744947449574496744977449874499745007450174502745037450474505745067450774508745097451074511745127451374514745157451674517745187451974520745217452274523745247452574526745277452874529745307453174532745337453474535745367453774538745397454074541745427454374544745457454674547745487454974550745517455274553745547455574556745577455874559745607456174562745637456474565745667456774568745697457074571745727457374574745757457674577745787457974580745817458274583745847458574586745877458874589745907459174592745937459474595745967459774598745997460074601746027460374604746057460674607746087460974610746117461274613746147461574616746177461874619746207462174622746237462474625746267462774628746297463074631746327463374634746357463674637746387463974640746417464274643746447464574646746477464874649746507465174652746537465474655746567465774658746597466074661746627466374664746657466674667746687466974670746717467274673746747467574676746777467874679746807468174682746837468474685746867468774688746897469074691746927469374694746957469674697746987469974700747017470274703747047470574706747077470874709747107471174712747137471474715747167471774718747197472074721747227472374724747257472674727747287472974730747317473274733747347473574736747377473874739747407474174742747437474474745747467474774748747497475074751747527475374754747557475674757747587475974760747617476274763747647476574766747677476874769747707477174772747737477474775747767477774778747797478074781747827478374784747857478674787747887478974790747917479274793747947479574796747977479874799748007480174802748037480474805748067480774808748097481074811748127481374814748157481674817748187481974820748217482274823748247482574826748277482874829748307483174832748337483474835748367483774838748397484074841748427484374844748457484674847748487484974850748517485274853748547485574856748577485874859748607486174862748637486474865748667486774868748697487074871748727487374874748757487674877748787487974880748817488274883748847488574886748877488874889748907489174892748937489474895748967489774898748997490074901749027490374904749057490674907749087490974910749117491274913749147491574916749177491874919749207492174922749237492474925749267492774928749297493074931749327493374934749357493674937749387493974940749417494274943749447494574946749477494874949749507495174952749537495474955749567495774958749597496074961749627496374964749657496674967749687496974970749717497274973749747497574976749777497874979749807498174982749837498474985749867498774988749897499074991749927499374994749957499674997749987499975000750017500275003750047500575006750077500875009750107501175012750137501475015750167501775018750197502075021750227502375024750257502675027750287502975030750317503275033750347503575036750377503875039750407504175042750437504475045750467504775048750497505075051750527505375054750557505675057750587505975060750617506275063750647506575066750677506875069750707507175072750737507475075750767507775078750797508075081750827508375084750857508675087750887508975090750917509275093750947509575096750977509875099751007510175102751037510475105751067510775108751097511075111751127511375114751157511675117751187511975120751217512275123751247512575126751277512875129751307513175132751337513475135751367513775138751397514075141751427514375144751457514675147751487514975150751517515275153751547515575156751577515875159751607516175162751637516475165751667516775168751697517075171751727517375174751757517675177751787517975180751817518275183751847518575186751877518875189751907519175192751937519475195751967519775198751997520075201752027520375204752057520675207752087520975210752117521275213752147521575216752177521875219752207522175222752237522475225752267522775228752297523075231752327523375234752357523675237752387523975240752417524275243752447524575246752477524875249752507525175252752537525475255752567525775258752597526075261752627526375264752657526675267752687526975270752717527275273752747527575276752777527875279752807528175282752837528475285752867528775288752897529075291752927529375294752957529675297752987529975300753017530275303753047530575306753077530875309753107531175312753137531475315753167531775318753197532075321753227532375324753257532675327753287532975330753317533275333753347533575336753377533875339753407534175342753437534475345753467534775348753497535075351753527535375354753557535675357753587535975360753617536275363753647536575366753677536875369753707537175372753737537475375753767537775378753797538075381753827538375384753857538675387753887538975390753917539275393753947539575396753977539875399754007540175402754037540475405754067540775408754097541075411754127541375414754157541675417754187541975420754217542275423754247542575426754277542875429754307543175432754337543475435754367543775438754397544075441754427544375444754457544675447754487544975450754517545275453754547545575456754577545875459754607546175462754637546475465754667546775468754697547075471754727547375474754757547675477754787547975480754817548275483754847548575486754877548875489754907549175492754937549475495754967549775498754997550075501755027550375504755057550675507755087550975510755117551275513755147551575516755177551875519755207552175522755237552475525755267552775528755297553075531755327553375534755357553675537755387553975540755417554275543755447554575546755477554875549755507555175552755537555475555755567555775558755597556075561755627556375564755657556675567755687556975570755717557275573755747557575576755777557875579755807558175582755837558475585755867558775588755897559075591755927559375594755957559675597755987559975600756017560275603756047560575606756077560875609756107561175612756137561475615756167561775618756197562075621756227562375624756257562675627756287562975630756317563275633756347563575636756377563875639756407564175642756437564475645756467564775648756497565075651756527565375654756557565675657756587565975660756617566275663756647566575666756677566875669756707567175672756737567475675756767567775678756797568075681756827568375684756857568675687756887568975690756917569275693756947569575696756977569875699757007570175702757037570475705757067570775708757097571075711757127571375714757157571675717757187571975720757217572275723757247572575726757277572875729757307573175732757337573475735757367573775738757397574075741757427574375744757457574675747757487574975750757517575275753757547575575756757577575875759757607576175762757637576475765757667576775768757697577075771757727577375774757757577675777757787577975780757817578275783757847578575786757877578875789757907579175792757937579475795757967579775798757997580075801758027580375804758057580675807758087580975810758117581275813758147581575816758177581875819758207582175822758237582475825758267582775828758297583075831758327583375834758357583675837758387583975840758417584275843758447584575846758477584875849758507585175852758537585475855758567585775858758597586075861758627586375864758657586675867758687586975870758717587275873758747587575876758777587875879758807588175882758837588475885758867588775888758897589075891758927589375894758957589675897758987589975900759017590275903759047590575906759077590875909759107591175912759137591475915759167591775918759197592075921759227592375924759257592675927759287592975930759317593275933759347593575936759377593875939759407594175942759437594475945759467594775948759497595075951759527595375954759557595675957759587595975960759617596275963759647596575966759677596875969759707597175972759737597475975759767597775978759797598075981759827598375984759857598675987759887598975990759917599275993759947599575996759977599875999760007600176002760037600476005760067600776008760097601076011760127601376014760157601676017760187601976020760217602276023760247602576026760277602876029760307603176032760337603476035760367603776038760397604076041760427604376044760457604676047760487604976050760517605276053760547605576056760577605876059760607606176062760637606476065760667606776068760697607076071760727607376074760757607676077760787607976080760817608276083760847608576086760877608876089760907609176092760937609476095760967609776098760997610076101761027610376104761057610676107761087610976110761117611276113761147611576116761177611876119761207612176122761237612476125761267612776128761297613076131761327613376134761357613676137761387613976140761417614276143761447614576146761477614876149761507615176152761537615476155761567615776158761597616076161761627616376164761657616676167761687616976170761717617276173761747617576176761777617876179761807618176182761837618476185761867618776188761897619076191761927619376194761957619676197761987619976200762017620276203762047620576206762077620876209762107621176212762137621476215762167621776218762197622076221762227622376224762257622676227762287622976230762317623276233762347623576236762377623876239762407624176242762437624476245762467624776248762497625076251762527625376254762557625676257762587625976260762617626276263762647626576266762677626876269762707627176272762737627476275762767627776278762797628076281762827628376284762857628676287762887628976290762917629276293762947629576296762977629876299763007630176302763037630476305763067630776308763097631076311763127631376314763157631676317763187631976320763217632276323763247632576326763277632876329763307633176332763337633476335763367633776338763397634076341763427634376344763457634676347763487634976350763517635276353763547635576356763577635876359763607636176362763637636476365763667636776368763697637076371763727637376374763757637676377763787637976380763817638276383763847638576386763877638876389763907639176392763937639476395763967639776398763997640076401764027640376404764057640676407764087640976410764117641276413764147641576416764177641876419764207642176422764237642476425764267642776428764297643076431764327643376434764357643676437764387643976440764417644276443764447644576446764477644876449764507645176452764537645476455764567645776458764597646076461764627646376464764657646676467764687646976470764717647276473764747647576476764777647876479764807648176482764837648476485764867648776488764897649076491764927649376494764957649676497764987649976500765017650276503765047650576506765077650876509765107651176512765137651476515765167651776518765197652076521765227652376524765257652676527765287652976530765317653276533765347653576536765377653876539765407654176542765437654476545765467654776548765497655076551765527655376554765557655676557765587655976560765617656276563765647656576566765677656876569765707657176572765737657476575765767657776578765797658076581765827658376584765857658676587765887658976590765917659276593765947659576596765977659876599766007660176602766037660476605766067660776608766097661076611766127661376614766157661676617766187661976620766217662276623766247662576626766277662876629766307663176632766337663476635766367663776638766397664076641766427664376644766457664676647766487664976650766517665276653766547665576656766577665876659766607666176662766637666476665766667666776668766697667076671766727667376674766757667676677766787667976680766817668276683766847668576686766877668876689766907669176692766937669476695766967669776698766997670076701767027670376704767057670676707767087670976710767117671276713767147671576716767177671876719767207672176722767237672476725767267672776728767297673076731767327673376734767357673676737767387673976740767417674276743767447674576746767477674876749767507675176752767537675476755767567675776758767597676076761767627676376764767657676676767767687676976770767717677276773767747677576776767777677876779767807678176782767837678476785767867678776788767897679076791767927679376794767957679676797767987679976800768017680276803768047680576806768077680876809768107681176812768137681476815768167681776818768197682076821768227682376824768257682676827768287682976830768317683276833768347683576836768377683876839768407684176842768437684476845768467684776848768497685076851768527685376854768557685676857768587685976860768617686276863768647686576866768677686876869768707687176872768737687476875768767687776878768797688076881768827688376884768857688676887768887688976890768917689276893768947689576896768977689876899769007690176902769037690476905769067690776908769097691076911769127691376914769157691676917769187691976920769217692276923769247692576926769277692876929769307693176932769337693476935769367693776938769397694076941769427694376944769457694676947769487694976950769517695276953769547695576956769577695876959769607696176962769637696476965769667696776968769697697076971769727697376974769757697676977769787697976980769817698276983769847698576986769877698876989769907699176992769937699476995769967699776998769997700077001770027700377004770057700677007770087700977010770117701277013770147701577016770177701877019770207702177022770237702477025770267702777028770297703077031770327703377034770357703677037770387703977040770417704277043770447704577046770477704877049770507705177052770537705477055770567705777058770597706077061770627706377064770657706677067770687706977070770717707277073770747707577076770777707877079770807708177082770837708477085770867708777088770897709077091770927709377094770957709677097770987709977100771017710277103771047710577106771077710877109771107711177112771137711477115771167711777118771197712077121771227712377124771257712677127771287712977130771317713277133771347713577136771377713877139771407714177142771437714477145771467714777148771497715077151771527715377154771557715677157771587715977160771617716277163771647716577166771677716877169771707717177172771737717477175771767717777178771797718077181771827718377184771857718677187771887718977190771917719277193771947719577196771977719877199772007720177202772037720477205772067720777208772097721077211772127721377214772157721677217772187721977220772217722277223772247722577226772277722877229772307723177232772337723477235772367723777238772397724077241772427724377244772457724677247772487724977250772517725277253772547725577256772577725877259772607726177262772637726477265772667726777268772697727077271772727727377274772757727677277772787727977280772817728277283772847728577286772877728877289772907729177292772937729477295772967729777298772997730077301773027730377304773057730677307773087730977310773117731277313773147731577316773177731877319773207732177322773237732477325773267732777328773297733077331773327733377334773357733677337773387733977340773417734277343773447734577346773477734877349773507735177352773537735477355773567735777358773597736077361773627736377364773657736677367773687736977370773717737277373773747737577376773777737877379773807738177382773837738477385773867738777388773897739077391773927739377394773957739677397773987739977400774017740277403774047740577406774077740877409774107741177412774137741477415774167741777418774197742077421774227742377424774257742677427774287742977430774317743277433774347743577436774377743877439774407744177442774437744477445774467744777448774497745077451774527745377454774557745677457774587745977460774617746277463774647746577466774677746877469774707747177472774737747477475774767747777478774797748077481774827748377484774857748677487774887748977490774917749277493774947749577496774977749877499775007750177502775037750477505775067750777508775097751077511775127751377514775157751677517775187751977520775217752277523775247752577526775277752877529775307753177532775337753477535775367753777538775397754077541775427754377544775457754677547775487754977550775517755277553775547755577556775577755877559775607756177562775637756477565775667756777568775697757077571775727757377574775757757677577775787757977580775817758277583775847758577586775877758877589775907759177592775937759477595775967759777598775997760077601776027760377604776057760677607776087760977610776117761277613776147761577616776177761877619776207762177622776237762477625776267762777628776297763077631776327763377634776357763677637776387763977640776417764277643776447764577646776477764877649776507765177652776537765477655776567765777658776597766077661776627766377664776657766677667776687766977670776717767277673776747767577676776777767877679776807768177682776837768477685776867768777688776897769077691776927769377694776957769677697776987769977700777017770277703777047770577706777077770877709777107771177712777137771477715777167771777718777197772077721777227772377724777257772677727777287772977730777317773277733777347773577736777377773877739777407774177742777437774477745777467774777748777497775077751777527775377754777557775677757777587775977760777617776277763777647776577766777677776877769777707777177772777737777477775777767777777778777797778077781777827778377784777857778677787777887778977790777917779277793777947779577796777977779877799778007780177802778037780477805778067780777808778097781077811778127781377814778157781677817778187781977820778217782277823778247782577826778277782877829778307783177832778337783477835778367783777838778397784077841778427784377844778457784677847778487784977850778517785277853778547785577856778577785877859778607786177862778637786477865778667786777868778697787077871778727787377874778757787677877778787787977880778817788277883778847788577886778877788877889778907789177892778937789477895778967789777898778997790077901779027790377904779057790677907779087790977910779117791277913779147791577916779177791877919779207792177922779237792477925779267792777928779297793077931779327793377934779357793677937779387793977940779417794277943779447794577946779477794877949779507795177952779537795477955779567795777958779597796077961779627796377964779657796677967779687796977970779717797277973779747797577976779777797877979779807798177982779837798477985779867798777988779897799077991779927799377994779957799677997779987799978000780017800278003780047800578006780077800878009780107801178012780137801478015780167801778018780197802078021780227802378024780257802678027780287802978030780317803278033780347803578036780377803878039780407804178042780437804478045780467804778048780497805078051780527805378054780557805678057780587805978060780617806278063780647806578066780677806878069780707807178072780737807478075780767807778078780797808078081780827808378084780857808678087780887808978090780917809278093780947809578096780977809878099781007810178102781037810478105781067810778108781097811078111781127811378114781157811678117781187811978120781217812278123781247812578126781277812878129781307813178132781337813478135781367813778138781397814078141781427814378144781457814678147781487814978150781517815278153781547815578156781577815878159781607816178162781637816478165781667816778168781697817078171781727817378174781757817678177781787817978180781817818278183781847818578186781877818878189781907819178192781937819478195781967819778198781997820078201782027820378204782057820678207782087820978210782117821278213782147821578216782177821878219782207822178222782237822478225782267822778228782297823078231782327823378234782357823678237782387823978240782417824278243782447824578246782477824878249782507825178252782537825478255782567825778258782597826078261782627826378264782657826678267782687826978270782717827278273782747827578276782777827878279782807828178282782837828478285782867828778288782897829078291782927829378294782957829678297782987829978300783017830278303783047830578306783077830878309783107831178312783137831478315783167831778318783197832078321783227832378324783257832678327783287832978330783317833278333783347833578336783377833878339783407834178342783437834478345783467834778348783497835078351783527835378354783557835678357783587835978360783617836278363783647836578366783677836878369783707837178372783737837478375783767837778378783797838078381783827838378384783857838678387783887838978390783917839278393783947839578396783977839878399784007840178402784037840478405784067840778408784097841078411784127841378414784157841678417784187841978420784217842278423784247842578426784277842878429784307843178432784337843478435784367843778438784397844078441784427844378444784457844678447784487844978450784517845278453784547845578456784577845878459784607846178462784637846478465784667846778468784697847078471784727847378474784757847678477784787847978480784817848278483784847848578486784877848878489784907849178492784937849478495784967849778498784997850078501785027850378504785057850678507785087850978510785117851278513785147851578516785177851878519785207852178522785237852478525785267852778528785297853078531785327853378534785357853678537785387853978540785417854278543785447854578546785477854878549785507855178552785537855478555785567855778558785597856078561785627856378564785657856678567785687856978570785717857278573785747857578576785777857878579785807858178582785837858478585785867858778588785897859078591785927859378594785957859678597785987859978600786017860278603786047860578606786077860878609786107861178612786137861478615786167861778618786197862078621786227862378624786257862678627786287862978630786317863278633786347863578636786377863878639786407864178642786437864478645786467864778648786497865078651786527865378654786557865678657786587865978660786617866278663786647866578666786677866878669786707867178672786737867478675786767867778678786797868078681786827868378684786857868678687786887868978690786917869278693786947869578696786977869878699787007870178702787037870478705787067870778708787097871078711787127871378714787157871678717787187871978720787217872278723787247872578726787277872878729787307873178732787337873478735787367873778738787397874078741787427874378744787457874678747787487874978750787517875278753787547875578756787577875878759787607876178762787637876478765787667876778768787697877078771787727877378774787757877678777787787877978780787817878278783787847878578786787877878878789787907879178792787937879478795787967879778798787997880078801788027880378804788057880678807788087880978810788117881278813788147881578816788177881878819788207882178822788237882478825788267882778828788297883078831788327883378834788357883678837788387883978840788417884278843788447884578846788477884878849788507885178852788537885478855788567885778858788597886078861788627886378864788657886678867788687886978870788717887278873788747887578876788777887878879788807888178882788837888478885788867888778888788897889078891788927889378894788957889678897788987889978900789017890278903789047890578906789077890878909789107891178912789137891478915789167891778918789197892078921789227892378924789257892678927789287892978930789317893278933789347893578936789377893878939789407894178942789437894478945789467894778948789497895078951789527895378954789557895678957789587895978960789617896278963789647896578966789677896878969789707897178972789737897478975789767897778978789797898078981789827898378984789857898678987789887898978990789917899278993789947899578996789977899878999790007900179002790037900479005790067900779008790097901079011790127901379014790157901679017790187901979020790217902279023790247902579026790277902879029790307903179032790337903479035790367903779038790397904079041790427904379044790457904679047790487904979050790517905279053790547905579056790577905879059790607906179062790637906479065790667906779068790697907079071790727907379074790757907679077790787907979080790817908279083790847908579086790877908879089790907909179092790937909479095790967909779098790997910079101791027910379104791057910679107791087910979110791117911279113791147911579116791177911879119791207912179122791237912479125791267912779128791297913079131791327913379134791357913679137791387913979140791417914279143791447914579146791477914879149791507915179152791537915479155791567915779158791597916079161791627916379164791657916679167791687916979170791717917279173791747917579176791777917879179791807918179182791837918479185791867918779188791897919079191791927919379194791957919679197791987919979200792017920279203792047920579206792077920879209792107921179212792137921479215792167921779218792197922079221792227922379224792257922679227792287922979230792317923279233792347923579236792377923879239792407924179242792437924479245792467924779248792497925079251792527925379254792557925679257792587925979260792617926279263792647926579266792677926879269792707927179272792737927479275792767927779278792797928079281792827928379284792857928679287792887928979290792917929279293792947929579296792977929879299793007930179302793037930479305793067930779308793097931079311793127931379314793157931679317793187931979320793217932279323793247932579326793277932879329793307933179332793337933479335793367933779338793397934079341793427934379344793457934679347793487934979350793517935279353793547935579356793577935879359793607936179362793637936479365793667936779368793697937079371793727937379374793757937679377793787937979380793817938279383793847938579386793877938879389793907939179392793937939479395793967939779398793997940079401794027940379404794057940679407794087940979410794117941279413794147941579416794177941879419794207942179422794237942479425794267942779428794297943079431794327943379434794357943679437794387943979440794417944279443794447944579446794477944879449794507945179452794537945479455794567945779458794597946079461794627946379464794657946679467794687946979470794717947279473794747947579476794777947879479794807948179482794837948479485794867948779488794897949079491794927949379494794957949679497794987949979500795017950279503795047950579506795077950879509795107951179512795137951479515795167951779518795197952079521795227952379524795257952679527795287952979530795317953279533795347953579536795377953879539795407954179542795437954479545795467954779548795497955079551795527955379554795557955679557795587955979560795617956279563795647956579566795677956879569795707957179572795737957479575795767957779578795797958079581795827958379584795857958679587795887958979590795917959279593795947959579596795977959879599796007960179602796037960479605796067960779608796097961079611796127961379614796157961679617796187961979620796217962279623796247962579626796277962879629796307963179632796337963479635796367963779638796397964079641796427964379644796457964679647796487964979650796517965279653796547965579656796577965879659796607966179662796637966479665796667966779668796697967079671796727967379674796757967679677796787967979680796817968279683796847968579686796877968879689796907969179692796937969479695796967969779698796997970079701797027970379704797057970679707797087970979710797117971279713797147971579716797177971879719797207972179722797237972479725797267972779728797297973079731797327973379734797357973679737797387973979740797417974279743797447974579746797477974879749797507975179752797537975479755797567975779758797597976079761797627976379764797657976679767797687976979770797717977279773797747977579776797777977879779797807978179782797837978479785797867978779788797897979079791797927979379794797957979679797797987979979800798017980279803798047980579806798077980879809798107981179812798137981479815798167981779818798197982079821798227982379824798257982679827798287982979830798317983279833798347983579836798377983879839798407984179842798437984479845798467984779848798497985079851798527985379854798557985679857798587985979860798617986279863798647986579866798677986879869798707987179872798737987479875798767987779878798797988079881798827988379884798857988679887798887988979890798917989279893798947989579896798977989879899799007990179902799037990479905799067990779908799097991079911799127991379914799157991679917799187991979920799217992279923799247992579926799277992879929799307993179932799337993479935799367993779938799397994079941799427994379944799457994679947799487994979950799517995279953799547995579956799577995879959799607996179962799637996479965799667996779968799697997079971799727997379974799757997679977799787997979980799817998279983799847998579986799877998879989799907999179992799937999479995799967999779998799998000080001800028000380004800058000680007800088000980010800118001280013800148001580016800178001880019800208002180022800238002480025800268002780028800298003080031800328003380034800358003680037800388003980040800418004280043800448004580046800478004880049800508005180052800538005480055800568005780058800598006080061800628006380064800658006680067800688006980070800718007280073800748007580076800778007880079800808008180082800838008480085800868008780088800898009080091800928009380094800958009680097800988009980100801018010280103801048010580106801078010880109801108011180112801138011480115801168011780118801198012080121801228012380124801258012680127801288012980130801318013280133801348013580136801378013880139801408014180142801438014480145801468014780148801498015080151801528015380154801558015680157801588015980160801618016280163801648016580166801678016880169801708017180172801738017480175801768017780178801798018080181801828018380184801858018680187801888018980190801918019280193801948019580196801978019880199802008020180202802038020480205802068020780208802098021080211802128021380214802158021680217802188021980220802218022280223802248022580226802278022880229802308023180232802338023480235802368023780238802398024080241802428024380244802458024680247802488024980250802518025280253802548025580256802578025880259802608026180262802638026480265802668026780268802698027080271802728027380274802758027680277802788027980280802818028280283802848028580286802878028880289802908029180292802938029480295802968029780298802998030080301803028030380304803058030680307803088030980310803118031280313803148031580316803178031880319803208032180322803238032480325803268032780328803298033080331803328033380334803358033680337803388033980340803418034280343803448034580346803478034880349803508035180352803538035480355803568035780358803598036080361803628036380364803658036680367803688036980370803718037280373803748037580376803778037880379803808038180382803838038480385803868038780388803898039080391803928039380394803958039680397803988039980400804018040280403804048040580406804078040880409804108041180412804138041480415804168041780418804198042080421804228042380424804258042680427804288042980430804318043280433804348043580436804378043880439804408044180442804438044480445804468044780448804498045080451804528045380454804558045680457804588045980460804618046280463804648046580466804678046880469804708047180472804738047480475804768047780478804798048080481804828048380484804858048680487804888048980490804918049280493804948049580496804978049880499805008050180502805038050480505805068050780508805098051080511805128051380514805158051680517805188051980520805218052280523805248052580526805278052880529805308053180532805338053480535805368053780538805398054080541805428054380544805458054680547805488054980550805518055280553805548055580556805578055880559805608056180562805638056480565805668056780568805698057080571805728057380574805758057680577805788057980580805818058280583805848058580586805878058880589805908059180592805938059480595805968059780598805998060080601806028060380604806058060680607806088060980610806118061280613806148061580616806178061880619806208062180622806238062480625806268062780628806298063080631806328063380634806358063680637806388063980640806418064280643806448064580646806478064880649806508065180652806538065480655806568065780658806598066080661806628066380664806658066680667806688066980670806718067280673806748067580676806778067880679806808068180682806838068480685806868068780688806898069080691806928069380694806958069680697806988069980700807018070280703807048070580706807078070880709807108071180712807138071480715807168071780718807198072080721807228072380724807258072680727807288072980730807318073280733807348073580736807378073880739807408074180742807438074480745807468074780748807498075080751807528075380754807558075680757807588075980760807618076280763807648076580766807678076880769807708077180772807738077480775807768077780778807798078080781807828078380784807858078680787807888078980790807918079280793807948079580796807978079880799808008080180802808038080480805808068080780808808098081080811808128081380814808158081680817808188081980820808218082280823808248082580826808278082880829808308083180832808338083480835808368083780838808398084080841808428084380844808458084680847808488084980850808518085280853808548085580856808578085880859808608086180862808638086480865808668086780868808698087080871808728087380874808758087680877808788087980880808818088280883808848088580886808878088880889808908089180892808938089480895808968089780898808998090080901809028090380904809058090680907809088090980910809118091280913809148091580916809178091880919809208092180922809238092480925809268092780928809298093080931809328093380934809358093680937809388093980940809418094280943809448094580946809478094880949809508095180952809538095480955809568095780958809598096080961809628096380964809658096680967809688096980970809718097280973809748097580976809778097880979809808098180982809838098480985809868098780988809898099080991809928099380994809958099680997809988099981000810018100281003810048100581006810078100881009810108101181012810138101481015810168101781018810198102081021810228102381024810258102681027810288102981030810318103281033810348103581036810378103881039810408104181042810438104481045810468104781048810498105081051810528105381054810558105681057810588105981060810618106281063810648106581066810678106881069810708107181072810738107481075810768107781078810798108081081810828108381084810858108681087810888108981090810918109281093810948109581096810978109881099811008110181102811038110481105811068110781108811098111081111811128111381114811158111681117811188111981120811218112281123811248112581126811278112881129811308113181132811338113481135811368113781138811398114081141811428114381144811458114681147811488114981150811518115281153811548115581156811578115881159811608116181162811638116481165811668116781168811698117081171811728117381174811758117681177811788117981180811818118281183811848118581186811878118881189811908119181192811938119481195811968119781198811998120081201812028120381204812058120681207812088120981210812118121281213812148121581216812178121881219812208122181222812238122481225812268122781228812298123081231812328123381234812358123681237812388123981240812418124281243812448124581246812478124881249812508125181252812538125481255812568125781258812598126081261812628126381264812658126681267812688126981270812718127281273812748127581276812778127881279812808128181282812838128481285812868128781288812898129081291812928129381294812958129681297812988129981300813018130281303813048130581306813078130881309813108131181312813138131481315813168131781318813198132081321813228132381324813258132681327813288132981330813318133281333813348133581336813378133881339813408134181342813438134481345813468134781348813498135081351813528135381354813558135681357813588135981360813618136281363813648136581366813678136881369813708137181372813738137481375813768137781378813798138081381813828138381384813858138681387813888138981390813918139281393813948139581396813978139881399814008140181402814038140481405814068140781408814098141081411814128141381414814158141681417814188141981420814218142281423814248142581426814278142881429814308143181432814338143481435814368143781438814398144081441814428144381444814458144681447814488144981450814518145281453814548145581456814578145881459814608146181462814638146481465814668146781468814698147081471814728147381474814758147681477814788147981480814818148281483814848148581486814878148881489814908149181492814938149481495814968149781498814998150081501815028150381504815058150681507815088150981510815118151281513815148151581516815178151881519815208152181522815238152481525815268152781528815298153081531815328153381534815358153681537815388153981540815418154281543815448154581546815478154881549815508155181552815538155481555815568155781558815598156081561815628156381564815658156681567815688156981570815718157281573815748157581576815778157881579815808158181582815838158481585815868158781588815898159081591815928159381594815958159681597815988159981600816018160281603816048160581606816078160881609816108161181612816138161481615816168161781618816198162081621816228162381624816258162681627816288162981630816318163281633816348163581636816378163881639816408164181642816438164481645816468164781648816498165081651816528165381654816558165681657816588165981660816618166281663816648166581666816678166881669816708167181672816738167481675816768167781678816798168081681816828168381684816858168681687816888168981690816918169281693816948169581696816978169881699817008170181702817038170481705817068170781708817098171081711817128171381714817158171681717817188171981720817218172281723817248172581726817278172881729817308173181732817338173481735817368173781738817398174081741817428174381744817458174681747817488174981750817518175281753817548175581756817578175881759817608176181762817638176481765817668176781768817698177081771817728177381774817758177681777817788177981780817818178281783817848178581786817878178881789817908179181792817938179481795817968179781798817998180081801818028180381804818058180681807818088180981810818118181281813818148181581816818178181881819818208182181822818238182481825818268182781828818298183081831818328183381834818358183681837818388183981840818418184281843818448184581846818478184881849818508185181852818538185481855818568185781858818598186081861818628186381864818658186681867818688186981870818718187281873818748187581876818778187881879818808188181882818838188481885818868188781888818898189081891818928189381894818958189681897818988189981900819018190281903819048190581906819078190881909819108191181912819138191481915819168191781918819198192081921819228192381924819258192681927819288192981930819318193281933819348193581936819378193881939819408194181942819438194481945819468194781948819498195081951819528195381954819558195681957819588195981960819618196281963819648196581966819678196881969819708197181972819738197481975819768197781978819798198081981819828198381984819858198681987819888198981990819918199281993819948199581996819978199881999820008200182002820038200482005820068200782008820098201082011820128201382014820158201682017820188201982020820218202282023820248202582026820278202882029820308203182032820338203482035820368203782038820398204082041820428204382044820458204682047820488204982050820518205282053820548205582056820578205882059820608206182062820638206482065820668206782068820698207082071820728207382074820758207682077820788207982080820818208282083820848208582086820878208882089820908209182092820938209482095820968209782098820998210082101821028210382104821058210682107821088210982110821118211282113821148211582116821178211882119821208212182122821238212482125821268212782128821298213082131821328213382134821358213682137821388213982140821418214282143821448214582146821478214882149821508215182152821538215482155821568215782158821598216082161821628216382164821658216682167821688216982170821718217282173821748217582176821778217882179821808218182182821838218482185821868218782188821898219082191821928219382194821958219682197821988219982200822018220282203822048220582206822078220882209822108221182212822138221482215822168221782218822198222082221822228222382224822258222682227822288222982230822318223282233822348223582236822378223882239822408224182242822438224482245822468224782248822498225082251822528225382254822558225682257822588225982260822618226282263822648226582266822678226882269822708227182272822738227482275822768227782278822798228082281822828228382284822858228682287822888228982290822918229282293822948229582296822978229882299823008230182302823038230482305823068230782308823098231082311823128231382314823158231682317823188231982320823218232282323823248232582326823278232882329823308233182332823338233482335823368233782338823398234082341823428234382344823458234682347823488234982350823518235282353823548235582356823578235882359823608236182362823638236482365823668236782368823698237082371823728237382374823758237682377823788237982380823818238282383823848238582386823878238882389823908239182392823938239482395823968239782398823998240082401824028240382404824058240682407824088240982410824118241282413824148241582416824178241882419824208242182422824238242482425824268242782428824298243082431824328243382434824358243682437824388243982440824418244282443824448244582446824478244882449824508245182452824538245482455824568245782458824598246082461824628246382464824658246682467824688246982470824718247282473824748247582476824778247882479824808248182482824838248482485824868248782488824898249082491824928249382494824958249682497824988249982500825018250282503825048250582506825078250882509825108251182512825138251482515825168251782518825198252082521825228252382524825258252682527825288252982530825318253282533825348253582536825378253882539825408254182542825438254482545825468254782548825498255082551825528255382554825558255682557825588255982560825618256282563825648256582566825678256882569825708257182572825738257482575825768257782578825798258082581825828258382584825858258682587825888258982590825918259282593825948259582596825978259882599826008260182602826038260482605826068260782608826098261082611826128261382614826158261682617826188261982620826218262282623826248262582626826278262882629826308263182632826338263482635826368263782638826398264082641826428264382644826458264682647826488264982650826518265282653826548265582656826578265882659826608266182662826638266482665826668266782668826698267082671826728267382674826758267682677826788267982680826818268282683826848268582686826878268882689826908269182692826938269482695826968269782698826998270082701827028270382704827058270682707827088270982710827118271282713827148271582716827178271882719827208272182722827238272482725827268272782728827298273082731827328273382734827358273682737827388273982740827418274282743827448274582746827478274882749827508275182752827538275482755827568275782758827598276082761827628276382764827658276682767827688276982770827718277282773827748277582776827778277882779827808278182782827838278482785827868278782788827898279082791827928279382794827958279682797827988279982800828018280282803828048280582806828078280882809828108281182812828138281482815828168281782818828198282082821828228282382824828258282682827828288282982830828318283282833828348283582836828378283882839828408284182842828438284482845828468284782848828498285082851828528285382854828558285682857828588285982860828618286282863828648286582866828678286882869828708287182872828738287482875828768287782878828798288082881828828288382884828858288682887828888288982890828918289282893828948289582896828978289882899829008290182902829038290482905829068290782908829098291082911829128291382914829158291682917829188291982920829218292282923829248292582926829278292882929829308293182932829338293482935829368293782938829398294082941829428294382944829458294682947829488294982950829518295282953829548295582956829578295882959829608296182962829638296482965829668296782968829698297082971829728297382974829758297682977829788297982980829818298282983829848298582986829878298882989829908299182992829938299482995829968299782998829998300083001830028300383004830058300683007830088300983010830118301283013830148301583016830178301883019830208302183022830238302483025830268302783028830298303083031830328303383034830358303683037830388303983040830418304283043830448304583046830478304883049830508305183052830538305483055830568305783058830598306083061830628306383064830658306683067830688306983070830718307283073830748307583076830778307883079830808308183082830838308483085830868308783088830898309083091830928309383094830958309683097830988309983100831018310283103831048310583106831078310883109831108311183112831138311483115831168311783118831198312083121831228312383124831258312683127831288312983130831318313283133831348313583136831378313883139831408314183142831438314483145831468314783148831498315083151831528315383154831558315683157831588315983160831618316283163831648316583166831678316883169831708317183172831738317483175831768317783178831798318083181831828318383184831858318683187831888318983190831918319283193831948319583196831978319883199832008320183202832038320483205832068320783208832098321083211832128321383214832158321683217832188321983220832218322283223832248322583226832278322883229832308323183232832338323483235832368323783238832398324083241832428324383244832458324683247832488324983250832518325283253832548325583256832578325883259832608326183262832638326483265832668326783268832698327083271832728327383274832758327683277832788327983280832818328283283832848328583286832878328883289832908329183292832938329483295832968329783298832998330083301833028330383304833058330683307833088330983310833118331283313833148331583316833178331883319833208332183322833238332483325833268332783328833298333083331833328333383334833358333683337833388333983340833418334283343833448334583346833478334883349833508335183352833538335483355833568335783358833598336083361833628336383364833658336683367833688336983370833718337283373833748337583376833778337883379833808338183382833838338483385833868338783388833898339083391833928339383394833958339683397833988339983400834018340283403834048340583406834078340883409834108341183412834138341483415834168341783418834198342083421834228342383424834258342683427834288342983430834318343283433834348343583436834378343883439834408344183442834438344483445834468344783448834498345083451834528345383454834558345683457834588345983460834618346283463834648346583466834678346883469834708347183472834738347483475834768347783478834798348083481834828348383484834858348683487834888348983490834918349283493834948349583496834978349883499835008350183502835038350483505835068350783508835098351083511835128351383514835158351683517835188351983520835218352283523835248352583526835278352883529835308353183532835338353483535835368353783538835398354083541835428354383544835458354683547835488354983550835518355283553835548355583556835578355883559835608356183562835638356483565835668356783568835698357083571835728357383574835758357683577835788357983580835818358283583835848358583586835878358883589835908359183592835938359483595835968359783598835998360083601836028360383604836058360683607836088360983610836118361283613836148361583616836178361883619836208362183622836238362483625836268362783628836298363083631836328363383634836358363683637836388363983640836418364283643836448364583646836478364883649836508365183652836538365483655836568365783658836598366083661836628366383664836658366683667836688366983670836718367283673836748367583676836778367883679836808368183682836838368483685836868368783688836898369083691836928369383694836958369683697836988369983700837018370283703837048370583706837078370883709837108371183712837138371483715837168371783718837198372083721837228372383724837258372683727837288372983730837318373283733837348373583736837378373883739837408374183742837438374483745837468374783748837498375083751837528375383754837558375683757837588375983760837618376283763837648376583766837678376883769837708377183772837738377483775837768377783778837798378083781837828378383784837858378683787837888378983790837918379283793837948379583796837978379883799838008380183802838038380483805838068380783808838098381083811838128381383814838158381683817838188381983820838218382283823838248382583826838278382883829838308383183832838338383483835838368383783838838398384083841838428384383844838458384683847838488384983850838518385283853838548385583856838578385883859838608386183862838638386483865838668386783868838698387083871838728387383874838758387683877838788387983880838818388283883838848388583886838878388883889838908389183892838938389483895838968389783898838998390083901839028390383904839058390683907839088390983910839118391283913839148391583916839178391883919839208392183922839238392483925839268392783928839298393083931839328393383934839358393683937839388393983940839418394283943839448394583946839478394883949839508395183952839538395483955839568395783958839598396083961839628396383964839658396683967839688396983970839718397283973839748397583976839778397883979839808398183982839838398483985839868398783988839898399083991839928399383994839958399683997839988399984000840018400284003840048400584006840078400884009840108401184012840138401484015840168401784018840198402084021840228402384024840258402684027840288402984030840318403284033840348403584036840378403884039840408404184042840438404484045840468404784048840498405084051840528405384054840558405684057840588405984060840618406284063840648406584066840678406884069840708407184072840738407484075840768407784078840798408084081840828408384084840858408684087840888408984090840918409284093840948409584096840978409884099841008410184102841038410484105841068410784108841098411084111841128411384114841158411684117841188411984120841218412284123841248412584126841278412884129841308413184132841338413484135841368413784138841398414084141841428414384144841458414684147841488414984150841518415284153841548415584156841578415884159841608416184162841638416484165841668416784168841698417084171841728417384174841758417684177841788417984180841818418284183841848418584186841878418884189841908419184192841938419484195841968419784198841998420084201842028420384204842058420684207842088420984210842118421284213842148421584216842178421884219842208422184222842238422484225842268422784228842298423084231842328423384234842358423684237842388423984240842418424284243842448424584246842478424884249842508425184252842538425484255842568425784258842598426084261842628426384264842658426684267842688426984270842718427284273842748427584276842778427884279842808428184282842838428484285842868428784288842898429084291842928429384294842958429684297842988429984300843018430284303843048430584306843078430884309843108431184312843138431484315843168431784318843198432084321843228432384324843258432684327843288432984330843318433284333843348433584336843378433884339843408434184342843438434484345843468434784348843498435084351843528435384354843558435684357843588435984360843618436284363843648436584366843678436884369843708437184372843738437484375843768437784378843798438084381843828438384384843858438684387843888438984390843918439284393843948439584396843978439884399844008440184402844038440484405844068440784408844098441084411844128441384414844158441684417844188441984420844218442284423844248442584426844278442884429844308443184432844338443484435844368443784438844398444084441844428444384444844458444684447844488444984450844518445284453844548445584456844578445884459844608446184462844638446484465844668446784468844698447084471844728447384474844758447684477844788447984480844818448284483844848448584486844878448884489844908449184492844938449484495844968449784498844998450084501845028450384504845058450684507845088450984510845118451284513845148451584516845178451884519845208452184522845238452484525845268452784528845298453084531845328453384534845358453684537845388453984540845418454284543845448454584546845478454884549845508455184552845538455484555845568455784558845598456084561845628456384564845658456684567845688456984570845718457284573845748457584576845778457884579845808458184582845838458484585845868458784588845898459084591845928459384594845958459684597845988459984600846018460284603846048460584606846078460884609846108461184612846138461484615846168461784618846198462084621846228462384624846258462684627846288462984630846318463284633846348463584636846378463884639846408464184642846438464484645846468464784648846498465084651846528465384654846558465684657846588465984660846618466284663846648466584666846678466884669846708467184672846738467484675846768467784678846798468084681846828468384684846858468684687846888468984690846918469284693846948469584696846978469884699847008470184702847038470484705847068470784708847098471084711847128471384714847158471684717847188471984720847218472284723847248472584726847278472884729847308473184732847338473484735847368473784738847398474084741847428474384744847458474684747847488474984750847518475284753847548475584756847578475884759847608476184762847638476484765847668476784768847698477084771847728477384774847758477684777847788477984780847818478284783847848478584786847878478884789847908479184792847938479484795847968479784798847998480084801848028480384804848058480684807848088480984810848118481284813848148481584816848178481884819848208482184822848238482484825848268482784828848298483084831848328483384834848358483684837848388483984840848418484284843848448484584846848478484884849848508485184852848538485484855848568485784858848598486084861848628486384864848658486684867848688486984870848718487284873848748487584876848778487884879848808488184882848838488484885848868488784888848898489084891848928489384894848958489684897848988489984900849018490284903849048490584906849078490884909849108491184912849138491484915849168491784918849198492084921849228492384924849258492684927849288492984930849318493284933849348493584936849378493884939849408494184942849438494484945849468494784948849498495084951849528495384954849558495684957849588495984960849618496284963849648496584966849678496884969849708497184972849738497484975849768497784978849798498084981849828498384984849858498684987849888498984990849918499284993849948499584996849978499884999850008500185002850038500485005850068500785008850098501085011850128501385014850158501685017850188501985020850218502285023850248502585026850278502885029850308503185032850338503485035850368503785038850398504085041850428504385044850458504685047850488504985050850518505285053850548505585056850578505885059850608506185062850638506485065850668506785068850698507085071850728507385074850758507685077850788507985080850818508285083850848508585086850878508885089850908509185092850938509485095850968509785098850998510085101851028510385104851058510685107851088510985110851118511285113851148511585116851178511885119851208512185122851238512485125851268512785128851298513085131851328513385134851358513685137851388513985140851418514285143851448514585146851478514885149851508515185152851538515485155851568515785158851598516085161851628516385164851658516685167851688516985170851718517285173851748517585176851778517885179851808518185182851838518485185851868518785188851898519085191851928519385194851958519685197851988519985200852018520285203852048520585206852078520885209852108521185212852138521485215852168521785218852198522085221852228522385224852258522685227852288522985230852318523285233852348523585236852378523885239852408524185242852438524485245852468524785248852498525085251852528525385254852558525685257852588525985260852618526285263852648526585266852678526885269852708527185272852738527485275852768527785278852798528085281852828528385284852858528685287852888528985290852918529285293852948529585296852978529885299853008530185302853038530485305853068530785308853098531085311853128531385314853158531685317853188531985320853218532285323853248532585326853278532885329853308533185332853338533485335853368533785338853398534085341853428534385344853458534685347853488534985350853518535285353853548535585356853578535885359853608536185362853638536485365853668536785368853698537085371853728537385374853758537685377853788537985380853818538285383853848538585386853878538885389853908539185392853938539485395853968539785398853998540085401854028540385404854058540685407854088540985410854118541285413854148541585416854178541885419854208542185422854238542485425854268542785428854298543085431854328543385434854358543685437854388543985440854418544285443854448544585446854478544885449854508545185452854538545485455854568545785458854598546085461854628546385464854658546685467854688546985470854718547285473854748547585476854778547885479854808548185482854838548485485854868548785488854898549085491854928549385494854958549685497854988549985500855018550285503855048550585506855078550885509855108551185512855138551485515855168551785518855198552085521855228552385524855258552685527855288552985530855318553285533855348553585536855378553885539855408554185542855438554485545855468554785548855498555085551855528555385554855558555685557855588555985560855618556285563855648556585566855678556885569855708557185572855738557485575855768557785578855798558085581855828558385584855858558685587855888558985590855918559285593855948559585596855978559885599856008560185602856038560485605856068560785608856098561085611856128561385614856158561685617856188561985620856218562285623856248562585626856278562885629856308563185632856338563485635856368563785638856398564085641856428564385644856458564685647856488564985650856518565285653856548565585656856578565885659856608566185662856638566485665856668566785668856698567085671856728567385674856758567685677856788567985680856818568285683856848568585686856878568885689856908569185692856938569485695856968569785698856998570085701857028570385704857058570685707857088570985710857118571285713857148571585716857178571885719857208572185722857238572485725857268572785728857298573085731857328573385734857358573685737857388573985740857418574285743857448574585746857478574885749857508575185752857538575485755857568575785758857598576085761857628576385764857658576685767857688576985770857718577285773857748577585776857778577885779857808578185782857838578485785857868578785788857898579085791857928579385794857958579685797857988579985800858018580285803858048580585806858078580885809858108581185812858138581485815858168581785818858198582085821858228582385824858258582685827858288582985830858318583285833858348583585836858378583885839858408584185842858438584485845858468584785848858498585085851858528585385854858558585685857858588585985860858618586285863858648586585866858678586885869858708587185872858738587485875858768587785878858798588085881858828588385884858858588685887858888588985890858918589285893858948589585896858978589885899859008590185902859038590485905859068590785908859098591085911859128591385914859158591685917859188591985920859218592285923859248592585926859278592885929859308593185932859338593485935859368593785938859398594085941859428594385944859458594685947859488594985950859518595285953859548595585956859578595885959859608596185962859638596485965859668596785968859698597085971859728597385974859758597685977859788597985980859818598285983859848598585986859878598885989859908599185992859938599485995859968599785998859998600086001860028600386004860058600686007860088600986010860118601286013860148601586016860178601886019860208602186022860238602486025860268602786028860298603086031860328603386034860358603686037860388603986040860418604286043860448604586046860478604886049860508605186052860538605486055860568605786058860598606086061860628606386064860658606686067860688606986070860718607286073860748607586076860778607886079860808608186082860838608486085860868608786088860898609086091860928609386094860958609686097860988609986100861018610286103861048610586106861078610886109861108611186112861138611486115861168611786118861198612086121861228612386124861258612686127861288612986130861318613286133861348613586136861378613886139861408614186142861438614486145861468614786148861498615086151861528615386154861558615686157861588615986160861618616286163861648616586166861678616886169861708617186172861738617486175861768617786178861798618086181861828618386184861858618686187861888618986190861918619286193861948619586196861978619886199862008620186202862038620486205862068620786208862098621086211862128621386214862158621686217862188621986220862218622286223862248622586226862278622886229862308623186232862338623486235862368623786238862398624086241862428624386244862458624686247862488624986250862518625286253862548625586256862578625886259862608626186262862638626486265862668626786268862698627086271862728627386274862758627686277862788627986280862818628286283862848628586286862878628886289862908629186292862938629486295862968629786298862998630086301863028630386304863058630686307863088630986310863118631286313863148631586316863178631886319863208632186322863238632486325863268632786328863298633086331863328633386334863358633686337863388633986340863418634286343863448634586346863478634886349863508635186352863538635486355863568635786358863598636086361863628636386364863658636686367863688636986370863718637286373863748637586376863778637886379863808638186382863838638486385863868638786388863898639086391863928639386394863958639686397863988639986400864018640286403864048640586406864078640886409864108641186412864138641486415864168641786418864198642086421864228642386424864258642686427864288642986430864318643286433864348643586436864378643886439864408644186442864438644486445864468644786448864498645086451864528645386454864558645686457864588645986460864618646286463864648646586466864678646886469864708647186472864738647486475864768647786478864798648086481864828648386484864858648686487864888648986490864918649286493864948649586496864978649886499865008650186502865038650486505865068650786508865098651086511865128651386514865158651686517865188651986520865218652286523865248652586526865278652886529865308653186532865338653486535865368653786538865398654086541865428654386544865458654686547865488654986550865518655286553865548655586556865578655886559865608656186562865638656486565865668656786568865698657086571865728657386574865758657686577865788657986580865818658286583865848658586586865878658886589865908659186592865938659486595865968659786598865998660086601866028660386604866058660686607866088660986610866118661286613866148661586616866178661886619866208662186622866238662486625866268662786628866298663086631866328663386634866358663686637866388663986640866418664286643866448664586646866478664886649866508665186652866538665486655866568665786658866598666086661866628666386664866658666686667866688666986670866718667286673866748667586676866778667886679866808668186682866838668486685866868668786688866898669086691866928669386694866958669686697866988669986700867018670286703867048670586706867078670886709867108671186712867138671486715867168671786718867198672086721867228672386724867258672686727867288672986730867318673286733867348673586736867378673886739867408674186742867438674486745867468674786748867498675086751867528675386754867558675686757867588675986760867618676286763867648676586766867678676886769867708677186772867738677486775867768677786778867798678086781867828678386784867858678686787867888678986790867918679286793867948679586796867978679886799868008680186802868038680486805868068680786808868098681086811868128681386814868158681686817868188681986820868218682286823868248682586826868278682886829868308683186832868338683486835868368683786838868398684086841868428684386844868458684686847868488684986850868518685286853868548685586856868578685886859868608686186862868638686486865868668686786868868698687086871868728687386874868758687686877868788687986880868818688286883868848688586886868878688886889868908689186892868938689486895868968689786898868998690086901869028690386904869058690686907869088690986910869118691286913869148691586916869178691886919869208692186922869238692486925869268692786928869298693086931869328693386934869358693686937869388693986940869418694286943869448694586946869478694886949869508695186952869538695486955869568695786958869598696086961869628696386964869658696686967869688696986970869718697286973869748697586976869778697886979869808698186982869838698486985869868698786988869898699086991869928699386994869958699686997869988699987000870018700287003870048700587006870078700887009870108701187012870138701487015870168701787018870198702087021870228702387024870258702687027870288702987030870318703287033870348703587036870378703887039870408704187042870438704487045870468704787048870498705087051870528705387054870558705687057870588705987060870618706287063870648706587066870678706887069870708707187072870738707487075870768707787078870798708087081870828708387084870858708687087870888708987090870918709287093870948709587096870978709887099871008710187102871038710487105871068710787108871098711087111871128711387114871158711687117871188711987120871218712287123871248712587126871278712887129871308713187132871338713487135871368713787138871398714087141871428714387144871458714687147871488714987150871518715287153871548715587156871578715887159871608716187162871638716487165871668716787168871698717087171871728717387174871758717687177871788717987180871818718287183871848718587186871878718887189871908719187192871938719487195871968719787198871998720087201872028720387204872058720687207872088720987210872118721287213872148721587216872178721887219872208722187222872238722487225872268722787228872298723087231872328723387234872358723687237872388723987240872418724287243872448724587246872478724887249872508725187252872538725487255872568725787258872598726087261872628726387264872658726687267872688726987270872718727287273872748727587276872778727887279872808728187282872838728487285872868728787288872898729087291872928729387294872958729687297872988729987300873018730287303873048730587306873078730887309873108731187312873138731487315873168731787318873198732087321873228732387324873258732687327873288732987330873318733287333873348733587336873378733887339873408734187342873438734487345873468734787348873498735087351873528735387354873558735687357873588735987360873618736287363873648736587366873678736887369873708737187372873738737487375873768737787378873798738087381873828738387384873858738687387873888738987390873918739287393873948739587396873978739887399874008740187402874038740487405874068740787408874098741087411874128741387414874158741687417874188741987420874218742287423874248742587426874278742887429874308743187432874338743487435874368743787438874398744087441874428744387444874458744687447874488744987450874518745287453874548745587456874578745887459874608746187462874638746487465874668746787468874698747087471874728747387474874758747687477874788747987480874818748287483874848748587486874878748887489874908749187492874938749487495874968749787498874998750087501875028750387504875058750687507875088750987510875118751287513875148751587516875178751887519875208752187522875238752487525875268752787528875298753087531875328753387534875358753687537875388753987540875418754287543875448754587546875478754887549875508755187552875538755487555875568755787558875598756087561875628756387564875658756687567875688756987570875718757287573875748757587576875778757887579875808758187582875838758487585875868758787588875898759087591875928759387594875958759687597875988759987600876018760287603876048760587606876078760887609876108761187612876138761487615876168761787618876198762087621876228762387624876258762687627876288762987630876318763287633876348763587636876378763887639876408764187642876438764487645876468764787648876498765087651876528765387654876558765687657876588765987660876618766287663876648766587666876678766887669876708767187672876738767487675876768767787678876798768087681876828768387684876858768687687876888768987690876918769287693876948769587696876978769887699877008770187702877038770487705877068770787708877098771087711877128771387714877158771687717877188771987720877218772287723877248772587726877278772887729877308773187732877338773487735877368773787738877398774087741877428774387744877458774687747877488774987750877518775287753877548775587756877578775887759877608776187762877638776487765877668776787768877698777087771877728777387774877758777687777877788777987780877818778287783877848778587786877878778887789877908779187792877938779487795877968779787798877998780087801878028780387804878058780687807878088780987810878118781287813878148781587816878178781887819878208782187822878238782487825878268782787828878298783087831878328783387834878358783687837878388783987840 |
- /*
- This file is part of Sencha Touch 2.1
- Copyright (c) 2011-2012 Sencha Inc
- Contact: http://www.sencha.com/contact
- Commercial Usage
- Licensees holding valid commercial licenses may use this file in accordance with the Commercial
- Software License Agreement provided with the Software or, alternatively, in accordance with the
- terms contained in a written agreement between you and Sencha.
- If you are unsure which license is appropriate for your use, please contact the sales department
- at http://www.sencha.com/contact.
- Build date: 2012-11-05 22:31:29 (08c91901ae8449841ff23e5d3fb404d6128d3b0b)
- */
- //@tag foundation,core
- //@define Ext
- /**
- * @class Ext
- * @singleton
- */
- (function() {
- var global = this,
- objectPrototype = Object.prototype,
- toString = objectPrototype.toString,
- enumerables = true,
- enumerablesTest = { toString: 1 },
- emptyFn = function(){},
- i;
- if (typeof Ext === 'undefined') {
- global.Ext = {};
- }
- Ext.global = global;
- for (i in enumerablesTest) {
- enumerables = null;
- }
- if (enumerables) {
- enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable',
- 'toLocaleString', 'toString', 'constructor'];
- }
- /**
- * An array containing extra enumerables for old browsers.
- * @property {String[]}
- */
- Ext.enumerables = enumerables;
- /**
- * Copies all the properties of config to the specified object.
- * Note that if recursive merging and cloning without referencing the original objects / arrays is needed, use
- * {@link Ext.Object#merge} instead.
- * @param {Object} object The receiver of the properties.
- * @param {Object} config The source of the properties.
- * @param {Object} [defaults] A different object that will also be applied for default values.
- * @return {Object} returns obj
- */
- Ext.apply = function(object, config, defaults) {
- if (defaults) {
- Ext.apply(object, defaults);
- }
- if (object && config && typeof config === 'object') {
- var i, j, k;
- for (i in config) {
- object[i] = config[i];
- }
- if (enumerables) {
- for (j = enumerables.length; j--;) {
- k = enumerables[j];
- if (config.hasOwnProperty(k)) {
- object[k] = config[k];
- }
- }
- }
- }
- return object;
- };
- Ext.buildSettings = Ext.apply({
- baseCSSPrefix: 'x-',
- scopeResetCSS: false
- }, Ext.buildSettings || {});
- Ext.apply(Ext, {
- /**
- * @property {Function}
- * A reusable empty function
- */
- emptyFn: emptyFn,
- baseCSSPrefix: Ext.buildSettings.baseCSSPrefix,
- /**
- * Copies all the properties of config to object if they don't already exist.
- * @param {Object} object The receiver of the properties.
- * @param {Object} config The source of the properties.
- * @return {Object} returns obj
- */
- applyIf: function(object, config) {
- var property;
- if (object) {
- for (property in config) {
- if (object[property] === undefined) {
- object[property] = config[property];
- }
- }
- }
- return object;
- },
- /**
- * Iterates either an array or an object. This method delegates to
- * {@link Ext.Array#each Ext.Array.each} if the given value is iterable, and {@link Ext.Object#each Ext.Object.each} otherwise.
- *
- * @param {Object/Array} object The object or array to be iterated.
- * @param {Function} fn The function to be called for each iteration. See and {@link Ext.Array#each Ext.Array.each} and
- * {@link Ext.Object#each Ext.Object.each} for detailed lists of arguments passed to this function depending on the given object
- * type that is being iterated.
- * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
- * Defaults to the object being iterated itself.
- */
- iterate: function(object, fn, scope) {
- if (Ext.isEmpty(object)) {
- return;
- }
- if (scope === undefined) {
- scope = object;
- }
- if (Ext.isIterable(object)) {
- Ext.Array.each.call(Ext.Array, object, fn, scope);
- }
- else {
- Ext.Object.each.call(Ext.Object, object, fn, scope);
- }
- }
- });
- Ext.apply(Ext, {
- /**
- * This method deprecated. Use {@link Ext#define Ext.define} instead.
- * @method
- * @param {Function} superclass
- * @param {Object} overrides
- * @return {Function} The subclass constructor from the `overrides` parameter, or a generated one if not provided.
- * @deprecated 4.0.0 Please use {@link Ext#define Ext.define} instead
- */
- extend: function() {
- // inline overrides
- var objectConstructor = objectPrototype.constructor,
- inlineOverrides = function(o) {
- for (var m in o) {
- if (!o.hasOwnProperty(m)) {
- continue;
- }
- this[m] = o[m];
- }
- };
- return function(subclass, superclass, overrides) {
- // First we check if the user passed in just the superClass with overrides
- if (Ext.isObject(superclass)) {
- overrides = superclass;
- superclass = subclass;
- subclass = overrides.constructor !== objectConstructor ? overrides.constructor : function() {
- superclass.apply(this, arguments);
- };
- }
- //<debug>
- if (!superclass) {
- Ext.Error.raise({
- sourceClass: 'Ext',
- sourceMethod: 'extend',
- msg: 'Attempting to extend from a class which has not been loaded on the page.'
- });
- }
- //</debug>
- // We create a new temporary class
- var F = function() {},
- subclassProto, superclassProto = superclass.prototype;
- F.prototype = superclassProto;
- subclassProto = subclass.prototype = new F();
- subclassProto.constructor = subclass;
- subclass.superclass = superclassProto;
- if (superclassProto.constructor === objectConstructor) {
- superclassProto.constructor = superclass;
- }
- subclass.override = function(overrides) {
- Ext.override(subclass, overrides);
- };
- subclassProto.override = inlineOverrides;
- subclassProto.proto = subclassProto;
- subclass.override(overrides);
- subclass.extend = function(o) {
- return Ext.extend(subclass, o);
- };
- return subclass;
- };
- }(),
- /**
- * Proxy to {@link Ext.Base#override}. Please refer {@link Ext.Base#override} for further details.
- *
- * @param {Object} cls The class to override
- * @param {Object} overrides The properties to add to `origClass`. This should be specified as an object literal
- * containing one or more properties.
- * @method override
- * @deprecated 4.1.0 Please use {@link Ext#define Ext.define} instead.
- */
- override: function(cls, overrides) {
- if (cls.$isClass) {
- return cls.override(overrides);
- }
- else {
- Ext.apply(cls.prototype, overrides);
- }
- }
- });
- // A full set of static methods to do type checking
- Ext.apply(Ext, {
- /**
- * Returns the given value itself if it's not empty, as described in {@link Ext#isEmpty}; returns the default
- * value (second argument) otherwise.
- *
- * @param {Object} value The value to test.
- * @param {Object} defaultValue The value to return if the original value is empty.
- * @param {Boolean} [allowBlank=false] (optional) `true` to allow zero length strings to qualify as non-empty.
- * @return {Object} `value`, if non-empty, else `defaultValue`.
- */
- valueFrom: function(value, defaultValue, allowBlank){
- return Ext.isEmpty(value, allowBlank) ? defaultValue : value;
- },
- /**
- * Returns the type of the given variable in string format. List of possible values are:
- *
- * - `undefined`: If the given value is `undefined`
- * - `null`: If the given value is `null`
- * - `string`: If the given value is a string
- * - `number`: If the given value is a number
- * - `boolean`: If the given value is a boolean value
- * - `date`: If the given value is a `Date` object
- * - `function`: If the given value is a function reference
- * - `object`: If the given value is an object
- * - `array`: If the given value is an array
- * - `regexp`: If the given value is a regular expression
- * - `element`: If the given value is a DOM Element
- * - `textnode`: If the given value is a DOM text node and contains something other than whitespace
- * - `whitespace`: If the given value is a DOM text node and contains only whitespace
- *
- * @param {Object} value
- * @return {String}
- */
- typeOf: function(value) {
- if (value === null) {
- return 'null';
- }
- var type = typeof value;
- if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
- return type;
- }
- var typeToString = toString.call(value);
- switch(typeToString) {
- case '[object Array]':
- return 'array';
- case '[object Date]':
- return 'date';
- case '[object Boolean]':
- return 'boolean';
- case '[object Number]':
- return 'number';
- case '[object RegExp]':
- return 'regexp';
- }
- if (type === 'function') {
- return 'function';
- }
- if (type === 'object') {
- if (value.nodeType !== undefined) {
- if (value.nodeType === 3) {
- return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
- }
- else {
- return 'element';
- }
- }
- return 'object';
- }
- //<debug error>
- Ext.Error.raise({
- sourceClass: 'Ext',
- sourceMethod: 'typeOf',
- msg: 'Failed to determine the type of the specified value "' + value + '". This is most likely a bug.'
- });
- //</debug>
- },
- /**
- * Returns `true` if the passed value is empty, `false` otherwise. The value is deemed to be empty if it is either:
- *
- * - `null`
- * - `undefined`
- * - a zero-length array.
- * - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`).
- *
- * @param {Object} value The value to test.
- * @param {Boolean} [allowEmptyString=false] (optional) `true` to allow empty strings.
- * @return {Boolean}
- */
- isEmpty: function(value, allowEmptyString) {
- return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
- },
- /**
- * Returns `true` if the passed value is a JavaScript Array, `false` otherwise.
- *
- * @param {Object} target The target to test.
- * @return {Boolean}
- * @method
- */
- isArray: ('isArray' in Array) ? Array.isArray : function(value) {
- return toString.call(value) === '[object Array]';
- },
- /**
- * Returns `true` if the passed value is a JavaScript Date object, `false` otherwise.
- * @param {Object} object The object to test.
- * @return {Boolean}
- */
- isDate: function(value) {
- return toString.call(value) === '[object Date]';
- },
- /**
- * Returns `true` if the passed value is a JavaScript Object, `false` otherwise.
- * @param {Object} value The value to test.
- * @return {Boolean}
- * @method
- */
- isObject: (toString.call(null) === '[object Object]') ?
- function(value) {
- // check ownerDocument here as well to exclude DOM nodes
- return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.ownerDocument === undefined;
- } :
- function(value) {
- return toString.call(value) === '[object Object]';
- },
- /**
- * @private
- */
- isSimpleObject: function(value) {
- return value instanceof Object && value.constructor === Object;
- },
- /**
- * Returns `true` if the passed value is a JavaScript 'primitive', a string, number or Boolean.
- * @param {Object} value The value to test.
- * @return {Boolean}
- */
- isPrimitive: function(value) {
- var type = typeof value;
- return type === 'string' || type === 'number' || type === 'boolean';
- },
- /**
- * Returns `true` if the passed value is a JavaScript Function, `false` otherwise.
- * @param {Object} value The value to test.
- * @return {Boolean}
- * @method
- */
- isFunction:
- // Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
- // Object.prorotype.toString (slower)
- (typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
- return toString.call(value) === '[object Function]';
- } : function(value) {
- return typeof value === 'function';
- },
- /**
- * Returns `true` if the passed value is a number. Returns `false` for non-finite numbers.
- * @param {Object} value The value to test.
- * @return {Boolean}
- */
- isNumber: function(value) {
- return typeof value === 'number' && isFinite(value);
- },
- /**
- * Validates that a value is numeric.
- * @param {Object} value Examples: 1, '1', '2.34'
- * @return {Boolean} `true` if numeric, `false` otherwise.
- */
- isNumeric: function(value) {
- return !isNaN(parseFloat(value)) && isFinite(value);
- },
- /**
- * Returns `true` if the passed value is a string.
- * @param {Object} value The value to test.
- * @return {Boolean}
- */
- isString: function(value) {
- return typeof value === 'string';
- },
- /**
- * Returns `true` if the passed value is a Boolean.
- *
- * @param {Object} value The value to test.
- * @return {Boolean}
- */
- isBoolean: function(value) {
- return typeof value === 'boolean';
- },
- /**
- * Returns `true` if the passed value is an HTMLElement.
- * @param {Object} value The value to test.
- * @return {Boolean}
- */
- isElement: function(value) {
- return value ? value.nodeType === 1 : false;
- },
- /**
- * Returns `true` if the passed value is a TextNode.
- * @param {Object} value The value to test.
- * @return {Boolean}
- */
- isTextNode: function(value) {
- return value ? value.nodeName === "#text" : false;
- },
- /**
- * Returns `true` if the passed value is defined.
- * @param {Object} value The value to test.
- * @return {Boolean}
- */
- isDefined: function(value) {
- return typeof value !== 'undefined';
- },
- /**
- * Returns `true` if the passed value is iterable, `false` otherwise.
- * @param {Object} value The value to test.
- * @return {Boolean}
- */
- isIterable: function(value) {
- return (value && typeof value !== 'string') ? value.length !== undefined : false;
- }
- });
- Ext.apply(Ext, {
- /**
- * Clone almost any type of variable including array, object, DOM nodes and Date without keeping the old reference.
- * @param {Object} item The variable to clone.
- * @return {Object} clone
- */
- clone: function(item) {
- if (item === null || item === undefined) {
- return item;
- }
- // DOM nodes
- if (item.nodeType && item.cloneNode) {
- return item.cloneNode(true);
- }
- // Strings
- var type = toString.call(item);
- // Dates
- if (type === '[object Date]') {
- return new Date(item.getTime());
- }
- var i, j, k, clone, key;
- // Arrays
- if (type === '[object Array]') {
- i = item.length;
- clone = [];
- while (i--) {
- clone[i] = Ext.clone(item[i]);
- }
- }
- // Objects
- else if (type === '[object Object]' && item.constructor === Object) {
- clone = {};
- for (key in item) {
- clone[key] = Ext.clone(item[key]);
- }
- if (enumerables) {
- for (j = enumerables.length; j--;) {
- k = enumerables[j];
- clone[k] = item[k];
- }
- }
- }
- return clone || item;
- },
- /**
- * @private
- * Generate a unique reference of Ext in the global scope, useful for sandboxing.
- */
- getUniqueGlobalNamespace: function() {
- var uniqueGlobalNamespace = this.uniqueGlobalNamespace;
- if (uniqueGlobalNamespace === undefined) {
- var i = 0;
- do {
- uniqueGlobalNamespace = 'ExtBox' + (++i);
- } while (Ext.global[uniqueGlobalNamespace] !== undefined);
- Ext.global[uniqueGlobalNamespace] = Ext;
- this.uniqueGlobalNamespace = uniqueGlobalNamespace;
- }
- return uniqueGlobalNamespace;
- },
- /**
- * @private
- */
- functionFactory: function() {
- var args = Array.prototype.slice.call(arguments),
- ln = args.length;
- if (ln > 0) {
- args[ln - 1] = 'var Ext=window.' + this.getUniqueGlobalNamespace() + ';' + args[ln - 1];
- }
- return Function.prototype.constructor.apply(Function.prototype, args);
- },
- /**
- * @private
- */
- globalEval: ('execScript' in global) ? function(code) {
- global.execScript(code)
- } : function(code) {
- (function(){
- eval(code);
- })();
- }
- //<feature logger>
- /**
- * @private
- * @property
- */
- ,Logger: {
- log: function(message, priority) {
- if ('console' in global) {
- if (!priority || !(priority in global.console)) {
- priority = 'log';
- }
- message = '[' + priority.toUpperCase() + '] ' + message;
- global.console[priority](message);
- }
- },
- verbose: function(message) {
- this.log(message, 'verbose');
- },
- info: function(message) {
- this.log(message, 'info');
- },
- warn: function(message) {
- this.log(message, 'warn');
- },
- error: function(message) {
- throw new Error(message);
- },
- deprecate: function(message) {
- this.log(message, 'warn');
- }
- }
- //</feature>
- });
- /**
- * Old alias to {@link Ext#typeOf}.
- * @deprecated 4.0.0 Please use {@link Ext#typeOf} instead.
- * @method
- * @alias Ext#typeOf
- */
- Ext.type = Ext.typeOf;
- })();
- //@tag foundation,core
- //@define Ext.Version
- //@require Ext
- /**
- * @author Jacky Nguyen <jacky@sencha.com>
- * @docauthor Jacky Nguyen <jacky@sencha.com>
- * @class Ext.Version
- *
- * A utility class that wrap around a string version number and provide convenient
- * method to perform comparison. See also: {@link Ext.Version#compare compare}. Example:
- *
- * var version = new Ext.Version('1.0.2beta');
- * console.log("Version is " + version); // Version is 1.0.2beta
- *
- * console.log(version.getMajor()); // 1
- * console.log(version.getMinor()); // 0
- * console.log(version.getPatch()); // 2
- * console.log(version.getBuild()); // 0
- * console.log(version.getRelease()); // beta
- *
- * console.log(version.isGreaterThan('1.0.1')); // true
- * console.log(version.isGreaterThan('1.0.2alpha')); // true
- * console.log(version.isGreaterThan('1.0.2RC')); // false
- * console.log(version.isGreaterThan('1.0.2')); // false
- * console.log(version.isLessThan('1.0.2')); // true
- *
- * console.log(version.match(1.0)); // true
- * console.log(version.match('1.0.2')); // true
- */
- (function() {
- // Current core version
- var version = '4.1.0', Version;
- Ext.Version = Version = Ext.extend(Object, {
- /**
- * Creates new Version object.
- * @param {String/Number} version The version number in the follow standard format: major[.minor[.patch[.build[release]]]]
- * Examples: 1.0 or 1.2.3beta or 1.2.3.4RC
- * @return {Ext.Version} this
- */
- constructor: function(version) {
- var toNumber = this.toNumber,
- parts, releaseStartIndex;
- if (version instanceof Version) {
- return version;
- }
- this.version = this.shortVersion = String(version).toLowerCase().replace(/_/g, '.').replace(/[\-+]/g, '');
- releaseStartIndex = this.version.search(/([^\d\.])/);
- if (releaseStartIndex !== -1) {
- this.release = this.version.substr(releaseStartIndex, version.length);
- this.shortVersion = this.version.substr(0, releaseStartIndex);
- }
- this.shortVersion = this.shortVersion.replace(/[^\d]/g, '');
- parts = this.version.split('.');
- this.major = toNumber(parts.shift());
- this.minor = toNumber(parts.shift());
- this.patch = toNumber(parts.shift());
- this.build = toNumber(parts.shift());
- return this;
- },
- /**
- * @param value
- * @return {Number}
- */
- toNumber: function(value) {
- value = parseInt(value || 0, 10);
- if (isNaN(value)) {
- value = 0;
- }
- return value;
- },
- /**
- * Override the native `toString()` method.
- * @private
- * @return {String} version
- */
- toString: function() {
- return this.version;
- },
- /**
- * Override the native `valueOf()` method.
- * @private
- * @return {String} version
- */
- valueOf: function() {
- return this.version;
- },
- /**
- * Returns the major component value.
- * @return {Number} major
- */
- getMajor: function() {
- return this.major || 0;
- },
- /**
- * Returns the minor component value.
- * @return {Number} minor
- */
- getMinor: function() {
- return this.minor || 0;
- },
- /**
- * Returns the patch component value.
- * @return {Number} patch
- */
- getPatch: function() {
- return this.patch || 0;
- },
- /**
- * Returns the build component value.
- * @return {Number} build
- */
- getBuild: function() {
- return this.build || 0;
- },
- /**
- * Returns the release component value.
- * @return {Number} release
- */
- getRelease: function() {
- return this.release || '';
- },
- /**
- * Returns whether this version if greater than the supplied argument.
- * @param {String/Number} target The version to compare with.
- * @return {Boolean} `true` if this version if greater than the target, `false` otherwise.
- */
- isGreaterThan: function(target) {
- return Version.compare(this.version, target) === 1;
- },
- /**
- * Returns whether this version if greater than or equal to the supplied argument.
- * @param {String/Number} target The version to compare with.
- * @return {Boolean} `true` if this version if greater than or equal to the target, `false` otherwise.
- */
- isGreaterThanOrEqual: function(target) {
- return Version.compare(this.version, target) >= 0;
- },
- /**
- * Returns whether this version if smaller than the supplied argument.
- * @param {String/Number} target The version to compare with.
- * @return {Boolean} `true` if this version if smaller than the target, `false` otherwise.
- */
- isLessThan: function(target) {
- return Version.compare(this.version, target) === -1;
- },
- /**
- * Returns whether this version if less than or equal to the supplied argument.
- * @param {String/Number} target The version to compare with.
- * @return {Boolean} `true` if this version if less than or equal to the target, `false` otherwise.
- */
- isLessThanOrEqual: function(target) {
- return Version.compare(this.version, target) <= 0;
- },
- /**
- * Returns whether this version equals to the supplied argument.
- * @param {String/Number} target The version to compare with.
- * @return {Boolean} `true` if this version equals to the target, `false` otherwise.
- */
- equals: function(target) {
- return Version.compare(this.version, target) === 0;
- },
- /**
- * Returns whether this version matches the supplied argument. Example:
- *
- * var version = new Ext.Version('1.0.2beta');
- * console.log(version.match(1)); // true
- * console.log(version.match(1.0)); // true
- * console.log(version.match('1.0.2')); // true
- * console.log(version.match('1.0.2RC')); // false
- *
- * @param {String/Number} target The version to compare with.
- * @return {Boolean} `true` if this version matches the target, `false` otherwise.
- */
- match: function(target) {
- target = String(target);
- return this.version.substr(0, target.length) === target;
- },
- /**
- * Returns this format: [major, minor, patch, build, release]. Useful for comparison.
- * @return {Number[]}
- */
- toArray: function() {
- return [this.getMajor(), this.getMinor(), this.getPatch(), this.getBuild(), this.getRelease()];
- },
- /**
- * Returns shortVersion version without dots and release.
- * @return {String}
- */
- getShortVersion: function() {
- return this.shortVersion;
- },
- /**
- * Convenient alias to {@link Ext.Version#isGreaterThan isGreaterThan}
- * @param {String/Number} target
- * @return {Boolean}
- */
- gt: function() {
- return this.isGreaterThan.apply(this, arguments);
- },
- /**
- * Convenient alias to {@link Ext.Version#isLessThan isLessThan}
- * @param {String/Number} target
- * @return {Boolean}
- */
- lt: function() {
- return this.isLessThan.apply(this, arguments);
- },
- /**
- * Convenient alias to {@link Ext.Version#isGreaterThanOrEqual isGreaterThanOrEqual}
- * @param {String/Number} target
- * @return {Boolean}
- */
- gtEq: function() {
- return this.isGreaterThanOrEqual.apply(this, arguments);
- },
- /**
- * Convenient alias to {@link Ext.Version#isLessThanOrEqual isLessThanOrEqual}
- * @param {String/Number} target
- * @return {Boolean}
- */
- ltEq: function() {
- return this.isLessThanOrEqual.apply(this, arguments);
- }
- });
- Ext.apply(Version, {
- // @private
- releaseValueMap: {
- 'dev': -6,
- 'alpha': -5,
- 'a': -5,
- 'beta': -4,
- 'b': -4,
- 'rc': -3,
- '#': -2,
- 'p': -1,
- 'pl': -1
- },
- /**
- * Converts a version component to a comparable value.
- *
- * @static
- * @param {Object} value The value to convert
- * @return {Object}
- */
- getComponentValue: function(value) {
- return !value ? 0 : (isNaN(value) ? this.releaseValueMap[value] || value : parseInt(value, 10));
- },
- /**
- * Compare 2 specified versions, starting from left to right. If a part contains special version strings,
- * they are handled in the following order:
- * 'dev' < 'alpha' = 'a' < 'beta' = 'b' < 'RC' = 'rc' < '#' < 'pl' = 'p' < 'anything else'
- *
- * @static
- * @param {String} current The current version to compare to.
- * @param {String} target The target version to compare to.
- * @return {Number} Returns -1 if the current version is smaller than the target version, 1 if greater, and 0 if they're equivalent.
- */
- compare: function(current, target) {
- var currentValue, targetValue, i;
- current = new Version(current).toArray();
- target = new Version(target).toArray();
- for (i = 0; i < Math.max(current.length, target.length); i++) {
- currentValue = this.getComponentValue(current[i]);
- targetValue = this.getComponentValue(target[i]);
- if (currentValue < targetValue) {
- return -1;
- } else if (currentValue > targetValue) {
- return 1;
- }
- }
- return 0;
- }
- });
- Ext.apply(Ext, {
- /**
- * @private
- */
- versions: {},
- /**
- * @private
- */
- lastRegisteredVersion: null,
- /**
- * Set version number for the given package name.
- *
- * @param {String} packageName The package name, for example: 'core', 'touch', 'extjs'.
- * @param {String/Ext.Version} version The version, for example: '1.2.3alpha', '2.4.0-dev'.
- * @return {Ext}
- */
- setVersion: function(packageName, version) {
- Ext.versions[packageName] = new Version(version);
- Ext.lastRegisteredVersion = Ext.versions[packageName];
- return this;
- },
- /**
- * Get the version number of the supplied package name; will return the last registered version
- * (last `Ext.setVersion()` call) if there's no package name given.
- *
- * @param {String} packageName (Optional) The package name, for example: 'core', 'touch', 'extjs'.
- * @return {Ext.Version} The version.
- */
- getVersion: function(packageName) {
- if (packageName === undefined) {
- return Ext.lastRegisteredVersion;
- }
- return Ext.versions[packageName];
- },
- /**
- * Create a closure for deprecated code.
- *
- * // This means Ext.oldMethod is only supported in 4.0.0beta and older.
- * // If Ext.getVersion('extjs') returns a version that is later than '4.0.0beta', for example '4.0.0RC',
- * // the closure will not be invoked
- * Ext.deprecate('extjs', '4.0.0beta', function() {
- * Ext.oldMethod = Ext.newMethod;
- * // ...
- * });
- *
- * @param {String} packageName The package name.
- * @param {String} since The last version before it's deprecated.
- * @param {Function} closure The callback function to be executed with the specified version is less than the current version.
- * @param {Object} scope The execution scope (`this`) if the closure
- */
- deprecate: function(packageName, since, closure, scope) {
- if (Version.compare(Ext.getVersion(packageName), since) < 1) {
- closure.call(scope);
- }
- }
- }); // End Versioning
- Ext.setVersion('core', version);
- })();
- //@tag foundation,core
- //@define Ext.String
- //@require Ext.Version
- /**
- * @class Ext.String
- *
- * A collection of useful static methods to deal with strings.
- * @singleton
- */
- Ext.String = {
- 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,
- escapeRe: /('|\\)/g,
- formatRe: /\{(\d+)\}/g,
- escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,
- /**
- * Convert certain characters (&, <, >, and ") to their HTML character equivalents for literal display in web pages.
- * @param {String} value The string to encode.
- * @return {String} The encoded text.
- * @method
- */
- htmlEncode: (function() {
- var entities = {
- '&': '&',
- '>': '>',
- '<': '<',
- '"': '"'
- }, keys = [], p, regex;
- for (p in entities) {
- keys.push(p);
- }
- regex = new RegExp('(' + keys.join('|') + ')', 'g');
- return function(value) {
- return (!value) ? value : String(value).replace(regex, function(match, capture) {
- return entities[capture];
- });
- };
- })(),
- /**
- * Convert certain characters (&, <, >, and ") from their HTML character equivalents.
- * @param {String} value The string to decode.
- * @return {String} The decoded text.
- * @method
- */
- htmlDecode: (function() {
- var entities = {
- '&': '&',
- '>': '>',
- '<': '<',
- '"': '"'
- }, keys = [], p, regex;
- for (p in entities) {
- keys.push(p);
- }
- regex = new RegExp('(' + keys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
- return function(value) {
- return (!value) ? value : String(value).replace(regex, function(match, capture) {
- if (capture in entities) {
- return entities[capture];
- } else {
- return String.fromCharCode(parseInt(capture.substr(2), 10));
- }
- });
- };
- })(),
- /**
- * Appends content to the query string of a URL, handling logic for whether to place
- * a question mark or ampersand.
- * @param {String} url The URL to append to.
- * @param {String} string The content to append to the URL.
- * @return {String} The resulting URL.
- */
- urlAppend : function(url, string) {
- if (!Ext.isEmpty(string)) {
- return url + (url.indexOf('?') === -1 ? '?' : '&') + string;
- }
- return url;
- },
- /**
- * Trims whitespace from either end of a string, leaving spaces within the string intact. Example:
- *
- * @example
- * var s = ' foo bar ';
- * alert('-' + s + '-'); // alerts "- foo bar -"
- * alert('-' + Ext.String.trim(s) + '-'); // alerts "-foo bar-"
- *
- * @param {String} string The string to escape
- * @return {String} The trimmed string
- */
- trim: function(string) {
- return string.replace(Ext.String.trimRegex, "");
- },
- /**
- * Capitalize the given string.
- * @param {String} string
- * @return {String}
- */
- capitalize: function(string) {
- return string.charAt(0).toUpperCase() + string.substr(1);
- },
- /**
- * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length.
- * @param {String} value The string to truncate.
- * @param {Number} length The maximum length to allow before truncating.
- * @param {Boolean} word `true` to try to find a common word break.
- * @return {String} The converted text.
- */
- ellipsis: function(value, len, word) {
- if (value && value.length > len) {
- if (word) {
- var vs = value.substr(0, len - 2),
- index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
- if (index !== -1 && index >= (len - 15)) {
- return vs.substr(0, index) + "...";
- }
- }
- return value.substr(0, len - 3) + "...";
- }
- return value;
- },
- /**
- * Escapes the passed string for use in a regular expression.
- * @param {String} string
- * @return {String}
- */
- escapeRegex: function(string) {
- return string.replace(Ext.String.escapeRegexRe, "\\$1");
- },
- /**
- * Escapes the passed string for ' and \.
- * @param {String} string The string to escape.
- * @return {String} The escaped string.
- */
- escape: function(string) {
- return string.replace(Ext.String.escapeRe, "\\$1");
- },
- /**
- * Utility function that allows you to easily switch a string between two alternating values. The passed value
- * is compared to the current string, and if they are equal, the other value that was passed in is returned. If
- * they are already different, the first value passed in is returned. Note that this method returns the new value
- * but does not change the current string.
- *
- * // alternate sort directions
- * sort = Ext.String.toggle(sort, 'ASC', 'DESC');
- *
- * // instead of conditional logic:
- * sort = (sort == 'ASC' ? 'DESC' : 'ASC');
- *
- * @param {String} string The current string.
- * @param {String} value The value to compare to the current string.
- * @param {String} other The new value to use if the string already equals the first value passed in.
- * @return {String} The new value.
- */
- toggle: function(string, value, other) {
- return string === value ? other : value;
- },
- /**
- * Pads the left side of a string with a specified character. This is especially useful
- * for normalizing number and date strings. Example usage:
- *
- * var s = Ext.String.leftPad('123', 5, '0');
- * alert(s); // '00123'
- *
- * @param {String} string The original string.
- * @param {Number} size The total length of the output string.
- * @param {String} [character= ] (optional) The character with which to pad the original string (defaults to empty string " ").
- * @return {String} The padded string.
- */
- leftPad: function(string, size, character) {
- var result = String(string);
- character = character || " ";
- while (result.length < size) {
- result = character + result;
- }
- return result;
- },
- /**
- * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens. Each
- * token must be unique, and must increment in the format {0}, {1}, etc. Example usage:
- *
- * var cls = 'my-class',
- * text = 'Some text';
- * var s = Ext.String.format('<div class="{0}">{1}</div>', cls, text);
- * alert(s); // '<div class="my-class">Some text</div>'
- *
- * @param {String} string The tokenized string to be formatted.
- * @param {String} value1 The value to replace token {0}.
- * @param {String} value2 Etc...
- * @return {String} The formatted string.
- */
- format: function(format) {
- var args = Ext.Array.toArray(arguments, 1);
- return format.replace(Ext.String.formatRe, function(m, i) {
- return args[i];
- });
- },
- /**
- * Returns a string with a specified number of repetitions a given string pattern.
- * The pattern be separated by a different string.
- *
- * var s = Ext.String.repeat('---', 4); // '------------'
- * var t = Ext.String.repeat('--', 3, '/'); // '--/--/--'
- *
- * @param {String} pattern The pattern to repeat.
- * @param {Number} count The number of times to repeat the pattern (may be 0).
- * @param {String} sep An option string to separate each pattern.
- */
- repeat: function(pattern, count, sep) {
- for (var buf = [], i = count; i--; ) {
- buf.push(pattern);
- }
- return buf.join(sep || '');
- }
- };
- /**
- * Old alias to {@link Ext.String#htmlEncode}.
- * @deprecated Use {@link Ext.String#htmlEncode} instead.
- * @method
- * @member Ext
- * @alias Ext.String#htmlEncode
- */
- Ext.htmlEncode = Ext.String.htmlEncode;
- /**
- * Old alias to {@link Ext.String#htmlDecode}.
- * @deprecated Use {@link Ext.String#htmlDecode} instead.
- * @method
- * @member Ext
- * @alias Ext.String#htmlDecode
- */
- Ext.htmlDecode = Ext.String.htmlDecode;
- /**
- * Old alias to {@link Ext.String#urlAppend}.
- * @deprecated Use {@link Ext.String#urlAppend} instead.
- * @method
- * @member Ext
- * @alias Ext.String#urlAppend
- */
- Ext.urlAppend = Ext.String.urlAppend;
- //@tag foundation,core
- //@define Ext.Array
- //@require Ext.String
- /**
- * @class Ext.Array
- * @singleton
- * @author Jacky Nguyen <jacky@sencha.com>
- * @docauthor Jacky Nguyen <jacky@sencha.com>
- *
- * A set of useful static methods to deal with arrays; provide missing methods for older browsers.
- */
- (function() {
- var arrayPrototype = Array.prototype,
- slice = arrayPrototype.slice,
- supportsSplice = function () {
- var array = [],
- lengthBefore,
- j = 20;
- if (!array.splice) {
- return false;
- }
- // This detects a bug in IE8 splice method:
- // see http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/6e946d03-e09f-4b22-a4dd-cd5e276bf05a/
- while (j--) {
- array.push("A");
- }
- 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");
- lengthBefore = array.length; //41
- array.splice(13, 0, "XXX"); // add one element
- if (lengthBefore+1 != array.length) {
- return false;
- }
- // end IE8 bug
- return true;
- }(),
- supportsForEach = 'forEach' in arrayPrototype,
- supportsMap = 'map' in arrayPrototype,
- supportsIndexOf = 'indexOf' in arrayPrototype,
- supportsEvery = 'every' in arrayPrototype,
- supportsSome = 'some' in arrayPrototype,
- supportsFilter = 'filter' in arrayPrototype,
- supportsSort = function() {
- var a = [1,2,3,4,5].sort(function(){ return 0; });
- return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
- }(),
- supportsSliceOnNodeList = true,
- ExtArray;
- try {
- // IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList
- if (typeof document !== 'undefined') {
- slice.call(document.getElementsByTagName('body'));
- }
- } catch (e) {
- supportsSliceOnNodeList = false;
- }
- function fixArrayIndex (array, index) {
- return (index < 0) ? Math.max(0, array.length + index)
- : Math.min(array.length, index);
- }
- /*
- Does the same work as splice, but with a slightly more convenient signature. The splice
- method has bugs in IE8, so this is the implementation we use on that platform.
- The rippling of items in the array can be tricky. Consider two use cases:
- index=2
- removeCount=2
- /=====\
- +---+---+---+---+---+---+---+---+
- | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
- +---+---+---+---+---+---+---+---+
- / \/ \/ \/ \
- / /\ /\ /\ \
- / / \/ \/ \ +--------------------------+
- / / /\ /\ +--------------------------+ \
- / / / \/ +--------------------------+ \ \
- / / / /+--------------------------+ \ \ \
- / / / / \ \ \ \
- v v v v v v v v
- +---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+
- | 0 | 1 | 4 | 5 | 6 | 7 | | 0 | 1 | a | b | c | 4 | 5 | 6 | 7 |
- +---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+
- A B \=========/
- insert=[a,b,c]
- In case A, it is obvious that copying of [4,5,6,7] must be left-to-right so
- that we don't end up with [0,1,6,7,6,7]. In case B, we have the opposite; we
- must go right-to-left or else we would end up with [0,1,a,b,c,4,4,4,4].
- */
- function replaceSim (array, index, removeCount, insert) {
- var add = insert ? insert.length : 0,
- length = array.length,
- pos = fixArrayIndex(array, index);
- // we try to use Array.push when we can for efficiency...
- if (pos === length) {
- if (add) {
- array.push.apply(array, insert);
- }
- } else {
- var remove = Math.min(removeCount, length - pos),
- tailOldPos = pos + remove,
- tailNewPos = tailOldPos + add - remove,
- tailCount = length - tailOldPos,
- lengthAfterRemove = length - remove,
- i;
- if (tailNewPos < tailOldPos) { // case A
- for (i = 0; i < tailCount; ++i) {
- array[tailNewPos+i] = array[tailOldPos+i];
- }
- } else if (tailNewPos > tailOldPos) { // case B
- for (i = tailCount; i--; ) {
- array[tailNewPos+i] = array[tailOldPos+i];
- }
- } // else, add == remove (nothing to do)
- if (add && pos === lengthAfterRemove) {
- array.length = lengthAfterRemove; // truncate array
- array.push.apply(array, insert);
- } else {
- array.length = lengthAfterRemove + add; // reserves space
- for (i = 0; i < add; ++i) {
- array[pos+i] = insert[i];
- }
- }
- }
- return array;
- }
- function replaceNative (array, index, removeCount, insert) {
- if (insert && insert.length) {
- if (index < array.length) {
- array.splice.apply(array, [index, removeCount].concat(insert));
- } else {
- array.push.apply(array, insert);
- }
- } else {
- array.splice(index, removeCount);
- }
- return array;
- }
- function eraseSim (array, index, removeCount) {
- return replaceSim(array, index, removeCount);
- }
- function eraseNative (array, index, removeCount) {
- array.splice(index, removeCount);
- return array;
- }
- function spliceSim (array, index, removeCount) {
- var pos = fixArrayIndex(array, index),
- removed = array.slice(index, fixArrayIndex(array, pos+removeCount));
- if (arguments.length < 4) {
- replaceSim(array, pos, removeCount);
- } else {
- replaceSim(array, pos, removeCount, slice.call(arguments, 3));
- }
- return removed;
- }
- function spliceNative (array) {
- return array.splice.apply(array, slice.call(arguments, 1));
- }
- var erase = supportsSplice ? eraseNative : eraseSim,
- replace = supportsSplice ? replaceNative : replaceSim,
- splice = supportsSplice ? spliceNative : spliceSim;
- // NOTE: from here on, use erase, replace or splice (not native methods)...
- ExtArray = Ext.Array = {
- /**
- * Iterates an array or an iterable value and invoke the given callback function for each item.
- *
- * var countries = ['Vietnam', 'Singapore', 'United States', 'Russia'];
- *
- * Ext.Array.each(countries, function(name, index, countriesItSelf) {
- * console.log(name);
- * });
- *
- * var sum = function() {
- * var sum = 0;
- *
- * Ext.Array.each(arguments, function(value) {
- * sum += value;
- * });
- *
- * return sum;
- * };
- *
- * sum(1, 2, 3); // returns 6
- *
- * The iteration can be stopped by returning false in the function callback.
- *
- * Ext.Array.each(countries, function(name, index, countriesItSelf) {
- * if (name === 'Singapore') {
- * return false; // break here
- * }
- * });
- *
- * {@link Ext#each Ext.each} is alias for {@link Ext.Array#each Ext.Array.each}
- *
- * @param {Array/NodeList/Object} iterable The value to be iterated. If this
- * argument is not iterable, the callback function is called once.
- * @param {Function} fn The callback function. If it returns `false`, the iteration stops and this method returns
- * the current `index`.
- * @param {Object} fn.item The item at the current `index` in the passed `array`
- * @param {Number} fn.index The current `index` within the `array`
- * @param {Array} fn.allItems The `array` itself which was passed as the first argument
- * @param {Boolean} fn.return Return false to stop iteration.
- * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
- * @param {Boolean} [reverse=false] (Optional) Reverse the iteration order (loop from the end to the beginning).
- * @return {Boolean} See description for the `fn` parameter.
- */
- each: function(array, fn, scope, reverse) {
- array = ExtArray.from(array);
- var i,
- ln = array.length;
- if (reverse !== true) {
- for (i = 0; i < ln; i++) {
- if (fn.call(scope || array[i], array[i], i, array) === false) {
- return i;
- }
- }
- }
- else {
- for (i = ln - 1; i > -1; i--) {
- if (fn.call(scope || array[i], array[i], i, array) === false) {
- return i;
- }
- }
- }
- return true;
- },
- /**
- * Iterates an array and invoke the given callback function for each item. Note that this will simply
- * delegate to the native `Array.prototype.forEach` method if supported. It doesn't support stopping the
- * iteration by returning `false` in the callback function like {@link Ext.Array#each}. However, performance
- * could be much better in modern browsers comparing with {@link Ext.Array#each}
- *
- * @param {Array} array The array to iterate.
- * @param {Function} fn The callback function.
- * @param {Object} fn.item The item at the current `index` in the passed `array`.
- * @param {Number} fn.index The current `index` within the `array`.
- * @param {Array} fn.allItems The `array` itself which was passed as the first argument.
- * @param {Object} scope (Optional) The execution scope (`this`) in which the specified function is executed.
- */
- forEach: supportsForEach ? function(array, fn, scope) {
- return array.forEach(fn, scope);
- } : function(array, fn, scope) {
- var i = 0,
- ln = array.length;
- for (; i < ln; i++) {
- fn.call(scope, array[i], i, array);
- }
- },
- /**
- * Get the index of the provided `item` in the given `array`, a supplement for the
- * missing arrayPrototype.indexOf in Internet Explorer.
- *
- * @param {Array} array The array to check.
- * @param {Object} item The item to look for.
- * @param {Number} from (Optional) The index at which to begin the search.
- * @return {Number} The index of item in the array (or -1 if it is not found).
- */
- indexOf: (supportsIndexOf) ? function(array, item, from) {
- return array.indexOf(item, from);
- } : function(array, item, from) {
- var i, length = array.length;
- for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
- if (array[i] === item) {
- return i;
- }
- }
- return -1;
- },
- /**
- * Checks whether or not the given `array` contains the specified `item`.
- *
- * @param {Array} array The array to check.
- * @param {Object} item The item to look for.
- * @return {Boolean} `true` if the array contains the item, `false` otherwise.
- */
- contains: supportsIndexOf ? function(array, item) {
- return array.indexOf(item) !== -1;
- } : function(array, item) {
- var i, ln;
- for (i = 0, ln = array.length; i < ln; i++) {
- if (array[i] === item) {
- return true;
- }
- }
- return false;
- },
- /**
- * Converts any iterable (numeric indices and a length property) into a true array.
- *
- * function test() {
- * var args = Ext.Array.toArray(arguments),
- * fromSecondToLastArgs = Ext.Array.toArray(arguments, 1);
- *
- * alert(args.join(' '));
- * alert(fromSecondToLastArgs.join(' '));
- * }
- *
- * test('just', 'testing', 'here'); // alerts 'just testing here';
- * // alerts 'testing here';
- *
- * Ext.Array.toArray(document.getElementsByTagName('div')); // will convert the NodeList into an array
- * Ext.Array.toArray('splitted'); // returns ['s', 'p', 'l', 'i', 't', 't', 'e', 'd']
- * Ext.Array.toArray('splitted', 0, 3); // returns ['s', 'p', 'l', 'i']
- *
- * {@link Ext#toArray Ext.toArray} is alias for {@link Ext.Array#toArray Ext.Array.toArray}
- *
- * @param {Object} iterable the iterable object to be turned into a true Array.
- * @param {Number} [start=0] (Optional) a zero-based index that specifies the start of extraction.
- * @param {Number} [end=-1] (Optional) a zero-based index that specifies the end of extraction.
- * @return {Array}
- */
- toArray: function(iterable, start, end){
- if (!iterable || !iterable.length) {
- return [];
- }
- if (typeof iterable === 'string') {
- iterable = iterable.split('');
- }
- if (supportsSliceOnNodeList) {
- return slice.call(iterable, start || 0, end || iterable.length);
- }
- var array = [],
- i;
- start = start || 0;
- end = end ? ((end < 0) ? iterable.length + end : end) : iterable.length;
- for (i = start; i < end; i++) {
- array.push(iterable[i]);
- }
- return array;
- },
- /**
- * Plucks the value of a property from each item in the Array. Example:
- *
- * Ext.Array.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className]
- *
- * @param {Array/NodeList} array The Array of items to pluck the value from.
- * @param {String} propertyName The property name to pluck from each element.
- * @return {Array} The value from each item in the Array.
- */
- pluck: function(array, propertyName) {
- var ret = [],
- i, ln, item;
- for (i = 0, ln = array.length; i < ln; i++) {
- item = array[i];
- ret.push(item[propertyName]);
- }
- return ret;
- },
- /**
- * Creates a new array with the results of calling a provided function on every element in this array.
- *
- * @param {Array} array
- * @param {Function} fn Callback function for each item.
- * @param {Object} scope Callback function scope.
- * @return {Array} results
- */
- map: supportsMap ? function(array, fn, scope) {
- return array.map(fn, scope);
- } : function(array, fn, scope) {
- var results = [],
- i = 0,
- len = array.length;
- for (; i < len; i++) {
- results[i] = fn.call(scope, array[i], i, array);
- }
- return results;
- },
- /**
- * Executes the specified function for each array element until the function returns a falsy value.
- * If such an item is found, the function will return `false` immediately.
- * Otherwise, it will return `true`.
- *
- * @param {Array} array
- * @param {Function} fn Callback function for each item.
- * @param {Object} scope Callback function scope.
- * @return {Boolean} `true` if no `false` value is returned by the callback function.
- */
- every: function(array, fn, scope) {
- //<debug>
- if (!fn) {
- Ext.Error.raise('Ext.Array.every must have a callback function passed as second argument.');
- }
- //</debug>
- if (supportsEvery) {
- return array.every(fn, scope);
- }
- var i = 0,
- ln = array.length;
- for (; i < ln; ++i) {
- if (!fn.call(scope, array[i], i, array)) {
- return false;
- }
- }
- return true;
- },
- /**
- * Executes the specified function for each array element until the function returns a truthy value.
- * If such an item is found, the function will return `true` immediately. Otherwise, it will return `false`.
- *
- * @param {Array} array
- * @param {Function} fn Callback function for each item.
- * @param {Object} scope Callback function scope.
- * @return {Boolean} `true` if the callback function returns a truthy value.
- */
- some: function(array, fn, scope) {
- //<debug>
- if (!fn) {
- Ext.Error.raise('Ext.Array.some must have a callback function passed as second argument.');
- }
- //</debug>
- if (supportsSome) {
- return array.some(fn, scope);
- }
- var i = 0,
- ln = array.length;
- for (; i < ln; ++i) {
- if (fn.call(scope, array[i], i, array)) {
- return true;
- }
- }
- return false;
- },
- /**
- * Filter through an array and remove empty item as defined in {@link Ext#isEmpty Ext.isEmpty}.
- *
- * See {@link Ext.Array#filter}
- *
- * @param {Array} array
- * @return {Array} results
- */
- clean: function(array) {
- var results = [],
- i = 0,
- ln = array.length,
- item;
- for (; i < ln; i++) {
- item = array[i];
- if (!Ext.isEmpty(item)) {
- results.push(item);
- }
- }
- return results;
- },
- /**
- * Returns a new array with unique items.
- *
- * @param {Array} array
- * @return {Array} results
- */
- unique: function(array) {
- var clone = [],
- i = 0,
- ln = array.length,
- item;
- for (; i < ln; i++) {
- item = array[i];
- if (ExtArray.indexOf(clone, item) === -1) {
- clone.push(item);
- }
- }
- return clone;
- },
- /**
- * Creates a new array with all of the elements of this array for which
- * the provided filtering function returns `true`.
- *
- * @param {Array} array
- * @param {Function} fn Callback function for each item.
- * @param {Object} scope Callback function scope.
- * @return {Array} results
- */
- filter: function(array, fn, scope) {
- if (supportsFilter) {
- return array.filter(fn, scope);
- }
- var results = [],
- i = 0,
- ln = array.length;
- for (; i < ln; i++) {
- if (fn.call(scope, array[i], i, array)) {
- results.push(array[i]);
- }
- }
- return results;
- },
- /**
- * Converts a value to an array if it's not already an array; returns:
- *
- * - An empty array if given value is `undefined` or `null`
- * - Itself if given value is already an array
- * - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike)
- * - An array with one item which is the given value, otherwise
- *
- * @param {Object} value The value to convert to an array if it's not already is an array.
- * @param {Boolean} [newReference=false] (Optional) `true` to clone the given array and return a new reference if necessary.
- * @return {Array} array
- */
- from: function(value, newReference) {
- if (value === undefined || value === null) {
- return [];
- }
- if (Ext.isArray(value)) {
- return (newReference) ? slice.call(value) : value;
- }
- if (value && value.length !== undefined && typeof value !== 'string') {
- return ExtArray.toArray(value);
- }
- return [value];
- },
- /**
- * Removes the specified item from the array if it exists.
- *
- * @param {Array} array The array.
- * @param {Object} item The item to remove.
- * @return {Array} The passed array itself.
- */
- remove: function(array, item) {
- var index = ExtArray.indexOf(array, item);
- if (index !== -1) {
- erase(array, index, 1);
- }
- return array;
- },
- /**
- * Push an item into the array only if the array doesn't contain it yet.
- *
- * @param {Array} array The array.
- * @param {Object} item The item to include.
- */
- include: function(array, item) {
- if (!ExtArray.contains(array, item)) {
- array.push(item);
- }
- },
- /**
- * Clone a flat array without referencing the previous one. Note that this is different
- * from `Ext.clone` since it doesn't handle recursive cloning. It's simply a convenient, easy-to-remember method
- * for `Array.prototype.slice.call(array)`.
- *
- * @param {Array} array The array
- * @return {Array} The clone array
- */
- clone: function(array) {
- return slice.call(array);
- },
- /**
- * Merge multiple arrays into one with unique items.
- *
- * {@link Ext.Array#union} is alias for {@link Ext.Array#merge}
- *
- * @param {Array} array1
- * @param {Array} array2
- * @param {Array} etc
- * @return {Array} merged
- */
- merge: function() {
- var args = slice.call(arguments),
- array = [],
- i, ln;
- for (i = 0, ln = args.length; i < ln; i++) {
- array = array.concat(args[i]);
- }
- return ExtArray.unique(array);
- },
- /**
- * Merge multiple arrays into one with unique items that exist in all of the arrays.
- *
- * @param {Array} array1
- * @param {Array} array2
- * @param {Array} etc
- * @return {Array} intersect
- */
- intersect: function() {
- var intersect = [],
- arrays = slice.call(arguments),
- i, j, k, minArray, array, x, y, ln, arraysLn, arrayLn;
- if (!arrays.length) {
- return intersect;
- }
- // Find the smallest array
- for (i = x = 0,ln = arrays.length; i < ln,array = arrays[i]; i++) {
- if (!minArray || array.length < minArray.length) {
- minArray = array;
- x = i;
- }
- }
- minArray = ExtArray.unique(minArray);
- erase(arrays, x, 1);
- // Use the smallest unique'd array as the anchor loop. If the other array(s) do contain
- // an item in the small array, we're likely to find it before reaching the end
- // of the inner loop and can terminate the search early.
- for (i = 0,ln = minArray.length; i < ln,x = minArray[i]; i++) {
- var count = 0;
- for (j = 0,arraysLn = arrays.length; j < arraysLn,array = arrays[j]; j++) {
- for (k = 0,arrayLn = array.length; k < arrayLn,y = array[k]; k++) {
- if (x === y) {
- count++;
- break;
- }
- }
- }
- if (count === arraysLn) {
- intersect.push(x);
- }
- }
- return intersect;
- },
- /**
- * Perform a set difference A-B by subtracting all items in array B from array A.
- *
- * @param {Array} arrayA
- * @param {Array} arrayB
- * @return {Array} difference
- */
- difference: function(arrayA, arrayB) {
- var clone = slice.call(arrayA),
- ln = clone.length,
- i, j, lnB;
- for (i = 0,lnB = arrayB.length; i < lnB; i++) {
- for (j = 0; j < ln; j++) {
- if (clone[j] === arrayB[i]) {
- erase(clone, j, 1);
- j--;
- ln--;
- }
- }
- }
- return clone;
- },
- /**
- * Returns a shallow copy of a part of an array. This is equivalent to the native
- * call `Array.prototype.slice.call(array, begin, end)`. This is often used when "array"
- * is "arguments" since the arguments object does not supply a slice method but can
- * be the context object to `Array.prototype.slice()`.
- *
- * @param {Array} array The array (or arguments object).
- * @param {Number} begin The index at which to begin. Negative values are offsets from
- * the end of the array.
- * @param {Number} end The index at which to end. The copied items do not include
- * end. Negative values are offsets from the end of the array. If end is omitted,
- * all items up to the end of the array are copied.
- * @return {Array} The copied piece of the array.
- */
- slice: function(array, begin, end) {
- return slice.call(array, begin, end);
- },
- /**
- * Sorts the elements of an Array.
- * By default, this method sorts the elements alphabetically and ascending.
- *
- * @param {Array} array The array to sort.
- * @param {Function} sortFn (optional) The comparison function.
- * @return {Array} The sorted array.
- */
- sort: function(array, sortFn) {
- if (supportsSort) {
- if (sortFn) {
- return array.sort(sortFn);
- } else {
- return array.sort();
- }
- }
- var length = array.length,
- i = 0,
- comparison,
- j, min, tmp;
- for (; i < length; i++) {
- min = i;
- for (j = i + 1; j < length; j++) {
- if (sortFn) {
- comparison = sortFn(array[j], array[min]);
- if (comparison < 0) {
- min = j;
- }
- } else if (array[j] < array[min]) {
- min = j;
- }
- }
- if (min !== i) {
- tmp = array[i];
- array[i] = array[min];
- array[min] = tmp;
- }
- }
- return array;
- },
- /**
- * Recursively flattens into 1-d Array. Injects Arrays inline.
- *
- * @param {Array} array The array to flatten
- * @return {Array} The 1-d array.
- */
- flatten: function(array) {
- var worker = [];
- function rFlatten(a) {
- var i, ln, v;
- for (i = 0, ln = a.length; i < ln; i++) {
- v = a[i];
- if (Ext.isArray(v)) {
- rFlatten(v);
- } else {
- worker.push(v);
- }
- }
- return worker;
- }
- return rFlatten(array);
- },
- /**
- * Returns the minimum value in the Array.
- *
- * @param {Array/NodeList} array The Array from which to select the minimum value.
- * @param {Function} comparisonFn (optional) a function to perform the comparison which determines minimization.
- * If omitted the "<" operator will be used.
- * __Note:__ gt = 1; eq = 0; lt = -1
- * @return {Object} minValue The minimum value.
- */
- min: function(array, comparisonFn) {
- var min = array[0],
- i, ln, item;
- for (i = 0, ln = array.length; i < ln; i++) {
- item = array[i];
- if (comparisonFn) {
- if (comparisonFn(min, item) === 1) {
- min = item;
- }
- }
- else {
- if (item < min) {
- min = item;
- }
- }
- }
- return min;
- },
- /**
- * Returns the maximum value in the Array.
- *
- * @param {Array/NodeList} array The Array from which to select the maximum value.
- * @param {Function} comparisonFn (optional) a function to perform the comparison which determines maximization.
- * If omitted the ">" operator will be used.
- * __Note:__ gt = 1; eq = 0; lt = -1
- * @return {Object} maxValue The maximum value
- */
- max: function(array, comparisonFn) {
- var max = array[0],
- i, ln, item;
- for (i = 0, ln = array.length; i < ln; i++) {
- item = array[i];
- if (comparisonFn) {
- if (comparisonFn(max, item) === -1) {
- max = item;
- }
- }
- else {
- if (item > max) {
- max = item;
- }
- }
- }
- return max;
- },
- /**
- * Calculates the mean of all items in the array.
- *
- * @param {Array} array The Array to calculate the mean value of.
- * @return {Number} The mean.
- */
- mean: function(array) {
- return array.length > 0 ? ExtArray.sum(array) / array.length : undefined;
- },
- /**
- * Calculates the sum of all items in the given array.
- *
- * @param {Array} array The Array to calculate the sum value of.
- * @return {Number} The sum.
- */
- sum: function(array) {
- var sum = 0,
- i, ln, item;
- for (i = 0,ln = array.length; i < ln; i++) {
- item = array[i];
- sum += item;
- }
- return sum;
- },
- //<debug>
- _replaceSim: replaceSim, // for unit testing
- _spliceSim: spliceSim,
- //</debug>
- /**
- * Removes items from an array. This is functionally equivalent to the splice method
- * of Array, but works around bugs in IE8's splice method and does not copy the
- * removed elements in order to return them (because very often they are ignored).
- *
- * @param {Array} array The Array on which to replace.
- * @param {Number} index The index in the array at which to operate.
- * @param {Number} removeCount The number of items to remove at index.
- * @return {Array} The array passed.
- * @method
- */
- erase: erase,
- /**
- * Inserts items in to an array.
- *
- * @param {Array} array The Array on which to replace.
- * @param {Number} index The index in the array at which to operate.
- * @param {Array} items The array of items to insert at index.
- * @return {Array} The array passed.
- */
- insert: function (array, index, items) {
- return replace(array, index, 0, items);
- },
- /**
- * Replaces items in an array. This is functionally equivalent to the splice method
- * of Array, but works around bugs in IE8's splice method and is often more convenient
- * to call because it accepts an array of items to insert rather than use a variadic
- * argument list.
- *
- * @param {Array} array The Array on which to replace.
- * @param {Number} index The index in the array at which to operate.
- * @param {Number} removeCount The number of items to remove at index (can be 0).
- * @param {Array} insert (optional) An array of items to insert at index.
- * @return {Array} The array passed.
- * @method
- */
- replace: replace,
- /**
- * Replaces items in an array. This is equivalent to the splice method of Array, but
- * works around bugs in IE8's splice method. The signature is exactly the same as the
- * splice method except that the array is the first argument. All arguments following
- * removeCount are inserted in the array at index.
- *
- * @param {Array} array The Array on which to replace.
- * @param {Number} index The index in the array at which to operate.
- * @param {Number} removeCount The number of items to remove at index (can be 0).
- * @return {Array} An array containing the removed items.
- * @method
- */
- splice: splice
- };
- /**
- * @method
- * @member Ext
- * @alias Ext.Array#each
- */
- Ext.each = ExtArray.each;
- /**
- * @method
- * @member Ext.Array
- * @alias Ext.Array#merge
- */
- ExtArray.union = ExtArray.merge;
- /**
- * Old alias to {@link Ext.Array#min}
- * @deprecated 4.0.0 Please use {@link Ext.Array#min} instead
- * @method
- * @member Ext
- * @alias Ext.Array#min
- */
- Ext.min = ExtArray.min;
- /**
- * Old alias to {@link Ext.Array#max}
- * @deprecated 4.0.0 Please use {@link Ext.Array#max} instead
- * @method
- * @member Ext
- * @alias Ext.Array#max
- */
- Ext.max = ExtArray.max;
- /**
- * Old alias to {@link Ext.Array#sum}
- * @deprecated 4.0.0 Please use {@link Ext.Array#sum} instead
- * @method
- * @member Ext
- * @alias Ext.Array#sum
- */
- Ext.sum = ExtArray.sum;
- /**
- * Old alias to {@link Ext.Array#mean}
- * @deprecated 4.0.0 Please use {@link Ext.Array#mean} instead
- * @method
- * @member Ext
- * @alias Ext.Array#mean
- */
- Ext.mean = ExtArray.mean;
- /**
- * Old alias to {@link Ext.Array#flatten}
- * @deprecated 4.0.0 Please use {@link Ext.Array#flatten} instead
- * @method
- * @member Ext
- * @alias Ext.Array#flatten
- */
- Ext.flatten = ExtArray.flatten;
- /**
- * Old alias to {@link Ext.Array#clean}
- * @deprecated 4.0.0 Please use {@link Ext.Array#clean} instead
- * @method
- * @member Ext
- * @alias Ext.Array#clean
- */
- Ext.clean = ExtArray.clean;
- /**
- * Old alias to {@link Ext.Array#unique}
- * @deprecated 4.0.0 Please use {@link Ext.Array#unique} instead
- * @method
- * @member Ext
- * @alias Ext.Array#unique
- */
- Ext.unique = ExtArray.unique;
- /**
- * Old alias to {@link Ext.Array#pluck Ext.Array.pluck}
- * @deprecated 4.0.0 Please use {@link Ext.Array#pluck Ext.Array.pluck} instead
- * @method
- * @member Ext
- * @alias Ext.Array#pluck
- */
- Ext.pluck = ExtArray.pluck;
- /**
- * @method
- * @member Ext
- * @alias Ext.Array#toArray
- */
- Ext.toArray = function() {
- return ExtArray.toArray.apply(ExtArray, arguments);
- };
- })();
- //@tag foundation,core
- //@define Ext.Number
- //@require Ext.Array
- /**
- * @class Ext.Number
- *
- * A collection of useful static methods to deal with numbers
- * @singleton
- */
- (function() {
- var isToFixedBroken = (0.9).toFixed() !== '1';
- Ext.Number = {
- /**
- * Checks whether or not the passed number is within a desired range. If the number is already within the
- * range it is returned, otherwise the min or max value is returned depending on which side of the range is
- * exceeded. Note that this method returns the constrained value but does not change the current number.
- * @param {Number} number The number to check
- * @param {Number} min The minimum number in the range
- * @param {Number} max The maximum number in the range
- * @return {Number} The constrained value if outside the range, otherwise the current value
- */
- constrain: function(number, min, max) {
- number = parseFloat(number);
- if (!isNaN(min)) {
- number = Math.max(number, min);
- }
- if (!isNaN(max)) {
- number = Math.min(number, max);
- }
- return number;
- },
- /**
- * Snaps the passed number between stopping points based upon a passed increment value.
- * @param {Number} value The unsnapped value.
- * @param {Number} increment The increment by which the value must move.
- * @param {Number} minValue The minimum value to which the returned value must be constrained. Overrides the increment..
- * @param {Number} maxValue The maximum value to which the returned value must be constrained. Overrides the increment..
- * @return {Number} The value of the nearest snap target.
- */
- snap : function(value, increment, minValue, maxValue) {
- var newValue = value,
- m;
- if (!(increment && value)) {
- return value;
- }
- m = value % increment;
- if (m !== 0) {
- newValue -= m;
- if (m * 2 >= increment) {
- newValue += increment;
- } else if (m * 2 < -increment) {
- newValue -= increment;
- }
- }
- return Ext.Number.constrain(newValue, minValue, maxValue);
- },
- /**
- * Formats a number using fixed-point notation
- * @param {Number} value The number to format
- * @param {Number} precision The number of digits to show after the decimal point
- */
- toFixed: function(value, precision) {
- if (isToFixedBroken) {
- precision = precision || 0;
- var pow = Math.pow(10, precision);
- return (Math.round(value * pow) / pow).toFixed(precision);
- }
- return value.toFixed(precision);
- },
- /**
- * Validate that a value is numeric and convert it to a number if necessary. Returns the specified default value if
- * it is not.
- Ext.Number.from('1.23', 1); // returns 1.23
- Ext.Number.from('abc', 1); // returns 1
- * @param {Object} value
- * @param {Number} defaultValue The value to return if the original value is non-numeric
- * @return {Number} value, if numeric, defaultValue otherwise
- */
- from: function(value, defaultValue) {
- if (isFinite(value)) {
- value = parseFloat(value);
- }
- return !isNaN(value) ? value : defaultValue;
- }
- };
- })();
- /**
- * This method is deprecated, please use {@link Ext.Number#from Ext.Number.from} instead
- *
- * @deprecated 4.0.0 Replaced by Ext.Number.from
- * @member Ext
- * @method num
- */
- Ext.num = function() {
- return Ext.Number.from.apply(this, arguments);
- };
- //@tag foundation,core
- //@define Ext.Object
- //@require Ext.Number
- /**
- * @author Jacky Nguyen <jacky@sencha.com>
- * @docauthor Jacky Nguyen <jacky@sencha.com>
- * @class Ext.Object
- *
- * A collection of useful static methods to deal with objects.
- *
- * @singleton
- */
- (function() {
- // The "constructor" for chain:
- var TemplateClass = function(){};
- var ExtObject = Ext.Object = {
- /**
- * Returns a new object with the given object as the prototype chain.
- * @param {Object} object The prototype chain for the new object.
- */
- chain: ('create' in Object) ? function(object){
- return Object.create(object);
- } : function (object) {
- TemplateClass.prototype = object;
- var result = new TemplateClass();
- TemplateClass.prototype = null;
- return result;
- },
- /**
- * Convert a `name` - `value` pair to an array of objects with support for nested structures; useful to construct
- * query strings. For example:
- *
- * Non-recursive:
- *
- * var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);
- *
- * // objects then equals:
- * [
- * { name: 'hobbies', value: 'reading' },
- * { name: 'hobbies', value: 'cooking' },
- * { name: 'hobbies', value: 'swimming' }
- * ]
- *
- * Recursive:
- *
- * var objects = Ext.Object.toQueryObjects('dateOfBirth', {
- * day: 3,
- * month: 8,
- * year: 1987,
- * extra: {
- * hour: 4,
- * minute: 30
- * }
- * }, true);
- *
- * // objects then equals:
- * [
- * { name: 'dateOfBirth[day]', value: 3 },
- * { name: 'dateOfBirth[month]', value: 8 },
- * { name: 'dateOfBirth[year]', value: 1987 },
- * { name: 'dateOfBirth[extra][hour]', value: 4 },
- * { name: 'dateOfBirth[extra][minute]', value: 30 }
- * ]
- *
- * @param {String} name
- * @param {Object} value
- * @param {Boolean} [recursive=false] `true` to recursively encode any sub-objects.
- * @return {Object[]} Array of objects with `name` and `value` fields.
- */
- toQueryObjects: function(name, value, recursive) {
- var self = ExtObject.toQueryObjects,
- objects = [],
- i, ln;
- if (Ext.isArray(value)) {
- for (i = 0, ln = value.length; i < ln; i++) {
- if (recursive) {
- objects = objects.concat(self(name + '[' + i + ']', value[i], true));
- }
- else {
- objects.push({
- name: name,
- value: value[i]
- });
- }
- }
- }
- else if (Ext.isObject(value)) {
- for (i in value) {
- if (value.hasOwnProperty(i)) {
- if (recursive) {
- objects = objects.concat(self(name + '[' + i + ']', value[i], true));
- }
- else {
- objects.push({
- name: name,
- value: value[i]
- });
- }
- }
- }
- }
- else {
- objects.push({
- name: name,
- value: value
- });
- }
- return objects;
- },
- /**
- * Takes an object and converts it to an encoded query string.
- *
- * Non-recursive:
- *
- * Ext.Object.toQueryString({foo: 1, bar: 2}); // returns "foo=1&bar=2"
- * Ext.Object.toQueryString({foo: null, bar: 2}); // returns "foo=&bar=2"
- * Ext.Object.toQueryString({'some price': '$300'}); // returns "some%20price=%24300"
- * Ext.Object.toQueryString({date: new Date(2011, 0, 1)}); // returns "date=%222011-01-01T00%3A00%3A00%22"
- * Ext.Object.toQueryString({colors: ['red', 'green', 'blue']}); // returns "colors=red&colors=green&colors=blue"
- *
- * Recursive:
- *
- * Ext.Object.toQueryString({
- * username: 'Jacky',
- * dateOfBirth: {
- * day: 1,
- * month: 2,
- * year: 1911
- * },
- * hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
- * }, true);
- *
- * // returns the following string (broken down and url-decoded for ease of reading purpose):
- * // 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
- *
- * @param {Object} object The object to encode.
- * @param {Boolean} [recursive=false] Whether or not to interpret the object in recursive format.
- * (PHP / Ruby on Rails servers and similar).
- * @return {String} queryString
- */
- toQueryString: function(object, recursive) {
- var paramObjects = [],
- params = [],
- i, j, ln, paramObject, value;
- for (i in object) {
- if (object.hasOwnProperty(i)) {
- paramObjects = paramObjects.concat(ExtObject.toQueryObjects(i, object[i], recursive));
- }
- }
- for (j = 0, ln = paramObjects.length; j < ln; j++) {
- paramObject = paramObjects[j];
- value = paramObject.value;
- if (Ext.isEmpty(value)) {
- value = '';
- }
- else if (Ext.isDate(value)) {
- value = Ext.Date.toString(value);
- }
- params.push(encodeURIComponent(paramObject.name) + '=' + encodeURIComponent(String(value)));
- }
- return params.join('&');
- },
- /**
- * Converts a query string back into an object.
- *
- * Non-recursive:
- *
- * Ext.Object.fromQueryString("foo=1&bar=2"); // returns {foo: 1, bar: 2}
- * Ext.Object.fromQueryString("foo=&bar=2"); // returns {foo: null, bar: 2}
- * Ext.Object.fromQueryString("some%20price=%24300"); // returns {'some price': '$300'}
- * Ext.Object.fromQueryString("colors=red&colors=green&colors=blue"); // returns {colors: ['red', 'green', 'blue']}
- *
- * Recursive:
- *
- * 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);
- *
- * // returns
- * {
- * username: 'Jacky',
- * dateOfBirth: {
- * day: '1',
- * month: '2',
- * year: '1911'
- * },
- * hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
- * }
- *
- * @param {String} queryString The query string to decode.
- * @param {Boolean} [recursive=false] Whether or not to recursively decode the string. This format is supported by
- * PHP / Ruby on Rails servers and similar.
- * @return {Object}
- */
- fromQueryString: function(queryString, recursive) {
- var parts = queryString.replace(/^\?/, '').split('&'),
- object = {},
- temp, components, name, value, i, ln,
- part, j, subLn, matchedKeys, matchedName,
- keys, key, nextKey;
- for (i = 0, ln = parts.length; i < ln; i++) {
- part = parts[i];
- if (part.length > 0) {
- components = part.split('=');
- name = decodeURIComponent(components[0]);
- value = (components[1] !== undefined) ? decodeURIComponent(components[1]) : '';
- if (!recursive) {
- if (object.hasOwnProperty(name)) {
- if (!Ext.isArray(object[name])) {
- object[name] = [object[name]];
- }
- object[name].push(value);
- }
- else {
- object[name] = value;
- }
- }
- else {
- matchedKeys = name.match(/(\[):?([^\]]*)\]/g);
- matchedName = name.match(/^([^\[]+)/);
- //<debug error>
- if (!matchedName) {
- throw new Error('[Ext.Object.fromQueryString] Malformed query string given, failed parsing name from "' + part + '"');
- }
- //</debug>
- name = matchedName[0];
- keys = [];
- if (matchedKeys === null) {
- object[name] = value;
- continue;
- }
- for (j = 0, subLn = matchedKeys.length; j < subLn; j++) {
- key = matchedKeys[j];
- key = (key.length === 2) ? '' : key.substring(1, key.length - 1);
- keys.push(key);
- }
- keys.unshift(name);
- temp = object;
- for (j = 0, subLn = keys.length; j < subLn; j++) {
- key = keys[j];
- if (j === subLn - 1) {
- if (Ext.isArray(temp) && key === '') {
- temp.push(value);
- }
- else {
- temp[key] = value;
- }
- }
- else {
- if (temp[key] === undefined || typeof temp[key] === 'string') {
- nextKey = keys[j+1];
- temp[key] = (Ext.isNumeric(nextKey) || nextKey === '') ? [] : {};
- }
- temp = temp[key];
- }
- }
- }
- }
- }
- return object;
- },
- /**
- * Iterate through an object and invoke the given callback function for each iteration. The iteration can be stop
- * by returning `false` in the callback function. For example:
- *
- * var person = {
- * name: 'Jacky',
- * hairColor: 'black',
- * loves: ['food', 'sleeping', 'wife']
- * };
- *
- * Ext.Object.each(person, function(key, value, myself) {
- * console.log(key + ":" + value);
- *
- * if (key === 'hairColor') {
- * return false; // stop the iteration
- * }
- * });
- *
- * @param {Object} object The object to iterate
- * @param {Function} fn The callback function.
- * @param {String} fn.key
- * @param {Mixed} fn.value
- * @param {Object} fn.object The object itself
- * @param {Object} [scope] The execution scope (`this`) of the callback function
- */
- each: function(object, fn, scope) {
- for (var property in object) {
- if (object.hasOwnProperty(property)) {
- if (fn.call(scope || object, property, object[property], object) === false) {
- return;
- }
- }
- }
- },
- /**
- * Merges any number of objects recursively without referencing them or their children.
- *
- * var extjs = {
- * companyName: 'Ext JS',
- * products: ['Ext JS', 'Ext GWT', 'Ext Designer'],
- * isSuperCool: true,
- * office: {
- * size: 2000,
- * location: 'Palo Alto',
- * isFun: true
- * }
- * };
- *
- * var newStuff = {
- * companyName: 'Sencha Inc.',
- * products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
- * office: {
- * size: 40000,
- * location: 'Redwood City'
- * }
- * };
- *
- * var sencha = Ext.Object.merge({}, extjs, newStuff);
- *
- * // sencha then equals to
- * {
- * companyName: 'Sencha Inc.',
- * products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
- * isSuperCool: true
- * office: {
- * size: 40000,
- * location: 'Redwood City'
- * isFun: true
- * }
- * }
- *
- * @param {Object} source The first object into which to merge the others.
- * @param {Object...} objs One or more objects to be merged into the first.
- * @return {Object} The object that is created as a result of merging all the objects passed in.
- */
- merge: function(source) {
- var i = 1,
- ln = arguments.length,
- mergeFn = ExtObject.merge,
- cloneFn = Ext.clone,
- object, key, value, sourceKey;
- for (; i < ln; i++) {
- object = arguments[i];
- for (key in object) {
- value = object[key];
- if (value && value.constructor === Object) {
- sourceKey = source[key];
- if (sourceKey && sourceKey.constructor === Object) {
- mergeFn(sourceKey, value);
- }
- else {
- source[key] = cloneFn(value);
- }
- }
- else {
- source[key] = value;
- }
- }
- }
- return source;
- },
- /**
- * @private
- * @param source
- */
- mergeIf: function(source) {
- var i = 1,
- ln = arguments.length,
- cloneFn = Ext.clone,
- object, key, value;
- for (; i < ln; i++) {
- object = arguments[i];
- for (key in object) {
- if (!(key in source)) {
- value = object[key];
- if (value && value.constructor === Object) {
- source[key] = cloneFn(value);
- }
- else {
- source[key] = value;
- }
- }
- }
- }
- return source;
- },
- /**
- * Returns the first matching key corresponding to the given value.
- * If no matching value is found, `null` is returned.
- *
- * var person = {
- * name: 'Jacky',
- * loves: 'food'
- * };
- *
- * alert(Ext.Object.getKey(sencha, 'food')); // alerts 'loves'
- *
- * @param {Object} object
- * @param {Object} value The value to find
- */
- getKey: function(object, value) {
- for (var property in object) {
- if (object.hasOwnProperty(property) && object[property] === value) {
- return property;
- }
- }
- return null;
- },
- /**
- * Gets all values of the given object as an array.
- *
- * var values = Ext.Object.getValues({
- * name: 'Jacky',
- * loves: 'food'
- * }); // ['Jacky', 'food']
- *
- * @param {Object} object
- * @return {Array} An array of values from the object.
- */
- getValues: function(object) {
- var values = [],
- property;
- for (property in object) {
- if (object.hasOwnProperty(property)) {
- values.push(object[property]);
- }
- }
- return values;
- },
- /**
- * Gets all keys of the given object as an array.
- *
- * var values = Ext.Object.getKeys({
- * name: 'Jacky',
- * loves: 'food'
- * }); // ['name', 'loves']
- *
- * @param {Object} object
- * @return {String[]} An array of keys from the object.
- * @method
- */
- getKeys: ('keys' in Object) ? Object.keys : function(object) {
- var keys = [],
- property;
- for (property in object) {
- if (object.hasOwnProperty(property)) {
- keys.push(property);
- }
- }
- return keys;
- },
- /**
- * Gets the total number of this object's own properties.
- *
- * var size = Ext.Object.getSize({
- * name: 'Jacky',
- * loves: 'food'
- * }); // size equals 2
- *
- * @param {Object} object
- * @return {Number} size
- */
- getSize: function(object) {
- var size = 0,
- property;
- for (property in object) {
- if (object.hasOwnProperty(property)) {
- size++;
- }
- }
- return size;
- },
- /**
- * @private
- */
- classify: function(object) {
- var objectProperties = [],
- arrayProperties = [],
- propertyClassesMap = {},
- objectClass = function() {
- var i = 0,
- ln = objectProperties.length,
- property;
- for (; i < ln; i++) {
- property = objectProperties[i];
- this[property] = new propertyClassesMap[property];
- }
- ln = arrayProperties.length;
- for (i = 0; i < ln; i++) {
- property = arrayProperties[i];
- this[property] = object[property].slice();
- }
- },
- key, value, constructor;
- for (key in object) {
- if (object.hasOwnProperty(key)) {
- value = object[key];
- if (value) {
- constructor = value.constructor;
- if (constructor === Object) {
- objectProperties.push(key);
- propertyClassesMap[key] = ExtObject.classify(value);
- }
- else if (constructor === Array) {
- arrayProperties.push(key);
- }
- }
- }
- }
- objectClass.prototype = object;
- return objectClass;
- },
- defineProperty: ('defineProperty' in Object) ? Object.defineProperty : function(object, name, descriptor) {
- if (descriptor.get) {
- object.__defineGetter__(name, descriptor.get);
- }
- if (descriptor.set) {
- object.__defineSetter__(name, descriptor.set);
- }
- }
- };
- /**
- * A convenient alias method for {@link Ext.Object#merge}.
- *
- * @member Ext
- * @method merge
- */
- Ext.merge = Ext.Object.merge;
- /**
- * @private
- */
- Ext.mergeIf = Ext.Object.mergeIf;
- /**
- * A convenient alias method for {@link Ext.Object#toQueryString}.
- *
- * @member Ext
- * @method urlEncode
- * @deprecated 4.0.0 Please use `{@link Ext.Object#toQueryString Ext.Object.toQueryString}` instead
- */
- Ext.urlEncode = function() {
- var args = Ext.Array.from(arguments),
- prefix = '';
- // Support for the old `pre` argument
- if ((typeof args[1] === 'string')) {
- prefix = args[1] + '&';
- args[1] = false;
- }
- return prefix + ExtObject.toQueryString.apply(ExtObject, args);
- };
- /**
- * A convenient alias method for {@link Ext.Object#fromQueryString}.
- *
- * @member Ext
- * @method urlDecode
- * @deprecated 4.0.0 Please use {@link Ext.Object#fromQueryString Ext.Object.fromQueryString} instead
- */
- Ext.urlDecode = function() {
- return ExtObject.fromQueryString.apply(ExtObject, arguments);
- };
- })();
- //@tag foundation,core
- //@define Ext.Function
- //@require Ext.Object
- /**
- * @class Ext.Function
- *
- * A collection of useful static methods to deal with function callbacks.
- * @singleton
- * @alternateClassName Ext.util.Functions
- */
- Ext.Function = {
- /**
- * A very commonly used method throughout the framework. It acts as a wrapper around another method
- * which originally accepts 2 arguments for `name` and `value`.
- * The wrapped function then allows "flexible" value setting of either:
- *
- * - `name` and `value` as 2 arguments
- * - one single object argument with multiple key - value pairs
- *
- * For example:
- *
- * var setValue = Ext.Function.flexSetter(function(name, value) {
- * this[name] = value;
- * });
- *
- * // Afterwards
- * // Setting a single name - value
- * setValue('name1', 'value1');
- *
- * // Settings multiple name - value pairs
- * setValue({
- * name1: 'value1',
- * name2: 'value2',
- * name3: 'value3'
- * });
- *
- * @param {Function} setter
- * @return {Function} flexSetter
- */
- flexSetter: function(fn) {
- return function(a, b) {
- var k, i;
- if (a === null) {
- return this;
- }
- if (typeof a !== 'string') {
- for (k in a) {
- if (a.hasOwnProperty(k)) {
- fn.call(this, k, a[k]);
- }
- }
- if (Ext.enumerables) {
- for (i = Ext.enumerables.length; i--;) {
- k = Ext.enumerables[i];
- if (a.hasOwnProperty(k)) {
- fn.call(this, k, a[k]);
- }
- }
- }
- } else {
- fn.call(this, a, b);
- }
- return this;
- };
- },
- /**
- * Create a new function from the provided `fn`, change `this` to the provided scope, optionally
- * overrides arguments for the call. Defaults to the arguments passed by the caller.
- *
- * {@link Ext#bind Ext.bind} is alias for {@link Ext.Function#bind Ext.Function.bind}
- *
- * @param {Function} fn The function to delegate.
- * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
- * **If omitted, defaults to the browser window.**
- * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
- * @param {Boolean/Number} appendArgs (optional) if `true` args are appended to call args instead of overriding,
- * if a number the args are inserted at the specified position.
- * @return {Function} The new function.
- */
- bind: function(fn, scope, args, appendArgs) {
- if (arguments.length === 2) {
- return function() {
- return fn.apply(scope, arguments);
- }
- }
- var method = fn,
- slice = Array.prototype.slice;
- return function() {
- var callArgs = args || arguments;
- if (appendArgs === true) {
- callArgs = slice.call(arguments, 0);
- callArgs = callArgs.concat(args);
- }
- else if (typeof appendArgs == 'number') {
- callArgs = slice.call(arguments, 0); // copy arguments first
- Ext.Array.insert(callArgs, appendArgs, args);
- }
- return method.apply(scope || window, callArgs);
- };
- },
- /**
- * Create a new function from the provided `fn`, the arguments of which are pre-set to `args`.
- * New arguments passed to the newly created callback when it's invoked are appended after the pre-set ones.
- * This is especially useful when creating callbacks.
- *
- * For example:
- *
- * var originalFunction = function(){
- * alert(Ext.Array.from(arguments).join(' '));
- * };
- *
- * var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']);
- *
- * callback(); // alerts 'Hello World'
- * callback('by Me'); // alerts 'Hello World by Me'
- *
- * {@link Ext#pass Ext.pass} is alias for {@link Ext.Function#pass Ext.Function.pass}
- *
- * @param {Function} fn The original function.
- * @param {Array} args The arguments to pass to new callback.
- * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
- * @return {Function} The new callback function.
- */
- pass: function(fn, args, scope) {
- if (!Ext.isArray(args)) {
- args = Ext.Array.clone(args);
- }
- return function() {
- args.push.apply(args, arguments);
- return fn.apply(scope || this, args);
- };
- },
- /**
- * Create an alias to the provided method property with name `methodName` of `object`.
- * Note that the execution scope will still be bound to the provided `object` itself.
- *
- * @param {Object/Function} object
- * @param {String} methodName
- * @return {Function} aliasFn
- */
- alias: function(object, methodName) {
- return function() {
- return object[methodName].apply(object, arguments);
- };
- },
- /**
- * Create a "clone" of the provided method. The returned method will call the given
- * method passing along all arguments and the "this" pointer and return its result.
- *
- * @param {Function} method
- * @return {Function} cloneFn
- */
- clone: function(method) {
- return function() {
- return method.apply(this, arguments);
- };
- },
- /**
- * Creates an interceptor function. The passed function is called before the original one. If it returns false,
- * the original one is not called. The resulting function returns the results of the original function.
- * The passed function is called with the parameters of the original function. Example usage:
- *
- * var sayHi = function(name){
- * alert('Hi, ' + name);
- * };
- *
- * sayHi('Fred'); // alerts "Hi, Fred"
- *
- * // create a new function that validates input without
- * // directly modifying the original function:
- * var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){
- * return name === 'Brian';
- * });
- *
- * sayHiToFriend('Fred'); // no alert
- * sayHiToFriend('Brian'); // alerts "Hi, Brian"
- *
- * @param {Function} origFn The original function.
- * @param {Function} newFn The function to call before the original.
- * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
- * **If omitted, defaults to the scope in which the original function is called or the browser window.**
- * @param {Object} [returnValue=null] (optional) The value to return if the passed function return `false`.
- * @return {Function} The new function.
- */
- createInterceptor: function(origFn, newFn, scope, returnValue) {
- var method = origFn;
- if (!Ext.isFunction(newFn)) {
- return origFn;
- }
- else {
- return function() {
- var me = this,
- args = arguments;
- newFn.target = me;
- newFn.method = origFn;
- return (newFn.apply(scope || me || window, args) !== false) ? origFn.apply(me || window, args) : returnValue || null;
- };
- }
- },
- /**
- * Creates a delegate (callback) which, when called, executes after a specific delay.
- *
- * @param {Function} fn The function which will be called on a delay when the returned function is called.
- * Optionally, a replacement (or additional) argument list may be specified.
- * @param {Number} delay The number of milliseconds to defer execution by whenever called.
- * @param {Object} scope (optional) The scope (`this` reference) used by the function at execution time.
- * @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)
- * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
- * if a number the args are inserted at the specified position.
- * @return {Function} A function which, when called, executes the original function after the specified delay.
- */
- createDelayed: function(fn, delay, scope, args, appendArgs) {
- if (scope || args) {
- fn = Ext.Function.bind(fn, scope, args, appendArgs);
- }
- return function() {
- var me = this,
- args = Array.prototype.slice.call(arguments);
- setTimeout(function() {
- fn.apply(me, args);
- }, delay);
- }
- },
- /**
- * Calls this function after the number of milliseconds specified, optionally in a specific scope. Example usage:
- *
- * var sayHi = function(name){
- * alert('Hi, ' + name);
- * };
- *
- * // executes immediately:
- * sayHi('Fred');
- *
- * // executes after 2 seconds:
- * Ext.Function.defer(sayHi, 2000, this, ['Fred']);
- *
- * // this syntax is sometimes useful for deferring
- * // execution of an anonymous function:
- * Ext.Function.defer(function(){
- * alert('Anonymous');
- * }, 100);
- *
- * {@link Ext#defer Ext.defer} is alias for {@link Ext.Function#defer Ext.Function.defer}
- *
- * @param {Function} fn The function to defer.
- * @param {Number} millis The number of milliseconds for the `setTimeout()` call.
- * If less than or equal to 0 the function is executed immediately.
- * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
- * If omitted, defaults to the browser window.
- * @param {Array} args (optional) Overrides arguments for the call. Defaults to the arguments passed by the caller.
- * @param {Boolean/Number} appendArgs (optional) if `true`, args are appended to call args instead of overriding,
- * if a number the args are inserted at the specified position.
- * @return {Number} The timeout id that can be used with `clearTimeout()`.
- */
- defer: function(fn, millis, scope, args, appendArgs) {
- fn = Ext.Function.bind(fn, scope, args, appendArgs);
- if (millis > 0) {
- return setTimeout(fn, millis);
- }
- fn();
- return 0;
- },
- /**
- * Create a combined function call sequence of the original function + the passed function.
- * The resulting function returns the results of the original function.
- * The passed function is called with the parameters of the original function. Example usage:
- *
- * var sayHi = function(name){
- * alert('Hi, ' + name);
- * };
- *
- * sayHi('Fred'); // alerts "Hi, Fred"
- *
- * var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){
- * alert('Bye, ' + name);
- * });
- *
- * sayGoodbye('Fred'); // both alerts show
- *
- * @param {Function} originalFn The original function.
- * @param {Function} newFn The function to sequence.
- * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
- * If omitted, defaults to the scope in which the original function is called or the browser window.
- * @return {Function} The new function.
- */
- createSequence: function(originalFn, newFn, scope) {
- if (!newFn) {
- return originalFn;
- }
- else {
- return function() {
- var result = originalFn.apply(this, arguments);
- newFn.apply(scope || this, arguments);
- return result;
- };
- }
- },
- /**
- * Creates a delegate function, optionally with a bound scope which, when called, buffers
- * the execution of the passed function for the configured number of milliseconds.
- * If called again within that period, the impending invocation will be canceled, and the
- * timeout period will begin again.
- *
- * @param {Function} fn The function to invoke on a buffered timer.
- * @param {Number} buffer The number of milliseconds by which to buffer the invocation of the
- * function.
- * @param {Object} scope (optional) The scope (`this` reference) in which
- * the passed function is executed. If omitted, defaults to the scope specified by the caller.
- * @param {Array} args (optional) Override arguments for the call. Defaults to the arguments
- * passed by the caller.
- * @return {Function} A function which invokes the passed function after buffering for the specified time.
- */
- createBuffered: function(fn, buffer, scope, args) {
- var timerId;
- return function() {
- var callArgs = args || Array.prototype.slice.call(arguments, 0),
- me = scope || this;
- if (timerId) {
- clearTimeout(timerId);
- }
- timerId = setTimeout(function(){
- fn.apply(me, callArgs);
- }, buffer);
- };
- },
- /**
- * Creates a throttled version of the passed function which, when called repeatedly and
- * rapidly, invokes the passed function only after a certain interval has elapsed since the
- * previous invocation.
- *
- * This is useful for wrapping functions which may be called repeatedly, such as
- * a handler of a mouse move event when the processing is expensive.
- *
- * @param {Function} fn The function to execute at a regular time interval.
- * @param {Number} interval The interval, in milliseconds, on which the passed function is executed.
- * @param {Object} scope (optional) The scope (`this` reference) in which
- * the passed function is executed. If omitted, defaults to the scope specified by the caller.
- * @return {Function} A function which invokes the passed function at the specified interval.
- */
- createThrottled: function(fn, interval, scope) {
- var lastCallTime, elapsed, lastArgs, timer, execute = function() {
- fn.apply(scope || this, lastArgs);
- lastCallTime = new Date().getTime();
- };
- return function() {
- elapsed = new Date().getTime() - lastCallTime;
- lastArgs = arguments;
- clearTimeout(timer);
- if (!lastCallTime || (elapsed >= interval)) {
- execute();
- } else {
- timer = setTimeout(execute, interval - elapsed);
- }
- };
- },
- interceptBefore: function(object, methodName, fn) {
- var method = object[methodName] || Ext.emptyFn;
- return object[methodName] = function() {
- var ret = fn.apply(this, arguments);
- method.apply(this, arguments);
- return ret;
- };
- },
- interceptAfter: function(object, methodName, fn) {
- var method = object[methodName] || Ext.emptyFn;
- return object[methodName] = function() {
- method.apply(this, arguments);
- return fn.apply(this, arguments);
- };
- }
- };
- /**
- * @method
- * @member Ext
- * @alias Ext.Function#defer
- */
- Ext.defer = Ext.Function.alias(Ext.Function, 'defer');
- /**
- * @method
- * @member Ext
- * @alias Ext.Function#pass
- */
- Ext.pass = Ext.Function.alias(Ext.Function, 'pass');
- /**
- * @method
- * @member Ext
- * @alias Ext.Function#bind
- */
- Ext.bind = Ext.Function.alias(Ext.Function, 'bind');
- //@tag foundation,core
- //@define Ext.JSON
- //@require Ext.Function
- /**
- * @class Ext.JSON
- * Modified version of Douglas Crockford's json.js that doesn't
- * mess with the Object prototype.
- * [http://www.json.org/js.html](http://www.json.org/js.html)
- * @singleton
- */
- Ext.JSON = new(function() {
- var useHasOwn = !! {}.hasOwnProperty,
- isNative = function() {
- var useNative = null;
- return function() {
- if (useNative === null) {
- useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]';
- }
- return useNative;
- };
- }(),
- pad = function(n) {
- return n < 10 ? "0" + n : n;
- },
- doDecode = function(json) {
- return eval("(" + json + ')');
- },
- doEncode = function(o) {
- if (!Ext.isDefined(o) || o === null) {
- return "null";
- } else if (Ext.isArray(o)) {
- return encodeArray(o);
- } else if (Ext.isDate(o)) {
- return Ext.JSON.encodeDate(o);
- } else if (Ext.isString(o)) {
- return encodeString(o);
- } else if (typeof o == "number") {
- //don't use isNumber here, since finite checks happen inside isNumber
- return isFinite(o) ? String(o) : "null";
- } else if (Ext.isBoolean(o)) {
- return String(o);
- } else if (Ext.isObject(o)) {
- return encodeObject(o);
- } else if (typeof o === "function") {
- return "null";
- }
- return 'undefined';
- },
- m = {
- "\b": '\\b',
- "\t": '\\t',
- "\n": '\\n',
- "\f": '\\f',
- "\r": '\\r',
- '"': '\\"',
- "\\": '\\\\',
- '\x0b': '\\u000b' //ie doesn't handle \v
- },
- charToReplace = /[\\\"\x00-\x1f\x7f-\uffff]/g,
- encodeString = function(s) {
- return '"' + s.replace(charToReplace, function(a) {
- var c = m[a];
- return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
- }) + '"';
- },
- encodeArray = function(o) {
- var a = ["[", ""],
- // Note empty string in case there are no serializable members.
- len = o.length,
- i;
- for (i = 0; i < len; i += 1) {
- a.push(doEncode(o[i]), ',');
- }
- // Overwrite trailing comma (or empty string)
- a[a.length - 1] = ']';
- return a.join("");
- },
- encodeObject = function(o) {
- var a = ["{", ""],
- // Note empty string in case there are no serializable members.
- i;
- for (i in o) {
- if (!useHasOwn || o.hasOwnProperty(i)) {
- a.push(doEncode(i), ":", doEncode(o[i]), ',');
- }
- }
- // Overwrite trailing comma (or empty string)
- a[a.length - 1] = '}';
- return a.join("");
- };
- /**
- * Encodes a Date. This returns the actual string which is inserted into the JSON string as the literal expression.
- * __The returned value includes enclosing double quotation marks.__
- *
- * The default return format is "yyyy-mm-ddThh:mm:ss".
- *
- * To override this:
- *
- * Ext.JSON.encodeDate = function(d) {
- * return Ext.Date.format(d, '"Y-m-d"');
- * };
- *
- * @param {Date} d The Date to encode.
- * @return {String} The string literal to use in a JSON string.
- */
- this.encodeDate = function(o) {
- return '"' + o.getFullYear() + "-"
- + pad(o.getMonth() + 1) + "-"
- + pad(o.getDate()) + "T"
- + pad(o.getHours()) + ":"
- + pad(o.getMinutes()) + ":"
- + pad(o.getSeconds()) + '"';
- };
- /**
- * Encodes an Object, Array or other value.
- * @param {Object} o The variable to encode.
- * @return {String} The JSON string.
- * @method
- */
- this.encode = function() {
- var ec;
- return function(o) {
- if (!ec) {
- // setup encoding function on first access
- ec = isNative() ? JSON.stringify : doEncode;
- }
- return ec(o);
- };
- }();
- /**
- * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws a Error unless the safe option is set.
- * @param {String} json The JSON string.
- * @param {Boolean} safe (optional) Whether to return `null` or throw an exception if the JSON is invalid.
- * @return {Object/null} The resulting object.
- * @method
- */
- this.decode = function() {
- var dc;
- return function(json, safe) {
- if (!dc) {
- // setup decoding function on first access
- dc = isNative() ? JSON.parse : doDecode;
- }
- try {
- return dc(json);
- } catch (e) {
- if (safe === true) {
- return null;
- }
- Ext.Error.raise({
- sourceClass: "Ext.JSON",
- sourceMethod: "decode",
- msg: "You're trying to decode an invalid JSON String: " + json
- });
- }
- };
- }();
- })();
- /**
- * Shorthand for {@link Ext.JSON#encode}.
- * @member Ext
- * @method encode
- * @alias Ext.JSON#encode
- */
- Ext.encode = Ext.JSON.encode;
- /**
- * Shorthand for {@link Ext.JSON#decode}.
- * @member Ext
- * @method decode
- * @alias Ext.JSON#decode
- */
- Ext.decode = Ext.JSON.decode;
- //@tag foundation,core
- //@define Ext.Error
- //@require Ext.JSON
- Ext.Error = {
- raise: function(object) {
- throw new Error(object.msg);
- }
- };
- //@tag foundation,core
- //@define Ext.Date
- //@require Ext.Error
- /**
- *
- */
- Ext.Date = {
- /** @ignore */
- now: Date.now,
- /**
- * @private
- * Private for now
- */
- toString: function(date) {
- if (!date) {
- date = new Date();
- }
- var pad = Ext.String.leftPad;
- return date.getFullYear() + "-"
- + pad(date.getMonth() + 1, 2, '0') + "-"
- + pad(date.getDate(), 2, '0') + "T"
- + pad(date.getHours(), 2, '0') + ":"
- + pad(date.getMinutes(), 2, '0') + ":"
- + pad(date.getSeconds(), 2, '0');
- }
- };
- //@tag foundation,core
- //@define Ext.Base
- //@require Ext.Date
- /**
- * @class Ext.Base
- *
- * @author Jacky Nguyen <jacky@sencha.com>
- * @aside guide class_system
- * @aside video class-system
- *
- * The root of all classes created with {@link Ext#define}.
- *
- * Ext.Base is the building block of all Ext classes. All classes in Ext inherit from Ext.Base. All prototype and static
- * members of this class are inherited by all other classes.
- *
- * See the [Class System Guide](#!/guide/class_system) for more.
- *
- */
- (function(flexSetter) {
- var noArgs = [],
- Base = function(){};
- // These static properties will be copied to every newly created class with {@link Ext#define}
- Ext.apply(Base, {
- $className: 'Ext.Base',
- $isClass: true,
- /**
- * Create a new instance of this Class.
- *
- * Ext.define('My.cool.Class', {
- * // ...
- * });
- *
- * My.cool.Class.create({
- * someConfig: true
- * });
- *
- * All parameters are passed to the constructor of the class.
- *
- * @return {Object} the created instance.
- * @static
- * @inheritable
- */
- create: function() {
- return Ext.create.apply(Ext, [this].concat(Array.prototype.slice.call(arguments, 0)));
- },
- /**
- * @private
- * @static
- * @inheritable
- */
- extend: function(parent) {
- var parentPrototype = parent.prototype,
- prototype, i, ln, name, statics;
- prototype = this.prototype = Ext.Object.chain(parentPrototype);
- prototype.self = this;
- this.superclass = prototype.superclass = parentPrototype;
- if (!parent.$isClass) {
- Ext.apply(prototype, Ext.Base.prototype);
- prototype.constructor = function() {
- parentPrototype.constructor.apply(this, arguments);
- };
- }
- //<feature classSystem.inheritableStatics>
- // Statics inheritance
- statics = parentPrototype.$inheritableStatics;
- if (statics) {
- for (i = 0,ln = statics.length; i < ln; i++) {
- name = statics[i];
- if (!this.hasOwnProperty(name)) {
- this[name] = parent[name];
- }
- }
- }
- //</feature>
- if (parent.$onExtended) {
- this.$onExtended = parent.$onExtended.slice();
- }
- //<feature classSystem.config>
- prototype.config = prototype.defaultConfig = new prototype.configClass;
- prototype.initConfigList = prototype.initConfigList.slice();
- prototype.initConfigMap = Ext.Object.chain(prototype.initConfigMap);
- //</feature>
- },
- /**
- * @private
- * @static
- * @inheritable
- */
- '$onExtended': [],
- /**
- * @private
- * @static
- * @inheritable
- */
- triggerExtended: function() {
- var callbacks = this.$onExtended,
- ln = callbacks.length,
- i, callback;
- if (ln > 0) {
- for (i = 0; i < ln; i++) {
- callback = callbacks[i];
- callback.fn.apply(callback.scope || this, arguments);
- }
- }
- },
- /**
- * @private
- * @static
- * @inheritable
- */
- onExtended: function(fn, scope) {
- this.$onExtended.push({
- fn: fn,
- scope: scope
- });
- return this;
- },
- /**
- * @private
- * @static
- * @inheritable
- */
- addConfig: function(config, fullMerge) {
- var prototype = this.prototype,
- initConfigList = prototype.initConfigList,
- initConfigMap = prototype.initConfigMap,
- defaultConfig = prototype.defaultConfig,
- hasInitConfigItem, name, value;
- fullMerge = Boolean(fullMerge);
- for (name in config) {
- if (config.hasOwnProperty(name) && (fullMerge || !(name in defaultConfig))) {
- value = config[name];
- hasInitConfigItem = initConfigMap[name];
- if (value !== null) {
- if (!hasInitConfigItem) {
- initConfigMap[name] = true;
- initConfigList.push(name);
- }
- }
- else if (hasInitConfigItem) {
- initConfigMap[name] = false;
- Ext.Array.remove(initConfigList, name);
- }
- }
- }
- if (fullMerge) {
- Ext.merge(defaultConfig, config);
- }
- else {
- Ext.mergeIf(defaultConfig, config);
- }
- prototype.configClass = Ext.Object.classify(defaultConfig);
- },
- /**
- * Add / override static properties of this class.
- *
- * Ext.define('My.cool.Class', {
- * // this.se
- * });
- *
- * My.cool.Class.addStatics({
- * someProperty: 'someValue', // My.cool.Class.someProperty = 'someValue'
- * method1: function() { }, // My.cool.Class.method1 = function() { ... };
- * method2: function() { } // My.cool.Class.method2 = function() { ... };
- * });
- *
- * @param {Object} members
- * @return {Ext.Base} this
- * @static
- * @inheritable
- */
- addStatics: function(members) {
- var member, name;
- //<debug>
- var className = Ext.getClassName(this);
- //</debug>
- for (name in members) {
- if (members.hasOwnProperty(name)) {
- member = members[name];
- //<debug>
- if (typeof member == 'function') {
- member.displayName = className + '.' + name;
- }
- //</debug>
- this[name] = member;
- }
- }
- return this;
- },
- /**
- * @private
- * @static
- * @inheritable
- */
- addInheritableStatics: function(members) {
- var inheritableStatics,
- hasInheritableStatics,
- prototype = this.prototype,
- name, member;
- inheritableStatics = prototype.$inheritableStatics;
- hasInheritableStatics = prototype.$hasInheritableStatics;
- if (!inheritableStatics) {
- inheritableStatics = prototype.$inheritableStatics = [];
- hasInheritableStatics = prototype.$hasInheritableStatics = {};
- }
- //<debug>
- var className = Ext.getClassName(this);
- //</debug>
- for (name in members) {
- if (members.hasOwnProperty(name)) {
- member = members[name];
- //<debug>
- if (typeof member == 'function') {
- member.displayName = className + '.' + name;
- }
- //</debug>
- this[name] = member;
- if (!hasInheritableStatics[name]) {
- hasInheritableStatics[name] = true;
- inheritableStatics.push(name);
- }
- }
- }
- return this;
- },
- /**
- * Add methods / properties to the prototype of this class.
- *
- * @example
- * Ext.define('My.awesome.Cat', {
- * constructor: function() {
- * // ...
- * }
- * });
- *
- * My.awesome.Cat.addMembers({
- * meow: function() {
- * alert('Meowww...');
- * }
- * });
- *
- * var kitty = new My.awesome.Cat();
- * kitty.meow();
- *
- * @param {Object} members
- * @static
- * @inheritable
- */
- addMembers: function(members) {
- var prototype = this.prototype,
- names = [],
- name, member;
- //<debug>
- var className = this.$className || '';
- //</debug>
- for (name in members) {
- if (members.hasOwnProperty(name)) {
- member = members[name];
- if (typeof member == 'function' && !member.$isClass && member !== Ext.emptyFn) {
- member.$owner = this;
- member.$name = name;
- //<debug>
- member.displayName = className + '#' + name;
- //</debug>
- }
- prototype[name] = member;
- }
- }
- return this;
- },
- /**
- * @private
- * @static
- * @inheritable
- */
- addMember: function(name, member) {
- if (typeof member == 'function' && !member.$isClass && member !== Ext.emptyFn) {
- member.$owner = this;
- member.$name = name;
- //<debug>
- member.displayName = (this.$className || '') + '#' + name;
- //</debug>
- }
- this.prototype[name] = member;
- return this;
- },
- /**
- * @private
- * @static
- * @inheritable
- */
- implement: function() {
- this.addMembers.apply(this, arguments);
- },
- /**
- * Borrow another class' members to the prototype of this class.
- *
- * Ext.define('Bank', {
- * money: '$$$',
- * printMoney: function() {
- * alert('$$$$$$$');
- * }
- * });
- *
- * Ext.define('Thief', {
- * // ...
- * });
- *
- * Thief.borrow(Bank, ['money', 'printMoney']);
- *
- * var steve = new Thief();
- *
- * alert(steve.money); // alerts '$$$'
- * steve.printMoney(); // alerts '$$$$$$$'
- *
- * @param {Ext.Base} fromClass The class to borrow members from
- * @param {Array/String} members The names of the members to borrow
- * @return {Ext.Base} this
- * @static
- * @inheritable
- * @private
- */
- borrow: function(fromClass, members) {
- var prototype = this.prototype,
- fromPrototype = fromClass.prototype,
- //<debug>
- className = Ext.getClassName(this),
- //</debug>
- i, ln, name, fn, toBorrow;
- members = Ext.Array.from(members);
- for (i = 0,ln = members.length; i < ln; i++) {
- name = members[i];
- toBorrow = fromPrototype[name];
- if (typeof toBorrow == 'function') {
- fn = function() {
- return toBorrow.apply(this, arguments);
- };
- //<debug>
- if (className) {
- fn.displayName = className + '#' + name;
- }
- //</debug>
- fn.$owner = this;
- fn.$name = name;
- prototype[name] = fn;
- }
- else {
- prototype[name] = toBorrow;
- }
- }
- return this;
- },
- /**
- * Override members of this class. Overridden methods can be invoked via
- * {@link Ext.Base#callParent}.
- *
- * Ext.define('My.Cat', {
- * constructor: function() {
- * alert("I'm a cat!");
- * }
- * });
- *
- * My.Cat.override({
- * constructor: function() {
- * alert("I'm going to be a cat!");
- *
- * var instance = this.callParent(arguments);
- *
- * alert("Meeeeoooowwww");
- *
- * return instance;
- * }
- * });
- *
- * var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
- * // alerts "I'm a cat!"
- * // alerts "Meeeeoooowwww"
- *
- * As of 2.1, direct use of this method is deprecated. Use {@link Ext#define Ext.define}
- * instead:
- *
- * Ext.define('My.CatOverride', {
- * override: 'My.Cat',
- *
- * constructor: function() {
- * alert("I'm going to be a cat!");
- *
- * var instance = this.callParent(arguments);
- *
- * alert("Meeeeoooowwww");
- *
- * return instance;
- * }
- * });
- *
- * The above accomplishes the same result but can be managed by the {@link Ext.Loader}
- * which can properly order the override and its target class and the build process
- * can determine whether the override is needed based on the required state of the
- * target class (My.Cat).
- *
- * @param {Object} members The properties to add to this class. This should be
- * specified as an object literal containing one or more properties.
- * @return {Ext.Base} this class
- * @static
- * @inheritable
- * @deprecated 2.1.0 Please use {@link Ext#define Ext.define} instead
- */
- override: function(members) {
- var me = this,
- enumerables = Ext.enumerables,
- target = me.prototype,
- cloneFunction = Ext.Function.clone,
- name, index, member, statics, names, previous;
- if (arguments.length === 2) {
- name = members;
- members = {};
- members[name] = arguments[1];
- enumerables = null;
- }
- do {
- names = []; // clean slate for prototype (1st pass) and static (2nd pass)
- statics = null; // not needed 1st pass, but needs to be cleared for 2nd pass
- for (name in members) { // hasOwnProperty is checked in the next loop...
- if (name == 'statics') {
- statics = members[name];
- }
- else if (name == 'config') {
- me.addConfig(members[name], true);
- }
- else {
- names.push(name);
- }
- }
- if (enumerables) {
- names.push.apply(names, enumerables);
- }
- for (index = names.length; index--; ) {
- name = names[index];
- if (members.hasOwnProperty(name)) {
- member = members[name];
- if (typeof member == 'function' && !member.$className && member !== Ext.emptyFn) {
- if (typeof member.$owner != 'undefined') {
- member = cloneFunction(member);
- }
- //<debug>
- var className = me.$className;
- if (className) {
- member.displayName = className + '#' + name;
- }
- //</debug>
- member.$owner = me;
- member.$name = name;
- previous = target[name];
- if (previous) {
- member.$previous = previous;
- }
- }
- target[name] = member;
- }
- }
- target = me; // 2nd pass is for statics
- members = statics; // statics will be null on 2nd pass
- } while (members);
- return this;
- },
- /**
- * @protected
- * @static
- * @inheritable
- */
- callParent: function(args) {
- var method;
- // This code is intentionally inlined for the least amount of debugger stepping
- return (method = this.callParent.caller) && (method.$previous ||
- ((method = method.$owner ? method : method.caller) &&
- method.$owner.superclass.$class[method.$name])).apply(this, args || noArgs);
- },
- //<feature classSystem.mixins>
- /**
- * Used internally by the mixins pre-processor
- * @private
- * @static
- * @inheritable
- */
- mixin: function(name, mixinClass) {
- var mixin = mixinClass.prototype,
- prototype = this.prototype,
- key;
- if (typeof mixin.onClassMixedIn != 'undefined') {
- mixin.onClassMixedIn.call(mixinClass, this);
- }
- if (!prototype.hasOwnProperty('mixins')) {
- if ('mixins' in prototype) {
- prototype.mixins = Ext.Object.chain(prototype.mixins);
- }
- else {
- prototype.mixins = {};
- }
- }
- for (key in mixin) {
- if (key === 'mixins') {
- Ext.merge(prototype.mixins, mixin[key]);
- }
- else if (typeof prototype[key] == 'undefined' && key != 'mixinId' && key != 'config') {
- prototype[key] = mixin[key];
- }
- }
- //<feature classSystem.config>
- if ('config' in mixin) {
- this.addConfig(mixin.config, false);
- }
- //</feature>
- prototype.mixins[name] = mixin;
- },
- //</feature>
- /**
- * Get the current class' name in string format.
- *
- * Ext.define('My.cool.Class', {
- * constructor: function() {
- * alert(this.self.getName()); // alerts 'My.cool.Class'
- * }
- * });
- *
- * My.cool.Class.getName(); // 'My.cool.Class'
- *
- * @return {String} className
- * @static
- * @inheritable
- */
- getName: function() {
- return Ext.getClassName(this);
- },
- /**
- * Create aliases for existing prototype methods. Example:
- *
- * Ext.define('My.cool.Class', {
- * method1: function() { },
- * method2: function() { }
- * });
- *
- * var test = new My.cool.Class();
- *
- * My.cool.Class.createAlias({
- * method3: 'method1',
- * method4: 'method2'
- * });
- *
- * test.method3(); // test.method1()
- *
- * My.cool.Class.createAlias('method5', 'method3');
- *
- * test.method5(); // test.method3() -> test.method1()
- *
- * @param {String/Object} alias The new method name, or an object to set multiple aliases. See
- * {@link Ext.Function#flexSetter flexSetter}
- * @param {String/Object} origin The original method name
- * @static
- * @inheritable
- * @method
- */
- createAlias: flexSetter(function(alias, origin) {
- this.override(alias, function() {
- return this[origin].apply(this, arguments);
- });
- }),
- /**
- * @private
- * @static
- * @inheritable
- */
- addXtype: function(xtype) {
- var prototype = this.prototype,
- xtypesMap = prototype.xtypesMap,
- xtypes = prototype.xtypes,
- xtypesChain = prototype.xtypesChain;
- if (!prototype.hasOwnProperty('xtypesMap')) {
- xtypesMap = prototype.xtypesMap = Ext.merge({}, prototype.xtypesMap || {});
- xtypes = prototype.xtypes = prototype.xtypes ? [].concat(prototype.xtypes) : [];
- xtypesChain = prototype.xtypesChain = prototype.xtypesChain ? [].concat(prototype.xtypesChain) : [];
- prototype.xtype = xtype;
- }
- if (!xtypesMap[xtype]) {
- xtypesMap[xtype] = true;
- xtypes.push(xtype);
- xtypesChain.push(xtype);
- Ext.ClassManager.setAlias(this, 'widget.' + xtype);
- }
- return this;
- }
- });
- Base.implement({
- isInstance: true,
- $className: 'Ext.Base',
- configClass: Ext.emptyFn,
- initConfigList: [],
- initConfigMap: {},
- /**
- * Get the reference to the class from which this object was instantiated. Note that unlike {@link Ext.Base#self},
- * `this.statics()` is scope-independent and it always returns the class from which it was called, regardless of what
- * `this` points to during run-time
- *
- * Ext.define('My.Cat', {
- * statics: {
- * totalCreated: 0,
- * speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
- * },
- *
- * constructor: function() {
- * var statics = this.statics();
- *
- * alert(statics.speciesName); // always equals to 'Cat' no matter what 'this' refers to
- * // equivalent to: My.Cat.speciesName
- *
- * alert(this.self.speciesName); // dependent on 'this'
- *
- * statics.totalCreated++;
- * },
- *
- * clone: function() {
- * var cloned = new this.self(); // dependent on 'this'
- *
- * cloned.groupName = this.statics().speciesName; // equivalent to: My.Cat.speciesName
- *
- * return cloned;
- * }
- * });
- *
- *
- * Ext.define('My.SnowLeopard', {
- * extend: 'My.Cat',
- *
- * statics: {
- * speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
- * },
- *
- * constructor: function() {
- * this.callParent();
- * }
- * });
- *
- * var cat = new My.Cat(); // alerts 'Cat', then alerts 'Cat'
- *
- * var snowLeopard = new My.SnowLeopard(); // alerts 'Cat', then alerts 'Snow Leopard'
- *
- * var clone = snowLeopard.clone();
- * alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
- * alert(clone.groupName); // alerts 'Cat'
- *
- * alert(My.Cat.totalCreated); // alerts 3
- *
- * @protected
- * @return {Ext.Class}
- */
- statics: function() {
- var method = this.statics.caller,
- self = this.self;
- if (!method) {
- return self;
- }
- return method.$owner;
- },
- /**
- * Call the "parent" method of the current method. That is the method previously
- * overridden by derivation or by an override (see {@link Ext#define}).
- *
- * Ext.define('My.Base', {
- * constructor: function (x) {
- * this.x = x;
- * },
- *
- * statics: {
- * method: function (x) {
- * return x;
- * }
- * }
- * });
- *
- * Ext.define('My.Derived', {
- * extend: 'My.Base',
- *
- * constructor: function () {
- * this.callParent([21]);
- * }
- * });
- *
- * var obj = new My.Derived();
- *
- * alert(obj.x); // alerts 21
- *
- * This can be used with an override as follows:
- *
- * Ext.define('My.DerivedOverride', {
- * override: 'My.Derived',
- *
- * constructor: function (x) {
- * this.callParent([x*2]); // calls original My.Derived constructor
- * }
- * });
- *
- * var obj = new My.Derived();
- *
- * alert(obj.x); // now alerts 42
- *
- * This also works with static methods.
- *
- * Ext.define('My.Derived2', {
- * extend: 'My.Base',
- *
- * statics: {
- * method: function (x) {
- * return this.callParent([x*2]); // calls My.Base.method
- * }
- * }
- * });
- *
- * alert(My.Base.method(10)); // alerts 10
- * alert(My.Derived2.method(10)); // alerts 20
- *
- * Lastly, it also works with overridden static methods.
- *
- * Ext.define('My.Derived2Override', {
- * override: 'My.Derived2',
- *
- * statics: {
- * method: function (x) {
- * return this.callParent([x*2]); // calls My.Derived2.method
- * }
- * }
- * });
- *
- * alert(My.Derived2.method(10)); // now alerts 40
- *
- * To override a method and replace it and also call the superclass method, use
- * {@link #callSuper}. This is often done to patch a method to fix a bug.
- *
- * @protected
- * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
- * from the current method, for example: `this.callParent(arguments)`
- * @return {Object} Returns the result of calling the parent method
- */
- callParent: function(args) {
- // NOTE: this code is deliberately as few expressions (and no function calls)
- // as possible so that a debugger can skip over this noise with the minimum number
- // of steps. Basically, just hit Step Into until you are where you really wanted
- // to be.
- var method,
- superMethod = (method = this.callParent.caller) && (method.$previous ||
- ((method = method.$owner ? method : method.caller) &&
- method.$owner.superclass[method.$name]));
- //<debug error>
- if (!superMethod) {
- method = this.callParent.caller;
- var parentClass, methodName;
- if (!method.$owner) {
- if (!method.caller) {
- throw new Error("Attempting to call a protected method from the public scope, which is not allowed");
- }
- method = method.caller;
- }
- parentClass = method.$owner.superclass;
- methodName = method.$name;
- if (!(methodName in parentClass)) {
- throw new Error("this.callParent() was called but there's no such method (" + methodName +
- ") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")");
- }
- }
- //</debug>
- return superMethod.apply(this, args || noArgs);
- },
- /**
- * This method is used by an override to call the superclass method but bypass any
- * overridden method. This is often done to "patch" a method that contains a bug
- * but for whatever reason cannot be fixed directly.
- *
- * Consider:
- *
- * Ext.define('Ext.some.Class', {
- * method: function () {
- * console.log('Good');
- * }
- * });
- *
- * Ext.define('Ext.some.DerivedClass', {
- * method: function () {
- * console.log('Bad');
- *
- * // ... logic but with a bug ...
- *
- * this.callParent();
- * }
- * });
- *
- * To patch the bug in `DerivedClass.method`, the typical solution is to create an
- * override:
- *
- * Ext.define('App.paches.DerivedClass', {
- * override: 'Ext.some.DerivedClass',
- *
- * method: function () {
- * console.log('Fixed');
- *
- * // ... logic but with bug fixed ...
- *
- * this.callSuper();
- * }
- * });
- *
- * The patch method cannot use `callParent` to call the superclass `method` since
- * that would call the overridden method containing the bug. In other words, the
- * above patch would only produce "Fixed" then "Good" in the console log, whereas,
- * using `callParent` would produce "Fixed" then "Bad" then "Good".
- *
- * @protected
- * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
- * from the current method, for example: `this.callSuper(arguments)`
- * @return {Object} Returns the result of calling the superclass method
- */
- callSuper: function(args) {
- var method,
- superMethod = (method = this.callSuper.caller) && ((method = method.$owner ? method : method.caller) &&
- method.$owner.superclass[method.$name]);
- //<debug error>
- if (!superMethod) {
- method = this.callSuper.caller;
- var parentClass, methodName;
- if (!method.$owner) {
- if (!method.caller) {
- throw new Error("Attempting to call a protected method from the public scope, which is not allowed");
- }
- method = method.caller;
- }
- parentClass = method.$owner.superclass;
- methodName = method.$name;
- if (!(methodName in parentClass)) {
- throw new Error("this.callSuper() was called but there's no such method (" + methodName +
- ") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")");
- }
- }
- //</debug>
- return superMethod.apply(this, args || noArgs);
- },
- /**
- * Call the original method that was previously overridden with {@link Ext.Base#override},
- *
- * This method is deprecated as {@link #callParent} does the same thing.
- *
- * Ext.define('My.Cat', {
- * constructor: function() {
- * alert("I'm a cat!");
- * }
- * });
- *
- * My.Cat.override({
- * constructor: function() {
- * alert("I'm going to be a cat!");
- *
- * var instance = this.callOverridden();
- *
- * alert("Meeeeoooowwww");
- *
- * return instance;
- * }
- * });
- *
- * var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
- * // alerts "I'm a cat!"
- * // alerts "Meeeeoooowwww"
- *
- * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
- * from the current method, for example: `this.callOverridden(arguments)`
- * @return {Object} Returns the result of calling the overridden method
- * @protected
- * @deprecated Use callParent instead
- */
- callOverridden: function(args) {
- var method;
- return (method = this.callOverridden.caller) && method.$previous.apply(this, args || noArgs);
- },
- /**
- * @property {Ext.Class} self
- *
- * Get the reference to the current class from which this object was instantiated. Unlike {@link Ext.Base#statics},
- * `this.self` is scope-dependent and it's meant to be used for dynamic inheritance. See {@link Ext.Base#statics}
- * for a detailed comparison
- *
- * Ext.define('My.Cat', {
- * statics: {
- * speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
- * },
- *
- * constructor: function() {
- * alert(this.self.speciesName); // dependent on 'this'
- * },
- *
- * clone: function() {
- * return new this.self();
- * }
- * });
- *
- *
- * Ext.define('My.SnowLeopard', {
- * extend: 'My.Cat',
- * statics: {
- * speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
- * }
- * });
- *
- * var cat = new My.Cat(); // alerts 'Cat'
- * var snowLeopard = new My.SnowLeopard(); // alerts 'Snow Leopard'
- *
- * var clone = snowLeopard.clone();
- * alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
- *
- * @protected
- */
- self: Base,
- // Default constructor, simply returns `this`
- constructor: function() {
- return this;
- },
- //<feature classSystem.config>
- wasInstantiated: false,
- /**
- * Initialize configuration for this class. a typical example:
- *
- * Ext.define('My.awesome.Class', {
- * // The default config
- * config: {
- * name: 'Awesome',
- * isAwesome: true
- * },
- *
- * constructor: function(config) {
- * this.initConfig(config);
- * }
- * });
- *
- * var awesome = new My.awesome.Class({
- * name: 'Super Awesome'
- * });
- *
- * alert(awesome.getName()); // 'Super Awesome'
- *
- * @protected
- * @param {Object} instanceConfig
- * @return {Object} mixins The mixin prototypes as key - value pairs
- */
- initConfig: function(instanceConfig) {
- //<debug>
- // if (instanceConfig && instanceConfig.breakOnInitConfig) {
- // debugger;
- // }
- //</debug>
- var configNameCache = Ext.Class.configNameCache,
- prototype = this.self.prototype,
- initConfigList = this.initConfigList,
- initConfigMap = this.initConfigMap,
- config = new this.configClass,
- defaultConfig = this.defaultConfig,
- i, ln, name, value, nameMap, getName;
- this.initConfig = Ext.emptyFn;
- this.initialConfig = instanceConfig || {};
- if (instanceConfig) {
- Ext.merge(config, instanceConfig);
- }
- this.config = config;
- // Optimize initConfigList *once* per class based on the existence of apply* and update* methods
- // Happens only once during the first instantiation
- if (!prototype.hasOwnProperty('wasInstantiated')) {
- prototype.wasInstantiated = true;
- for (i = 0,ln = initConfigList.length; i < ln; i++) {
- name = initConfigList[i];
- nameMap = configNameCache[name];
- value = defaultConfig[name];
- if (!(nameMap.apply in prototype)
- && !(nameMap.update in prototype)
- && prototype[nameMap.set].$isDefault
- && typeof value != 'object') {
- prototype[nameMap.internal] = defaultConfig[name];
- initConfigMap[name] = false;
- Ext.Array.remove(initConfigList, name);
- i--;
- ln--;
- }
- }
- }
- if (instanceConfig) {
- initConfigList = initConfigList.slice();
- for (name in instanceConfig) {
- if (name in defaultConfig && !initConfigMap[name]) {
- initConfigList.push(name);
- }
- }
- }
- // Point all getters to the initGetters
- for (i = 0,ln = initConfigList.length; i < ln; i++) {
- name = initConfigList[i];
- nameMap = configNameCache[name];
- this[nameMap.get] = this[nameMap.initGet];
- }
- this.beforeInitConfig(config);
- for (i = 0,ln = initConfigList.length; i < ln; i++) {
- name = initConfigList[i];
- nameMap = configNameCache[name];
- getName = nameMap.get;
- if (this.hasOwnProperty(getName)) {
- this[nameMap.set].call(this, config[name]);
- delete this[getName];
- }
- }
- return this;
- },
- beforeInitConfig: Ext.emptyFn,
- /**
- * @private
- */
- getCurrentConfig: function() {
- var defaultConfig = this.defaultConfig,
- configNameCache = Ext.Class.configNameCache,
- config = {},
- name, nameMap;
- for (name in defaultConfig) {
- nameMap = configNameCache[name];
- config[name] = this[nameMap.get].call(this);
- }
- return config;
- },
- /**
- * @private
- */
- setConfig: function(config, applyIfNotSet) {
- if (!config) {
- return this;
- }
- var configNameCache = Ext.Class.configNameCache,
- currentConfig = this.config,
- defaultConfig = this.defaultConfig,
- initialConfig = this.initialConfig,
- configList = [],
- name, i, ln, nameMap;
- applyIfNotSet = Boolean(applyIfNotSet);
- for (name in config) {
- if ((applyIfNotSet && (name in initialConfig))) {
- continue;
- }
- currentConfig[name] = config[name];
- if (name in defaultConfig) {
- configList.push(name);
- nameMap = configNameCache[name];
- this[nameMap.get] = this[nameMap.initGet];
- }
- }
- for (i = 0,ln = configList.length; i < ln; i++) {
- name = configList[i];
- nameMap = configNameCache[name];
- this[nameMap.set].call(this, config[name]);
- delete this[nameMap.get];
- }
- return this;
- },
- set: function(name, value) {
- return this[Ext.Class.configNameCache[name].set].call(this, value);
- },
- get: function(name) {
- return this[Ext.Class.configNameCache[name].get].call(this);
- },
- /**
- * @private
- */
- getConfig: function(name) {
- return this[Ext.Class.configNameCache[name].get].call(this);
- },
- /**
- * @private
- */
- hasConfig: function(name) {
- return (name in this.defaultConfig);
- },
- /**
- * Returns the initial configuration passed to constructor.
- *
- * @param {String} [name] When supplied, value for particular configuration
- * option is returned, otherwise the full config object is returned.
- * @return {Object/Mixed}
- */
- getInitialConfig: function(name) {
- var config = this.config;
- if (!name) {
- return config;
- }
- else {
- return config[name];
- }
- },
- /**
- * @private
- */
- onConfigUpdate: function(names, callback, scope) {
- var self = this.self,
- //<debug>
- className = self.$className,
- //</debug>
- i, ln, name,
- updaterName, updater, newUpdater;
- names = Ext.Array.from(names);
- scope = scope || this;
- for (i = 0,ln = names.length; i < ln; i++) {
- name = names[i];
- updaterName = 'update' + Ext.String.capitalize(name);
- updater = this[updaterName] || Ext.emptyFn;
- newUpdater = function() {
- updater.apply(this, arguments);
- scope[callback].apply(scope, arguments);
- };
- newUpdater.$name = updaterName;
- newUpdater.$owner = self;
- //<debug>
- newUpdater.displayName = className + '#' + updaterName;
- //</debug>
- this[updaterName] = newUpdater;
- }
- },
- //</feature>
- /**
- * @private
- * @param name
- * @param value
- * @return {Mixed}
- */
- link: function(name, value) {
- this.$links = {};
- this.link = this.doLink;
- return this.link.apply(this, arguments);
- },
- doLink: function(name, value) {
- this.$links[name] = true;
- this[name] = value;
- return value;
- },
- /**
- * @private
- */
- unlink: function() {
- var i, ln, link, value;
- for (i = 0, ln = arguments.length; i < ln; i++) {
- link = arguments[i];
- if (this.hasOwnProperty(link)) {
- value = this[link];
- if (value) {
- if (value.isInstance && !value.isDestroyed) {
- value.destroy();
- }
- else if (value.parentNode && 'nodeType' in value) {
- value.parentNode.removeChild(value);
- }
- }
- delete this[link];
- }
- }
- return this;
- },
- /**
- * @protected
- */
- destroy: function() {
- this.destroy = Ext.emptyFn;
- this.isDestroyed = true;
- if (this.hasOwnProperty('$links')) {
- this.unlink.apply(this, Ext.Object.getKeys(this.$links));
- delete this.$links;
- }
- }
- });
- Ext.Base = Base;
- })(Ext.Function.flexSetter);
- //@tag foundation,core
- //@define Ext.Class
- //@require Ext.Base
- /**
- * @class Ext.Class
- *
- * @author Jacky Nguyen <jacky@sencha.com>
- * @aside guide class_system
- * @aside video class-system
- *
- * Handles class creation throughout the framework. This is a low level factory that is used by Ext.ClassManager and generally
- * should not be used directly. If you choose to use Ext.Class you will lose out on the namespace, aliasing and dependency loading
- * features made available by Ext.ClassManager. The only time you would use Ext.Class directly is to create an anonymous class.
- *
- * If you wish to create a class you should use {@link Ext#define Ext.define} which aliases
- * {@link Ext.ClassManager#create Ext.ClassManager.create} to enable namespacing and dynamic dependency resolution.
- *
- * Ext.Class is the factory and **not** the superclass of everything. For the base class that **all** Ext classes inherit
- * from, see {@link Ext.Base}.
- */
- (function() {
- var ExtClass,
- Base = Ext.Base,
- baseStaticMembers = [],
- baseStaticMember, baseStaticMemberLength;
- for (baseStaticMember in Base) {
- if (Base.hasOwnProperty(baseStaticMember)) {
- baseStaticMembers.push(baseStaticMember);
- }
- }
- baseStaticMemberLength = baseStaticMembers.length;
- /**
- * @method constructor
- * Creates a new anonymous class.
- *
- * @param {Object} data An object represent the properties of this class.
- * @param {Function} onCreated (optional) The callback function to be executed when this class is fully created.
- * Note that the creation process can be asynchronous depending on the pre-processors used.
- *
- * @return {Ext.Base} The newly created class
- */
- Ext.Class = ExtClass = function(Class, data, onCreated) {
- if (typeof Class != 'function') {
- onCreated = data;
- data = Class;
- Class = null;
- }
- if (!data) {
- data = {};
- }
- Class = ExtClass.create(Class);
- ExtClass.process(Class, data, onCreated);
- return Class;
- };
- Ext.apply(ExtClass, {
- /**
- * @private
- * @static
- */
- onBeforeCreated: function(Class, data, hooks) {
- Class.addMembers(data);
- hooks.onCreated.call(Class, Class);
- },
- /**
- * @private
- * @static
- */
- create: function(Class) {
- var name, i;
- if (!Class) {
- Class = function() {
- return this.constructor.apply(this, arguments);
- };
- }
- for (i = 0; i < baseStaticMemberLength; i++) {
- name = baseStaticMembers[i];
- Class[name] = Base[name];
- }
- return Class;
- },
- /**
- * @private
- * @static
- */
- process: function(Class, data, onCreated) {
- var preprocessorStack = data.preprocessors || ExtClass.defaultPreprocessors,
- preprocessors = this.preprocessors,
- hooks = {
- onBeforeCreated: this.onBeforeCreated,
- onCreated: onCreated || Ext.emptyFn
- },
- index = 0,
- name, preprocessor, properties,
- i, ln, fn, property, process;
- delete data.preprocessors;
- process = function(Class, data, hooks) {
- fn = null;
- while (fn === null) {
- name = preprocessorStack[index++];
- if (name) {
- preprocessor = preprocessors[name];
- properties = preprocessor.properties;
- if (properties === true) {
- fn = preprocessor.fn;
- }
- else {
- for (i = 0,ln = properties.length; i < ln; i++) {
- property = properties[i];
- if (data.hasOwnProperty(property)) {
- fn = preprocessor.fn;
- break;
- }
- }
- }
- }
- else {
- hooks.onBeforeCreated.apply(this, arguments);
- return;
- }
- }
- if (fn.call(this, Class, data, hooks, process) !== false) {
- process.apply(this, arguments);
- }
- };
- process.call(this, Class, data, hooks);
- },
- /**
- * @private
- * @static
- */
- preprocessors: {},
- /**
- * Register a new pre-processor to be used during the class creation process.
- *
- * @private
- * @static
- * @param {String} name The pre-processor's name.
- * @param {Function} fn The callback function to be executed. Typical format:
- *
- * function(cls, data, fn) {
- * // Your code here
- *
- * // Execute this when the processing is finished.
- * // Asynchronous processing is perfectly OK
- * if (fn) {
- * fn.call(this, cls, data);
- * }
- * });
- *
- * @param {Function} fn.cls The created class.
- * @param {Object} fn.data The set of properties passed in {@link Ext.Class} constructor.
- * @param {Function} fn.fn The callback function that __must__ to be executed when this pre-processor finishes,
- * regardless of whether the processing is synchronous or asynchronous.
- *
- * @return {Ext.Class} this
- */
- registerPreprocessor: function(name, fn, properties, position, relativeTo) {
- if (!position) {
- position = 'last';
- }
- if (!properties) {
- properties = [name];
- }
- this.preprocessors[name] = {
- name: name,
- properties: properties || false,
- fn: fn
- };
- this.setDefaultPreprocessorPosition(name, position, relativeTo);
- return this;
- },
- /**
- * Retrieve a pre-processor callback function by its name, which has been registered before.
- *
- * @private
- * @static
- * @param {String} name
- * @return {Function} preprocessor
- */
- getPreprocessor: function(name) {
- return this.preprocessors[name];
- },
- /**
- * @private
- * @static
- */
- getPreprocessors: function() {
- return this.preprocessors;
- },
- /**
- * @private
- * @static
- */
- defaultPreprocessors: [],
- /**
- * Retrieve the array stack of default pre-processors.
- * @private
- * @static
- * @return {Function} defaultPreprocessors
- */
- getDefaultPreprocessors: function() {
- return this.defaultPreprocessors;
- },
- /**
- * Set the default array stack of default pre-processors.
- *
- * @private
- * @static
- * @param {Array} preprocessors
- * @return {Ext.Class} this
- */
- setDefaultPreprocessors: function(preprocessors) {
- this.defaultPreprocessors = Ext.Array.from(preprocessors);
- return this;
- },
- /**
- * Insert this pre-processor at a specific position in the stack, optionally relative to
- * any existing pre-processor. For example:
- *
- * Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
- * // Your code here
- *
- * if (fn) {
- * fn.call(this, cls, data);
- * }
- * }).insertDefaultPreprocessor('debug', 'last');
- *
- * @private
- * @static
- * @param {String} name The pre-processor name. Note that it needs to be registered with
- * {@link Ext.Class#registerPreprocessor registerPreprocessor} before this.
- * @param {String} offset The insertion position. Four possible values are:
- * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument).
- * @param {String} relativeName
- * @return {Ext.Class} this
- */
- setDefaultPreprocessorPosition: function(name, offset, relativeName) {
- var defaultPreprocessors = this.defaultPreprocessors,
- index;
- if (typeof offset == 'string') {
- if (offset === 'first') {
- defaultPreprocessors.unshift(name);
- return this;
- }
- else if (offset === 'last') {
- defaultPreprocessors.push(name);
- return this;
- }
- offset = (offset === 'after') ? 1 : -1;
- }
- index = Ext.Array.indexOf(defaultPreprocessors, relativeName);
- if (index !== -1) {
- Ext.Array.splice(defaultPreprocessors, Math.max(0, index + offset), 0, name);
- }
- return this;
- },
- /**
- * @private
- * @static
- */
- configNameCache: {},
- /**
- * @private
- * @static
- */
- getConfigNameMap: function(name) {
- var cache = this.configNameCache,
- map = cache[name],
- capitalizedName;
- if (!map) {
- capitalizedName = name.charAt(0).toUpperCase() + name.substr(1);
- map = cache[name] = {
- name: name,
- internal: '_' + name,
- initializing: 'is' + capitalizedName + 'Initializing',
- apply: 'apply' + capitalizedName,
- update: 'update' + capitalizedName,
- set: 'set' + capitalizedName,
- get: 'get' + capitalizedName,
- initGet: 'initGet' + capitalizedName,
- doSet : 'doSet' + capitalizedName,
- changeEvent: name.toLowerCase() + 'change'
- }
- }
- return map;
- },
- /**
- * @private
- * @static
- */
- generateSetter: function(nameMap) {
- var internalName = nameMap.internal,
- getName = nameMap.get,
- applyName = nameMap.apply,
- updateName = nameMap.update,
- setter;
- setter = function(value) {
- var oldValue = this[internalName],
- applier = this[applyName],
- updater = this[updateName];
- delete this[getName];
- if (applier) {
- value = applier.call(this, value, oldValue);
- }
- if (typeof value != 'undefined') {
- this[internalName] = value;
- if (updater && value !== oldValue) {
- updater.call(this, value, oldValue);
- }
- }
- return this;
- };
- setter.$isDefault = true;
- return setter;
- },
- /**
- * @private
- * @static
- */
- generateInitGetter: function(nameMap) {
- var name = nameMap.name,
- setName = nameMap.set,
- getName = nameMap.get,
- initializingName = nameMap.initializing;
- return function() {
- this[initializingName] = true;
- delete this[getName];
- this[setName].call(this, this.config[name]);
- delete this[initializingName];
- return this[getName].apply(this, arguments);
- }
- },
- /**
- * @private
- * @static
- */
- generateGetter: function(nameMap) {
- var internalName = nameMap.internal;
- return function() {
- return this[internalName];
- }
- }
- });
- /**
- * @cfg {String} extend
- * The parent class that this class extends. For example:
- *
- * @example
- * Ext.define('Person', {
- * say: function(text) {
- * alert(text);
- * }
- * });
- *
- * Ext.define('Developer', {
- * extend: 'Person',
- * say: function(text) {
- * this.callParent(["print " + text]);
- * }
- * });
- *
- * var person1 = Ext.create("Person");
- * person1.say("Bill");
- *
- * var developer1 = Ext.create("Developer");
- * developer1.say("Ted");
- */
- ExtClass.registerPreprocessor('extend', function(Class, data) {
- var Base = Ext.Base,
- extend = data.extend,
- Parent;
- delete data.extend;
- if (extend && extend !== Object) {
- Parent = extend;
- }
- else {
- Parent = Base;
- }
- Class.extend(Parent);
- Class.triggerExtended.apply(Class, arguments);
- if (data.onClassExtended) {
- Class.onExtended(data.onClassExtended, Class);
- delete data.onClassExtended;
- }
- }, true);
- //<feature classSystem.statics>
- /**
- * @cfg {Object} statics
- * List of static methods for this class. For example:
- *
- * Ext.define('Computer', {
- * statics: {
- * factory: function(brand) {
- * // 'this' in static methods refer to the class itself
- * return new this(brand);
- * }
- * },
- *
- * constructor: function() {
- * // ...
- * }
- * });
- *
- * var dellComputer = Computer.factory('Dell');
- */
- ExtClass.registerPreprocessor('statics', function(Class, data) {
- Class.addStatics(data.statics);
- delete data.statics;
- });
- //</feature>
- //<feature classSystem.inheritableStatics>
- /**
- * @cfg {Object} inheritableStatics
- * List of inheritable static methods for this class.
- * Otherwise just like {@link #statics} but subclasses inherit these methods.
- */
- ExtClass.registerPreprocessor('inheritableStatics', function(Class, data) {
- Class.addInheritableStatics(data.inheritableStatics);
- delete data.inheritableStatics;
- });
- //</feature>
- //<feature classSystem.config>
- /**
- * @cfg {Object} config
- *
- * List of configuration options with their default values.
- *
- * __Note:__ You need to make sure {@link Ext.Base#initConfig} is called from your constructor if you are defining
- * your own class or singleton, unless you are extending a Component. Otherwise the generated getter and setter
- * methods will not be initialized.
- *
- * Each config item will have its own setter and getter method automatically generated inside the class prototype
- * during class creation time, if the class does not have those methods explicitly defined.
- *
- * As an example, let's convert the name property of a Person class to be a config item, then add extra age and
- * gender items.
- *
- * Ext.define('My.sample.Person', {
- * config: {
- * name: 'Mr. Unknown',
- * age: 0,
- * gender: 'Male'
- * },
- *
- * constructor: function(config) {
- * this.initConfig(config);
- *
- * return this;
- * }
- *
- * // ...
- * });
- *
- * Within the class, this.name still has the default value of "Mr. Unknown". However, it's now publicly accessible
- * without sacrificing encapsulation, via setter and getter methods.
- *
- * var jacky = new Person({
- * name: "Jacky",
- * age: 35
- * });
- *
- * alert(jacky.getAge()); // alerts 35
- * alert(jacky.getGender()); // alerts "Male"
- *
- * jacky.walk(10); // alerts "Jacky is walking 10 steps"
- *
- * jacky.setName("Mr. Nguyen");
- * alert(jacky.getName()); // alerts "Mr. Nguyen"
- *
- * jacky.walk(10); // alerts "Mr. Nguyen is walking 10 steps"
- *
- * Notice that we changed the class constructor to invoke this.initConfig() and pass in the provided config object.
- * Two key things happened:
- *
- * - The provided config object when the class is instantiated is recursively merged with the default config object.
- * - All corresponding setter methods are called with the merged values.
- *
- * Beside storing the given values, throughout the frameworks, setters generally have two key responsibilities:
- *
- * - Filtering / validation / transformation of the given value before it's actually stored within the instance.
- * - Notification (such as firing events) / post-processing after the value has been set, or changed from a
- * previous value.
- *
- * By standardize this common pattern, the default generated setters provide two extra template methods that you
- * can put your own custom logics into, i.e: an "applyFoo" and "updateFoo" method for a "foo" config item, which are
- * executed before and after the value is actually set, respectively. Back to the example class, let's validate that
- * age must be a valid positive number, and fire an 'agechange' if the value is modified.
- *
- * Ext.define('My.sample.Person', {
- * config: {
- * // ...
- * },
- *
- * constructor: {
- * // ...
- * },
- *
- * applyAge: function(age) {
- * if (typeof age !== 'number' || age < 0) {
- * console.warn("Invalid age, must be a positive number");
- * return;
- * }
- *
- * return age;
- * },
- *
- * updateAge: function(newAge, oldAge) {
- * // age has changed from "oldAge" to "newAge"
- * this.fireEvent('agechange', this, newAge, oldAge);
- * }
- *
- * // ...
- * });
- *
- * var jacky = new Person({
- * name: "Jacky",
- * age: 'invalid'
- * });
- *
- * alert(jacky.getAge()); // alerts 0
- *
- * alert(jacky.setAge(-100)); // alerts 0
- * alert(jacky.getAge()); // alerts 0
- *
- * alert(jacky.setAge(35)); // alerts 0
- * alert(jacky.getAge()); // alerts 35
- *
- * In other words, when leveraging the config feature, you mostly never need to define setter and getter methods
- * explicitly. Instead, "apply*" and "update*" methods should be implemented where necessary. Your code will be
- * consistent throughout and only contain the minimal logic that you actually care about.
- *
- * When it comes to inheritance, the default config of the parent class is automatically, recursively merged with
- * the child's default config. The same applies for mixins.
- */
- ExtClass.registerPreprocessor('config', function(Class, data) {
- var config = data.config,
- prototype = Class.prototype,
- defaultConfig = prototype.config,
- nameMap, name, setName, getName, initGetName, internalName, value;
- delete data.config;
- for (name in config) {
- // Once per config item, per class hierarchy
- if (config.hasOwnProperty(name) && !(name in defaultConfig)) {
- value = config[name];
- nameMap = this.getConfigNameMap(name);
- setName = nameMap.set;
- getName = nameMap.get;
- initGetName = nameMap.initGet;
- internalName = nameMap.internal;
- data[initGetName] = this.generateInitGetter(nameMap);
- if (value === null && !data.hasOwnProperty(internalName)) {
- data[internalName] = null;
- }
- if (!data.hasOwnProperty(getName)) {
- data[getName] = this.generateGetter(nameMap);
- }
- if (!data.hasOwnProperty(setName)) {
- data[setName] = this.generateSetter(nameMap);
- }
- }
- }
- Class.addConfig(config, true);
- });
- //</feature>
- //<feature classSystem.mixins>
- /**
- * @cfg {Object} mixins
- * List of classes to mix into this class. For example:
- *
- * Ext.define('CanSing', {
- * sing: function() {
- * alert("I'm on the highway to hell...");
- * }
- * });
- *
- * Ext.define('Musician', {
- * extend: 'Person',
- *
- * mixins: {
- * canSing: 'CanSing'
- * }
- * });
- */
- ExtClass.registerPreprocessor('mixins', function(Class, data, hooks) {
- var mixins = data.mixins,
- name, mixin, i, ln;
- delete data.mixins;
- Ext.Function.interceptBefore(hooks, 'onCreated', function() {
- if (mixins instanceof Array) {
- for (i = 0,ln = mixins.length; i < ln; i++) {
- mixin = mixins[i];
- name = mixin.prototype.mixinId || mixin.$className;
- Class.mixin(name, mixin);
- }
- }
- else {
- for (name in mixins) {
- if (mixins.hasOwnProperty(name)) {
- Class.mixin(name, mixins[name]);
- }
- }
- }
- });
- });
- //</feature>
- //<feature classSystem.backwardsCompatible>
- // Backwards compatible
- Ext.extend = function(Class, Parent, members) {
- if (arguments.length === 2 && Ext.isObject(Parent)) {
- members = Parent;
- Parent = Class;
- Class = null;
- }
- var cls;
- if (!Parent) {
- throw new Error("[Ext.extend] Attempting to extend from a class which has not been loaded on the page.");
- }
- members.extend = Parent;
- members.preprocessors = [
- 'extend'
- //<feature classSystem.statics>
- ,'statics'
- //</feature>
- //<feature classSystem.inheritableStatics>
- ,'inheritableStatics'
- //</feature>
- //<feature classSystem.mixins>
- ,'mixins'
- //</feature>
- //<feature classSystem.config>
- ,'config'
- //</feature>
- ];
- if (Class) {
- cls = new ExtClass(Class, members);
- }
- else {
- cls = new ExtClass(members);
- }
- cls.prototype.override = function(o) {
- for (var m in o) {
- if (o.hasOwnProperty(m)) {
- this[m] = o[m];
- }
- }
- };
- return cls;
- };
- //</feature>
- })();
- //@tag foundation,core
- //@define Ext.ClassManager
- //@require Ext.Class
- /**
- * @class Ext.ClassManager
- *
- * @author Jacky Nguyen <jacky@sencha.com>
- * @aside guide class_system
- * @aside video class-system
- *
- * Ext.ClassManager manages all classes and handles mapping from string class name to
- * actual class objects throughout the whole framework. It is not generally accessed directly, rather through
- * these convenient shorthands:
- *
- * - {@link Ext#define Ext.define}
- * - {@link Ext.ClassManager#create Ext.create}
- * - {@link Ext#widget Ext.widget}
- * - {@link Ext#getClass Ext.getClass}
- * - {@link Ext#getClassName Ext.getClassName}
- *
- * ## Basic syntax:
- *
- * Ext.define(className, properties);
- *
- * in which `properties` is an object represent a collection of properties that apply to the class. See
- * {@link Ext.ClassManager#create} for more detailed instructions.
- *
- * @example
- * Ext.define('Person', {
- * name: 'Unknown',
- *
- * constructor: function(name) {
- * if (name) {
- * this.name = name;
- * }
- *
- * return this;
- * },
- *
- * eat: function(foodType) {
- * alert("I'm eating: " + foodType);
- *
- * return this;
- * }
- * });
- *
- * var aaron = new Person("Aaron");
- * aaron.eat("Sandwich"); // alert("I'm eating: Sandwich");
- *
- * Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of
- * everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc.
- *
- * ## Inheritance:
- *
- * Ext.define('Developer', {
- * extend: 'Person',
- *
- * constructor: function(name, isGeek) {
- * this.isGeek = isGeek;
- *
- * // Apply a method from the parent class' prototype
- * this.callParent([name]);
- *
- * return this;
- *
- * },
- *
- * code: function(language) {
- * alert("I'm coding in: " + language);
- *
- * this.eat("Bugs");
- *
- * return this;
- * }
- * });
- *
- * var jacky = new Developer("Jacky", true);
- * jacky.code("JavaScript"); // alert("I'm coding in: JavaScript");
- * // alert("I'm eating: Bugs");
- *
- * See {@link Ext.Base#callParent} for more details on calling superclass' methods
- *
- * ## Mixins:
- *
- * Ext.define('CanPlayGuitar', {
- * playGuitar: function() {
- * alert("F#...G...D...A");
- * }
- * });
- *
- * Ext.define('CanComposeSongs', {
- * composeSongs: function() { }
- * });
- *
- * Ext.define('CanSing', {
- * sing: function() {
- * alert("I'm on the highway to hell...");
- * }
- * });
- *
- * Ext.define('Musician', {
- * extend: 'Person',
- *
- * mixins: {
- * canPlayGuitar: 'CanPlayGuitar',
- * canComposeSongs: 'CanComposeSongs',
- * canSing: 'CanSing'
- * }
- * });
- *
- * Ext.define('CoolPerson', {
- * extend: 'Person',
- *
- * mixins: {
- * canPlayGuitar: 'CanPlayGuitar',
- * canSing: 'CanSing'
- * },
- *
- * sing: function() {
- * alert("Ahem...");
- *
- * this.mixins.canSing.sing.call(this);
- *
- * alert("[Playing guitar at the same time...]");
- *
- * this.playGuitar();
- * }
- * });
- *
- * var me = new CoolPerson("Jacky");
- *
- * me.sing(); // alert("Ahem...");
- * // alert("I'm on the highway to hell...");
- * // alert("[Playing guitar at the same time...]");
- * // alert("F#...G...D...A");
- *
- * ## Config:
- *
- * Ext.define('SmartPhone', {
- * config: {
- * hasTouchScreen: false,
- * operatingSystem: 'Other',
- * price: 500
- * },
- *
- * isExpensive: false,
- *
- * constructor: function(config) {
- * this.initConfig(config);
- *
- * return this;
- * },
- *
- * applyPrice: function(price) {
- * this.isExpensive = (price > 500);
- *
- * return price;
- * },
- *
- * applyOperatingSystem: function(operatingSystem) {
- * if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) {
- * return 'Other';
- * }
- *
- * return operatingSystem;
- * }
- * });
- *
- * var iPhone = new SmartPhone({
- * hasTouchScreen: true,
- * operatingSystem: 'iOS'
- * });
- *
- * iPhone.getPrice(); // 500;
- * iPhone.getOperatingSystem(); // 'iOS'
- * iPhone.getHasTouchScreen(); // true;
- *
- * iPhone.isExpensive; // false;
- * iPhone.setPrice(600);
- * iPhone.getPrice(); // 600
- * iPhone.isExpensive; // true;
- *
- * iPhone.setOperatingSystem('AlienOS');
- * iPhone.getOperatingSystem(); // 'Other'
- *
- * ## Statics:
- *
- * Ext.define('Computer', {
- * statics: {
- * factory: function(brand) {
- * // 'this' in static methods refer to the class itself
- * return new this(brand);
- * }
- * },
- *
- * constructor: function() { }
- * });
- *
- * var dellComputer = Computer.factory('Dell');
- *
- * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing
- * static properties within class methods
- *
- * @singleton
- */
- (function(Class, alias, arraySlice, arrayFrom, global) {
- var Manager = Ext.ClassManager = {
- /**
- * @property classes
- * @type Object
- * All classes which were defined through the ClassManager. Keys are the
- * name of the classes and the values are references to the classes.
- * @private
- */
- classes: {},
- /**
- * @private
- */
- existCache: {},
- /**
- * @private
- */
- namespaceRewrites: [{
- from: 'Ext.',
- to: Ext
- }],
- /**
- * @private
- */
- maps: {
- alternateToName: {},
- aliasToName: {},
- nameToAliases: {},
- nameToAlternates: {}
- },
- /** @private */
- enableNamespaceParseCache: true,
- /** @private */
- namespaceParseCache: {},
- /** @private */
- instantiators: [],
- /**
- * Checks if a class has already been created.
- *
- * @param {String} className
- * @return {Boolean} exist
- */
- isCreated: function(className) {
- var existCache = this.existCache,
- i, ln, part, root, parts;
- //<debug error>
- if (typeof className != 'string' || className.length < 1) {
- throw new Error("[Ext.ClassManager] Invalid classname, must be a string and must not be empty");
- }
- //</debug>
- if (this.classes[className] || existCache[className]) {
- return true;
- }
- root = global;
- parts = this.parseNamespace(className);
- for (i = 0, ln = parts.length; i < ln; i++) {
- part = parts[i];
- if (typeof part != 'string') {
- root = part;
- } else {
- if (!root || !root[part]) {
- return false;
- }
- root = root[part];
- }
- }
- existCache[className] = true;
- this.triggerCreated(className);
- return true;
- },
- /**
- * @private
- */
- createdListeners: [],
- /**
- * @private
- */
- nameCreatedListeners: {},
- /**
- * @private
- */
- triggerCreated: function(className) {
- var listeners = this.createdListeners,
- nameListeners = this.nameCreatedListeners,
- alternateNames = this.maps.nameToAlternates[className],
- names = [className],
- i, ln, j, subLn, listener, name;
- for (i = 0,ln = listeners.length; i < ln; i++) {
- listener = listeners[i];
- listener.fn.call(listener.scope, className);
- }
- if (alternateNames) {
- names.push.apply(names, alternateNames);
- }
- for (i = 0,ln = names.length; i < ln; i++) {
- name = names[i];
- listeners = nameListeners[name];
- if (listeners) {
- for (j = 0,subLn = listeners.length; j < subLn; j++) {
- listener = listeners[j];
- listener.fn.call(listener.scope, name);
- }
- delete nameListeners[name];
- }
- }
- },
- /**
- * @private
- */
- onCreated: function(fn, scope, className) {
- var listeners = this.createdListeners,
- nameListeners = this.nameCreatedListeners,
- listener = {
- fn: fn,
- scope: scope
- };
- if (className) {
- if (this.isCreated(className)) {
- fn.call(scope, className);
- return;
- }
- if (!nameListeners[className]) {
- nameListeners[className] = [];
- }
- nameListeners[className].push(listener);
- }
- else {
- listeners.push(listener);
- }
- },
- /**
- * Supports namespace rewriting.
- * @private
- */
- parseNamespace: function(namespace) {
- //<debug error>
- if (typeof namespace != 'string') {
- throw new Error("[Ext.ClassManager] Invalid namespace, must be a string");
- }
- //</debug>
- var cache = this.namespaceParseCache;
- if (this.enableNamespaceParseCache) {
- if (cache.hasOwnProperty(namespace)) {
- return cache[namespace];
- }
- }
- var parts = [],
- rewrites = this.namespaceRewrites,
- root = global,
- name = namespace,
- rewrite, from, to, i, ln;
- for (i = 0, ln = rewrites.length; i < ln; i++) {
- rewrite = rewrites[i];
- from = rewrite.from;
- to = rewrite.to;
- if (name === from || name.substring(0, from.length) === from) {
- name = name.substring(from.length);
- if (typeof to != 'string') {
- root = to;
- } else {
- parts = parts.concat(to.split('.'));
- }
- break;
- }
- }
- parts.push(root);
- parts = parts.concat(name.split('.'));
- if (this.enableNamespaceParseCache) {
- cache[namespace] = parts;
- }
- return parts;
- },
- /**
- * Creates a namespace and assign the `value` to the created object.
- *
- * Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject);
- * alert(MyCompany.pkg.Example === someObject); // alerts true
- *
- * @param {String} name
- * @param {Mixed} value
- */
- setNamespace: function(name, value) {
- var root = global,
- parts = this.parseNamespace(name),
- ln = parts.length - 1,
- leaf = parts[ln],
- i, part;
- for (i = 0; i < ln; i++) {
- part = parts[i];
- if (typeof part != 'string') {
- root = part;
- } else {
- if (!root[part]) {
- root[part] = {};
- }
- root = root[part];
- }
- }
- root[leaf] = value;
- return root[leaf];
- },
- /**
- * The new Ext.ns, supports namespace rewriting.
- * @private
- */
- createNamespaces: function() {
- var root = global,
- parts, part, i, j, ln, subLn;
- for (i = 0, ln = arguments.length; i < ln; i++) {
- parts = this.parseNamespace(arguments[i]);
- for (j = 0, subLn = parts.length; j < subLn; j++) {
- part = parts[j];
- if (typeof part != 'string') {
- root = part;
- } else {
- if (!root[part]) {
- root[part] = {};
- }
- root = root[part];
- }
- }
- }
- return root;
- },
- /**
- * Sets a name reference to a class.
- *
- * @param {String} name
- * @param {Object} value
- * @return {Ext.ClassManager} this
- */
- set: function(name, value) {
- var me = this,
- maps = me.maps,
- nameToAlternates = maps.nameToAlternates,
- targetName = me.getName(value),
- alternates;
- me.classes[name] = me.setNamespace(name, value);
- if (targetName && targetName !== name) {
- maps.alternateToName[name] = targetName;
- alternates = nameToAlternates[targetName] || (nameToAlternates[targetName] = []);
- alternates.push(name);
- }
- return this;
- },
- /**
- * Retrieve a class by its name.
- *
- * @param {String} name
- * @return {Ext.Class} class
- */
- get: function(name) {
- var classes = this.classes;
- if (classes[name]) {
- return classes[name];
- }
- var root = global,
- parts = this.parseNamespace(name),
- part, i, ln;
- for (i = 0, ln = parts.length; i < ln; i++) {
- part = parts[i];
- if (typeof part != 'string') {
- root = part;
- } else {
- if (!root || !root[part]) {
- return null;
- }
- root = root[part];
- }
- }
- return root;
- },
- /**
- * Register the alias for a class.
- *
- * @param {Ext.Class/String} cls a reference to a class or a `className`.
- * @param {String} alias Alias to use when referring to this class.
- */
- setAlias: function(cls, alias) {
- var aliasToNameMap = this.maps.aliasToName,
- nameToAliasesMap = this.maps.nameToAliases,
- className;
- if (typeof cls == 'string') {
- className = cls;
- } else {
- className = this.getName(cls);
- }
- if (alias && aliasToNameMap[alias] !== className) {
- //<debug info>
- if (aliasToNameMap[alias]) {
- Ext.Logger.info("[Ext.ClassManager] Overriding existing alias: '" + alias + "' " +
- "of: '" + aliasToNameMap[alias] + "' with: '" + className + "'. Be sure it's intentional.");
- }
- //</debug>
- aliasToNameMap[alias] = className;
- }
- if (!nameToAliasesMap[className]) {
- nameToAliasesMap[className] = [];
- }
- if (alias) {
- Ext.Array.include(nameToAliasesMap[className], alias);
- }
- return this;
- },
- /**
- * Adds a batch of class name to alias mappings
- * @param {Object} aliases The set of mappings of the form
- * className : [values...]
- */
- addNameAliasMappings: function(aliases){
- var aliasToNameMap = this.maps.aliasToName,
- nameToAliasesMap = this.maps.nameToAliases,
- className, aliasList, alias, i;
- for (className in aliases) {
- aliasList = nameToAliasesMap[className] ||
- (nameToAliasesMap[className] = []);
- for (i = 0; i < aliases[className].length; i++) {
- alias = aliases[className][i];
- if (!aliasToNameMap[alias]) {
- aliasToNameMap[alias] = className;
- aliasList.push(alias);
- }
- }
- }
- return this;
- },
- /**
- *
- * @param {Object} alternates The set of mappings of the form
- * className : [values...]
- */
- addNameAlternateMappings: function(alternates) {
- var alternateToName = this.maps.alternateToName,
- nameToAlternates = this.maps.nameToAlternates,
- className, aliasList, alternate, i;
- for (className in alternates) {
- aliasList = nameToAlternates[className] ||
- (nameToAlternates[className] = []);
- for (i = 0; i < alternates[className].length; i++) {
- alternate = alternates[className];
- if (!alternateToName[alternate]) {
- alternateToName[alternate] = className;
- aliasList.push(alternate);
- }
- }
- }
- return this;
- },
- /**
- * Get a reference to the class by its alias.
- *
- * @param {String} alias
- * @return {Ext.Class} class
- */
- getByAlias: function(alias) {
- return this.get(this.getNameByAlias(alias));
- },
- /**
- * Get the name of a class by its alias.
- *
- * @param {String} alias
- * @return {String} className
- */
- getNameByAlias: function(alias) {
- return this.maps.aliasToName[alias] || '';
- },
- /**
- * Get the name of a class by its alternate name.
- *
- * @param {String} alternate
- * @return {String} className
- */
- getNameByAlternate: function(alternate) {
- return this.maps.alternateToName[alternate] || '';
- },
- /**
- * Get the aliases of a class by the class name
- *
- * @param {String} name
- * @return {Array} aliases
- */
- getAliasesByName: function(name) {
- return this.maps.nameToAliases[name] || [];
- },
- /**
- * Get the name of the class by its reference or its instance;
- * usually invoked by the shorthand {@link Ext#getClassName Ext.getClassName}
- *
- * Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
- *
- * @param {Ext.Class/Object} object
- * @return {String} className
- */
- getName: function(object) {
- return object && object.$className || '';
- },
- /**
- * Get the class of the provided object; returns null if it's not an instance
- * of any class created with Ext.define. This is usually invoked by the shorthand {@link Ext#getClass Ext.getClass}.
- *
- * var component = new Ext.Component();
- *
- * Ext.ClassManager.getClass(component); // returns Ext.Component
- *
- * @param {Object} object
- * @return {Ext.Class} class
- */
- getClass: function(object) {
- return object && object.self || null;
- },
- /**
- * @private
- */
- create: function(className, data, createdFn) {
- //<debug error>
- if (typeof className != 'string') {
- throw new Error("[Ext.define] Invalid class name '" + className + "' specified, must be a non-empty string");
- }
- //</debug>
- data.$className = className;
- return new Class(data, function() {
- var postprocessorStack = data.postprocessors || Manager.defaultPostprocessors,
- registeredPostprocessors = Manager.postprocessors,
- index = 0,
- postprocessors = [],
- postprocessor, process, i, ln, j, subLn, postprocessorProperties, postprocessorProperty;
- delete data.postprocessors;
- for (i = 0,ln = postprocessorStack.length; i < ln; i++) {
- postprocessor = postprocessorStack[i];
- if (typeof postprocessor == 'string') {
- postprocessor = registeredPostprocessors[postprocessor];
- postprocessorProperties = postprocessor.properties;
- if (postprocessorProperties === true) {
- postprocessors.push(postprocessor.fn);
- }
- else if (postprocessorProperties) {
- for (j = 0,subLn = postprocessorProperties.length; j < subLn; j++) {
- postprocessorProperty = postprocessorProperties[j];
- if (data.hasOwnProperty(postprocessorProperty)) {
- postprocessors.push(postprocessor.fn);
- break;
- }
- }
- }
- }
- else {
- postprocessors.push(postprocessor);
- }
- }
- process = function(clsName, cls, clsData) {
- postprocessor = postprocessors[index++];
- if (!postprocessor) {
- Manager.set(className, cls);
- if (createdFn) {
- createdFn.call(cls, cls);
- }
- Manager.triggerCreated(className);
- return;
- }
- if (postprocessor.call(this, clsName, cls, clsData, process) !== false) {
- process.apply(this, arguments);
- }
- };
- process.call(Manager, className, this, data);
- });
- },
- createOverride: function(className, data) {
- var overriddenClassName = data.override,
- requires = Ext.Array.from(data.requires);
- delete data.override;
- delete data.requires;
- this.existCache[className] = true;
- Ext.require(requires, function() {
- // Override the target class right after it's created
- this.onCreated(function() {
- this.get(overriddenClassName).override(data);
- // This push the overridding file itself into Ext.Loader.history
- // Hence if the target class never exists, the overriding file will
- // never be included in the build
- this.triggerCreated(className);
- }, this, overriddenClassName);
- }, this);
- return this;
- },
- /**
- * Instantiate a class by its alias; usually invoked by the convenient shorthand {@link Ext#createByAlias Ext.createByAlias}
- * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
- * attempt to load the class via synchronous loading.
- *
- * var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800 });
- *
- * @param {String} alias
- * @param {Mixed...} args Additional arguments after the alias will be passed to the class constructor.
- * @return {Object} instance
- */
- instantiateByAlias: function() {
- var alias = arguments[0],
- args = arraySlice.call(arguments),
- className = this.getNameByAlias(alias);
- if (!className) {
- className = this.maps.aliasToName[alias];
- //<debug error>
- if (!className) {
- throw new Error("[Ext.createByAlias] Cannot create an instance of unrecognized alias: " + alias);
- }
- //</debug>
- //<debug warn>
- Ext.Logger.warn("[Ext.Loader] Synchronously loading '" + className + "'; consider adding " +
- "Ext.require('" + alias + "') above Ext.onReady");
- //</debug>
- Ext.syncRequire(className);
- }
- args[0] = className;
- return this.instantiate.apply(this, args);
- },
- /**
- * Instantiate a class by either full name, alias or alternate name; usually invoked by the convenient
- * shorthand {@link Ext.ClassManager#create Ext.create}.
- *
- * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
- * attempt to load the class via synchronous loading.
- *
- * For example, all these three lines return the same result:
- *
- * // alias
- * var formPanel = Ext.create('widget.formpanel', { width: 600, height: 800 });
- *
- * // alternate name
- * var formPanel = Ext.create('Ext.form.FormPanel', { width: 600, height: 800 });
- *
- * // full class name
- * var formPanel = Ext.create('Ext.form.Panel', { width: 600, height: 800 });
- *
- * @param {String} name
- * @param {Mixed} args Additional arguments after the name will be passed to the class' constructor.
- * @return {Object} instance
- */
- instantiate: function() {
- var name = arguments[0],
- args = arraySlice.call(arguments, 1),
- alias = name,
- possibleName, cls;
- if (typeof name != 'function') {
- //<debug error>
- if ((typeof name != 'string' || name.length < 1)) {
- throw new Error("[Ext.create] Invalid class name or alias '" + name + "' specified, must be a non-empty string");
- }
- //</debug>
- cls = this.get(name);
- }
- else {
- cls = name;
- }
- // No record of this class name, it's possibly an alias, so look it up
- if (!cls) {
- possibleName = this.getNameByAlias(name);
- if (possibleName) {
- name = possibleName;
- cls = this.get(name);
- }
- }
- // Still no record of this class name, it's possibly an alternate name, so look it up
- if (!cls) {
- possibleName = this.getNameByAlternate(name);
- if (possibleName) {
- name = possibleName;
- cls = this.get(name);
- }
- }
- // Still not existing at this point, try to load it via synchronous mode as the last resort
- if (!cls) {
- //<debug warn>
- Ext.Logger.warn("[Ext.Loader] Synchronously loading '" + name + "'; consider adding '" +
- ((possibleName) ? alias : name) + "' explicitly as a require of the corresponding class");
- //</debug>
- Ext.syncRequire(name);
- cls = this.get(name);
- }
- //<debug error>
- if (!cls) {
- throw new Error("[Ext.create] Cannot create an instance of unrecognized class name / alias: " + alias);
- }
- if (typeof cls != 'function') {
- throw new Error("[Ext.create] '" + name + "' is a singleton and cannot be instantiated");
- }
- //</debug>
- return this.getInstantiator(args.length)(cls, args);
- },
- /**
- * @private
- * @param name
- * @param args
- */
- dynInstantiate: function(name, args) {
- args = arrayFrom(args, true);
- args.unshift(name);
- return this.instantiate.apply(this, args);
- },
- /**
- * @private
- * @param length
- */
- getInstantiator: function(length) {
- var instantiators = this.instantiators,
- instantiator;
- instantiator = instantiators[length];
- if (!instantiator) {
- var i = length,
- args = [];
- for (i = 0; i < length; i++) {
- args.push('a[' + i + ']');
- }
- instantiator = instantiators[length] = new Function('c', 'a', 'return new c(' + args.join(',') + ')');
- //<debug>
- instantiator.displayName = "Ext.ClassManager.instantiate" + length;
- //</debug>
- }
- return instantiator;
- },
- /**
- * @private
- */
- postprocessors: {},
- /**
- * @private
- */
- defaultPostprocessors: [],
- /**
- * Register a post-processor function.
- *
- * @private
- * @param {String} name
- * @param {Function} postprocessor
- */
- registerPostprocessor: function(name, fn, properties, position, relativeTo) {
- if (!position) {
- position = 'last';
- }
- if (!properties) {
- properties = [name];
- }
- this.postprocessors[name] = {
- name: name,
- properties: properties || false,
- fn: fn
- };
- this.setDefaultPostprocessorPosition(name, position, relativeTo);
- return this;
- },
- /**
- * Set the default post processors array stack which are applied to every class.
- *
- * @private
- * @param {String/Array} The name of a registered post processor or an array of registered names.
- * @return {Ext.ClassManager} this
- */
- setDefaultPostprocessors: function(postprocessors) {
- this.defaultPostprocessors = arrayFrom(postprocessors);
- return this;
- },
- /**
- * Insert this post-processor at a specific position in the stack, optionally relative to
- * any existing post-processor
- *
- * @private
- * @param {String} name The post-processor name. Note that it needs to be registered with
- * {@link Ext.ClassManager#registerPostprocessor} before this
- * @param {String} offset The insertion position. Four possible values are:
- * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
- * @param {String} relativeName
- * @return {Ext.ClassManager} this
- */
- setDefaultPostprocessorPosition: function(name, offset, relativeName) {
- var defaultPostprocessors = this.defaultPostprocessors,
- index;
- if (typeof offset == 'string') {
- if (offset === 'first') {
- defaultPostprocessors.unshift(name);
- return this;
- }
- else if (offset === 'last') {
- defaultPostprocessors.push(name);
- return this;
- }
- offset = (offset === 'after') ? 1 : -1;
- }
- index = Ext.Array.indexOf(defaultPostprocessors, relativeName);
- if (index !== -1) {
- Ext.Array.splice(defaultPostprocessors, Math.max(0, index + offset), 0, name);
- }
- return this;
- },
- /**
- * Converts a string expression to an array of matching class names. An expression can either refers to class aliases
- * or class names. Expressions support wildcards:
- *
- * // returns ['Ext.window.Window']
- * var window = Ext.ClassManager.getNamesByExpression('widget.window');
- *
- * // returns ['widget.panel', 'widget.window', ...]
- * var allWidgets = Ext.ClassManager.getNamesByExpression('widget.*');
- *
- * // returns ['Ext.data.Store', 'Ext.data.ArrayProxy', ...]
- * var allData = Ext.ClassManager.getNamesByExpression('Ext.data.*');
- *
- * @param {String} expression
- * @return {Array} classNames
- */
- getNamesByExpression: function(expression) {
- var nameToAliasesMap = this.maps.nameToAliases,
- names = [],
- name, alias, aliases, possibleName, regex, i, ln;
- //<debug error>
- if (typeof expression != 'string' || expression.length < 1) {
- throw new Error("[Ext.ClassManager.getNamesByExpression] Expression " + expression + " is invalid, must be a non-empty string");
- }
- //</debug>
- if (expression.indexOf('*') !== -1) {
- expression = expression.replace(/\*/g, '(.*?)');
- regex = new RegExp('^' + expression + '$');
- for (name in nameToAliasesMap) {
- if (nameToAliasesMap.hasOwnProperty(name)) {
- aliases = nameToAliasesMap[name];
- if (name.search(regex) !== -1) {
- names.push(name);
- }
- else {
- for (i = 0, ln = aliases.length; i < ln; i++) {
- alias = aliases[i];
- if (alias.search(regex) !== -1) {
- names.push(name);
- break;
- }
- }
- }
- }
- }
- } else {
- possibleName = this.getNameByAlias(expression);
- if (possibleName) {
- names.push(possibleName);
- } else {
- possibleName = this.getNameByAlternate(expression);
- if (possibleName) {
- names.push(possibleName);
- } else {
- names.push(expression);
- }
- }
- }
- return names;
- }
- };
- //<feature classSystem.alias>
- /**
- * @cfg {String[]} alias
- * @member Ext.Class
- * List of short aliases for class names. Most useful for defining xtypes for widgets:
- *
- * Ext.define('MyApp.CoolPanel', {
- * extend: 'Ext.panel.Panel',
- * alias: ['widget.coolpanel'],
- * title: 'Yeah!'
- * });
- *
- * // Using Ext.create
- * Ext.create('widget.coolpanel');
- *
- * // Using the shorthand for widgets and in xtypes
- * Ext.widget('panel', {
- * items: [
- * {xtype: 'coolpanel', html: 'Foo'},
- * {xtype: 'coolpanel', html: 'Bar'}
- * ]
- * });
- */
- Manager.registerPostprocessor('alias', function(name, cls, data) {
- var aliases = data.alias,
- i, ln;
- for (i = 0,ln = aliases.length; i < ln; i++) {
- alias = aliases[i];
- this.setAlias(cls, alias);
- }
- }, ['xtype', 'alias']);
- //</feature>
- //<feature classSystem.singleton>
- /**
- * @cfg {Boolean} singleton
- * @member Ext.Class
- * When set to true, the class will be instantiated as singleton. For example:
- *
- * Ext.define('Logger', {
- * singleton: true,
- * log: function(msg) {
- * console.log(msg);
- * }
- * });
- *
- * Logger.log('Hello');
- */
- Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
- fn.call(this, name, new cls(), data);
- return false;
- });
- //</feature>
- //<feature classSystem.alternateClassName>
- /**
- * @cfg {String/String[]} alternateClassName
- * @member Ext.Class
- * Defines alternate names for this class. For example:
- *
- * @example
- * Ext.define('Developer', {
- * alternateClassName: ['Coder', 'Hacker'],
- * code: function(msg) {
- * alert('Typing... ' + msg);
- * }
- * });
- *
- * var joe = Ext.create('Developer');
- * joe.code('stackoverflow');
- *
- * var rms = Ext.create('Hacker');
- * rms.code('hack hack');
- */
- Manager.registerPostprocessor('alternateClassName', function(name, cls, data) {
- var alternates = data.alternateClassName,
- i, ln, alternate;
- if (!(alternates instanceof Array)) {
- alternates = [alternates];
- }
- for (i = 0, ln = alternates.length; i < ln; i++) {
- alternate = alternates[i];
- //<debug error>
- if (typeof alternate != 'string') {
- throw new Error("[Ext.define] Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string");
- }
- //</debug>
- this.set(alternate, cls);
- }
- });
- //</feature>
- Ext.apply(Ext, {
- /**
- * Instantiate a class by either full name, alias or alternate name.
- *
- * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
- * attempt to load the class via synchronous loading.
- *
- * For example, all these three lines return the same result:
- *
- * // alias
- * var formPanel = Ext.create('widget.formpanel', { width: 600, height: 800 });
- *
- * // alternate name
- * var formPanel = Ext.create('Ext.form.FormPanel', { width: 600, height: 800 });
- *
- * // full class name
- * var formPanel = Ext.create('Ext.form.Panel', { width: 600, height: 800 });
- *
- * @param {String} name
- * @param {Mixed} args Additional arguments after the name will be passed to the class' constructor.
- * @return {Object} instance
- * @member Ext
- */
- create: alias(Manager, 'instantiate'),
- /**
- * Convenient shorthand to create a widget by its xtype, also see {@link Ext.ClassManager#instantiateByAlias}
- *
- * var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button')
- * var panel = Ext.widget('panel'); // Equivalent to Ext.create('widget.panel')
- *
- * @member Ext
- * @method widget
- */
- widget: function(name) {
- var args = arraySlice.call(arguments);
- args[0] = 'widget.' + name;
- return Manager.instantiateByAlias.apply(Manager, args);
- },
- /**
- * Convenient shorthand, see {@link Ext.ClassManager#instantiateByAlias}.
- * @member Ext
- * @method createByAlias
- */
- createByAlias: alias(Manager, 'instantiateByAlias'),
- /**
- * Defines a class or override. A basic class is defined like this:
- *
- * Ext.define('My.awesome.Class', {
- * someProperty: 'something',
- *
- * someMethod: function(s) {
- * console.log(s + this.someProperty);
- * }
- * });
- *
- * var obj = new My.awesome.Class();
- *
- * obj.someMethod('Say '); // logs 'Say something' to the console
- *
- * To defines an override, include the `override` property. The content of an
- * override is aggregated with the specified class in order to extend or modify
- * that class. This can be as simple as setting default property values or it can
- * extend and/or replace methods. This can also extend the statics of the class.
- *
- * One use for an override is to break a large class into manageable pieces.
- *
- * // File: /src/app/Panel.js
- * Ext.define('My.app.Panel', {
- * extend: 'Ext.panel.Panel',
- * requires: [
- * 'My.app.PanelPart2',
- * 'My.app.PanelPart3'
- * ],
- *
- * constructor: function (config) {
- * this.callParent(arguments); // calls Ext.panel.Panel's constructor
- * // ...
- * },
- *
- * statics: {
- * method: function () {
- * return 'abc';
- * }
- * }
- * });
- *
- * // File: /src/app/PanelPart2.js
- * Ext.define('My.app.PanelPart2', {
- * override: 'My.app.Panel',
- *
- * constructor: function (config) {
- * this.callParent(arguments); // calls My.app.Panel's constructor
- * // ...
- * }
- * });
- *
- * Another use for an override is to provide optional parts of classes that can be
- * independently required. In this case, the class may even be unaware of the
- * override altogether.
- *
- * Ext.define('My.ux.CoolTip', {
- * override: 'Ext.tip.ToolTip',
- *
- * constructor: function (config) {
- * this.callParent(arguments); // calls Ext.tip.ToolTip's constructor
- * // ...
- * }
- * });
- *
- * The above override can now be required as normal.
- *
- * Ext.define('My.app.App', {
- * requires: [
- * 'My.ux.CoolTip'
- * ]
- * });
- *
- * Overrides can also contain statics:
- *
- * Ext.define('My.app.BarMod', {
- * override: 'Ext.foo.Bar',
- *
- * statics: {
- * method: function (x) {
- * return this.callParent([x * 2]); // call Ext.foo.Bar.method
- * }
- * }
- * });
- *
- * __IMPORTANT:__ An override is only included in a build if the class it overrides is
- * required. Otherwise, the override, like the target class, is not included.
- *
- * @param {String} className The class name to create in string dot-namespaced format, for example:
- * 'My.very.awesome.Class', 'FeedViewer.plugin.CoolPager'
- *
- * It is highly recommended to follow this simple convention:
- * - The root and the class name are 'CamelCased'
- * - Everything else is lower-cased
- *
- * @param {Object} data The key - value pairs of properties to apply to this class. Property names can be of
- * any valid strings, except those in the reserved listed below:
- *
- * - `mixins`
- * - `statics`
- * - `config`
- * - `alias`
- * - `self`
- * - `singleton`
- * - `alternateClassName`
- * - `override`
- *
- * @param {Function} [createdFn] Optional callback to execute after the class (or override)
- * is created. The execution scope (`this`) will be the newly created class itself.
- * @return {Ext.Base}
- *
- * @member Ext
- * @method define
- */
- define: function (className, data, createdFn) {
- if ('override' in data) {
- return Manager.createOverride.apply(Manager, arguments);
- }
- return Manager.create.apply(Manager, arguments);
- },
- /**
- * Convenient shorthand for {@link Ext.ClassManager#getName}.
- * @member Ext
- * @method getClassName
- * @inheritdoc Ext.ClassManager#getName
- */
- getClassName: alias(Manager, 'getName'),
- /**
- * Returns the display name for object. This name is looked for in order from the following places:
- *
- * - `displayName` field of the object.
- * - `$name` and `$class` fields of the object.
- * - '$className` field of the object.
- *
- * This method is used by {@link Ext.Logger#log} to display information about objects.
- *
- * @param {Mixed} [object] The object who's display name to determine.
- * @return {String} The determined display name, or "Anonymous" if none found.
- * @member Ext
- */
- getDisplayName: function(object) {
- if (object) {
- if (object.displayName) {
- return object.displayName;
- }
- if (object.$name && object.$class) {
- return Ext.getClassName(object.$class) + '#' + object.$name;
- }
- if (object.$className) {
- return object.$className;
- }
- }
- return 'Anonymous';
- },
- /**
- * Convenient shorthand, see {@link Ext.ClassManager#getClass}.
- * @member Ext
- * @method getClass
- */
- getClass: alias(Manager, 'getClass'),
- /**
- * Creates namespaces to be used for scoping variables and classes so that they are not global.
- * Specifying the last node of a namespace implicitly creates all other nodes. Usage:
- *
- * Ext.namespace('Company', 'Company.data');
- *
- * // equivalent and preferable to the above syntax
- * Ext.namespace('Company.data');
- *
- * Company.Widget = function() {
- * // ...
- * };
- *
- * Company.data.CustomStore = function(config) {
- * // ...
- * };
- *
- * @param {String} namespace1
- * @param {String} namespace2
- * @param {String} etc
- * @return {Object} The namespace object. If multiple arguments are passed, this will be the last namespace created.
- * @member Ext
- * @method namespace
- */
- namespace: alias(Manager, 'createNamespaces')
- });
- /**
- * Old name for {@link Ext#widget}.
- * @deprecated 4.0.0 Please use {@link Ext#widget} instead.
- * @method createWidget
- * @member Ext
- */
- Ext.createWidget = Ext.widget;
- /**
- * Convenient alias for {@link Ext#namespace Ext.namespace}.
- * @member Ext
- * @method ns
- */
- Ext.ns = Ext.namespace;
- Class.registerPreprocessor('className', function(cls, data) {
- if (data.$className) {
- cls.$className = data.$className;
- //<debug>
- cls.displayName = cls.$className;
- //</debug>
- }
- }, true, 'first');
- Class.registerPreprocessor('alias', function(cls, data) {
- var prototype = cls.prototype,
- xtypes = arrayFrom(data.xtype),
- aliases = arrayFrom(data.alias),
- widgetPrefix = 'widget.',
- widgetPrefixLength = widgetPrefix.length,
- xtypesChain = Array.prototype.slice.call(prototype.xtypesChain || []),
- xtypesMap = Ext.merge({}, prototype.xtypesMap || {}),
- i, ln, alias, xtype;
- for (i = 0,ln = aliases.length; i < ln; i++) {
- alias = aliases[i];
- //<debug error>
- if (typeof alias != 'string' || alias.length < 1) {
- throw new Error("[Ext.define] Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string");
- }
- //</debug>
- if (alias.substring(0, widgetPrefixLength) === widgetPrefix) {
- xtype = alias.substring(widgetPrefixLength);
- Ext.Array.include(xtypes, xtype);
- }
- }
- cls.xtype = data.xtype = xtypes[0];
- data.xtypes = xtypes;
- for (i = 0,ln = xtypes.length; i < ln; i++) {
- xtype = xtypes[i];
- if (!xtypesMap[xtype]) {
- xtypesMap[xtype] = true;
- xtypesChain.push(xtype);
- }
- }
- data.xtypesChain = xtypesChain;
- data.xtypesMap = xtypesMap;
- Ext.Function.interceptAfter(data, 'onClassCreated', function() {
- var mixins = prototype.mixins,
- key, mixin;
- for (key in mixins) {
- if (mixins.hasOwnProperty(key)) {
- mixin = mixins[key];
- xtypes = mixin.xtypes;
- if (xtypes) {
- for (i = 0,ln = xtypes.length; i < ln; i++) {
- xtype = xtypes[i];
- if (!xtypesMap[xtype]) {
- xtypesMap[xtype] = true;
- xtypesChain.push(xtype);
- }
- }
- }
- }
- }
- });
- for (i = 0,ln = xtypes.length; i < ln; i++) {
- xtype = xtypes[i];
- //<debug error>
- if (typeof xtype != 'string' || xtype.length < 1) {
- throw new Error("[Ext.define] Invalid xtype of: '" + xtype + "' for class: '" + name + "'; must be a valid non-empty string");
- }
- //</debug>
- Ext.Array.include(aliases, widgetPrefix + xtype);
- }
- data.alias = aliases;
- }, ['xtype', 'alias']);
- })(Ext.Class, Ext.Function.alias, Array.prototype.slice, Ext.Array.from, Ext.global);
- //@tag foundation,core
- //@define Ext.Loader
- //@require Ext.ClassManager
- /**
- * @class Ext.Loader
- *
- * @author Jacky Nguyen <jacky@sencha.com>
- * @docauthor Jacky Nguyen <jacky@sencha.com>
- * @aside guide mvc_dependencies
- *
- * Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
- * via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
- * approaches, and leverage their advantages for the best development flow.
- * We'll discuss about the pros and cons of each approach.
- *
- * __Note:__ The Loader is only enabled by default in development versions of the library (eg sencha-touch-debug.js). To
- * explicitly enable the loader, use `Ext.Loader.setConfig({ enabled: true });` before the start of your script.
- *
- * ## Asynchronous Loading
- *
- * - Advantages:
- * + Cross-domain
- * + No web server needed: you can run the application via the file system protocol (i.e: `file://path/to/your/index
- * .html`)
- * + Best possible debugging experience: error messages come with the exact file name and line number
- *
- * - Disadvantages:
- * + Dependencies need to be specified before-hand
- *
- * ### Method 1: Explicitly include what you need: ###
- *
- * // Syntax
- * // Ext.require({String/Array} expressions);
- *
- * // Example: Single alias
- * Ext.require('widget.window');
- *
- * // Example: Single class name
- * Ext.require('Ext.window.Window');
- *
- * // Example: Multiple aliases / class names mix
- * Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
- *
- * // Wildcards
- * Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
- *
- * ### Method 2: Explicitly exclude what you don't need: ###
- *
- * // Syntax: Note that it must be in this chaining format.
- * // Ext.exclude({String/Array} expressions)
- * // .require({String/Array} expressions);
- *
- * // Include everything except Ext.data.*
- * Ext.exclude('Ext.data.*').require('*');
- *
- * // Include all widgets except widget.checkbox*,
- * // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
- * Ext.exclude('widget.checkbox*').require('widget.*');
- *
- * # Synchronous Loading on Demand #
- *
- * - *Advantages:*
- * + There's no need to specify dependencies before-hand, which is always the convenience of including ext-all.js
- * before
- *
- * - *Disadvantages:*
- * + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
- * + Must be from the same domain due to XHR restriction
- * + Need a web server, same reason as above
- *
- * There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
- *
- * Ext.create('widget.window', {}); // Instead of new Ext.window.Window({...});
- *
- * Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
- *
- * Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
- *
- * Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
- * existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load the given
- * class and all its dependencies.
- *
- * # Hybrid Loading - The Best of Both Worlds #
- *
- * It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
- *
- * ### Step 1: Start writing your application using synchronous approach. ###
- * Ext.Loader will automatically fetch all dependencies on demand as they're
- * needed during run-time. For example:
- *
- * Ext.onReady(function(){
- * var window = Ext.createWidget('window', {
- * width: 500,
- * height: 300,
- * layout: {
- * type: 'border',
- * padding: 5
- * },
- * title: 'Hello Dialog',
- * items: [{
- * title: 'Navigation',
- * collapsible: true,
- * region: 'west',
- * width: 200,
- * html: 'Hello',
- * split: true
- * }, {
- * title: 'TabPanel',
- * region: 'center'
- * }]
- * });
- *
- * window.show();
- * });
- *
- * ### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these: ###
- *
- * [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code
- * ClassManager.js:432
- * [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
- *
- * Simply copy and paste the suggested code above `Ext.onReady`, i.e:
- *
- * Ext.require('Ext.window.Window');
- * Ext.require('Ext.layout.container.Border');
- *
- * Ext.onReady(function () {
- * // ...
- * });
- *
- * Everything should now load via asynchronous mode.
- *
- * # Deployment #
- *
- * It's important to note that dynamic loading should only be used during development on your local machines.
- * During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
- * the whole process of transitioning from / to between development / maintenance and production as easy as
- * possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies your application
- * needs in the exact loading sequence. It's as simple as concatenating all files in this array into one,
- * then include it on top of your application.
- *
- * This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
- *
- * @singleton
- */
- (function(Manager, Class, flexSetter, alias, pass, arrayFrom, arrayErase, arrayInclude) {
- var
- dependencyProperties = ['extend', 'mixins', 'requires'],
- Loader,
- setPathCount = 0;;
- Loader = Ext.Loader = {
- /**
- * @private
- */
- isInHistory: {},
- /**
- * An array of class names to keep track of the dependency loading order.
- * This is not guaranteed to be the same every time due to the asynchronous
- * nature of the Loader.
- *
- * @property history
- * @type Array
- */
- history: [],
- /**
- * Configuration
- * @private
- */
- config: {
- /**
- * Whether or not to enable the dynamic dependency loading feature.
- * @cfg {Boolean} enabled
- */
- enabled: true,
- /**
- * @cfg {Boolean} disableCaching
- * Appends current timestamp to script files to prevent caching.
- */
- disableCaching: true,
- /**
- * @cfg {String} disableCachingParam
- * The get parameter name for the cache buster's timestamp.
- */
- disableCachingParam: '_dc',
- /**
- * @cfg {Object} paths
- * The mapping from namespaces to file paths.
- *
- * {
- * 'Ext': '.', // This is set by default, Ext.layout.container.Container will be
- * // loaded from ./layout/Container.js
- *
- * 'My': './src/my_own_folder' // My.layout.Container will be loaded from
- * // ./src/my_own_folder/layout/Container.js
- * }
- *
- * Note that all relative paths are relative to the current HTML document.
- * If not being specified, for example, `Other.awesome.Class`
- * will simply be loaded from `./Other/awesome/Class.js`.
- */
- paths: {
- 'Ext': '.'
- }
- },
- /**
- * Set the configuration for the loader. This should be called right after ext-(debug).js
- * is included in the page, and before Ext.onReady. i.e:
- *
- * <script type="text/javascript" src="ext-core-debug.js"></script>
- * <script type="text/javascript">
- * Ext.Loader.setConfig({
- * enabled: true,
- * paths: {
- * 'My': 'my_own_path'
- * }
- * });
- * <script>
- * <script type="text/javascript">
- * Ext.require(...);
- *
- * Ext.onReady(function() {
- * // application code here
- * });
- * </script>
- *
- * Refer to config options of {@link Ext.Loader} for the list of possible properties.
- *
- * @param {Object} config The config object to override the default values.
- * @return {Ext.Loader} this
- */
- setConfig: function(name, value) {
- if (Ext.isObject(name) && arguments.length === 1) {
- Ext.merge(this.config, name);
- }
- else {
- this.config[name] = (Ext.isObject(value)) ? Ext.merge(this.config[name], value) : value;
- }
- setPathCount += 1;
- return this;
- },
- /**
- * Get the config value corresponding to the specified name. If no name is given, will return the config object.
- * @param {String} name The config property name.
- * @return {Object/Mixed}
- */
- getConfig: function(name) {
- if (name) {
- return this.config[name];
- }
- return this.config;
- },
- /**
- * Sets the path of a namespace.
- * For example:
- *
- * Ext.Loader.setPath('Ext', '.');
- *
- * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
- * @param {String} [path] See {@link Ext.Function#flexSetter flexSetter}
- * @return {Ext.Loader} this
- * @method
- */
- setPath: flexSetter(function(name, path) {
- this.config.paths[name] = path;
- setPathCount += 1;
- return this;
- }),
- /**
- * Sets a batch of path entries
- *
- * @param {Object } paths a set of className: path mappings
- * @return {Ext.Loader} this
- */
- addClassPathMappings: function(paths) {
- var name;
- if(setPathCount == 0){
- Loader.config.paths = paths;
- } else {
- for(name in paths){
- Loader.config.paths[name] = paths[name];
- }
- }
- setPathCount++;
- return Loader;
- },
- /**
- * Translates a className to a file path by adding the
- * the proper prefix and converting the .'s to /'s. For example:
- *
- * Ext.Loader.setPath('My', '/path/to/My');
- *
- * alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
- *
- * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
- *
- * Ext.Loader.setPath({
- * 'My': '/path/to/lib',
- * 'My.awesome': '/other/path/for/awesome/stuff',
- * 'My.awesome.more': '/more/awesome/path'
- * });
- *
- * alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
- *
- * alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
- *
- * alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
- *
- * alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
- *
- * @param {String} className
- * @return {String} path
- */
- getPath: function(className) {
- var path = '',
- paths = this.config.paths,
- prefix = this.getPrefix(className);
- if (prefix.length > 0) {
- if (prefix === className) {
- return paths[prefix];
- }
- path = paths[prefix];
- className = className.substring(prefix.length + 1);
- }
- if (path.length > 0) {
- path += '/';
- }
- return path.replace(/\/\.\//g, '/') + className.replace(/\./g, "/") + '.js';
- },
- /**
- * @private
- * @param {String} className
- */
- getPrefix: function(className) {
- var paths = this.config.paths,
- prefix, deepestPrefix = '';
- if (paths.hasOwnProperty(className)) {
- return className;
- }
- for (prefix in paths) {
- if (paths.hasOwnProperty(prefix) && prefix + '.' === className.substring(0, prefix.length + 1)) {
- if (prefix.length > deepestPrefix.length) {
- deepestPrefix = prefix;
- }
- }
- }
- return deepestPrefix;
- },
- /**
- * 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#require Ext.require} for convenience.
- * @param {String/Array} expressions Can either be a string or an array of string.
- * @param {Function} fn (optional) The callback function.
- * @param {Object} scope (optional) The execution scope (`this`) of the callback function.
- * @param {String/Array} excludes (optional) Classes to be excluded, useful when being used with expressions.
- */
- require: function(expressions, fn, scope, excludes) {
- if (fn) {
- fn.call(scope);
- }
- },
- /**
- * 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
- * @param {String/Array} expressions Can either be a string or an array of string
- * @param {Function} fn (optional) The callback function
- * @param {Object} scope (optional) The execution scope (`this`) of the callback function
- * @param {String/Array} excludes (optional) Classes to be excluded, useful when being used with expressions
- */
- syncRequire: function() {},
- /**
- * Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
- * Can be chained with more `require` and `exclude` methods, eg:
- *
- * Ext.exclude('Ext.data.*').require('*');
- *
- * Ext.exclude('widget.button*').require('widget.*');
- *
- * @param {Array} excludes
- * @return {Object} object contains `require` method for chaining.
- */
- exclude: function(excludes) {
- var me = this;
- return {
- require: function(expressions, fn, scope) {
- return me.require(expressions, fn, scope, excludes);
- },
- syncRequire: function(expressions, fn, scope) {
- return me.syncRequire(expressions, fn, scope, excludes);
- }
- };
- },
- /**
- * Add a new listener to be executed when all required scripts are fully loaded.
- *
- * @param {Function} fn The function callback to be executed.
- * @param {Object} scope The execution scope (`this`) of the callback function.
- * @param {Boolean} withDomReady Whether or not to wait for document DOM ready as well.
- */
- onReady: function(fn, scope, withDomReady, options) {
- var oldFn;
- if (withDomReady !== false && Ext.onDocumentReady) {
- oldFn = fn;
- fn = function() {
- Ext.onDocumentReady(oldFn, scope, options);
- };
- }
- fn.call(scope);
- }
- };
- //<feature classSystem.loader>
- Ext.apply(Loader, {
- /**
- * @private
- */
- documentHead: typeof document != 'undefined' && (document.head || document.getElementsByTagName('head')[0]),
- /**
- * Flag indicating whether there are still files being loaded
- * @private
- */
- isLoading: false,
- /**
- * Maintain the queue for all dependencies. Each item in the array is an object of the format:
- *
- * {
- * requires: [...], // The required classes for this queue item
- * callback: function() { ... } // The function to execute when all classes specified in requires exist
- * }
- * @private
- */
- queue: [],
- /**
- * Maintain the list of files that have already been handled so that they never get double-loaded
- * @private
- */
- isClassFileLoaded: {},
- /**
- * @private
- */
- isFileLoaded: {},
- /**
- * Maintain the list of listeners to execute when all required scripts are fully loaded
- * @private
- */
- readyListeners: [],
- /**
- * Contains optional dependencies to be loaded last
- * @private
- */
- optionalRequires: [],
- /**
- * Map of fully qualified class names to an array of dependent classes.
- * @private
- */
- requiresMap: {},
- /**
- * @private
- */
- numPendingFiles: 0,
- /**
- * @private
- */
- numLoadedFiles: 0,
- /** @private */
- hasFileLoadError: false,
- /**
- * @private
- */
- classNameToFilePathMap: {},
- /**
- * @private
- */
- syncModeEnabled: false,
- scriptElements: {},
- /**
- * Refresh all items in the queue. If all dependencies for an item exist during looping,
- * it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
- * empty
- * @private
- */
- refreshQueue: function() {
- var queue = this.queue,
- ln = queue.length,
- i, item, j, requires, references;
- if (ln === 0) {
- this.triggerReady();
- return;
- }
- for (i = 0; i < ln; i++) {
- item = queue[i];
- if (item) {
- requires = item.requires;
- references = item.references;
- // Don't bother checking when the number of files loaded
- // is still less than the array length
- if (requires.length > this.numLoadedFiles) {
- continue;
- }
- j = 0;
- do {
- if (Manager.isCreated(requires[j])) {
- // Take out from the queue
- arrayErase(requires, j, 1);
- }
- else {
- j++;
- }
- } while (j < requires.length);
- if (item.requires.length === 0) {
- arrayErase(queue, i, 1);
- item.callback.call(item.scope);
- this.refreshQueue();
- break;
- }
- }
- }
- return this;
- },
- /**
- * Inject a script element to document's head, call onLoad and onError accordingly
- * @private
- */
- injectScriptElement: function(url, onLoad, onError, scope) {
- var script = document.createElement('script'),
- me = this,
- onLoadFn = function() {
- me.cleanupScriptElement(script);
- onLoad.call(scope);
- },
- onErrorFn = function() {
- me.cleanupScriptElement(script);
- onError.call(scope);
- };
- script.type = 'text/javascript';
- script.src = url;
- script.onload = onLoadFn;
- script.onerror = onErrorFn;
- script.onreadystatechange = function() {
- if (this.readyState === 'loaded' || this.readyState === 'complete') {
- onLoadFn();
- }
- };
- this.documentHead.appendChild(script);
- return script;
- },
- removeScriptElement: function(url) {
- var scriptElements = this.scriptElements;
- if (scriptElements[url]) {
- this.cleanupScriptElement(scriptElements[url], true);
- delete scriptElements[url];
- }
- return this;
- },
- /**
- * @private
- */
- cleanupScriptElement: function(script, remove) {
- script.onload = null;
- script.onreadystatechange = null;
- script.onerror = null;
- if (remove) {
- this.documentHead.removeChild(script);
- }
- return this;
- },
- /**
- * Load a script file, supports both asynchronous and synchronous approaches
- *
- * @param {String} url
- * @param {Function} onLoad
- * @param {Object} scope
- * @param {Boolean} synchronous
- * @private
- */
- loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
- var me = this,
- isFileLoaded = this.isFileLoaded,
- scriptElements = this.scriptElements,
- noCacheUrl = url + (this.getConfig('disableCaching') ? ('?' + this.getConfig('disableCachingParam') + '=' + Ext.Date.now()) : ''),
- xhr, status, content, onScriptError;
- if (isFileLoaded[url]) {
- return this;
- }
- scope = scope || this;
- this.isLoading = true;
- if (!synchronous) {
- onScriptError = function() {
- //<debug error>
- onError.call(scope, "Failed loading '" + url + "', please verify that the file exists", synchronous);
- //</debug>
- };
- if (!Ext.isReady && Ext.onDocumentReady) {
- Ext.onDocumentReady(function() {
- if (!isFileLoaded[url]) {
- scriptElements[url] = me.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
- }
- });
- }
- else {
- scriptElements[url] = this.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
- }
- }
- else {
- if (typeof XMLHttpRequest != 'undefined') {
- xhr = new XMLHttpRequest();
- } else {
- xhr = new ActiveXObject('Microsoft.XMLHTTP');
- }
- try {
- xhr.open('GET', noCacheUrl, false);
- xhr.send(null);
- }
- catch (e) {
- //<debug error>
- onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; It's likely that the file is either " +
- "being loaded from a different domain or from the local file system whereby cross origin " +
- "requests are not allowed due to security reasons. Use asynchronous loading with " +
- "Ext.require instead.", synchronous);
- //</debug>
- }
- status = (xhr.status == 1223) ? 204 : xhr.status;
- content = xhr.responseText;
- if ((status >= 200 && status < 300) || status == 304 || (status == 0 && content.length > 0)) {
- // Debugger friendly, file names are still shown even though they're eval'ed code
- // Breakpoints work on both Firebug and Chrome's Web Inspector
- Ext.globalEval(content + "\n//@ sourceURL=" + url);
- onLoad.call(scope);
- }
- else {
- //<debug>
- onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; please " +
- "verify that the file exists. " +
- "XHR status code: " + status, synchronous);
- //</debug>
- }
- // Prevent potential IE memory leak
- xhr = null;
- }
- },
- // documented above
- syncRequire: function() {
- var syncModeEnabled = this.syncModeEnabled;
- if (!syncModeEnabled) {
- this.syncModeEnabled = true;
- }
- this.require.apply(this, arguments);
- if (!syncModeEnabled) {
- this.syncModeEnabled = false;
- }
- this.refreshQueue();
- },
- // documented above
- require: function(expressions, fn, scope, excludes) {
- var excluded = {},
- included = {},
- queue = this.queue,
- classNameToFilePathMap = this.classNameToFilePathMap,
- isClassFileLoaded = this.isClassFileLoaded,
- excludedClassNames = [],
- possibleClassNames = [],
- classNames = [],
- references = [],
- callback,
- syncModeEnabled,
- filePath, expression, exclude, className,
- possibleClassName, i, j, ln, subLn;
- if (excludes) {
- excludes = arrayFrom(excludes);
- for (i = 0,ln = excludes.length; i < ln; i++) {
- exclude = excludes[i];
- if (typeof exclude == 'string' && exclude.length > 0) {
- excludedClassNames = Manager.getNamesByExpression(exclude);
- for (j = 0,subLn = excludedClassNames.length; j < subLn; j++) {
- excluded[excludedClassNames[j]] = true;
- }
- }
- }
- }
- expressions = arrayFrom(expressions);
- if (fn) {
- if (fn.length > 0) {
- callback = function() {
- var classes = [],
- i, ln, name;
- for (i = 0,ln = references.length; i < ln; i++) {
- name = references[i];
- classes.push(Manager.get(name));
- }
- return fn.apply(this, classes);
- };
- }
- else {
- callback = fn;
- }
- }
- else {
- callback = Ext.emptyFn;
- }
- scope = scope || Ext.global;
- for (i = 0,ln = expressions.length; i < ln; i++) {
- expression = expressions[i];
- if (typeof expression == 'string' && expression.length > 0) {
- possibleClassNames = Manager.getNamesByExpression(expression);
- subLn = possibleClassNames.length;
- for (j = 0; j < subLn; j++) {
- possibleClassName = possibleClassNames[j];
- if (excluded[possibleClassName] !== true) {
- references.push(possibleClassName);
- if (!Manager.isCreated(possibleClassName) && !included[possibleClassName]) {
- included[possibleClassName] = true;
- classNames.push(possibleClassName);
- }
- }
- }
- }
- }
- // If the dynamic dependency feature is not being used, throw an error
- // if the dependencies are not defined
- if (classNames.length > 0) {
- if (!this.config.enabled) {
- throw new Error("Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " +
- "Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + classNames.join(', '));
- }
- }
- else {
- callback.call(scope);
- return this;
- }
- syncModeEnabled = this.syncModeEnabled;
- if (!syncModeEnabled) {
- queue.push({
- requires: classNames.slice(), // this array will be modified as the queue is processed,
- // so we need a copy of it
- callback: callback,
- scope: scope
- });
- }
- ln = classNames.length;
- for (i = 0; i < ln; i++) {
- className = classNames[i];
- filePath = this.getPath(className);
- // If we are synchronously loading a file that has already been asynchronously loaded before
- // we need to destroy the script tag and revert the count
- // This file will then be forced loaded in synchronous
- if (syncModeEnabled && isClassFileLoaded.hasOwnProperty(className)) {
- this.numPendingFiles--;
- this.removeScriptElement(filePath);
- delete isClassFileLoaded[className];
- }
- if (!isClassFileLoaded.hasOwnProperty(className)) {
- isClassFileLoaded[className] = false;
- classNameToFilePathMap[className] = filePath;
- this.numPendingFiles++;
- this.loadScriptFile(
- filePath,
- pass(this.onFileLoaded, [className, filePath], this),
- pass(this.onFileLoadError, [className, filePath]),
- this,
- syncModeEnabled
- );
- }
- }
- if (syncModeEnabled) {
- callback.call(scope);
- if (ln === 1) {
- return Manager.get(className);
- }
- }
- return this;
- },
- /**
- * @private
- * @param {String} className
- * @param {String} filePath
- */
- onFileLoaded: function(className, filePath) {
- this.numLoadedFiles++;
- this.isClassFileLoaded[className] = true;
- this.isFileLoaded[filePath] = true;
- this.numPendingFiles--;
- if (this.numPendingFiles === 0) {
- this.refreshQueue();
- }
- //<debug>
- if (!this.syncModeEnabled && this.numPendingFiles === 0 && this.isLoading && !this.hasFileLoadError) {
- var queue = this.queue,
- missingClasses = [],
- missingPaths = [],
- requires,
- i, ln, j, subLn;
- for (i = 0,ln = queue.length; i < ln; i++) {
- requires = queue[i].requires;
- for (j = 0,subLn = requires.length; j < subLn; j++) {
- if (this.isClassFileLoaded[requires[j]]) {
- missingClasses.push(requires[j]);
- }
- }
- }
- if (missingClasses.length < 1) {
- return;
- }
- missingClasses = Ext.Array.filter(Ext.Array.unique(missingClasses), function(item) {
- return !this.requiresMap.hasOwnProperty(item);
- }, this);
- for (i = 0,ln = missingClasses.length; i < ln; i++) {
- missingPaths.push(this.classNameToFilePathMap[missingClasses[i]]);
- }
- throw new Error("The following classes are not declared even if their files have been " +
- "loaded: '" + missingClasses.join("', '") + "'. Please check the source code of their " +
- "corresponding files for possible typos: '" + missingPaths.join("', '"));
- }
- //</debug>
- },
- /**
- * @private
- */
- onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
- this.numPendingFiles--;
- this.hasFileLoadError = true;
- //<debug error>
- throw new Error("[Ext.Loader] " + errorMessage);
- //</debug>
- },
- /**
- * @private
- */
- addOptionalRequires: function(requires) {
- var optionalRequires = this.optionalRequires,
- i, ln, require;
- requires = arrayFrom(requires);
- for (i = 0, ln = requires.length; i < ln; i++) {
- require = requires[i];
- arrayInclude(optionalRequires, require);
- }
- return this;
- },
- /**
- * @private
- */
- triggerReady: function(force) {
- var readyListeners = this.readyListeners,
- optionalRequires = this.optionalRequires,
- listener;
- if (this.isLoading || force) {
- this.isLoading = false;
- if (optionalRequires.length !== 0) {
- // Clone then empty the array to eliminate potential recursive loop issue
- optionalRequires = optionalRequires.slice();
- // Empty the original array
- this.optionalRequires.length = 0;
- this.require(optionalRequires, pass(this.triggerReady, [true], this), this);
- return this;
- }
- while (readyListeners.length) {
- listener = readyListeners.shift();
- listener.fn.call(listener.scope);
- if (this.isLoading) {
- return this;
- }
- }
- }
- return this;
- },
- // duplicate definition (documented above)
- onReady: function(fn, scope, withDomReady, options) {
- var oldFn;
- if (withDomReady !== false && Ext.onDocumentReady) {
- oldFn = fn;
- fn = function() {
- Ext.onDocumentReady(oldFn, scope, options);
- };
- }
- if (!this.isLoading) {
- fn.call(scope);
- }
- else {
- this.readyListeners.push({
- fn: fn,
- scope: scope
- });
- }
- },
- /**
- * @private
- * @param {String} className
- */
- historyPush: function(className) {
- var isInHistory = this.isInHistory;
- if (className && this.isClassFileLoaded.hasOwnProperty(className) && !isInHistory[className]) {
- isInHistory[className] = true;
- this.history.push(className);
- }
- return this;
- }
- });
- //</feature>
- /**
- * Convenient alias of {@link Ext.Loader#require}. Please see the introduction documentation of
- * {@link Ext.Loader} for examples.
- * @member Ext
- * @method require
- * @inheritdoc Ext.Loader#require
- */
- Ext.require = alias(Loader, 'require');
- /**
- * Synchronous version of {@link Ext#require}, convenient alias of {@link Ext.Loader#syncRequire}.
- * @member Ext
- * @method syncRequire
- * @inheritdoc Ext.Loader#syncRequire
- */
- Ext.syncRequire = alias(Loader, 'syncRequire');
- /**
- * Convenient shortcut to {@link Ext.Loader#exclude}.
- * @member Ext
- * @method exclude
- * @inheritdoc Ext.Loader#exclude
- */
- Ext.exclude = alias(Loader, 'exclude');
- /**
- * Adds a listener to be notified when the document is ready and all dependencies are loaded.
- *
- * @param {Function} fn The method the event invokes.
- * @param {Object} [scope] The scope in which the handler function executes. Defaults to the browser window.
- * @param {Boolean} [options] Options object as passed to {@link Ext.Element#addListener}. It is recommended
- * that the options `{single: true}` be used so that the handler is removed on first invocation.
- * @member Ext
- * @method onReady
- */
- Ext.onReady = function(fn, scope, options) {
- Loader.onReady(fn, scope, true, options);
- };
- Class.registerPreprocessor('loader', function(cls, data, hooks, continueFn) {
- var me = this,
- dependencies = [],
- className = Manager.getName(cls),
- i, j, ln, subLn, value, propertyName, propertyValue;
- /*
- Loop through the dependencyProperties, look for string class names and push
- them into a stack, regardless of whether the property's value is a string, array or object. For example:
- {
- extend: 'Ext.MyClass',
- requires: ['Ext.some.OtherClass'],
- mixins: {
- observable: 'Ext.mixin.Observable';
- }
- }
- which will later be transformed into:
- {
- extend: Ext.MyClass,
- requires: [Ext.some.OtherClass],
- mixins: {
- observable: Ext.mixin.Observable;
- }
- }
- */
- for (i = 0,ln = dependencyProperties.length; i < ln; i++) {
- propertyName = dependencyProperties[i];
- if (data.hasOwnProperty(propertyName)) {
- propertyValue = data[propertyName];
- if (typeof propertyValue == 'string') {
- dependencies.push(propertyValue);
- }
- else if (propertyValue instanceof Array) {
- for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
- value = propertyValue[j];
- if (typeof value == 'string') {
- dependencies.push(value);
- }
- }
- }
- else if (typeof propertyValue != 'function') {
- for (j in propertyValue) {
- if (propertyValue.hasOwnProperty(j)) {
- value = propertyValue[j];
- if (typeof value == 'string') {
- dependencies.push(value);
- }
- }
- }
- }
- }
- }
- if (dependencies.length === 0) {
- return;
- }
- //<feature classSystem.loader>
- //<debug error>
- var deadlockPath = [],
- requiresMap = Loader.requiresMap,
- detectDeadlock;
- /*
- Automatically detect deadlocks before-hand,
- will throw an error with detailed path for ease of debugging. Examples of deadlock cases:
- - A extends B, then B extends A
- - A requires B, B requires C, then C requires A
- The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks
- no matter how deep the path is.
- */
- if (className) {
- requiresMap[className] = dependencies;
- //<debug>
- if (!Loader.requiredByMap) Loader.requiredByMap = {};
- Ext.Array.each(dependencies, function(dependency){
- if (!Loader.requiredByMap[dependency]) Loader.requiredByMap[dependency] = [];
- Loader.requiredByMap[dependency].push(className);
- });
- //</debug>
- detectDeadlock = function(cls) {
- deadlockPath.push(cls);
- if (requiresMap[cls]) {
- if (Ext.Array.contains(requiresMap[cls], className)) {
- throw new Error("Deadlock detected while loading dependencies! '" + className + "' and '" +
- deadlockPath[1] + "' " + "mutually require each other. Path: " +
- deadlockPath.join(' -> ') + " -> " + deadlockPath[0]);
- }
- for (i = 0,ln = requiresMap[cls].length; i < ln; i++) {
- detectDeadlock(requiresMap[cls][i]);
- }
- }
- };
- detectDeadlock(className);
- }
- //</debug>
- //</feature>
- Loader.require(dependencies, function() {
- for (i = 0,ln = dependencyProperties.length; i < ln; i++) {
- propertyName = dependencyProperties[i];
- if (data.hasOwnProperty(propertyName)) {
- propertyValue = data[propertyName];
- if (typeof propertyValue == 'string') {
- data[propertyName] = Manager.get(propertyValue);
- }
- else if (propertyValue instanceof Array) {
- for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
- value = propertyValue[j];
- if (typeof value == 'string') {
- data[propertyName][j] = Manager.get(value);
- }
- }
- }
- else if (typeof propertyValue != 'function') {
- for (var k in propertyValue) {
- if (propertyValue.hasOwnProperty(k)) {
- value = propertyValue[k];
- if (typeof value == 'string') {
- data[propertyName][k] = Manager.get(value);
- }
- }
- }
- }
- }
- }
- continueFn.call(me, cls, data, hooks);
- });
- return false;
- }, true, 'after', 'className');
- //<feature classSystem.loader>
- /**
- * @cfg {String[]} uses
- * @member Ext.Class
- * List of optional classes to load together with this class. These aren't necessarily loaded before
- * this class is created, but are guaranteed to be available before Ext.onReady listeners are
- * invoked
- */
- Manager.registerPostprocessor('uses', function(name, cls, data) {
- var uses = arrayFrom(data.uses),
- items = [],
- i, ln, item;
- for (i = 0,ln = uses.length; i < ln; i++) {
- item = uses[i];
- if (typeof item == 'string') {
- items.push(item);
- }
- }
- Loader.addOptionalRequires(items);
- });
- Manager.onCreated(function(className) {
- this.historyPush(className);
- }, Loader);
- //</feature>
- })(Ext.ClassManager, Ext.Class, Ext.Function.flexSetter, Ext.Function.alias,
- Ext.Function.pass, Ext.Array.from, Ext.Array.erase, Ext.Array.include);
- // initalize the default path of the framework
- // trimmed down version of sench-touch-debug-suffix.js
- // with alias / alternates removed, as those are handled separately by
- // compiler-generated metadata
- (function() {
- var scripts = document.getElementsByTagName('script'),
- currentScript = scripts[scripts.length - 1],
- src = currentScript.src,
- path = src.substring(0, src.lastIndexOf('/') + 1),
- Loader = Ext.Loader;
- //<debug>
- // if we're running in dev mode out of the repo src tree, then this
- // file will potentially be loaded from the touch/src/core/class folder
- // so we'll need to adjust for that
- if(src.indexOf("src/core/class/") != -1) {
- path = path + "../../../";
- }
- //</debug>
-
- Loader.setConfig({
- enabled: true,
- disableCaching: !/[?&](cache|breakpoint)/i.test(location.search),
- paths: {
- 'Ext' : path + 'src'
- }
- });
-
- })();
- //@tag dom,core
- //@define Ext.EventManager
- //@define Ext.core.EventManager
- //@require Ext.Loader
- /**
- * @class Ext.EventManager
- *
- * This object has been deprecated in Sencha Touch 2.0.0. Please refer to the method documentation for specific alternatives.
- *
- * @deprecated 2.0.0
- * @singleton
- * @private
- */
- //@tag dom,core
- //@define Ext-more
- //@require Ext.EventManager
- /**
- * @class Ext
- *
- * Ext is the global namespace for the whole Sencha Touch framework. Every class, function and configuration for the
- * whole framework exists under this single global variable. The Ext singleton itself contains a set of useful helper
- * functions (like {@link #apply}, {@link #min} and others), but most of the framework that you use day to day exists
- * in specialized classes (for example {@link Ext.Panel}, {@link Ext.Carousel} and others).
- *
- * If you are new to Sencha Touch we recommend starting with the [Getting Started Guide][getting_started] to
- * get a feel for how the framework operates. After that, use the more focused guides on subjects like panels, forms and data
- * to broaden your understanding. The MVC guides take you through the process of building full applications using the
- * framework, and detail how to deploy them to production.
- *
- * The functions listed below are mostly utility functions used internally by many of the classes shipped in the
- * framework, but also often useful in your own apps.
- *
- * A method that is crucial to beginning your application is {@link #setup Ext.setup}. Please refer to it's documentation, or the
- * [Getting Started Guide][getting_started] as a reference on beginning your application.
- *
- * Ext.setup({
- * onReady: function() {
- * Ext.Viewport.add({
- * xtype: 'component',
- * html: 'Hello world!'
- * });
- * }
- * });
- *
- * [getting_started]: #!/guide/getting_started
- */
- Ext.setVersion('touch', '2.1.0');
- Ext.apply(Ext, {
- /**
- * The version of the framework
- * @type String
- */
- version: Ext.getVersion('touch'),
- /**
- * @private
- */
- idSeed: 0,
- /**
- * Repaints the whole page. This fixes frequently encountered painting issues in mobile Safari.
- */
- repaint: function() {
- var mask = Ext.getBody().createChild({
- cls: Ext.baseCSSPrefix + 'mask ' + Ext.baseCSSPrefix + 'mask-transparent'
- });
- setTimeout(function() {
- mask.destroy();
- }, 0);
- },
- /**
- * Generates unique ids. If the element already has an `id`, it is unchanged.
- * @param {Mixed} el (optional) The element to generate an id for.
- * @param {String} [prefix=ext-gen] (optional) The `id` prefix.
- * @return {String} The generated `id`.
- */
- id: function(el, prefix) {
- if (el && el.id) {
- return el.id;
- }
- el = Ext.getDom(el) || {};
- if (el === document || el === document.documentElement) {
- el.id = 'ext-application';
- }
- else if (el === document.body) {
- el.id = 'ext-viewport';
- }
- else if (el === window) {
- el.id = 'ext-window';
- }
- el.id = el.id || ((prefix || 'ext-element-') + (++Ext.idSeed));
- return el.id;
- },
- /**
- * Returns the current document body as an {@link Ext.Element}.
- * @return {Ext.Element} The document body.
- */
- getBody: function() {
- if (!Ext.documentBodyElement) {
- if (!document.body) {
- throw new Error("[Ext.getBody] document.body does not exist at this point");
- }
- Ext.documentBodyElement = Ext.get(document.body);
- }
- return Ext.documentBodyElement;
- },
- /**
- * Returns the current document head as an {@link Ext.Element}.
- * @return {Ext.Element} The document head.
- */
- getHead: function() {
- if (!Ext.documentHeadElement) {
- Ext.documentHeadElement = Ext.get(document.head || document.getElementsByTagName('head')[0]);
- }
- return Ext.documentHeadElement;
- },
- /**
- * Returns the current HTML document object as an {@link Ext.Element}.
- * @return {Ext.Element} The document.
- */
- getDoc: function() {
- if (!Ext.documentElement) {
- Ext.documentElement = Ext.get(document);
- }
- return Ext.documentElement;
- },
- /**
- * This is shorthand reference to {@link Ext.ComponentMgr#get}.
- * Looks up an existing {@link Ext.Component Component} by {@link Ext.Component#getId id}
- * @param {String} id The component {@link Ext.Component#getId id}
- * @return {Ext.Component} The Component, `undefined` if not found, or `null` if a
- * Class was found.
- */
- getCmp: function(id) {
- return Ext.ComponentMgr.get(id);
- },
- /**
- * Copies a set of named properties from the source object to the destination object.
- *
- * Example:
- *
- * ImageComponent = Ext.extend(Ext.Component, {
- * initComponent: function() {
- * this.autoEl = { tag: 'img' };
- * MyComponent.superclass.initComponent.apply(this, arguments);
- * this.initialBox = Ext.copyTo({}, this.initialConfig, 'x,y,width,height');
- * }
- * });
- *
- * Important note: To borrow class prototype methods, use {@link Ext.Base#borrow} instead.
- *
- * @param {Object} dest The destination object.
- * @param {Object} source The source object.
- * @param {String/String[]} names Either an Array of property names, or a comma-delimited list
- * of property names to copy.
- * @param {Boolean} [usePrototypeKeys=false] (optional) Pass `true` to copy keys off of the prototype as well as the instance.
- * @return {Object} The modified object.
- */
- copyTo : function(dest, source, names, usePrototypeKeys) {
- if (typeof names == 'string') {
- names = names.split(/[,;\s]/);
- }
- Ext.each (names, function(name) {
- if (usePrototypeKeys || source.hasOwnProperty(name)) {
- dest[name] = source[name];
- }
- }, this);
- return dest;
- },
- /**
- * Attempts to destroy any objects passed to it by removing all event listeners, removing them from the
- * DOM (if applicable) and calling their destroy functions (if available). This method is primarily
- * intended for arguments of type {@link Ext.Element} and {@link Ext.Component}.
- * Any number of elements and/or components can be passed into this function in a single
- * call as separate arguments.
- * @param {Mixed...} args An {@link Ext.Element}, {@link Ext.Component}, or an Array of either of these to destroy.
- */
- destroy: function() {
- var args = arguments,
- ln = args.length,
- i, item;
- for (i = 0; i < ln; i++) {
- item = args[i];
- if (item) {
- if (Ext.isArray(item)) {
- this.destroy.apply(this, item);
- }
- else if (Ext.isFunction(item.destroy)) {
- item.destroy();
- }
- }
- }
- },
- /**
- * Return the dom node for the passed String (id), dom node, or Ext.Element.
- * Here are some examples:
- *
- * // gets dom node based on id
- * var elDom = Ext.getDom('elId');
- *
- * // gets dom node based on the dom node
- * var elDom1 = Ext.getDom(elDom);
- *
- * // If we don't know if we are working with an
- * // Ext.Element or a dom node use Ext.getDom
- * function(el){
- * var dom = Ext.getDom(el);
- * // do something with the dom node
- * }
- *
- * __Note:__ the dom node to be found actually needs to exist (be rendered, etc)
- * when this method is called to be successful.
- * @param {Mixed} el
- * @return {HTMLElement}
- */
- getDom: function(el) {
- if (!el || !document) {
- return null;
- }
- return el.dom ? el.dom : (typeof el == 'string' ? document.getElementById(el) : el);
- },
- /**
- * Removes this element from the document, removes all DOM event listeners, and deletes the cache reference.
- * All DOM event listeners are removed from this element.
- * @param {HTMLElement} node The node to remove.
- */
- removeNode: function(node) {
- if (node && node.parentNode && node.tagName != 'BODY') {
- Ext.get(node).clearListeners();
- node.parentNode.removeChild(node);
- delete Ext.cache[node.id];
- }
- },
- /**
- * @private
- */
- defaultSetupConfig: {
- eventPublishers: {
- dom: {
- xclass: 'Ext.event.publisher.Dom'
- },
- touchGesture: {
- xclass: 'Ext.event.publisher.TouchGesture',
- recognizers: {
- drag: {
- xclass: 'Ext.event.recognizer.Drag'
- },
- tap: {
- xclass: 'Ext.event.recognizer.Tap'
- },
- doubleTap: {
- xclass: 'Ext.event.recognizer.DoubleTap'
- },
- longPress: {
- xclass: 'Ext.event.recognizer.LongPress'
- },
- swipe: {
- xclass: 'Ext.event.recognizer.HorizontalSwipe'
- },
- pinch: {
- xclass: 'Ext.event.recognizer.Pinch'
- },
- rotate: {
- xclass: 'Ext.event.recognizer.Rotate'
- }
- }
- },
- componentDelegation: {
- xclass: 'Ext.event.publisher.ComponentDelegation'
- },
- componentPaint: {
- xclass: 'Ext.event.publisher.ComponentPaint'
- },
- // componentSize: {
- // xclass: 'Ext.event.publisher.ComponentSize'
- // },
- elementPaint: {
- xclass: 'Ext.event.publisher.ElementPaint'
- },
- elementSize: {
- xclass: 'Ext.event.publisher.ElementSize'
- }
- },
- //<feature logger>
- logger: {
- enabled: true,
- xclass: 'Ext.log.Logger',
- minPriority: 'deprecate',
- writers: {
- console: {
- xclass: 'Ext.log.writer.Console',
- throwOnErrors: true,
- formatter: {
- xclass: 'Ext.log.formatter.Default'
- }
- }
- }
- },
- //</feature>
- animator: {
- xclass: 'Ext.fx.Runner'
- },
- viewport: {
- xclass: 'Ext.viewport.Viewport'
- }
- },
- /**
- * @private
- */
- isSetup: false,
- /**
- * This indicate the start timestamp of current cycle.
- * It is only reliable during dom-event-initiated cycles and
- * {@link Ext.draw.Animator} initiated cycles.
- */
- frameStartTime: +new Date(),
- /**
- * @private
- */
- setupListeners: [],
- /**
- * @private
- */
- onSetup: function(fn, scope) {
- if (Ext.isSetup) {
- fn.call(scope);
- }
- else {
- Ext.setupListeners.push({
- fn: fn,
- scope: scope
- });
- }
- },
- /**
- * Ext.setup() is the entry-point to initialize a Sencha Touch application. Note that if your application makes
- * use of MVC architecture, use {@link Ext#application} instead.
- *
- * This method accepts one single argument in object format. The most basic use of Ext.setup() is as follows:
- *
- * Ext.setup({
- * onReady: function() {
- * // ...
- * }
- * });
- *
- * This sets up the viewport, initializes the event system, instantiates a default animation runner, and a default
- * logger (during development). When all of that is ready, it invokes the callback function given to the `onReady` key.
- *
- * The default scope (`this`) of `onReady` is the main viewport. By default the viewport instance is stored in
- * {@link Ext.Viewport}. For example, this snippet adds a 'Hello World' button that is centered on the screen:
- *
- * Ext.setup({
- * onReady: function() {
- * this.add({
- * xtype: 'button',
- * centered: true,
- * text: 'Hello world!'
- * }); // Equivalent to Ext.Viewport.add(...)
- * }
- * });
- *
- * @param {Object} config An object with the following config options:
- *
- * @param {Function} config.onReady
- * A function to be called when the application is ready. Your application logic should be here.
- *
- * @param {Object} config.viewport
- * A custom config object to be used when creating the global {@link Ext.Viewport} instance. Please refer to the
- * {@link Ext.Viewport} documentation for more information.
- *
- * Ext.setup({
- * viewport: {
- * width: 500,
- * height: 500
- * },
- * onReady: function() {
- * // ...
- * }
- * });
- *
- * @param {String/Object} config.icon
- * Specifies a set of URLs to the application icon for different device form factors. This icon is displayed
- * when the application is added to the device's Home Screen.
- *
- * Ext.setup({
- * icon: {
- * 57: 'resources/icons/Icon.png',
- * 72: 'resources/icons/Icon~ipad.png',
- * 114: 'resources/icons/Icon@2x.png',
- * 144: 'resources/icons/Icon~ipad@2x.png'
- * },
- * onReady: function() {
- * // ...
- * }
- * });
- *
- * Each key represents the dimension of the icon as a square shape. For example: '57' is the key for a 57 x 57
- * icon image. Here is the breakdown of each dimension and its device target:
- *
- * - 57: Non-retina iPhone, iPod touch, and all Android devices
- * - 72: Retina iPhone and iPod touch
- * - 114: Non-retina iPad (first and second generation)
- * - 144: Retina iPad (third generation)
- *
- * Note that the dimensions of the icon images must be exactly 57x57, 72x72, 114x114 and 144x144 respectively.
- *
- * It is highly recommended that you provide all these different sizes to accommodate a full range of
- * devices currently available. However if you only have one icon in one size, make it 57x57 in size and
- * specify it as a string value. This same icon will be used on all supported devices.
- *
- * Ext.setup({
- * icon: 'resources/icons/Icon.png',
- * onReady: function() {
- * // ...
- * }
- * });
- *
- * @param {Object} config.startupImage
- * Specifies a set of URLs to the application startup images for different device form factors. This image is
- * displayed when the application is being launched from the Home Screen icon. Note that this currently only applies
- * to iOS devices.
- *
- * Ext.setup({
- * startupImage: {
- * '320x460': 'resources/startup/320x460.jpg',
- * '640x920': 'resources/startup/640x920.png',
- * '640x1096': 'resources/startup/640x1096.png',
- * '768x1004': 'resources/startup/768x1004.png',
- * '748x1024': 'resources/startup/748x1024.png',
- * '1536x2008': 'resources/startup/1536x2008.png',
- * '1496x2048': 'resources/startup/1496x2048.png'
- * },
- * onReady: function() {
- * // ...
- * }
- * });
- *
- * Each key represents the dimension of the image. For example: '320x460' is the key for a 320px x 460px image.
- * Here is the breakdown of each dimension and its device target:
- *
- * - 320x460: Non-retina iPhone, iPod touch, and all Android devices
- * - 640x920: Retina iPhone and iPod touch
- * - 640x1096: iPhone 5 and iPod touch (fifth generation)
- * - 768x1004: Non-retina iPad (first and second generation) in portrait orientation
- * - 748x1024: Non-retina iPad (first and second generation) in landscape orientation
- * - 1536x2008: Retina iPad (third generation) in portrait orientation
- * - 1496x2048: Retina iPad (third generation) in landscape orientation
- *
- * Please note that there's no automatic fallback mechanism for the startup images. In other words, if you don't specify
- * a valid image for a certain device, nothing will be displayed while the application is being launched on that device.
- *
- * @param {Boolean} isIconPrecomposed
- * True to not having a glossy effect added to the icon by the OS, which will preserve its exact look. This currently
- * only applies to iOS devices.
- *
- * @param {String} statusBarStyle
- * The style of status bar to be shown on applications added to the iOS home screen. Valid options are:
- *
- * * `default`
- * * `black`
- * * `black-translucent`
- *
- * @param {String[]} config.requires
- * An array of required classes for your application which will be automatically loaded before `onReady` is invoked.
- * Please refer to {@link Ext.Loader} and {@link Ext.Loader#require} for more information.
- *
- * Ext.setup({
- * requires: ['Ext.Button', 'Ext.tab.Panel'],
- * onReady: function() {
- * // ...
- * }
- * });
- *
- * @param {Object} config.eventPublishers
- * Sencha Touch, by default, includes various {@link Ext.event.recognizer.Recognizer} subclasses to recognize events fired
- * in your application. The list of default recognizers can be found in the documentation for
- * {@link Ext.event.recognizer.Recognizer}.
- *
- * To change the default recognizers, you can use the following syntax:
- *
- * Ext.setup({
- * eventPublishers: {
- * touchGesture: {
- * recognizers: {
- * swipe: {
- * // this will include both vertical and horizontal swipe recognizers
- * xclass: 'Ext.event.recognizer.Swipe'
- * }
- * }
- * }
- * },
- * onReady: function() {
- * // ...
- * }
- * });
- *
- * You can also disable recognizers using this syntax:
- *
- * Ext.setup({
- * eventPublishers: {
- * touchGesture: {
- * recognizers: {
- * swipe: null,
- * pinch: null,
- * rotate: null
- * }
- * }
- * },
- * onReady: function() {
- * // ...
- * }
- * });
- */
- setup: function(config) {
- var defaultSetupConfig = Ext.defaultSetupConfig,
- emptyFn = Ext.emptyFn,
- onReady = config.onReady || emptyFn,
- onUpdated = config.onUpdated || emptyFn,
- scope = config.scope,
- requires = Ext.Array.from(config.requires),
- extOnReady = Ext.onReady,
- head = Ext.getHead(),
- callback, viewport, precomposed;
- Ext.setup = function() {
- throw new Error("Ext.setup has already been called before");
- };
- delete config.requires;
- delete config.onReady;
- delete config.onUpdated;
- delete config.scope;
- Ext.require(['Ext.event.Dispatcher']);
- callback = function() {
- var listeners = Ext.setupListeners,
- ln = listeners.length,
- i, listener;
- delete Ext.setupListeners;
- Ext.isSetup = true;
- for (i = 0; i < ln; i++) {
- listener = listeners[i];
- listener.fn.call(listener.scope);
- }
- Ext.onReady = extOnReady;
- Ext.onReady(onReady, scope);
- };
- Ext.onUpdated = onUpdated;
- Ext.onReady = function(fn, scope) {
- var origin = onReady;
- onReady = function() {
- origin();
- Ext.onReady(fn, scope);
- };
- };
- config = Ext.merge({}, defaultSetupConfig, config);
- Ext.onDocumentReady(function() {
- Ext.factoryConfig(config, function(data) {
- Ext.event.Dispatcher.getInstance().setPublishers(data.eventPublishers);
- if (data.logger) {
- Ext.Logger = data.logger;
- }
- if (data.animator) {
- Ext.Animator = data.animator;
- }
- if (data.viewport) {
- Ext.Viewport = viewport = data.viewport;
- if (!scope) {
- scope = viewport;
- }
- Ext.require(requires, function() {
- Ext.Viewport.on('ready', callback, null, {single: true});
- });
- }
- else {
- Ext.require(requires, callback);
- }
- });
- });
- function addMeta(name, content) {
- var meta = document.createElement('meta');
- meta.setAttribute('name', name);
- meta.setAttribute('content', content);
- head.append(meta);
- }
- function addIcon(href, sizes, precomposed) {
- var link = document.createElement('link');
- link.setAttribute('rel', 'apple-touch-icon' + (precomposed ? '-precomposed' : ''));
- link.setAttribute('href', href);
- if (sizes) {
- link.setAttribute('sizes', sizes);
- }
- head.append(link);
- }
- function addStartupImage(href, media) {
- var link = document.createElement('link');
- link.setAttribute('rel', 'apple-touch-startup-image');
- link.setAttribute('href', href);
- if (media) {
- link.setAttribute('media', media);
- }
- head.append(link);
- }
- var icon = config.icon,
- isIconPrecomposed = Boolean(config.isIconPrecomposed),
- startupImage = config.startupImage || {},
- statusBarStyle = config.statusBarStyle,
- devicePixelRatio = window.devicePixelRatio || 1;
- if (navigator.standalone) {
- addMeta('viewport', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0');
- }
- else {
- addMeta('viewport', 'initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0');
- }
- addMeta('apple-mobile-web-app-capable', 'yes');
- addMeta('apple-touch-fullscreen', 'yes');
- // status bar style
- if (statusBarStyle) {
- addMeta('apple-mobile-web-app-status-bar-style', statusBarStyle);
- }
- if (Ext.isString(icon)) {
- icon = {
- 57: icon,
- 72: icon,
- 114: icon,
- 144: icon
- };
- }
- else if (!icon) {
- icon = {};
- }
- if (Ext.os.is.iPad) {
- if (devicePixelRatio >= 2) {
- // Retina iPad - Landscape
- if ('1496x2048' in startupImage) {
- addStartupImage(startupImage['1496x2048'], '(orientation: landscape)');
- }
- // Retina iPad - Portrait
- if ('1536x2008' in startupImage) {
- addStartupImage(startupImage['1536x2008'], '(orientation: portrait)');
- }
- // Retina iPad
- if ('144' in icon) {
- addIcon(icon['144'], '144x144', isIconPrecomposed);
- }
- }
- else {
- // Non-Retina iPad - Landscape
- if ('748x1024' in startupImage) {
- addStartupImage(startupImage['748x1024'], '(orientation: landscape)');
- }
- // Non-Retina iPad - Portrait
- if ('768x1004' in startupImage) {
- addStartupImage(startupImage['768x1004'], '(orientation: portrait)');
- }
- // Non-Retina iPad
- if ('72' in icon) {
- addIcon(icon['72'], '72x72', isIconPrecomposed);
- }
- }
- }
- else {
- // Retina iPhone, iPod touch with iOS version >= 4.3
- if (devicePixelRatio >= 2 && Ext.os.version.gtEq('4.3')) {
- if (Ext.os.is.iPhone5) {
- addStartupImage(startupImage['640x1096']);
- } else {
- addStartupImage(startupImage['640x920']);
- }
- // Retina iPhone and iPod touch
- if ('114' in icon) {
- addIcon(icon['114'], '114x114', isIconPrecomposed);
- }
- }
- else {
- addStartupImage(startupImage['320x460']);
- // Non-Retina iPhone, iPod touch, and Android devices
- if ('57' in icon) {
- addIcon(icon['57'], null, isIconPrecomposed);
- }
- }
- }
- },
- /**
- * @member Ext
- * @method application
- *
- * Loads Ext.app.Application class and starts it up with given configuration after the page is ready.
- *
- * Ext.application({
- * launch: function() {
- * alert('Application launched!');
- * }
- * });
- *
- * See {@link Ext.app.Application} for details.
- *
- * @param {Object} config An object with the following config options:
- *
- * @param {Function} config.launch
- * A function to be called when the application is ready. Your application logic should be here. Please see {@link Ext.app.Application}
- * for details.
- *
- * @param {Object} config.viewport
- * An object to be used when creating the global {@link Ext.Viewport} instance. Please refer to the {@link Ext.Viewport}
- * documentation for more information.
- *
- * Ext.application({
- * viewport: {
- * layout: 'vbox'
- * },
- * launch: function() {
- * Ext.Viewport.add({
- * flex: 1,
- * html: 'top (flex: 1)'
- * });
- *
- * Ext.Viewport.add({
- * flex: 4,
- * html: 'bottom (flex: 4)'
- * });
- * }
- * });
- *
- * @param {String/Object} config.icon
- * Specifies a set of URLs to the application icon for different device form factors. This icon is displayed
- * when the application is added to the device's Home Screen.
- *
- * Ext.application({
- * icon: {
- * 57: 'resources/icons/Icon.png',
- * 72: 'resources/icons/Icon~ipad.png',
- * 114: 'resources/icons/Icon@2x.png',
- * 144: 'resources/icons/Icon~ipad@2x.png'
- * },
- * launch: function() {
- * // ...
- * }
- * });
- *
- * Each key represents the dimension of the icon as a square shape. For example: '57' is the key for a 57 x 57
- * icon image. Here is the breakdown of each dimension and its device target:
- *
- * - 57: Non-retina iPhone, iPod touch, and all Android devices
- * - 72: Retina iPhone and iPod touch
- * - 114: Non-retina iPad (first and second generation)
- * - 144: Retina iPad (third generation)
- *
- * Note that the dimensions of the icon images must be exactly 57x57, 72x72, 114x114 and 144x144 respectively.
- *
- * It is highly recommended that you provide all these different sizes to accommodate a full range of
- * devices currently available. However if you only have one icon in one size, make it 57x57 in size and
- * specify it as a string value. This same icon will be used on all supported devices.
- *
- * Ext.setup({
- * icon: 'resources/icons/Icon.png',
- * onReady: function() {
- * // ...
- * }
- * });
- *
- * @param {Object} config.startupImage
- * Specifies a set of URLs to the application startup images for different device form factors. This image is
- * displayed when the application is being launched from the Home Screen icon. Note that this currently only applies
- * to iOS devices.
- *
- * Ext.application({
- * startupImage: {
- * '320x460': 'resources/startup/320x460.jpg',
- * '640x920': 'resources/startup/640x920.png',
- * '640x1096': 'resources/startup/640x1096.png',
- * '768x1004': 'resources/startup/768x1004.png',
- * '748x1024': 'resources/startup/748x1024.png',
- * '1536x2008': 'resources/startup/1536x2008.png',
- * '1496x2048': 'resources/startup/1496x2048.png'
- * },
- * launch: function() {
- * // ...
- * }
- * });
- *
- * Each key represents the dimension of the image. For example: '320x460' is the key for a 320px x 460px image.
- * Here is the breakdown of each dimension and its device target:
- *
- * - 320x460: Non-retina iPhone, iPod touch, and all Android devices
- * - 640x920: Retina iPhone and iPod touch
- * - 640x1096: iPhone 5 and iPod touch (fifth generation)
- * - 768x1004: Non-retina iPad (first and second generation) in portrait orientation
- * - 748x1024: Non-retina iPad (first and second generation) in landscape orientation
- * - 1536x2008: Retina iPad (third generation) in portrait orientation
- * - 1496x2048: Retina iPad (third generation) in landscape orientation
- *
- * Please note that there's no automatic fallback mechanism for the startup images. In other words, if you don't specify
- * a valid image for a certain device, nothing will be displayed while the application is being launched on that device.
- *
- * @param {Boolean} config.isIconPrecomposed
- * True to not having a glossy effect added to the icon by the OS, which will preserve its exact look. This currently
- * only applies to iOS devices.
- *
- * @param {String} config.statusBarStyle
- * The style of status bar to be shown on applications added to the iOS home screen. Valid options are:
- *
- * * `default`
- * * `black`
- * * `black-translucent`
- *
- * @param {String[]} config.requires
- * An array of required classes for your application which will be automatically loaded if {@link Ext.Loader#enabled} is set
- * to `true`. Please refer to {@link Ext.Loader} and {@link Ext.Loader#require} for more information.
- *
- * Ext.application({
- * requires: ['Ext.Button', 'Ext.tab.Panel'],
- * launch: function() {
- * // ...
- * }
- * });
- *
- * @param {Object} config.eventPublishers
- * Sencha Touch, by default, includes various {@link Ext.event.recognizer.Recognizer} subclasses to recognize events fired
- * in your application. The list of default recognizers can be found in the documentation for {@link Ext.event.recognizer.Recognizer}.
- *
- * To change the default recognizers, you can use the following syntax:
- *
- * Ext.application({
- * eventPublishers: {
- * touchGesture: {
- * recognizers: {
- * swipe: {
- * // this will include both vertical and horizontal swipe recognizers
- * xclass: 'Ext.event.recognizer.Swipe'
- * }
- * }
- * }
- * },
- * launch: function() {
- * // ...
- * }
- * });
- *
- * You can also disable recognizers using this syntax:
- *
- * Ext.application({
- * eventPublishers: {
- * touchGesture: {
- * recognizers: {
- * swipe: null,
- * pinch: null,
- * rotate: null
- * }
- * }
- * },
- * launch: function() {
- * // ...
- * }
- * });
- */
- application: function(config) {
- var appName = config.name,
- onReady, scope, requires;
- if (!config) {
- config = {};
- }
- if (!Ext.Loader.config.paths[appName]) {
- Ext.Loader.setPath(appName, config.appFolder || 'app');
- }
- requires = Ext.Array.from(config.requires);
- config.requires = ['Ext.app.Application'];
- onReady = config.onReady;
- scope = config.scope;
- config.onReady = function() {
- config.requires = requires;
- new Ext.app.Application(config);
- if (onReady) {
- onReady.call(scope);
- }
- };
- Ext.setup(config);
- },
- /**
- * @private
- * @param config
- * @param callback
- * @member Ext
- */
- factoryConfig: function(config, callback) {
- var isSimpleObject = Ext.isSimpleObject(config);
- if (isSimpleObject && config.xclass) {
- var className = config.xclass;
- delete config.xclass;
- Ext.require(className, function() {
- Ext.factoryConfig(config, function(cfg) {
- callback(Ext.create(className, cfg));
- });
- });
- return;
- }
- var isArray = Ext.isArray(config),
- keys = [],
- key, value, i, ln;
- if (isSimpleObject || isArray) {
- if (isSimpleObject) {
- for (key in config) {
- if (config.hasOwnProperty(key)) {
- value = config[key];
- if (Ext.isSimpleObject(value) || Ext.isArray(value)) {
- keys.push(key);
- }
- }
- }
- }
- else {
- for (i = 0,ln = config.length; i < ln; i++) {
- value = config[i];
- if (Ext.isSimpleObject(value) || Ext.isArray(value)) {
- keys.push(i);
- }
- }
- }
- i = 0;
- ln = keys.length;
- if (ln === 0) {
- callback(config);
- return;
- }
- function fn(value) {
- config[key] = value;
- i++;
- factory();
- }
- function factory() {
- if (i >= ln) {
- callback(config);
- return;
- }
- key = keys[i];
- value = config[key];
- Ext.factoryConfig(value, fn);
- }
- factory();
- return;
- }
- callback(config);
- },
- /**
- * A global factory method to instantiate a class from a config object. For example, these two calls are equivalent:
- *
- * Ext.factory({ text: 'My Button' }, 'Ext.Button');
- * Ext.create('Ext.Button', { text: 'My Button' });
- *
- * If an existing instance is also specified, it will be updated with the supplied config object. This is useful
- * if you need to either create or update an object, depending on if an instance already exists. For example:
- *
- * var button;
- * button = Ext.factory({ text: 'New Button' }, 'Ext.Button', button); // Button created
- * button = Ext.factory({ text: 'Updated Button' }, 'Ext.Button', button); // Button updated
- *
- * @param {Object} config The config object to instantiate or update an instance with.
- * @param {String} classReference The class to instantiate from.
- * @param {Object} [instance] The instance to update.
- * @param [aliasNamespace]
- * @member Ext
- */
- factory: function(config, classReference, instance, aliasNamespace) {
- var manager = Ext.ClassManager,
- newInstance;
- // If config is falsy or a valid instance, destroy the current instance
- // (if it exists) and replace with the new one
- if (!config || config.isInstance) {
- if (instance && instance !== config) {
- instance.destroy();
- }
- return config;
- }
- if (aliasNamespace) {
- // If config is a string value, treat it as an alias
- if (typeof config == 'string') {
- return manager.instantiateByAlias(aliasNamespace + '.' + config);
- }
- // Same if 'type' is given in config
- else if (Ext.isObject(config) && 'type' in config) {
- return manager.instantiateByAlias(aliasNamespace + '.' + config.type, config);
- }
- }
- if (config === true) {
- return instance || manager.instantiate(classReference);
- }
- //<debug error>
- if (!Ext.isObject(config)) {
- Ext.Logger.error("Invalid config, must be a valid config object");
- }
- //</debug>
- if ('xtype' in config) {
- newInstance = manager.instantiateByAlias('widget.' + config.xtype, config);
- }
- else if ('xclass' in config) {
- newInstance = manager.instantiate(config.xclass, config);
- }
- if (newInstance) {
- if (instance) {
- instance.destroy();
- }
- return newInstance;
- }
- if (instance) {
- return instance.setConfig(config);
- }
- return manager.instantiate(classReference, config);
- },
- /**
- * @private
- * @member Ext
- */
- deprecateClassMember: function(cls, oldName, newName, message) {
- return this.deprecateProperty(cls.prototype, oldName, newName, message);
- },
- /**
- * @private
- * @member Ext
- */
- deprecateClassMembers: function(cls, members) {
- var prototype = cls.prototype,
- oldName, newName;
- for (oldName in members) {
- if (members.hasOwnProperty(oldName)) {
- newName = members[oldName];
- this.deprecateProperty(prototype, oldName, newName);
- }
- }
- },
- /**
- * @private
- * @member Ext
- */
- deprecateProperty: function(object, oldName, newName, message) {
- if (!message) {
- message = "'" + oldName + "' is deprecated";
- }
- if (newName) {
- message += ", please use '" + newName + "' instead";
- }
- if (newName) {
- Ext.Object.defineProperty(object, oldName, {
- get: function() {
- //<debug warn>
- Ext.Logger.deprecate(message, 1);
- //</debug>
- return this[newName];
- },
- set: function(value) {
- //<debug warn>
- Ext.Logger.deprecate(message, 1);
- //</debug>
- this[newName] = value;
- },
- configurable: true
- });
- }
- },
- /**
- * @private
- * @member Ext
- */
- deprecatePropertyValue: function(object, name, value, message) {
- Ext.Object.defineProperty(object, name, {
- get: function() {
- //<debug warn>
- Ext.Logger.deprecate(message, 1);
- //</debug>
- return value;
- },
- configurable: true
- });
- },
- /**
- * @private
- * @member Ext
- */
- deprecateMethod: function(object, name, method, message) {
- object[name] = function() {
- //<debug warn>
- Ext.Logger.deprecate(message, 2);
- //</debug>
- if (method) {
- return method.apply(this, arguments);
- }
- };
- },
- /**
- * @private
- * @member Ext
- */
- deprecateClassMethod: function(cls, name, method, message) {
- if (typeof name != 'string') {
- var from, to;
- for (from in name) {
- if (name.hasOwnProperty(from)) {
- to = name[from];
- Ext.deprecateClassMethod(cls, from, to);
- }
- }
- return;
- }
- var isLateBinding = typeof method == 'string',
- member;
- if (!message) {
- message = "'" + name + "()' is deprecated, please use '" + (isLateBinding ? method : method.name) +
- "()' instead";
- }
- if (isLateBinding) {
- member = function() {
- //<debug warn>
- Ext.Logger.deprecate(message, this);
- //</debug>
- return this[method].apply(this, arguments);
- };
- }
- else {
- member = function() {
- //<debug warn>
- Ext.Logger.deprecate(message, this);
- //</debug>
- return method.apply(this, arguments);
- };
- }
- if (name in cls.prototype) {
- Ext.Object.defineProperty(cls.prototype, name, {
- value: null,
- writable: true,
- configurable: true
- });
- }
- cls.addMember(name, member);
- },
- //<debug>
- /**
- * Useful snippet to show an exact, narrowed-down list of top-level Components that are not yet destroyed.
- * @private
- */
- showLeaks: function() {
- var map = Ext.ComponentManager.all.map,
- leaks = [],
- parent;
- Ext.Object.each(map, function(id, component) {
- while ((parent = component.getParent()) && map.hasOwnProperty(parent.getId())) {
- component = parent;
- }
- if (leaks.indexOf(component) === -1) {
- leaks.push(component);
- }
- });
- console.log(leaks);
- },
- //</debug>
- /**
- * True when the document is fully initialized and ready for action
- * @type Boolean
- * @member Ext
- * @private
- */
- isReady : false,
- /**
- * @private
- * @member Ext
- */
- readyListeners: [],
- /**
- * @private
- * @member Ext
- */
- triggerReady: function() {
- var listeners = Ext.readyListeners,
- i, ln, listener;
- if (!Ext.isReady) {
- Ext.isReady = true;
- for (i = 0,ln = listeners.length; i < ln; i++) {
- listener = listeners[i];
- listener.fn.call(listener.scope);
- }
- delete Ext.readyListeners;
- }
- },
- /**
- * @private
- * @member Ext
- */
- onDocumentReady: function(fn, scope) {
- if (Ext.isReady) {
- fn.call(scope);
- }
- else {
- var triggerFn = Ext.triggerReady;
- Ext.readyListeners.push({
- fn: fn,
- scope: scope
- });
- if (Ext.browser.is.PhoneGap && !Ext.os.is.Desktop) {
- if (!Ext.readyListenerAttached) {
- Ext.readyListenerAttached = true;
- document.addEventListener('deviceready', triggerFn, false);
- }
- }
- else {
- if (document.readyState.match(/interactive|complete|loaded/) !== null) {
- triggerFn();
- }
- else if (!Ext.readyListenerAttached) {
- Ext.readyListenerAttached = true;
- window.addEventListener('DOMContentLoaded', triggerFn, false);
- }
- }
- }
- },
- /**
- * Calls function after specified delay, or right away when delay == 0.
- * @param {Function} callback The callback to execute.
- * @param {Object} scope (optional) The scope to execute in.
- * @param {Array} args (optional) The arguments to pass to the function.
- * @param {Number} delay (optional) Pass a number to delay the call by a number of milliseconds.
- * @member Ext
- */
- callback: function(callback, scope, args, delay) {
- if (Ext.isFunction(callback)) {
- args = args || [];
- scope = scope || window;
- if (delay) {
- Ext.defer(callback, delay, scope, args);
- } else {
- callback.apply(scope, args);
- }
- }
- }
- });
- //<debug>
- Ext.Object.defineProperty(Ext, 'Msg', {
- get: function() {
- Ext.Logger.error("Using Ext.Msg without requiring Ext.MessageBox");
- return null;
- },
- set: function(value) {
- Ext.Object.defineProperty(Ext, 'Msg', {
- value: value
- });
- return value;
- },
- configurable: true
- });
- //</debug>
- //@tag dom,core
- //@require Ext-more
- /**
- * Provides information about browser.
- *
- * Should not be manually instantiated unless for unit-testing.
- * Access the global instance stored in {@link Ext.browser} instead.
- * @private
- */
- Ext.define('Ext.env.Browser', {
- requires: ['Ext.Version'],
- statics: {
- browserNames: {
- ie: 'IE',
- firefox: 'Firefox',
- safari: 'Safari',
- chrome: 'Chrome',
- opera: 'Opera',
- dolfin: 'Dolfin',
- webosbrowser: 'webOSBrowser',
- chromeMobile: 'ChromeMobile',
- silk: 'Silk',
- other: 'Other'
- },
- engineNames: {
- webkit: 'WebKit',
- gecko: 'Gecko',
- presto: 'Presto',
- trident: 'Trident',
- other: 'Other'
- },
- enginePrefixes: {
- webkit: 'AppleWebKit/',
- gecko: 'Gecko/',
- presto: 'Presto/',
- trident: 'Trident/'
- },
- browserPrefixes: {
- ie: 'MSIE ',
- firefox: 'Firefox/',
- chrome: 'Chrome/',
- safari: 'Version/',
- opera: 'Opera/',
- dolfin: 'Dolfin/',
- webosbrowser: 'wOSBrowser/',
- chromeMobile: 'CrMo/',
- silk: 'Silk/'
- }
- },
- styleDashPrefixes: {
- WebKit: '-webkit-',
- Gecko: '-moz-',
- Trident: '-ms-',
- Presto: '-o-',
- Other: ''
- },
- stylePrefixes: {
- WebKit: 'Webkit',
- Gecko: 'Moz',
- Trident: 'ms',
- Presto: 'O',
- Other: ''
- },
- propertyPrefixes: {
- WebKit: 'webkit',
- Gecko: 'moz',
- Trident: 'ms',
- Presto: 'o',
- Other: ''
- },
- // scope: Ext.env.Browser.prototype
- /**
- * A "hybrid" property, can be either accessed as a method call, for example:
- *
- * if (Ext.browser.is('IE')) {
- * // ...
- * }
- *
- * Or as an object with Boolean properties, for example:
- *
- * if (Ext.browser.is.IE) {
- * // ...
- * }
- *
- * Versions can be conveniently checked as well. For example:
- *
- * if (Ext.browser.is.IE6) {
- * // Equivalent to (Ext.browser.is.IE && Ext.browser.version.equals(6))
- * }
- *
- * __Note:__ Only {@link Ext.Version#getMajor major component} and {@link Ext.Version#getShortVersion simplified}
- * value of the version are available via direct property checking.
- *
- * Supported values are:
- *
- * - IE
- * - Firefox
- * - Safari
- * - Chrome
- * - Opera
- * - WebKit
- * - Gecko
- * - Presto
- * - Trident
- * - WebView
- * - Other
- *
- * @param {String} value The OS name to check.
- * @return {Boolean}
- */
- is: Ext.emptyFn,
- /**
- * The full name of the current browser.
- * Possible values are:
- *
- * - IE
- * - Firefox
- * - Safari
- * - Chrome
- * - Opera
- * - Other
- * @type String
- * @readonly
- */
- name: null,
- /**
- * Refer to {@link Ext.Version}.
- * @type Ext.Version
- * @readonly
- */
- version: null,
- /**
- * The full name of the current browser's engine.
- * Possible values are:
- *
- * - WebKit
- * - Gecko
- * - Presto
- * - Trident
- * - Other
- * @type String
- * @readonly
- */
- engineName: null,
- /**
- * Refer to {@link Ext.Version}.
- * @type Ext.Version
- * @readonly
- */
- engineVersion: null,
- setFlag: function(name, value) {
- if (typeof value == 'undefined') {
- value = true;
- }
- this.is[name] = value;
- this.is[name.toLowerCase()] = value;
- return this;
- },
- constructor: function(userAgent) {
- /**
- * @property {String}
- * Browser User Agent string.
- */
- this.userAgent = userAgent;
- is = this.is = function(name) {
- return is[name] === true;
- };
- var statics = this.statics(),
- browserMatch = userAgent.match(new RegExp('((?:' + Ext.Object.getValues(statics.browserPrefixes).join(')|(?:') + '))([\\w\\._]+)')),
- engineMatch = userAgent.match(new RegExp('((?:' + Ext.Object.getValues(statics.enginePrefixes).join(')|(?:') + '))([\\w\\._]+)')),
- browserNames = statics.browserNames,
- browserName = browserNames.other,
- engineNames = statics.engineNames,
- engineName = engineNames.other,
- browserVersion = '',
- engineVersion = '',
- isWebView = false,
- is, i, name;
- if (browserMatch) {
- browserName = browserNames[Ext.Object.getKey(statics.browserPrefixes, browserMatch[1])];
- browserVersion = new Ext.Version(browserMatch[2]);
- }
- if (engineMatch) {
- engineName = engineNames[Ext.Object.getKey(statics.enginePrefixes, engineMatch[1])];
- engineVersion = new Ext.Version(engineMatch[2]);
- }
- // Facebook changes the userAgent when you view a website within their iOS app. For some reason, the strip out information
- // about the browser, so we have to detect that and fake it...
- if (userAgent.match(/FB/) && browserName == "Other") {
- browserName = browserNames.safari;
- engineName = engineNames.webkit;
- }
- if (userAgent.match(/Android.*Chrome/g)) {
- browserName = 'ChromeMobile';
- }
- Ext.apply(this, {
- engineName: engineName,
- engineVersion: engineVersion,
- name: browserName,
- version: browserVersion
- });
- this.setFlag(browserName);
- if (browserVersion) {
- this.setFlag(browserName + (browserVersion.getMajor() || ''));
- this.setFlag(browserName + browserVersion.getShortVersion());
- }
- for (i in browserNames) {
- if (browserNames.hasOwnProperty(i)) {
- name = browserNames[i];
- this.setFlag(name, browserName === name);
- }
- }
- this.setFlag(name);
- if (engineVersion) {
- this.setFlag(engineName + (engineVersion.getMajor() || ''));
- this.setFlag(engineName + engineVersion.getShortVersion());
- }
- for (i in engineNames) {
- if (engineNames.hasOwnProperty(i)) {
- name = engineNames[i];
- this.setFlag(name, engineName === name);
- }
- }
- this.setFlag('Standalone', !!navigator.standalone);
- if (typeof window.PhoneGap != 'undefined' || typeof window.Cordova != 'undefined' || typeof window.cordova != 'undefined') {
- isWebView = true;
- this.setFlag('PhoneGap');
- }
- else if (!!window.isNK) {
- isWebView = true;
- this.setFlag('Sencha');
- }
- // Check if running in UIWebView
- if (/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)(?!.*FBAN)/i.test(userAgent)) {
- isWebView = true;
- }
- // Flag to check if it we are in the WebView
- this.setFlag('WebView', isWebView);
- /**
- * @property {Boolean}
- * `true` if browser is using strict mode.
- */
- this.isStrict = document.compatMode == "CSS1Compat";
- /**
- * @property {Boolean}
- * `true` if page is running over SSL.
- */
- this.isSecure = /^https/i.test(window.location.protocol);
- return this;
- },
- getStyleDashPrefix: function() {
- return this.styleDashPrefixes[this.engineName];
- },
- getStylePrefix: function() {
- return this.stylePrefixes[this.engineName];
- },
- getVendorProperyName: function(name) {
- var prefix = this.propertyPrefixes[this.engineName];
- if (prefix.length > 0) {
- return prefix + Ext.String.capitalize(name);
- }
- return name;
- }
- }, function() {
- /**
- * @class Ext.browser
- * @extends Ext.env.Browser
- * @singleton
- * Provides useful information about the current browser.
- *
- * Example:
- *
- * if (Ext.browser.is.IE) {
- * // IE specific code here
- * }
- *
- * if (Ext.browser.is.WebKit) {
- * // WebKit specific code here
- * }
- *
- * console.log("Version " + Ext.browser.version);
- *
- * For a full list of supported values, refer to {@link #is} property/method.
- *
- * @aside guide environment_package
- */
- var browserEnv = Ext.browser = new this(Ext.global.navigator.userAgent);
- });
- //@tag dom,core
- //@require Ext.env.Browser
- /**
- * Provides information about operating system environment.
- *
- * Should not be manually instantiated unless for unit-testing.
- * Access the global instance stored in {@link Ext.os} instead.
- * @private
- */
- Ext.define('Ext.env.OS', {
- requires: ['Ext.Version'],
- statics: {
- names: {
- ios: 'iOS',
- android: 'Android',
- webos: 'webOS',
- blackberry: 'BlackBerry',
- rimTablet: 'RIMTablet',
- mac: 'MacOS',
- win: 'Windows',
- linux: 'Linux',
- bada: 'Bada',
- other: 'Other'
- },
- prefixes: {
- ios: 'i(?:Pad|Phone|Pod)(?:.*)CPU(?: iPhone)? OS ',
- android: '(Android |HTC_|Silk/)', // Some HTC devices ship with an OSX userAgent by default,
- // so we need to add a direct check for HTC_
- blackberry: 'BlackBerry(?:.*)Version\/',
- rimTablet: 'RIM Tablet OS ',
- webos: '(?:webOS|hpwOS)\/',
- bada: 'Bada\/'
- }
- },
- /**
- * A "hybrid" property, can be either accessed as a method call, i.e:
- *
- * if (Ext.os.is('Android')) {
- * // ...
- * }
- *
- * or as an object with boolean properties, i.e:
- *
- * if (Ext.os.is.Android) {
- * // ...
- * }
- *
- * Versions can be conveniently checked as well. For example:
- *
- * if (Ext.os.is.Android2) {
- * // Equivalent to (Ext.os.is.Android && Ext.os.version.equals(2))
- * }
- *
- * if (Ext.os.is.iOS32) {
- * // Equivalent to (Ext.os.is.iOS && Ext.os.version.equals(3.2))
- * }
- *
- * Note that only {@link Ext.Version#getMajor major component} and {@link Ext.Version#getShortVersion simplified}
- * value of the version are available via direct property checking. Supported values are:
- *
- * - iOS
- * - iPad
- * - iPhone
- * - iPhone5 (also true for 4in iPods).
- * - iPod
- * - Android
- * - WebOS
- * - BlackBerry
- * - Bada
- * - MacOS
- * - Windows
- * - Linux
- * - Other
- * @param {String} value The OS name to check.
- * @return {Boolean}
- */
- is: Ext.emptyFn,
- /**
- * @property {String} [name=null]
- * @readonly
- * The full name of the current operating system. Possible values are:
- *
- * - iOS
- * - Android
- * - WebOS
- * - BlackBerry,
- * - MacOS
- * - Windows
- * - Linux
- * - Other
- */
- name: null,
- /**
- * @property {Ext.Version} [version=null]
- * Refer to {@link Ext.Version}
- * @readonly
- */
- version: null,
- setFlag: function(name, value) {
- if (typeof value == 'undefined') {
- value = true;
- }
- this.is[name] = value;
- this.is[name.toLowerCase()] = value;
- return this;
- },
- constructor: function(userAgent, platform) {
- var statics = this.statics(),
- names = statics.names,
- prefixes = statics.prefixes,
- name,
- version = '',
- i, prefix, match, item, is;
- is = this.is = function(name) {
- return this.is[name] === true;
- };
- for (i in prefixes) {
- if (prefixes.hasOwnProperty(i)) {
- prefix = prefixes[i];
- match = userAgent.match(new RegExp('(?:'+prefix+')([^\\s;]+)'));
- if (match) {
- name = names[i];
- // This is here because some HTC android devices show an OSX Snow Leopard userAgent by default.
- // And the Kindle Fire doesn't have any indicator of Android as the OS in its User Agent
- if (match[1] && (match[1] == "HTC_" || match[1] == "Silk/")) {
- version = new Ext.Version("2.3");
- } else {
- version = new Ext.Version(match[match.length - 1]);
- }
- break;
- }
- }
- }
- if (!name) {
- name = names[(userAgent.toLowerCase().match(/mac|win|linux/) || ['other'])[0]];
- version = new Ext.Version('');
- }
- this.name = name;
- this.version = version;
- if (platform) {
- this.setFlag(platform.replace(/ simulator$/i, ''));
- }
- this.setFlag(name);
- if (version) {
- this.setFlag(name + (version.getMajor() || ''));
- this.setFlag(name + version.getShortVersion());
- }
- for (i in names) {
- if (names.hasOwnProperty(i)) {
- item = names[i];
- if (!is.hasOwnProperty(name)) {
- this.setFlag(item, (name === item));
- }
- }
- }
- // Detect if the device is the iPhone 5.
- if (this.name == "iOS" && window.screen.height == 568) {
- this.setFlag('iPhone5');
- }
- return this;
- }
- }, function() {
- var navigation = Ext.global.navigator,
- userAgent = navigation.userAgent,
- osEnv, osName, deviceType;
- /**
- * @class Ext.os
- * @extends Ext.env.OS
- * @singleton
- * Provides useful information about the current operating system environment.
- *
- * Example:
- *
- * if (Ext.os.is.Windows) {
- * // Windows specific code here
- * }
- *
- * if (Ext.os.is.iOS) {
- * // iPad, iPod, iPhone, etc.
- * }
- *
- * console.log("Version " + Ext.os.version);
- *
- * For a full list of supported values, refer to the {@link #is} property/method.
- *
- * @aside guide environment_package
- */
- Ext.os = osEnv = new this(userAgent, navigation.platform);
- osName = osEnv.name;
- var search = window.location.search.match(/deviceType=(Tablet|Phone)/),
- nativeDeviceType = window.deviceType;
- // Override deviceType by adding a get variable of deviceType. NEEDED FOR DOCS APP.
- // E.g: example/kitchen-sink.html?deviceType=Phone
- if (search && search[1]) {
- deviceType = search[1];
- }
- else if (nativeDeviceType === 'iPhone') {
- deviceType = 'Phone';
- }
- else if (nativeDeviceType === 'iPad') {
- deviceType = 'Tablet';
- }
- else {
- if (!osEnv.is.Android && !osEnv.is.iOS && /Windows|Linux|MacOS/.test(osName)) {
- deviceType = 'Desktop';
- // always set it to false when you are on a desktop
- Ext.browser.is.WebView = false;
- }
- else if (osEnv.is.iPad || osEnv.is.Android3 || (osEnv.is.Android4 && userAgent.search(/mobile/i) == -1)) {
- deviceType = 'Tablet';
- }
- else {
- deviceType = 'Phone';
- }
- }
- /**
- * @property {String} deviceType
- * The generic type of the current device.
- *
- * Possible values:
- *
- * - Phone
- * - Tablet
- * - Desktop
- *
- * For testing purposes the deviceType can be overridden by adding
- * a deviceType parameter to the URL of the page, like so:
- *
- * http://localhost/mypage.html?deviceType=Tablet
- *
- */
- osEnv.setFlag(deviceType, true);
- osEnv.deviceType = deviceType;
- /**
- * @class Ext.is
- * Used to detect if the current browser supports a certain feature, and the type of the current browser.
- * @deprecated 2.0.0
- * Please refer to the {@link Ext.browser}, {@link Ext.os} and {@link Ext.feature} classes instead.
- */
- });
- //@tag dom,core
- /**
- * Provides information about browser.
- *
- * Should not be manually instantiated unless for unit-testing.
- * Access the global instance stored in {@link Ext.browser} instead.
- * @private
- */
- Ext.define('Ext.env.Feature', {
- requires: ['Ext.env.Browser', 'Ext.env.OS'],
- constructor: function() {
- this.testElements = {};
- this.has = function(name) {
- return !!this.has[name];
- };
- return this;
- },
- getTestElement: function(tag, createNew) {
- if (tag === undefined) {
- tag = 'div';
- }
- else if (typeof tag !== 'string') {
- return tag;
- }
- if (createNew) {
- return document.createElement(tag);
- }
- if (!this.testElements[tag]) {
- this.testElements[tag] = document.createElement(tag);
- }
- return this.testElements[tag];
- },
- isStyleSupported: function(name, tag) {
- var elementStyle = this.getTestElement(tag).style,
- cName = Ext.String.capitalize(name);
- if (typeof elementStyle[name] !== 'undefined'
- || typeof elementStyle[Ext.browser.getStylePrefix(name) + cName] !== 'undefined') {
- return true;
- }
- return false;
- },
- isEventSupported: function(name, tag) {
- if (tag === undefined) {
- tag = window;
- }
- var element = this.getTestElement(tag),
- eventName = 'on' + name.toLowerCase(),
- isSupported = (eventName in element);
- if (!isSupported) {
- if (element.setAttribute && element.removeAttribute) {
- element.setAttribute(eventName, '');
- isSupported = typeof element[eventName] === 'function';
- if (typeof element[eventName] !== 'undefined') {
- element[eventName] = undefined;
- }
- element.removeAttribute(eventName);
- }
- }
- return isSupported;
- },
- getSupportedPropertyName: function(object, name) {
- var vendorName = Ext.browser.getVendorProperyName(name);
- if (vendorName in object) {
- return vendorName;
- }
- else if (name in object) {
- return name;
- }
- return null;
- },
- registerTest: Ext.Function.flexSetter(function(name, fn) {
- this.has[name] = fn.call(this);
- return this;
- })
- }, function() {
- /**
- * @class Ext.feature
- * @extend Ext.env.Feature
- * @singleton
- *
- * A simple class to verify if a browser feature exists or not on the current device.
- *
- * if (Ext.feature.has.Canvas) {
- * // do some cool things with canvas here
- * }
- *
- * See the {@link #has} property/method for details of the features that can be detected.
- *
- * @aside guide environment_package
- */
- Ext.feature = new this;
- var has = Ext.feature.has;
- /**
- * @method has
- * @member Ext.feature
- * Verifies if a browser feature exists or not on the current device.
- *
- * A "hybrid" property, can be either accessed as a method call, i.e:
- *
- * if (Ext.feature.has('Canvas')) {
- * // ...
- * }
- *
- * or as an object with boolean properties, i.e:
- *
- * if (Ext.feature.has.Canvas) {
- * // ...
- * }
- *
- * Possible properties/parameter values:
- *
- * - Canvas
- * - Svg
- * - Vml
- * - Touch - supports touch events (`touchstart`).
- * - Orientation - supports different orientations.
- * - OrientationChange - supports the `orientationchange` event.
- * - DeviceMotion - supports the `devicemotion` event.
- * - Geolocation
- * - SqlDatabase
- * - WebSockets
- * - Range - supports [DOM document fragments.][1]
- * - CreateContextualFragment - supports HTML fragment parsing using [range.createContextualFragment()][2].
- * - History - supports history management with [history.pushState()][3].
- * - CssTransforms
- * - Css3dTransforms
- * - CssAnimations
- * - CssTransitions
- * - Audio - supports the `<audio>` tag.
- * - Video - supports the `<video>` tag.
- * - ClassList - supports the HTML5 classList API.
- * - LocalStorage - LocalStorage is supported and can be written to.
- *
- * [1]: https://developer.mozilla.org/en/DOM/range
- * [2]: https://developer.mozilla.org/en/DOM/range.createContextualFragment
- * [3]: https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history#The_pushState().C2.A0method
- *
- * @param {String} value The feature name to check.
- * @return {Boolean}
- */
- Ext.feature.registerTest({
- Canvas: function() {
- var element = this.getTestElement('canvas');
- return !!(element && element.getContext && element.getContext('2d'));
- },
- Svg: function() {
- var doc = document;
- return !!(doc.createElementNS && !!doc.createElementNS("http:/" + "/www.w3.org/2000/svg", "svg").createSVGRect);
- },
- Vml: function() {
- var element = this.getTestElement(),
- ret = false;
- element.innerHTML = "<!--[if vml]><br><![endif]-->";
- ret = (element.childNodes.length === 1);
- element.innerHTML = "";
- return ret;
- },
- Touch: function() {
- return this.isEventSupported('touchstart') && !(Ext.os && Ext.os.name.match(/Windows|MacOS|Linux/) && !Ext.os.is.BlackBerry6);
- },
- Orientation: function() {
- return ('orientation' in window) && this.isEventSupported('orientationchange');
- },
- OrientationChange: function() {
- return this.isEventSupported('orientationchange');
- },
- DeviceMotion: function() {
- return this.isEventSupported('devicemotion');
- },
- Geolocation: function() {
- return 'geolocation' in window.navigator;
- },
- SqlDatabase: function() {
- return 'openDatabase' in window;
- },
- WebSockets: function() {
- return 'WebSocket' in window;
- },
- Range: function() {
- return !!document.createRange;
- },
- CreateContextualFragment: function() {
- var range = !!document.createRange ? document.createRange() : false;
- return range && !!range.createContextualFragment;
- },
- History: function() {
- return ('history' in window && 'pushState' in window.history);
- },
- CssTransforms: function() {
- return this.isStyleSupported('transform');
- },
- Css3dTransforms: function() {
- // See https://sencha.jira.com/browse/TOUCH-1544
- return this.has('CssTransforms') && this.isStyleSupported('perspective') && !Ext.os.is.Android2;
- },
- CssAnimations: function() {
- return this.isStyleSupported('animationName');
- },
- CssTransitions: function() {
- return this.isStyleSupported('transitionProperty');
- },
- Audio: function() {
- return !!this.getTestElement('audio').canPlayType;
- },
- Video: function() {
- return !!this.getTestElement('video').canPlayType;
- },
- ClassList: function() {
- return "classList" in this.getTestElement();
- },
- LocalStorage : function() {
- var supported = false;
- try {
- if ('localStorage' in window && window['localStorage'] !== null) {
- //this should throw an error in private browsing mode in iOS
- localStorage.setItem('sencha-localstorage-test', 'test success');
- //clean up if setItem worked
- localStorage.removeItem('sencha-localstorage-test');
- supported = true;
- }
- } catch ( e ) {}
- return supported;
- }
- });
- });
- //@tag dom,core
- //@define Ext.DomQuery
- //@define Ext.core.DomQuery
- //@require Ext.env.Feature
- /**
- * @class Ext.DomQuery
- * @alternateClassName Ext.dom.Query
- *
- * Provides functionality to select elements on the page based on a CSS selector. Delegates to
- * document.querySelectorAll. More information can be found at
- * [http://www.w3.org/TR/css3-selectors/](http://www.w3.org/TR/css3-selectors/)
- *
- * All selectors, attribute filters and pseudos below can be combined infinitely in any order. For example
- * `div.foo:nth-child(odd)[@foo=bar].bar:first` would be a perfectly valid selector.
- *
- * ## Element Selectors:
- *
- * * \* any element
- * * E an element with the tag E
- * * E F All descendant elements of E that have the tag F
- * * E > F or E/F all direct children elements of E that have the tag F
- * * E + F all elements with the tag F that are immediately preceded by an element with the tag E
- * * E ~ F all elements with the tag F that are preceded by a sibling element with the tag E
- *
- * ## Attribute Selectors:
- *
- * The use of @ and quotes are optional. For example, div[@foo='bar'] is also a valid attribute selector.
- *
- * * E[foo] has an attribute "foo"
- * * E[foo=bar] has an attribute "foo" that equals "bar"
- * * E[foo^=bar] has an attribute "foo" that starts with "bar"
- * * E[foo$=bar] has an attribute "foo" that ends with "bar"
- * * E[foo*=bar] has an attribute "foo" that contains the substring "bar"
- * * E[foo%=2] has an attribute "foo" that is evenly divisible by 2
- * * E[foo!=bar] has an attribute "foo" that does not equal "bar"
- *
- * ## Pseudo Classes:
- *
- * * E:first-child E is the first child of its parent
- * * E:last-child E is the last child of its parent
- * * E:nth-child(n) E is the nth child of its parent (1 based as per the spec)
- * * E:nth-child(odd) E is an odd child of its parent
- * * E:nth-child(even) E is an even child of its parent
- * * E:only-child E is the only child of its parent
- * * E:checked E is an element that is has a checked attribute that is true (e.g. a radio or checkbox)
- * * E:first the first E in the resultset
- * * E:last the last E in the resultset
- * * E:nth(n) the nth E in the resultset (1 based)
- * * E:odd shortcut for :nth-child(odd)
- * * E:even shortcut for :nth-child(even)
- * * E:not(S) an E element that does not match simple selector S
- * * E:has(S) an E element that has a descendant that matches simple selector S
- * * E:next(S) an E element whose next sibling matches simple selector S
- * * E:prev(S) an E element whose previous sibling matches simple selector S
- * * E:any(S1|S2|S2) an E element which matches any of the simple selectors S1, S2 or S3//\\
- *
- * ## CSS Value Selectors:
- *
- * * E{display=none} CSS value "display" that equals "none"
- * * E{display^=none} CSS value "display" that starts with "none"
- * * E{display$=none} CSS value "display" that ends with "none"
- * * E{display*=none} CSS value "display" that contains the substring "none"
- * * E{display%=2} CSS value "display" that is evenly divisible by 2
- * * E{display!=none} CSS value "display" that does not equal "none"
- */
- Ext.define('Ext.dom.Query', {
- /**
- * Selects a group of elements.
- * @param {String} selector The selector/xpath query (can be a comma separated list of selectors)
- * @param {HTMLElement/String} [root] The start of the query (defaults to document).
- * @return {HTMLElement[]} An Array of DOM elements which match the selector. If there are
- * no matches, and empty Array is returned.
- */
- select: function(q, root) {
- var results = [],
- nodes,
- i,
- j,
- qlen,
- nlen;
- root = root || document;
- if (typeof root == 'string') {
- root = document.getElementById(root);
- }
- q = q.split(",");
- for (i = 0,qlen = q.length; i < qlen; i++) {
- if (typeof q[i] == 'string') {
- //support for node attribute selection
- if (q[i][0] == '@') {
- nodes = root.getAttributeNode(q[i].substring(1));
- results.push(nodes);
- }
- else {
- nodes = root.querySelectorAll(q[i]);
- for (j = 0,nlen = nodes.length; j < nlen; j++) {
- results.push(nodes[j]);
- }
- }
- }
- }
- return results;
- },
- /**
- * Selects a single element.
- * @param {String} selector The selector/xpath query
- * @param {HTMLElement/String} [root] The start of the query (defaults to document).
- * @return {HTMLElement} The DOM element which matched the selector.
- */
- selectNode: function(q, root) {
- return this.select(q, root)[0];
- },
- /**
- * Returns true if the passed element(s) match the passed simple selector (e.g. div.some-class or span:first-child)
- * @param {String/HTMLElement/Array} el An element id, element or array of elements
- * @param {String} selector The simple selector to test
- * @return {Boolean}
- */
- is: function(el, q) {
- if (typeof el == "string") {
- el = document.getElementById(el);
- }
- return this.select(q).indexOf(el) !== -1;
- },
- isXml: function(el) {
- var docEl = (el ? el.ownerDocument || el : 0).documentElement;
- return docEl ? docEl.nodeName !== "HTML" : false;
- }
- }, function() {
- Ext.ns('Ext.core');
- Ext.core.DomQuery = Ext.DomQuery = new this();
- Ext.query = Ext.Function.alias(Ext.DomQuery, 'select');
- });
- //@tag dom,core
- //@define Ext.DomHelper
- //@require Ext.dom.Query
- /**
- * @class Ext.DomHelper
- * @alternateClassName Ext.dom.Helper
- *
- * The DomHelper class provides a layer of abstraction from DOM and transparently supports creating elements via DOM or
- * using HTML fragments. It also has the ability to create HTML fragment templates from your DOM building code.
- *
- * ## DomHelper element specification object
- *
- * A specification object is used when creating elements. Attributes of this object are assumed to be element
- * attributes, except for 4 special attributes:
- *
- * * **tag**: The tag name of the element
- * * **children (or cn)**: An array of the same kind of element definition objects to be created and appended. These
- * can be nested as deep as you want.
- * * **cls**: The class attribute of the element. This will end up being either the "class" attribute on a HTML
- * fragment or className for a DOM node, depending on whether DomHelper is using fragments or DOM.
- * * **html**: The innerHTML for the element
- *
- * ## Insertion methods
- *
- * Commonly used insertion methods:
- *
- * * {@link #append}
- * * {@link #insertBefore}
- * * {@link #insertAfter}
- * * {@link #overwrite}
- * * {@link #insertHtml}
- *
- * ## Example
- *
- * This is an example, where an unordered list with 3 children items is appended to an existing element with id
- * 'my-div':
- *
- * var dh = Ext.DomHelper; // create shorthand alias
- * // specification object
- * var spec = {
- * id: 'my-ul',
- * tag: 'ul',
- * cls: 'my-list',
- * // append children after creating
- * children: [ // may also specify 'cn' instead of 'children'
- * {tag: 'li', id: 'item0', html: 'List Item 0'},
- * {tag: 'li', id: 'item1', html: 'List Item 1'},
- * {tag: 'li', id: 'item2', html: 'List Item 2'}
- * ]
- * };
- * var list = dh.append(
- * 'my-div', // the context element 'my-div' can either be the id or the actual node
- * spec // the specification object
- * );
- *
- * Element creation specification parameters in this class may also be passed as an Array of specification objects.
- * This can be used to insert multiple sibling nodes into an existing container very efficiently. For example, to add
- * more list items to the example above:
- *
- * dh.append('my-ul', [
- * {tag: 'li', id: 'item3', html: 'List Item 3'},
- * {tag: 'li', id: 'item4', html: 'List Item 4'}
- * ]);
- *
- * ## Templating
- *
- * The real power is in the built-in templating. Instead of creating or appending any elements, createTemplate returns
- * a Template object which can be used over and over to insert new elements. Revisiting the example above, we could
- * utilize templating this time:
- *
- * // create the node
- * var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
- * // get template
- * var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
- *
- * for(var i = 0; i < 5; i++){
- * tpl.append(list, i); // use template to append to the actual node
- * }
- *
- * An example using a template:
- *
- * var html = '"{0}" href="{1}" class="nav">{2}';
- *
- * var tpl = new Ext.DomHelper.createTemplate(html);
- * tpl.append('blog-roll', ['link1', 'http://www.tommymaintz.com/', "Tommy's Site"]);
- * tpl.append('blog-roll', ['link2', 'http://www.avins.org/', "Jamie's Site"]);
- *
- * The same example using named parameters:
- *
- * var html = '"{id}" href="{url}" class="nav">{text}';
- *
- * var tpl = new Ext.DomHelper.createTemplate(html);
- * tpl.append('blog-roll', {
- * id: 'link1',
- * url: 'http://www.tommymaintz.com/',
- * text: "Tommy's Site"
- * });
- * tpl.append('blog-roll', {
- * id: 'link2',
- * url: 'http://www.avins.org/',
- * text: "Jamie's Site"
- * });
- *
- * ## Compiling Templates
- *
- * Templates are applied using regular expressions. The performance is great, but if you are adding a bunch of DOM
- * elements using the same template, you can increase performance even further by "compiling" the template. The way
- * "compile()" works is the template is parsed and broken up at the different variable points and a dynamic function is
- * created and eval'ed. The generated function performs string concatenation of these parts and the passed variables
- * instead of using regular expressions.
- *
- * var html = '"{id}" href="{url}" class="nav">{text}';
- *
- * var tpl = new Ext.DomHelper.createTemplate(html);
- * tpl.compile();
- *
- * // ... use template like normal
- *
- * ## Performance Boost
- *
- * DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead of DOM can
- * significantly boost performance.
- *
- * Element creation specification parameters may also be strings. If useDom is false, then the string is used as
- * innerHTML. If useDom is true, a string specification results in the creation of a text node. Usage:
- *
- * Ext.DomHelper.useDom = true; // force it to use DOM; reduces performance
- *
- */
- Ext.define('Ext.dom.Helper', {
- emptyTags : /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
- confRe : /tag|children|cn|html|tpl|tplData$/i,
- endRe : /end/i,
- attribXlat: { cls : 'class', htmlFor : 'for' },
- closeTags: {},
- decamelizeName : function () {
- var camelCaseRe = /([a-z])([A-Z])/g,
- cache = {};
- function decamel (match, p1, p2) {
- return p1 + '-' + p2.toLowerCase();
- }
- return function (s) {
- return cache[s] || (cache[s] = s.replace(camelCaseRe, decamel));
- };
- }(),
- generateMarkup: function(spec, buffer) {
- var me = this,
- attr, val, tag, i, closeTags;
- if (typeof spec == "string") {
- buffer.push(spec);
- } else if (Ext.isArray(spec)) {
- for (i = 0; i < spec.length; i++) {
- if (spec[i]) {
- me.generateMarkup(spec[i], buffer);
- }
- }
- } else {
- tag = spec.tag || 'div';
- buffer.push('<', tag);
- for (attr in spec) {
- if (spec.hasOwnProperty(attr)) {
- val = spec[attr];
- if (!me.confRe.test(attr)) {
- if (typeof val == "object") {
- buffer.push(' ', attr, '="');
- me.generateStyles(val, buffer).push('"');
- } else {
- buffer.push(' ', me.attribXlat[attr] || attr, '="', val, '"');
- }
- }
- }
- }
- // Now either just close the tag or try to add children and close the tag.
- if (me.emptyTags.test(tag)) {
- buffer.push('/>');
- } else {
- buffer.push('>');
- // Apply the tpl html, and cn specifications
- if ((val = spec.tpl)) {
- val.applyOut(spec.tplData, buffer);
- }
- if ((val = spec.html)) {
- buffer.push(val);
- }
- if ((val = spec.cn || spec.children)) {
- me.generateMarkup(val, buffer);
- }
- // we generate a lot of close tags, so cache them rather than push 3 parts
- closeTags = me.closeTags;
- buffer.push(closeTags[tag] || (closeTags[tag] = '</' + tag + '>'));
- }
- }
- return buffer;
- },
- /**
- * Converts the styles from the given object to text. The styles are CSS style names
- * with their associated value.
- *
- * The basic form of this method returns a string:
- *
- * var s = Ext.DomHelper.generateStyles({
- * backgroundColor: 'red'
- * });
- *
- * // s = 'background-color:red;'
- *
- * Alternatively, this method can append to an output array.
- *
- * var buf = [];
- *
- * // ...
- *
- * Ext.DomHelper.generateStyles({
- * backgroundColor: 'red'
- * }, buf);
- *
- * In this case, the style text is pushed on to the array and the array is returned.
- *
- * @param {Object} styles The object describing the styles.
- * @param {String[]} [buffer] The output buffer.
- * @return {String/String[]} If buffer is passed, it is returned. Otherwise the style
- * string is returned.
- */
- generateStyles: function (styles, buffer) {
- var a = buffer || [],
- name;
- for (name in styles) {
- if (styles.hasOwnProperty(name)) {
- a.push(this.decamelizeName(name), ':', styles[name], ';');
- }
- }
- return buffer || a.join('');
- },
- /**
- * Returns the markup for the passed Element(s) config.
- * @param {Object} spec The DOM object spec (and children).
- * @return {String}
- */
- markup: function(spec) {
- if (typeof spec == "string") {
- return spec;
- }
- var buf = this.generateMarkup(spec, []);
- return buf.join('');
- },
- /**
- * Applies a style specification to an element.
- * @param {String/HTMLElement} el The element to apply styles to
- * @param {String/Object/Function} styles A style specification string e.g. 'width:100px', or object in the form {width:'100px'}, or
- * a function which returns such a specification.
- */
- applyStyles: function(el, styles) {
- Ext.fly(el).applyStyles(styles);
- },
- /**
- * @private
- * Fix for browsers which no longer support createContextualFragment
- */
- createContextualFragment: function(html){
- var div = document.createElement("div"),
- fragment = document.createDocumentFragment(),
- i = 0,
- length, childNodes;
- div.innerHTML = html;
- childNodes = div.childNodes;
- length = childNodes.length;
- for (; i < length; i++) {
- fragment.appendChild(childNodes[i].cloneNode(true));
- }
- return fragment;
- },
- /**
- * Inserts an HTML fragment into the DOM.
- * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
- *
- * For example take the following HTML: `<div>Contents</div>`
- *
- * Using different `where` values inserts element to the following places:
- *
- * - beforeBegin: `<HERE><div>Contents</div>`
- * - afterBegin: `<div><HERE>Contents</div>`
- * - beforeEnd: `<div>Contents<HERE></div>`
- * - afterEnd: `<div>Contents</div><HERE>`
- *
- * @param {HTMLElement/TextNode} el The context element
- * @param {String} html The HTML fragment
- * @return {HTMLElement} The new node
- */
- insertHtml: function(where, el, html) {
- var setStart, range, frag, rangeEl, isBeforeBegin, isAfterBegin;
- where = where.toLowerCase();
- if (Ext.isTextNode(el)) {
- if (where == 'afterbegin' ) {
- where = 'beforebegin';
- }
- else if (where == 'beforeend') {
- where = 'afterend';
- }
- }
- isBeforeBegin = where == 'beforebegin';
- isAfterBegin = where == 'afterbegin';
- range = Ext.feature.has.CreateContextualFragment ? el.ownerDocument.createRange() : undefined;
- setStart = 'setStart' + (this.endRe.test(where) ? 'After' : 'Before');
- if (isBeforeBegin || where == 'afterend') {
- if (range) {
- range[setStart](el);
- frag = range.createContextualFragment(html);
- }
- else {
- frag = this.createContextualFragment(html);
- }
- el.parentNode.insertBefore(frag, isBeforeBegin ? el : el.nextSibling);
- return el[(isBeforeBegin ? 'previous' : 'next') + 'Sibling'];
- }
- else {
- rangeEl = (isAfterBegin ? 'first' : 'last') + 'Child';
- if (el.firstChild) {
- if (range) {
- range[setStart](el[rangeEl]);
- frag = range.createContextualFragment(html);
- } else {
- frag = this.createContextualFragment(html);
- }
- if (isAfterBegin) {
- el.insertBefore(frag, el.firstChild);
- } else {
- el.appendChild(frag);
- }
- } else {
- el.innerHTML = html;
- }
- return el[rangeEl];
- }
- },
- /**
- * Creates new DOM element(s) and inserts them before el.
- * @param {String/HTMLElement/Ext.Element} el The context element
- * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
- * @param {Boolean} [returnElement] true to return a Ext.Element
- * @return {HTMLElement/Ext.Element} The new node
- */
- insertBefore: function(el, o, returnElement) {
- return this.doInsert(el, o, returnElement, 'beforebegin');
- },
- /**
- * Creates new DOM element(s) and inserts them after el.
- * @param {String/HTMLElement/Ext.Element} el The context element
- * @param {Object} o The DOM object spec (and children)
- * @param {Boolean} [returnElement] true to return a Ext.Element
- * @return {HTMLElement/Ext.Element} The new node
- */
- insertAfter: function(el, o, returnElement) {
- return this.doInsert(el, o, returnElement, 'afterend');
- },
- /**
- * Creates new DOM element(s) and inserts them as the first child of el.
- * @param {String/HTMLElement/Ext.Element} el The context element
- * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
- * @param {Boolean} [returnElement] true to return a Ext.Element
- * @return {HTMLElement/Ext.Element} The new node
- */
- insertFirst: function(el, o, returnElement) {
- return this.doInsert(el, o, returnElement, 'afterbegin');
- },
- /**
- * Creates new DOM element(s) and appends them to el.
- * @param {String/HTMLElement/Ext.Element} el The context element
- * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
- * @param {Boolean} [returnElement] true to return a Ext.Element
- * @return {HTMLElement/Ext.Element} The new node
- */
- append: function(el, o, returnElement) {
- return this.doInsert(el, o, returnElement, 'beforeend');
- },
- /**
- * Creates new DOM element(s) and overwrites the contents of el with them.
- * @param {String/HTMLElement/Ext.Element} el The context element
- * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
- * @param {Boolean} [returnElement] true to return a Ext.Element
- * @return {HTMLElement/Ext.Element} The new node
- */
- overwrite: function(el, o, returnElement) {
- el = Ext.getDom(el);
- el.innerHTML = this.markup(o);
- return returnElement ? Ext.get(el.firstChild) : el.firstChild;
- },
- doInsert: function(el, o, returnElement, pos) {
- var newNode = this.insertHtml(pos, Ext.getDom(el), this.markup(o));
- return returnElement ? Ext.get(newNode, true) : newNode;
- },
- /**
- * Creates a new Ext.Template from the DOM object spec.
- * @param {Object} o The DOM object spec (and children)
- * @return {Ext.Template} The new template
- */
- createTemplate: function(o) {
- var html = this.markup(o);
- return new Ext.Template(html);
- }
- }, function() {
- Ext.ns('Ext.core');
- Ext.core.DomHelper = Ext.DomHelper = new this;
- });
- //@tag dom,core
- //@require Ext.dom.Helper
- /**
- * An Identifiable mixin.
- * @private
- */
- Ext.define('Ext.mixin.Identifiable', {
- statics: {
- uniqueIds: {}
- },
- isIdentifiable: true,
- mixinId: 'identifiable',
- idCleanRegex: /\.|[^\w\-]/g,
- defaultIdPrefix: 'ext-',
- defaultIdSeparator: '-',
- getOptimizedId: function() {
- return this.id;
- },
- getUniqueId: function() {
- var id = this.id,
- prototype, separator, xtype, uniqueIds, prefix;
- if (!id) {
- prototype = this.self.prototype;
- separator = this.defaultIdSeparator;
- uniqueIds = Ext.mixin.Identifiable.uniqueIds;
- if (!prototype.hasOwnProperty('identifiablePrefix')) {
- xtype = this.xtype;
- if (xtype) {
- prefix = this.defaultIdPrefix + xtype + separator;
- }
- else {
- prefix = prototype.$className.replace(this.idCleanRegex, separator).toLowerCase() + separator;
- }
- prototype.identifiablePrefix = prefix;
- }
- prefix = this.identifiablePrefix;
- if (!uniqueIds.hasOwnProperty(prefix)) {
- uniqueIds[prefix] = 0;
- }
- id = this.id = prefix + (++uniqueIds[prefix]);
- }
- this.getUniqueId = this.getOptimizedId;
- return id;
- },
- setId: function(id) {
- this.id = id;
- },
- /**
- * Retrieves the id of this component. Will autogenerate an id if one has not already been set.
- * @return {String} id
- */
- getId: function() {
- var id = this.id;
- if (!id) {
- id = this.getUniqueId();
- }
- this.getId = this.getOptimizedId;
- return id;
- }
- });
- //@tag dom,core
- //@define Ext.Element-all
- //@define Ext.Element
- /**
- * Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser differences.
- *
- * All instances of this class inherit the methods of Ext.Fx making visual effects easily available to all DOM elements.
- *
- * Note that the events documented in this class are not Ext events, they encapsulate browser events. To access the
- * underlying browser event, see {@link Ext.EventObject#browserEvent}. Some older browsers may not support the full range of
- * events. Which events are supported is beyond the control of Sencha Touch.
- *
- * ## Usage
- *
- * // by id
- * var el = Ext.get("my-div");
- *
- * // by DOM element reference
- * var el = Ext.get(myDivElement);
- *
- * ## Composite (Collections of) Elements
- *
- * For working with collections of Elements, see {@link Ext.CompositeElement}.
- *
- * @mixins Ext.mixin.Observable
- */
- Ext.define('Ext.dom.Element', {
- alternateClassName: 'Ext.Element',
- mixins: [
- 'Ext.mixin.Identifiable'
- ],
- requires: [
- 'Ext.dom.Query',
- 'Ext.dom.Helper'
- ],
- observableType: 'element',
- xtype: 'element',
- statics: {
- CREATE_ATTRIBUTES: {
- style: 'style',
- className: 'className',
- cls: 'cls',
- classList: 'classList',
- text: 'text',
- hidden: 'hidden',
- html: 'html',
- children: 'children'
- },
- create: function(attributes, domNode) {
- var ATTRIBUTES = this.CREATE_ATTRIBUTES,
- element, elementStyle, tag, value, name, i, ln;
- if (!attributes) {
- attributes = {};
- }
- if (attributes.isElement) {
- return attributes.dom;
- }
- else if ('nodeType' in attributes) {
- return attributes;
- }
- if (typeof attributes == 'string') {
- return document.createTextNode(attributes);
- }
- tag = attributes.tag;
- if (!tag) {
- tag = 'div';
- }
- if (attributes.namespace) {
- element = document.createElementNS(attributes.namespace, tag);
- } else {
- element = document.createElement(tag);
- }
- elementStyle = element.style;
- for (name in attributes) {
- if (name != 'tag') {
- value = attributes[name];
- switch (name) {
- case ATTRIBUTES.style:
- if (typeof value == 'string') {
- element.setAttribute(name, value);
- }
- else {
- for (i in value) {
- if (value.hasOwnProperty(i)) {
- elementStyle[i] = value[i];
- }
- }
- }
- break;
- case ATTRIBUTES.className:
- case ATTRIBUTES.cls:
- element.className = value;
- break;
- case ATTRIBUTES.classList:
- element.className = value.join(' ');
- break;
- case ATTRIBUTES.text:
- element.textContent = value;
- break;
- case ATTRIBUTES.hidden:
- if (value) {
- element.style.display = 'none';
- }
- break;
- case ATTRIBUTES.html:
- element.innerHTML = value;
- break;
- case ATTRIBUTES.children:
- for (i = 0,ln = value.length; i < ln; i++) {
- element.appendChild(this.create(value[i], true));
- }
- break;
- default:
- element.setAttribute(name, value);
- }
- }
- }
- if (domNode) {
- return element;
- }
- else {
- return this.get(element);
- }
- },
- documentElement: null,
- cache: {},
- /**
- * Retrieves Ext.dom.Element objects. {@link Ext#get} is alias for {@link Ext.dom.Element#get}.
- *
- * **This method does not retrieve {@link Ext.Element Element}s.** This method retrieves Ext.dom.Element
- * objects which encapsulate DOM elements. To retrieve a Element by its ID, use {@link Ext.ElementManager#get}.
- *
- * Uses simple caching to consistently return the same object. Automatically fixes if an object was recreated with
- * the same id via AJAX or DOM.
- *
- * @param {String/HTMLElement/Ext.Element} el The `id` of the node, a DOM Node or an existing Element.
- * @return {Ext.dom.Element} The Element object (or `null` if no matching element was found).
- * @static
- * @inheritable
- */
- get: function(element) {
- var cache = this.cache,
- instance, dom, id;
- if (!element) {
- return null;
- }
- if (typeof element == 'string') {
- if (cache.hasOwnProperty(element)) {
- return cache[element];
- }
- if (!(dom = document.getElementById(element))) {
- return null;
- }
- cache[element] = instance = new this(dom);
- return instance;
- }
- if ('tagName' in element) { // dom element
- id = element.id;
- if (cache.hasOwnProperty(id)) {
- return cache[id];
- }
- instance = new this(element);
- cache[instance.getId()] = instance;
- return instance;
- }
- if (element.isElement) {
- return element;
- }
- if (element.isComposite) {
- return element;
- }
- if (Ext.isArray(element)) {
- return this.select(element);
- }
- if (element === document) {
- // create a bogus element object representing the document object
- if (!this.documentElement) {
- this.documentElement = new this(document.documentElement);
- this.documentElement.setId('ext-application');
- }
- return this.documentElement;
- }
- return null;
- },
- data: function(element, key, value) {
- var cache = Ext.cache,
- id, data;
- element = this.get(element);
- if (!element) {
- return null;
- }
- id = element.id;
- data = cache[id].data;
- if (!data) {
- cache[id].data = data = {};
- }
- if (arguments.length == 2) {
- return data[key];
- }
- else {
- return (data[key] = value);
- }
- }
- },
- isElement: true,
- /**
- * @event painted
- * Fires whenever this Element actually becomes visible (painted) on the screen. This is useful when you need to
- * perform 'read' operations on the DOM element, i.e: calculating natural sizes and positioning.
- *
- * __Note:__ This event is not available to be used with event delegation. Instead `painted` only fires if you explicitly
- * add at least one listener to it, for performance reasons.
- *
- * @param {Ext.Element} this The component instance.
- */
- /**
- * @event resize
- * Important note: For the best performance on mobile devices, use this only when you absolutely need to monitor
- * a Element's size.
- *
- * __Note:__ This event is not available to be used with event delegation. Instead `resize` only fires if you explicitly
- * add at least one listener to it, for performance reasons.
- *
- * @param {Ext.Element} this The component instance.
- */
- constructor: function(dom) {
- if (typeof dom == 'string') {
- dom = document.getElementById(dom);
- }
- if (!dom) {
- throw new Error("Invalid domNode reference or an id of an existing domNode: " + dom);
- }
- /**
- * The DOM element
- * @property dom
- * @type HTMLElement
- */
- this.dom = dom;
- this.getUniqueId();
- },
- attach: function (dom) {
- this.dom = dom;
- this.id = dom.id;
- return this;
- },
- getUniqueId: function() {
- var id = this.id,
- dom;
- if (!id) {
- dom = this.dom;
- if (dom.id.length > 0) {
- this.id = id = dom.id;
- }
- else {
- dom.id = id = this.mixins.identifiable.getUniqueId.call(this);
- }
- this.self.cache[id] = this;
- }
- return id;
- },
- setId: function(id) {
- var currentId = this.id,
- cache = this.self.cache;
- if (currentId) {
- delete cache[currentId];
- }
- this.dom.id = id;
- /**
- * The DOM element ID
- * @property id
- * @type String
- */
- this.id = id;
- cache[id] = this;
- return this;
- },
- /**
- * Sets the `innerHTML` of this element.
- * @param {String} html The new HTML.
- */
- setHtml: function(html) {
- this.dom.innerHTML = html;
- },
- /**
- * Returns the `innerHTML` of an element.
- * @return {String}
- */
- getHtml: function() {
- return this.dom.innerHTML;
- },
- setText: function(text) {
- this.dom.textContent = text;
- },
- redraw: function() {
- var dom = this.dom,
- domStyle = dom.style;
- domStyle.display = 'none';
- dom.offsetHeight;
- domStyle.display = '';
- },
- isPainted: function() {
- var dom = this.dom;
- return Boolean(dom && dom.offsetParent);
- },
- /**
- * Sets the passed attributes as attributes of this element (a style attribute can be a string, object or function).
- * @param {Object} attributes The object with the attributes.
- * @param {Boolean} [useSet=true] `false` to override the default `setAttribute` to use expandos.
- * @return {Ext.dom.Element} this
- */
- set: function(attributes, useSet) {
- var dom = this.dom,
- attribute, value;
- for (attribute in attributes) {
- if (attributes.hasOwnProperty(attribute)) {
- value = attributes[attribute];
- if (attribute == 'style') {
- this.applyStyles(value);
- }
- else if (attribute == 'cls') {
- dom.className = value;
- }
- else if (useSet !== false) {
- if (value === undefined) {
- dom.removeAttribute(attribute);
- } else {
- dom.setAttribute(attribute, value);
- }
- }
- else {
- dom[attribute] = value;
- }
- }
- }
- return this;
- },
- /**
- * Returns `true` if this element matches the passed simple selector (e.g. 'div.some-class' or 'span:first-child').
- * @param {String} selector The simple selector to test.
- * @return {Boolean} `true` if this element matches the selector, else `false`.
- */
- is: function(selector) {
- return Ext.DomQuery.is(this.dom, selector);
- },
- /**
- * Returns the value of the `value` attribute.
- * @param {Boolean} asNumber `true` to parse the value as a number.
- * @return {String/Number}
- */
- getValue: function(asNumber) {
- var value = this.dom.value;
- return asNumber ? parseInt(value, 10) : value;
- },
- /**
- * Returns the value of an attribute from the element's underlying DOM node.
- * @param {String} name The attribute name.
- * @param {String} [namespace] The namespace in which to look for the attribute.
- * @return {String} The attribute value.
- */
- getAttribute: function(name, namespace) {
- var dom = this.dom;
- return dom.getAttributeNS(namespace, name) || dom.getAttribute(namespace + ":" + name)
- || dom.getAttribute(name) || dom[name];
- },
- setSizeState: function(state) {
- var classes = ['x-sized', 'x-unsized', 'x-stretched'],
- states = [true, false, null],
- index = states.indexOf(state),
- addedClass;
- if (index !== -1) {
- addedClass = classes[index];
- classes.splice(index, 1);
- this.addCls(addedClass);
- }
- this.removeCls(classes);
- return this;
- },
- /**
- * Removes this element's DOM reference. Note that event and cache removal is handled at {@link Ext#removeNode}
- */
- destroy: function() {
- this.isDestroyed = true;
- var cache = Ext.Element.cache,
- dom = this.dom;
- if (dom && dom.parentNode && dom.tagName != 'BODY') {
- dom.parentNode.removeChild(dom);
- }
- delete cache[this.id];
- delete this.dom;
- }
- }, function(Element) {
- Ext.elements = Ext.cache = Element.cache;
- this.addStatics({
- Fly: new Ext.Class({
- extend: Element,
- constructor: function(dom) {
- this.dom = dom;
- }
- }),
- _flyweights: {},
- /**
- * Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference
- * to this element - the dom node can be overwritten by other code. {@link Ext#fly} is alias for
- * {@link Ext.dom.Element#fly}.
- *
- * Use this to make one-time references to DOM elements which are not going to be accessed again either by
- * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link
- * Ext#get Ext.get} will be more appropriate to take advantage of the caching provided by the {@link Ext.dom.Element}
- * class.
- *
- * @param {String/HTMLElement} element The DOM node or `id`.
- * @param {String} [named] Allows for creation of named reusable flyweights to prevent conflicts (e.g.
- * internally Ext uses "_global").
- * @return {Ext.dom.Element} The shared Element object (or `null` if no matching element was found).
- * @static
- */
- fly: function(element, named) {
- var fly = null,
- flyweights = Element._flyweights,
- cachedElement;
- named = named || '_global';
- element = Ext.getDom(element);
- if (element) {
- fly = flyweights[named] || (flyweights[named] = new Element.Fly());
- fly.dom = element;
- fly.isSynchronized = false;
- cachedElement = Ext.cache[element.id];
- if (cachedElement && cachedElement.isElement) {
- cachedElement.isSynchronized = false;
- }
- }
- return fly;
- }
- });
- /**
- * @member Ext
- * @method get
- * @alias Ext.dom.Element#get
- */
- Ext.get = function(element) {
- return Element.get.call(Element, element);
- };
- /**
- * @member Ext
- * @method fly
- * @alias Ext.dom.Element#fly
- */
- Ext.fly = function() {
- return Element.fly.apply(Element, arguments);
- };
- Ext.ClassManager.onCreated(function() {
- Element.mixin('observable', Ext.mixin.Observable);
- }, null, 'Ext.mixin.Observable');
- });
- //@tag dom,core
- //@define Ext.Element-all
- //@define Ext.Element-static
- //@require Ext.Element
- /**
- * @class Ext.dom.Element
- */
- Ext.dom.Element.addStatics({
- numberRe: /\d+$/,
- unitRe: /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
- camelRe: /(-[a-z])/gi,
- cssRe: /([a-z0-9-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,
- opacityRe: /alpha\(opacity=(.*)\)/i,
- propertyCache: {},
- defaultUnit: "px",
- borders: {l: 'border-left-width', r: 'border-right-width', t: 'border-top-width', b: 'border-bottom-width'},
- paddings: {l: 'padding-left', r: 'padding-right', t: 'padding-top', b: 'padding-bottom'},
- margins: {l: 'margin-left', r: 'margin-right', t: 'margin-top', b: 'margin-bottom'},
- /**
- * Test if size has a unit, otherwise appends the passed unit string, or the default for this Element.
- * @param {Object} size The size to set.
- * @param {String} units The units to append to a numeric size value.
- * @return {String}
- * @private
- * @static
- */
- addUnits: function(size, units) {
- // Size set to a value which means "auto"
- if (size === "" || size == "auto" || size === undefined || size === null) {
- return size || '';
- }
- // Otherwise, warn if it's not a valid CSS measurement
- if (Ext.isNumber(size) || this.numberRe.test(size)) {
- return size + (units || this.defaultUnit || 'px');
- }
- else if (!this.unitRe.test(size)) {
- //<debug>
- Ext.Logger.warn("Warning, size detected (" + size + ") not a valid property value on Element.addUnits.");
- //</debug>
- return size || '';
- }
- return size;
- },
- /**
- * @static
- * @return {Boolean}
- * @private
- */
- isAncestor: function(p, c) {
- var ret = false;
- p = Ext.getDom(p);
- c = Ext.getDom(c);
- if (p && c) {
- if (p.contains) {
- return p.contains(c);
- } else if (p.compareDocumentPosition) {
- return !!(p.compareDocumentPosition(c) & 16);
- } else {
- while ((c = c.parentNode)) {
- ret = c == p || ret;
- }
- }
- }
- return ret;
- },
- /**
- * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
- * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
- * @static
- * @param {Number/String} box The encoded margins
- * @return {Object} An object with margin sizes for top, right, bottom and left containing the unit
- */
- parseBox: function(box) {
- if (typeof box != 'string') {
- box = box.toString();
- }
- var parts = box.split(' '),
- ln = parts.length;
- if (ln == 1) {
- parts[1] = parts[2] = parts[3] = parts[0];
- }
- else if (ln == 2) {
- parts[2] = parts[0];
- parts[3] = parts[1];
- }
- else if (ln == 3) {
- parts[3] = parts[1];
- }
- return {
- top: parts[0] || 0,
- right: parts[1] || 0,
- bottom: parts[2] || 0,
- left: parts[3] || 0
- };
- },
- /**
- * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
- * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
- * @static
- * @param {Number/String} box The encoded margins
- * @param {String} units The type of units to add
- * @return {String} An string with unitized (px if units is not specified) metrics for top, right, bottom and left
- */
- unitizeBox: function(box, units) {
- var me = this;
- box = me.parseBox(box);
- return me.addUnits(box.top, units) + ' ' +
- me.addUnits(box.right, units) + ' ' +
- me.addUnits(box.bottom, units) + ' ' +
- me.addUnits(box.left, units);
- },
- // @private
- camelReplaceFn: function(m, a) {
- return a.charAt(1).toUpperCase();
- },
- /**
- * Normalizes CSS property keys from dash delimited to camel case JavaScript Syntax.
- * For example:
- *
- * - border-width -> borderWidth
- * - padding-top -> paddingTop
- *
- * @static
- * @param {String} prop The property to normalize
- * @return {String} The normalized string
- */
- normalize: function(prop) {
- // TODO: Mobile optimization?
- // if (prop == 'float') {
- // prop = Ext.supports.Float ? 'cssFloat' : 'styleFloat';
- // }
- return this.propertyCache[prop] || (this.propertyCache[prop] = prop.replace(this.camelRe, this.camelReplaceFn));
- },
- /**
- * Returns the top Element that is located at the passed coordinates
- * @static
- * @param {Number} x The x coordinate
- * @param {Number} y The y coordinate
- * @return {String} The found Element
- */
- fromPoint: function(x, y) {
- return Ext.get(document.elementFromPoint(x, y));
- },
- /**
- * Converts a CSS string into an object with a property for each style.
- *
- * The sample code below would return an object with 2 properties, one
- * for background-color and one for color.
- *
- * var css = 'background-color: red;color: blue; ';
- * console.log(Ext.dom.Element.parseStyles(css));
- *
- * @static
- * @param {String} styles A CSS string
- * @return {Object} styles
- */
- parseStyles: function(styles) {
- var out = {},
- cssRe = this.cssRe,
- matches;
- if (styles) {
- // Since we're using the g flag on the regex, we need to set the lastIndex.
- // This automatically happens on some implementations, but not others, see:
- // http://stackoverflow.com/questions/2645273/javascript-regular-expression-literal-persists-between-function-calls
- // http://blog.stevenlevithan.com/archives/fixing-javascript-regexp
- cssRe.lastIndex = 0;
- while ((matches = cssRe.exec(styles))) {
- out[matches[1]] = matches[2];
- }
- }
- return out;
- }
- });
- //@tag dom,core
- //@define Ext.Element-all
- //@define Ext.Element-alignment
- //@require Ext.Element-static
- /**
- * @class Ext.dom.Element
- */
- //@tag dom,core
- //@define Ext.Element-all
- //@define Ext.Element-insertion
- //@require Ext.Element-alignment
- /**
- * @class Ext.dom.Element
- */
- Ext.dom.Element.addMembers({
- /**
- * Appends the passed element(s) to this element.
- * @param {HTMLElement/Ext.dom.Element} element a DOM Node or an existing Element.
- * @return {Ext.dom.Element} This element.
- */
- appendChild: function(element) {
- this.dom.appendChild(Ext.getDom(element));
- return this;
- },
- removeChild: function(element) {
- this.dom.removeChild(Ext.getDom(element));
- return this;
- },
- append: function() {
- this.appendChild.apply(this, arguments);
- },
- /**
- * Appends this element to the passed element.
- * @param {String/HTMLElement/Ext.dom.Element} el The new parent element.
- * The id of the node, a DOM Node or an existing Element.
- * @return {Ext.dom.Element} This element.
- */
- appendTo: function(el) {
- Ext.getDom(el).appendChild(this.dom);
- return this;
- },
- /**
- * Inserts this element before the passed element in the DOM.
- * @param {String/HTMLElement/Ext.dom.Element} el The element before which this element will be inserted.
- * The id of the node, a DOM Node or an existing Element.
- * @return {Ext.dom.Element} This element.
- */
- insertBefore: function(el) {
- el = Ext.getDom(el);
- el.parentNode.insertBefore(this.dom, el);
- return this;
- },
- /**
- * Inserts this element after the passed element in the DOM.
- * @param {String/HTMLElement/Ext.dom.Element} el The element to insert after.
- * The `id` of the node, a DOM Node or an existing Element.
- * @return {Ext.dom.Element} This element.
- */
- insertAfter: function(el) {
- el = Ext.getDom(el);
- el.parentNode.insertBefore(this.dom, el.nextSibling);
- return this;
- },
- /**
- * Inserts an element as the first child of this element.
- * @param {String/HTMLElement/Ext.dom.Element} element The `id` or element to insert.
- * @return {Ext.dom.Element} this
- */
- insertFirst: function(element) {
- var elementDom = Ext.getDom(element),
- dom = this.dom,
- firstChild = dom.firstChild;
- if (!firstChild) {
- dom.appendChild(elementDom);
- }
- else {
- dom.insertBefore(elementDom, firstChild);
- }
- return this;
- },
- /**
- * Inserts (or creates) the passed element (or DomHelper config) as a sibling of this element
- * @param {String/HTMLElement/Ext.dom.Element/Object/Array} el The id, element to insert or a DomHelper config
- * to create and insert *or* an array of any of those.
- * @param {String} [where=before] (optional) 'before' or 'after'.
- * @param {Boolean} returnDom (optional) `true` to return the raw DOM element instead of Ext.dom.Element.
- * @return {Ext.dom.Element} The inserted Element. If an array is passed, the last inserted element is returned.
- */
- insertSibling: function(el, where, returnDom) {
- var me = this, rt,
- isAfter = (where || 'before').toLowerCase() == 'after',
- insertEl;
- if (Ext.isArray(el)) {
- insertEl = me;
- Ext.each(el, function(e) {
- rt = Ext.fly(insertEl, '_internal').insertSibling(e, where, returnDom);
- if (isAfter) {
- insertEl = rt;
- }
- });
- return rt;
- }
- el = el || {};
- if (el.nodeType || el.dom) {
- rt = me.dom.parentNode.insertBefore(Ext.getDom(el), isAfter ? me.dom.nextSibling : me.dom);
- if (!returnDom) {
- rt = Ext.get(rt);
- }
- } else {
- if (isAfter && !me.dom.nextSibling) {
- rt = Ext.core.DomHelper.append(me.dom.parentNode, el, !returnDom);
- } else {
- rt = Ext.core.DomHelper[isAfter ? 'insertAfter' : 'insertBefore'](me.dom, el, !returnDom);
- }
- }
- return rt;
- },
- /**
- * Replaces the passed element with this element.
- * @param {String/HTMLElement/Ext.dom.Element} el The element to replace.
- * The id of the node, a DOM Node or an existing Element.
- * @return {Ext.dom.Element} This element.
- */
- replace: function(element) {
- element = Ext.getDom(element);
- element.parentNode.replaceChild(this.dom, element);
- return this;
- },
- /**
- * Replaces this element with the passed element.
- * @param {String/HTMLElement/Ext.dom.Element/Object} el The new element (id of the node, a DOM Node
- * or an existing Element) or a DomHelper config of an element to create.
- * @return {Ext.dom.Element} This element.
- */
- replaceWith: function(el) {
- var me = this;
- if (el.nodeType || el.dom || typeof el == 'string') {
- el = Ext.get(el);
- me.dom.parentNode.insertBefore(el, me.dom);
- } else {
- el = Ext.core.DomHelper.insertBefore(me.dom, el);
- }
- delete Ext.cache[me.id];
- Ext.removeNode(me.dom);
- me.id = Ext.id(me.dom = el);
- Ext.dom.Element.addToCache(me.isFlyweight ? new Ext.dom.Element(me.dom) : me);
- return me;
- },
- doReplaceWith: function(element) {
- var dom = this.dom;
- dom.parentNode.replaceChild(Ext.getDom(element), dom);
- },
- /**
- * Creates the passed DomHelper config and appends it to this element or optionally inserts it before the passed child element.
- * @param {Object} config DomHelper element config object. If no tag is specified (e.g., `{tag:'input'}`) then a div will be
- * automatically generated with the specified attributes.
- * @param {HTMLElement} insertBefore (optional) a child element of this element.
- * @param {Boolean} returnDom (optional) `true` to return the dom node instead of creating an Element.
- * @return {Ext.dom.Element} The new child element.
- */
- createChild: function(config, insertBefore, returnDom) {
- config = config || {tag: 'div'};
- if (insertBefore) {
- return Ext.core.DomHelper.insertBefore(insertBefore, config, returnDom !== true);
- }
- else {
- return Ext.core.DomHelper[!this.dom.firstChild ? 'insertFirst' : 'append'](this.dom, config, returnDom !== true);
- }
- },
- /**
- * Creates and wraps this element with another element.
- * @param {Object} [config] (optional) DomHelper element config object for the wrapper element or `null` for an empty div
- * @param {Boolean} [domNode] (optional) `true` to return the raw DOM element instead of Ext.dom.Element.
- * @return {HTMLElement/Ext.dom.Element} The newly created wrapper element.
- */
- wrap: function(config, domNode) {
- var dom = this.dom,
- wrapper = this.self.create(config, domNode),
- wrapperDom = (domNode) ? wrapper : wrapper.dom,
- parentNode = dom.parentNode;
- if (parentNode) {
- parentNode.insertBefore(wrapperDom, dom);
- }
- wrapperDom.appendChild(dom);
- return wrapper;
- },
- wrapAllChildren: function(config) {
- var dom = this.dom,
- children = dom.childNodes,
- wrapper = this.self.create(config),
- wrapperDom = wrapper.dom;
- while (children.length > 0) {
- wrapperDom.appendChild(dom.firstChild);
- }
- dom.appendChild(wrapperDom);
- return wrapper;
- },
- unwrapAllChildren: function() {
- var dom = this.dom,
- children = dom.childNodes,
- parentNode = dom.parentNode;
- if (parentNode) {
- while (children.length > 0) {
- parentNode.insertBefore(dom, dom.firstChild);
- }
- this.destroy();
- }
- },
- unwrap: function() {
- var dom = this.dom,
- parentNode = dom.parentNode,
- grandparentNode;
- if (parentNode) {
- grandparentNode = parentNode.parentNode;
- grandparentNode.insertBefore(dom, parentNode);
- grandparentNode.removeChild(parentNode);
- }
- else {
- grandparentNode = document.createDocumentFragment();
- grandparentNode.appendChild(dom);
- }
- return this;
- },
- detach: function() {
- var dom = this.dom;
- if (dom && dom.parentNode && dom.tagName !== 'BODY') {
- dom.parentNode.removeChild(dom);
- }
- return this;
- },
- /**
- * Inserts an HTML fragment into this element.
- * @param {String} where Where to insert the HTML in relation to this element - 'beforeBegin', 'afterBegin', 'beforeEnd', 'afterEnd'.
- * See {@link Ext.DomHelper#insertHtml} for details.
- * @param {String} html The HTML fragment
- * @param {Boolean} [returnEl=false] (optional) `true` to return an Ext.dom.Element.
- * @return {HTMLElement/Ext.dom.Element} The inserted node (or nearest related if more than 1 inserted).
- */
- insertHtml: function(where, html, returnEl) {
- var el = Ext.core.DomHelper.insertHtml(where, this.dom, html);
- return returnEl ? Ext.get(el) : el;
- }
- });
- //@tag dom,core
- //@define Ext.Element-all
- //@define Ext.Element-position
- //@require Ext.Element-insertion
- /**
- * @class Ext.dom.Element
- */
- Ext.dom.Element.override({
- /**
- * 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`).
- * @return {Number} The X position of the element
- */
- getX: function(el) {
- return this.getXY(el)[0];
- },
- /**
- * 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`).
- * @return {Number} The Y position of the element
- */
- getY: function(el) {
- return this.getXY(el)[1];
- },
- /**
- * 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`).
- * @return {Array} The XY position of the element
- */
- getXY: function() {
- var rect = this.dom.getBoundingClientRect(),
- round = Math.round;
- return [round(rect.left + window.pageXOffset), round(rect.top + window.pageYOffset)];
- },
- /**
- * Returns the offsets of this element from the passed element. Both element must be part of the DOM tree
- * and not have `display:none` to have page coordinates.
- * @param {Mixed} element The element to get the offsets from.
- * @return {Array} The XY page offsets (e.g. [100, -200])
- */
- getOffsetsTo: function(el) {
- var o = this.getXY(),
- e = Ext.fly(el, '_internal').getXY();
- return [o[0] - e[0], o[1] - e[1]];
- },
- /**
- * 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`).
- * @param {Number} The X position of the element
- * @param {Boolean/Object} animate (optional) `true` for the default animation, or a standard Element animation config object.
- * @return {Ext.dom.Element} this
- */
- setX: function(x) {
- return this.setXY([x, this.getY()]);
- },
- /**
- * 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`).
- * @param {Number} The Y position of the element.
- * @param {Boolean/Object} animate (optional) `true` for the default animation, or a standard Element animation config object.
- * @return {Ext.dom.Element} this
- */
- setY: function(y) {
- return this.setXY([this.getX(), y]);
- },
- /**
- * Sets the position of the element in page coordinates, regardless of how the element is positioned.
- * The element must be part of the DOM tree to have page coordinates (`display:none` or elements not appended return `false`).
- * @param {Array} pos Contains X & Y [x, y] values for new position (coordinates are page-based).
- * @param {Boolean/Object} animate (optional) `true` for the default animation, or a standard Element animation config object.
- * @return {Ext.dom.Element} this
- */
- setXY: function(pos) {
- var me = this;
- if (arguments.length > 1) {
- pos = [pos, arguments[1]];
- }
- // me.position();
- var pts = me.translatePoints(pos),
- style = me.dom.style;
- for (pos in pts) {
- if (!pts.hasOwnProperty(pos)) {
- continue;
- }
- if (!isNaN(pts[pos])) style[pos] = pts[pos] + "px";
- }
- return me;
- },
- /**
- * Gets the left X coordinate.
- * @return {Number}
- */
- getLeft: function() {
- return parseInt(this.getStyle('left'), 10) || 0;
- },
- /**
- * Gets the right X coordinate of the element (element X position + element width).
- * @return {Number}
- */
- getRight: function() {
- return parseInt(this.getStyle('right'), 10) || 0;
- },
- /**
- * Gets the top Y coordinate.
- * @return {Number}
- */
- getTop: function() {
- return parseInt(this.getStyle('top'), 10) || 0;
- },
- /**
- * Gets the bottom Y coordinate of the element (element Y position + element height).
- * @return {Number}
- */
- getBottom: function() {
- return parseInt(this.getStyle('bottom'), 10) || 0;
- },
- /**
- * Translates the passed page coordinates into left/top CSS values for this element.
- * @param {Number/Array} x The page `x` or an array containing [x, y].
- * @param {Number} y (optional) The page `y`, required if `x` is not an array.
- * @return {Object} An object with `left` and `top` properties. e.g. `{left: (value), top: (value)}`.
- */
- translatePoints: function(x, y) {
- y = isNaN(x[1]) ? y : x[1];
- x = isNaN(x[0]) ? x : x[0];
- var me = this,
- relative = me.isStyle('position', 'relative'),
- o = me.getXY(),
- l = parseInt(me.getStyle('left'), 10),
- t = parseInt(me.getStyle('top'), 10);
- l = !isNaN(l) ? l : (relative ? 0 : me.dom.offsetLeft);
- t = !isNaN(t) ? t : (relative ? 0 : me.dom.offsetTop);
- return {left: (x - o[0] + l), top: (y - o[1] + t)};
- },
- /**
- * Sets the element's box. Use {@link #getBox} on another element to get a box object.
- * @param {Object} box The box to fill, for example:
- *
- * {
- * left: ...,
- * top: ...,
- * width: ...,
- * height: ...
- * }
- *
- * @return {Ext.dom.Element} this
- */
- setBox: function(box) {
- var me = this,
- width = box.width,
- height = box.height,
- top = box.top,
- left = box.left;
- if (left !== undefined) {
- me.setLeft(left);
- }
- if (top !== undefined) {
- me.setTop(top);
- }
- if (width !== undefined) {
- me.setWidth(width);
- }
- if (height !== undefined) {
- me.setHeight(height);
- }
- return this;
- },
- /**
- * Return an object defining the area of this Element which can be passed to {@link #setBox} to
- * set another Element's size/location to match this element.
- *
- * The returned object may also be addressed as an Array where index 0 contains the X position
- * and index 1 contains the Y position. So the result may also be used for {@link #setXY}.
- *
- * @param {Boolean} contentBox (optional) If `true` a box for the content of the element is returned.
- * @param {Boolean} local (optional) If `true` the element's left and top are returned instead of page x/y.
- * @return {Object} An object in the format
- * @return {Number} return.x The element's X position.
- * @return {Number} return.y The element's Y position.
- * @return {Number} return.width The element's width.
- * @return {Number} return.height The element's height.
- * @return {Number} return.bottom The element's lower bound.
- * @return {Number} return.right The element's rightmost bound.
- */
- getBox: function(contentBox, local) {
- var me = this,
- dom = me.dom,
- width = dom.offsetWidth,
- height = dom.offsetHeight,
- xy, box, l, r, t, b;
- if (!local) {
- xy = me.getXY();
- }
- else if (contentBox) {
- xy = [0, 0];
- }
- else {
- xy = [parseInt(me.getStyle("left"), 10) || 0, parseInt(me.getStyle("top"), 10) || 0];
- }
- if (!contentBox) {
- box = {
- x: xy[0],
- y: xy[1],
- 0: xy[0],
- 1: xy[1],
- width: width,
- height: height
- };
- }
- else {
- l = me.getBorderWidth.call(me, "l") + me.getPadding.call(me, "l");
- r = me.getBorderWidth.call(me, "r") + me.getPadding.call(me, "r");
- t = me.getBorderWidth.call(me, "t") + me.getPadding.call(me, "t");
- b = me.getBorderWidth.call(me, "b") + me.getPadding.call(me, "b");
- box = {
- x: xy[0] + l,
- y: xy[1] + t,
- 0: xy[0] + l,
- 1: xy[1] + t,
- width: width - (l + r),
- height: height - (t + b)
- };
- }
- box.left = box.x;
- box.top = box.y;
- box.right = box.x + box.width;
- box.bottom = box.y + box.height;
- return box;
- },
- /**
- * Return an object defining the area of this Element which can be passed to {@link #setBox} to
- * set another Element's size/location to match this element.
- * @param {Boolean} asRegion (optional) If `true` an {@link Ext.util.Region} will be returned.
- * @return {Object} box An object in the format:
- *
- * {
- * x: <Element's X position>,
- * y: <Element's Y position>,
- * width: <Element's width>,
- * height: <Element's height>,
- * bottom: <Element's lower bound>,
- * right: <Element's rightmost bound>
- * }
- *
- * The returned object may also be addressed as an Array where index 0 contains the X position
- * and index 1 contains the Y position. So the result may also be used for {@link #setXY}.
- */
- getPageBox: function(getRegion) {
- var me = this,
- el = me.dom,
- w = el.offsetWidth,
- h = el.offsetHeight,
- xy = me.getXY(),
- t = xy[1],
- r = xy[0] + w,
- b = xy[1] + h,
- l = xy[0];
- if (!el) {
- return new Ext.util.Region();
- }
- if (getRegion) {
- return new Ext.util.Region(t, r, b, l);
- }
- else {
- return {
- left: l,
- top: t,
- width: w,
- height: h,
- right: r,
- bottom: b
- };
- }
- }
- });
- //@tag dom,core
- //@define Ext.Element-all
- //@define Ext.Element-style
- //@require Ext.Element-position
- /**
- * @class Ext.dom.Element
- */
- Ext.dom.Element.addMembers({
- WIDTH: 'width',
- HEIGHT: 'height',
- MIN_WIDTH: 'min-width',
- MIN_HEIGHT: 'min-height',
- MAX_WIDTH: 'max-width',
- MAX_HEIGHT: 'max-height',
- TOP: 'top',
- RIGHT: 'right',
- BOTTOM: 'bottom',
- LEFT: 'left',
- /**
- * @property VISIBILITY
- * Visibility mode constant for use with {@link #setVisibilityMode}. Use `visibility` to hide element.
- */
- VISIBILITY: 1,
- /**
- * @property DISPLAY
- * Visibility mode constant for use with {@link #setVisibilityMode}. Use `display` to hide element.
- */
- DISPLAY: 2,
- /**
- * @property OFFSETS
- * Visibility mode constant for use with {@link #setVisibilityMode}. Use offsets to hide element.
- */
- OFFSETS: 3,
- SEPARATOR: '-',
- trimRe: /^\s+|\s+$/g,
- wordsRe: /\w/g,
- spacesRe: /\s+/,
- styleSplitRe: /\s*(?::|;)\s*/,
- transparentRe: /^(?:transparent|(?:rgba[(](?:\s*\d+\s*[,]){3}\s*0\s*[)]))$/i,
- classNameSplitRegex: /[\s]+/,
- borders: {
- t: 'border-top-width',
- r: 'border-right-width',
- b: 'border-bottom-width',
- l: 'border-left-width'
- },
- paddings: {
- t: 'padding-top',
- r: 'padding-right',
- b: 'padding-bottom',
- l: 'padding-left'
- },
- margins: {
- t: 'margin-top',
- r: 'margin-right',
- b: 'margin-bottom',
- l: 'margin-left'
- },
- /**
- * @property {String} defaultUnit
- * The default unit to append to CSS values where a unit isn't provided.
- */
- defaultUnit: "px",
- isSynchronized: false,
- /**
- * @private
- */
- synchronize: function() {
- var dom = this.dom,
- hasClassMap = {},
- className = dom.className,
- classList, i, ln, name;
- if (className.length > 0) {
- classList = dom.className.split(this.classNameSplitRegex);
- for (i = 0, ln = classList.length; i < ln; i++) {
- name = classList[i];
- hasClassMap[name] = true;
- }
- }
- else {
- classList = [];
- }
- this.classList = classList;
- this.hasClassMap = hasClassMap;
- this.isSynchronized = true;
- return this;
- },
- /**
- * Adds the given CSS class(es) to this Element.
- * @param {String} names The CSS class(es) to add to this element.
- * @param {String} [prefix] (optional) Prefix to prepend to each class.
- * @param {String} [suffix] (optional) Suffix to append to each class.
- */
- addCls: function(names, prefix, suffix) {
- if (!names) {
- return this;
- }
- if (!this.isSynchronized) {
- this.synchronize();
- }
- var dom = this.dom,
- map = this.hasClassMap,
- classList = this.classList,
- SEPARATOR = this.SEPARATOR,
- i, ln, name;
- prefix = prefix ? prefix + SEPARATOR : '';
- suffix = suffix ? SEPARATOR + suffix : '';
- if (typeof names == 'string') {
- names = names.split(this.spacesRe);
- }
- for (i = 0, ln = names.length; i < ln; i++) {
- name = prefix + names[i] + suffix;
- if (!map[name]) {
- map[name] = true;
- classList.push(name);
- }
- }
- dom.className = classList.join(' ');
- return this;
- },
- /**
- * Removes the given CSS class(es) from this Element.
- * @param {String} names The CSS class(es) to remove from this element.
- * @param {String} [prefix=''] (optional) Prefix to prepend to each class to be removed.
- * @param {String} [suffix=''] (optional) Suffix to append to each class to be removed.
- */
- removeCls: function(names, prefix, suffix) {
- if (!names) {
- return this;
- }
- if (!this.isSynchronized) {
- this.synchronize();
- }
- if (!suffix) {
- suffix = '';
- }
- var dom = this.dom,
- map = this.hasClassMap,
- classList = this.classList,
- SEPARATOR = this.SEPARATOR,
- i, ln, name;
- prefix = prefix ? prefix + SEPARATOR : '';
- suffix = suffix ? SEPARATOR + suffix : '';
- if (typeof names == 'string') {
- names = names.split(this.spacesRe);
- }
- for (i = 0, ln = names.length; i < ln; i++) {
- name = prefix + names[i] + suffix;
- if (map[name]) {
- delete map[name];
- Ext.Array.remove(classList, name);
- }
- }
- dom.className = classList.join(' ');
- return this;
- },
- /**
- * Replaces a CSS class on the element with another. If the old name does not exist, the new name will simply be added.
- * @param {String} oldClassName The CSS class to replace.
- * @param {String} newClassName The replacement CSS class.
- * @return {Ext.dom.Element} this
- */
- replaceCls: function(oldName, newName, prefix, suffix) {
- return this.removeCls(oldName, prefix, suffix).addCls(newName, prefix, suffix);
- },
- /**
- * Checks if the specified CSS class exists on this element's DOM node.
- * @param {String} className The CSS class to check for.
- * @return {Boolean} `true` if the class exists, else `false`.
- */
- hasCls: function(name) {
- if (!this.isSynchronized) {
- this.synchronize();
- }
- return this.hasClassMap.hasOwnProperty(name);
- },
- /**
- * Toggles the specified CSS class on this element (removes it if it already exists, otherwise adds it).
- * @param {String} className The CSS class to toggle.
- * @return {Ext.dom.Element} this
- */
- toggleCls: function(className, force){
- if (typeof force !== 'boolean') {
- force = !this.hasCls(className);
- }
- return (force) ? this.addCls(className) : this.removeCls(className);
- },
- /**
- * @private
- * @param firstClass
- * @param secondClass
- * @param flag
- * @param prefix
- * @return {Mixed}
- */
- swapCls: function(firstClass, secondClass, flag, prefix) {
- if (flag === undefined) {
- flag = true;
- }
- var addedClass = flag ? firstClass : secondClass,
- removedClass = flag ? secondClass : firstClass;
- if (removedClass) {
- this.removeCls(prefix ? prefix + '-' + removedClass : removedClass);
- }
- if (addedClass) {
- this.addCls(prefix ? prefix + '-' + addedClass : addedClass);
- }
- return this;
- },
- /**
- * Set the width of this Element.
- * @param {Number/String} width The new width.
- * @return {Ext.dom.Element} this
- */
- setWidth: function(width) {
- return this.setLengthValue(this.WIDTH, width);
- },
- /**
- * Set the height of this Element.
- * @param {Number/String} height The new height.
- * @return {Ext.dom.Element} this
- */
- setHeight: function(height) {
- return this.setLengthValue(this.HEIGHT, height);
- },
- /**
- * Set the size of this Element.
- *
- * @param {Number/String} width The new width. This may be one of:
- *
- * - A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).
- * - A String used to set the CSS width style. Animation may **not** be used.
- * - A size object in the format `{width: widthValue, height: heightValue}`.
- *
- * @param {Number/String} height The new height. This may be one of:
- *
- * - A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels).
- * - A String used to set the CSS height style. Animation may **not** be used.
- * @return {Ext.dom.Element} this
- */
- setSize: function(width, height) {
- if (Ext.isObject(width)) {
- // in case of object from getSize()
- height = width.height;
- width = width.width;
- }
- this.setWidth(width);
- this.setHeight(height);
- return this;
- },
- /**
- * Set the minimum width of this Element.
- * @param {Number/String} width The new minimum width.
- * @return {Ext.dom.Element} this
- */
- setMinWidth: function(width) {
- return this.setLengthValue(this.MIN_WIDTH, width);
- },
- /**
- * Set the minimum height of this Element.
- * @param {Number/String} height The new minimum height.
- * @return {Ext.dom.Element} this
- */
- setMinHeight: function(height) {
- return this.setLengthValue(this.MIN_HEIGHT, height);
- },
- /**
- * Set the maximum width of this Element.
- * @param {Number/String} width The new maximum width.
- * @return {Ext.dom.Element} this
- */
- setMaxWidth: function(width) {
- return this.setLengthValue(this.MAX_WIDTH, width);
- },
- /**
- * Set the maximum height of this Element.
- * @param {Number/String} height The new maximum height.
- * @return {Ext.dom.Element} this
- */
- setMaxHeight: function(height) {
- return this.setLengthValue(this.MAX_HEIGHT, height);
- },
- /**
- * Sets the element's top position directly using CSS style (instead of {@link #setY}).
- * @param {String} top The top CSS property value.
- * @return {Ext.dom.Element} this
- */
- setTop: function(top) {
- return this.setLengthValue(this.TOP, top);
- },
- /**
- * Sets the element's CSS right style.
- * @param {String} right The right CSS property value.
- * @return {Ext.dom.Element} this
- */
- setRight: function(right) {
- return this.setLengthValue(this.RIGHT, right);
- },
- /**
- * Sets the element's CSS bottom style.
- * @param {String} bottom The bottom CSS property value.
- * @return {Ext.dom.Element} this
- */
- setBottom: function(bottom) {
- return this.setLengthValue(this.BOTTOM, bottom);
- },
- /**
- * Sets the element's left position directly using CSS style (instead of {@link #setX}).
- * @param {String} left The left CSS property value.
- * @return {Ext.dom.Element} this
- */
- setLeft: function(left) {
- return this.setLengthValue(this.LEFT, left);
- },
- setMargin: function(margin) {
- var domStyle = this.dom.style;
- if (margin || margin === 0) {
- margin = this.self.unitizeBox((margin === true) ? 5 : margin);
- domStyle.setProperty('margin', margin, 'important');
- }
- else {
- domStyle.removeProperty('margin-top');
- domStyle.removeProperty('margin-right');
- domStyle.removeProperty('margin-bottom');
- domStyle.removeProperty('margin-left');
- }
- },
- setPadding: function(padding) {
- var domStyle = this.dom.style;
- if (padding || padding === 0) {
- padding = this.self.unitizeBox((padding === true) ? 5 : padding);
- domStyle.setProperty('padding', padding, 'important');
- }
- else {
- domStyle.removeProperty('padding-top');
- domStyle.removeProperty('padding-right');
- domStyle.removeProperty('padding-bottom');
- domStyle.removeProperty('padding-left');
- }
- },
- setBorder: function(border) {
- var domStyle = this.dom.style;
- if (border || border === 0) {
- border = this.self.unitizeBox((border === true) ? 1 : border);
- domStyle.setProperty('border-width', border, 'important');
- }
- else {
- domStyle.removeProperty('border-top-width');
- domStyle.removeProperty('border-right-width');
- domStyle.removeProperty('border-bottom-width');
- domStyle.removeProperty('border-left-width');
- }
- },
- setLengthValue: function(name, value) {
- var domStyle = this.dom.style;
- if (value === null) {
- domStyle.removeProperty(name);
- return this;
- }
- if (typeof value == 'number') {
- value = value + 'px';
- }
- domStyle.setProperty(name, value, 'important');
- return this;
- },
- /**
- * Sets the visibility of the element (see details). If the `visibilityMode` is set to `Element.DISPLAY`, it will use
- * the display property to hide the element, otherwise it uses visibility. The default is to hide and show using the `visibility` property.
- * @param {Boolean} visible Whether the element is visible.
- * @return {Ext.Element} this
- */
- setVisible: function(visible) {
- var mode = this.getVisibilityMode(),
- method = visible ? 'removeCls' : 'addCls';
- switch (mode) {
- case this.VISIBILITY:
- this.removeCls(['x-hidden-display', 'x-hidden-offsets']);
- this[method]('x-hidden-visibility');
- break;
- case this.DISPLAY:
- this.removeCls(['x-hidden-visibility', 'x-hidden-offsets']);
- this[method]('x-hidden-display');
- break;
- case this.OFFSETS:
- this.removeCls(['x-hidden-visibility', 'x-hidden-display']);
- this[method]('x-hidden-offsets');
- break;
- }
- return this;
- },
- getVisibilityMode: function() {
- var dom = this.dom,
- mode = Ext.dom.Element.data(dom, 'visibilityMode');
- if (mode === undefined) {
- Ext.dom.Element.data(dom, 'visibilityMode', mode = this.DISPLAY);
- }
- return mode;
- },
- /**
- * Use this to change the visibility mode between {@link #VISIBILITY}, {@link #DISPLAY} or {@link #OFFSETS}.
- */
- setVisibilityMode: function(mode) {
- this.self.data(this.dom, 'visibilityMode', mode);
- return this;
- },
- /**
- * Shows this element.
- * Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
- */
- show: function() {
- var dom = this.dom;
- if (dom) {
- dom.style.removeProperty('display');
- }
- },
- /**
- * Hides this element.
- * Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
- */
- hide: function() {
- this.dom.style.setProperty('display', 'none', 'important');
- },
- setVisibility: function(isVisible) {
- var domStyle = this.dom.style;
- if (isVisible) {
- domStyle.removeProperty('visibility');
- }
- else {
- domStyle.setProperty('visibility', 'hidden', 'important');
- }
- },
- /**
- * This shared object is keyed by style name (e.g., 'margin-left' or 'marginLeft'). The
- * values are objects with the following properties:
- *
- * * `name` (String) : The actual name to be presented to the DOM. This is typically the value
- * returned by {@link #normalize}.
- * * `get` (Function) : A hook function that will perform the get on this style. These
- * functions receive "(dom, el)" arguments. The `dom` parameter is the DOM Element
- * from which to get the style. The `el` argument (may be `null`) is the Ext.Element.
- * * `set` (Function) : A hook function that will perform the set on this style. These
- * functions receive "(dom, value, el)" arguments. The `dom` parameter is the DOM Element
- * from which to get this style. The `value` parameter is the new value for the style. The
- * `el` argument (may be `null`) is the Ext.Element.
- *
- * The `this` pointer is the object that contains `get` or `set`, which means that
- * `this.name` can be accessed if needed. The hook functions are both optional.
- * @private
- */
- styleHooks: {},
- // @private
- addStyles: function(sides, styles) {
- var totalSize = 0,
- sidesArr = sides.match(this.wordsRe),
- i = 0,
- len = sidesArr.length,
- side, size;
- for (; i < len; i++) {
- side = sidesArr[i];
- size = side && parseInt(this.getStyle(styles[side]), 10);
- if (size) {
- totalSize += Math.abs(size);
- }
- }
- return totalSize;
- },
- /**
- * Checks if the current value of a style is equal to a given value.
- * @param {String} style property whose value is returned.
- * @param {String} value to check against.
- * @return {Boolean} `true` for when the current value equals the given value.
- */
- isStyle: function(style, val) {
- return this.getStyle(style) == val;
- },
- getStyleValue: function(name) {
- return this.dom.style.getPropertyValue(name);
- },
- /**
- * Normalizes `currentStyle` and `computedStyle`.
- * @param {String} prop The style property whose value is returned.
- * @return {String} The current value of the style property for this element.
- */
- getStyle: function(prop) {
- var me = this,
- dom = me.dom,
- hook = me.styleHooks[prop],
- cs, result;
- if (dom == document) {
- return null;
- }
- if (!hook) {
- me.styleHooks[prop] = hook = { name: Ext.dom.Element.normalize(prop) };
- }
- if (hook.get) {
- return hook.get(dom, me);
- }
- cs = window.getComputedStyle(dom, '');
- // why the dom.style lookup? It is not true that "style == computedStyle" as
- // well as the fact that 0/false are valid answers...
- result = (cs && cs[hook.name]); // || dom.style[hook.name];
- // WebKit returns rgb values for transparent, how does this work n IE9+
- // if (!supportsTransparentColor && result == 'rgba(0, 0, 0, 0)') {
- // result = 'transparent';
- // }
- return result;
- },
- /**
- * Wrapper for setting style properties, also takes single object parameter of multiple styles.
- * @param {String/Object} property The style property to be set, or an object of multiple styles.
- * @param {String} [value] The value to apply to the given property, or `null` if an object was passed.
- * @return {Ext.dom.Element} this
- */
- setStyle: function(prop, value) {
- var me = this,
- dom = me.dom,
- hooks = me.styleHooks,
- style = dom.style,
- valueFrom = Ext.valueFrom,
- name, hook;
- // we don't promote the 2-arg form to object-form to avoid the overhead...
- if (typeof prop == 'string') {
- hook = hooks[prop];
- if (!hook) {
- hooks[prop] = hook = { name: Ext.dom.Element.normalize(prop) };
- }
- value = valueFrom(value, '');
- if (hook.set) {
- hook.set(dom, value, me);
- } else {
- style[hook.name] = value;
- }
- }
- else {
- for (name in prop) {
- if (prop.hasOwnProperty(name)) {
- hook = hooks[name];
- if (!hook) {
- hooks[name] = hook = { name: Ext.dom.Element.normalize(name) };
- }
- value = valueFrom(prop[name], '');
- if (hook.set) {
- hook.set(dom, value, me);
- }
- else {
- style[hook.name] = value;
- }
- }
- }
- }
- return me;
- },
- /**
- * Returns the offset height of the element.
- * @param {Boolean} [contentHeight] `true` to get the height minus borders and padding.
- * @return {Number} The element's height.
- */
- getHeight: function(contentHeight) {
- var dom = this.dom,
- height = contentHeight ? (dom.clientHeight - this.getPadding("tb")) : dom.offsetHeight;
- return height > 0 ? height : 0;
- },
- /**
- * Returns the offset width of the element.
- * @param {Boolean} [contentWidth] `true` to get the width minus borders and padding.
- * @return {Number} The element's width.
- */
- getWidth: function(contentWidth) {
- var dom = this.dom,
- width = contentWidth ? (dom.clientWidth - this.getPadding("lr")) : dom.offsetWidth;
- return width > 0 ? width : 0;
- },
- /**
- * Gets the width of the border(s) for the specified side(s)
- * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
- * passing `'lr'` would get the border **l**eft width + the border **r**ight width.
- * @return {Number} The width of the sides passed added together
- */
- getBorderWidth: function(side) {
- return this.addStyles(side, this.borders);
- },
- /**
- * Gets the width of the padding(s) for the specified side(s).
- * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
- * passing `'lr'` would get the padding **l**eft + the padding **r**ight.
- * @return {Number} The padding of the sides passed added together.
- */
- getPadding: function(side) {
- return this.addStyles(side, this.paddings);
- },
- /**
- * More flexible version of {@link #setStyle} for setting style properties.
- * @param {String/Object/Function} styles A style specification string, e.g. "width:100px", or object in the form `{width:"100px"}`, or
- * a function which returns such a specification.
- * @return {Ext.dom.Element} this
- */
- applyStyles: function(styles) {
- if (styles) {
- var dom = this.dom,
- styleType, i, len;
- if (typeof styles == 'function') {
- styles = styles.call();
- }
- styleType = typeof styles;
- if (styleType == 'string') {
- styles = Ext.util.Format.trim(styles).split(this.styleSplitRe);
- for (i = 0, len = styles.length; i < len;) {
- dom.style[Ext.dom.Element.normalize(styles[i++])] = styles[i++];
- }
- }
- else if (styleType == 'object') {
- this.setStyle(styles);
- }
- }
- },
- /**
- * Returns the size of the element.
- * @param {Boolean} [contentSize] `true` to get the width/size minus borders and padding.
- * @return {Object} An object containing the element's size:
- * @return {Number} return.width
- * @return {Number} return.height
- */
- getSize: function(contentSize) {
- var dom = this.dom;
- return {
- width: Math.max(0, contentSize ? (dom.clientWidth - this.getPadding("lr")) : dom.offsetWidth),
- height: Math.max(0, contentSize ? (dom.clientHeight - this.getPadding("tb")) : dom.offsetHeight)
- };
- },
- /**
- * Forces the browser to repaint this element.
- * @return {Ext.dom.Element} this
- */
- repaint: function() {
- var dom = this.dom;
- this.addCls(Ext.baseCSSPrefix + 'repaint');
- setTimeout(function() {
- Ext.fly(dom).removeCls(Ext.baseCSSPrefix + 'repaint');
- }, 1);
- return this;
- },
- /**
- * Returns an object with properties top, left, right and bottom representing the margins of this element unless sides is passed,
- * then it returns the calculated width of the sides (see {@link #getPadding}).
- * @param {String} [sides] Any combination of 'l', 'r', 't', 'b' to get the sum of those sides.
- * @return {Object/Number}
- */
- getMargin: function(side) {
- var me = this,
- hash = {t: "top", l: "left", r: "right", b: "bottom"},
- o = {},
- key;
- if (!side) {
- for (key in me.margins) {
- o[hash[key]] = parseFloat(me.getStyle(me.margins[key])) || 0;
- }
- return o;
- } else {
- return me.addStyles.call(me, side, me.margins);
- }
- }
- });
- //@tag dom,core
- //@define Ext.Element-all
- //@define Ext.Element-traversal
- //@require Ext.Element-style
- /**
- * @class Ext.dom.Element
- */
- Ext.dom.Element.addMembers({
- getParent: function() {
- return Ext.get(this.dom.parentNode);
- },
- getFirstChild: function() {
- return Ext.get(this.dom.firstElementChild);
- },
- /**
- * Returns `true` if this element is an ancestor of the passed element.
- * @param {HTMLElement/String} element The element to check.
- * @return {Boolean} `true` if this element is an ancestor of `el`, else `false`.
- */
- contains: function(element) {
- if (!element) {
- return false;
- }
- var dom = Ext.getDom(element);
- // we need el-contains-itself logic here because isAncestor does not do that:
- return (dom === this.dom) || this.self.isAncestor(this.dom, dom);
- },
- /**
- * 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')
- * @param {String} selector The simple selector to test.
- * @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
- * The max depth to search as a number or element (defaults to `50 || document.body`)
- * @param {Boolean} returnEl (optional) `true` to return a Ext.Element object instead of DOM node.
- * @return {HTMLElement/null} The matching DOM node (or `null` if no match was found).
- */
- findParent: function(simpleSelector, maxDepth, returnEl) {
- var p = this.dom,
- b = document.body,
- depth = 0,
- stopEl;
- maxDepth = maxDepth || 50;
- if (isNaN(maxDepth)) {
- stopEl = Ext.getDom(maxDepth);
- maxDepth = Number.MAX_VALUE;
- }
- while (p && p.nodeType == 1 && depth < maxDepth && p != b && p != stopEl) {
- if (Ext.DomQuery.is(p, simpleSelector)) {
- return returnEl ? Ext.get(p) : p;
- }
- depth++;
- p = p.parentNode;
- }
- return null;
- },
- /**
- * Looks at parent nodes for a match of the passed simple selector (e.g. 'div.some-class' or 'span:first-child').
- * @param {String} selector The simple selector to test.
- * @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
- * The max depth to search as a number or element (defaults to `10 || document.body`).
- * @param {Boolean} returnEl (optional) `true` to return a Ext.Element object instead of DOM node.
- * @return {HTMLElement/null} The matching DOM node (or `null` if no match was found).
- */
- findParentNode: function(simpleSelector, maxDepth, returnEl) {
- var p = Ext.fly(this.dom.parentNode, '_internal');
- return p ? p.findParent(simpleSelector, maxDepth, returnEl) : null;
- },
- /**
- * Walks up the dom looking for a parent node that matches the passed simple selector (e.g. 'div.some-class' or 'span:first-child').
- * This is a shortcut for `findParentNode()` that always returns an Ext.dom.Element.
- * @param {String} selector The simple selector to test
- * @param {Number/String/HTMLElement/Ext.Element} maxDepth (optional)
- * The max depth to search as a number or element (defaults to `10 || document.body`).
- * @return {Ext.dom.Element/null} The matching DOM node (or `null` if no match was found).
- */
- up: function(simpleSelector, maxDepth) {
- return this.findParentNode(simpleSelector, maxDepth, true);
- },
- select: function(selector, composite) {
- return Ext.dom.Element.select(selector, this.dom, composite);
- },
- /**
- * Selects child nodes based on the passed CSS selector (the selector should not contain an id).
- * @param {String} selector The CSS selector.
- * @return {HTMLElement[]} An array of the matched nodes.
- */
- query: function(selector) {
- return Ext.DomQuery.select(selector, this.dom);
- },
- /**
- * Selects a single child at any depth below this element based on the passed CSS selector (the selector should not contain an id).
- * @param {String} selector The CSS selector.
- * @param {Boolean} [returnDom=false] (optional) `true` to return the DOM node instead of Ext.dom.Element.
- * @return {HTMLElement/Ext.dom.Element} The child Ext.dom.Element (or DOM node if `returnDom` is `true`).
- */
- down: function(selector, returnDom) {
- var n = Ext.DomQuery.selectNode(selector, this.dom);
- return returnDom ? n : Ext.get(n);
- },
- /**
- * Selects a single *direct* child based on the passed CSS selector (the selector should not contain an id).
- * @param {String} selector The CSS selector.
- * @param {Boolean} [returnDom=false] (optional) `true` to return the DOM node instead of Ext.dom.Element.
- * @return {HTMLElement/Ext.dom.Element} The child Ext.dom.Element (or DOM node if `returnDom` is `true`)
- */
- child: function(selector, returnDom) {
- var node,
- me = this,
- id;
- id = Ext.get(me).id;
- // Escape . or :
- id = id.replace(/[\.:]/g, "\\$0");
- node = Ext.DomQuery.selectNode('#' + id + " > " + selector, me.dom);
- return returnDom ? node : Ext.get(node);
- },
- /**
- * Gets the parent node for this element, optionally chaining up trying to match a selector.
- * @param {String} selector (optional) Find a parent node that matches the passed simple selector.
- * @param {Boolean} returnDom (optional) `true` to return a raw DOM node instead of an Ext.dom.Element.
- * @return {Ext.dom.Element/HTMLElement/null} The parent node or `null`.
- */
- parent: function(selector, returnDom) {
- return this.matchNode('parentNode', 'parentNode', selector, returnDom);
- },
- /**
- * Gets the next sibling, skipping text nodes.
- * @param {String} selector (optional) Find the next sibling that matches the passed simple selector.
- * @param {Boolean} returnDom (optional) `true` to return a raw dom node instead of an Ext.dom.Element.
- * @return {Ext.dom.Element/HTMLElement/null} The next sibling or `null`.
- */
- next: function(selector, returnDom) {
- return this.matchNode('nextSibling', 'nextSibling', selector, returnDom);
- },
- /**
- * Gets the previous sibling, skipping text nodes.
- * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector.
- * @param {Boolean} returnDom (optional) `true` to return a raw DOM node instead of an Ext.dom.Element
- * @return {Ext.dom.Element/HTMLElement/null} The previous sibling or `null`.
- */
- prev: function(selector, returnDom) {
- return this.matchNode('previousSibling', 'previousSibling', selector, returnDom);
- },
- /**
- * Gets the first child, skipping text nodes.
- * @param {String} selector (optional) Find the next sibling that matches the passed simple selector.
- * @param {Boolean} returnDom (optional) `true` to return a raw DOM node instead of an Ext.dom.Element.
- * @return {Ext.dom.Element/HTMLElement/null} The first child or `null`.
- */
- first: function(selector, returnDom) {
- return this.matchNode('nextSibling', 'firstChild', selector, returnDom);
- },
- /**
- * Gets the last child, skipping text nodes.
- * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector.
- * @param {Boolean} returnDom (optional) `true` to return a raw DOM node instead of an Ext.dom.Element.
- * @return {Ext.dom.Element/HTMLElement/null} The last child or `null`.
- */
- last: function(selector, returnDom) {
- return this.matchNode('previousSibling', 'lastChild', selector, returnDom);
- },
- matchNode: function(dir, start, selector, returnDom) {
- if (!this.dom) {
- return null;
- }
- var n = this.dom[start];
- while (n) {
- if (n.nodeType == 1 && (!selector || Ext.DomQuery.is(n, selector))) {
- return !returnDom ? Ext.get(n) : n;
- }
- n = n[dir];
- }
- return null;
- },
- isAncestor: function(element) {
- return this.self.isAncestor.call(this.self, this.dom, element);
- }
- });
- //@tag dom,core
- //@require Ext.Element-all
- /**
- * This class encapsulates a *collection* of DOM elements, providing methods to filter members, or to perform collective
- * actions upon the whole set.
- *
- * Although they are not listed, this class supports all of the methods of {@link Ext.dom.Element} and
- * {@link Ext.Anim}. The methods from these classes will be performed on all the elements in this collection.
- *
- * Example:
- *
- * var els = Ext.select("#some-el div.some-class");
- * // or select directly from an existing element
- * var el = Ext.get('some-el');
- * el.select('div.some-class');
- *
- * els.setWidth(100); // all elements become 100 width
- * els.hide(true); // all elements fade out and hide
- * // or
- * els.setWidth(100).hide(true);
- *
- * @mixins Ext.dom.Element
- */
- Ext.define('Ext.dom.CompositeElementLite', {
- alternateClassName: ['Ext.CompositeElementLite', 'Ext.CompositeElement'],
- requires: ['Ext.dom.Element'],
-
- // We use the @mixins tag above to document that CompositeElement has
- // all the same methods as Element, but the @mixins tag also pulls in
- // configs and properties which we don't want, so hide them explicitly:
- /** @cfg bubbleEvents @hide */
- /** @cfg listeners @hide */
- /** @property DISPLAY @hide */
- /** @property OFFSETS @hide */
- /** @property VISIBILITY @hide */
- /** @property defaultUnit @hide */
- /** @property dom @hide */
- /** @property id @hide */
- // Also hide the static #get method that also gets inherited
- /** @method get @static @hide */
- statics: {
- /**
- * @private
- * @static
- * Copies all of the functions from Ext.dom.Element's prototype onto CompositeElementLite's prototype.
- */
- importElementMethods: function() {
- }
- },
- constructor: function(elements, root) {
- /**
- * @property {HTMLElement[]} elements
- * @readonly
- * The Array of DOM elements which this CompositeElement encapsulates.
- *
- * This will not *usually* be accessed in developers' code, but developers wishing to augment the capabilities
- * of the CompositeElementLite class may use it when adding methods to the class.
- *
- * For example to add the `nextAll` method to the class to **add** all following siblings of selected elements,
- * the code would be
- *
- * Ext.override(Ext.dom.CompositeElementLite, {
- * nextAll: function() {
- * var elements = this.elements, i, l = elements.length, n, r = [], ri = -1;
- *
- * // Loop through all elements in this Composite, accumulating
- * // an Array of all siblings.
- * for (i = 0; i < l; i++) {
- * for (n = elements[i].nextSibling; n; n = n.nextSibling) {
- * r[++ri] = n;
- * }
- * }
- *
- * // Add all found siblings to this Composite
- * return this.add(r);
- * }
- * });
- */
- this.elements = [];
- this.add(elements, root);
- this.el = new Ext.dom.Element.Fly();
- },
- isComposite: true,
- // @private
- getElement: function(el) {
- // Set the shared flyweight dom property to the current element
- return this.el.attach(el).synchronize();
- },
- // @private
- transformElement: function(el) {
- return Ext.getDom(el);
- },
- /**
- * Returns the number of elements in this Composite.
- * @return {Number}
- */
- getCount: function() {
- return this.elements.length;
- },
- /**
- * Adds elements to this Composite object.
- * @param {HTMLElement[]/Ext.dom.CompositeElementLite} els Either an Array of DOM elements to add, or another Composite
- * object who's elements should be added.
- * @param {HTMLElement/String} [root] The root element of the query or id of the root.
- * @return {Ext.dom.CompositeElementLite} This Composite object.
- */
- add: function(els, root) {
- var elements = this.elements,
- i, ln;
- if (!els) {
- return this;
- }
- if (typeof els == "string") {
- els = Ext.dom.Element.selectorFunction(els, root);
- }
- else if (els.isComposite) {
- els = els.elements;
- }
- else if (!Ext.isIterable(els)) {
- els = [els];
- }
- for (i = 0, ln = els.length; i < ln; ++i) {
- elements.push(this.transformElement(els[i]));
- }
- return this;
- },
- invoke: function(fn, args) {
- var elements = this.elements,
- ln = elements.length,
- element,
- i;
- for (i = 0; i < ln; i++) {
- element = elements[i];
- if (element) {
- Ext.dom.Element.prototype[fn].apply(this.getElement(element), args);
- }
- }
- return this;
- },
- /**
- * Returns a flyweight Element of the dom element object at the specified index.
- * @param {Number} index
- * @return {Ext.dom.Element}
- */
- item: function(index) {
- var el = this.elements[index],
- out = null;
- if (el) {
- out = this.getElement(el);
- }
- return out;
- },
- // fixes scope with flyweight.
- addListener: function(eventName, handler, scope, opt) {
- var els = this.elements,
- len = els.length,
- i, e;
- for (i = 0; i < len; i++) {
- e = els[i];
- if (e) {
- e.on(eventName, handler, scope || e, opt);
- }
- }
- return this;
- },
- /**
- * Calls the passed function for each element in this composite.
- * @param {Function} fn The function to call.
- * @param {Ext.dom.Element} fn.el The current Element in the iteration. **This is the flyweight
- * (shared) Ext.dom.Element instance, so if you require a a reference to the dom node, use el.dom.**
- * @param {Ext.dom.CompositeElementLite} fn.c This Composite object.
- * @param {Number} fn.index The zero-based index in the iteration.
- * @param {Object} [scope] The scope (this reference) in which the function is executed.
- * Defaults to the Element.
- * @return {Ext.dom.CompositeElementLite} this
- */
- each: function(fn, scope) {
- var me = this,
- els = me.elements,
- len = els.length,
- i, e;
- for (i = 0; i < len; i++) {
- e = els[i];
- if (e) {
- e = this.getElement(e);
- if (fn.call(scope || e, e, me, i) === false) {
- break;
- }
- }
- }
- return me;
- },
- /**
- * Clears this Composite and adds the elements passed.
- * @param {HTMLElement[]/Ext.dom.CompositeElementLite} els Either an array of DOM elements, or another Composite from which
- * to fill this Composite.
- * @return {Ext.dom.CompositeElementLite} this
- */
- fill: function(els) {
- var me = this;
- me.elements = [];
- me.add(els);
- return me;
- },
- /**
- * Filters this composite to only elements that match the passed selector.
- * @param {String/Function} selector A string CSS selector or a comparison function. The comparison function will be
- * called with the following arguments:
- * @param {Ext.dom.Element} selector.el The current DOM element.
- * @param {Number} selector.index The current index within the collection.
- * @return {Ext.dom.CompositeElementLite} this
- */
- filter: function(selector) {
- var els = [],
- me = this,
- fn = Ext.isFunction(selector) ? selector
- : function(el) {
- return el.is(selector);
- };
- me.each(function(el, self, i) {
- if (fn(el, i) !== false) {
- els[els.length] = me.transformElement(el);
- }
- });
- me.elements = els;
- return me;
- },
- /**
- * Find the index of the passed element within the composite collection.
- * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, or an Ext.dom.Element, or an HtmlElement
- * to find within the composite collection.
- * @return {Number} The index of the passed Ext.dom.Element in the composite collection, or -1 if not found.
- */
- indexOf: function(el) {
- return Ext.Array.indexOf(this.elements, this.transformElement(el));
- },
- /**
- * Replaces the specified element with the passed element.
- * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, the Element itself, the index of the
- * element in this composite to replace.
- * @param {String/Ext.Element} replacement The id of an element or the Element itself.
- * @param {Boolean} [domReplace] `true` to remove and replace the element in the document too.
- * @return {Ext.dom.CompositeElementLite} this
- */
- replaceElement: function(el, replacement, domReplace) {
- var index = !isNaN(el) ? el : this.indexOf(el),
- d;
- if (index > -1) {
- replacement = Ext.getDom(replacement);
- if (domReplace) {
- d = this.elements[index];
- d.parentNode.insertBefore(replacement, d);
- Ext.removeNode(d);
- }
- Ext.Array.splice(this.elements, index, 1, replacement);
- }
- return this;
- },
- /**
- * Removes all elements.
- */
- clear: function() {
- this.elements = [];
- },
- addElements: function(els, root) {
- if (!els) {
- return this;
- }
- if (typeof els == "string") {
- els = Ext.dom.Element.selectorFunction(els, root);
- }
- var yels = this.elements;
- Ext.each(els, function(e) {
- yels.push(Ext.get(e));
- });
- return this;
- },
- /**
- * Returns the first Element
- * @return {Ext.dom.Element}
- */
- first: function() {
- return this.item(0);
- },
- /**
- * Returns the last Element
- * @return {Ext.dom.Element}
- */
- last: function() {
- return this.item(this.getCount() - 1);
- },
- /**
- * Returns `true` if this composite contains the passed element
- * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, or an Ext.Element, or an HtmlElement to
- * find within the composite collection.
- * @return {Boolean}
- */
- contains: function(el) {
- return this.indexOf(el) != -1;
- },
- /**
- * Removes the specified element(s).
- * @param {String/HTMLElement/Ext.Element/Number} el The id of an element, the Element itself, the index of the
- * element in this composite or an array of any of those.
- * @param {Boolean} [removeDom] `true` to also remove the element from the document
- * @return {Ext.dom.CompositeElementLite} this
- */
- removeElement: function(keys, removeDom) {
- var me = this,
- elements = this.elements,
- el;
- Ext.each(keys, function(val) {
- if ((el = (elements[val] || elements[val = me.indexOf(val)]))) {
- if (removeDom) {
- if (el.dom) {
- el.remove();
- }
- else {
- Ext.removeNode(el);
- }
- }
- Ext.Array.erase(elements, val, 1);
- }
- });
- return this;
- }
- }, function() {
- var Element = Ext.dom.Element,
- elementPrototype = Element.prototype,
- prototype = this.prototype,
- name;
- for (name in elementPrototype) {
- if (typeof elementPrototype[name] == 'function'){
- (function(key) {
- prototype[key] = prototype[key] || function() {
- return this.invoke(key, arguments);
- };
- }).call(prototype, name);
- }
- }
- prototype.on = prototype.addListener;
- if (Ext.DomQuery){
- Element.selectorFunction = Ext.DomQuery.select;
- }
- /**
- * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
- * to be applied to many related elements in one statement through the returned
- * {@link Ext.dom.CompositeElementLite CompositeElementLite} object.
- * @param {String/HTMLElement[]} selector The CSS selector or an array of elements
- * @param {HTMLElement/String} [root] The root element of the query or id of the root
- * @return {Ext.dom.CompositeElementLite}
- * @member Ext.dom.Element
- * @method select
- */
- Element.select = function(selector, root) {
- var elements;
- if (typeof selector == "string") {
- elements = Element.selectorFunction(selector, root);
- }
- else if (selector.length !== undefined) {
- elements = selector;
- }
- else {
- //<debug>
- throw new Error("[Ext.select] Invalid selector specified: " + selector);
- //</debug>
- }
- return new Ext.CompositeElementLite(elements);
- };
- /**
- * @member Ext
- * @method select
- * @alias Ext.dom.Element#select
- */
- Ext.select = function() {
- return Element.select.apply(Element, arguments);
- };
- });
- //@require Ext.Class
- //@require Ext.ClassManager
- //@require Ext.Loader
- /**
- * Base class for all mixins.
- * @private
- */
- Ext.define('Ext.mixin.Mixin', {
- onClassExtended: function(cls, data) {
- var mixinConfig = data.mixinConfig,
- parentClassMixinConfig,
- beforeHooks, afterHooks;
- if (mixinConfig) {
- parentClassMixinConfig = cls.superclass.mixinConfig;
- if (parentClassMixinConfig) {
- mixinConfig = data.mixinConfig = Ext.merge({}, parentClassMixinConfig, mixinConfig);
- }
- data.mixinId = mixinConfig.id;
- beforeHooks = mixinConfig.beforeHooks;
- afterHooks = mixinConfig.hooks || mixinConfig.afterHooks;
- if (beforeHooks || afterHooks) {
- Ext.Function.interceptBefore(data, 'onClassMixedIn', function(targetClass) {
- var mixin = this.prototype;
- if (beforeHooks) {
- Ext.Object.each(beforeHooks, function(from, to) {
- targetClass.override(to, function() {
- if (mixin[from].apply(this, arguments) !== false) {
- return this.callOverridden(arguments);
- }
- });
- });
- }
- if (afterHooks) {
- Ext.Object.each(afterHooks, function(from, to) {
- targetClass.override(to, function() {
- var ret = this.callOverridden(arguments);
- mixin[from].apply(this, arguments);
- return ret;
- });
- });
- }
- });
- }
- }
- }
- });
- //@require @core
- /**
- * @private
- */
- Ext.define('Ext.event.ListenerStack', {
- currentOrder: 'current',
- length: 0,
- constructor: function() {
- this.listeners = {
- before: [],
- current: [],
- after: []
- };
- this.lateBindingMap = {};
- return this;
- },
- add: function(fn, scope, options, order) {
- var lateBindingMap = this.lateBindingMap,
- listeners = this.getAll(order),
- i = listeners.length,
- bindingMap, listener, id;
- if (typeof fn == 'string' && scope.isIdentifiable) {
- id = scope.getId();
- bindingMap = lateBindingMap[id];
- if (bindingMap) {
- if (bindingMap[fn]) {
- return false;
- }
- else {
- bindingMap[fn] = true;
- }
- }
- else {
- lateBindingMap[id] = bindingMap = {};
- bindingMap[fn] = true;
- }
- }
- else {
- if (i > 0) {
- while (i--) {
- listener = listeners[i];
- if (listener.fn === fn && listener.scope === scope) {
- listener.options = options;
- return false;
- }
- }
- }
- }
- listener = this.create(fn, scope, options, order);
- if (options && options.prepend) {
- delete options.prepend;
- listeners.unshift(listener);
- }
- else {
- listeners.push(listener);
- }
- this.length++;
- return true;
- },
- getAt: function(index, order) {
- return this.getAll(order)[index];
- },
- getAll: function(order) {
- if (!order) {
- order = this.currentOrder;
- }
- return this.listeners[order];
- },
- count: function(order) {
- return this.getAll(order).length;
- },
- create: function(fn, scope, options, order) {
- return {
- stack: this,
- fn: fn,
- firingFn: false,
- boundFn: false,
- isLateBinding: typeof fn == 'string',
- scope: scope,
- options: options || {},
- order: order
- };
- },
- remove: function(fn, scope, order) {
- var listeners = this.getAll(order),
- i = listeners.length,
- isRemoved = false,
- lateBindingMap = this.lateBindingMap,
- listener, id;
- if (i > 0) {
- // Start from the end index, faster than looping from the
- // beginning for "single" listeners,
- // which are normally LIFO
- while (i--) {
- listener = listeners[i];
- if (listener.fn === fn && listener.scope === scope) {
- listeners.splice(i, 1);
- isRemoved = true;
- this.length--;
- if (typeof fn == 'string' && scope.isIdentifiable) {
- id = scope.getId();
- if (lateBindingMap[id] && lateBindingMap[id][fn]) {
- delete lateBindingMap[id][fn];
- }
- }
- break;
- }
- }
- }
- return isRemoved;
- }
- });
- //@require @core
- /**
- * @private
- */
- Ext.define('Ext.event.Controller', {
- isFiring: false,
- listenerStack: null,
- constructor: function(info) {
- this.firingListeners = [];
- this.firingArguments = [];
- this.setInfo(info);
- return this;
- },
- setInfo: function(info) {
- this.info = info;
- },
- getInfo: function() {
- return this.info;
- },
- setListenerStacks: function(listenerStacks) {
- this.listenerStacks = listenerStacks;
- },
- fire: function(args, action) {
- var listenerStacks = this.listenerStacks,
- firingListeners = this.firingListeners,
- firingArguments = this.firingArguments,
- push = firingListeners.push,
- ln = listenerStacks.length,
- listeners, beforeListeners, currentListeners, afterListeners,
- isActionBefore = false,
- isActionAfter = false,
- i;
- firingListeners.length = 0;
- if (action) {
- if (action.order !== 'after') {
- isActionBefore = true;
- }
- else {
- isActionAfter = true;
- }
- }
- if (ln === 1) {
- listeners = listenerStacks[0].listeners;
- beforeListeners = listeners.before;
- currentListeners = listeners.current;
- afterListeners = listeners.after;
- if (beforeListeners.length > 0) {
- push.apply(firingListeners, beforeListeners);
- }
- if (isActionBefore) {
- push.call(firingListeners, action);
- }
- if (currentListeners.length > 0) {
- push.apply(firingListeners, currentListeners);
- }
- if (isActionAfter) {
- push.call(firingListeners, action);
- }
- if (afterListeners.length > 0) {
- push.apply(firingListeners, afterListeners);
- }
- }
- else {
- for (i = 0; i < ln; i++) {
- beforeListeners = listenerStacks[i].listeners.before;
- if (beforeListeners.length > 0) {
- push.apply(firingListeners, beforeListeners);
- }
- }
- if (isActionBefore) {
- push.call(firingListeners, action);
- }
- for (i = 0; i < ln; i++) {
- currentListeners = listenerStacks[i].listeners.current;
- if (currentListeners.length > 0) {
- push.apply(firingListeners, currentListeners);
- }
- }
- if (isActionAfter) {
- push.call(firingListeners, action);
- }
- for (i = 0; i < ln; i++) {
- afterListeners = listenerStacks[i].listeners.after;
- if (afterListeners.length > 0) {
- push.apply(firingListeners, afterListeners);
- }
- }
- }
- if (firingListeners.length === 0) {
- return this;
- }
- if (!args) {
- args = [];
- }
- firingArguments.length = 0;
- firingArguments.push.apply(firingArguments, args);
- // Backwards compatibility
- firingArguments.push(null, this);
- this.doFire();
- return this;
- },
- doFire: function() {
- var firingListeners = this.firingListeners,
- firingArguments = this.firingArguments,
- optionsArgumentIndex = firingArguments.length - 2,
- i, ln, listener, options, fn, firingFn,
- boundFn, isLateBinding, scope, args, result;
- this.isPausing = false;
- this.isPaused = false;
- this.isStopped = false;
- this.isFiring = true;
- for (i = 0,ln = firingListeners.length; i < ln; i++) {
- listener = firingListeners[i];
- options = listener.options;
- fn = listener.fn;
- firingFn = listener.firingFn;
- boundFn = listener.boundFn;
- isLateBinding = listener.isLateBinding;
- scope = listener.scope;
- // Re-bind the callback if it has changed since the last time it's bound (overridden)
- if (isLateBinding && boundFn && boundFn !== scope[fn]) {
- boundFn = false;
- firingFn = false;
- }
- if (!boundFn) {
- if (isLateBinding) {
- boundFn = scope[fn];
- if (!boundFn) {
- continue;
- }
- }
- else {
- boundFn = fn;
- }
- listener.boundFn = boundFn;
- }
- if (!firingFn) {
- firingFn = boundFn;
- if (options.buffer) {
- firingFn = Ext.Function.createBuffered(firingFn, options.buffer, scope);
- }
- if (options.delay) {
- firingFn = Ext.Function.createDelayed(firingFn, options.delay, scope);
- }
- listener.firingFn = firingFn;
- }
- firingArguments[optionsArgumentIndex] = options;
- args = firingArguments;
- if (options.args) {
- args = options.args.concat(args);
- }
- if (options.single === true) {
- listener.stack.remove(fn, scope, listener.order);
- }
- result = firingFn.apply(scope, args);
- if (result === false) {
- this.stop();
- }
- if (this.isStopped) {
- break;
- }
- if (this.isPausing) {
- this.isPaused = true;
- firingListeners.splice(0, i + 1);
- return;
- }
- }
- this.isFiring = false;
- this.listenerStacks = null;
- firingListeners.length = 0;
- firingArguments.length = 0;
- this.connectingController = null;
- },
- connect: function(controller) {
- this.connectingController = controller;
- },
- resume: function() {
- var connectingController = this.connectingController;
- this.isPausing = false;
- if (this.isPaused && this.firingListeners.length > 0) {
- this.isPaused = false;
- this.doFire();
- }
- if (connectingController) {
- connectingController.resume();
- }
- return this;
- },
- isInterrupted: function() {
- return this.isStopped || this.isPaused;
- },
- stop: function() {
- var connectingController = this.connectingController;
- this.isStopped = true;
- if (connectingController) {
- this.connectingController = null;
- connectingController.stop();
- }
- this.isFiring = false;
- this.listenerStacks = null;
- return this;
- },
- pause: function() {
- var connectingController = this.connectingController;
- this.isPausing = true;
- if (connectingController) {
- connectingController.pause();
- }
- return this;
- }
- });
- //@require @core
- /**
- * @private
- */
- Ext.define('Ext.event.Dispatcher', {
- requires: [
- 'Ext.event.ListenerStack',
- 'Ext.event.Controller'
- ],
- statics: {
- getInstance: function() {
- if (!this.instance) {
- this.instance = new this();
- }
- return this.instance;
- },
- setInstance: function(instance) {
- this.instance = instance;
- return this;
- }
- },
- config: {
- publishers: {}
- },
- wildcard: '*',
- constructor: function(config) {
- this.listenerStacks = {};
- this.activePublishers = {};
- this.publishersCache = {};
- this.noActivePublishers = [];
- this.controller = null;
- this.initConfig(config);
- return this;
- },
- getListenerStack: function(targetType, target, eventName, createIfNotExist) {
- var listenerStacks = this.listenerStacks,
- map = listenerStacks[targetType],
- listenerStack;
- createIfNotExist = Boolean(createIfNotExist);
- if (!map) {
- if (createIfNotExist) {
- listenerStacks[targetType] = map = {};
- }
- else {
- return null;
- }
- }
- map = map[target];
- if (!map) {
- if (createIfNotExist) {
- listenerStacks[targetType][target] = map = {};
- }
- else {
- return null;
- }
- }
- listenerStack = map[eventName];
- if (!listenerStack) {
- if (createIfNotExist) {
- map[eventName] = listenerStack = new Ext.event.ListenerStack();
- }
- else {
- return null;
- }
- }
- return listenerStack;
- },
- getController: function(targetType, target, eventName, connectedController) {
- var controller = this.controller,
- info = {
- targetType: targetType,
- target: target,
- eventName: eventName
- };
- if (!controller) {
- this.controller = controller = new Ext.event.Controller();
- }
- if (controller.isFiring) {
- controller = new Ext.event.Controller();
- }
- controller.setInfo(info);
- if (connectedController && controller !== connectedController) {
- controller.connect(connectedController);
- }
- return controller;
- },
- applyPublishers: function(publishers) {
- var i, publisher;
- this.publishersCache = {};
- for (i in publishers) {
- if (publishers.hasOwnProperty(i)) {
- publisher = publishers[i];
- this.registerPublisher(publisher);
- }
- }
- return publishers;
- },
- registerPublisher: function(publisher) {
- var activePublishers = this.activePublishers,
- targetType = publisher.getTargetType(),
- publishers = activePublishers[targetType];
- if (!publishers) {
- activePublishers[targetType] = publishers = [];
- }
- publishers.push(publisher);
- publisher.setDispatcher(this);
- return this;
- },
- getCachedActivePublishers: function(targetType, eventName) {
- var cache = this.publishersCache,
- publishers;
- if ((publishers = cache[targetType]) && (publishers = publishers[eventName])) {
- return publishers;
- }
- return null;
- },
- cacheActivePublishers: function(targetType, eventName, publishers) {
- var cache = this.publishersCache;
- if (!cache[targetType]) {
- cache[targetType] = {};
- }
- cache[targetType][eventName] = publishers;
- return publishers;
- },
- getActivePublishers: function(targetType, eventName) {
- var publishers, activePublishers,
- i, ln, publisher;
- if ((publishers = this.getCachedActivePublishers(targetType, eventName))) {
- return publishers;
- }
- activePublishers = this.activePublishers[targetType];
- if (activePublishers) {
- publishers = [];
- for (i = 0,ln = activePublishers.length; i < ln; i++) {
- publisher = activePublishers[i];
- if (publisher.handles(eventName)) {
- publishers.push(publisher);
- }
- }
- }
- else {
- publishers = this.noActivePublishers;
- }
- return this.cacheActivePublishers(targetType, eventName, publishers);
- },
- hasListener: function(targetType, target, eventName) {
- var listenerStack = this.getListenerStack(targetType, target, eventName);
- if (listenerStack) {
- return listenerStack.count() > 0;
- }
- return false;
- },
- addListener: function(targetType, target, eventName) {
- var publishers = this.getActivePublishers(targetType, eventName),
- ln = publishers.length,
- i;
- if (ln > 0) {
- for (i = 0; i < ln; i++) {
- publishers[i].subscribe(target, eventName);
- }
- }
- return this.doAddListener.apply(this, arguments);
- },
- doAddListener: function(targetType, target, eventName, fn, scope, options, order) {
- var listenerStack = this.getListenerStack(targetType, target, eventName, true);
- return listenerStack.add(fn, scope, options, order);
- },
- removeListener: function(targetType, target, eventName) {
- var publishers = this.getActivePublishers(targetType, eventName),
- ln = publishers.length,
- i;
- if (ln > 0) {
- for (i = 0; i < ln; i++) {
- publishers[i].unsubscribe(target, eventName);
- }
- }
- return this.doRemoveListener.apply(this, arguments);
- },
- doRemoveListener: function(targetType, target, eventName, fn, scope, order) {
- var listenerStack = this.getListenerStack(targetType, target, eventName);
- if (listenerStack === null) {
- return false;
- }
- return listenerStack.remove(fn, scope, order);
- },
- clearListeners: function(targetType, target, eventName) {
- var listenerStacks = this.listenerStacks,
- ln = arguments.length,
- stacks, publishers, i, publisherGroup;
- if (ln === 3) {
- if (listenerStacks[targetType] && listenerStacks[targetType][target]) {
- this.removeListener(targetType, target, eventName);
- delete listenerStacks[targetType][target][eventName];
- }
- }
- else if (ln === 2) {
- if (listenerStacks[targetType]) {
- stacks = listenerStacks[targetType][target];
- if (stacks) {
- for (eventName in stacks) {
- if (stacks.hasOwnProperty(eventName)) {
- publishers = this.getActivePublishers(targetType, eventName);
- for (i = 0,ln = publishers.length; i < ln; i++) {
- publishers[i].unsubscribe(target, eventName, true);
- }
- }
- }
- delete listenerStacks[targetType][target];
- }
- }
- }
- else if (ln === 1) {
- publishers = this.activePublishers[targetType];
- for (i = 0,ln = publishers.length; i < ln; i++) {
- publishers[i].unsubscribeAll();
- }
- delete listenerStacks[targetType];
- }
- else {
- publishers = this.activePublishers;
- for (targetType in publishers) {
- if (publishers.hasOwnProperty(targetType)) {
- publisherGroup = publishers[targetType];
- for (i = 0,ln = publisherGroup.length; i < ln; i++) {
- publisherGroup[i].unsubscribeAll();
- }
- }
- }
- delete this.listenerStacks;
- this.listenerStacks = {};
- }
- return this;
- },
- dispatchEvent: function(targetType, target, eventName) {
- var publishers = this.getActivePublishers(targetType, eventName),
- ln = publishers.length,
- i;
- if (ln > 0) {
- for (i = 0; i < ln; i++) {
- publishers[i].notify(target, eventName);
- }
- }
- return this.doDispatchEvent.apply(this, arguments);
- },
- doDispatchEvent: function(targetType, target, eventName, args, action, connectedController) {
- var listenerStack = this.getListenerStack(targetType, target, eventName),
- wildcardStacks = this.getWildcardListenerStacks(targetType, target, eventName),
- controller;
- if ((listenerStack === null || listenerStack.length == 0)) {
- if (wildcardStacks.length == 0 && !action) {
- return;
- }
- }
- else {
- wildcardStacks.push(listenerStack);
- }
- controller = this.getController(targetType, target, eventName, connectedController);
- controller.setListenerStacks(wildcardStacks);
- controller.fire(args, action);
- return !controller.isInterrupted();
- },
- getWildcardListenerStacks: function(targetType, target, eventName) {
- var stacks = [],
- wildcard = this.wildcard,
- isEventNameNotWildcard = eventName !== wildcard,
- isTargetNotWildcard = target !== wildcard,
- stack;
- if (isEventNameNotWildcard && (stack = this.getListenerStack(targetType, target, wildcard))) {
- stacks.push(stack);
- }
- if (isTargetNotWildcard && (stack = this.getListenerStack(targetType, wildcard, eventName))) {
- stacks.push(stack);
- }
- return stacks;
- }
- });
- /**
- * Mixin that provides a common interface for publishing events. Classes using this mixin can use the {@link #fireEvent}
- * and {@link #fireAction} methods to notify listeners of events on the class.
- *
- * Classes can also define a {@link #listeners} config to add an event handler to the current object. See
- * {@link #addListener} for more details.
- *
- * ## Example
- *
- * Ext.define('Employee', {
- * mixins: ['Ext.mixin.Observable'],
- *
- * config: {
- * fullName: ''
- * },
- *
- * constructor: function(config) {
- * this.initConfig(config); // We need to initialize the config options when the class is instantiated
- * },
- *
- * quitJob: function() {
- * this.fireEvent('quit');
- * }
- * });
- *
- * var newEmployee = Ext.create('Employee', {
- *
- * fullName: 'Ed Spencer',
- *
- * listeners: {
- * quit: function() { // This function will be called when the 'quit' event is fired
- * // By default, "this" will be the object that fired the event.
- * console.log(this.getFullName() + " has quit!");
- * }
- * }
- * });
- *
- * newEmployee.quitJob(); // Will log 'Ed Spencer has quit!'
- *
- * @aside guide events
- */
- Ext.define('Ext.mixin.Observable', {
- requires: ['Ext.event.Dispatcher'],
- extend: 'Ext.mixin.Mixin',
- mixins: ['Ext.mixin.Identifiable'],
- mixinConfig: {
- id: 'observable',
- hooks: {
- destroy: 'destroy'
- }
- },
- alternateClassName: 'Ext.util.Observable',
- // @private
- isObservable: true,
- observableType: 'observable',
- validIdRegex: /^([\w\-]+)$/,
- observableIdPrefix: '#',
- listenerOptionsRegex: /^(?:delegate|single|delay|buffer|args|prepend)$/,
- config: {
- /**
- * @cfg {Object} listeners
- *
- * A config object containing one or more event handlers to be added to this object during initialization. This
- * should be a valid listeners `config` object as specified in the {@link #addListener} example for attaching
- * multiple handlers at once.
- *
- * See the [Event guide](#!/guide/events) for more
- *
- * __Note:__ It is bad practice to specify a listener's `config` when you are defining a class using `Ext.define()`.
- * Instead, only specify listeners when you are instantiating your class with `Ext.create()`.
- * @accessor
- */
- listeners: null,
- /**
- * @cfg {String/String[]} bubbleEvents The event name to bubble, or an Array of event names.
- * @accessor
- */
- bubbleEvents: null
- },
- constructor: function(config) {
- this.initConfig(config);
- },
- applyListeners: function(listeners) {
- if (listeners) {
- this.addListener(listeners);
- }
- },
- applyBubbleEvents: function(bubbleEvents) {
- if (bubbleEvents) {
- this.enableBubble(bubbleEvents);
- }
- },
- getOptimizedObservableId: function() {
- return this.observableId;
- },
- getObservableId: function() {
- if (!this.observableId) {
- var id = this.getUniqueId();
- //<debug error>
- if (!id.match(this.validIdRegex)) {
- Ext.Logger.error("Invalid unique id of '" + id + "' for this object", this);
- }
- //</debug>
- this.observableId = this.observableIdPrefix + id;
- this.getObservableId = this.getOptimizedObservableId;
- }
- return this.observableId;
- },
- getOptimizedEventDispatcher: function() {
- return this.eventDispatcher;
- },
- getEventDispatcher: function() {
- if (!this.eventDispatcher) {
- this.eventDispatcher = Ext.event.Dispatcher.getInstance();
- this.getEventDispatcher = this.getOptimizedEventDispatcher;
- this.getListeners();
- this.getBubbleEvents();
- }
- return this.eventDispatcher;
- },
- getManagedListeners: function(object, eventName) {
- var id = object.getUniqueId(),
- managedListeners = this.managedListeners;
- if (!managedListeners) {
- this.managedListeners = managedListeners = {};
- }
- if (!managedListeners[id]) {
- managedListeners[id] = {};
- object.doAddListener('destroy', 'clearManagedListeners', this, {
- single: true,
- args: [object]
- });
- }
- if (!managedListeners[id][eventName]) {
- managedListeners[id][eventName] = [];
- }
- return managedListeners[id][eventName];
- },
- getUsedSelectors: function() {
- var selectors = this.usedSelectors;
- if (!selectors) {
- selectors = this.usedSelectors = [];
- selectors.$map = {};
- }
- return selectors;
- },
- /**
- * Fires the specified event with the passed parameters (minus the event name, plus the `options` object passed
- * to {@link #addListener}).
- *
- * The first argument is the name of the event. Every other argument passed will be available when you listen for
- * the event.
- *
- * ## Example
- *
- * Firstly, we set up a listener for our new event.
- *
- * this.on('myevent', function(arg1, arg2, arg3, arg4, options, e) {
- * console.log(arg1); // true
- * console.log(arg2); // 2
- * console.log(arg3); // { test: 'foo' }
- * console.log(arg4); // 14
- * console.log(options); // the options added when adding the listener
- * console.log(e); // the event object with information about the event
- * });
- *
- * And then we can fire off the event.
- *
- * this.fireEvent('myevent', true, 2, { test: 'foo' }, 14);
- *
- * An event may be set to bubble up an Observable parent hierarchy by calling {@link #enableBubble}.
- *
- * @param {String} eventName The name of the event to fire.
- * @param {Object...} args Variable number of parameters are passed to handlers.
- * @return {Boolean} Returns `false` if any of the handlers return `false`, otherwise it returns `true`.
- */
- fireEvent: function(eventName) {
- var args = Array.prototype.slice.call(arguments, 1);
- return this.doFireEvent(eventName, args);
- },
- /**
- * Fires the specified event with the passed parameters and execute a function (action)
- * at the end if there are no listeners that return `false`.
- *
- * @param {String} eventName The name of the event to fire.
- * @param {Array} args Arguments to pass to handers.
- * @param {Function} fn Action.
- * @param {Object} scope Scope of fn.
- * @return {Object}
- */
- fireAction: function(eventName, args, fn, scope, options, order) {
- var fnType = typeof fn,
- action;
- if (args === undefined) {
- args = [];
- }
- if (fnType != 'undefined') {
- action = {
- fn: fn,
- isLateBinding: fnType == 'string',
- scope: scope || this,
- options: options || {},
- order: order
- };
- }
- return this.doFireEvent(eventName, args, action);
- },
- doFireEvent: function(eventName, args, action, connectedController) {
- if (this.eventFiringSuspended) {
- return;
- }
- var id = this.getObservableId(),
- dispatcher = this.getEventDispatcher();
- return dispatcher.dispatchEvent(this.observableType, id, eventName, args, action, connectedController);
- },
- /**
- * @private
- * @param name
- * @param fn
- * @param scope
- * @param options
- * @return {Boolean}
- */
- doAddListener: function(name, fn, scope, options, order) {
- var isManaged = (scope && scope !== this && scope.isIdentifiable),
- usedSelectors = this.getUsedSelectors(),
- usedSelectorsMap = usedSelectors.$map,
- selector = this.getObservableId(),
- isAdded, managedListeners, delegate;
- if (!options) {
- options = {};
- }
- if (!scope) {
- scope = this;
- }
- if (options.delegate) {
- delegate = options.delegate;
- // See https://sencha.jira.com/browse/TOUCH-1579
- selector += ' ' + delegate;
- }
- if (!(selector in usedSelectorsMap)) {
- usedSelectorsMap[selector] = true;
- usedSelectors.push(selector);
- }
- isAdded = this.addDispatcherListener(selector, name, fn, scope, options, order);
- if (isAdded && isManaged) {
- managedListeners = this.getManagedListeners(scope, name);
- managedListeners.push({
- delegate: delegate,
- scope: scope,
- fn: fn,
- order: order
- });
- }
- return isAdded;
- },
- addDispatcherListener: function(selector, name, fn, scope, options, order) {
- return this.getEventDispatcher().addListener(this.observableType, selector, name, fn, scope, options, order);
- },
- doRemoveListener: function(name, fn, scope, options, order) {
- var isManaged = (scope && scope !== this && scope.isIdentifiable),
- selector = this.getObservableId(),
- isRemoved,
- managedListeners, i, ln, listener, delegate;
- if (options && options.delegate) {
- delegate = options.delegate;
- // See https://sencha.jira.com/browse/TOUCH-1579
- selector += ' ' + delegate;
- }
- if (!scope) {
- scope = this;
- }
- isRemoved = this.removeDispatcherListener(selector, name, fn, scope, order);
- if (isRemoved && isManaged) {
- managedListeners = this.getManagedListeners(scope, name);
- for (i = 0,ln = managedListeners.length; i < ln; i++) {
- listener = managedListeners[i];
- if (listener.fn === fn && listener.scope === scope && listener.delegate === delegate && listener.order === order) {
- managedListeners.splice(i, 1);
- break;
- }
- }
- }
- return isRemoved;
- },
- removeDispatcherListener: function(selector, name, fn, scope, order) {
- return this.getEventDispatcher().removeListener(this.observableType, selector, name, fn, scope, order);
- },
- clearManagedListeners: function(object) {
- var managedListeners = this.managedListeners,
- id, namedListeners, listeners, eventName, i, ln, listener, options;
- if (!managedListeners) {
- return this;
- }
- if (object) {
- if (typeof object != 'string') {
- id = object.getUniqueId();
- }
- else {
- id = object;
- }
- namedListeners = managedListeners[id];
- for (eventName in namedListeners) {
- if (namedListeners.hasOwnProperty(eventName)) {
- listeners = namedListeners[eventName];
- for (i = 0,ln = listeners.length; i < ln; i++) {
- listener = listeners[i];
- options = {};
- if (listener.delegate) {
- options.delegate = listener.delegate;
- }
- if (this.doRemoveListener(eventName, listener.fn, listener.scope, options, listener.order)) {
- i--;
- ln--;
- }
- }
- }
- }
- delete managedListeners[id];
- return this;
- }
- for (id in managedListeners) {
- if (managedListeners.hasOwnProperty(id)) {
- this.clearManagedListeners(id);
- }
- }
- },
- /**
- * @private
- * @param operation
- * @param eventName
- * @param fn
- * @param scope
- * @param options
- * @param order
- * @return {Object}
- */
- changeListener: function(actionFn, eventName, fn, scope, options, order) {
- var eventNames,
- listeners,
- listenerOptionsRegex,
- actualOptions,
- name, value, i, ln, listener, valueType;
- if (typeof fn != 'undefined') {
- // Support for array format to add multiple listeners
- if (typeof eventName != 'string') {
- for (i = 0,ln = eventName.length; i < ln; i++) {
- name = eventName[i];
- actionFn.call(this, name, fn, scope, options, order);
- }
- return this;
- }
- actionFn.call(this, eventName, fn, scope, options, order);
- }
- else if (Ext.isArray(eventName)) {
- listeners = eventName;
- for (i = 0,ln = listeners.length; i < ln; i++) {
- listener = listeners[i];
- actionFn.call(this, listener.event, listener.fn, listener.scope, listener, listener.order);
- }
- }
- else {
- listenerOptionsRegex = this.listenerOptionsRegex;
- options = eventName;
- eventNames = [];
- listeners = [];
- actualOptions = {};
- for (name in options) {
- value = options[name];
- if (name === 'scope') {
- scope = value;
- continue;
- }
- else if (name === 'order') {
- order = value;
- continue;
- }
- if (!listenerOptionsRegex.test(name)) {
- valueType = typeof value;
- if (valueType != 'string' && valueType != 'function') {
- actionFn.call(this, name, value.fn, value.scope || scope, value, value.order || order);
- continue;
- }
- eventNames.push(name);
- listeners.push(value);
- }
- else {
- actualOptions[name] = value;
- }
- }
- for (i = 0,ln = eventNames.length; i < ln; i++) {
- actionFn.call(this, eventNames[i], listeners[i], scope, actualOptions, order);
- }
- }
- return this;
- },
- /**
- * Appends an event handler to this object. You can review the available handlers by looking at the 'events'
- * section of the documentation for the component you are working with.
- *
- * ## Combining Options
- *
- * Using the options argument, it is possible to combine different types of listeners:
- *
- * A delayed, one-time listener:
- *
- * container.on('tap', this.handleTap, this, {
- * single: true,
- * delay: 100
- * });
- *
- * ## Attaching multiple handlers in 1 call
- *
- * The method also allows for a single argument to be passed which is a config object containing properties which
- * specify multiple events. For example:
- *
- * container.on({
- * tap : this.onTap,
- * swipe: this.onSwipe,
- *
- * scope: this // Important. Ensure "this" is correct during handler execution
- * });
- *
- * One can also specify options for each event handler separately:
- *
- * container.on({
- * tap : { fn: this.onTap, scope: this, single: true },
- * swipe: { fn: button.onSwipe, scope: button }
- * });
- *
- * See the [Events Guide](#!/guide/events) for more.
- *
- * @param {String/String[]/Object} eventName The name of the event to listen for. May also be an object who's property names are
- * event names.
- * @param {Function} fn The method the event invokes. Will be called with arguments given to
- * {@link #fireEvent} plus the `options` parameter described below.
- * @param {Object} [scope] The scope (`this` reference) in which the handler function is executed. **If
- * omitted, defaults to the object which fired the event.**
- * @param {Object} [options] An object containing handler configuration.
- *
- * This object may contain any of the following properties:
- * @param {Object} [options.scope] The scope (`this` reference) in which the handler function is executed. If omitted, defaults to the object
- * which fired the event.
- * @param {Number} [options.delay] The number of milliseconds to delay the invocation of the handler after the event fires.
- * @param {Boolean} [options.single] `true` to add a handler to handle just the next firing of the event, and then remove itself.
- * @param {String} [options.order=current] The order of when the listener should be added into the listener queue.
- *
- * 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.
- *
- * Available options are `before`, `current` and `after`.
- *
- * @param {Number} [options.buffer] Causes the handler to be delayed by the specified number of milliseconds. If the event fires again within that
- * time, the original handler is _not_ invoked, but the new handler is scheduled in its place.
- * @param {String} [options.element] Allows you to add a listener onto a element of this component using the elements reference.
- *
- * Ext.create('Ext.Component', {
- * listeners: {
- * element: 'element',
- * tap: function() {
- * alert('element tap!');
- * }
- * }
- * });
- *
- * All components have the `element` reference, which is the outer most element of the component. {@link Ext.Container} also has the
- * `innerElement` element which contains all children. In most cases `element` is adequate.
- *
- * @param {String} [options.delegate] Uses {@link Ext.ComponentQuery} to delegate events to a specified query selector within this item.
- *
- * // Create a container with a two children; a button and a toolbar
- * var container = Ext.create('Ext.Container', {
- * items: [
- * {
- * xtype: 'toolbar',
- * docked: 'top',
- * title: 'My Toolbar'
- * },
- * {
- * xtype: 'button',
- * text: 'My Button'
- * }
- * ]
- * });
- *
- * container.on({
- * // Ext.Buttons have an xtype of 'button', so we use that are a selector for our delegate
- * delegate: 'button',
- *
- * tap: function() {
- * alert('Button tapped!');
- * }
- * });
- *
- * @param {String} [order='current'] The order of when the listener should be added into the listener queue.
- * Possible values are `before`, `current` and `after`.
- */
- addListener: function(eventName, fn, scope, options, order) {
- return this.changeListener(this.doAddListener, eventName, fn, scope, options, order);
- },
- toggleListener: function(toggle, eventName, fn, scope, options, order) {
- return this.changeListener(toggle ? this.doAddListener : this.doRemoveListener, eventName, fn, scope, options, order);
- },
- /**
- * Appends a before-event handler. Returning `false` from the handler will stop the event.
- *
- * Same as {@link #addListener} with `order` set to `'before'`.
- *
- * @param {String/String[]/Object} eventName The name of the event to listen for.
- * @param {Function} fn The method the event invokes.
- * @param {Object} [scope] The scope for `fn`.
- * @param {Object} [options] An object containing handler configuration.
- */
- addBeforeListener: function(eventName, fn, scope, options) {
- return this.addListener(eventName, fn, scope, options, 'before');
- },
- /**
- * Appends an after-event handler.
- *
- * Same as {@link #addListener} with `order` set to `'after'`.
- *
- * @param {String/String[]/Object} eventName The name of the event to listen for.
- * @param {Function} fn The method the event invokes.
- * @param {Object} [scope] The scope for `fn`.
- * @param {Object} [options] An object containing handler configuration.
- */
- addAfterListener: function(eventName, fn, scope, options) {
- return this.addListener(eventName, fn, scope, options, 'after');
- },
- /**
- * Removes an event handler.
- *
- * @param {String/String[]/Object} eventName The type of event the handler was associated with.
- * @param {Function} fn The handler to remove. **This must be a reference to the function passed into the
- * {@link #addListener} call.**
- * @param {Object} [scope] The scope originally specified for the handler. It must be the same as the
- * scope argument specified in the original call to {@link #addListener} or the listener will not be removed.
- * @param {Object} [options] Extra options object. See {@link #addListener} for details.
- * @param {String} [order='current'] The order of the listener to remove.
- * Possible values are `before`, `current` and `after`.
- */
- removeListener: function(eventName, fn, scope, options, order) {
- return this.changeListener(this.doRemoveListener, eventName, fn, scope, options, order);
- },
- /**
- * Removes a before-event handler.
- *
- * Same as {@link #removeListener} with `order` set to `'before'`.
- *
- * @param {String/String[]/Object} eventName The name of the event the handler was associated with.
- * @param {Function} fn The handler to remove.
- * @param {Object} [scope] The scope originally specified for `fn`.
- * @param {Object} [options] Extra options object.
- */
- removeBeforeListener: function(eventName, fn, scope, options) {
- return this.removeListener(eventName, fn, scope, options, 'before');
- },
- /**
- * Removes a before-event handler.
- *
- * Same as {@link #removeListener} with `order` set to `'after'`.
- *
- * @param {String/String[]/Object} eventName The name of the event the handler was associated with.
- * @param {Function} fn The handler to remove.
- * @param {Object} [scope] The scope originally specified for `fn`.
- * @param {Object} [options] Extra options object.
- */
- removeAfterListener: function(eventName, fn, scope, options) {
- return this.removeListener(eventName, fn, scope, options, 'after');
- },
- /**
- * Removes all listeners for this object.
- */
- clearListeners: function() {
- var usedSelectors = this.getUsedSelectors(),
- dispatcher = this.getEventDispatcher(),
- i, ln, selector;
- for (i = 0,ln = usedSelectors.length; i < ln; i++) {
- selector = usedSelectors[i];
- dispatcher.clearListeners(this.observableType, selector);
- }
- },
- /**
- * Checks to see if this object has any listeners for a specified event
- *
- * @param {String} eventName The name of the event to check for
- * @return {Boolean} True if the event is being listened for, else false
- */
- hasListener: function(eventName) {
- return this.getEventDispatcher().hasListener(this.observableType, this.getObservableId(), eventName);
- },
- /**
- * Suspends the firing of all events. (see {@link #resumeEvents})
- *
- * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
- * after the {@link #resumeEvents} call instead of discarding all suspended events.
- */
- suspendEvents: function(queueSuspended) {
- this.eventFiringSuspended = true;
- },
- /**
- * Resumes firing events (see {@link #suspendEvents}).
- *
- * If events were suspended using the `queueSuspended` parameter, then all events fired
- * during event suspension will be sent to any listeners now.
- */
- resumeEvents: function() {
- this.eventFiringSuspended = false;
- },
- /**
- * Relays selected events from the specified Observable as if the events were fired by `this`.
- * @param {Object} object The Observable whose events this object is to relay.
- * @param {String/Array/Object} events Array of event names to relay.
- */
- relayEvents: function(object, events, prefix) {
- var i, ln, oldName, newName;
- if (typeof prefix == 'undefined') {
- prefix = '';
- }
- if (typeof events == 'string') {
- events = [events];
- }
- if (Ext.isArray(events)) {
- for (i = 0,ln = events.length; i < ln; i++) {
- oldName = events[i];
- newName = prefix + oldName;
- object.addListener(oldName, this.createEventRelayer(newName), this);
- }
- }
- else {
- for (oldName in events) {
- if (events.hasOwnProperty(oldName)) {
- newName = prefix + events[oldName];
- object.addListener(oldName, this.createEventRelayer(newName), this);
- }
- }
- }
- return this;
- },
- /**
- * @private
- * @param args
- * @param fn
- */
- relayEvent: function(args, fn, scope, options, order) {
- var fnType = typeof fn,
- controller = args[args.length - 1],
- eventName = controller.getInfo().eventName,
- action;
- args = Array.prototype.slice.call(args, 0, -2);
- args[0] = this;
- if (fnType != 'undefined') {
- action = {
- fn: fn,
- scope: scope || this,
- options: options || {},
- order: order,
- isLateBinding: fnType == 'string'
- };
- }
- return this.doFireEvent(eventName, args, action, controller);
- },
- /**
- * @private
- * Creates an event handling function which re-fires the event from this object as the passed event name.
- * @param newName
- * @return {Function}
- */
- createEventRelayer: function(newName){
- return function() {
- return this.doFireEvent(newName, Array.prototype.slice.call(arguments, 0, -2));
- }
- },
- /**
- * Enables events fired by this Observable to bubble up an owner hierarchy by calling `this.getBubbleTarget()` if
- * present. There is no implementation in the Observable base class.
- *
- * @param {String/String[]} events The event name to bubble, or an Array of event names.
- */
- enableBubble: function(events) {
- var isBubblingEnabled = this.isBubblingEnabled,
- i, ln, name;
- if (!isBubblingEnabled) {
- isBubblingEnabled = this.isBubblingEnabled = {};
- }
- if (typeof events == 'string') {
- events = Ext.Array.clone(arguments);
- }
- for (i = 0,ln = events.length; i < ln; i++) {
- name = events[i];
- if (!isBubblingEnabled[name]) {
- isBubblingEnabled[name] = true;
- this.addListener(name, this.createEventBubbler(name), this);
- }
- }
- },
- createEventBubbler: function(name) {
- return function doBubbleEvent() {
- var bubbleTarget = ('getBubbleTarget' in this) ? this.getBubbleTarget() : null;
- if (bubbleTarget && bubbleTarget !== this && bubbleTarget.isObservable) {
- bubbleTarget.fireAction(name, Array.prototype.slice.call(arguments, 0, -2), doBubbleEvent, bubbleTarget, null, 'after');
- }
- }
- },
- getBubbleTarget: function() {
- return false;
- },
- destroy: function() {
- if (this.observableId) {
- this.fireEvent('destroy', this);
- this.clearListeners();
- this.clearManagedListeners();
- }
- },
- /**
- * @ignore
- */
- addEvents: Ext.emptyFn
- }, function() {
- this.createAlias({
- /**
- * @method
- * Alias for {@link #addListener}.
- * @inheritdoc Ext.mixin.Observable#addListener
- */
- on: 'addListener',
- /**
- * @method
- * Alias for {@link #removeListener}.
- * @inheritdoc Ext.mixin.Observable#removeListener
- */
- un: 'removeListener',
- /**
- * @method
- * Alias for {@link #addBeforeListener}.
- * @inheritdoc Ext.mixin.Observable#addBeforeListener
- */
- onBefore: 'addBeforeListener',
- /**
- * @method
- * Alias for {@link #addAfterListener}.
- * @inheritdoc Ext.mixin.Observable#addAfterListener
- */
- onAfter: 'addAfterListener',
- /**
- * @method
- * Alias for {@link #removeBeforeListener}.
- * @inheritdoc Ext.mixin.Observable#removeBeforeListener
- */
- unBefore: 'removeBeforeListener',
- /**
- * @method
- * Alias for {@link #removeAfterListener}.
- * @inheritdoc Ext.mixin.Observable#removeAfterListener
- */
- unAfter: 'removeAfterListener'
- });
- });
- /**
- * @private
- */
- Ext.define('Ext.Evented', {
- alternateClassName: 'Ext.EventedBase',
- mixins: ['Ext.mixin.Observable'],
- statics: {
- generateSetter: function(nameMap) {
- var internalName = nameMap.internal,
- applyName = nameMap.apply,
- changeEventName = nameMap.changeEvent,
- doSetName = nameMap.doSet;
- return function(value) {
- var initialized = this.initialized,
- oldValue = this[internalName],
- applier = this[applyName];
- if (applier) {
- value = applier.call(this, value, oldValue);
- if (typeof value == 'undefined') {
- return this;
- }
- }
- // The old value might have been changed at this point
- // (after the apply call chain) so it should be read again
- oldValue = this[internalName];
- if (value !== oldValue) {
- if (initialized) {
- this.fireAction(changeEventName, [this, value, oldValue], this.doSet, this, {
- nameMap: nameMap
- });
- }
- else {
- this[internalName] = value;
- if (this[doSetName]) {
- this[doSetName].call(this, value, oldValue);
- }
- }
- }
- return this;
- }
- }
- },
- initialized: false,
- constructor: function(config) {
- this.initialConfig = config;
- this.initialize();
- },
- initialize: function() {
- this.initConfig(this.initialConfig);
- this.initialized = true;
- },
- doSet: function(me, value, oldValue, options) {
- var nameMap = options.nameMap;
- me[nameMap.internal] = value;
- if (me[nameMap.doSet]) {
- me[nameMap.doSet].call(this, value, oldValue);
- }
- },
- onClassExtended: function(Class, data) {
- if (!data.hasOwnProperty('eventedConfig')) {
- return;
- }
- var ExtClass = Ext.Class,
- config = data.config,
- eventedConfig = data.eventedConfig,
- name, nameMap;
- data.config = (config) ? Ext.applyIf(config, eventedConfig) : eventedConfig;
- /*
- * These are generated setters for eventedConfig
- *
- * If the component is initialized, it invokes fireAction to fire the event as well,
- * which indicate something has changed. Otherwise, it just executes the action
- * (happens during initialization)
- *
- * This is helpful when we only want the event to be fired for subsequent changes.
- * Also it's a major performance improvement for instantiation when fired events
- * are mostly useless since there's no listeners
- */
- for (name in eventedConfig) {
- if (eventedConfig.hasOwnProperty(name)) {
- nameMap = ExtClass.getConfigNameMap(name);
- data[nameMap.set] = this.generateSetter(nameMap);
- }
- }
- }
- });
- /**
- * @private
- * This is the abstract class for {@link Ext.Component}.
- *
- * This should never be overridden.
- */
- Ext.define('Ext.AbstractComponent', {
- extend: 'Ext.Evented',
- onClassExtended: function(Class, members) {
- if (!members.hasOwnProperty('cachedConfig')) {
- return;
- }
- var prototype = Class.prototype,
- config = members.config,
- cachedConfig = members.cachedConfig,
- cachedConfigList = prototype.cachedConfigList,
- hasCachedConfig = prototype.hasCachedConfig,
- name, value;
- delete members.cachedConfig;
- prototype.cachedConfigList = cachedConfigList = (cachedConfigList) ? cachedConfigList.slice() : [];
- prototype.hasCachedConfig = hasCachedConfig = (hasCachedConfig) ? Ext.Object.chain(hasCachedConfig) : {};
- if (!config) {
- members.config = config = {};
- }
- for (name in cachedConfig) {
- if (cachedConfig.hasOwnProperty(name)) {
- value = cachedConfig[name];
- if (!hasCachedConfig[name]) {
- hasCachedConfig[name] = true;
- cachedConfigList.push(name);
- }
- config[name] = value;
- }
- }
- },
- getElementConfig: Ext.emptyFn,
- referenceAttributeName: 'reference',
- referenceSelector: '[reference]',
- /**
- * @private
- * Significantly improve instantiation time for Component with multiple references
- * Ext.Element instance of the reference domNode is only created the very first time
- * it's ever used.
- */
- addReferenceNode: function(name, domNode) {
- Ext.Object.defineProperty(this, name, {
- get: function() {
- var reference;
- delete this[name];
- this[name] = reference = new Ext.Element(domNode);
- return reference;
- },
- configurable: true
- });
- },
- initElement: function() {
- var prototype = this.self.prototype,
- id = this.getId(),
- referenceList = [],
- cleanAttributes = true,
- referenceAttributeName = this.referenceAttributeName,
- needsOptimization = false,
- renderTemplate, renderElement, element,
- referenceNodes, i, ln, referenceNode, reference,
- configNameCache, defaultConfig, cachedConfigList, initConfigList, initConfigMap, configList,
- elements, name, nameMap, internalName;
- if (prototype.hasOwnProperty('renderTemplate')) {
- renderTemplate = this.renderTemplate.cloneNode(true);
- renderElement = renderTemplate.firstChild;
- }
- else {
- cleanAttributes = false;
- needsOptimization = true;
- renderTemplate = document.createDocumentFragment();
- renderElement = Ext.Element.create(this.getElementConfig(), true);
- renderTemplate.appendChild(renderElement);
- }
- referenceNodes = renderTemplate.querySelectorAll(this.referenceSelector);
- for (i = 0,ln = referenceNodes.length; i < ln; i++) {
- referenceNode = referenceNodes[i];
- reference = referenceNode.getAttribute(referenceAttributeName);
- if (cleanAttributes) {
- referenceNode.removeAttribute(referenceAttributeName);
- }
- if (reference == 'element') {
- referenceNode.id = id;
- this.element = element = new Ext.Element(referenceNode);
- }
- else {
- this.addReferenceNode(reference, referenceNode);
- }
- referenceList.push(reference);
- }
- this.referenceList = referenceList;
- if (!this.innerElement) {
- this.innerElement = element;
- }
- if (!this.bodyElement) {
- this.bodyElement = this.innerElement;
- }
- if (renderElement === element.dom) {
- this.renderElement = element;
- }
- else {
- this.addReferenceNode('renderElement', renderElement);
- }
- // This happens only *once* per class, during the very first instantiation
- // to optimize renderTemplate based on cachedConfig
- if (needsOptimization) {
- configNameCache = Ext.Class.configNameCache;
- defaultConfig = this.config;
- cachedConfigList = this.cachedConfigList;
- initConfigList = this.initConfigList;
- initConfigMap = this.initConfigMap;
- configList = [];
- for (i = 0,ln = cachedConfigList.length; i < ln; i++) {
- name = cachedConfigList[i];
- nameMap = configNameCache[name];
- if (initConfigMap[name]) {
- initConfigMap[name] = false;
- Ext.Array.remove(initConfigList, name);
- }
- if (defaultConfig[name] !== null) {
- configList.push(name);
- this[nameMap.get] = this[nameMap.initGet];
- }
- }
- for (i = 0,ln = configList.length; i < ln; i++) {
- name = configList[i];
- nameMap = configNameCache[name];
- internalName = nameMap.internal;
- this[internalName] = null;
- this[nameMap.set].call(this, defaultConfig[name]);
- delete this[nameMap.get];
- prototype[internalName] = this[internalName];
- }
- renderElement = this.renderElement.dom;
- prototype.renderTemplate = renderTemplate = document.createDocumentFragment();
- renderTemplate.appendChild(renderElement.cloneNode(true));
- elements = renderTemplate.querySelectorAll('[id]');
- for (i = 0,ln = elements.length; i < ln; i++) {
- element = elements[i];
- element.removeAttribute('id');
- }
- for (i = 0,ln = referenceList.length; i < ln; i++) {
- reference = referenceList[i];
- this[reference].dom.removeAttribute('reference');
- }
- }
- return this;
- }
- });
- /**
- * Represents a collection of a set of key and value pairs. Each key in the HashMap must be unique, the same
- * key cannot exist twice. Access to items is provided via the key only. Sample usage:
- *
- * var map = Ext.create('Ext.util.HashMap');
- * map.add('key1', 1);
- * map.add('key2', 2);
- * map.add('key3', 3);
- *
- * map.each(function(key, value, length){
- * console.log(key, value, length);
- * });
- *
- * The HashMap is an unordered class, there is no guarantee when iterating over the items that they will be in
- * any particular order. If this is required, then use a {@link Ext.util.MixedCollection}.
- */
- Ext.define('Ext.util.HashMap', {
- mixins: {
- observable: 'Ext.mixin.Observable'
- },
- /**
- * @cfg {Function} keyFn
- * A function that is used to retrieve a default key for a passed object.
- * A default is provided that returns the **id** property on the object.
- * This function is only used if the add method is called with a single argument.
- */
- /**
- * Creates new HashMap.
- * @param {Object} config The configuration options
- */
- constructor: function(config) {
- /**
- * @event add
- * Fires when a new item is added to the hash.
- * @param {Ext.util.HashMap} this
- * @param {String} key The key of the added item.
- * @param {Object} value The value of the added item.
- */
- /**
- * @event clear
- * Fires when the hash is cleared.
- * @param {Ext.util.HashMap} this
- */
- /**
- * @event remove
- * Fires when an item is removed from the hash.
- * @param {Ext.util.HashMap} this
- * @param {String} key The key of the removed item.
- * @param {Object} value The value of the removed item.
- */
- /**
- * @event replace
- * Fires when an item is replaced in the hash.
- * @param {Ext.util.HashMap} this
- * @param {String} key The key of the replaced item.
- * @param {Object} value The new value for the item.
- * @param {Object} old The old value for the item.
- */
- this.callParent();
- this.mixins.observable.constructor.call(this);
- this.clear(true);
- },
- /**
- * Gets the number of items in the hash.
- * @return {Number} The number of items in the hash.
- */
- getCount: function() {
- return this.length;
- },
- /**
- * Implementation for being able to extract the key from an object if only
- * a single argument is passed.
- * @private
- * @param {String} key The key
- * @param {Object} value The value
- * @return {Array} [key, value]
- */
- getData: function(key, value) {
- // if we have no value, it means we need to get the key from the object
- if (value === undefined) {
- value = key;
- key = this.getKey(value);
- }
- return [key, value];
- },
- /**
- * Extracts the key from an object. This is a default implementation, it may be overridden.
- * @private
- * @param {Object} o The object to get the key from.
- * @return {String} The key to use.
- */
- getKey: function(o) {
- return o.id;
- },
- /**
- * Add a new item to the hash. An exception will be thrown if the key already exists.
- * @param {String} key The key of the new item.
- * @param {Object} value The value of the new item.
- * @return {Object} The value of the new item added.
- */
- add: function(key, value) {
- var me = this,
- data;
- if (me.containsKey(key)) {
- throw new Error('This key already exists in the HashMap');
- }
- data = this.getData(key, value);
- key = data[0];
- value = data[1];
- me.map[key] = value;
- ++me.length;
- me.fireEvent('add', me, key, value);
- return value;
- },
- /**
- * Replaces an item in the hash. If the key doesn't exist, the
- * `{@link #method-add}` method will be used.
- * @param {String} key The key of the item.
- * @param {Object} value The new value for the item.
- * @return {Object} The new value of the item.
- */
- replace: function(key, value) {
- var me = this,
- map = me.map,
- old;
- if (!me.containsKey(key)) {
- me.add(key, value);
- }
- old = map[key];
- map[key] = value;
- me.fireEvent('replace', me, key, value, old);
- return value;
- },
- /**
- * Remove an item from the hash.
- * @param {Object} o The value of the item to remove.
- * @return {Boolean} `true` if the item was successfully removed.
- */
- remove: function(o) {
- var key = this.findKey(o);
- if (key !== undefined) {
- return this.removeByKey(key);
- }
- return false;
- },
- /**
- * Remove an item from the hash.
- * @param {String} key The key to remove.
- * @return {Boolean} `true` if the item was successfully removed.
- */
- removeByKey: function(key) {
- var me = this,
- value;
- if (me.containsKey(key)) {
- value = me.map[key];
- delete me.map[key];
- --me.length;
- me.fireEvent('remove', me, key, value);
- return true;
- }
- return false;
- },
- /**
- * Retrieves an item with a particular key.
- * @param {String} key The key to lookup.
- * @return {Object} The value at that key. If it doesn't exist, `undefined` is returned.
- */
- get: function(key) {
- return this.map[key];
- },
- /**
- * Removes all items from the hash.
- * @return {Ext.util.HashMap} this
- */
- clear: function(/* private */ initial) {
- var me = this;
- me.map = {};
- me.length = 0;
- if (initial !== true) {
- me.fireEvent('clear', me);
- }
- return me;
- },
- /**
- * Checks whether a key exists in the hash.
- * @param {String} key The key to check for.
- * @return {Boolean} `true` if they key exists in the hash.
- */
- containsKey: function(key) {
- return this.map[key] !== undefined;
- },
- /**
- * Checks whether a value exists in the hash.
- * @param {Object} value The value to check for.
- * @return {Boolean} `true` if the value exists in the dictionary.
- */
- contains: function(value) {
- return this.containsKey(this.findKey(value));
- },
- /**
- * Return all of the keys in the hash.
- * @return {Array} An array of keys.
- */
- getKeys: function() {
- return this.getArray(true);
- },
- /**
- * Return all of the values in the hash.
- * @return {Array} An array of values.
- */
- getValues: function() {
- return this.getArray(false);
- },
- /**
- * Gets either the keys/values in an array from the hash.
- * @private
- * @param {Boolean} isKey `true` to extract the keys, otherwise, the value.
- * @return {Array} An array of either keys/values from the hash.
- */
- getArray: function(isKey) {
- var arr = [],
- key,
- map = this.map;
- for (key in map) {
- if (map.hasOwnProperty(key)) {
- arr.push(isKey ? key : map[key]);
- }
- }
- return arr;
- },
- /**
- * Executes the specified function once for each item in the hash.
- *
- * @param {Function} fn The function to execute.
- * @param {String} fn.key The key of the item.
- * @param {Number} fn.value The value of the item.
- * @param {Number} fn.length The total number of items in the hash.
- * @param {Boolean} fn.return Returning `false` from the function will cease the iteration.
- * @param {Object} [scope=this] The scope to execute in.
- * @return {Ext.util.HashMap} this
- */
- each: function(fn, scope) {
- // copy items so they may be removed during iteration.
- var items = Ext.apply({}, this.map),
- key,
- length = this.length;
- scope = scope || this;
- for (key in items) {
- if (items.hasOwnProperty(key)) {
- if (fn.call(scope, key, items[key], length) === false) {
- break;
- }
- }
- }
- return this;
- },
- /**
- * Performs a shallow copy on this hash.
- * @return {Ext.util.HashMap} The new hash object.
- */
- clone: function() {
- var hash = new Ext.util.HashMap(),
- map = this.map,
- key;
- hash.suspendEvents();
- for (key in map) {
- if (map.hasOwnProperty(key)) {
- hash.add(key, map[key]);
- }
- }
- hash.resumeEvents();
- return hash;
- },
- /**
- * @private
- * Find the key for a value.
- * @param {Object} value The value to find.
- * @return {Object} The value of the item. Returns `undefined` if not found.
- */
- findKey: function(value) {
- var key,
- map = this.map;
- for (key in map) {
- if (map.hasOwnProperty(key) && map[key] === value) {
- return key;
- }
- }
- return undefined;
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.AbstractManager', {
- /* Begin Definitions */
- requires: ['Ext.util.HashMap'],
- /* End Definitions */
- typeName: 'type',
- constructor: function(config) {
- Ext.apply(this, config || {});
- /**
- * @property {Ext.util.HashMap} all
- * Contains all of the items currently managed
- */
- this.all = Ext.create('Ext.util.HashMap');
- this.types = {};
- },
- /**
- * Returns an item by id.
- * For additional details see {@link Ext.util.HashMap#get}.
- * @param {String} id The `id` of the item.
- * @return {Object} The item, `undefined` if not found.
- */
- get : function(id) {
- return this.all.get(id);
- },
- /**
- * Registers an item to be managed.
- * @param {Object} item The item to register.
- */
- register: function(item) {
- this.all.add(item);
- },
- /**
- * Unregisters an item by removing it from this manager.
- * @param {Object} item The item to unregister.
- */
- unregister: function(item) {
- this.all.remove(item);
- },
- /**
- * Registers a new item constructor, keyed by a type key.
- * @param {String} type The mnemonic string by which the class may be looked up.
- * @param {Function} cls The new instance class.
- */
- registerType : function(type, cls) {
- this.types[type] = cls;
- cls[this.typeName] = type;
- },
- /**
- * Checks if an item type is registered.
- * @param {String} type The mnemonic string by which the class may be looked up.
- * @return {Boolean} Whether the type is registered.
- */
- isRegistered : function(type){
- return this.types[type] !== undefined;
- },
- /**
- * Creates and returns an instance of whatever this manager manages, based on the supplied type and
- * config object.
- * @param {Object} config The config object.
- * @param {String} defaultType If no type is discovered in the config object, we fall back to this type.
- * @return {Object} The instance of whatever this manager is managing.
- */
- create: function(config, defaultType) {
- var type = config[this.typeName] || config.type || defaultType,
- Constructor = this.types[type];
- //<debug>
- if (Constructor == undefined) {
- Ext.Error.raise("The '" + type + "' type has not been registered with this manager");
- }
- //</debug>
- return new Constructor(config);
- },
- /**
- * Registers a function that will be called when an item with the specified id is added to the manager.
- * This will happen on instantiation.
- * @param {String} id The item `id`.
- * @param {Function} fn The callback function. Called with a single parameter, the item.
- * @param {Object} scope The scope (`this` reference) in which the callback is executed.
- * Defaults to the item.
- */
- onAvailable : function(id, fn, scope){
- var all = this.all,
- item;
- if (all.containsKey(id)) {
- item = all.get(id);
- fn.call(scope || item, item);
- } else {
- all.on('add', function(map, key, item){
- if (key == id) {
- fn.call(scope || item, item);
- all.un('add', fn, scope);
- }
- });
- }
- },
- /**
- * Executes the specified function once for each item in the collection.
- * @param {Function} fn The function to execute.
- * @param {String} fn.key The key of the item
- * @param {Number} fn.value The value of the item
- * @param {Number} fn.length The total number of items in the collection
- * @param {Boolean} fn.return False to cease iteration.
- * @param {Object} [scope=this] The scope to execute in.
- */
- each: function(fn, scope){
- this.all.each(fn, scope || this);
- },
- /**
- * Gets the number of items in the collection.
- * @return {Number} The number of items in the collection.
- */
- getCount: function(){
- return this.all.getCount();
- }
- });
- /**
- * @private
- *
- * Provides a registry of all Components (instances of {@link Ext.Component} or any subclass
- * thereof) on a page so that they can be easily accessed by {@link Ext.Component component}
- * {@link Ext.Component#getId id} (see {@link #get}, or the convenience method {@link Ext#getCmp Ext.getCmp}).
- *
- * This object also provides a registry of available Component _classes_
- * indexed by a mnemonic code known as the Component's `xtype`.
- * The `xtype` provides a way to avoid instantiating child Components
- * when creating a full, nested config object for a complete Ext page.
- *
- * A child Component may be specified simply as a _config object_
- * as long as the correct `xtype` is specified so that if and when the Component
- * needs rendering, the correct type can be looked up for lazy instantiation.
- *
- * For a list of all available `xtype`, see {@link Ext.Component}.
- */
- Ext.define('Ext.ComponentManager', {
- alternateClassName: 'Ext.ComponentMgr',
- singleton: true,
- constructor: function() {
- var map = {};
- // The sole reason for this is just to support the old code of ComponentQuery
- this.all = {
- map: map,
- getArray: function() {
- var list = [],
- id;
- for (id in map) {
- list.push(map[id]);
- }
- return list;
- }
- };
- this.map = map;
- },
- /**
- * Registers an item to be managed.
- * @param {Object} component The item to register.
- */
- register: function(component) {
- var id = component.getId();
- // <debug>
- if (this.map[id]) {
- 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()`.');
- }
- // </debug>
- this.map[component.getId()] = component;
- },
- /**
- * Unregisters an item by removing it from this manager.
- * @param {Object} component The item to unregister.
- */
- unregister: function(component) {
- delete this.map[component.getId()];
- },
- /**
- * Checks if an item type is registered.
- * @param {String} component The mnemonic string by which the class may be looked up.
- * @return {Boolean} Whether the type is registered.
- */
- isRegistered : function(component){
- return this.map[component] !== undefined;
- },
- /**
- * Returns an item by id.
- * For additional details see {@link Ext.util.HashMap#get}.
- * @param {String} id The `id` of the item.
- * @return {Object} The item, or `undefined` if not found.
- */
- get: function(id) {
- return this.map[id];
- },
- /**
- * Creates a new Component from the specified config object using the
- * config object's `xtype` to determine the class to instantiate.
- * @param {Object} config A configuration object for the Component you wish to create.
- * @param {Function} defaultType (optional) The constructor to provide the default Component type if
- * the config object does not contain a `xtype`. (Optional if the config contains an `xtype`).
- * @return {Ext.Component} The newly instantiated Component.
- */
- create: function(component, defaultType) {
- if (component.isComponent) {
- return component;
- }
- else if (Ext.isString(component)) {
- return Ext.createByAlias('widget.' + component);
- }
- else {
- var type = component.xtype || defaultType;
- return Ext.createByAlias('widget.' + type, component);
- }
- },
- registerType: Ext.emptyFn
- });
- //@define Ext.DateExtras
- /**
- * @class Ext.Date
- * @mixins Ext.DateExtras
- * A set of useful static methods to deal with date.
- *
- * __Note:__ Unless you require `Ext.DateExtras`, only the {@link #now} method will be available. You **MUST**
- * require `Ext.DateExtras` to use the other methods available below.
- *
- * Usage with {@link Ext#setup}:
- *
- * @example
- * Ext.setup({
- * requires: 'Ext.DateExtras',
- * onReady: function() {
- * var date = new Date();
- * alert(Ext.Date.format(date, 'n/j/Y'));
- * }
- * });
- *
- * The date parsing and formatting syntax contains a subset of
- * [PHP's `date()` function](http://www.php.net/date), and the formats that are
- * supported will provide results equivalent to their PHP versions.
- *
- * The following is a list of all currently supported formats:
- * <pre>
- Format Description Example returned values
- ------ ----------------------------------------------------------------------- -----------------------
- d Day of the month, 2 digits with leading zeros 01 to 31
- D A short textual representation of the day of the week Mon to Sun
- j Day of the month without leading zeros 1 to 31
- l A full textual representation of the day of the week Sunday to Saturday
- N ISO-8601 numeric representation of the day of the week 1 (for Monday) through 7 (for Sunday)
- S English ordinal suffix for the day of the month, 2 characters st, nd, rd or th. Works well with j
- w Numeric representation of the day of the week 0 (for Sunday) to 6 (for Saturday)
- z The day of the year (starting from 0) 0 to 364 (365 in leap years)
- W ISO-8601 week number of year, weeks starting on Monday 01 to 53
- F A full textual representation of a month, such as January or March January to December
- m Numeric representation of a month, with leading zeros 01 to 12
- M A short textual representation of a month Jan to Dec
- n Numeric representation of a month, without leading zeros 1 to 12
- t Number of days in the given month 28 to 31
- L Whether it's a leap year 1 if it is a leap year, 0 otherwise.
- o ISO-8601 year number (identical to (Y), but if the ISO week number (W) Examples: 1998 or 2004
- belongs to the previous or next year, that year is used instead)
- Y A full numeric representation of a year, 4 digits Examples: 1999 or 2003
- y A two digit representation of a year Examples: 99 or 03
- a Lowercase Ante meridiem and Post meridiem am or pm
- A Uppercase Ante meridiem and Post meridiem AM or PM
- g 12-hour format of an hour without leading zeros 1 to 12
- G 24-hour format of an hour without leading zeros 0 to 23
- h 12-hour format of an hour with leading zeros 01 to 12
- H 24-hour format of an hour with leading zeros 00 to 23
- i Minutes, with leading zeros 00 to 59
- s Seconds, with leading zeros 00 to 59
- u Decimal fraction of a second Examples:
- (minimum 1 digit, arbitrary number of digits allowed) 001 (i.e. 0.001s) or
- 100 (i.e. 0.100s) or
- 999 (i.e. 0.999s) or
- 999876543210 (i.e. 0.999876543210s)
- O Difference to Greenwich time (GMT) in hours and minutes Example: +1030
- P Difference to Greenwich time (GMT) with colon between hours and minutes Example: -08:00
- T Timezone abbreviation of the machine running the code Examples: EST, MDT, PDT ...
- Z Timezone offset in seconds (negative if west of UTC, positive if east) -43200 to 50400
- c ISO 8601 date
- Notes: Examples:
- 1) If unspecified, the month / day defaults to the current month / day, 1991 or
- the time defaults to midnight, while the timezone defaults to the 1992-10 or
- browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
- and minutes. The "T" delimiter, seconds, milliseconds and timezone 1994-08-19T16:20+01:00 or
- are optional. 1995-07-18T17:21:28-02:00 or
- 2) The decimal fraction of a second, if specified, must contain at 1996-06-17T18:22:29.98765+03:00 or
- least 1 digit (there is no limit to the maximum number 1997-05-16T19:23:30,12345-0400 or
- of digits allowed), and may be delimited by either a '.' or a ',' 1998-04-15T20:24:31.2468Z or
- Refer to the examples on the right for the various levels of 1999-03-14T20:24:32Z or
- date-time granularity which are supported, or see 2000-02-13T21:25:33
- http://www.w3.org/TR/NOTE-datetime for more info. 2001-01-12 22:26:34
- U Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) 1193432466 or -2138434463
- MS Microsoft AJAX serialized dates \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
- \/Date(1238606590509+0800)\/
- </pre>
- *
- * 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).
- *
- * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
- *
- * // Sample date:
- * // 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
- *
- * var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
- * console.log(Ext.Date.format(dt, 'Y-m-d')); // 2007-01-10
- * console.log(Ext.Date.format(dt, 'F j, Y, g:i a')); // January 10, 2007, 3:05 pm
- * 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
- *
- * Here are some standard date/time patterns that you might find helpful. They
- * are not part of the source of Ext.Date, but to use them you can simply copy this
- * block of code into any script that is included after Ext.Date and they will also become
- * globally available on the Date object. Feel free to add or remove patterns as needed in your code.
- *
- * Ext.Date.patterns = {
- * ISO8601Long: "Y-m-d H:i:s",
- * ISO8601Short: "Y-m-d",
- * ShortDate: "n/j/Y",
- * LongDate: "l, F d, Y",
- * FullDateTime: "l, F d, Y g:i:s A",
- * MonthDay: "F d",
- * ShortTime: "g:i A",
- * LongTime: "g:i:s A",
- * SortableDateTime: "Y-m-d\\TH:i:s",
- * UniversalSortableDateTime: "Y-m-d H:i:sO",
- * YearMonth: "F, Y"
- * };
- *
- * Example usage:
- *
- * @example
- * var dt = new Date();
- * Ext.Date.patterns = {
- * ShortDate: "n/j/Y"
- * };
- * alert(Ext.Date.format(dt, Ext.Date.patterns.ShortDate));
- *
- * Developer-written, custom formats may be used by supplying both a formatting and a parsing function
- * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.
- * @singleton
- */
- /*
- * Most of the date-formatting functions below are the excellent work of Baron Schwartz.
- * see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
- * They generate precompiled functions from format patterns instead of parsing and
- * processing each pattern every time a date is formatted. These functions are available
- * on every Date object.
- */
- (function() {
- // create private copy of Ext's Ext.util.Format.format() method
- // - to remove unnecessary dependency
- // - to resolve namespace conflict with MS-Ajax's implementation
- function xf(format) {
- var args = Array.prototype.slice.call(arguments, 1);
- return format.replace(/\{(\d+)\}/g, function(m, i) {
- return args[i];
- });
- }
- /**
- * Extra methods to be mixed into Ext.Date.
- *
- * Require this class to get Ext.Date with all the methods listed below.
- *
- * Using Ext.setup:
- *
- * @example
- * Ext.setup({
- * requires: 'Ext.DateExtras',
- * onReady: function() {
- * var date = new Date();
- * alert(Ext.Date.format(date, 'n/j/Y'));
- * }
- * });
- *
- * Using Ext.application:
- *
- * @example
- * Ext.application({
- * requires: 'Ext.DateExtras',
- * launch: function() {
- * var date = new Date();
- * alert(Ext.Date.format(date, 'n/j/Y'));
- * }
- * });
- *
- * @singleton
- */
- Ext.DateExtras = {
- /**
- * Returns the current timestamp.
- * @return {Number} The current timestamp.
- * @method
- */
- now: Date.now || function() {
- return +new Date();
- },
- /**
- * Returns the number of milliseconds between two dates.
- * @param {Date} dateA The first date.
- * @param {Date} [dateB=new Date()] (optional) The second date, defaults to now.
- * @return {Number} The difference in milliseconds.
- */
- getElapsed: function(dateA, dateB) {
- return Math.abs(dateA - (dateB || new Date()));
- },
- /**
- * Global flag which determines if strict date parsing should be used.
- * Strict date parsing will not roll-over invalid dates, which is the
- * default behavior of JavaScript Date objects.
- * (see {@link #parse} for more information)
- * @type Boolean
- */
- useStrict: false,
- // @private
- formatCodeToRegex: function(character, currentGroup) {
- // Note: currentGroup - position in regex result array (see notes for Ext.Date.parseCodes below)
- var p = utilDate.parseCodes[character];
- if (p) {
- p = typeof p == 'function'? p() : p;
- utilDate.parseCodes[character] = p; // reassign function result to prevent repeated execution
- }
- return p ? Ext.applyIf({
- c: p.c ? xf(p.c, currentGroup || "{0}") : p.c
- }, p) : {
- g: 0,
- c: null,
- s: Ext.String.escapeRegex(character) // treat unrecognized characters as literals
- };
- },
- /**
- * An object hash in which each property is a date parsing function. The property name is the
- * format string which that function parses.
- *
- * This object is automatically populated with date parsing functions as
- * date formats are requested for Ext standard formatting strings.
- *
- * Custom parsing functions may be inserted into this object, keyed by a name which from then on
- * may be used as a format string to {@link #parse}.
- *
- * Example:
- *
- * Ext.Date.parseFunctions['x-date-format'] = myDateParser;
- *
- * A parsing function should return a Date object, and is passed the following parameters:
- *
- * - `date`: {@link String} - The date string to parse.
- * - `strict`: {@link Boolean} - `true` to validate date strings while parsing
- * (i.e. prevent JavaScript Date "rollover"). __The default must be `false`.__
- * Invalid date strings should return `null` when parsed.
- *
- * To enable Dates to also be _formatted_ according to that format, a corresponding
- * formatting function must be placed into the {@link #formatFunctions} property.
- * @property parseFunctions
- * @type Object
- */
- parseFunctions: {
- "MS": function(input, strict) {
- // note: the timezone offset is ignored since the MS Ajax server sends
- // a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
- var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');
- var r = (input || '').match(re);
- return r? new Date(((r[1] || '') + r[2]) * 1) : null;
- }
- },
- parseRegexes: [],
- /**
- * An object hash in which each property is a date formatting function. The property name is the
- * format string which corresponds to the produced formatted date string.
- *
- * This object is automatically populated with date formatting functions as
- * date formats are requested for Ext standard formatting strings.
- *
- * Custom formatting functions may be inserted into this object, keyed by a name which from then on
- * may be used as a format string to {@link #format}.
- *
- * Example:
- *
- * Ext.Date.formatFunctions['x-date-format'] = myDateFormatter;
- *
- * A formatting function should return a string representation of the Date object which is the scope (this) of the function.
- *
- * To enable date strings to also be _parsed_ according to that format, a corresponding
- * parsing function must be placed into the {@link #parseFunctions} property.
- * @property formatFunctions
- * @type Object
- */
- formatFunctions: {
- "MS": function() {
- // UTC milliseconds since Unix epoch (MS-AJAX serialized date format (MRSF))
- return '\\/Date(' + this.getTime() + ')\\/';
- }
- },
- y2kYear : 50,
- /**
- * Date interval constant.
- * @type String
- * @readonly
- */
- MILLI : "ms",
- /**
- * Date interval constant.
- * @type String
- * @readonly
- */
- SECOND : "s",
- /**
- * Date interval constant.
- * @type String
- * @readonly
- */
- MINUTE : "mi",
- /**
- * Date interval constant.
- * @type String
- * @readonly
- */
- HOUR : "h",
- /**
- * Date interval constant.
- * @type String
- * @readonly
- */
- DAY : "d",
- /**
- * Date interval constant.
- * @type String
- * @readonly
- */
- MONTH : "mo",
- /**
- * Date interval constant.
- * @type String
- * @readonly
- */
- YEAR : "y",
- /**
- * An object hash containing default date values used during date parsing.
- *
- * The following properties are available:
- *
- * - `y`: {@link Number} - The default year value. Defaults to `undefined`.
- * - `m`: {@link Number} - The default 1-based month value. Defaults to `undefined`.
- * - `d`: {@link Number} - The default day value. Defaults to `undefined`.
- * - `h`: {@link Number} - The default hour value. Defaults to `undefined`.
- * - `i`: {@link Number} - The default minute value. Defaults to `undefined`.
- * - `s`: {@link Number} - The default second value. Defaults to `undefined`.
- * - `ms`: {@link Number} - The default millisecond value. Defaults to `undefined`.
- *
- * Override these properties to customize the default date values used by the {@link #parse} method.
- *
- * __Note:__ In countries which experience Daylight Saving Time (i.e. DST), the `h`, `i`, `s`
- * and `ms` properties may coincide with the exact time in which DST takes effect.
- * It is the responsibility of the developer to account for this.
- *
- * Example Usage:
- *
- * @example
- * // set default day value to the first day of the month
- * Ext.Date.defaults.d = 1;
- *
- * // parse a February date string containing only year and month values.
- * // setting the default day value to 1 prevents weird date rollover issues.
- * // when attempting to parse the following date string on, for example, March 31st 2009.
- * alert(Ext.Date.parse('2009-02', 'Y-m')); // returns a Date object representing February 1st 2009.
- *
- * @property defaults
- * @type Object
- */
- defaults: {},
- /**
- * An array of textual day names.
- * Override these values for international dates.
- * Example:
- *
- * Ext.Date.dayNames = [
- * 'SundayInYourLang',
- * 'MondayInYourLang'
- * // ...
- * ];
- *
- * @type Array
- */
- dayNames : [
- "Sunday",
- "Monday",
- "Tuesday",
- "Wednesday",
- "Thursday",
- "Friday",
- "Saturday"
- ],
- /**
- * An array of textual month names.
- * Override these values for international dates.
- * Example:
- *
- * Ext.Date.monthNames = [
- * 'JanInYourLang',
- * 'FebInYourLang'
- * // ...
- * ];
- *
- * @type Array
- */
- monthNames : [
- "January",
- "February",
- "March",
- "April",
- "May",
- "June",
- "July",
- "August",
- "September",
- "October",
- "November",
- "December"
- ],
- /**
- * An object hash of zero-based JavaScript month numbers (with short month names as keys).
- *
- * __Note:__ keys are case-sensitive.
- *
- * Override these values for international dates.
- * Example:
- *
- * Ext.Date.monthNumbers = {
- * 'ShortJanNameInYourLang': 0,
- * 'ShortFebNameInYourLang': 1
- * // ...
- * };
- *
- * @type Object
- */
- monthNumbers : {
- Jan:0,
- Feb:1,
- Mar:2,
- Apr:3,
- May:4,
- Jun:5,
- Jul:6,
- Aug:7,
- Sep:8,
- Oct:9,
- Nov:10,
- Dec:11
- },
- /**
- * The date format string that the {@link Ext.util.Format#date} function uses.
- * See {@link Ext.Date} for details.
- *
- * This defaults to `m/d/Y`, but may be overridden in a locale file.
- * @property defaultFormat
- * @type String
- */
- defaultFormat : "m/d/Y",
- /**
- * Get the short month name for the given month number.
- * Override this function for international dates.
- * @param {Number} month A zero-based JavaScript month number.
- * @return {String} The short month name.
- */
- getShortMonthName : function(month) {
- return utilDate.monthNames[month].substring(0, 3);
- },
- /**
- * Get the short day name for the given day number.
- * Override this function for international dates.
- * @param {Number} day A zero-based JavaScript day number.
- * @return {String} The short day name.
- */
- getShortDayName : function(day) {
- return utilDate.dayNames[day].substring(0, 3);
- },
- /**
- * Get the zero-based JavaScript month number for the given short/full month name.
- * Override this function for international dates.
- * @param {String} name The short/full month name.
- * @return {Number} The zero-based JavaScript month number.
- */
- getMonthNumber : function(name) {
- // handle camel casing for English month names (since the keys for the Ext.Date.monthNumbers hash are case sensitive)
- return utilDate.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
- },
- /**
- * The base format-code to formatting-function hashmap used by the {@link #format} method.
- * Formatting functions are strings (or functions which return strings) which
- * will return the appropriate value when evaluated in the context of the Date object
- * from which the {@link #format} method is called.
- * Add to / override these mappings for custom date formatting.
- *
- * __Note:__ `Ext.Date.format()` treats characters as literals if an appropriate mapping cannot be found.
- *
- * Example:
- *
- * @example
- * Ext.Date.formatCodes.x = "Ext.util.Format.leftPad(this.getDate(), 2, '0')";
- * alert(Ext.Date.format(new Date(), 'x')); // returns the current day of the month
- *
- * @type Object
- */
- formatCodes : {
- d: "Ext.String.leftPad(this.getDate(), 2, '0')",
- D: "Ext.Date.getShortDayName(this.getDay())", // get localized short day name
- j: "this.getDate()",
- l: "Ext.Date.dayNames[this.getDay()]",
- N: "(this.getDay() ? this.getDay() : 7)",
- S: "Ext.Date.getSuffix(this)",
- w: "this.getDay()",
- z: "Ext.Date.getDayOfYear(this)",
- W: "Ext.String.leftPad(Ext.Date.getWeekOfYear(this), 2, '0')",
- F: "Ext.Date.monthNames[this.getMonth()]",
- m: "Ext.String.leftPad(this.getMonth() + 1, 2, '0')",
- M: "Ext.Date.getShortMonthName(this.getMonth())", // get localized short month name
- n: "(this.getMonth() + 1)",
- t: "Ext.Date.getDaysInMonth(this)",
- L: "(Ext.Date.isLeapYear(this) ? 1 : 0)",
- o: "(this.getFullYear() + (Ext.Date.getWeekOfYear(this) == 1 && this.getMonth() > 0 ? +1 : (Ext.Date.getWeekOfYear(this) >= 52 && this.getMonth() < 11 ? -1 : 0)))",
- Y: "Ext.String.leftPad(this.getFullYear(), 4, '0')",
- y: "('' + this.getFullYear()).substring(2, 4)",
- a: "(this.getHours() < 12 ? 'am' : 'pm')",
- A: "(this.getHours() < 12 ? 'AM' : 'PM')",
- g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
- G: "this.getHours()",
- h: "Ext.String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
- H: "Ext.String.leftPad(this.getHours(), 2, '0')",
- i: "Ext.String.leftPad(this.getMinutes(), 2, '0')",
- s: "Ext.String.leftPad(this.getSeconds(), 2, '0')",
- u: "Ext.String.leftPad(this.getMilliseconds(), 3, '0')",
- O: "Ext.Date.getGMTOffset(this)",
- P: "Ext.Date.getGMTOffset(this, true)",
- T: "Ext.Date.getTimezone(this)",
- Z: "(this.getTimezoneOffset() * -60)",
- c: function() { // ISO-8601 -- GMT format
- for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
- var e = c.charAt(i);
- code.push(e == "T" ? "'T'" : utilDate.getFormatCode(e)); // treat T as a character literal
- }
- return code.join(" + ");
- },
- /*
- c: function() { // ISO-8601 -- UTC format
- return [
- "this.getUTCFullYear()", "'-'",
- "Ext.util.Format.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
- "Ext.util.Format.leftPad(this.getUTCDate(), 2, '0')",
- "'T'",
- "Ext.util.Format.leftPad(this.getUTCHours(), 2, '0')", "':'",
- "Ext.util.Format.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
- "Ext.util.Format.leftPad(this.getUTCSeconds(), 2, '0')",
- "'Z'"
- ].join(" + ");
- },
- */
- U: "Math.round(this.getTime() / 1000)"
- },
- /**
- * Checks if the passed Date parameters will cause a JavaScript Date "rollover".
- * @param {Number} year 4-digit year.
- * @param {Number} month 1-based month-of-year.
- * @param {Number} day Day of month.
- * @param {Number} hour (optional) Hour.
- * @param {Number} minute (optional) Minute.
- * @param {Number} second (optional) Second.
- * @param {Number} millisecond (optional) Millisecond.
- * @return {Boolean} `true` if the passed parameters do not cause a Date "rollover", `false` otherwise.
- */
- isValid : function(y, m, d, h, i, s, ms) {
- // setup defaults
- h = h || 0;
- i = i || 0;
- s = s || 0;
- ms = ms || 0;
- // Special handling for year < 100
- var dt = utilDate.add(new Date(y < 100 ? 100 : y, m - 1, d, h, i, s, ms), utilDate.YEAR, y < 100 ? y - 100 : 0);
- return y == dt.getFullYear() &&
- m == dt.getMonth() + 1 &&
- d == dt.getDate() &&
- h == dt.getHours() &&
- i == dt.getMinutes() &&
- s == dt.getSeconds() &&
- ms == dt.getMilliseconds();
- },
- /**
- * Parses the passed string using the specified date format.
- * Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
- * The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
- * which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
- * the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
- * Keep in mind that the input date string must precisely match the specified format string
- * in order for the parse operation to be successful (failed parse operations return a `null` value).
- *
- * Example:
- *
- * // dt = Fri May 25 2007 (current date)
- * var dt = new Date();
- *
- * // dt = Thu May 25 2006 (today's month/day in 2006)
- * dt = Ext.Date.parse("2006", "Y");
- *
- * // dt = Sun Jan 15 2006 (all date parts specified)
- * dt = Ext.Date.parse("2006-01-15", "Y-m-d");
- *
- * // dt = Sun Jan 15 2006 15:20:01
- * dt = Ext.Date.parse("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
- *
- * // attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
- * dt = Ext.Date.parse("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // null
- *
- * @param {String} input The raw date string.
- * @param {String} format The expected date string format.
- * @param {Boolean} [strict=false] (optional) `true` to validate date strings while parsing (i.e. prevents JavaScript Date "rollover").
- * Invalid date strings will return `null` when parsed.
- * @return {Date/null} The parsed Date, or `null` if an invalid date string.
- */
- parse : function(input, format, strict) {
- var p = utilDate.parseFunctions;
- if (p[format] == null) {
- utilDate.createParser(format);
- }
- return p[format](input, Ext.isDefined(strict) ? strict : utilDate.useStrict);
- },
- // Backwards compat
- parseDate: function(input, format, strict){
- return utilDate.parse(input, format, strict);
- },
- // @private
- getFormatCode : function(character) {
- var f = utilDate.formatCodes[character];
- if (f) {
- f = typeof f == 'function'? f() : f;
- utilDate.formatCodes[character] = f; // reassign function result to prevent repeated execution
- }
- // note: unknown characters are treated as literals
- return f || ("'" + Ext.String.escape(character) + "'");
- },
- // @private
- createFormat : function(format) {
- var code = [],
- special = false,
- ch = '';
- for (var i = 0; i < format.length; ++i) {
- ch = format.charAt(i);
- if (!special && ch == "\\") {
- special = true;
- } else if (special) {
- special = false;
- code.push("'" + Ext.String.escape(ch) + "'");
- } else if (ch == '\n') {
- code.push(Ext.JSON.encode(ch));
- } else {
- code.push(utilDate.getFormatCode(ch));
- }
- }
- utilDate.formatFunctions[format] = Ext.functionFactory("return " + code.join('+'));
- },
- // @private
- createParser : (function() {
- var code = [
- "var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
- "def = Ext.Date.defaults,",
- "results = String(input).match(Ext.Date.parseRegexes[{0}]);", // either null, or an array of matched strings
- "if(results){",
- "{1}",
- "if(u != null){", // i.e. unix time is defined
- "v = new Date(u * 1000);", // give top priority to UNIX time
- "}else{",
- // create Date object representing midnight of the current day;
- // this will provide us with our date defaults
- // (note: clearTime() handles Daylight Saving Time automatically)
- "dt = Ext.Date.clearTime(new Date);",
- // date calculations (note: these calculations create a dependency on Ext.Number.from())
- "y = Ext.Number.from(y, Ext.Number.from(def.y, dt.getFullYear()));",
- "m = Ext.Number.from(m, Ext.Number.from(def.m - 1, dt.getMonth()));",
- "d = Ext.Number.from(d, Ext.Number.from(def.d, dt.getDate()));",
- // time calculations (note: these calculations create a dependency on Ext.Number.from())
- "h = Ext.Number.from(h, Ext.Number.from(def.h, dt.getHours()));",
- "i = Ext.Number.from(i, Ext.Number.from(def.i, dt.getMinutes()));",
- "s = Ext.Number.from(s, Ext.Number.from(def.s, dt.getSeconds()));",
- "ms = Ext.Number.from(ms, Ext.Number.from(def.ms, dt.getMilliseconds()));",
- "if(z >= 0 && y >= 0){",
- // both the year and zero-based day of year are defined and >= 0.
- // these 2 values alone provide sufficient info to create a full date object
- // create Date object representing January 1st for the given year
- // handle years < 100 appropriately
- "v = Ext.Date.add(new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
- // then add day of year, checking for Date "rollover" if necessary
- "v = !strict? v : (strict === true && (z <= 364 || (Ext.Date.isLeapYear(v) && z <= 365))? Ext.Date.add(v, Ext.Date.DAY, z) : null);",
- "}else if(strict === true && !Ext.Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
- "v = null;", // invalid date, so return null
- "}else{",
- // plain old Date object
- // handle years < 100 properly
- "v = Ext.Date.add(new Date(y < 100 ? 100 : y, m, d, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
- "}",
- "}",
- "}",
- "if(v){",
- // favor UTC offset over GMT offset
- "if(zz != null){",
- // reset to UTC, then add offset
- "v = Ext.Date.add(v, Ext.Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
- "}else if(o){",
- // reset to GMT, then add offset
- "v = Ext.Date.add(v, Ext.Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
- "}",
- "}",
- "return v;"
- ].join('\n');
- return function(format) {
- var regexNum = utilDate.parseRegexes.length,
- currentGroup = 1,
- calc = [],
- regex = [],
- special = false,
- ch = "";
- for (var i = 0; i < format.length; ++i) {
- ch = format.charAt(i);
- if (!special && ch == "\\") {
- special = true;
- } else if (special) {
- special = false;
- regex.push(Ext.String.escape(ch));
- } else {
- var obj = utilDate.formatCodeToRegex(ch, currentGroup);
- currentGroup += obj.g;
- regex.push(obj.s);
- if (obj.g && obj.c) {
- calc.push(obj.c);
- }
- }
- }
- utilDate.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
- utilDate.parseFunctions[format] = Ext.functionFactory("input", "strict", xf(code, regexNum, calc.join('')));
- };
- })(),
- // @private
- parseCodes : {
- /*
- * Notes:
- * g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
- * c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
- * s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
- */
- d: {
- g:1,
- c:"d = parseInt(results[{0}], 10);\n",
- s:"(\\d{2})" // day of month with leading zeros (01 - 31)
- },
- j: {
- g:1,
- c:"d = parseInt(results[{0}], 10);\n",
- s:"(\\d{1,2})" // day of month without leading zeros (1 - 31)
- },
- D: function() {
- for (var a = [], i = 0; i < 7; a.push(utilDate.getShortDayName(i)), ++i); // get localized short day names
- return {
- g:0,
- c:null,
- s:"(?:" + a.join("|") +")"
- };
- },
- l: function() {
- return {
- g:0,
- c:null,
- s:"(?:" + utilDate.dayNames.join("|") + ")"
- };
- },
- N: {
- g:0,
- c:null,
- s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
- },
- S: {
- g:0,
- c:null,
- s:"(?:st|nd|rd|th)"
- },
- w: {
- g:0,
- c:null,
- s:"[0-6]" // JavaScript day number (0 (sunday) - 6 (saturday))
- },
- z: {
- g:1,
- c:"z = parseInt(results[{0}], 10);\n",
- s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
- },
- W: {
- g:0,
- c:null,
- s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
- },
- F: function() {
- return {
- g:1,
- c:"m = parseInt(Ext.Date.getMonthNumber(results[{0}]), 10);\n", // get localized month number
- s:"(" + utilDate.monthNames.join("|") + ")"
- };
- },
- M: function() {
- for (var a = [], i = 0; i < 12; a.push(utilDate.getShortMonthName(i)), ++i); // get localized short month names
- return Ext.applyIf({
- s:"(" + a.join("|") + ")"
- }, utilDate.formatCodeToRegex("F"));
- },
- m: {
- g:1,
- c:"m = parseInt(results[{0}], 10) - 1;\n",
- s:"(\\d{2})" // month number with leading zeros (01 - 12)
- },
- n: {
- g:1,
- c:"m = parseInt(results[{0}], 10) - 1;\n",
- s:"(\\d{1,2})" // month number without leading zeros (1 - 12)
- },
- t: {
- g:0,
- c:null,
- s:"(?:\\d{2})" // no. of days in the month (28 - 31)
- },
- L: {
- g:0,
- c:null,
- s:"(?:1|0)"
- },
- o: function() {
- return utilDate.formatCodeToRegex("Y");
- },
- Y: {
- g:1,
- c:"y = parseInt(results[{0}], 10);\n",
- s:"(\\d{4})" // 4-digit year
- },
- y: {
- g:1,
- c:"var ty = parseInt(results[{0}], 10);\n"
- + "y = ty > Ext.Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
- s:"(\\d{1,2})"
- },
- /*
- * In the am/pm parsing routines, we allow both upper and lower case
- * even though it doesn't exactly match the spec. It gives much more flexibility
- * in being able to specify case insensitive regexes.
- */
- a: {
- g:1,
- c:"if (/(am)/i.test(results[{0}])) {\n"
- + "if (!h || h == 12) { h = 0; }\n"
- + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
- s:"(am|pm|AM|PM)"
- },
- A: {
- g:1,
- c:"if (/(am)/i.test(results[{0}])) {\n"
- + "if (!h || h == 12) { h = 0; }\n"
- + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
- s:"(AM|PM|am|pm)"
- },
- g: function() {
- return utilDate.formatCodeToRegex("G");
- },
- G: {
- g:1,
- c:"h = parseInt(results[{0}], 10);\n",
- s:"(\\d{1,2})" // 24-hr format of an hour without leading zeros (0 - 23)
- },
- h: function() {
- return utilDate.formatCodeToRegex("H");
- },
- H: {
- g:1,
- c:"h = parseInt(results[{0}], 10);\n",
- s:"(\\d{2})" // 24-hr format of an hour with leading zeros (00 - 23)
- },
- i: {
- g:1,
- c:"i = parseInt(results[{0}], 10);\n",
- s:"(\\d{2})" // minutes with leading zeros (00 - 59)
- },
- s: {
- g:1,
- c:"s = parseInt(results[{0}], 10);\n",
- s:"(\\d{2})" // seconds with leading zeros (00 - 59)
- },
- u: {
- g:1,
- c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
- s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
- },
- O: {
- g:1,
- c:[
- "o = results[{0}];",
- "var sn = o.substring(0,1),", // get + / - sign
- "hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
- "mn = o.substring(3,5) % 60;", // get minutes
- "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
- ].join("\n"),
- s: "([+\-]\\d{4})" // GMT offset in hrs and mins
- },
- P: {
- g:1,
- c:[
- "o = results[{0}];",
- "var sn = o.substring(0,1),", // get + / - sign
- "hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
- "mn = o.substring(4,6) % 60;", // get minutes
- "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
- ].join("\n"),
- s: "([+\-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
- },
- T: {
- g:0,
- c:null,
- s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
- },
- Z: {
- g:1,
- c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
- + "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
- s:"([+\-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
- },
- c: function() {
- var calc = [],
- arr = [
- utilDate.formatCodeToRegex("Y", 1), // year
- utilDate.formatCodeToRegex("m", 2), // month
- utilDate.formatCodeToRegex("d", 3), // day
- utilDate.formatCodeToRegex("h", 4), // hour
- utilDate.formatCodeToRegex("i", 5), // minute
- utilDate.formatCodeToRegex("s", 6), // second
- {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)
- {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
- "if(results[8]) {", // timezone specified
- "if(results[8] == 'Z'){",
- "zz = 0;", // UTC
- "}else if (results[8].indexOf(':') > -1){",
- utilDate.formatCodeToRegex("P", 8).c, // timezone offset with colon separator
- "}else{",
- utilDate.formatCodeToRegex("O", 8).c, // timezone offset without colon separator
- "}",
- "}"
- ].join('\n')}
- ];
- for (var i = 0, l = arr.length; i < l; ++i) {
- calc.push(arr[i].c);
- }
- return {
- g:1,
- c:calc.join(""),
- s:[
- arr[0].s, // year (required)
- "(?:", "-", arr[1].s, // month (optional)
- "(?:", "-", arr[2].s, // day (optional)
- "(?:",
- "(?:T| )?", // time delimiter -- either a "T" or a single blank space
- 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
- "(?::", arr[5].s, ")?", // seconds (optional)
- "(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
- "(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
- ")?",
- ")?",
- ")?"
- ].join("")
- };
- },
- U: {
- g:1,
- c:"u = parseInt(results[{0}], 10);\n",
- s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
- }
- },
- // Old Ext.Date prototype methods.
- // @private
- dateFormat: function(date, format) {
- return utilDate.format(date, format);
- },
- /**
- * Formats a date given the supplied format string.
- * @param {Date} date The date to format.
- * @param {String} format The format string.
- * @return {String} The formatted date.
- */
- format: function(date, format) {
- if (utilDate.formatFunctions[format] == null) {
- utilDate.createFormat(format);
- }
- var result = utilDate.formatFunctions[format].call(date);
- return result + '';
- },
- /**
- * Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
- *
- * __Note:__ The date string returned by the JavaScript Date object's `toString()` method varies
- * between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
- * For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
- * `getTimezone()` first tries to get the timezone abbreviation from between a pair of parentheses
- * (which may or may not be present), failing which it proceeds to get the timezone abbreviation
- * from the GMT offset portion of the date string.
- *
- * @example
- * var dt = new Date('9/17/2011');
- * alert(Ext.Date.getTimezone(dt));
- *
- * @param {Date} date The date.
- * @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
- */
- getTimezone : function(date) {
- // the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
- //
- // Opera : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
- // 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)
- // FF : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
- // IE : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
- // IE : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
- //
- // this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
- // step 1: (?:\((.*)\) -- find timezone in parentheses
- // 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
- // step 3: remove all non uppercase characters found in step 1 and 2
- return date.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
- },
- /**
- * Get the offset from GMT of the current date (equivalent to the format specifier 'O').
- *
- * @example
- * var dt = new Date('9/17/2011');
- * alert(Ext.Date.getGMTOffset(dt));
- *
- * @param {Date} date The date.
- * @param {Boolean} [colon=false] (optional) `true` to separate the hours and minutes with a colon.
- * @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
- */
- getGMTOffset : function(date, colon) {
- var offset = date.getTimezoneOffset();
- return (offset > 0 ? "-" : "+")
- + Ext.String.leftPad(Math.floor(Math.abs(offset) / 60), 2, "0")
- + (colon ? ":" : "")
- + Ext.String.leftPad(Math.abs(offset % 60), 2, "0");
- },
- /**
- * Get the numeric day number of the year, adjusted for leap year.
- *
- * @example
- * var dt = new Date('9/17/2011');
- * alert(Ext.Date.getDayOfYear(dt)); // 259
- *
- * @param {Date} date The date.
- * @return {Number} 0 to 364 (365 in leap years).
- */
- getDayOfYear: function(date) {
- var num = 0,
- d = Ext.Date.clone(date),
- m = date.getMonth(),
- i;
- for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
- num += utilDate.getDaysInMonth(d);
- }
- return num + date.getDate() - 1;
- },
- /**
- * Get the numeric ISO-8601 week number of the year
- * (equivalent to the format specifier 'W', but without a leading zero).
- *
- * @example
- * var dt = new Date('9/17/2011');
- * alert(Ext.Date.getWeekOfYear(dt)); // 37
- *
- * @param {Date} date The date.
- * @return {Number} 1 to 53.
- * @method
- */
- getWeekOfYear : (function() {
- // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
- var ms1d = 864e5, // milliseconds in a day
- ms7d = 7 * ms1d; // milliseconds in a week
- return function(date) { // return a closure so constants get calculated only once
- var DC3 = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate() + 3) / ms1d, // an Absolute Day Number
- AWN = Math.floor(DC3 / 7), // an Absolute Week Number
- Wyr = new Date(AWN * ms7d).getUTCFullYear();
- return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
- };
- })(),
- /**
- * Checks if the current date falls within a leap year.
- *
- * @example
- * var dt = new Date('1/10/2011');
- * alert(Ext.Date.isLeapYear(dt)); // false
- *
- * @param {Date} date The date.
- * @return {Boolean} `true` if the current date falls within a leap year, `false` otherwise.
- */
- isLeapYear : function(date) {
- var year = date.getFullYear();
- return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
- },
- /**
- * Get the first day of the current month, adjusted for leap year. The returned value
- * is the numeric day index within the week (0-6) which can be used in conjunction with
- * the {@link #monthNames} array to retrieve the textual day name.
- *
- * @example
- * var dt = new Date('1/10/2007'),
- * firstDay = Ext.Date.getFirstDayOfMonth(dt);
- * alert(Ext.Date.dayNames[firstDay]); // 'Monday'
- *
- * @param {Date} date The date
- * @return {Number} The day number (0-6).
- */
- getFirstDayOfMonth : function(date) {
- var day = (date.getDay() - (date.getDate() - 1)) % 7;
- return (day < 0) ? (day + 7) : day;
- },
- /**
- * Get the last day of the current month, adjusted for leap year. The returned value
- * is the numeric day index within the week (0-6) which can be used in conjunction with
- * the {@link #monthNames} array to retrieve the textual day name.
- *
- * @example
- * var dt = new Date('1/10/2007'),
- * lastDay = Ext.Date.getLastDayOfMonth(dt);
- * alert(Ext.Date.dayNames[lastDay]); // 'Wednesday'
- *
- * @param {Date} date The date.
- * @return {Number} The day number (0-6).
- */
- getLastDayOfMonth : function(date) {
- return utilDate.getLastDateOfMonth(date).getDay();
- },
- /**
- * Get the date of the first day of the month in which this date resides.
- *
- * @example
- * var dt = new Date('1/10/2007'),
- * lastDate = Ext.Date.getFirstDateOfMonth(dt);
- * alert(lastDate); // Mon Jan 01 2007 00:00:00 GMT-0800 (PST)
- *
- * @param {Date} date The date.
- * @return {Date}
- */
- getFirstDateOfMonth : function(date) {
- return new Date(date.getFullYear(), date.getMonth(), 1);
- },
- /**
- * Get the date of the last day of the month in which this date resides.
- *
- * @example
- * var dt = new Date('1/10/2007'),
- * lastDate = Ext.Date.getLastDateOfMonth(dt);
- * alert(lastDate); // Wed Jan 31 2007 00:00:00 GMT-0800 (PST)
- *
- * @param {Date} date The date.
- * @return {Date}
- */
- getLastDateOfMonth : function(date) {
- return new Date(date.getFullYear(), date.getMonth(), utilDate.getDaysInMonth(date));
- },
- /**
- * Get the number of days in the current month, adjusted for leap year.
- *
- * @example
- * var dt = new Date('1/10/2007');
- * alert(Ext.Date.getDaysInMonth(dt)); // 31
- *
- * @param {Date} date The date.
- * @return {Number} The number of days in the month.
- * @method
- */
- getDaysInMonth: (function() {
- var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
- return function(date) { // return a closure for efficiency
- var m = date.getMonth();
- return m == 1 && utilDate.isLeapYear(date) ? 29 : daysInMonth[m];
- };
- })(),
- /**
- * Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
- *
- * @example
- * var dt = new Date('9/17/2011');
- * alert(Ext.Date.getSuffix(dt)); // 'th'
- *
- * @param {Date} date The date.
- * @return {String} 'st', 'nd', 'rd' or 'th'.
- */
- getSuffix : function(date) {
- switch (date.getDate()) {
- case 1:
- case 21:
- case 31:
- return "st";
- case 2:
- case 22:
- return "nd";
- case 3:
- case 23:
- return "rd";
- default:
- return "th";
- }
- },
- /**
- * Creates and returns a new Date instance with the exact same date value as the called instance.
- * Dates are copied and passed by reference, so if a copied date variable is modified later, the original
- * variable will also be changed. When the intention is to create a new variable that will not
- * modify the original instance, you should create a clone.
- *
- * Example of correctly cloning a date:
- *
- * // wrong way:
- * var orig = new Date('10/1/2006');
- * var copy = orig;
- * copy.setDate(5);
- * console.log(orig); // returns 'Thu Oct 05 2006'!
- *
- * // correct way:
- * var orig = new Date('10/1/2006'),
- * copy = Ext.Date.clone(orig);
- * copy.setDate(5);
- * console.log(orig); // returns 'Thu Oct 01 2006'
- *
- * @param {Date} date The date.
- * @return {Date} The new Date instance.
- */
- clone : function(date) {
- return new Date(date.getTime());
- },
- /**
- * Checks if the current date is affected by Daylight Saving Time (DST).
- *
- * @example
- * var dt = new Date('9/17/2011');
- * alert(Ext.Date.isDST(dt));
- *
- * @param {Date} date The date.
- * @return {Boolean} `true` if the current date is affected by DST.
- */
- isDST : function(date) {
- // adapted from http://sencha.com/forum/showthread.php?p=247172#post247172
- // courtesy of @geoffrey.mcgill
- return new Date(date.getFullYear(), 0, 1).getTimezoneOffset() != date.getTimezoneOffset();
- },
- /**
- * Attempts to clear all time information from this Date by setting the time to midnight of the same day,
- * automatically adjusting for Daylight Saving Time (DST) where applicable.
- *
- * __Note:__ DST timezone information for the browser's host operating system is assumed to be up-to-date.
- *
- * @param {Date} date The date.
- * @param {Boolean} [clone=false] `true` to create a clone of this date, clear the time and return it.
- * @return {Date} this or the clone.
- */
- clearTime : function(date, clone) {
- if (clone) {
- return Ext.Date.clearTime(Ext.Date.clone(date));
- }
- // get current date before clearing time
- var d = date.getDate();
- // clear time
- date.setHours(0);
- date.setMinutes(0);
- date.setSeconds(0);
- date.setMilliseconds(0);
- if (date.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
- // note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
- // refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
- // increment hour until cloned date == current date
- for (var hr = 1, c = utilDate.add(date, Ext.Date.HOUR, hr); c.getDate() != d; hr++, c = utilDate.add(date, Ext.Date.HOUR, hr));
- date.setDate(d);
- date.setHours(c.getHours());
- }
- return date;
- },
- /**
- * Provides a convenient method for performing basic date arithmetic. This method
- * does not modify the Date instance being called - it creates and returns
- * a new Date instance containing the resulting date value.
- *
- * @example
- * // Basic usage:
- * var dt = Ext.Date.add(new Date('10/29/2006'), Ext.Date.DAY, 5);
- * alert(dt); // 'Fri Nov 03 2006 00:00:00'
- *
- * You can also subtract date values by passing a negative value:
- *
- * @example
- * // Negative values will be subtracted:
- * var dt2 = Ext.Date.add(new Date('10/1/2006'), Ext.Date.DAY, -5);
- * alert(dt2); // 'Tue Sep 26 2006 00:00:00'
- *
- * @param {Date} date The date to modify.
- * @param {String} interval A valid date interval enum value.
- * @param {Number} value The amount to add to the current date.
- * @return {Date} The new Date instance.
- */
- add : function(date, interval, value) {
- var d = Ext.Date.clone(date);
- if (!interval || value === 0) return d;
- switch(interval.toLowerCase()) {
- case Ext.Date.MILLI:
- d= new Date(d.valueOf() + value);
- break;
- case Ext.Date.SECOND:
- d= new Date(d.valueOf() + value * 1000);
- break;
- case Ext.Date.MINUTE:
- d= new Date(d.valueOf() + value * 60000);
- break;
- case Ext.Date.HOUR:
- d= new Date(d.valueOf() + value * 3600000);
- break;
- case Ext.Date.DAY:
- d= new Date(d.valueOf() + value * 86400000);
- break;
- case Ext.Date.MONTH:
- var day = date.getDate();
- if (day > 28) {
- day = Math.min(day, Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(date), 'mo', value)).getDate());
- }
- d.setDate(day);
- d.setMonth(date.getMonth() + value);
- break;
- case Ext.Date.YEAR:
- d.setFullYear(date.getFullYear() + value);
- break;
- }
- return d;
- },
- /**
- * Checks if a date falls on or between the given start and end dates.
- * @param {Date} date The date to check.
- * @param {Date} start Start date.
- * @param {Date} end End date.
- * @return {Boolean} `true` if this date falls on or between the given start and end dates.
- */
- between : function(date, start, end) {
- var t = date.getTime();
- return start.getTime() <= t && t <= end.getTime();
- },
- /**
- * Calculate how many units are there between two time.
- * @param {Date} min The first time.
- * @param {Date} max The second time.
- * @param {String} unit The unit. This unit is compatible with the date interval constants.
- * @return {Number} The maximum number n of units that min + n * unit <= max.
- */
- diff: function (min, max, unit) {
- var ExtDate = Ext.Date, est, diff = +max - min;
- switch (unit) {
- case ExtDate.MILLI:
- return diff;
- case ExtDate.SECOND:
- return Math.floor(diff / 1000);
- case ExtDate.MINUTE:
- return Math.floor(diff / 60000);
- case ExtDate.HOUR:
- return Math.floor(diff / 3600000);
- case ExtDate.DAY:
- return Math.floor(diff / 86400000);
- case 'w':
- return Math.floor(diff / 604800000);
- case ExtDate.MONTH:
- est = (max.getFullYear() * 12 + max.getMonth()) - (min.getFullYear() * 12 + min.getMonth());
- if (Ext.Date.add(min, unit, est) > max) {
- return est - 1;
- } else {
- return est;
- }
- case ExtDate.YEAR:
- est = max.getFullYear() - min.getFullYear();
- if (Ext.Date.add(min, unit, est) > max) {
- return est - 1;
- } else {
- return est;
- }
- }
- },
- /**
- * Align the date to `unit`.
- * @param {Date} date The date to be aligned.
- * @param {String} unit The unit. This unit is compatible with the date interval constants.
- * @return {Date} The aligned date.
- */
- align: function (date, unit, step) {
- var num = new Date(+date);
- switch (unit.toLowerCase()) {
- case Ext.Date.MILLI:
- return num;
- break;
- case Ext.Date.SECOND:
- num.setUTCSeconds(num.getUTCSeconds() - num.getUTCSeconds() % step);
- num.setUTCMilliseconds(0);
- return num;
- break;
- case Ext.Date.MINUTE:
- num.setUTCMinutes(num.getUTCMinutes() - num.getUTCMinutes() % step);
- num.setUTCSeconds(0);
- num.setUTCMilliseconds(0);
- return num;
- break;
- case Ext.Date.HOUR:
- num.setUTCHours(num.getUTCHours() - num.getUTCHours() % step);
- num.setUTCMinutes(0);
- num.setUTCSeconds(0);
- num.setUTCMilliseconds(0);
- return num;
- break;
- case Ext.Date.DAY:
- if (step == 7 || step == 14){
- num.setUTCDate(num.getUTCDate() - num.getUTCDay() + 1);
- }
- num.setUTCHours(0);
- num.setUTCMinutes(0);
- num.setUTCSeconds(0);
- num.setUTCMilliseconds(0);
- return num;
- break;
- case Ext.Date.MONTH:
- num.setUTCMonth(num.getUTCMonth() - (num.getUTCMonth() - 1) % step,1);
- num.setUTCHours(0);
- num.setUTCMinutes(0);
- num.setUTCSeconds(0);
- num.setUTCMilliseconds(0);
- return num;
- break;
- case Ext.Date.YEAR:
- num.setUTCFullYear(num.getUTCFullYear() - num.getUTCFullYear() % step, 1, 1);
- num.setUTCHours(0);
- num.setUTCMinutes(0);
- num.setUTCSeconds(0);
- num.setUTCMilliseconds(0);
- return date;
- break;
- }
- }
- };
- var utilDate = Ext.DateExtras;
- Ext.apply(Ext.Date, utilDate);
- })();
- /**
- * Reusable data formatting functions
- */
- Ext.define('Ext.util.Format', {
- requires: [
- 'Ext.DateExtras'
- ],
- singleton: true,
- /**
- * The global default date format.
- */
- defaultDateFormat: 'm/d/Y',
- escapeRe: /('|\\)/g,
- 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,
- formatRe: /\{(\d+)\}/g,
- escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,
- dashesRe: /-/g,
- iso8601TestRe: /\d\dT\d\d/,
- iso8601SplitRe: /[- :T\.Z\+]/,
- /**
- * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length.
- * @param {String} value The string to truncate.
- * @param {Number} length The maximum length to allow before truncating.
- * @param {Boolean} word True to try to find a common word break.
- * @return {String} The converted text.
- */
- ellipsis: function(value, len, word) {
- if (value && value.length > len) {
- if (word) {
- var vs = value.substr(0, len - 2),
- index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
- if (index != -1 && index >= (len - 15)) {
- return vs.substr(0, index) + "...";
- }
- }
- return value.substr(0, len - 3) + "...";
- }
- return value;
- },
- /**
- * Escapes the passed string for use in a regular expression.
- * @param {String} str
- * @return {String}
- */
- escapeRegex: function(s) {
- return s.replace(Ext.util.Format.escapeRegexRe, "\\$1");
- },
- /**
- * Escapes the passed string for ' and \.
- * @param {String} string The string to escape.
- * @return {String} The escaped string.
- */
- escape: function(string) {
- return string.replace(Ext.util.Format.escapeRe, "\\$1");
- },
- /**
- * Utility function that allows you to easily switch a string between two alternating values. The passed value
- * is compared to the current string, and if they are equal, the other value that was passed in is returned. If
- * they are already different, the first value passed in is returned.
- *
- * __Note:__ This method returns the new value but does not change the current string.
- *
- * // alternate sort directions
- * sort = Ext.util.Format.toggle(sort, 'ASC', 'DESC');
- *
- * // instead of conditional logic:
- * sort = (sort === 'ASC' ? 'DESC' : 'ASC');
- *
- * @param {String} string The current string
- * @param {String} value The value to compare to the current string
- * @param {String} other The new value to use if the string already equals the first value passed in
- * @return {String} The new value
- */
- toggle: function(string, value, other) {
- return string == value ? other : value;
- },
- /**
- * Trims whitespace from either end of a string, leaving spaces within the string intact. Example:
- *
- * var s = ' foo bar ';
- * alert('-' + s + '-'); // alerts "- foo bar -"
- * alert('-' + Ext.util.Format.trim(s) + '-'); // alerts "-foo bar-"
- *
- * @param {String} string The string to escape
- * @return {String} The trimmed string
- */
- trim: function(string) {
- return string.replace(Ext.util.Format.trimRe, "");
- },
- /**
- * Pads the left side of a string with a specified character. This is especially useful
- * for normalizing number and date strings. Example usage:
- *
- * var s = Ext.util.Format.leftPad('123', 5, '0');
- * // s now contains the string: '00123'
- *
- * @param {String} string The original string.
- * @param {Number} size The total length of the output string.
- * @param {String} [char=' '] (optional) The character with which to pad the original string.
- * @return {String} The padded string.
- */
- leftPad: function (val, size, ch) {
- var result = String(val);
- ch = ch || " ";
- while (result.length < size) {
- result = ch + result;
- }
- return result;
- },
- /**
- * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens. Each
- * token must be unique, and must increment in the format {0}, {1}, etc. Example usage:
- *
- * var cls = 'my-class', text = 'Some text';
- * var s = Ext.util.Format.format('<div class="{0}">{1}</div>', cls, text);
- * // s now contains the string: '<div class="my-class">Some text</div>'
- *
- * @param {String} string The tokenized string to be formatted.
- * @param {String...} values The values to replace token {0}, {1}, etc.
- * @return {String} The formatted string.
- */
- format: function (format) {
- var args = Ext.toArray(arguments, 1);
- return format.replace(Ext.util.Format.formatRe, function(m, i) {
- return args[i];
- });
- },
- /**
- * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
- * @param {String} value The string to encode.
- * @return {String} The encoded text.
- */
- htmlEncode: function(value) {
- return ! value ? value: String(value).replace(/&/g, "&").replace(/>/g, ">").replace(/</g, "<").replace(/"/g, """);
- },
- /**
- * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
- * @param {String} value The string to decode.
- * @return {String} The decoded text.
- */
- htmlDecode: function(value) {
- return ! value ? value: String(value).replace(/>/g, ">").replace(/</g, "<").replace(/"/g, '"').replace(/&/g, "&");
- },
- /**
- * Parse a value into a formatted date using the specified format pattern.
- * @param {String/Date} value The value to format. Strings must conform to the format expected by the JavaScript
- * Date object's [parse() method](http://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date/parse).
- * @param {String} [format='m/d/Y'] (optional) Any valid date format string.
- * @return {String} The formatted date string.
- */
- date: function(value, format) {
- var date = value;
- if (!value) {
- return "";
- }
- if (!Ext.isDate(value)) {
- date = new Date(Date.parse(value));
- if (isNaN(date)) {
- // Dates with ISO 8601 format are not well supported by mobile devices, this can work around the issue.
- if (this.iso8601TestRe.test(value)) {
- date = value.split(this.iso8601SplitRe);
- date = new Date(date[0], date[1]-1, date[2], date[3], date[4], date[5]);
- }
- if (isNaN(date)) {
- // Dates with the format "2012-01-20" fail, but "2012/01/20" work in some browsers. We'll try and
- // get around that.
- date = new Date(Date.parse(value.replace(this.dashesRe, "/")));
- //<debug>
- if (isNaN(date)) {
- Ext.Logger.error("Cannot parse the passed value " + value + " into a valid date");
- }
- //</debug>
- }
- }
- value = date;
- }
- return Ext.Date.format(value, format || Ext.util.Format.defaultDateFormat);
- }
- });
- /**
- * Represents an HTML fragment template. Templates may be {@link #compile precompiled} for greater performance.
- *
- * An instance of this class may be created by passing to the constructor either a single argument, or multiple
- * arguments:
- *
- * # Single argument: String/Array
- *
- * The single argument may be either a String or an Array:
- *
- * - String:
- *
- * var t = new Ext.Template("<div>Hello {0}.</div>");
- * t.{@link #append}('some-element', ['foo']);
- *
- * - Array: An Array will be combined with `join('')`.
- *
- * var t = new Ext.Template([
- * '<div name="{id}">',
- * '<span class="{cls}">{name:trim} {value:ellipsis(10)}</span>',
- * '</div>'
- * ]);
- * t.{@link #compile}();
- * t.{@link #append}('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
- *
- * # Multiple arguments: String, Object, Array, ...
- *
- * Multiple arguments will be combined with `join('')`.
- *
- * var t = new Ext.Template(
- * '<div name="{id}">',
- * '<span class="{cls}">{name} {value}</span>',
- * '</div>',
- * // a configuration object:
- * {
- * compiled: true // {@link #compile} immediately
- * }
- * );
- *
- * # Notes
- *
- * - For a list of available format functions, see {@link Ext.util.Format}.
- * - `disableFormats` reduces `{@link #apply}` time when no formatting is required.
- */
- Ext.define('Ext.Template', {
- /* Begin Definitions */
- requires: ['Ext.dom.Helper', 'Ext.util.Format'],
- inheritableStatics: {
- /**
- * Creates a template from the passed element's value (_display:none_ textarea, preferred) or `innerHTML`.
- * @param {String/HTMLElement} el A DOM element or its `id`.
- * @param {Object} config (optional) Config object.
- * @return {Ext.Template} The created template.
- * @static
- * @inheritable
- */
- from: function(el, config) {
- el = Ext.getDom(el);
- return new this(el.value || el.innerHTML, config || '');
- }
- },
- /* End Definitions */
- /**
- * Creates new template.
- *
- * @param {String...} html List of strings to be concatenated into template.
- * Alternatively an array of strings can be given, but then no config object may be passed.
- * @param {Object} config (optional) Config object.
- */
- constructor: function(html) {
- var me = this,
- args = arguments,
- buffer = [],
- i = 0,
- length = args.length,
- value;
- me.initialConfig = {};
-
- // Allow an array to be passed here so we can
- // pass an array of strings and an object
- // at the end
- if (length === 1 && Ext.isArray(html)) {
- args = html;
- length = args.length;
- }
- if (length > 1) {
- for (; i < length; i++) {
- value = args[i];
- if (typeof value == 'object') {
- Ext.apply(me.initialConfig, value);
- Ext.apply(me, value);
- } else {
- buffer.push(value);
- }
- }
- } else {
- buffer.push(html);
- }
- // @private
- me.html = buffer.join('');
- if (me.compiled) {
- me.compile();
- }
- },
- /**
- * @property {Boolean} isTemplate
- * `true` in this class to identify an object as an instantiated Template, or subclass thereof.
- */
- isTemplate: true,
- /**
- * @cfg {Boolean} [compiled=false]
- * `true` to immediately compile the template.
- */
- /**
- * @cfg {Boolean} [disableFormats=false]
- * `true` to disable format functions in the template. If the template doesn't contain
- * format functions, setting `disableFormats` to `true` will reduce apply time.
- */
- disableFormats: false,
- re: /\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
- /**
- * Returns an HTML fragment of this template with the specified values applied.
- *
- * @param {Object/Array} values The template values. Can be an array if your params are numeric:
- *
- * var tpl = new Ext.Template('Name: {0}, Age: {1}');
- * tpl.apply(['John', 25]);
- *
- * or an object:
- *
- * var tpl = new Ext.Template('Name: {name}, Age: {age}');
- * tpl.apply({name: 'John', age: 25});
- *
- * @return {String} The HTML fragment.
- */
- apply: function(values) {
- var me = this,
- useFormat = me.disableFormats !== true,
- fm = Ext.util.Format,
- tpl = me,
- ret;
- if (me.compiled) {
- return me.compiled(values).join('');
- }
- function fn(m, name, format, args) {
- if (format && useFormat) {
- if (args) {
- args = [values[name]].concat(Ext.functionFactory('return ['+ args +'];')());
- } else {
- args = [values[name]];
- }
- if (format.substr(0, 5) == "this.") {
- return tpl[format.substr(5)].apply(tpl, args);
- }
- else {
- return fm[format].apply(fm, args);
- }
- }
- else {
- return values[name] !== undefined ? values[name] : "";
- }
- }
- ret = me.html.replace(me.re, fn);
- return ret;
- },
- /**
- * Appends the result of this template to the provided output array.
- * @param {Object/Array} values The template values. See {@link #apply}.
- * @param {Array} out The array to which output is pushed.
- * @return {Array} The given out array.
- */
- applyOut: function(values, out) {
- var me = this;
- if (me.compiled) {
- out.push.apply(out, me.compiled(values));
- } else {
- out.push(me.apply(values));
- }
- return out;
- },
- /**
- * @method applyTemplate
- * @member Ext.Template
- * Alias for {@link #apply}.
- * @inheritdoc Ext.Template#apply
- */
- applyTemplate: function () {
- return this.apply.apply(this, arguments);
- },
- /**
- * Sets the HTML used as the template and optionally compiles it.
- * @param {String} html
- * @param {Boolean} compile (optional) `true` to compile the template.
- * @return {Ext.Template} this
- */
- set: function(html, compile) {
- var me = this;
- me.html = html;
- me.compiled = null;
- return compile ? me.compile() : me;
- },
- compileARe: /\\/g,
- compileBRe: /(\r\n|\n)/g,
- compileCRe: /'/g,
- /**
- * Compiles the template into an internal function, eliminating the RegEx overhead.
- * @return {Ext.Template} this
- */
- compile: function() {
- var me = this,
- fm = Ext.util.Format,
- useFormat = me.disableFormats !== true,
- body, bodyReturn;
- function fn(m, name, format, args) {
- if (format && useFormat) {
- args = args ? ',' + args: "";
- if (format.substr(0, 5) != "this.") {
- format = "fm." + format + '(';
- }
- else {
- format = 'this.' + format.substr(5) + '(';
- }
- }
- else {
- args = '';
- format = "(values['" + name + "'] == undefined ? '' : ";
- }
- return "'," + format + "values['" + name + "']" + args + ") ,'";
- }
- bodyReturn = me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn);
- body = "this.compiled = function(values){ return ['" + bodyReturn + "'];};";
- eval(body);
- return me;
- },
- /**
- * Applies the supplied values to the template and inserts the new node(s) as the first child of el.
- *
- * @param {String/HTMLElement/Ext.Element} el The context element.
- * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
- * @param {Boolean} returnElement (optional) `true` to return a Ext.Element.
- * @return {HTMLElement/Ext.Element} The new node or Element.
- */
- insertFirst: function(el, values, returnElement) {
- return this.doInsert('afterBegin', el, values, returnElement);
- },
- /**
- * Applies the supplied values to the template and inserts the new node(s) before el.
- *
- * @param {String/HTMLElement/Ext.Element} el The context element.
- * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
- * @param {Boolean} returnElement (optional) `true` to return an Ext.Element.
- * @return {HTMLElement/Ext.Element} The new node or Element
- */
- insertBefore: function(el, values, returnElement) {
- return this.doInsert('beforeBegin', el, values, returnElement);
- },
- /**
- * Applies the supplied values to the template and inserts the new node(s) after el.
- *
- * @param {String/HTMLElement/Ext.Element} el The context element.
- * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
- * @param {Boolean} returnElement (optional) `true` to return a Ext.Element.
- * @return {HTMLElement/Ext.Element} The new node or Element.
- */
- insertAfter: function(el, values, returnElement) {
- return this.doInsert('afterEnd', el, values, returnElement);
- },
- /**
- * Applies the supplied `values` to the template and appends the new node(s) to the specified `el`.
- *
- * For example usage see {@link Ext.Template Ext.Template class docs}.
- *
- * @param {String/HTMLElement/Ext.Element} el The context element.
- * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
- * @param {Boolean} returnElement (optional) true to return an Ext.Element.
- * @return {HTMLElement/Ext.Element} The new node or Element.
- */
- append: function(el, values, returnElement) {
- return this.doInsert('beforeEnd', el, values, returnElement);
- },
- doInsert: function(where, el, values, returnElement) {
- var newNode = Ext.DomHelper.insertHtml(where, Ext.getDom(el), this.apply(values));
- return returnElement ? Ext.get(newNode) : newNode;
- },
- /**
- * Applies the supplied values to the template and overwrites the content of el with the new node(s).
- *
- * @param {String/HTMLElement/Ext.Element} el The context element.
- * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
- * @param {Boolean} returnElement (optional) true to return a Ext.Element.
- * @return {HTMLElement/Ext.Element} The new node or Element.
- */
- overwrite: function(el, values, returnElement) {
- var newNode = Ext.DomHelper.overwrite(Ext.getDom(el), this.apply(values));
- return returnElement ? Ext.get(newNode) : newNode;
- }
- });
- /**
- * This class parses the XTemplate syntax and calls abstract methods to process the parts.
- * @private
- */
- Ext.define('Ext.XTemplateParser', {
- constructor: function (config) {
- Ext.apply(this, config);
- },
- /**
- * @property {Number} level The 'for' loop context level. This is adjusted up by one
- * prior to calling {@link #doFor} and down by one after calling the corresponding
- * {@link #doEnd} that closes the loop. This will be 1 on the first {@link #doFor}
- * call.
- */
- /**
- * This method is called to process a piece of raw text from the tpl.
- * @param {String} text
- * @method doText
- */
- // doText: function (text)
- /**
- * This method is called to process expressions (like `{[expr]}`).
- * @param {String} expr The body of the expression (inside "{[" and "]}").
- * @method doExpr
- */
- // doExpr: function (expr)
- /**
- * This method is called to process simple tags (like `{tag}`).
- * @param {String} tag
- * @method doTag
- */
- // doTag: function (tag)
- /**
- * This method is called to process `<tpl else>`.
- * @method doElse
- */
- // doElse: function ()
- /**
- * This method is called to process `{% text %}`.
- * @param {String} text
- * @method doEval
- */
- // doEval: function (text)
- /**
- * This method is called to process `<tpl if="action">`. If there are other attributes,
- * these are passed in the actions object.
- * @param {String} action
- * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
- * @method doIf
- */
- // doIf: function (action, actions)
- /**
- * This method is called to process `<tpl elseif="action">`. If there are other attributes,
- * these are passed in the actions object.
- * @param {String} action
- * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
- * @method doElseIf
- */
- // doElseIf: function (action, actions)
- /**
- * This method is called to process `<tpl switch="action">`. If there are other attributes,
- * these are passed in the actions object.
- * @param {String} action
- * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
- * @method doSwitch
- */
- // doSwitch: function (action, actions)
- /**
- * This method is called to process `<tpl case="action">`. If there are other attributes,
- * these are passed in the actions object.
- * @param {String} action
- * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
- * @method doCase
- */
- // doCase: function (action, actions)
- /**
- * This method is called to process `<tpl default>`.
- * @method doDefault
- */
- // doDefault: function ()
- /**
- * This method is called to process `</tpl>`. It is given the action type that started
- * the tpl and the set of additional actions.
- * @param {String} type The type of action that is being ended.
- * @param {Object} actions The other actions keyed by the attribute name (such as 'exec').
- * @method doEnd
- */
- // doEnd: function (type, actions)
- /**
- * This method is called to process `<tpl for="action">`. If there are other attributes,
- * these are passed in the actions object.
- * @param {String} action
- * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
- * @method doFor
- */
- // doFor: function (action, actions)
- /**
- * This method is called to process `<tpl exec="action">`. If there are other attributes,
- * these are passed in the actions object.
- * @param {String} action
- * @param {Object} actions Other actions keyed by the attribute name.
- * @method doExec
- */
- // doExec: function (action, actions)
- /**
- * This method is called to process an empty `<tpl>`. This is unlikely to need to be
- * implemented, so a default (do nothing) version is provided.
- * @method
- */
- doTpl: Ext.emptyFn,
- parse: function (str) {
- var me = this,
- len = str.length,
- aliases = { elseif: 'elif' },
- topRe = me.topRe,
- actionsRe = me.actionsRe,
- index, stack, s, m, t, prev, frame, subMatch, begin, end, actions,
- prop;
- me.level = 0;
- me.stack = stack = [];
- for (index = 0; index < len; index = end) {
- topRe.lastIndex = index;
- m = topRe.exec(str);
- if (!m) {
- me.doText(str.substring(index, len));
- break;
- }
- begin = m.index;
- end = topRe.lastIndex;
- if (index < begin) {
- me.doText(str.substring(index, begin));
- }
- if (m[1]) {
- end = str.indexOf('%}', begin+2);
- me.doEval(str.substring(begin+2, end));
- end += 2;
- } else if (m[2]) {
- end = str.indexOf(']}', begin+2);
- me.doExpr(str.substring(begin+2, end));
- end += 2;
- } else if (m[3]) { // if ('{' token)
- me.doTag(m[3]);
- } else if (m[4]) { // content of a <tpl xxxxxx xxx> tag
- actions = null;
- while ((subMatch = actionsRe.exec(m[4])) !== null) {
- s = subMatch[2] || subMatch[3];
- if (s) {
- s = Ext.String.htmlDecode(s); // decode attr value
- t = subMatch[1];
- t = aliases[t] || t;
- actions = actions || {};
- prev = actions[t];
- if (typeof prev == 'string') {
- actions[t] = [prev, s];
- } else if (prev) {
- actions[t].push(s);
- } else {
- actions[t] = s;
- }
- }
- }
- if (!actions) {
- if (me.elseRe.test(m[4])) {
- me.doElse();
- } else if (me.defaultRe.test(m[4])) {
- me.doDefault();
- } else {
- me.doTpl();
- stack.push({ type: 'tpl' });
- }
- }
- else if (actions['if']) {
- me.doIf(actions['if'], actions);
- stack.push({ type: 'if' });
- }
- else if (actions['switch']) {
- me.doSwitch(actions['switch'], actions);
- stack.push({ type: 'switch' });
- }
- else if (actions['case']) {
- me.doCase(actions['case'], actions);
- }
- else if (actions['elif']) {
- me.doElseIf(actions['elif'], actions);
- }
- else if (actions['for']) {
- ++me.level;
- // Extract property name to use from indexed item
- if (prop = me.propRe.exec(m[4])) {
- actions.propName = prop[1] || prop[2];
- }
- me.doFor(actions['for'], actions);
- stack.push({ type: 'for', actions: actions });
- }
- else if (actions.exec) {
- me.doExec(actions.exec, actions);
- stack.push({ type: 'exec', actions: actions });
- }
- /*
- else {
- // todo - error
- }
- */
- } else if (m[0].length === 5) {
- // 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>)
- // in this case no action is needed other than pushing it on to the stack
- stack.push({ type: 'tpl' });
- } else {
- frame = stack.pop();
- me.doEnd(frame.type, frame.actions);
- if (frame.type == 'for') {
- --me.level;
- }
- }
- }
- },
- // Internal regexes
-
- topRe: /(?:(\{\%)|(\{\[)|\{([^{}]*)\})|(?:<tpl([^>]*)\>)|(?:<\/tpl>)/g,
- actionsRe: /\s*(elif|elseif|if|for|exec|switch|case|eval)\s*\=\s*(?:(?:"([^"]*)")|(?:'([^']*)'))\s*/g,
- propRe: /prop=(?:(?:"([^"]*)")|(?:'([^']*)'))/,
- defaultRe: /^\s*default\s*$/,
- elseRe: /^\s*else\s*$/
- });
- /**
- * This class compiles the XTemplate syntax into a function object. The function is used
- * like so:
- *
- * function (out, values, parent, xindex, xcount) {
- * // out is the output array to store results
- * // values, parent, xindex and xcount have their historical meaning
- * }
- *
- * @private
- */
- Ext.define('Ext.XTemplateCompiler', {
- extend: 'Ext.XTemplateParser',
- // Chrome really likes "new Function" to realize the code block (as in it is
- // 2x-3x faster to call it than using eval), but Firefox chokes on it badly.
- // IE and Opera are also fine with the "new Function" technique.
- useEval: Ext.isGecko,
- // See [http://jsperf.com/nige-array-append](http://jsperf.com/nige-array-append) for quickest way to append to an array of unknown length
- // (Due to arbitrary code execution inside a template, we cannot easily track the length in var)
- // On IE6 and 7 `myArray[myArray.length]='foo'` is better. On other browsers `myArray.push('foo')` is better.
- useIndex: Ext.isIE6 || Ext.isIE7,
- useFormat: true,
- propNameRe: /^[\w\d\$]*$/,
- compile: function (tpl) {
- var me = this,
- code = me.generate(tpl);
- // When using "new Function", we have to pass our "Ext" variable to it in order to
- // support sandboxing. If we did not, the generated function would use the global
- // "Ext", not the "Ext" from our sandbox (scope chain).
- //
- return me.useEval ? me.evalTpl(code) : (new Function('Ext', code))(Ext);
- },
- generate: function (tpl) {
- var me = this,
- // note: Ext here is properly sandboxed
- definitions = 'var fm=Ext.util.Format,ts=Object.prototype.toString;',
- code;
- // Track how many levels we use, so that we only "var" each level's variables once
- me.maxLevel = 0;
- me.body = [
- 'var c0=values, a0=' + me.createArrayTest(0) + ', p0=parent, n0=xcount, i0=xindex, v;\n'
- ];
- if (me.definitions) {
- if (typeof me.definitions === 'string') {
- me.definitions = [me.definitions, definitions ];
- } else {
- me.definitions.push(definitions);
- }
- } else {
- me.definitions = [ definitions ];
- }
- me.switches = [];
- me.parse(tpl);
- me.definitions.push(
- (me.useEval ? '$=' : 'return') + ' function (' + me.fnArgs + ') {',
- me.body.join(''),
- '}'
- );
- code = me.definitions.join('\n');
- // Free up the arrays.
- me.definitions.length = me.body.length = me.switches.length = 0;
- delete me.definitions;
- delete me.body;
- delete me.switches;
- return code;
- },
- //-----------------------------------
- // XTemplateParser callouts
- //
- doText: function (text) {
- var me = this,
- out = me.body;
- text = text.replace(me.aposRe, "\\'").replace(me.newLineRe, '\\n');
- if (me.useIndex) {
- out.push('out[out.length]=\'', text, '\'\n');
- } else {
- out.push('out.push(\'', text, '\')\n');
- }
- },
- doExpr: function (expr) {
- var out = this.body;
- out.push('if ((v=' + expr + ')!==undefined && (v=' + expr + ')!==null) out');
- // Coerce value to string using concatenation of an empty string literal.
- // See http://jsperf.com/tostringvscoercion/5
- if (this.useIndex) {
- out.push('[out.length]=v+\'\'\n');
- } else {
- out.push('.push(v+\'\')\n');
- }
- },
- doTag: function (tag) {
- this.doExpr(this.parseTag(tag));
- },
- doElse: function () {
- this.body.push('} else {\n');
- },
- doEval: function (text) {
- this.body.push(text, '\n');
- },
- doIf: function (action, actions) {
- var me = this;
- // If it's just a propName, use it directly in the if
- if (action === '.') {
- me.body.push('if (values) {\n');
- } else if (me.propNameRe.test(action)) {
- me.body.push('if (', me.parseTag(action), ') {\n');
- }
- // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values)
- else {
- me.body.push('if (', me.addFn(action), me.callFn, ') {\n');
- }
- if (actions.exec) {
- me.doExec(actions.exec);
- }
- },
- doElseIf: function (action, actions) {
- var me = this;
- // If it's just a propName, use it directly in the else if
- if (action === '.') {
- me.body.push('else if (values) {\n');
- } else if (me.propNameRe.test(action)) {
- me.body.push('} else if (', me.parseTag(action), ') {\n');
- }
- // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values)
- else {
- me.body.push('} else if (', me.addFn(action), me.callFn, ') {\n');
- }
- if (actions.exec) {
- me.doExec(actions.exec);
- }
- },
- doSwitch: function (action) {
- var me = this;
- // If it's just a propName, use it directly in the switch
- if (action === '.') {
- me.body.push('switch (values) {\n');
- } else if (me.propNameRe.test(action)) {
- me.body.push('switch (', me.parseTag(action), ') {\n');
- }
- // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values)
- else {
- me.body.push('switch (', me.addFn(action), me.callFn, ') {\n');
- }
- me.switches.push(0);
- },
- doCase: function (action) {
- var me = this,
- cases = Ext.isArray(action) ? action : [action],
- n = me.switches.length - 1,
- match, i;
- if (me.switches[n]) {
- me.body.push('break;\n');
- } else {
- me.switches[n]++;
- }
- for (i = 0, n = cases.length; i < n; ++i) {
- match = me.intRe.exec(cases[i]);
- cases[i] = match ? match[1] : ("'" + cases[i].replace(me.aposRe,"\\'") + "'");
- }
- me.body.push('case ', cases.join(': case '), ':\n');
- },
- doDefault: function () {
- var me = this,
- n = me.switches.length - 1;
- if (me.switches[n]) {
- me.body.push('break;\n');
- } else {
- me.switches[n]++;
- }
- me.body.push('default:\n');
- },
- doEnd: function (type, actions) {
- var me = this,
- L = me.level-1;
- if (type == 'for') {
- /*
- To exit a for loop we must restore the outer loop's context. The code looks
- like this (which goes with that produced by doFor:
- for (...) { // the part generated by doFor
- ... // the body of the for loop
- // ... any tpl for exec statement goes here...
- }
- parent = p1;
- values = r2;
- xcount = n1;
- xindex = i1
- */
- if (actions.exec) {
- me.doExec(actions.exec);
- }
- me.body.push('}\n');
- me.body.push('parent=p',L,';values=r',L+1,';xcount=n',L,';xindex=i',L,'\n');
- } else if (type == 'if' || type == 'switch') {
- me.body.push('}\n');
- }
- },
- doFor: function (action, actions) {
- var me = this,
- s,
- L = me.level,
- up = L-1,
- pL = 'p' + L,
- parentAssignment;
- // If it's just a propName, use it directly in the switch
- if (action === '.') {
- s = 'values';
- } else if (me.propNameRe.test(action)) {
- s = me.parseTag(action);
- }
- // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values)
- else {
- s = me.addFn(action) + me.callFn;
- }
- /*
- We are trying to produce a block of code that looks like below. We use the nesting
- level to uniquely name the control variables.
- // Omit "var " if we have already been through level 2
- var i2 = 0,
- n2 = 0,
- c2 = values['propName'],
- // c2 is the context object for the for loop
- a2 = Array.isArray(c2);
- p2 = c1,
- // p2 is the parent context (of the outer for loop)
- r2 = values
- // r2 is the values object to
- // If iterating over the current data, the parent is always set to c2
- parent = c2;
- // If iterating over a property in an object, set the parent to the object
- parent = a1 ? c1[i1] : p2 // set parent
- if (c2) {
- if (a2) {
- n2 = c2.length;
- } else if (c2.isMixedCollection) {
- c2 = c2.items;
- n2 = c2.length;
- } else if (c2.isStore) {
- c2 = c2.data.items;
- n2 = c2.length;
- } else {
- c2 = [ c2 ];
- n2 = 1;
- }
- }
- // i2 is the loop index and n2 is the number (xcount) of this for loop
- for (xcount = n2; i2 < n2; ++i2) {
- values = c2[i2] // adjust special vars to inner scope
- xindex = i2 + 1 // xindex is 1-based
- The body of the loop is whatever comes between the tpl and /tpl statements (which
- is handled by doEnd).
- */
- // Declare the vars for a particular level only if we have not already declared them.
- if (me.maxLevel < L) {
- me.maxLevel = L;
- me.body.push('var ');
- }
- if (action == '.') {
- parentAssignment = 'c' + L;
- } else {
- parentAssignment = 'a' + up + '?c' + up + '[i' + up + ']:p' + L;
- }
- 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',
- 'parent=',parentAssignment,'\n',
- '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',
- 'for (xcount=n',L,';i',L,'<n'+L+';++i',L,'){\n',
- 'values=c',L,'[i',L,']');
- if (actions.propName) {
- me.body.push('.', actions.propName);
- }
- me.body.push('\n',
- 'xindex=i',L,'+1\n');
- },
- createArrayTest: ('isArray' in Array) ? function(L) {
- return 'Array.isArray(c' + L + ')';
- } : function(L) {
- return 'ts.call(c' + L + ')==="[object Array]"';
- },
- doExec: function (action, actions) {
- var me = this,
- name = 'f' + me.definitions.length;
- me.definitions.push('function ' + name + '(' + me.fnArgs + ') {',
- ' try { with(values) {',
- ' ' + action,
- ' }} catch(e) {',
- //<debug>
- 'Ext.Logger.log("XTemplate Error: " + e.message);',
- //</debug>
- '}',
- '}');
- me.body.push(name + me.callFn + '\n');
- },
- //-----------------------------------
- // Internal
- //
- addFn: function (body) {
- var me = this,
- name = 'f' + me.definitions.length;
- if (body === '.') {
- me.definitions.push('function ' + name + '(' + me.fnArgs + ') {',
- ' return values',
- '}');
- } else if (body === '..') {
- me.definitions.push('function ' + name + '(' + me.fnArgs + ') {',
- ' return parent',
- '}');
- } else {
- me.definitions.push('function ' + name + '(' + me.fnArgs + ') {',
- ' try { with(values) {',
- ' return(' + body + ')',
- ' }} catch(e) {',
- //<debug>
- 'Ext.Logger.log("XTemplate Error: " + e.message);',
- //</debug>
- '}',
- '}');
- }
- return name;
- },
- parseTag: function (tag) {
- var me = this,
- m = me.tagRe.exec(tag),
- name = m[1],
- format = m[2],
- args = m[3],
- math = m[4],
- v;
- // name = "." - Just use the values object.
- if (name == '.') {
- // filter to not include arrays/objects/nulls
- if (!me.validTypes) {
- me.definitions.push('var validTypes={string:1,number:1,boolean:1};');
- me.validTypes = true;
- }
- v = 'validTypes[typeof values] || ts.call(values) === "[object Date]" ? values : ""';
- }
- // name = "#" - Use the xindex
- else if (name == '#') {
- v = 'xindex';
- }
- else if (name.substr(0, 7) == "parent.") {
- v = name;
- }
- // compound JavaScript property name (e.g., "foo.bar")
- else if (isNaN(name) && name.indexOf('-') == -1 && name.indexOf('.') != -1) {
- v = "values." + name;
- }
- // number or a '-' in it or a single word (maybe a keyword): use array notation
- // (http://jsperf.com/string-property-access/4)
- else {
- v = "values['" + name + "']";
- }
- if (math) {
- v = '(' + v + math + ')';
- }
- if (format && me.useFormat) {
- args = args ? ',' + args : "";
- if (format.substr(0, 5) != "this.") {
- format = "fm." + format + '(';
- } else {
- format += '(';
- }
- } else {
- return v;
- }
- return format + v + args + ')';
- },
- // @private
- evalTpl: function ($) {
- // We have to use eval to realize the code block and capture the inner func we also
- // don't want a deep scope chain. We only do this in Firefox and it is also unhappy
- // with eval containing a return statement, so instead we assign to "$" and return
- // that. Because we use "eval", we are automatically sandboxed properly.
- eval($);
- return $;
- },
- newLineRe: /\r\n|\r|\n/g,
- aposRe: /[']/g,
- intRe: /^\s*(\d+)\s*$/,
- tagRe: /([\w-\.\#\$]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?/
- }, function () {
- var proto = this.prototype;
- proto.fnArgs = 'out,values,parent,xindex,xcount';
- proto.callFn = '.call(this,' + proto.fnArgs + ')';
- });
- /**
- * A template class that supports advanced functionality like:
- *
- * - Autofilling arrays using templates and sub-templates
- * - Conditional processing with basic comparison operators
- * - Basic math function support
- * - Execute arbitrary inline code with special built-in template variables
- * - Custom member functions
- * - 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
- *
- * XTemplate provides the templating mechanism built into {@link Ext.DataView}.
- *
- * The {@link Ext.Template} describes the acceptable parameters to pass to the constructor. The following examples
- * demonstrate all of the supported features.
- *
- * # Sample Data
- *
- * This is the data object used for reference in each code example:
- *
- * var data = {
- * name: 'Don Griffin',
- * title: 'Senior Technomage',
- * company: 'Sencha Inc.',
- * drinks: ['Coffee', 'Water', 'More Coffee'],
- * kids: [
- * { name: 'Aubrey', age: 17 },
- * { name: 'Joshua', age: 13 },
- * { name: 'Cale', age: 10 },
- * { name: 'Nikol', age: 5 },
- * { name: 'Solomon', age: 0 }
- * ]
- * };
- *
- * # Auto filling of arrays
- *
- * The **tpl** tag and the **for** operator are used to process the provided data object:
- *
- * - If the value specified in for is an array, it will auto-fill, repeating the template block inside the tpl
- * tag for each item in the array.
- * - If for="." is specified, the data object provided is examined.
- * - While processing an array, the special variable {#} will provide the current array index + 1 (starts at 1, not 0).
- *
- * Examples:
- *
- * <tpl for=".">...</tpl> // loop through array at root node
- * <tpl for="foo">...</tpl> // loop through array at foo node
- * <tpl for="foo.bar">...</tpl> // loop through array at foo.bar node
- *
- * Using the sample data above:
- *
- * var tpl = new Ext.XTemplate(
- * '<p>Kids: ',
- * '<tpl for=".">', // process the data.kids node
- * '<p>{#}. {name}</p>', // use current array index to autonumber
- * '</tpl></p>'
- * );
- * tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
- *
- * An example illustrating how the **for** property can be leveraged to access specified members of the provided data
- * object to populate the template:
- *
- * var tpl = new Ext.XTemplate(
- * '<p>Name: {name}</p>',
- * '<p>Title: {title}</p>',
- * '<p>Company: {company}</p>',
- * '<p>Kids: ',
- * '<tpl for="kids">', // interrogate the kids property within the data
- * '<p>{name}</p>',
- * '</tpl></p>'
- * );
- * tpl.overwrite(panel.body, data); // pass the root node of the data object
- *
- * Flat arrays that contain values (and not objects) can be auto-rendered using the special **`{.}`** variable inside a
- * loop. This variable will represent the value of the array at the current index:
- *
- * var tpl = new Ext.XTemplate(
- * '<p>{name}\'s favorite beverages:</p>',
- * '<tpl for="drinks">',
- * '<div> - {.}</div>',
- * '</tpl>'
- * );
- * tpl.overwrite(panel.body, data);
- *
- * When processing a sub-template, for example while looping through a child array, you can access the parent object's
- * members via the **parent** object:
- *
- * var tpl = new Ext.XTemplate(
- * '<p>Name: {name}</p>',
- * '<p>Kids: ',
- * '<tpl for="kids">',
- * '<tpl if="age > 1">',
- * '<p>{name}</p>',
- * '<p>Dad: {parent.name}</p>',
- * '</tpl>',
- * '</tpl></p>'
- * );
- * tpl.overwrite(panel.body, data);
- *
- * # Conditional processing with basic comparison operators
- *
- * The **tpl** tag and the **if** operator are used to provide conditional checks for deciding whether or not to render
- * specific parts of the template.
- *
- * Using the sample data above:
- *
- * var tpl = new Ext.XTemplate(
- * '<p>Name: {name}</p>',
- * '<p>Kids: ',
- * '<tpl for="kids">',
- * '<tpl if="age > 1">',
- * '<p>{name}</p>',
- * '</tpl>',
- * '</tpl></p>'
- * );
- * tpl.overwrite(panel.body, data);
- *
- * More advanced conditionals are also supported:
- *
- * var tpl = new Ext.XTemplate(
- * '<p>Name: {name}</p>',
- * '<p>Kids: ',
- * '<tpl for="kids">',
- * '<p>{name} is a ',
- * '<tpl if="age >= 13">',
- * '<p>teenager</p>',
- * '<tpl elseif="age >= 2">',
- * '<p>kid</p>',
- * '<tpl else>',
- * '<p>baby</p>',
- * '</tpl>',
- * '</tpl></p>'
- * );
- *
- * var tpl = new Ext.XTemplate(
- * '<p>Name: {name}</p>',
- * '<p>Kids: ',
- * '<tpl for="kids">',
- * '<p>{name} is a ',
- * '<tpl switch="name">',
- * '<tpl case="Aubrey" case="Nikol">',
- * '<p>girl</p>',
- * '<tpl default">',
- * '<p>boy</p>',
- * '</tpl>',
- * '</tpl></p>'
- * );
- *
- * A `break` is implied between each case and default, however, multiple cases can be listed
- * in a single <tpl> tag.
- *
- * # Using double quotes
- *
- * Examples:
- *
- * var tpl = new Ext.XTemplate(
- * "<tpl if='age > 1 && age < 10'>Child</tpl>",
- * "<tpl if='age >= 10 && age < 18'>Teenager</tpl>",
- * "<tpl if='this.isGirl(name)'>...</tpl>",
- * '<tpl if="id == \'download\'">...</tpl>',
- * "<tpl if='needsIcon'><img src='{icon}' class='{iconCls}'/></tpl>",
- * "<tpl if='name == \"Don\"'>Hello</tpl>"
- * );
- *
- * # Basic math support
- *
- * The following basic math operators may be applied directly on numeric data values:
- *
- * + - * /
- *
- * For example:
- *
- * var tpl = new Ext.XTemplate(
- * '<p>Name: {name}</p>',
- * '<p>Kids: ',
- * '<tpl for="kids">',
- * '<tpl if="age > 1">', // <-- Note that the > is encoded
- * '<p>{#}: {name}</p>', // <-- Auto-number each item
- * '<p>In 5 Years: {age+5}</p>', // <-- Basic math
- * '<p>Dad: {parent.name}</p>',
- * '</tpl>',
- * '</tpl></p>'
- * );
- * tpl.overwrite(panel.body, data);
- *
- * # Execute arbitrary inline code with special built-in template variables
- *
- * Anything between `{[ ... ]}` is considered code to be executed in the scope of the template.
- * The expression is evaluated and the result is included in the generated result. There are
- * some special variables available in that code:
- *
- * - **out**: The output array into which the template is being appended (using `push` to later
- * `join`).
- * - **values**: The values in the current scope. If you are using scope changing sub-templates,
- * you can change what values is.
- * - **parent**: The scope (values) of the ancestor template.
- * - **xindex**: If you are in a looping template, the index of the loop you are in (1-based).
- * - **xcount**: If you are in a looping template, the total length of the array you are looping.
- *
- * This example demonstrates basic row striping using an inline code block and the xindex variable:
- *
- * var tpl = new Ext.XTemplate(
- * '<p>Name: {name}</p>',
- * '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
- * '<p>Kids: ',
- * '<tpl for="kids">',
- * '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
- * '{name}',
- * '</div>',
- * '</tpl></p>'
- * );
- *
- * Any code contained in "verbatim" blocks (using "{% ... %}") will be inserted directly in
- * the generated code for the template. These blocks are not included in the output. This
- * can be used for simple things like break/continue in a loop, or control structures or
- * method calls (when they don't produce output). The `this` references the template instance.
- *
- * var tpl = new Ext.XTemplate(
- * '<p>Name: {name}</p>',
- * '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
- * '<p>Kids: ',
- * '<tpl for="kids">',
- * '{% if (xindex % 2 === 0) continue; %}',
- * '{name}',
- * '{% if (xindex > 100) break; %}',
- * '</div>',
- * '</tpl></p>'
- * );
- *
- * # Template member functions
- *
- * One or more member functions can be specified in a configuration object passed into the XTemplate constructor for
- * more complex processing:
- *
- * var tpl = new Ext.XTemplate(
- * '<p>Name: {name}</p>',
- * '<p>Kids: ',
- * '<tpl for="kids">',
- * '<tpl if="this.isGirl(name)">',
- * '<p>Girl: {name} - {age}</p>',
- * '<tpl else>',
- * '<p>Boy: {name} - {age}</p>',
- * '</tpl>',
- * '<tpl if="this.isBaby(age)">',
- * '<p>{name} is a baby!</p>',
- * '</tpl>',
- * '</tpl></p>',
- * {
- * // XTemplate configuration:
- * disableFormats: true,
- * // member functions:
- * isGirl: function(name){
- * return name == 'Sara Grace';
- * },
- * isBaby: function(age){
- * return age < 1;
- * }
- * }
- * );
- * tpl.overwrite(panel.body, data);
- */
- Ext.define('Ext.XTemplate', {
- extend: 'Ext.Template',
- requires: 'Ext.XTemplateCompiler',
- /**
- * @private
- */
- emptyObj: {},
- /**
- * @cfg {Boolean} compiled
- * Only applies to {@link Ext.Template}, XTemplates are compiled automatically on the
- * first call to {@link #apply} or {@link #applyOut}.
- * @hide
- */
- apply: function(values) {
- return this.applyOut(values, []).join('');
- },
- /**
- * Appends the result of this template to the provided output array.
- * @param {Object/Array} values The template values. See {@link #apply}.
- * @param {Array} out The array to which output is pushed.
- * @param {Object} parent
- * @return {Array} The given out array.
- */
- applyOut: function(values, out, parent) {
- var me = this,
- xindex = values.xindex,
- xcount = values.xcount,
- compiler;
- if (!me.fn) {
- compiler = new Ext.XTemplateCompiler({
- useFormat : me.disableFormats !== true,
- definitions : me.definitions
- });
- me.fn = compiler.compile(me.html);
- }
- try {
- xindex = typeof xindex === 'number' ? xindex : 1;
- xcount = typeof xcount === 'number' ? xcount : 1;
- me.fn.call(me, out, values, parent || me.emptyObj, xindex, xcount);
- } catch (e) {
- //<debug>
- Ext.Logger.log('Error: ' + e.message);
- //</debug>
- }
- return out;
- },
- /**
- * Does nothing. XTemplates are compiled automatically, so this function simply returns this.
- * @return {Ext.XTemplate} this
- */
- compile: function() {
- return this;
- },
- statics: {
- /**
- * Gets an `XTemplate` from an object (an instance of an {@link Ext#define}'d class).
- * Many times, templates are configured high in the class hierarchy and are to be
- * shared by all classes that derive from that base. To further complicate matters,
- * these templates are seldom actual instances but are rather configurations. For
- * example:
- *
- * Ext.define('MyApp.Class', {
- * someTpl: [
- * 'tpl text here'
- * ]
- * });
- *
- * The goal being to share that template definition with all instances and even
- * instances of derived classes, until `someTpl` is overridden. This method will
- * "upgrade" these configurations to be real `XTemplate` instances *in place* (to
- * avoid creating one instance per object).
- *
- * @param {Object} instance The object from which to get the `XTemplate` (must be
- * an instance of an {@link Ext#define}'d class).
- * @param {String} name The name of the property by which to get the `XTemplate`.
- * @return {Ext.XTemplate} The `XTemplate` instance or null if not found.
- * @protected
- */
- getTpl: function (instance, name) {
- var tpl = instance[name], // go for it! 99% of the time we will get it!
- proto;
- if (tpl && !tpl.isTemplate) { // tpl is just a configuration (not an instance)
- // create the template instance from the configuration:
- tpl = Ext.ClassManager.dynInstantiate('Ext.XTemplate', tpl);
- // and replace the reference with the new instance:
- if (instance.hasOwnProperty(name)) { // the tpl is on the instance
- instance[name] = tpl;
- } else { // must be somewhere in the prototype chain
- for (proto = instance.self.prototype; proto; proto = proto.superclass) {
- if (proto.hasOwnProperty(name)) {
- proto[name] = tpl;
- break;
- }
- }
- }
- }
- // else !tpl (no such tpl) or the tpl is an instance already... either way, tpl
- // is ready to return
- return tpl || null;
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.behavior.Behavior', {
- constructor: function(component) {
- this.component = component;
- component.on('destroy', 'onComponentDestroy', this);
- },
- onComponentDestroy: Ext.emptyFn
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.easing.Abstract', {
- config: {
- startTime: 0,
- startValue: 0
- },
- isEasing: true,
- isEnded: false,
- constructor: function(config) {
- this.initConfig(config);
- return this;
- },
- applyStartTime: function(startTime) {
- if (!startTime) {
- startTime = Ext.Date.now();
- }
- return startTime;
- },
- updateStartTime: function(startTime) {
- this.reset();
- },
- reset: function() {
- this.isEnded = false;
- },
- getValue: Ext.emptyFn
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.easing.Linear', {
- extend: 'Ext.fx.easing.Abstract',
- alias: 'easing.linear',
- config: {
- duration: 0,
- endValue: 0
- },
- updateStartValue: function(startValue) {
- this.distance = this.getEndValue() - startValue;
- },
- updateEndValue: function(endValue) {
- this.distance = endValue - this.getStartValue();
- },
- getValue: function() {
- var deltaTime = Ext.Date.now() - this.getStartTime(),
- duration = this.getDuration();
- if (deltaTime > duration) {
- this.isEnded = true;
- return this.getEndValue();
- }
- else {
- return this.getStartValue() + ((deltaTime / duration) * this.distance);
- }
- }
- });
- /**
- * @private
- *
- * The abstract class. Sub-classes are expected, at the very least, to implement translation logics inside
- * the 'translate' method
- */
- Ext.define('Ext.util.translatable.Abstract', {
- extend: 'Ext.Evented',
- requires: ['Ext.fx.easing.Linear'],
- config: {
- easing: null,
- easingX: null,
- easingY: null,
- fps: Ext.os.is.Android4 ? 50 : 60
- },
- /**
- * @event animationstart
- * Fires whenever the animation is started
- * @param {Ext.util.translatable.Abstract} this
- * @param {Number} x The current translation on the x axis
- * @param {Number} y The current translation on the y axis
- */
- /**
- * @event animationframe
- * Fires for each animation frame
- * @param {Ext.util.translatable.Abstract} this
- * @param {Number} x The new translation on the x axis
- * @param {Number} y The new translation on the y axis
- */
- /**
- * @event animationend
- * Fires whenever the animation is ended
- * @param {Ext.util.translatable.Abstract} this
- * @param {Number} x The current translation on the x axis
- * @param {Number} y The current translation on the y axis
- */
- x: 0,
- y: 0,
- activeEasingX: null,
- activeEasingY: null,
- isAnimating: false,
- isTranslatable: true,
- constructor: function(config) {
- this.doAnimationFrame = Ext.Function.bind(this.doAnimationFrame, this);
- this.initConfig(config);
- },
- factoryEasing: function(easing) {
- return Ext.factory(easing, Ext.fx.easing.Linear, null, 'easing');
- },
- applyEasing: function(easing) {
- if (!this.getEasingX()) {
- this.setEasingX(this.factoryEasing(easing));
- }
- if (!this.getEasingY()) {
- this.setEasingY(this.factoryEasing(easing));
- }
- },
- applyEasingX: function(easing) {
- return this.factoryEasing(easing);
- },
- applyEasingY: function(easing) {
- return this.factoryEasing(easing);
- },
- updateFps: function(fps) {
- this.animationInterval = 1000 / fps;
- },
- doTranslate: Ext.emptyFn,
- translate: function(x, y, animation) {
- if (animation) {
- return this.translateAnimated(x, y, animation);
- }
- if (this.isAnimating) {
- this.stopAnimation();
- }
- if (!isNaN(x) && typeof x == 'number') {
- this.x = x;
- }
- if (!isNaN(y) && typeof y == 'number') {
- this.y = y;
- }
- this.doTranslate(x, y);
- },
- translateAxis: function(axis, value, animation) {
- var x, y;
- if (axis == 'x') {
- x = value;
- }
- else {
- y = value;
- }
- return this.translate(x, y, animation);
- },
- animate: function(easingX, easingY) {
- this.activeEasingX = easingX;
- this.activeEasingY = easingY;
- this.isAnimating = true;
- this.lastX = null;
- this.lastY = null;
- this.animationFrameId = requestAnimationFrame(this.doAnimationFrame);
- this.fireEvent('animationstart', this, this.x, this.y);
- return this;
- },
- translateAnimated: function(x, y, animation) {
- if (!Ext.isObject(animation)) {
- animation = {};
- }
- if (this.isAnimating) {
- this.stopAnimation();
- }
- var now = Ext.Date.now(),
- easing = animation.easing,
- easingX = (typeof x == 'number') ? (animation.easingX || easing || this.getEasingX() || true) : null,
- easingY = (typeof y == 'number') ? (animation.easingY || easing || this.getEasingY() || true) : null;
- if (easingX) {
- easingX = this.factoryEasing(easingX);
- easingX.setStartTime(now);
- easingX.setStartValue(this.x);
- easingX.setEndValue(x);
- if ('duration' in animation) {
- easingX.setDuration(animation.duration);
- }
- }
- if (easingY) {
- easingY = this.factoryEasing(easingY);
- easingY.setStartTime(now);
- easingY.setStartValue(this.y);
- easingY.setEndValue(y);
- if ('duration' in animation) {
- easingY.setDuration(animation.duration);
- }
- }
- return this.animate(easingX, easingY);
- },
- doAnimationFrame: function() {
- var me = this,
- easingX = me.activeEasingX,
- easingY = me.activeEasingY,
- now = Date.now(),
- x, y;
- this.animationFrameId = requestAnimationFrame(this.doAnimationFrame);
- if (!me.isAnimating) {
- return;
- }
- me.lastRun = now;
- if (easingX === null && easingY === null) {
- me.stopAnimation();
- return;
- }
- if (easingX !== null) {
- me.x = x = Math.round(easingX.getValue());
- if (easingX.isEnded) {
- me.activeEasingX = null;
- me.fireEvent('axisanimationend', me, 'x', x);
- }
- }
- else {
- x = me.x;
- }
- if (easingY !== null) {
- me.y = y = Math.round(easingY.getValue());
- if (easingY.isEnded) {
- me.activeEasingY = null;
- me.fireEvent('axisanimationend', me, 'y', y);
- }
- }
- else {
- y = me.y;
- }
- if (me.lastX !== x || me.lastY !== y) {
- me.doTranslate(x, y);
- me.lastX = x;
- me.lastY = y;
- }
- me.fireEvent('animationframe', me, x, y);
- },
- stopAnimation: function() {
- if (!this.isAnimating) {
- return;
- }
- this.activeEasingX = null;
- this.activeEasingY = null;
- this.isAnimating = false;
- cancelAnimationFrame(this.animationFrameId);
- this.fireEvent('animationend', this, this.x, this.y);
- },
- refresh: function() {
- this.translate(this.x, this.y);
- },
- destroy: function() {
- if (this.isAnimating) {
- this.stopAnimation();
- }
- this.callParent(arguments);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.util.translatable.Dom', {
- extend: 'Ext.util.translatable.Abstract',
- config: {
- element: null
- },
- applyElement: function(element) {
- if (!element) {
- return;
- }
- return Ext.get(element);
- },
- updateElement: function() {
- this.refresh();
- }
- });
- /**
- * @private
- *
- * CSS Transform implementation
- */
- Ext.define('Ext.util.translatable.CssTransform', {
- extend: 'Ext.util.translatable.Dom',
- doTranslate: function() {
- this.getElement().dom.style.webkitTransform = 'translate3d(' + this.x + 'px, ' + this.y + 'px, 0px)';
- },
- destroy: function() {
- var element = this.getElement();
- if (element && !element.isDestroyed) {
- element.dom.style.webkitTransform = null;
- }
- this.callSuper();
- }
- });
- /**
- * @private
- *
- * Scroll position implementation
- */
- Ext.define('Ext.util.translatable.ScrollPosition', {
- extend: 'Ext.util.translatable.Dom',
- wrapperWidth: 0,
- wrapperHeight: 0,
- config: {
- useWrapper: true
- },
- getWrapper: function() {
- var wrapper = this.wrapper,
- element = this.getElement(),
- container;
- if (!wrapper) {
- container = element.getParent();
- if (!container) {
- return null;
- }
- if (this.getUseWrapper()) {
- wrapper = element.wrap();
- }
- else {
- wrapper = container;
- }
- element.addCls('x-translatable');
- wrapper.addCls('x-translatable-container');
- this.wrapper = wrapper;
- wrapper.on('resize', 'onWrapperResize', this);
- wrapper.on('painted', 'refresh', this);
- this.refresh();
- }
- return wrapper;
- },
- doTranslate: function(x, y) {
- var wrapper = this.getWrapper(),
- dom;
- if (wrapper) {
- dom = wrapper.dom;
- if (typeof x == 'number') {
- dom.scrollLeft = this.wrapperWidth - x;
- }
- if (typeof y == 'number') {
- dom.scrollTop = this.wrapperHeight - y;
- }
- }
- },
- onWrapperResize: function(wrapper, info) {
- this.wrapperWidth = info.width;
- this.wrapperHeight = info.height;
- this.refresh();
- },
- destroy: function() {
- var element = this.getElement(),
- wrapper = this.wrapper;
- if (wrapper) {
- if (!element.isDestroyed) {
- if (this.getUseWrapper()) {
- wrapper.doReplaceWith(element);
- }
- element.removeCls('x-translatable');
- }
- wrapper.removeCls('x-translatable-container');
- wrapper.un('resize', 'onWrapperResize', this);
- wrapper.un('painted', 'refresh', this);
- delete this.wrapper;
- delete this._element;
- }
- this.callSuper();
- }
- });
- /**
- * The utility class to abstract different implementations to have the best performance when applying 2D translation
- * on any DOM element.
- *
- * @private
- */
- Ext.define('Ext.util.Translatable', {
- requires: [
- 'Ext.util.translatable.CssTransform',
- 'Ext.util.translatable.ScrollPosition'
- ],
- constructor: function(config) {
- var namespace = Ext.util.translatable,
- CssTransform = namespace.CssTransform,
- ScrollPosition = namespace.ScrollPosition,
- classReference;
- if (typeof config == 'object' && 'translationMethod' in config) {
- if (config.translationMethod === 'scrollposition') {
- classReference = ScrollPosition;
- }
- else if (config.translationMethod === 'csstransform') {
- classReference = CssTransform;
- }
- }
- if (!classReference) {
- if (Ext.os.is.Android2 || Ext.browser.is.ChromeMobile) {
- classReference = ScrollPosition;
- }
- else {
- classReference = CssTransform;
- }
- }
- return new classReference(config);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.behavior.Translatable', {
- extend: 'Ext.behavior.Behavior',
- requires: [
- 'Ext.util.Translatable'
- ],
- setConfig: function(config) {
- var translatable = this.translatable,
- component = this.component;
- if (config) {
- if (!translatable) {
- this.translatable = translatable = new Ext.util.Translatable(config);
- translatable.setElement(component.renderElement);
- translatable.on('destroy', 'onTranslatableDestroy', this);
- }
- else if (Ext.isObject(config)) {
- translatable.setConfig(config);
- }
- }
- else if (translatable) {
- translatable.destroy();
- }
- return this;
- },
- getTranslatable: function() {
- return this.translatable;
- },
- onTranslatableDestroy: function() {
- delete this.translatable;
- },
- onComponentDestroy: function() {
- var translatable = this.translatable;
- if (translatable) {
- translatable.destroy();
- }
- }
- });
- /**
- * A core util class to bring Draggable behavior to any DOM element
- */
- Ext.define('Ext.util.Draggable', {
- isDraggable: true,
- mixins: [
- 'Ext.mixin.Observable'
- ],
- requires: [
- 'Ext.util.Translatable'
- ],
- /**
- * @event dragstart
- * @preventable initDragStart
- * Fires whenever the component starts to be dragged
- * @param {Ext.util.Draggable} this
- * @param {Ext.event.Event} e the event object
- * @param {Number} offsetX The current offset value on the x axis
- * @param {Number} offsetY The current offset value on the y axis
- */
- /**
- * @event drag
- * Fires whenever the component is dragged
- * @param {Ext.util.Draggable} this
- * @param {Ext.event.Event} e the event object
- * @param {Number} offsetX The new offset value on the x axis
- * @param {Number} offsetY The new offset value on the y axis
- */
- /**
- * @event dragend
- * Fires whenever the component is dragged
- * @param {Ext.util.Draggable} this
- * @param {Ext.event.Event} e the event object
- * @param {Number} offsetX The current offset value on the x axis
- * @param {Number} offsetY The current offset value on the y axis
- */
- config: {
- cls: Ext.baseCSSPrefix + 'draggable',
- draggingCls: Ext.baseCSSPrefix + 'dragging',
- element: null,
- constraint: 'container',
- disabled: null,
- /**
- * @cfg {String} direction
- * Possible values: 'vertical', 'horizontal', or 'both'
- * @accessor
- */
- direction: 'both',
- /**
- * @cfg {Object/Number} initialOffset
- * The initial draggable offset. When specified as Number,
- * both x and y will be set to that value.
- */
- initialOffset: {
- x: 0,
- y: 0
- },
- translatable: {}
- },
- DIRECTION_BOTH: 'both',
- DIRECTION_VERTICAL: 'vertical',
- DIRECTION_HORIZONTAL: 'horizontal',
- defaultConstraint: {
- min: { x: -Infinity, y: -Infinity },
- max: { x: Infinity, y: Infinity }
- },
- containerWidth: 0,
- containerHeight: 0,
- width: 0,
- height: 0,
- /**
- * Creates new Draggable.
- * @param {Object} config The configuration object for this Draggable.
- */
- constructor: function(config) {
- var element;
- this.extraConstraint = {};
- this.initialConfig = config;
- this.offset = {
- x: 0,
- y: 0
- };
- this.listeners = {
- dragstart: 'onDragStart',
- drag : 'onDrag',
- dragend : 'onDragEnd',
- resize : 'onElementResize',
- scope: this
- };
- if (config && config.element) {
- element = config.element;
- delete config.element;
- this.setElement(element);
- }
- return this;
- },
- applyElement: function(element) {
- if (!element) {
- return;
- }
- return Ext.get(element);
- },
- updateElement: function(element) {
- element.on(this.listeners);
- this.initConfig(this.initialConfig);
- },
- updateInitialOffset: function(initialOffset) {
- if (typeof initialOffset == 'number') {
- initialOffset = {
- x: initialOffset,
- y: initialOffset
- };
- }
- var offset = this.offset,
- x, y;
- offset.x = x = initialOffset.x;
- offset.y = y = initialOffset.y;
- this.getTranslatable().translate(x, y);
- },
- updateCls: function(cls) {
- this.getElement().addCls(cls);
- },
- applyTranslatable: function(translatable, currentInstance) {
- translatable = Ext.factory(translatable, Ext.util.Translatable, currentInstance);
- translatable.setElement(this.getElement());
- return translatable;
- },
- setExtraConstraint: function(constraint) {
- this.extraConstraint = constraint || {};
- this.refreshConstraint();
- return this;
- },
- addExtraConstraint: function(constraint) {
- Ext.merge(this.extraConstraint, constraint);
- this.refreshConstraint();
- return this;
- },
- applyConstraint: function(newConstraint) {
- this.currentConstraint = newConstraint;
- if (!newConstraint) {
- newConstraint = this.defaultConstraint;
- }
- if (newConstraint === 'container') {
- return Ext.merge(this.getContainerConstraint(), this.extraConstraint);
- }
- return Ext.merge({}, this.extraConstraint, newConstraint);
- },
- updateConstraint: function() {
- this.refreshOffset();
- },
- getContainerConstraint: function() {
- var container = this.getContainer(),
- element = this.getElement();
- if (!container || !element.dom) {
- return this.defaultConstraint;
- }
- return {
- min: { x: 0, y: 0 },
- max: { x: this.containerWidth - this.width, y: this.containerHeight - this.height }
- };
- },
- getContainer: function() {
- var container = this.container;
- if (!container) {
- container = this.getElement().getParent();
- if (container) {
- this.container = container;
- container.on({
- resize: 'onContainerResize',
- destroy: 'onContainerDestroy',
- scope: this
- });
- }
- }
- return container;
- },
- onElementResize: function(element, info) {
- this.width = info.width;
- this.height = info.height;
- this.refresh();
- },
- onContainerResize: function(container, info) {
- this.containerWidth = info.width;
- this.containerHeight = info.height;
- this.refresh();
- },
- onContainerDestroy: function() {
- delete this.container;
- delete this.containerSizeMonitor;
- },
- detachListeners: function() {
- this.getElement().un(this.listeners);
- },
- isAxisEnabled: function(axis) {
- var direction = this.getDirection();
- if (axis === 'x') {
- return (direction === this.DIRECTION_BOTH || direction === this.DIRECTION_HORIZONTAL);
- }
- return (direction === this.DIRECTION_BOTH || direction === this.DIRECTION_VERTICAL);
- },
- onDragStart: function(e) {
- if (this.getDisabled()) {
- return false;
- }
- var offset = this.offset;
- this.fireAction('dragstart', [this, e, offset.x, offset.y], this.initDragStart);
- },
- initDragStart: function(me, e, offsetX, offsetY) {
- this.dragStartOffset = {
- x: offsetX,
- y: offsetY
- };
- this.isDragging = true;
- this.getElement().addCls(this.getDraggingCls());
- },
- onDrag: function(e) {
- if (!this.isDragging) {
- return;
- }
- var startOffset = this.dragStartOffset;
- this.fireAction('drag', [this, e, startOffset.x + e.deltaX, startOffset.y + e.deltaY], this.doDrag);
- },
- doDrag: function(me, e, offsetX, offsetY) {
- me.setOffset(offsetX, offsetY);
- },
- onDragEnd: function(e) {
- if (!this.isDragging) {
- return;
- }
- this.onDrag(e);
- this.isDragging = false;
- this.getElement().removeCls(this.getDraggingCls());
- this.fireEvent('dragend', this, e, this.offset.x, this.offset.y);
- },
- setOffset: function(x, y, animation) {
- var currentOffset = this.offset,
- constraint = this.getConstraint(),
- minOffset = constraint.min,
- maxOffset = constraint.max,
- min = Math.min,
- max = Math.max;
- if (this.isAxisEnabled('x') && typeof x == 'number') {
- x = min(max(x, minOffset.x), maxOffset.x);
- }
- else {
- x = currentOffset.x;
- }
- if (this.isAxisEnabled('y') && typeof y == 'number') {
- y = min(max(y, minOffset.y), maxOffset.y);
- }
- else {
- y = currentOffset.y;
- }
- currentOffset.x = x;
- currentOffset.y = y;
- this.getTranslatable().translate(x, y, animation);
- },
- getOffset: function() {
- return this.offset;
- },
- refreshConstraint: function() {
- this.setConstraint(this.currentConstraint);
- },
- refreshOffset: function() {
- var offset = this.offset;
- this.setOffset(offset.x, offset.y);
- },
- refresh: function() {
- this.refreshConstraint();
- this.getTranslatable().refresh();
- this.refreshOffset();
- },
- /**
- * Enable the Draggable.
- * @return {Ext.util.Draggable} This Draggable instance
- */
- enable: function() {
- return this.setDisabled(false);
- },
- /**
- * Disable the Draggable.
- * @return {Ext.util.Draggable} This Draggable instance
- */
- disable: function() {
- return this.setDisabled(true);
- },
- destroy: function() {
- var translatable = this.getTranslatable();
- var element = this.getElement();
- if (element && !element.isDestroyed) {
- element.removeCls(this.getCls());
- }
- this.detachListeners();
- if (translatable) {
- translatable.destroy();
- }
- }
- }, function() {
- });
- /**
- * @private
- */
- Ext.define('Ext.behavior.Draggable', {
- extend: 'Ext.behavior.Behavior',
- requires: [
- 'Ext.util.Draggable'
- ],
- setConfig: function(config) {
- var draggable = this.draggable,
- component = this.component;
- if (config) {
- if (!draggable) {
- component.setTranslatable(true);
- this.draggable = draggable = new Ext.util.Draggable(config);
- draggable.setTranslatable(component.getTranslatable());
- draggable.setElement(component.renderElement);
- draggable.on('destroy', 'onDraggableDestroy', this);
- component.on(this.listeners);
- }
- else if (Ext.isObject(config)) {
- draggable.setConfig(config);
- }
- }
- else if (draggable) {
- draggable.destroy();
- }
- return this;
- },
- getDraggable: function() {
- return this.draggable;
- },
- onDraggableDestroy: function() {
- delete this.draggable;
- },
- onComponentDestroy: function() {
- var draggable = this.draggable;
- if (draggable) {
- draggable.destroy();
- }
- }
- });
- /**
- * A Traversable mixin.
- * @private
- */
- Ext.define('Ext.mixin.Traversable', {
- extend: 'Ext.mixin.Mixin',
- mixinConfig: {
- id: 'traversable'
- },
- setParent: function(parent) {
- this.parent = parent;
- return this;
- },
- /**
- * @member Ext.Component
- * Returns `true` if this component has a parent.
- * @return {Boolean} `true` if this component has a parent.
- */
- hasParent: function() {
- return Boolean(this.parent);
- },
- /**
- * @member Ext.Component
- * Returns the parent of this component, if it has one.
- * @return {Ext.Component} The parent of this component.
- */
- getParent: function() {
- return this.parent;
- },
- getAncestors: function() {
- var ancestors = [],
- parent = this.getParent();
- while (parent) {
- ancestors.push(parent);
- parent = parent.getParent();
- }
- return ancestors;
- },
- getAncestorIds: function() {
- var ancestorIds = [],
- parent = this.getParent();
- while (parent) {
- ancestorIds.push(parent.getId());
- parent = parent.getParent();
- }
- return ancestorIds;
- }
- });
- (function(clsPrefix) {
- /**
- * Most of the visual classes you interact with in Sencha Touch are Components. Every Component in Sencha Touch is a
- * subclass of Ext.Component, which means they can all:
- *
- * * Render themselves onto the page using a template
- * * Show and hide themselves at any time
- * * Center themselves on the screen
- * * Enable and disable themselves
- *
- * They can also do a few more advanced things:
- *
- * * Float above other components (windows, message boxes and overlays)
- * * Change size and position on the screen with animation
- * * Dock other Components inside themselves (useful for toolbars)
- * * Align to other components, allow themselves to be dragged around, make their content scrollable & more
- *
- * ## Available Components
- *
- * There are many components available in Sencha Touch, separated into 4 main groups:
- *
- * ### Navigation components
- * * {@link Ext.Toolbar}
- * * {@link Ext.Button}
- * * {@link Ext.TitleBar}
- * * {@link Ext.SegmentedButton}
- * * {@link Ext.Title}
- * * {@link Ext.Spacer}
- *
- * ### Store-bound components
- * * {@link Ext.dataview.DataView}
- * * {@link Ext.Carousel}
- * * {@link Ext.List}
- * * {@link Ext.NestedList}
- *
- * ### Form components
- * * {@link Ext.form.Panel}
- * * {@link Ext.form.FieldSet}
- * * {@link Ext.field.Checkbox}
- * * {@link Ext.field.Hidden}
- * * {@link Ext.field.Slider}
- * * {@link Ext.field.Text}
- * * {@link Ext.picker.Picker}
- * * {@link Ext.picker.Date}
- *
- * ### General components
- * * {@link Ext.Panel}
- * * {@link Ext.tab.Panel}
- * * {@link Ext.Viewport Ext.Viewport}
- * * {@link Ext.Img}
- * * {@link Ext.Map}
- * * {@link Ext.Audio}
- * * {@link Ext.Video}
- * * {@link Ext.Sheet}
- * * {@link Ext.ActionSheet}
- * * {@link Ext.MessageBox}
- *
- *
- * ## Instantiating Components
- *
- * Components are created the same way as all other classes in Sencha Touch - using Ext.create. Here's how we can
- * create a Text field:
- *
- * var panel = Ext.create('Ext.Panel', {
- * html: 'This is my panel'
- * });
- *
- * This will create a {@link Ext.Panel Panel} instance, configured with some basic HTML content. A Panel is just a
- * simple Component that can render HTML and also contain other items. In this case we've created a Panel instance but
- * it won't show up on the screen yet because items are not rendered immediately after being instantiated. This allows
- * us to create some components and move them around before rendering and laying them out, which is a good deal faster
- * than moving them after rendering.
- *
- * To show this panel on the screen now we can simply add it to the global Viewport:
- *
- * Ext.Viewport.add(panel);
- *
- * Panels are also Containers, which means they can contain other Components, arranged by a layout. Let's revisit the
- * above example now, this time creating a panel with two child Components and a hbox layout:
- *
- * @example
- * var panel = Ext.create('Ext.Panel', {
- * layout: 'hbox',
- *
- * items: [
- * {
- * xtype: 'panel',
- * flex: 1,
- * html: 'Left Panel, 1/3rd of total size',
- * style: 'background-color: #5E99CC;'
- * },
- * {
- * xtype: 'panel',
- * flex: 2,
- * html: 'Right Panel, 2/3rds of total size',
- * style: 'background-color: #759E60;'
- * }
- * ]
- * });
- *
- * Ext.Viewport.add(panel);
- *
- * This time we created 3 Panels - the first one is created just as before but the inner two are declared inline using
- * an xtype. Xtype is a convenient way of creating Components without having to go through the process of using
- * Ext.create and specifying the full class name, instead you can just provide the xtype for the class inside an object
- * and the framework will create the components for you.
- *
- * We also specified a layout for the top level panel - in this case hbox, which splits the horizontal width of the
- * parent panel based on the 'flex' of each child. For example, if the parent Panel above is 300px wide then the first
- * child will be flexed to 100px wide and the second to 200px because the first one was given `flex: 1` and the second
- * `flex: 2`.
- *
- * ## Using xtype
- *
- * xtype is an easy way to create Components without using the full class name. This is especially useful when creating
- * a {@link Ext.Container Container} that contains child Components. An xtype is simply a shorthand way of specifying a
- * Component - for example you can use `xtype: 'panel'` instead of typing out Ext.panel.Panel.
- *
- * Sample usage:
- *
- * @example miniphone
- * Ext.create('Ext.Container', {
- * fullscreen: true,
- * layout: 'fit',
- *
- * items: [
- * {
- * xtype: 'panel',
- * html: 'This panel is created by xtype'
- * },
- * {
- * xtype: 'toolbar',
- * title: 'So is the toolbar',
- * docked: 'top'
- * }
- * ]
- * });
- *
- *
- * ### Common xtypes
- *
- * These are the xtypes that are most commonly used. For an exhaustive list please see the
- * [Components Guide](#!/guide/components).
- *
- * <pre>
- xtype Class
- ----------------- ---------------------
- actionsheet Ext.ActionSheet
- audio Ext.Audio
- button Ext.Button
- image Ext.Img
- label Ext.Label
- loadmask Ext.LoadMask
- map Ext.Map
- panel Ext.Panel
- segmentedbutton Ext.SegmentedButton
- sheet Ext.Sheet
- spacer Ext.Spacer
- titlebar Ext.TitleBar
- toolbar Ext.Toolbar
- video Ext.Video
- carousel Ext.carousel.Carousel
- navigationview Ext.navigation.View
- datepicker Ext.picker.Date
- picker Ext.picker.Picker
- slider Ext.slider.Slider
- thumb Ext.slider.Thumb
- tabpanel Ext.tab.Panel
- viewport Ext.viewport.Default
- DataView Components
- ---------------------------------------------
- dataview Ext.dataview.DataView
- list Ext.dataview.List
- nestedlist Ext.dataview.NestedList
- Form Components
- ---------------------------------------------
- checkboxfield Ext.field.Checkbox
- datepickerfield Ext.field.DatePicker
- emailfield Ext.field.Email
- hiddenfield Ext.field.Hidden
- numberfield Ext.field.Number
- passwordfield Ext.field.Password
- radiofield Ext.field.Radio
- searchfield Ext.field.Search
- selectfield Ext.field.Select
- sliderfield Ext.field.Slider
- spinnerfield Ext.field.Spinner
- textfield Ext.field.Text
- textareafield Ext.field.TextArea
- togglefield Ext.field.Toggle
- urlfield Ext.field.Url
- fieldset Ext.form.FieldSet
- formpanel Ext.form.Panel
- * </pre>
- *
- * ## Configuring Components
- *
- * Whenever you create a new Component you can pass in configuration options. All of the configurations for a given
- * Component are listed in the "Config options" section of its class docs page. You can pass in any number of
- * configuration options when you instantiate the Component, and modify any of them at any point later. For example, we
- * can easily modify the {@link Ext.Panel#html html content} of a Panel after creating it:
- *
- * @example miniphone
- * // we can configure the HTML when we instantiate the Component
- * var panel = Ext.create('Ext.Panel', {
- * fullscreen: true,
- * html: 'This is a Panel'
- * });
- *
- * // we can update the HTML later using the setHtml method:
- * panel.setHtml('Some new HTML');
- *
- * // we can retrieve the current HTML using the getHtml method:
- * Ext.Msg.alert(panel.getHtml()); // displays "Some new HTML"
- *
- * Every config has a getter method and a setter method - these are automatically generated and always follow the same
- * pattern. For example, a config called `html` will receive `getHtml` and `setHtml` methods, a config called `defaultType`
- * will receive `getDefaultType` and `setDefaultType` methods, and so on.
- *
- * ## Further Reading
- *
- * See the [Component & Container Guide](#!/guide/components) for more information, and check out the
- * {@link Ext.Container} class docs also.
- *
- * @aside guide components
- * @aside guide events
- *
- */
- Ext.define('Ext.Component', {
- extend: 'Ext.AbstractComponent',
- alternateClassName: 'Ext.lib.Component',
- mixins: ['Ext.mixin.Traversable'],
- requires: [
- 'Ext.ComponentManager',
- 'Ext.XTemplate',
- 'Ext.dom.Element',
- 'Ext.behavior.Translatable',
- 'Ext.behavior.Draggable'
- ],
- /**
- * @cfg {String} xtype
- * The `xtype` configuration option can be used to optimize Component creation and rendering. It serves as a
- * shortcut to the full component name. For example, the component `Ext.button.Button` has an xtype of `button`.
- *
- * You can define your own xtype on a custom {@link Ext.Component component} by specifying the
- * {@link Ext.Class#alias alias} config option with a prefix of `widget`. For example:
- *
- * Ext.define('PressMeButton', {
- * extend: 'Ext.button.Button',
- * alias: 'widget.pressmebutton',
- * text: 'Press Me'
- * });
- *
- * Any Component can be created implicitly as an object config with an xtype specified, allowing it to be
- * declared and passed into the rendering pipeline without actually being instantiated as an object. Not only is
- * rendering deferred, but the actual creation of the object itself is also deferred, saving memory and resources
- * until they are actually needed. In complex, nested layouts containing many Components, this can make a
- * noticeable improvement in performance.
- *
- * // Explicit creation of contained Components:
- * var panel = new Ext.Panel({
- * // ...
- * items: [
- * Ext.create('Ext.button.Button', {
- * text: 'OK'
- * })
- * ]
- * });
- *
- * // Implicit creation using xtype:
- * var panel = new Ext.Panel({
- * // ...
- * items: [{
- * xtype: 'button',
- * text: 'OK'
- * }]
- * });
- *
- * In the first example, the button will always be created immediately during the panel's initialization. With
- * many added Components, this approach could potentially slow the rendering of the page. In the second example,
- * the button will not be created or rendered until the panel is actually displayed in the browser. If the panel
- * is never displayed (for example, if it is a tab that remains hidden) then the button will never be created and
- * will never consume any resources whatsoever.
- */
- xtype: 'component',
- observableType: 'component',
- cachedConfig: {
- /**
- * @cfg {String} baseCls
- * The base CSS class to apply to this component's element. This will also be prepended to
- * other elements within this component. To add specific styling for sub-classes, use the {@link #cls} config.
- * @accessor
- */
- baseCls: null,
- /**
- * @cfg {String/String[]} cls The CSS class to add to this component's element, in addition to the {@link #baseCls}
- * @accessor
- */
- cls: null,
- /**
- * @cfg {String} [floatingCls="x-floating"] The CSS class to add to this component when it is floatable.
- * @accessor
- */
- floatingCls: clsPrefix + 'floating',
- /**
- * @cfg {String} [hiddenCls="x-item-hidden"] The CSS class to add to the component when it is hidden
- * @accessor
- */
- hiddenCls: clsPrefix + 'item-hidden',
- /**
- * @cfg {String} ui The ui to be used on this Component
- */
- ui: null,
- /**
- * @cfg {Number/String} margin The margin to use on this Component. Can be specified as a number (in which case
- * all edges get the same margin) or a CSS string like '5 10 10 10'
- * @accessor
- */
- margin: null,
- /**
- * @cfg {Number/String} padding The padding to use on this Component. Can be specified as a number (in which
- * case all edges get the same padding) or a CSS string like '5 10 10 10'
- * @accessor
- */
- padding: null,
- /**
- * @cfg {Number/String} border The border width to use on this Component. Can be specified as a number (in which
- * case all edges get the same border width) or a CSS string like '5 10 10 10'.
- *
- * Please note that this will not add
- * a `border-color` or `border-style` CSS property to the component; you must do that manually using either CSS or
- * the {@link #style} configuration.
- *
- * ## Using {@link #style}:
- *
- * Ext.Viewport.add({
- * centered: true,
- * width: 100,
- * height: 100,
- *
- * border: 3,
- * style: 'border-color: blue; border-style: solid;'
- * // ...
- * });
- *
- * ## Using CSS:
- *
- * Ext.Viewport.add({
- * centered: true,
- * width: 100,
- * height: 100,
- *
- * border: 3,
- * cls: 'my-component'
- * // ...
- * });
- *
- * And your CSS file:
- *
- * .my-component {
- * border-color: red;
- * border-style: solid;
- * }
- *
- * @accessor
- */
- border: null,
- /**
- * @cfg {String} [styleHtmlCls="x-html"]
- * The class that is added to the content target when you set `styleHtmlContent` to `true`.
- * @accessor
- */
- styleHtmlCls: clsPrefix + 'html',
- /**
- * @cfg {Boolean} [styleHtmlContent=false]
- * `true` to automatically style the HTML inside the content target of this component (body for panels).
- * @accessor
- */
- styleHtmlContent: null
- },
- eventedConfig: {
- /**
- * @cfg {Number} flex
- * The flex of this item *if* this item item is inside a {@link Ext.layout.HBox} or {@link Ext.layout.VBox}
- * layout.
- *
- * You can also update the flex of a component dynamically using the {@link Ext.layout.FlexBox#setItemFlex}
- * method.
- */
- flex: null,
- /**
- * @cfg {Number/String} left
- * The absolute left position of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
- * Explicitly setting this value will make this Component become 'floating', which means its layout will no
- * longer be affected by the Container that it resides in.
- * @accessor
- * @evented
- */
- left: null,
- /**
- * @cfg {Number/String} top
- * The absolute top position of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
- * Explicitly setting this value will make this Component become 'floating', which means its layout will no
- * longer be affected by the Container that it resides in.
- * @accessor
- * @evented
- */
- top: null,
- /**
- * @cfg {Number/String} right
- * The absolute right position of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
- * Explicitly setting this value will make this Component become 'floating', which means its layout will no
- * longer be affected by the Container that it resides in.
- * @accessor
- * @evented
- */
- right: null,
- /**
- * @cfg {Number/String} bottom
- * The absolute bottom position of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
- * Explicitly setting this value will make this Component become 'floating', which means its layout will no
- * longer be affected by the Container that it resides in.
- * @accessor
- * @evented
- */
- bottom: null,
- /**
- * @cfg {Number/String} width
- * The width of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
- * By default, if this is not explicitly set, this Component's element will simply have its own natural size.
- * @accessor
- * @evented
- */
- width: null,
- /**
- * @cfg {Number/String} height
- * The height of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
- * By default, if this is not explicitly set, this Component's element will simply have its own natural size.
- * @accessor
- * @evented
- */
- height: null,
- /**
- * @cfg {Number/String} minWidth
- * The minimum width of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
- * @accessor
- * @evented
- */
- minWidth: null,
- /**
- * @cfg {Number/String} minHeight
- * The minimum height of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
- * @accessor
- * @evented
- */
- minHeight: null,
- /**
- * @cfg {Number/String} maxWidth
- * The maximum width of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
- * Note that this config will not apply if the Component is 'floating' (absolutely positioned or centered)
- * @accessor
- * @evented
- */
- maxWidth: null,
- /**
- * @cfg {Number/String} maxHeight
- * The maximum height of this Component; must be a valid CSS length value, e.g: `300`, `100px`, `30%`, etc.
- * Note that this config will not apply if the Component is 'floating' (absolutely positioned or centered)
- * @accessor
- * @evented
- */
- maxHeight: null,
- /**
- * @cfg {String} docked
- * The dock position of this component in its container. Can be `left`, `top`, `right` or `bottom`.
- *
- * __Notes__
- *
- * You must use a HTML5 doctype for {@link #docked} `bottom` to work. To do this, simply add the following code to the HTML file:
- *
- * <!doctype html>
- *
- * So your index.html file should look a little like this:
- *
- * <!doctype html>
- * <html>
- * <head>
- * <title>MY application title</title>
- * ...
- *
- * @accessor
- * @evented
- */
- docked: null,
- /**
- * @cfg {Boolean} centered
- * Whether or not this Component is absolutely centered inside its Container
- * @accessor
- * @evented
- */
- centered: null,
- /**
- * @cfg {Boolean} hidden
- * Whether or not this Component is hidden (its CSS `display` property is set to `none`)
- * @accessor
- * @evented
- */
- hidden: null,
- /**
- * @cfg {Boolean} disabled
- * Whether or not this component is disabled
- * @accessor
- * @evented
- */
- disabled: null
- },
- config: {
- /**
- * @cfg {String/Object} style Optional CSS styles that will be rendered into an inline style attribute when the
- * Component is rendered.
- *
- * You can pass either a string syntax:
- *
- * style: 'background:red'
- *
- * Or by using an object:
- *
- * style: {
- * background: 'red'
- * }
- *
- * When using the object syntax, you can define CSS Properties by using a string:
- *
- * style: {
- * 'border-left': '1px solid red'
- * }
- *
- * Although the object syntax is much easier to read, we suggest you to use the string syntax for better performance.
- *
- * @accessor
- */
- style: null,
- /**
- * @cfg {String/Ext.Element/HTMLElement} html Optional HTML content to render inside this Component, or a reference
- * to an existing element on the page.
- * @accessor
- */
- html: null,
- /**
- * @cfg {Object} draggable Configuration options to make this Component draggable
- * @accessor
- */
- draggable: null,
- /**
- * @cfg {Object} translatable
- * @private
- * @accessor
- */
- translatable: null,
- /**
- * @cfg {Ext.Element} renderTo Optional element to render this Component to. Usually this is not needed because
- * a Component is normally full screen or automatically rendered inside another {@link Ext.Container Container}
- * @accessor
- */
- renderTo: null,
- /**
- * @cfg {Number} zIndex The z-index to give this Component when it is rendered
- * @accessor
- */
- zIndex: null,
- /**
- * @cfg {String/String[]/Ext.Template[]/Ext.XTemplate[]} tpl
- * A {@link String}, {@link Ext.Template}, {@link Ext.XTemplate} or an {@link Array} of strings to form an {@link Ext.XTemplate}.
- * Used in conjunction with the {@link #data} and {@link #tplWriteMode} configurations.
- *
- * __Note__
- * The {@link #data} configuration _must_ be set for any content to be shown in the component when using this configuration.
- * @accessor
- */
- tpl: null,
- /**
- * @cfg {String/Mixed} enterAnimation
- * Animation effect to apply when the Component is being shown. Typically you want to use an
- * inbound animation type such as 'fadeIn' or 'slideIn'.
- * @deprecated 2.0.0 Please use {@link #showAnimation} instead.
- * @accessor
- */
- enterAnimation: null,
- /**
- * @cfg {String/Mixed} exitAnimation
- * Animation effect to apply when the Component is being hidden.
- * @deprecated 2.0.0 Please use {@link #hideAnimation} instead. Typically you want to use an
- * outbound animation type such as 'fadeOut' or 'slideOut'.
- * @accessor
- */
- exitAnimation: null,
- /**
- * @cfg {String/Mixed} showAnimation
- * Animation effect to apply when the Component is being shown. Typically you want to use an
- * inbound animation type such as 'fadeIn' or 'slideIn'.
- * @accessor
- */
- showAnimation: null,
- /**
- * @cfg {String/Mixed} hideAnimation
- * Animation effect to apply when the Component is being hidden. Typically you want to use an
- * outbound animation type such as 'fadeOut' or 'slideOut'.
- * @accessor
- */
- hideAnimation: null,
- /**
- * @cfg {String} tplWriteMode The Ext.(X)Template method to use when
- * updating the content area of the Component.
- * Valid modes are:
- *
- * - append
- * - insertAfter
- * - insertBefore
- * - insertFirst
- * - overwrite
- * @accessor
- */
- tplWriteMode: 'overwrite',
- /**
- * @cfg {Mixed} data
- * The initial set of data to apply to the `{@link #tpl}` to
- * update the content area of the Component.
- * @accessor
- */
- data: null,
- /**
- * @cfg {String} [disabledCls="x-item-disabled"] The CSS class to add to the component when it is disabled
- * @accessor
- */
- disabledCls: clsPrefix + 'item-disabled',
- /**
- * @cfg {Ext.Element/HTMLElement/String} contentEl The configured element will automatically be
- * added as the content of this component. When you pass a string, we expect it to be an element id.
- * If the content element is hidden, we will automatically show it.
- * @accessor
- */
- contentEl: null,
- /**
- * @cfg {String} id
- * The **unique id of this component instance.**
- *
- * It should not be necessary to use this configuration except for singleton objects in your application. Components
- * created with an id may be accessed globally using {@link Ext#getCmp Ext.getCmp}.
- *
- * Instead of using assigned ids, use the {@link #itemId} config, and {@link Ext.ComponentQuery ComponentQuery}
- * which provides selector-based searching for Sencha Components analogous to DOM querying. The
- * {@link Ext.Container} class contains {@link Ext.Container#down shortcut methods} to query
- * its descendant Components by selector.
- *
- * Note that this id will also be used as the element id for the containing HTML element that is rendered to the
- * page for this component. This allows you to write id-based CSS rules to style the specific instance of this
- * component uniquely, and also to select sub-elements using this component's id as the parent.
- *
- * **Note**: to avoid complications imposed by a unique id also see `{@link #itemId}`.
- *
- * Defaults to an auto-assigned id.
- */
- /**
- * @cfg {String} itemId
- * An itemId can be used as an alternative way to get a reference to a component when no object reference is
- * available. Instead of using an `{@link #id}` with {@link Ext#getCmp}, use `itemId` with
- * {@link Ext.Container#getComponent} which will retrieve `itemId`'s or {@link #id}'s. Since `itemId`'s are an
- * index to the container's internal MixedCollection, the `itemId` is scoped locally to the container - avoiding
- * potential conflicts with {@link Ext.ComponentManager} which requires a **unique** `{@link #id}`.
- *
- * Also see {@link #id}, {@link Ext.Container#query}, {@link Ext.Container#down} and {@link Ext.Container#child}.
- *
- * @accessor
- */
- itemId: undefined,
- /**
- * @cfg {Ext.data.Model} record A model instance which updates the Component's html based on it's tpl. Similar to the data
- * configuration, but tied to to a record to make allow dynamic updates. This must be a model
- * instance and not a configuration of one.
- * @accessor
- */
- record: null,
- /**
- * @cfg {Object/Array} plugins
- * @accessor
- * An object or array of objects that will provide custom functionality for this component. The only
- * requirement for a valid plugin is that it contain an init method that accepts a reference of type Ext.Component.
- *
- * When a component is created, if any plugins are available, the component will call the init method on each
- * plugin, passing a reference to itself. Each plugin can then call methods or respond to events on the
- * component as needed to provide its functionality.
- *
- * For examples of plugins, see Ext.plugin.PullRefresh and Ext.plugin.ListPaging
- *
- * ## Example code
- *
- * A plugin by alias:
- *
- * Ext.create('Ext.dataview.List', {
- * config: {
- * plugins: 'listpaging',
- * itemTpl: '<div class="item">{title}</div>',
- * store: 'Items'
- * }
- * });
- *
- * Multiple plugins by alias:
- *
- * Ext.create('Ext.dataview.List', {
- * config: {
- * plugins: ['listpaging', 'pullrefresh'],
- * itemTpl: '<div class="item">{title}</div>',
- * store: 'Items'
- * }
- * });
- *
- * Single plugin by class name with config options:
- *
- * Ext.create('Ext.dataview.List', {
- * config: {
- * plugins: {
- * xclass: 'Ext.plugin.ListPaging', // Reference plugin by class
- * autoPaging: true
- * },
- *
- * itemTpl: '<div class="item">{title}</div>',
- * store: 'Items'
- * }
- * });
- *
- * Multiple plugins by class name with config options:
- *
- * Ext.create('Ext.dataview.List', {
- * config: {
- * plugins: [
- * {
- * xclass: 'Ext.plugin.PullRefresh',
- * pullRefreshText: 'Pull to refresh...'
- * },
- * {
- * xclass: 'Ext.plugin.ListPaging',
- * autoPaging: true
- * }
- * ],
- *
- * itemTpl: '<div class="item">{title}</div>',
- * store: 'Items'
- * }
- * });
- *
- */
- plugins: null
- },
- /**
- * @event show
- * Fires whenever the Component is shown
- * @param {Ext.Component} this The component instance
- */
- /**
- * @event hide
- * Fires whenever the Component is hidden
- * @param {Ext.Component} this The component instance
- */
- /**
- * @event fullscreen
- * Fires whenever a Component with the fullscreen config is instantiated
- * @param {Ext.Component} this The component instance
- */
- /**
- * @event floatingchange
- * Fires whenever there is a change in the floating status of a component
- * @param {Ext.Component} this The component instance
- * @param {Boolean} floating The component's new floating state
- */
- /**
- * @event beforeorientationchange
- * Fires before orientation changes.
- * @removed 2.0.0 This event is now only available `onBefore` the Viewport's {@link Ext.Viewport#orientationchange}
- */
- /**
- * @event orientationchange
- * Fires when orientation changes.
- * @removed 2.0.0 This event is now only available on the Viewport's {@link Ext.Viewport#orientationchange}
- */
- /**
- * @event initialize
- * Fires when the component has been initialized
- * @param {Ext.Component} this The component instance
- */
- /**
- * @event painted
- * @inheritdoc Ext.dom.Element#painted
- */
- /**
- * @event erased
- * Fires when the component is no longer displayed in the DOM. Listening to this event will
- * degrade performance not recommend for general use.
- * @param {Ext.Component} this The component instance
- */
- /**
- * @event resize
- * @inheritdoc Ext.dom.Element#resize
- */
- /**
- * @private
- */
- listenerOptionsRegex: /^(?:delegate|single|delay|buffer|args|prepend|element)$/,
- /**
- * @private
- */
- alignmentRegex: /^([a-z]+)-([a-z]+)(\?)?$/,
- /**
- * @private
- */
- isComponent: true,
- /**
- * @private
- */
- floating: false,
- /**
- * @private
- */
- rendered: false,
- /**
- * @private
- */
- isInner: true,
- /**
- * @readonly
- * @private
- */
- dockPositions: {
- top: true,
- right: true,
- bottom: true,
- left: true
- },
- innerElement: null,
- element: null,
- template: [],
- widthLayoutSized: false,
- heightLayoutSized: false,
- layoutStretched: false,
- sizeState: false,
- sizeFlags: 0x0,
- LAYOUT_WIDTH: 0x1,
- LAYOUT_HEIGHT: 0x2,
- LAYOUT_BOTH: 0x3,
- LAYOUT_STRETCHED: 0x4,
- /**
- * Creates new Component.
- * @param {Object} config The standard configuration object.
- */
- constructor: function(config) {
- var me = this,
- currentConfig = me.config,
- id;
- me.onInitializedListeners = [];
- me.initialConfig = config;
- if (config !== undefined && 'id' in config) {
- id = config.id;
- }
- else if ('id' in currentConfig) {
- id = currentConfig.id;
- }
- else {
- id = me.getId();
- }
- me.id = id;
- me.setId(id);
- Ext.ComponentManager.register(me);
- me.initElement();
- me.initConfig(me.initialConfig);
- me.refreshSizeState = me.doRefreshSizeState;
- me.refreshFloating = me.doRefreshFloating;
- if (me.refreshSizeStateOnInitialized) {
- me.refreshSizeState();
- }
- if (me.refreshFloatingOnInitialized) {
- me.refreshFloating();
- }
- me.initialize();
- me.triggerInitialized();
- /**
- * Force the component to take up 100% width and height available, by adding it to {@link Ext.Viewport}.
- * @cfg {Boolean} fullscreen
- */
- if (me.config.fullscreen) {
- me.fireEvent('fullscreen', me);
- }
- me.fireEvent('initialize', me);
- },
- beforeInitConfig: function(config) {
- this.beforeInitialize.apply(this, arguments);
- },
- /**
- * @private
- */
- beforeInitialize: Ext.emptyFn,
- /**
- * Allows addition of behavior to the rendering phase.
- * @protected
- * @template
- */
- initialize: Ext.emptyFn,
- getTemplate: function() {
- return this.template;
- },
- /**
- * @private
- * @return {Object}
- * @return {String} return.reference
- * @return {Array} return.classList
- * @return {Object} return.children
- */
- getElementConfig: function() {
- return {
- reference: 'element',
- classList: ['x-unsized'],
- children: this.getTemplate()
- };
- },
- /**
- * @private
- */
- triggerInitialized: function() {
- var listeners = this.onInitializedListeners,
- ln = listeners.length,
- listener, fn, scope, args, i;
- if (!this.initialized) {
- this.initialized = true;
- if (ln > 0) {
- for (i = 0; i < ln; i++) {
- listener = listeners[i];
- fn = listener.fn;
- scope = listener.scope;
- args = listener.args;
- if (typeof fn == 'string') {
- scope[fn].apply(scope, args);
- }
- else {
- fn.apply(scope, args);
- }
- }
- listeners.length = 0;
- }
- }
- },
- /**
- * @private
- * @param fn
- * @param scope
- */
- onInitialized: function(fn, scope, args) {
- var listeners = this.onInitializedListeners;
- if (!scope) {
- scope = this;
- }
- if (this.initialized) {
- if (typeof fn == 'string') {
- scope[fn].apply(scope, args);
- }
- else {
- fn.apply(scope, args);
- }
- }
- else {
- listeners.push({
- fn: fn,
- scope: scope,
- args: args
- });
- }
- },
- renderTo: function(container, insertBeforeElement) {
- var dom = this.renderElement.dom,
- containerDom = Ext.getDom(container),
- insertBeforeChildDom = Ext.getDom(insertBeforeElement);
- if (containerDom) {
- if (insertBeforeChildDom) {
- containerDom.insertBefore(dom, insertBeforeChildDom);
- }
- else {
- containerDom.appendChild(dom);
- }
- this.setRendered(Boolean(dom.offsetParent));
- }
- },
- /**
- * @private
- * @chainable
- */
- setParent: function(parent) {
- var currentParent = this.parent;
- if (parent && currentParent && currentParent !== parent) {
- currentParent.remove(this, false);
- }
- this.parent = parent;
- return this;
- },
- applyPlugins: function(config) {
- var ln, i, configObj;
- if (!config) {
- return config;
- }
- config = [].concat(config);
- for (i = 0, ln = config.length; i < ln; i++) {
- configObj = config[i];
- config[i] = Ext.factory(configObj, 'Ext.plugin.Plugin', null, 'plugin');
- }
- return config;
- },
- updatePlugins: function(newPlugins, oldPlugins) {
- var ln, i;
- if (newPlugins) {
- for (i = 0, ln = newPlugins.length; i < ln; i++) {
- newPlugins[i].init(this);
- }
- }
- if (oldPlugins) {
- for (i = 0, ln = oldPlugins.length; i < ln; i++) {
- Ext.destroy(oldPlugins[i]);
- }
- }
- },
- updateRenderTo: function(newContainer) {
- this.renderTo(newContainer);
- },
- updateStyle: function(style) {
- this.element.applyStyles(style);
- },
- updateBorder: function(border) {
- this.element.setBorder(border);
- },
- updatePadding: function(padding) {
- this.innerElement.setPadding(padding);
- },
- updateMargin: function(margin) {
- this.element.setMargin(margin);
- },
- updateUi: function(newUi, oldUi) {
- var baseCls = this.getBaseCls();
- if (baseCls) {
- if (oldUi) {
- this.element.removeCls(oldUi, baseCls);
- }
- if (newUi) {
- this.element.addCls(newUi, baseCls);
- }
- }
- },
- applyBaseCls: function(baseCls) {
- return baseCls || clsPrefix + this.xtype;
- },
- updateBaseCls: function(newBaseCls, oldBaseCls) {
- var me = this,
- ui = me.getUi();
- if (newBaseCls) {
- this.element.addCls(newBaseCls);
- if (ui) {
- this.element.addCls(newBaseCls, null, ui);
- }
- }
- if (oldBaseCls) {
- this.element.removeCls(oldBaseCls);
- if (ui) {
- this.element.removeCls(oldBaseCls, null, ui);
- }
- }
- },
- /**
- * Adds a CSS class (or classes) to this Component's rendered element.
- * @param {String} cls The CSS class to add.
- * @param {String} [prefix=""] Optional prefix to add to each class.
- * @param {String} [suffix=""] Optional suffix to add to each class.
- */
- addCls: function(cls, prefix, suffix) {
- var oldCls = this.getCls(),
- newCls = (oldCls) ? oldCls.slice() : [],
- ln, i, cachedCls;
- prefix = prefix || '';
- suffix = suffix || '';
- if (typeof cls == "string") {
- cls = [cls];
- }
- ln = cls.length;
- //check if there is currently nothing in the array and we don't need to add a prefix or a suffix.
- //if true, we can just set the newCls value to the cls property, because that is what the value will be
- //if false, we need to loop through each and add them to the newCls array
- if (!newCls.length && prefix === '' && suffix === '') {
- newCls = cls;
- } else {
- for (i = 0; i < ln; i++) {
- cachedCls = prefix + cls[i] + suffix;
- if (newCls.indexOf(cachedCls) == -1) {
- newCls.push(cachedCls);
- }
- }
- }
- this.setCls(newCls);
- },
- /**
- * Removes the given CSS class(es) from this Component's rendered element.
- * @param {String} cls The class(es) to remove.
- * @param {String} [prefix=""] Optional prefix to prepend before each class.
- * @param {String} [suffix=""] Optional suffix to append to each class.
- */
- removeCls: function(cls, prefix, suffix) {
- var oldCls = this.getCls(),
- newCls = (oldCls) ? oldCls.slice() : [],
- ln, i;
- prefix = prefix || '';
- suffix = suffix || '';
- if (typeof cls == "string") {
- newCls = Ext.Array.remove(newCls, prefix + cls + suffix);
- } else {
- ln = cls.length;
- for (i = 0; i < ln; i++) {
- newCls = Ext.Array.remove(newCls, prefix + cls[i] + suffix);
- }
- }
- this.setCls(newCls);
- },
- /**
- * Replaces specified classes with the newly specified classes.
- * It uses the {@link #addCls} and {@link #removeCls} methods, so if the class(es) you are removing don't exist, it will
- * still add the new classes.
- * @param {String} oldCls The class(es) to remove.
- * @param {String} newCls The class(es) to add.
- * @param {String} [prefix=""] Optional prefix to prepend before each class.
- * @param {String} [suffix=""] Optional suffix to append to each class.
- */
- replaceCls: function(oldCls, newCls, prefix, suffix) {
- // We could have just called {@link #removeCls} and {@link #addCls}, but that would mean {@link #updateCls}
- // would get called twice, which would have performance implications because it will update the dom.
- var cls = this.getCls(),
- array = (cls) ? cls.slice() : [],
- ln, i, cachedCls;
- prefix = prefix || '';
- suffix = suffix || '';
- //remove all oldCls
- if (typeof oldCls == "string") {
- array = Ext.Array.remove(array, prefix + oldCls + suffix);
- } else if (oldCls) {
- ln = oldCls.length;
- for (i = 0; i < ln; i++) {
- array = Ext.Array.remove(array, prefix + oldCls[i] + suffix);
- }
- }
- //add all newCls
- if (typeof newCls == "string") {
- array.push(prefix + newCls + suffix);
- } else if (newCls) {
- ln = newCls.length;
- //check if there is currently nothing in the array and we don't need to add a prefix or a suffix.
- //if true, we can just set the array value to the newCls property, because that is what the value will be
- //if false, we need to loop through each and add them to the array
- if (!array.length && prefix === '' && suffix === '') {
- array = newCls;
- } else {
- for (i = 0; i < ln; i++) {
- cachedCls = prefix + newCls[i] + suffix;
- if (array.indexOf(cachedCls) == -1) {
- array.push(cachedCls);
- }
- }
- }
- }
- this.setCls(array);
- },
- /**
- * @private
- * @chainable
- */
- toggleCls: function(className, force) {
- this.element.toggleCls(className, force);
- return this;
- },
- /**
- * @private
- * Checks if the `cls` is a string. If it is, changed it into an array.
- * @param {String/Array} cls
- * @return {Array/null}
- */
- applyCls: function(cls) {
- if (typeof cls == "string") {
- cls = [cls];
- }
- //reset it back to null if there is nothing.
- if (!cls || !cls.length) {
- cls = null;
- }
- return cls;
- },
- /**
- * @private
- * All cls methods directly report to the {@link #cls} configuration, so anytime it changes, {@link #updateCls} will be called
- */
- updateCls: function(newCls, oldCls) {
- if (oldCls != newCls && this.element) {
- this.element.replaceCls(oldCls, newCls);
- }
- },
- /**
- * Updates the {@link #styleHtmlCls} configuration
- */
- updateStyleHtmlCls: function(newHtmlCls, oldHtmlCls) {
- var innerHtmlElement = this.innerHtmlElement,
- innerElement = this.innerElement;
- if (this.getStyleHtmlContent() && oldHtmlCls) {
- if (innerHtmlElement) {
- innerHtmlElement.replaceCls(oldHtmlCls, newHtmlCls);
- } else {
- innerElement.replaceCls(oldHtmlCls, newHtmlCls);
- }
- }
- },
- applyStyleHtmlContent: function(config) {
- return Boolean(config);
- },
- updateStyleHtmlContent: function(styleHtmlContent) {
- var htmlCls = this.getStyleHtmlCls(),
- innerElement = this.innerElement,
- innerHtmlElement = this.innerHtmlElement;
- if (styleHtmlContent) {
- if (innerHtmlElement) {
- innerHtmlElement.addCls(htmlCls);
- } else {
- innerElement.addCls(htmlCls);
- }
- } else {
- if (innerHtmlElement) {
- innerHtmlElement.removeCls(htmlCls);
- } else {
- innerElement.addCls(htmlCls);
- }
- }
- },
- applyContentEl: function(contentEl) {
- if (contentEl) {
- return Ext.get(contentEl);
- }
- },
- updateContentEl: function(newContentEl, oldContentEl) {
- if (oldContentEl) {
- oldContentEl.hide();
- Ext.getBody().append(oldContentEl);
- }
- if (newContentEl) {
- this.setHtml(newContentEl.dom);
- newContentEl.show();
- }
- },
- /**
- * Returns the height and width of the Component.
- * @return {Object} The current `height` and `width` of the Component.
- * @return {Number} return.width
- * @return {Number} return.height
- */
- getSize: function() {
- return {
- width: this.getWidth(),
- height: this.getHeight()
- };
- },
- /**
- * @private
- * @return {Boolean}
- */
- isCentered: function() {
- return Boolean(this.getCentered());
- },
- isFloating: function() {
- return this.floating;
- },
- isDocked: function() {
- return Boolean(this.getDocked());
- },
- isInnerItem: function() {
- return this.isInner;
- },
- setIsInner: function(isInner) {
- if (isInner !== this.isInner) {
- this.isInner = isInner;
- if (this.initialized) {
- this.fireEvent('innerstatechange', this, isInner);
- }
- }
- },
- filterPositionValue: function(value) {
- if (value === '' || value === 'auto') {
- value = null;
- }
- return value;
- },
- filterLengthValue: function(value) {
- if (value === 'auto' || (!value && value !== 0)) {
- return null;
- }
- return value;
- },
- applyTop: function(top) {
- return this.filterPositionValue(top);
- },
- applyRight: function(right) {
- return this.filterPositionValue(right);
- },
- applyBottom: function(bottom) {
- return this.filterPositionValue(bottom);
- },
- applyLeft: function(left) {
- return this.filterPositionValue(left);
- },
- applyWidth: function(width) {
- return this.filterLengthValue(width);
- },
- applyHeight: function(height) {
- return this.filterLengthValue(height);
- },
- applyMinWidth: function(width) {
- return this.filterLengthValue(width);
- },
- applyMinHeight: function(height) {
- return this.filterLengthValue(height);
- },
- applyMaxWidth: function(width) {
- return this.filterLengthValue(width);
- },
- applyMaxHeight: function(height) {
- return this.filterLengthValue(height);
- },
- doSetTop: function(top) {
- this.element.setTop(top);
- this.refreshFloating();
- },
- doSetRight: function(right) {
- this.element.setRight(right);
- this.refreshFloating();
- },
- doSetBottom: function(bottom) {
- this.element.setBottom(bottom);
- this.refreshFloating();
- },
- doSetLeft: function(left) {
- this.element.setLeft(left);
- this.refreshFloating();
- },
- doSetWidth: function(width) {
- this.element.setWidth(width);
- this.refreshSizeState();
- },
- doSetHeight: function(height) {
- this.element.setHeight(height);
- this.refreshSizeState();
- },
- applyFlex: function(flex) {
- if (flex) {
- flex = Number(flex);
- if (isNaN(flex)) {
- flex = null;
- }
- }
- else {
- flex = null
- }
- return flex;
- },
- doSetFlex: Ext.emptyFn,
- refreshSizeState: function() {
- this.refreshSizeStateOnInitialized = true;
- },
- doRefreshSizeState: function() {
- var hasWidth = this.getWidth() !== null || this.widthLayoutSized || (this.getLeft() !== null && this.getRight() !== null),
- hasHeight = this.getHeight() !== null || this.heightLayoutSized || (this.getTop() !== null && this.getBottom() !== null),
- stretched = this.layoutStretched || (!hasHeight && this.getMinHeight() !== null),
- state = hasWidth && hasHeight,
- flags = (hasWidth && this.LAYOUT_WIDTH) | (hasHeight && this.LAYOUT_HEIGHT) | (stretched && this.LAYOUT_STRETCHED);
- if (!state && stretched) {
- state = null;
- }
- this.setSizeState(state);
- this.setSizeFlags(flags);
- },
- setLayoutSizeFlags: function(flags) {
- this.layoutStretched = !!(flags & this.LAYOUT_STRETCHED);
- this.widthLayoutSized = !!(flags & this.LAYOUT_WIDTH);
- this.heightLayoutSized = !!(flags & this.LAYOUT_HEIGHT);
- this.refreshSizeState();
- },
- setSizeFlags: function(flags) {
- if (flags !== this.sizeFlags) {
- this.sizeFlags = flags;
- if (this.initialized) {
- this.fireEvent('sizeflagschange', this, flags);
- }
- }
- },
- getSizeFlags: function() {
- if (!this.initialized) {
- this.doRefreshSizeState();
- }
- return this.sizeFlags;
- },
- setSizeState: function(state) {
- if (state !== this.sizeState) {
- this.sizeState = state;
- this.element.setSizeState(state);
- if (this.initialized) {
- this.fireEvent('sizestatechange', this, state);
- }
- }
- },
- getSizeState: function() {
- if (!this.initialized) {
- this.doRefreshSizeState();
- }
- return this.sizeState;
- },
- doSetMinWidth: function(width) {
- this.element.setMinWidth(width);
- },
- doSetMinHeight: function(height) {
- this.element.setMinHeight(height);
- this.refreshSizeState();
- },
- doSetMaxWidth: function(width) {
- this.element.setMaxWidth(width);
- },
- doSetMaxHeight: function(height) {
- this.element.setMaxHeight(height);
- },
- /**
- * @private
- * @param {Boolean} centered
- * @return {Boolean}
- */
- applyCentered: function(centered) {
- centered = Boolean(centered);
- if (centered) {
- this.refreshInnerState = Ext.emptyFn;
- if (this.isFloating()) {
- this.resetFloating();
- }
- if (this.isDocked()) {
- this.setDocked(false);
- }
- this.setIsInner(false);
- delete this.refreshInnerState;
- }
- return centered;
- },
- doSetCentered: function(centered) {
- this.toggleCls(this.getFloatingCls(), centered);
- if (!centered) {
- this.refreshInnerState();
- }
- },
- applyDocked: function(docked) {
- if (!docked) {
- return null;
- }
- //<debug error>
- if (!/^(top|right|bottom|left)$/.test(docked)) {
- Ext.Logger.error("Invalid docking position of '" + docked.position + "', must be either 'top', 'right', 'bottom', " +
- "'left' or `null` (for no docking)", this);
- return;
- }
- //</debug>
- this.refreshInnerState = Ext.emptyFn;
- if (this.isFloating()) {
- this.resetFloating();
- }
- if (this.isCentered()) {
- this.setCentered(false);
- }
- this.setIsInner(false);
- delete this.refreshInnerState;
- return docked;
- },
- doSetDocked: function(docked) {
- if (!docked) {
- this.refreshInnerState();
- }
- },
- /**
- * Resets {@link #top}, {@link #right}, {@link #bottom} and {@link #left} configurations to `null`, which
- * will un-float this component.
- */
- resetFloating: function() {
- this.setTop(null);
- this.setRight(null);
- this.setBottom(null);
- this.setLeft(null);
- },
- refreshInnerState: function() {
- this.setIsInner(!this.isCentered() && !this.isFloating() && !this.isDocked());
- },
- refreshFloating: function() {
- this.refreshFloatingOnInitialized = true;
- },
- doRefreshFloating: function() {
- var floating = true,
- floatingCls = this.getFloatingCls();
- if (this.getTop() === null && this.getBottom() === null &&
- this.getRight() === null && this.getLeft() === null) {
- floating = false;
- }
- else {
- this.refreshSizeState();
- }
- if (floating !== this.floating) {
- this.floating = floating;
- this.element.toggleCls(floatingCls, floating);
- if (floating) {
- this.refreshInnerState = Ext.emptyFn;
- if (this.isCentered()) {
- this.setCentered(false);
- }
- if (this.isDocked()) {
- this.setDocked(false);
- }
- this.setIsInner(false);
- delete this.refreshInnerState;
- }
- if (this.initialized) {
- this.fireEvent('floatingchange', this, floating);
- }
- if (!floating) {
- this.refreshInnerState();
- }
- }
- },
- /**
- * Updates the floatingCls if the component is currently floating
- * @private
- */
- updateFloatingCls: function(newFloatingCls, oldFloatingCls) {
- if (this.isFloating()) {
- this.replaceCls(oldFloatingCls, newFloatingCls);
- }
- },
- applyDisabled: function(disabled) {
- return Boolean(disabled);
- },
- doSetDisabled: function(disabled) {
- this.element[disabled ? 'addCls' : 'removeCls'](this.getDisabledCls());
- },
- updateDisabledCls: function(newDisabledCls, oldDisabledCls) {
- if (this.isDisabled()) {
- this.element.replaceCls(oldDisabledCls, newDisabledCls);
- }
- },
- /**
- * Disables this Component
- */
- disable: function() {
- this.setDisabled(true);
- },
- /**
- * Enables this Component
- */
- enable: function() {
- this.setDisabled(false);
- },
- /**
- * Returns `true` if this Component is currently disabled.
- * @return {Boolean} `true` if currently disabled.
- */
- isDisabled: function() {
- return this.getDisabled();
- },
- applyZIndex: function(zIndex) {
- if (!zIndex && zIndex !== 0) {
- zIndex = null;
- }
- if (zIndex !== null) {
- zIndex = Number(zIndex);
- if (isNaN(zIndex)) {
- zIndex = null;
- }
- }
- return zIndex;
- },
- updateZIndex: function(zIndex) {
- var element = this.element,
- domStyle;
- if (element && !element.isDestroyed) {
- domStyle = element.dom.style;
- if (zIndex !== null) {
- domStyle.setProperty('z-index', zIndex, 'important');
- }
- else {
- domStyle.removeProperty('z-index');
- }
- }
- },
- getInnerHtmlElement: function() {
- var innerHtmlElement = this.innerHtmlElement,
- styleHtmlCls = this.getStyleHtmlCls();
- if (!innerHtmlElement || !innerHtmlElement.dom || !innerHtmlElement.dom.parentNode) {
- this.innerHtmlElement = innerHtmlElement = this.innerElement.createChild({ cls: 'x-innerhtml ' });
- if (this.getStyleHtmlContent()) {
- this.innerHtmlElement.addCls(styleHtmlCls);
- this.innerElement.removeCls(styleHtmlCls);
- }
- }
- return innerHtmlElement;
- },
- updateHtml: function(html) {
- var innerHtmlElement = this.getInnerHtmlElement();
- if (Ext.isElement(html)){
- innerHtmlElement.setHtml('');
- innerHtmlElement.append(html);
- }
- else {
- innerHtmlElement.setHtml(html);
- }
- },
- applyHidden: function(hidden) {
- return Boolean(hidden);
- },
- doSetHidden: function(hidden) {
- var element = this.renderElement;
- if (element.isDestroyed) {
- return;
- }
- if (hidden) {
- element.hide();
- }
- else {
- element.show();
- }
- if (this.element) {
- this.element[hidden ? 'addCls' : 'removeCls'](this.getHiddenCls());
- }
- this.fireEvent(hidden ? 'hide' : 'show', this);
- },
- updateHiddenCls: function(newHiddenCls, oldHiddenCls) {
- if (this.isHidden()) {
- this.element.replaceCls(oldHiddenCls, newHiddenCls);
- }
- },
- /**
- * Returns `true` if this Component is currently hidden.
- * @return {Boolean} `true` if currently hidden.
- */
- isHidden: function() {
- return this.getHidden();
- },
- /**
- * Hides this Component
- * @param {Object/Boolean} animation (optional)
- * @return {Ext.Component}
- * @chainable
- */
- hide: function(animation) {
- if (!this.getHidden()) {
- if (animation === undefined || (animation && animation.isComponent)) {
- animation = this.getHideAnimation();
- }
- if (animation) {
- if (animation === true) {
- animation = 'fadeOut';
- }
- this.onBefore({
- hiddenchange: 'animateFn',
- scope: this,
- single: true,
- args: [animation]
- });
- }
- this.setHidden(true);
- }
- return this;
- },
- /**
- * Shows this component.
- * @param {Object/Boolean} animation (optional)
- * @return {Ext.Component}
- * @chainable
- */
- show: function(animation) {
- var hidden = this.getHidden();
- if (hidden || hidden === null) {
- if (animation === true) {
- animation = 'fadeIn';
- }
- else if (animation === undefined || (animation && animation.isComponent)) {
- animation = this.getShowAnimation();
- }
- if (animation) {
- this.onBefore({
- hiddenchange: 'animateFn',
- scope: this,
- single: true,
- args: [animation]
- });
- }
- this.setHidden(false);
- }
- return this;
- },
- animateFn: function(animation, component, newState, oldState, options, controller) {
- if (animation && (!newState || (newState && this.isPainted()))) {
- var anim = new Ext.fx.Animation(animation);
- anim.setElement(component.element);
- if (newState) {
- anim.setOnEnd(function() {
- controller.resume();
- });
- controller.pause();
- }
- Ext.Animator.run(anim);
- }
- },
- /**
- * @private
- */
- setVisibility: function(isVisible) {
- this.renderElement.setVisibility(isVisible);
- },
- /**
- * @private
- */
- isRendered: function() {
- return this.rendered;
- },
- /**
- * @private
- */
- isPainted: function() {
- return this.renderElement.isPainted();
- },
- /**
- * @private
- */
- applyTpl: function(config) {
- return (Ext.isObject(config) && config.isTemplate) ? config : new Ext.XTemplate(config);
- },
- applyData: function(data) {
- if (Ext.isObject(data)) {
- return Ext.apply({}, data);
- } else if (!data) {
- data = {};
- }
- return data;
- },
- /**
- * @private
- */
- updateData: function(newData) {
- var me = this;
- if (newData) {
- var tpl = me.getTpl(),
- tplWriteMode = me.getTplWriteMode();
- if (tpl) {
- tpl[tplWriteMode](me.getInnerHtmlElement(), newData);
- }
- /**
- * @event updatedata
- * Fires whenever the data of the component is updated
- * @param {Ext.Component} this The component instance
- * @param {Object} newData The new data
- */
- this.fireEvent('updatedata', me, newData);
- }
- },
- applyRecord: function(config) {
- if (config && Ext.isObject(config) && config.isModel) {
- return config;
- }
- return null;
- },
- updateRecord: function(newRecord, oldRecord) {
- var me = this;
- if (oldRecord) {
- oldRecord.unjoin(me);
- }
- if (!newRecord) {
- me.updateData('');
- }
- else {
- newRecord.join(me);
- me.updateData(newRecord.getData(true));
- }
- },
- // @private Used to handle joining of a record to a tpl
- afterEdit: function() {
- this.updateRecord(this.getRecord());
- },
- // @private Used to handle joining of a record to a tpl
- afterErase: function() {
- this.setRecord(null);
- },
- applyItemId: function(itemId) {
- return itemId || this.getId();
- },
- /**
- * Tests whether or not this Component is of a specific xtype. This can test whether this Component is descended
- * from the xtype (default) or whether it is directly of the xtype specified (`shallow = true`).
- * __If using your own subclasses, be aware that a Component must register its own xtype
- * to participate in determination of inherited xtypes.__
- *
- * For a list of all available xtypes, see the {@link Ext.Component} header.
- *
- * Example usage:
- *
- * var t = new Ext.field.Text();
- * var isText = t.isXType('textfield'); // true
- * var isBoxSubclass = t.isXType('field'); // true, descended from Ext.field.Field
- * var isBoxInstance = t.isXType('field', true); // false, not a direct Ext.field.Field instance
- *
- * @param {String} xtype The xtype to check for this Component.
- * @param {Boolean} shallow (optional) `false` to check whether this Component is descended from the xtype (this is
- * the default), or `true` to check whether this Component is directly of the specified xtype.
- * @return {Boolean} `true` if this component descends from the specified xtype, `false` otherwise.
- */
- isXType: function(xtype, shallow) {
- if (shallow) {
- return this.xtypes.indexOf(xtype) != -1;
- }
- return Boolean(this.xtypesMap[xtype]);
- },
- /**
- * Returns this Component's xtype hierarchy as a slash-delimited string. For a list of all
- * available xtypes, see the {@link Ext.Component} header.
- *
- * __Note:__ If using your own subclasses, be aware that a Component must register its own xtype
- * to participate in determination of inherited xtypes.
- *
- * Example usage:
- *
- * var t = new Ext.field.Text();
- * alert(t.getXTypes()); // alerts 'component/field/textfield'
- *
- * @return {String} The xtype hierarchy string.
- */
- getXTypes: function() {
- return this.xtypesChain.join('/');
- },
- getDraggableBehavior: function() {
- var behavior = this.draggableBehavior;
- if (!behavior) {
- behavior = this.draggableBehavior = new Ext.behavior.Draggable(this);
- }
- return behavior;
- },
- applyDraggable: function(config) {
- this.getDraggableBehavior().setConfig(config);
- },
- getDraggable: function() {
- return this.getDraggableBehavior().getDraggable();
- },
- getTranslatableBehavior: function() {
- var behavior = this.translatableBehavior;
- if (!behavior) {
- behavior = this.translatableBehavior = new Ext.behavior.Translatable(this);
- }
- return behavior;
- },
- applyTranslatable: function(config) {
- this.getTranslatableBehavior().setConfig(config);
- },
- getTranslatable: function() {
- return this.getTranslatableBehavior().getTranslatable();
- },
- translateAxis: function(axis, value, animation) {
- var x, y;
- if (axis === 'x') {
- x = value;
- }
- else {
- y = value;
- }
- return this.translate(x, y, animation);
- },
- translate: function() {
- var translatable = this.getTranslatable();
- if (!translatable) {
- this.setTranslatable(true);
- translatable = this.getTranslatable();
- }
- translatable.translate.apply(translatable, arguments);
- },
- /**
- * @private
- * @param rendered
- */
- setRendered: function(rendered) {
- var wasRendered = this.rendered;
- if (rendered !== wasRendered) {
- this.rendered = rendered;
- return true;
- }
- return false;
- },
- /**
- * Sets the size of the Component.
- * @param {Number} width The new width for the Component.
- * @param {Number} height The new height for the Component.
- */
- setSize: function(width, height) {
- if (width != undefined) {
- this.setWidth(width);
- }
- if (height != undefined) {
- this.setHeight(height);
- }
- },
- //@private
- doAddListener: function(name, fn, scope, options, order) {
- if (options && 'element' in options) {
- //<debug error>
- if (this.referenceList.indexOf(options.element) === -1) {
- Ext.Logger.error("Adding event listener with an invalid element reference of '" + options.element +
- "' for this component. Available values are: '" + this.referenceList.join("', '") + "'", this);
- }
- //</debug>
- // The default scope is this component
- return this[options.element].doAddListener(name, fn, scope || this, options, order);
- }
- if (name == 'painted' || name == 'resize') {
- return this.element.doAddListener(name, fn, scope || this, options, order);
- }
- return this.callParent(arguments);
- },
- //@private
- doRemoveListener: function(name, fn, scope, options, order) {
- if (options && 'element' in options) {
- //<debug error>
- if (this.referenceList.indexOf(options.element) === -1) {
- Ext.Logger.error("Removing event listener with an invalid element reference of '" + options.element +
- "' for this component. Available values are: '" + this.referenceList.join('", "') + "'", this);
- }
- //</debug>
- // The default scope is this component
- this[options.element].doRemoveListener(name, fn, scope || this, options, order);
- }
- return this.callParent(arguments);
- },
- /**
- * Shows this component by another component. If you specify no alignment, it will automatically
- * position this component relative to the reference component.
- *
- * For example, say we are aligning a Panel next to a Button, the alignment string would look like this:
- *
- * [panel-vertical (t/b/c)][panel-horizontal (l/r/c)]-[button-vertical (t/b/c)][button-horizontal (l/r/c)]
- *
- * where t = top, b = bottom, c = center, l = left, r = right.
- *
- * ## Examples
- *
- * - `tl-tr` means top-left corner of the Panel to the top-right corner of the Button
- * - `tc-bc` means top-center of the Panel to the bottom-center of the Button
- *
- * You can put a '?' at the end of the alignment string to constrain the floating element to the
- * {@link Ext.Viewport Viewport}
- *
- * // show `panel` by `button` using the default positioning (auto fit)
- * panel.showBy(button);
- *
- * // align the top left corner of `panel` with the top right corner of `button` (constrained to viewport)
- * panel.showBy(button, "tl-tr?");
- *
- * // align the bottom right corner of `panel` with the center left edge of `button` (not constrained by viewport)
- * panel.showBy(button, "br-cl");
- *
- * @param {Ext.Component} component The target component to show this component by.
- * @param {String} alignment (optional) The specific alignment.
- */
- showBy: function(component, alignment) {
- var me = this,
- viewport = Ext.Viewport,
- parent = me.getParent();
- me.setVisibility(false);
- if (parent !== viewport) {
- viewport.add(me);
- }
- me.show();
- me.on({
- hide: 'onShowByErased',
- destroy: 'onShowByErased',
- single: true,
- scope: me
- });
- viewport.on('resize', 'alignTo', me, { args: [component, alignment] });
- me.alignTo(component, alignment);
- me.setVisibility(true);
- },
- /**
- * @private
- * @param component
- */
- onShowByErased: function() {
- Ext.Viewport.un('resize', 'alignTo', this);
- },
- /**
- * @private
- */
- alignTo: function(component, alignment) {
- var alignToElement = component.isComponent ? component.renderElement : component,
- element = this.renderElement,
- alignToBox = alignToElement.getPageBox(),
- constrainBox = this.getParent().element.getPageBox(),
- box = element.getPageBox(),
- alignToHeight = alignToBox.height,
- alignToWidth = alignToBox.width,
- height = box.height,
- width = box.width;
- // Keep off the sides...
- constrainBox.bottom -= 5;
- constrainBox.height -= 10;
- constrainBox.left += 5;
- constrainBox.right -= 5;
- constrainBox.top += 5;
- constrainBox.width -= 10;
- if (!alignment || alignment === 'auto') {
- if (constrainBox.bottom - alignToBox.bottom < height) {
- if (alignToBox.top - constrainBox.top < height) {
- if (alignToBox.left - constrainBox.left < width) {
- alignment = 'cl-cr?';
- }
- else {
- alignment = 'cr-cl?';
- }
- }
- else {
- alignment = 'bc-tc?';
- }
- }
- else {
- alignment = 'tc-bc?';
- }
- }
- var matches = alignment.match(this.alignmentRegex);
- //<debug error>
- if (!matches) {
- Ext.Logger.error("Invalid alignment value of '" + alignment + "'");
- }
- //</debug>
- var from = matches[1].split(''),
- to = matches[2].split(''),
- constrained = (matches[3] === '?'),
- fromVertical = from[0],
- fromHorizontal = from[1] || fromVertical,
- toVertical = to[0],
- toHorizontal = to[1] || toVertical,
- top = alignToBox.top,
- left = alignToBox.left,
- halfAlignHeight = alignToHeight / 2,
- halfAlignWidth = alignToWidth / 2,
- halfWidth = width / 2,
- halfHeight = height / 2,
- maxLeft, maxTop;
- switch (fromVertical) {
- case 't':
- switch (toVertical) {
- case 'c':
- top += halfAlignHeight;
- break;
- case 'b':
- top += alignToHeight;
- }
- break;
- case 'b':
- switch (toVertical) {
- case 'c':
- top -= (height - halfAlignHeight);
- break;
- case 't':
- top -= height;
- break;
- case 'b':
- top -= height - alignToHeight;
- }
- break;
- case 'c':
- switch (toVertical) {
- case 't':
- top -= halfHeight;
- break;
- case 'c':
- top -= (halfHeight - halfAlignHeight);
- break;
- case 'b':
- top -= (halfHeight - alignToHeight);
- }
- break;
- }
- switch (fromHorizontal) {
- case 'l':
- switch (toHorizontal) {
- case 'c':
- left += halfAlignHeight;
- break;
- case 'r':
- left += alignToWidth;
- }
- break;
- case 'r':
- switch (toHorizontal) {
- case 'r':
- left -= (width - alignToWidth);
- break;
- case 'c':
- left -= (width - halfWidth);
- break;
- case 'l':
- left -= width;
- }
- break;
- case 'c':
- switch (toHorizontal) {
- case 'l':
- left -= halfWidth;
- break;
- case 'c':
- left -= (halfWidth - halfAlignWidth);
- break;
- case 'r':
- left -= (halfWidth - alignToWidth);
- }
- break;
- }
- if (constrained) {
- maxLeft = (constrainBox.left + constrainBox.width) - width;
- maxTop = (constrainBox.top + constrainBox.height) - height;
- left = Math.max(constrainBox.left, Math.min(maxLeft, left));
- top = Math.max(constrainBox.top, Math.min(maxTop, top));
- }
- this.setLeft(left);
- this.setTop(top);
- },
- /**
- * Walks up the `ownerCt` axis looking for an ancestor Container which matches
- * the passed simple selector.
- *
- * Example:
- *
- * var owningTabPanel = grid.up('tabpanel');
- *
- * @param {String} selector (optional) The simple selector to test.
- * @return {Ext.Container} The matching ancestor Container (or `undefined` if no match was found).
- */
- up: function(selector) {
- var result = this.parent;
- if (selector) {
- for (; result; result = result.parent) {
- if (Ext.ComponentQuery.is(result, selector)) {
- return result;
- }
- }
- }
- return result;
- },
- getBubbleTarget: function() {
- return this.getParent();
- },
- /**
- * Destroys this Component. If it is currently added to a Container it will first be removed from that Container.
- * All Ext.Element references are also deleted and the Component is de-registered from Ext.ComponentManager
- */
- destroy: function() {
- this.destroy = Ext.emptyFn;
- var parent = this.getParent(),
- referenceList = this.referenceList,
- i, ln, reference;
- this.isDestroying = true;
- Ext.destroy(this.getTranslatable(), this.getPlugins());
- // Remove this component itself from the container if it's currently contained
- if (parent) {
- parent.remove(this, false);
- }
- // Destroy all element references
- for (i = 0, ln = referenceList.length; i < ln; i++) {
- reference = referenceList[i];
- this[reference].destroy();
- delete this[reference];
- }
- Ext.destroy(this.innerHtmlElement);
- this.setRecord(null);
- this.callSuper();
- Ext.ComponentManager.unregister(this);
- }
- // Convert old properties in data into a config object
- }, function() {
- });
- })(Ext.baseCSSPrefix);
- /**
- *
- */
- Ext.define('Ext.layout.wrapper.Inner', {
- config: {
- sizeState: null,
- container: null
- },
- constructor: function(config) {
- this.initConfig(config);
- },
- getElement: function() {
- return this.getContainer().bodyElement;
- },
- setInnerWrapper: Ext.emptyFn,
- getInnerWrapper: Ext.emptyFn
- });
- /**
- *
- */
- Ext.define('Ext.layout.Abstract', {
- mixins: ['Ext.mixin.Observable'],
-
- isLayout: true,
- constructor: function(config) {
- this.initialConfig = config;
- },
- setContainer: function(container) {
- this.container = container;
- this.initConfig(this.initialConfig);
- return this;
- },
- onItemAdd: function() {},
- onItemRemove: function() {},
- onItemMove: function() {},
- onItemCenteredChange: function() {},
- onItemFloatingChange: function() {},
- onItemDockedChange: function() {},
- onItemInnerStateChange: function() {}
- });
- /**
- *
- */
- Ext.define('Ext.mixin.Bindable', {
- extend: 'Ext.mixin.Mixin',
- mixinConfig: {
- id: 'bindable'
- },
- bind: function(instance, boundMethod, bindingMethod, preventDefault) {
- if (!bindingMethod) {
- bindingMethod = boundMethod;
- }
- var boundFn = instance[boundMethod],
- fn;
- instance[boundMethod] = fn = function() {
- var binding = fn.$binding,
- scope = binding.bindingScope,
- args = Array.prototype.slice.call(arguments);
- args.push(arguments);
- if (!binding.preventDefault && scope[binding.bindingMethod].apply(scope, args) !== false) {
- return binding.boundFn.apply(this, arguments);
- }
- };
- fn.$binding = {
- preventDefault: !!preventDefault,
- boundFn: boundFn,
- bindingMethod: bindingMethod,
- bindingScope: this
- };
- return this;
- },
- unbind: function(instance, boundMethod, bindingMethod) {
- if (!bindingMethod) {
- bindingMethod = boundMethod;
- }
- var fn = instance[boundMethod],
- binding = fn.$binding,
- boundFn, currentBinding;
- while (binding) {
- boundFn = binding.boundFn;
- if (binding.bindingMethod === bindingMethod && binding.bindingScope === this) {
- if (currentBinding) {
- currentBinding.boundFn = boundFn;
- }
- else {
- instance[boundMethod] = boundFn;
- }
- return this;
- }
- currentBinding = binding;
- binding = binding.boundFn;
- }
- return this;
- }
- });
- /**
- *
- */
- Ext.define('Ext.util.Wrapper', {
- mixins: ['Ext.mixin.Bindable'],
- constructor: function(elementConfig, wrappedElement) {
- var element = this.link('element', Ext.Element.create(elementConfig));
- if (wrappedElement) {
- element.insertBefore(wrappedElement);
- this.wrap(wrappedElement);
- }
- },
- bindSize: function(sizeName) {
- var wrappedElement = this.wrappedElement,
- boundMethodName;
- this.boundSizeName = sizeName;
- this.boundMethodName = boundMethodName = sizeName === 'width' ? 'setWidth' : 'setHeight';
- this.bind(wrappedElement, boundMethodName, 'onBoundSizeChange');
- wrappedElement[boundMethodName].call(wrappedElement, wrappedElement.getStyleValue(sizeName));
- },
- onBoundSizeChange: function(size, args) {
- var element = this.element;
- if (typeof size === 'string' && size.substr(-1) === '%') {
- args[0] = '100%';
- }
- else {
- size = '';
- }
- element[this.boundMethodName].call(element, size);
- },
- wrap: function(wrappedElement) {
- var element = this.element,
- innerDom;
- this.wrappedElement = wrappedElement;
- innerDom = element.dom;
- while (innerDom.firstElementChild !== null) {
- innerDom = innerDom.firstElementChild;
- }
- innerDom.appendChild(wrappedElement.dom);
- },
- destroy: function() {
- var element = this.element,
- dom = element.dom,
- wrappedElement = this.wrappedElement,
- boundMethodName = this.boundMethodName,
- parentNode = dom.parentNode,
- size;
- if (boundMethodName) {
- this.unbind(wrappedElement, boundMethodName, 'onBoundSizeChange');
- size = element.getStyle(this.boundSizeName);
- if (size) {
- wrappedElement[boundMethodName].call(wrappedElement, size);
- }
- }
- if (parentNode) {
- if (!wrappedElement.isDestroyed) {
- parentNode.replaceChild(dom.firstElementChild, dom);
- }
- delete this.wrappedElement;
- }
- this.callSuper();
- }
- });
- /**
- *
- */
- Ext.define('Ext.layout.wrapper.BoxDock', {
- config: {
- direction: 'horizontal',
- element: {
- className: 'x-dock'
- },
- bodyElement: {
- className: 'x-dock-body'
- },
- innerWrapper: null,
- sizeState: false,
- container: null
- },
- positionMap: {
- top: 'start',
- left: 'start',
- bottom: 'end',
- right: 'end'
- },
- constructor: function(config) {
- this.items = {
- start: [],
- end: []
- };
- this.itemsCount = 0;
- this.initConfig(config);
- },
- addItems: function(items) {
- var i, ln, item;
- for (i = 0, ln = items.length; i < ln; i++) {
- item = items[i];
- this.addItem(item);
- }
- },
- addItem: function(item) {
- var docked = item.getDocked(),
- position = this.positionMap[docked],
- wrapper = item.$dockWrapper,
- container = this.getContainer(),
- index = container.indexOf(item),
- element = item.element,
- items = this.items,
- sideItems = items[position],
- i, ln, sibling, referenceElement, siblingIndex;
- if (wrapper) {
- wrapper.removeItem(item);
- }
- item.$dockWrapper = this;
- item.addCls('x-dock-item');
- item.addCls('x-docked-' + docked);
- for (i = 0, ln = sideItems.length; i < ln; i++) {
- sibling = sideItems[i];
- siblingIndex = container.indexOf(sibling);
- if (siblingIndex > index) {
- referenceElement = sibling.element;
- sideItems.splice(i, 0, item);
- break;
- }
- }
- if (!referenceElement) {
- sideItems.push(item);
- referenceElement = this.getBodyElement();
- }
- this.itemsCount++;
- if (position === 'start') {
- element.insertBefore(referenceElement);
- }
- else {
- element.insertAfter(referenceElement);
- }
- },
- removeItem: function(item) {
- var position = item.getDocked(),
- items = this.items[this.positionMap[position]];
- Ext.Array.remove(items, item);
- item.element.detach();
- delete item.$dockWrapper;
- item.removeCls('x-dock-item');
- item.removeCls('x-docked-' + position);
- if (--this.itemsCount === 0) {
- this.destroy();
- }
- },
- getItemsSlice: function(index) {
- var container = this.getContainer(),
- items = this.items,
- slice = [],
- sideItems, i, ln, item;
- for (sideItems = items.start, i = 0, ln = sideItems.length; i < ln; i++) {
- item = sideItems[i];
- if (container.indexOf(item) > index) {
- slice.push(item);
- }
- }
- for (sideItems = items.end, i = 0, ln = sideItems.length; i < ln; i++) {
- item = sideItems[i];
- if (container.indexOf(item) > index) {
- slice.push(item);
- }
- }
- return slice;
- },
- applyElement: function(element) {
- return Ext.Element.create(element);
- },
- updateElement: function(element) {
- element.addCls('x-dock-' + this.getDirection());
- },
- applyBodyElement: function(bodyElement) {
- return Ext.Element.create(bodyElement);
- },
- updateBodyElement: function(bodyElement) {
- this.getElement().append(bodyElement);
- },
- updateInnerWrapper: function(innerWrapper, oldInnerWrapper) {
- var bodyElement = this.getBodyElement();
- if (oldInnerWrapper && oldInnerWrapper.$outerWrapper === this) {
- oldInnerWrapper.getElement().detach();
- delete oldInnerWrapper.$outerWrapper;
- }
- if (innerWrapper) {
- innerWrapper.setSizeState(this.getSizeState());
- innerWrapper.$outerWrapper = this;
- bodyElement.append(innerWrapper.getElement());
- }
- },
- updateSizeState: function(state) {
- var innerWrapper = this.getInnerWrapper();
- this.getElement().setSizeState(state);
- if (innerWrapper) {
- innerWrapper.setSizeState(state);
- }
- },
- destroy: function() {
- var innerWrapper = this.getInnerWrapper(),
- outerWrapper = this.$outerWrapper,
- innerWrapperElement;
- if (innerWrapper) {
- if (outerWrapper) {
- outerWrapper.setInnerWrapper(innerWrapper);
- }
- else {
- innerWrapperElement = innerWrapper.getElement();
- if (!innerWrapperElement.isDestroyed) {
- innerWrapperElement.replace(this.getElement());
- }
- delete innerWrapper.$outerWrapper;
- }
- }
- delete this.$outerWrapper;
- this.setInnerWrapper(null);
- this.unlink('_bodyElement', '_element');
- this.callSuper();
- }
- });
- /**
- *
- */
- Ext.define('Ext.layout.Default', {
- extend: 'Ext.layout.Abstract',
- isAuto: true,
- alias: ['layout.default', 'layout.auto'],
- requires: [
- 'Ext.util.Wrapper',
- 'Ext.layout.wrapper.BoxDock',
- 'Ext.layout.wrapper.Inner'
- ],
- config: {
- /**
- * @cfg {Ext.fx.layout.Card} animation Layout animation configuration
- * Controls how layout transitions are animated. Currently only available for
- * Card Layouts.
- *
- * Possible values are:
- *
- * - cover
- * - cube
- * - fade
- * - flip
- * - pop
- * - reveal
- * - scroll
- * - slide
- * @accessor
- */
- animation: null
- },
- centerWrapperClass: 'x-center',
- dockWrapperClass: 'x-dock',
- positionMap: {
- top: 'start',
- left: 'start',
- middle: 'center',
- bottom: 'end',
- right: 'end'
- },
- positionDirectionMap: {
- top: 'vertical',
- bottom: 'vertical',
- left: 'horizontal',
- right: 'horizontal'
- },
- setContainer: function(container) {
- var options = {
- delegate: '> component'
- };
- this.dockedItems = [];
- this.callSuper(arguments);
- container.on('centeredchange', 'onItemCenteredChange', this, options, 'before')
- .on('floatingchange', 'onItemFloatingChange', this, options, 'before')
- .on('dockedchange', 'onBeforeItemDockedChange', this, options, 'before')
- .on('dockedchange', 'onAfterItemDockedChange', this, options);
- },
- monitorSizeStateChange: function() {
- this.monitorSizeStateChange = Ext.emptyFn;
- this.container.on('sizestatechange', 'onContainerSizeStateChange', this);
- },
- monitorSizeFlagsChange: function() {
- this.monitorSizeFlagsChange = Ext.emptyFn;
- this.container.on('sizeflagschange', 'onContainerSizeFlagsChange', this);
- },
- onItemAdd: function(item) {
- var docked = item.getDocked();
- if (docked !== null) {
- this.dockItem(item);
- }
- else if (item.isCentered()) {
- this.onItemCenteredChange(item, true);
- }
- else if (item.isFloating()) {
- this.onItemFloatingChange(item, true);
- }
- else {
- this.onItemInnerStateChange(item, true);
- }
- },
- /**
- *
- * @param item
- * @param isInner
- * @param [destroying]
- */
- onItemInnerStateChange: function(item, isInner, destroying) {
- if (isInner) {
- this.insertInnerItem(item, this.container.innerIndexOf(item));
- }
- else {
- this.removeInnerItem(item);
- }
- },
- insertInnerItem: function(item, index) {
- var container = this.container,
- containerDom = container.innerElement.dom,
- itemDom = item.element.dom,
- nextSibling = container.getInnerAt(index + 1),
- nextSiblingDom = nextSibling ? nextSibling.element.dom : null;
- containerDom.insertBefore(itemDom, nextSiblingDom);
- return this;
- },
- insertBodyItem: function(item) {
- var container = this.container.setUseBodyElement(true),
- bodyDom = container.bodyElement.dom;
- if (item.getZIndex() === null) {
- item.setZIndex((container.indexOf(item) + 1) * 2);
- }
- bodyDom.insertBefore(item.element.dom, bodyDom.firstChild);
- return this;
- },
- removeInnerItem: function(item) {
- item.element.detach();
- },
- removeBodyItem: function(item) {
- item.setZIndex(null);
- item.element.detach();
- },
- onItemRemove: function(item, index, destroying) {
- var docked = item.getDocked();
- if (docked) {
- this.undockItem(item);
- }
- else if (item.isCentered()) {
- this.onItemCenteredChange(item, false);
- }
- else if (item.isFloating()) {
- this.onItemFloatingChange(item, false);
- }
- else {
- this.onItemInnerStateChange(item, false, destroying);
- }
- },
- onItemMove: function(item, toIndex, fromIndex) {
- if (item.isCentered() || item.isFloating()) {
- item.setZIndex((toIndex + 1) * 2);
- }
- else if (item.isInnerItem()) {
- this.insertInnerItem(item, this.container.innerIndexOf(item));
- }
- else {
- this.undockItem(item);
- this.dockItem(item);
- }
- },
- onItemCenteredChange: function(item, centered) {
- var wrapperName = '$centerWrapper';
- if (centered) {
- this.insertBodyItem(item);
- item.link(wrapperName, new Ext.util.Wrapper({
- className: this.centerWrapperClass
- }, item.element));
- }
- else {
- item.unlink(wrapperName);
- this.removeBodyItem(item);
- }
- },
- onItemFloatingChange: function(item, floating) {
- if (floating) {
- this.insertBodyItem(item);
- }
- else {
- this.removeBodyItem(item);
- }
- },
- onBeforeItemDockedChange: function(item, docked, oldDocked) {
- if (oldDocked) {
- this.undockItem(item);
- }
- },
- onAfterItemDockedChange: function(item, docked, oldDocked) {
- if (docked) {
- this.dockItem(item);
- }
- },
- onContainerSizeStateChange: function() {
- var dockWrapper = this.getDockWrapper();
- if (dockWrapper) {
- dockWrapper.setSizeState(this.container.getSizeState());
- }
- },
- onContainerSizeFlagsChange: function() {
- var items = this.dockedItems,
- i, ln, item;
- for (i = 0, ln = items.length; i < ln; i++) {
- item = items[i];
- this.refreshDockedItemLayoutSizeFlags(item);
- }
- },
- refreshDockedItemLayoutSizeFlags: function(item) {
- var container = this.container,
- dockedDirection = this.positionDirectionMap[item.getDocked()],
- binaryMask = (dockedDirection === 'horizontal') ? container.LAYOUT_HEIGHT : container.LAYOUT_WIDTH,
- flags = (container.getSizeFlags() & binaryMask);
- item.setLayoutSizeFlags(flags);
- },
- dockItem: function(item) {
- var DockClass = Ext.layout.wrapper.BoxDock,
- dockedItems = this.dockedItems,
- ln = dockedItems.length,
- container = this.container,
- itemIndex = container.indexOf(item),
- positionDirectionMap = this.positionDirectionMap,
- direction = positionDirectionMap[item.getDocked()],
- dockInnerWrapper = this.dockInnerWrapper,
- referenceDirection, i, dockedItem, index, previousItem, slice,
- referenceItem, referenceDocked, referenceWrapper, newWrapper, nestedWrapper;
- this.monitorSizeStateChange();
- this.monitorSizeFlagsChange();
- if (!dockInnerWrapper) {
- dockInnerWrapper = this.link('dockInnerWrapper', new Ext.layout.wrapper.Inner({
- container: this.container
- }));
- }
- if (ln === 0) {
- dockedItems.push(item);
- newWrapper = new DockClass({
- container: this.container,
- direction: direction
- });
- newWrapper.addItem(item);
- newWrapper.getElement().replace(dockInnerWrapper.getElement());
- newWrapper.setInnerWrapper(dockInnerWrapper);
- container.onInitialized('onContainerSizeStateChange', this);
- }
- else {
- for (i = 0; i < ln; i++) {
- dockedItem = dockedItems[i];
- index = container.indexOf(dockedItem);
- if (index > itemIndex) {
- referenceItem = previousItem || dockedItems[0];
- dockedItems.splice(i, 0, item);
- break;
- }
- previousItem = dockedItem;
- }
- if (!referenceItem) {
- referenceItem = dockedItems[ln - 1];
- dockedItems.push(item);
- }
- referenceDocked = referenceItem.getDocked();
- referenceWrapper = referenceItem.$dockWrapper;
- referenceDirection = positionDirectionMap[referenceDocked];
- if (direction === referenceDirection) {
- referenceWrapper.addItem(item);
- }
- else {
- slice = referenceWrapper.getItemsSlice(itemIndex);
- newWrapper = new DockClass({
- container: this.container,
- direction: direction
- });
- if (slice.length > 0) {
- if (slice.length === referenceWrapper.itemsCount) {
- nestedWrapper = referenceWrapper;
- newWrapper.setSizeState(nestedWrapper.getSizeState());
- newWrapper.getElement().replace(nestedWrapper.getElement());
- }
- else {
- nestedWrapper = new DockClass({
- container: this.container,
- direction: referenceDirection
- });
- nestedWrapper.setInnerWrapper(referenceWrapper.getInnerWrapper());
- nestedWrapper.addItems(slice);
- referenceWrapper.setInnerWrapper(newWrapper);
- }
- newWrapper.setInnerWrapper(nestedWrapper);
- }
- else {
- newWrapper.setInnerWrapper(referenceWrapper.getInnerWrapper());
- referenceWrapper.setInnerWrapper(newWrapper);
- }
- newWrapper.addItem(item);
- }
- }
- container.onInitialized('refreshDockedItemLayoutSizeFlags', this, [item]);
- },
- getDockWrapper: function() {
- var dockedItems = this.dockedItems;
- if (dockedItems.length > 0) {
- return dockedItems[0].$dockWrapper;
- }
- return null;
- },
- undockItem: function(item) {
- var dockedItems = this.dockedItems;
- if (item.$dockWrapper) {
- item.$dockWrapper.removeItem(item);
- }
- Ext.Array.remove(dockedItems, item);
- item.setLayoutSizeFlags(0);
- },
- destroy: function() {
- this.dockedItems.length = 0;
- delete this.dockedItems;
- this.callSuper();
- }
- });
- /**
- *
- */
- Ext.define('Ext.layout.Box', {
- extend: 'Ext.layout.Default',
- config: {
- orient: 'horizontal',
- align: 'start',
- pack: 'start'
- },
- alias: 'layout.tablebox',
- layoutBaseClass: 'x-layout-tablebox',
- itemClass: 'x-layout-tablebox-item',
- setContainer: function(container) {
- this.callSuper(arguments);
- container.innerElement.addCls(this.layoutBaseClass);
- container.on('flexchange', 'onItemFlexChange', this, {
- delegate: '> component'
- });
- },
- onItemInnerStateChange: function(item, isInner) {
- this.callSuper(arguments);
- item.toggleCls(this.itemClass, isInner);
- },
- onItemFlexChange: function() {
- }
- });
- /**
- *
- */
- Ext.define('Ext.layout.FlexBox', {
- extend: 'Ext.layout.Box',
- alias: 'layout.box',
- config: {
- align: 'stretch'
- },
- layoutBaseClass: 'x-layout-box',
- itemClass: 'x-layout-box-item',
- setContainer: function(container) {
- this.callSuper(arguments);
- this.monitorSizeFlagsChange();
- },
- applyOrient: function(orient) {
- //<debug error>
- if (orient !== 'horizontal' && orient !== 'vertical') {
- Ext.Logger.error("Invalid box orient of: '" + orient + "', must be either 'horizontal' or 'vertical'");
- }
- //</debug>
- return orient;
- },
- updateOrient: function(orient, oldOrient) {
- var container = this.container,
- delegation = {
- delegate: '> component'
- };
- if (orient === 'horizontal') {
- this.sizePropertyName = 'width';
- }
- else {
- this.sizePropertyName = 'height';
- }
- container.innerElement.swapCls('x-' + orient, 'x-' + oldOrient);
- if (oldOrient) {
- container.un(oldOrient === 'horizontal' ? 'widthchange' : 'heightchange', 'onItemSizeChange', this, delegation);
- this.redrawContainer();
- }
- container.on(orient === 'horizontal' ? 'widthchange' : 'heightchange', 'onItemSizeChange', this, delegation);
- },
- onItemInnerStateChange: function(item, isInner) {
- this.callSuper(arguments);
- var flex, size;
- item.toggleCls(this.itemClass, isInner);
- if (isInner) {
- flex = item.getFlex();
- size = item.get(this.sizePropertyName);
- if (flex) {
- this.doItemFlexChange(item, flex);
- }
- else if (size) {
- this.doItemSizeChange(item, size);
- }
- }
- this.refreshItemSizeState(item);
- },
- refreshItemSizeState: function(item) {
- var isInner = item.isInnerItem(),
- container = this.container,
- LAYOUT_HEIGHT = container.LAYOUT_HEIGHT,
- LAYOUT_WIDTH = container.LAYOUT_WIDTH,
- dimension = this.sizePropertyName,
- layoutSizeFlags = 0,
- containerSizeFlags = container.getSizeFlags();
- if (isInner) {
- layoutSizeFlags |= container.LAYOUT_STRETCHED;
- if (this.getAlign() === 'stretch') {
- layoutSizeFlags |= containerSizeFlags & (dimension === 'width' ? LAYOUT_HEIGHT : LAYOUT_WIDTH);
- }
- if (item.getFlex()) {
- layoutSizeFlags |= containerSizeFlags & (dimension === 'width' ? LAYOUT_WIDTH : LAYOUT_HEIGHT);
- }
- }
- item.setLayoutSizeFlags(layoutSizeFlags);
- },
- refreshAllItemSizedStates: function() {
- var innerItems = this.container.innerItems,
- i, ln, item;
- for (i = 0,ln = innerItems.length; i < ln; i++) {
- item = innerItems[i];
- this.refreshItemSizeState(item);
- }
- },
- onContainerSizeFlagsChange: function() {
- this.refreshAllItemSizedStates();
- this.callSuper(arguments);
- },
- onItemSizeChange: function(item, size) {
- if (item.isInnerItem()) {
- this.doItemSizeChange(item, size);
- }
- },
- doItemSizeChange: function(item, size) {
- if (size) {
- item.setFlex(null);
- this.redrawContainer();
- }
- },
- onItemFlexChange: function(item, flex) {
- if (item.isInnerItem()) {
- this.doItemFlexChange(item, flex);
- this.refreshItemSizeState(item);
- }
- },
- doItemFlexChange: function(item, flex) {
- this.setItemFlex(item, flex);
- if (flex) {
- item.set(this.sizePropertyName, null);
- }
- else {
- this.redrawContainer();
- }
- },
- redrawContainer: function() {
- var container = this.container,
- renderedTo = container.element.dom.parentNode;
- if (renderedTo && renderedTo.nodeType !== 11) {
- container.innerElement.redraw();
- }
- },
- /**
- * Sets the flex of an item in this box layout.
- * @param {Ext.Component} item The item of this layout which you want to update the flex of.
- * @param {Number} flex The flex to set on this method
- */
- setItemFlex: function(item, flex) {
- var element = item.element;
- element.toggleCls('x-flexed', !!flex);
- element.setStyle('-webkit-box-flex', flex);
- },
- convertPosition: function(position) {
- var positionMap = this.positionMap;
- if (positionMap.hasOwnProperty(position)) {
- return positionMap[position];
- }
- return position;
- },
- applyAlign: function(align) {
- return this.convertPosition(align);
- },
- updateAlign: function(align, oldAlign) {
- var container = this.container;
- container.innerElement.swapCls(align, oldAlign, true, 'x-align');
- if (oldAlign !== undefined) {
- this.refreshAllItemSizedStates();
- }
- },
- applyPack: function(pack) {
- return this.convertPosition(pack);
- },
- updatePack: function(pack, oldPack) {
- this.container.innerElement.swapCls(pack, oldPack, true, 'x-pack');
- }
- });
- /**
- *
- */
- Ext.define('Ext.layout.HBox', {
- extend: 'Ext.layout.FlexBox',
- alias: 'layout.hbox'
- });
- /**
- *
- */
- Ext.define('Ext.layout.Fit', {
- extend: 'Ext.layout.Default',
- isFit: true,
- alias: 'layout.fit',
- layoutClass: 'x-layout-fit',
- itemClass: 'x-layout-fit-item',
- setContainer: function(container) {
- this.callSuper(arguments);
- container.innerElement.addCls(this.layoutClass);
- this.onContainerSizeFlagsChange();
- this.monitorSizeFlagsChange();
- },
- onContainerSizeFlagsChange: function() {
- var container = this.container,
- sizeFlags = container.getSizeFlags(),
- stretched = Boolean(sizeFlags & container.LAYOUT_STRETCHED),
- innerItems = container.innerItems,
- i, ln, item;
- this.callSuper();
- for (i = 0,ln = innerItems.length; i < ln; i++) {
- item = innerItems[i];
- item.setLayoutSizeFlags(sizeFlags);
- }
- container.innerElement.toggleCls('x-stretched', stretched);
- },
- onItemInnerStateChange: function(item, isInner) {
- this.callSuper(arguments);
- item.toggleCls(this.itemClass, isInner);
- item.setLayoutSizeFlags(isInner ? this.container.getSizeFlags() : 0);
- }
- });
- /**
- *
- */
- Ext.define('Ext.layout.Float', {
- extend: 'Ext.layout.Default',
- alias: 'layout.float',
- config: {
- direction: 'left'
- },
- layoutClass: 'layout-float',
- itemClass: 'layout-float-item',
- setContainer: function(container) {
- this.callSuper(arguments);
- container.innerElement.addCls(this.layoutClass);
- },
- onItemInnerStateChange: function(item, isInner) {
- this.callSuper(arguments);
- item.toggleCls(this.itemClass, isInner);
- },
- updateDirection: function(direction, oldDirection) {
- var prefix = 'direction-';
- this.container.innerElement.swapCls(prefix + direction, prefix + oldDirection);
- }
- });
- /**
- *
- */
- Ext.define('Ext.layout.wrapper.Dock', {
- requires: [
- 'Ext.util.Wrapper'
- ],
- config: {
- direction: 'horizontal',
- element: {
- className: 'x-dock'
- },
- bodyElement: {
- className: 'x-dock-body'
- },
- innerWrapper: null,
- sizeState: false,
- container: null
- },
- positionMap: {
- top: 'start',
- left: 'start',
- bottom: 'end',
- right: 'end'
- },
- constructor: function(config) {
- this.items = {
- start: [],
- end: []
- };
- this.itemsCount = 0;
- this.initConfig(config);
- },
- addItems: function(items) {
- var i, ln, item;
- for (i = 0, ln = items.length; i < ln; i++) {
- item = items[i];
- this.addItem(item);
- }
- },
- addItem: function(item) {
- var docked = item.getDocked(),
- position = this.positionMap[docked],
- wrapper = item.$dockWrapper,
- container = this.getContainer(),
- index = container.indexOf(item),
- items = this.items,
- sideItems = items[position],
- itemWrapper, element, i, ln, sibling, referenceElement, siblingIndex;
- if (wrapper) {
- wrapper.removeItem(item);
- }
- item.$dockWrapper = this;
- itemWrapper = item.link('$dockItemWrapper', new Ext.util.Wrapper({
- className: 'x-dock-item'
- }));
- item.addCls('x-docked-' + docked);
- element = itemWrapper.element;
- for (i = 0, ln = sideItems.length; i < ln; i++) {
- sibling = sideItems[i];
- siblingIndex = container.indexOf(sibling);
- if (siblingIndex > index) {
- referenceElement = sibling.element;
- sideItems.splice(i, 0, item);
- break;
- }
- }
- if (!referenceElement) {
- sideItems.push(item);
- referenceElement = this.getBodyElement();
- }
- this.itemsCount++;
- if (position === 'start') {
- element.insertBefore(referenceElement);
- }
- else {
- element.insertAfter(referenceElement);
- }
- itemWrapper.wrap(item.element);
- itemWrapper.bindSize(this.getDirection() === 'horizontal' ? 'width' : 'height');
- },
- removeItem: function(item) {
- var position = item.getDocked(),
- items = this.items[this.positionMap[position]];
- item.removeCls('x-docked-' + position);
- Ext.Array.remove(items, item);
- item.unlink('$dockItemWrapper');
- item.element.detach();
- delete item.$dockWrapper;
- if (--this.itemsCount === 0) {
- this.destroy();
- }
- },
- getItemsSlice: function(index) {
- var container = this.getContainer(),
- items = this.items,
- slice = [],
- sideItems, i, ln, item;
- for (sideItems = items.start, i = 0, ln = sideItems.length; i < ln; i++) {
- item = sideItems[i];
- if (container.indexOf(item) > index) {
- slice.push(item);
- }
- }
- for (sideItems = items.end, i = 0, ln = sideItems.length; i < ln; i++) {
- item = sideItems[i];
- if (container.indexOf(item) > index) {
- slice.push(item);
- }
- }
- return slice;
- },
- applyElement: function(element) {
- return Ext.Element.create(element);
- },
- updateElement: function(element) {
- element.addCls('x-dock-' + this.getDirection());
- },
- applyBodyElement: function(bodyElement) {
- return Ext.Element.create(bodyElement);
- },
- updateBodyElement: function(bodyElement) {
- this.getElement().append(bodyElement);
- },
- updateInnerWrapper: function(innerWrapper, oldInnerWrapper) {
- var innerElement = this.getBodyElement();
- if (oldInnerWrapper && oldInnerWrapper.$outerWrapper === this) {
- innerElement.remove(oldInnerWrapper.getElement());
- delete oldInnerWrapper.$outerWrapper;
- }
- if (innerWrapper) {
- innerWrapper.setSizeState(this.getSizeState());
- innerWrapper.$outerWrapper = this;
- innerElement.append(innerWrapper.getElement());
- }
- },
- updateSizeState: function(state) {
- var innerWrapper = this.getInnerWrapper();
- this.getElement().setSizeState(state);
- if (innerWrapper) {
- innerWrapper.setSizeState(state);
- }
- },
- destroy: function() {
- var innerWrapper = this.getInnerWrapper(),
- outerWrapper = this.$outerWrapper;
- if (innerWrapper) {
- if (outerWrapper) {
- outerWrapper.setInnerWrapper(innerWrapper);
- }
- else {
- innerWrapper.getElement().replace(this.getElement());
- delete innerWrapper.$outerWrapper;
- }
- }
- delete this.$outerWrapper;
- this.setInnerWrapper(null);
- this.unlink('_bodyElement', '_element');
- this.callSuper();
- }
- });
- /**
- *
- */
- Ext.define('Ext.layout.VBox', {
- extend: 'Ext.layout.FlexBox',
- alias: 'layout.vbox',
- config: {
- orient: 'vertical'
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.layout.card.Abstract', {
- extend: 'Ext.Evented',
- isAnimation: true,
- config: {
- direction: 'left',
- duration: null,
- reverse: null,
- layout: null
- },
- updateLayout: function() {
- this.enable();
- },
- enable: function() {
- var layout = this.getLayout();
- if (layout) {
- layout.onBefore('activeitemchange', 'onActiveItemChange', this);
- }
- },
- disable: function() {
- var layout = this.getLayout();
- if (this.isAnimating) {
- this.stopAnimation();
- }
- if (layout) {
- layout.unBefore('activeitemchange', 'onActiveItemChange', this);
- }
- },
- onActiveItemChange: Ext.emptyFn,
- destroy: function() {
- var layout = this.getLayout();
- if (this.isAnimating) {
- this.stopAnimation();
- }
- if (layout) {
- layout.unBefore('activeitemchange', 'onActiveItemChange', this);
- }
- this.setLayout(null);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.State', {
- isAnimatable: {
- 'background-color' : true,
- 'background-image' : true,
- 'background-position': true,
- 'border-bottom-color': true,
- 'border-bottom-width': true,
- 'border-color' : true,
- 'border-left-color' : true,
- 'border-left-width' : true,
- 'border-right-color' : true,
- 'border-right-width' : true,
- 'border-spacing' : true,
- 'border-top-color' : true,
- 'border-top-width' : true,
- 'border-width' : true,
- 'bottom' : true,
- 'color' : true,
- 'crop' : true,
- 'font-size' : true,
- 'font-weight' : true,
- 'height' : true,
- 'left' : true,
- 'letter-spacing' : true,
- 'line-height' : true,
- 'margin-bottom' : true,
- 'margin-left' : true,
- 'margin-right' : true,
- 'margin-top' : true,
- 'max-height' : true,
- 'max-width' : true,
- 'min-height' : true,
- 'min-width' : true,
- 'opacity' : true,
- 'outline-color' : true,
- 'outline-offset' : true,
- 'outline-width' : true,
- 'padding-bottom' : true,
- 'padding-left' : true,
- 'padding-right' : true,
- 'padding-top' : true,
- 'right' : true,
- 'text-indent' : true,
- 'text-shadow' : true,
- 'top' : true,
- 'vertical-align' : true,
- 'visibility' : true,
- 'width' : true,
- 'word-spacing' : true,
- 'z-index' : true,
- 'zoom' : true,
- 'transform' : true
- },
- constructor: function(data) {
- this.data = {};
- this.set(data);
- },
- setConfig: function(data) {
- this.set(data);
- return this;
- },
- setRaw: function(data) {
- this.data = data;
- return this;
- },
- clear: function() {
- return this.setRaw({});
- },
- setTransform: function(name, value) {
- var data = this.data,
- isArray = Ext.isArray(value),
- transform = data.transform,
- ln, key;
- if (!transform) {
- transform = data.transform = {
- translateX: 0,
- translateY: 0,
- translateZ: 0,
- scaleX: 1,
- scaleY: 1,
- scaleZ: 1,
- rotate: 0,
- rotateX: 0,
- rotateY: 0,
- rotateZ: 0,
- skewX: 0,
- skewY: 0
- };
- }
- if (typeof name == 'string') {
- switch (name) {
- case 'translate':
- if (isArray) {
- ln = value.length;
- if (ln == 0) { break; }
- transform.translateX = value[0];
- if (ln == 1) { break; }
- transform.translateY = value[1];
- if (ln == 2) { break; }
- transform.translateZ = value[2];
- }
- else {
- transform.translateX = value;
- }
- break;
- case 'rotate':
- if (isArray) {
- ln = value.length;
- if (ln == 0) { break; }
- transform.rotateX = value[0];
- if (ln == 1) { break; }
- transform.rotateY = value[1];
- if (ln == 2) { break; }
- transform.rotateZ = value[2];
- }
- else {
- transform.rotate = value;
- }
- break;
- case 'scale':
- if (isArray) {
- ln = value.length;
- if (ln == 0) { break; }
- transform.scaleX = value[0];
- if (ln == 1) { break; }
- transform.scaleY = value[1];
- if (ln == 2) { break; }
- transform.scaleZ = value[2];
- }
- else {
- transform.scaleX = value;
- transform.scaleY = value;
- }
- break;
- case 'skew':
- if (isArray) {
- ln = value.length;
- if (ln == 0) { break; }
- transform.skewX = value[0];
- if (ln == 1) { break; }
- transform.skewY = value[1];
- }
- else {
- transform.skewX = value;
- }
- break;
- default:
- transform[name] = value;
- }
- }
- else {
- for (key in name) {
- if (name.hasOwnProperty(key)) {
- value = name[key];
- this.setTransform(key, value);
- }
- }
- }
- },
- set: function(name, value) {
- var data = this.data,
- key;
- if (typeof name != 'string') {
- for (key in name) {
- value = name[key];
- if (key === 'transform') {
- this.setTransform(value);
- }
- else {
- data[key] = value;
- }
- }
- }
- else {
- if (name === 'transform') {
- this.setTransform(value);
- }
- else {
- data[name] = value;
- }
- }
- return this;
- },
- unset: function(name) {
- var data = this.data;
- if (data.hasOwnProperty(name)) {
- delete data[name];
- }
- return this;
- },
- getData: function() {
- return this.data;
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.animation.Abstract', {
- extend: 'Ext.Evented',
- isAnimation: true,
- requires: [
- 'Ext.fx.State'
- ],
- config: {
- name: '',
- element: null,
- /**
- * @cfg
- * Before configuration.
- */
- before: null,
- from: {},
- to: {},
- after: null,
- states: {},
- duration: 300,
- /**
- * @cfg
- * Easing type.
- */
- easing: 'linear',
- iteration: 1,
- direction: 'normal',
- delay: 0,
- onBeforeStart: null,
- onEnd: null,
- onBeforeEnd: null,
- scope: null,
- reverse: null,
- preserveEndState: false,
- replacePrevious: true
- },
- STATE_FROM: '0%',
- STATE_TO: '100%',
- DIRECTION_UP: 'up',
- DIRECTION_DOWN: 'down',
- DIRECTION_LEFT: 'left',
- DIRECTION_RIGHT: 'right',
- stateNameRegex: /^(?:[\d\.]+)%$/,
- constructor: function() {
- this.states = {};
- this.callParent(arguments);
- return this;
- },
- applyElement: function(element) {
- return Ext.get(element);
- },
- applyBefore: function(before, current) {
- if (before) {
- return Ext.factory(before, Ext.fx.State, current);
- }
- },
- applyAfter: function(after, current) {
- if (after) {
- return Ext.factory(after, Ext.fx.State, current);
- }
- },
- setFrom: function(from) {
- return this.setState(this.STATE_FROM, from);
- },
- setTo: function(to) {
- return this.setState(this.STATE_TO, to);
- },
- getFrom: function() {
- return this.getState(this.STATE_FROM);
- },
- getTo: function() {
- return this.getState(this.STATE_TO);
- },
- setStates: function(states) {
- var validNameRegex = this.stateNameRegex,
- name;
- for (name in states) {
- if (validNameRegex.test(name)) {
- this.setState(name, states[name]);
- }
- }
- return this;
- },
- getStates: function() {
- return this.states;
- },
- stop: function() {
- this.fireEvent('stop', this);
- },
- destroy: function() {
- this.stop();
- this.callParent();
- },
- setState: function(name, state) {
- var states = this.getStates(),
- stateInstance;
- stateInstance = Ext.factory(state, Ext.fx.State, states[name]);
- if (stateInstance) {
- states[name] = stateInstance;
- }
- //<debug error>
- else if (name === this.STATE_TO) {
- Ext.Logger.error("Setting and invalid '100%' / 'to' state of: " + state);
- }
- //</debug>
- return this;
- },
- getState: function(name) {
- return this.getStates()[name];
- },
- getData: function() {
- var states = this.getStates(),
- statesData = {},
- before = this.getBefore(),
- after = this.getAfter(),
- from = states[this.STATE_FROM],
- to = states[this.STATE_TO],
- fromData = from.getData(),
- toData = to.getData(),
- data, name, state;
- for (name in states) {
- if (states.hasOwnProperty(name)) {
- state = states[name];
- data = state.getData();
- statesData[name] = data;
- }
- }
- if (Ext.os.is.Android2) {
- statesData['0.0001%'] = fromData;
- }
- return {
- before: before ? before.getData() : {},
- after: after ? after.getData() : {},
- states: statesData,
- from: fromData,
- to: toData,
- duration: this.getDuration(),
- iteration: this.getIteration(),
- direction: this.getDirection(),
- easing: this.getEasing(),
- delay: this.getDelay(),
- onEnd: this.getOnEnd(),
- onBeforeEnd: this.getOnBeforeEnd(),
- onBeforeStart: this.getOnBeforeStart(),
- scope: this.getScope(),
- preserveEndState: this.getPreserveEndState(),
- replacePrevious: this.getReplacePrevious()
- };
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.animation.Slide', {
- extend: 'Ext.fx.animation.Abstract',
- alternateClassName: 'Ext.fx.animation.SlideIn',
- alias: ['animation.slide', 'animation.slideIn'],
- config: {
- /**
- * @cfg {String} direction The direction of which the slide animates
- * @accessor
- */
- direction: 'left',
- /**
- * @cfg {Boolean} out True if you want to make this animation slide out, instead of slide in.
- * @accessor
- */
- out: false,
- /**
- * @cfg {Number} offset The offset that the animation should go offscreen before entering (or when exiting)
- * @accessor
- */
- offset: 0,
- /**
- * @cfg
- * @inheritdoc
- */
- easing: 'auto',
- containerBox: 'auto',
- elementBox: 'auto',
- isElementBoxFit: true,
- useCssTransform: true
- },
- reverseDirectionMap: {
- up: 'down',
- down: 'up',
- left: 'right',
- right: 'left'
- },
- applyEasing: function(easing) {
- if (easing === 'auto') {
- return 'ease-' + ((this.getOut()) ? 'in' : 'out');
- }
- return easing;
- },
- getContainerBox: function() {
- var box = this._containerBox;
- if (box === 'auto') {
- box = this.getElement().getParent().getPageBox();
- }
- return box;
- },
- getElementBox: function() {
- var box = this._elementBox;
- if (this.getIsElementBoxFit()) {
- return this.getContainerBox();
- }
- if (box === 'auto') {
- box = this.getElement().getPageBox();
- }
- return box;
- },
- getData: function() {
- var elementBox = this.getElementBox(),
- containerBox = this.getContainerBox(),
- box = elementBox ? elementBox : containerBox,
- from = this.getFrom(),
- to = this.getTo(),
- out = this.getOut(),
- offset = this.getOffset(),
- direction = this.getDirection(),
- useCssTransform = this.getUseCssTransform(),
- reverse = this.getReverse(),
- translateX = 0,
- translateY = 0,
- fromX, fromY, toX, toY;
- if (reverse) {
- direction = this.reverseDirectionMap[direction];
- }
- switch (direction) {
- case this.DIRECTION_UP:
- if (out) {
- translateY = containerBox.top - box.top - box.height - offset;
- }
- else {
- translateY = containerBox.bottom - box.bottom + box.height + offset;
- }
- break;
- case this.DIRECTION_DOWN:
- if (out) {
- translateY = containerBox.bottom - box.bottom + box.height + offset;
- }
- else {
- translateY = containerBox.top - box.height - box.top - offset;
- }
- break;
- case this.DIRECTION_RIGHT:
- if (out) {
- translateX = containerBox.right - box.right + box.width + offset;
- }
- else {
- translateX = containerBox.left - box.left - box.width - offset;
- }
- break;
- case this.DIRECTION_LEFT:
- if (out) {
- translateX = containerBox.left - box.left - box.width - offset;
- }
- else {
- translateX = containerBox.right - box.right + box.width + offset;
- }
- break;
- }
- fromX = (out) ? 0 : translateX;
- fromY = (out) ? 0 : translateY;
- if (useCssTransform) {
- from.setTransform({
- translateX: fromX,
- translateY: fromY
- });
- }
- else {
- from.set('left', fromX);
- from.set('top', fromY);
- }
- toX = (out) ? translateX : 0;
- toY = (out) ? translateY : 0;
- if (useCssTransform) {
- to.setTransform({
- translateX: toX,
- translateY: toY
- });
- }
- else {
- to.set('left', toX);
- to.set('top', toY);
- }
- return this.callParent(arguments);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.animation.SlideOut', {
- extend: 'Ext.fx.animation.Slide',
- alias: ['animation.slideOut'],
- config: {
- // @hide
- out: true
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.animation.Fade', {
- extend: 'Ext.fx.animation.Abstract',
- alternateClassName: 'Ext.fx.animation.FadeIn',
- alias: ['animation.fade', 'animation.fadeIn'],
- config: {
- /**
- * @cfg {Boolean} out True if you want to make this animation fade out, instead of fade in.
- * @accessor
- */
- out: false,
- before: {
- display: null,
- opacity: 0
- },
- after: {
- opacity: null
- },
- reverse: null
- },
- updateOut: function(newOut) {
- var to = this.getTo(),
- from = this.getFrom();
- if (newOut) {
- from.set('opacity', 1);
- to.set('opacity', 0);
- } else {
- from.set('opacity', 0);
- to.set('opacity', 1);
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.animation.FadeOut', {
- extend: 'Ext.fx.animation.Fade',
- alias: 'animation.fadeOut',
- config: {
- // @hide
- out: true,
- before: {}
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.animation.Flip', {
- extend: 'Ext.fx.animation.Abstract',
- alias: 'animation.flip',
- config: {
- easing: 'ease-in',
- /**
- * @cfg {String} direction The direction of which the slide animates
- * @accessor
- */
- direction: 'right',
- half: false,
- out: null
- },
- getData: function() {
- var from = this.getFrom(),
- to = this.getTo(),
- direction = this.getDirection(),
- out = this.getOut(),
- half = this.getHalf(),
- rotate = (half) ? 90 : 180,
- fromScale = 1,
- toScale = 1,
- fromRotateX = 0,
- fromRotateY = 0,
- toRotateX = 0,
- toRotateY = 0;
- if (out) {
- toScale = 0.8;
- }
- else {
- fromScale = 0.8;
- }
- switch (direction) {
- case this.DIRECTION_UP:
- if (out) {
- toRotateX = rotate;
- }
- else {
- fromRotateX = -rotate;
- }
- break;
- case this.DIRECTION_DOWN:
- if (out) {
- toRotateX = -rotate;
- }
- else {
- fromRotateX = rotate;
- }
- break;
- case this.DIRECTION_RIGHT:
- if (out) {
- toRotateY = -rotate;
- }
- else {
- fromRotateY = rotate;
- }
- break;
- case this.DIRECTION_LEFT:
- if (out) {
- toRotateY = -rotate;
- }
- else {
- fromRotateY = rotate;
- }
- break;
- }
- from.setTransform({
- rotateX: fromRotateX,
- rotateY: fromRotateY,
- scale: fromScale
- });
- to.setTransform({
- rotateX: toRotateX,
- rotateY: toRotateY,
- scale: toScale
- });
- return this.callParent(arguments);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.animation.Pop', {
- extend: 'Ext.fx.animation.Abstract',
- alias: ['animation.pop', 'animation.popIn'],
- alternateClassName: 'Ext.fx.animation.PopIn',
- config: {
- /**
- * @cfg {Boolean} out True if you want to make this animation pop out, instead of pop in.
- * @accessor
- */
- out: false,
- before: {
- display: null,
- opacity: 0
- },
- after: {
- opacity: null
- }
- },
- getData: function() {
- var to = this.getTo(),
- from = this.getFrom(),
- out = this.getOut();
- if (out) {
- from.set('opacity', 1);
- from.setTransform({
- scale: 1
- });
- to.set('opacity', 0);
- to.setTransform({
- scale: 0
- });
- }
- else {
- from.set('opacity', 0);
- from.setTransform({
- scale: 0
- });
- to.set('opacity', 1);
- to.setTransform({
- scale: 1
- });
- }
- return this.callParent(arguments);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.animation.PopOut', {
- extend: 'Ext.fx.animation.Pop',
- alias: 'animation.popOut',
- config: {
- // @hide
- out: true,
- before: {}
- }
- });
- /**
- * @private
- * @author Jacky Nguyen <jacky@sencha.com>
- */
- Ext.define('Ext.fx.Animation', {
- requires: [
- 'Ext.fx.animation.Slide',
- 'Ext.fx.animation.SlideOut',
- 'Ext.fx.animation.Fade',
- 'Ext.fx.animation.FadeOut',
- 'Ext.fx.animation.Flip',
- 'Ext.fx.animation.Pop',
- 'Ext.fx.animation.PopOut'
- // 'Ext.fx.animation.Cube'
- ],
- constructor: function(config) {
- var defaultClass = Ext.fx.animation.Abstract,
- type;
- if (typeof config == 'string') {
- type = config;
- config = {};
- }
- else if (config && config.type) {
- type = config.type;
- }
- if (type) {
- if (Ext.os.is.Android2) {
- if (type == 'pop') {
- type = 'fade';
- }
- if (type == 'popIn') {
- type = 'fadeIn';
- }
- if (type == 'popOut') {
- type = 'fadeOut';
- }
- }
- defaultClass = Ext.ClassManager.getByAlias('animation.' + type);
- //<debug error>
- if (!defaultClass) {
- Ext.Logger.error("Invalid animation type of: '" + type + "'");
- }
- //</debug>
- }
- return Ext.factory(config, defaultClass);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.layout.card.Style', {
- extend: 'Ext.fx.layout.card.Abstract',
- requires: [
- 'Ext.fx.Animation'
- ],
- config: {
- inAnimation: {
- before: {
- visibility: null
- },
- preserveEndState: false,
- replacePrevious: true
- },
- outAnimation: {
- preserveEndState: false,
- replacePrevious: true
- }
- },
- constructor: function(config) {
- var inAnimation, outAnimation;
- this.initConfig(config);
- this.endAnimationCounter = 0;
- inAnimation = this.getInAnimation();
- outAnimation = this.getOutAnimation();
- inAnimation.on('animationend', 'incrementEnd', this);
- outAnimation.on('animationend', 'incrementEnd', this);
- },
- updateDirection: function(direction) {
- this.getInAnimation().setDirection(direction);
- this.getOutAnimation().setDirection(direction);
- },
- updateDuration: function(duration) {
- this.getInAnimation().setDuration(duration);
- this.getOutAnimation().setDuration(duration);
- },
- updateReverse: function(reverse) {
- this.getInAnimation().setReverse(reverse);
- this.getOutAnimation().setReverse(reverse);
- },
- incrementEnd: function() {
- this.endAnimationCounter++;
- if (this.endAnimationCounter > 1) {
- this.endAnimationCounter = 0;
- this.fireEvent('animationend', this);
- }
- },
- applyInAnimation: function(animation, inAnimation) {
- return Ext.factory(animation, Ext.fx.Animation, inAnimation);
- },
- applyOutAnimation: function(animation, outAnimation) {
- return Ext.factory(animation, Ext.fx.Animation, outAnimation);
- },
- updateInAnimation: function(animation) {
- animation.setScope(this);
- },
- updateOutAnimation: function(animation) {
- animation.setScope(this);
- },
- onActiveItemChange: function(cardLayout, newItem, oldItem, options, controller) {
- var inAnimation = this.getInAnimation(),
- outAnimation = this.getOutAnimation(),
- inElement, outElement;
- if (newItem && oldItem && oldItem.isPainted()) {
- inElement = newItem.renderElement;
- outElement = oldItem.renderElement;
- inAnimation.setElement(inElement);
- outAnimation.setElement(outElement);
- outAnimation.setOnBeforeEnd(function(element, interrupted) {
- if (interrupted || Ext.Animator.hasRunningAnimations(element)) {
- controller.firingArguments[1] = null;
- controller.firingArguments[2] = null;
- }
- });
- outAnimation.setOnEnd(function() {
- controller.resume();
- });
- inElement.dom.style.setProperty('visibility', 'hidden', '!important');
- newItem.show();
- Ext.Animator.run([outAnimation, inAnimation]);
- controller.pause();
- }
- },
- destroy: function () {
- Ext.destroy(this.getInAnimation(), this.getOutAnimation());
- this.callParent(arguments);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.layout.card.Slide', {
- extend: 'Ext.fx.layout.card.Style',
- alias: 'fx.layout.card.slide',
- config: {
- inAnimation: {
- type: 'slide',
- easing: 'ease-out'
- },
- outAnimation: {
- type: 'slide',
- easing: 'ease-out',
- out: true
- }
- },
- updateReverse: function(reverse) {
- this.getInAnimation().setReverse(reverse);
- this.getOutAnimation().setReverse(reverse);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.layout.card.Cover', {
- extend: 'Ext.fx.layout.card.Style',
- alias: 'fx.layout.card.cover',
- config: {
- reverse: null,
- inAnimation: {
- before: {
- 'z-index': 100
- },
- after: {
- 'z-index': 0
- },
- type: 'slide',
- easing: 'ease-out'
- },
- outAnimation: {
- easing: 'ease-out',
- from: {
- opacity: 0.99
- },
- to: {
- opacity: 1
- },
- out: true
- }
- },
- updateReverse: function(reverse) {
- this.getInAnimation().setReverse(reverse);
- this.getOutAnimation().setReverse(reverse);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.layout.card.Reveal', {
- extend: 'Ext.fx.layout.card.Style',
- alias: 'fx.layout.card.reveal',
- config: {
- inAnimation: {
- easing: 'ease-out',
- from: {
- opacity: 0.99
- },
- to: {
- opacity: 1
- }
- },
- outAnimation: {
- before: {
- 'z-index': 100
- },
- after: {
- 'z-index': 0
- },
- type: 'slide',
- easing: 'ease-out',
- out: true
- }
- },
- updateReverse: function(reverse) {
- this.getInAnimation().setReverse(reverse);
- this.getOutAnimation().setReverse(reverse);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.layout.card.Fade', {
- extend: 'Ext.fx.layout.card.Style',
- alias: 'fx.layout.card.fade',
- config: {
- reverse: null,
-
- inAnimation: {
- type: 'fade',
- easing: 'ease-out'
- },
- outAnimation: {
- type: 'fade',
- easing: 'ease-out',
- out: true
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.layout.card.Flip', {
- extend: 'Ext.fx.layout.card.Style',
- alias: 'fx.layout.card.flip',
- config: {
- duration: 500,
- inAnimation: {
- type: 'flip',
- half: true,
- easing: 'ease-out',
- before: {
- 'backface-visibility': 'hidden'
- },
- after: {
- 'backface-visibility': null
- }
- },
- outAnimation: {
- type: 'flip',
- half: true,
- easing: 'ease-in',
- before: {
- 'backface-visibility': 'hidden'
- },
- after: {
- 'backface-visibility': null
- },
- out: true
- }
- },
- updateDuration: function(duration) {
- var halfDuration = duration / 2,
- inAnimation = this.getInAnimation(),
- outAnimation = this.getOutAnimation();
- inAnimation.setDelay(halfDuration);
- inAnimation.setDuration(halfDuration);
- outAnimation.setDuration(halfDuration);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.layout.card.Pop', {
- extend: 'Ext.fx.layout.card.Style',
- alias: 'fx.layout.card.pop',
- config: {
- duration: 500,
- inAnimation: {
- type: 'pop',
- easing: 'ease-out'
- },
- outAnimation: {
- type: 'pop',
- easing: 'ease-in',
- out: true
- }
- },
- updateDuration: function(duration) {
- var halfDuration = duration / 2,
- inAnimation = this.getInAnimation(),
- outAnimation = this.getOutAnimation();
- inAnimation.setDelay(halfDuration);
- inAnimation.setDuration(halfDuration);
- outAnimation.setDuration(halfDuration);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.layout.card.Scroll', {
- extend: 'Ext.fx.layout.card.Abstract',
- requires: [
- 'Ext.fx.easing.Linear'
- ],
- alias: 'fx.layout.card.scroll',
- config: {
- duration: 150
- },
- constructor: function(config) {
- this.initConfig(config);
- this.doAnimationFrame = Ext.Function.bind(this.doAnimationFrame, this);
- },
- getEasing: function() {
- var easing = this.easing;
- if (!easing) {
- this.easing = easing = new Ext.fx.easing.Linear();
- }
- return easing;
- },
- updateDuration: function(duration) {
- this.getEasing().setDuration(duration);
- },
- onActiveItemChange: function(cardLayout, newItem, oldItem, options, controller) {
- var direction = this.getDirection(),
- easing = this.getEasing(),
- containerElement, inElement, outElement, containerWidth, containerHeight, reverse;
- if (newItem && oldItem) {
- if (this.isAnimating) {
- this.stopAnimation();
- }
- newItem.setWidth('100%');
- newItem.setHeight('100%');
- containerElement = this.getLayout().container.innerElement;
- containerWidth = containerElement.getWidth();
- containerHeight = containerElement.getHeight();
- inElement = newItem.renderElement;
- outElement = oldItem.renderElement;
- this.oldItem = oldItem;
- this.newItem = newItem;
- this.currentEventController = controller;
- this.containerElement = containerElement;
- this.isReverse = reverse = this.getReverse();
- newItem.show();
- if (direction == 'right') {
- direction = 'left';
- this.isReverse = reverse = !reverse;
- }
- else if (direction == 'down') {
- direction = 'up';
- this.isReverse = reverse = !reverse;
- }
- if (direction == 'left') {
- if (reverse) {
- easing.setConfig({
- startValue: containerWidth,
- endValue: 0
- });
- containerElement.dom.scrollLeft = containerWidth;
- outElement.setLeft(containerWidth);
- }
- else {
- easing.setConfig({
- startValue: 0,
- endValue: containerWidth
- });
- inElement.setLeft(containerWidth);
- }
- }
- else {
- if (reverse) {
- easing.setConfig({
- startValue: containerHeight,
- endValue: 0
- });
- containerElement.dom.scrollTop = containerHeight;
- outElement.setTop(containerHeight);
- }
- else {
- easing.setConfig({
- startValue: 0,
- endValue: containerHeight
- });
- inElement.setTop(containerHeight);
- }
- }
- this.startAnimation();
- controller.pause();
- }
- },
- startAnimation: function() {
- this.isAnimating = true;
- this.getEasing().setStartTime(Date.now());
- this.timer = setInterval(this.doAnimationFrame, 20);
- this.doAnimationFrame();
- },
- doAnimationFrame: function() {
- var easing = this.getEasing(),
- direction = this.getDirection(),
- scroll = 'scrollTop',
- value;
- if (direction == 'left' || direction == 'right') {
- scroll = 'scrollLeft';
- }
- if (easing.isEnded) {
- this.stopAnimation();
- }
- else {
- value = easing.getValue();
- this.containerElement.dom[scroll] = value;
- }
- },
- stopAnimation: function() {
- var me = this,
- direction = me.getDirection(),
- scroll = 'setTop',
- oldItem = me.oldItem,
- newItem = me.newItem;
- if (direction == 'left' || direction == 'right') {
- scroll = 'setLeft';
- }
- me.currentEventController.resume();
- if (me.isReverse && oldItem && oldItem.renderElement && oldItem.renderElement.dom) {
- oldItem.renderElement[scroll](null);
- }
- else if (newItem && newItem.renderElement && newItem.renderElement.dom) {
- newItem.renderElement[scroll](null);
- }
- clearInterval(me.timer);
- me.isAnimating = false;
- me.fireEvent('animationend', me);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.layout.Card', {
- requires: [
- 'Ext.fx.layout.card.Slide',
- 'Ext.fx.layout.card.Cover',
- 'Ext.fx.layout.card.Reveal',
- 'Ext.fx.layout.card.Fade',
- 'Ext.fx.layout.card.Flip',
- 'Ext.fx.layout.card.Pop',
- // 'Ext.fx.layout.card.Cube',
- 'Ext.fx.layout.card.Scroll'
- ],
- constructor: function(config) {
- var defaultClass = Ext.fx.layout.card.Abstract,
- type;
- if (!config) {
- return null;
- }
- if (typeof config == 'string') {
- type = config;
- config = {};
- }
- else if (config.type) {
- type = config.type;
- }
- config.elementBox = false;
- if (type) {
- if (Ext.os.is.Android2) {
- // In Android 2 we only support scroll and fade. Otherwise force it to slide.
- if (type != 'fade') {
- type = 'scroll';
- }
- }
- else if (type === 'slide' && Ext.browser.is.ChromeMobile) {
- type = 'scroll';
- }
- defaultClass = Ext.ClassManager.getByAlias('fx.layout.card.' + type);
- //<debug error>
- if (!defaultClass) {
- Ext.Logger.error("Unknown card animation type: '" + type + "'");
- }
- //</debug>
- }
- return Ext.factory(config, defaultClass);
- }
- });
- /**
- * @aside guide layouts
- * @aside video layouts
- *
- * Sometimes you want to show several screens worth of information but you've only got a small screen to work with.
- * TabPanels and Carousels both enable you to see one screen of many at a time, and underneath they both use a Card
- * Layout.
- *
- * Card Layout takes the size of the Container it is applied to and sizes the currently active item to fill the
- * Container completely. It then hides the rest of the items, allowing you to change which one is currently visible but
- * only showing one at once:
- *
- * {@img ../guides/layouts/card.jpg}
- *
- *
- * Here the gray box is our Container, and the blue box inside it is the currently active card. The three other cards
- * are hidden from view, but can be swapped in later. While it's not too common to create Card layouts directly, you
- * can do so like this:
- *
- * var panel = Ext.create('Ext.Panel', {
- * layout: 'card',
- * items: [
- * {
- * html: "First Item"
- * },
- * {
- * html: "Second Item"
- * },
- * {
- * html: "Third Item"
- * },
- * {
- * html: "Fourth Item"
- * }
- * ]
- * });
- *
- * panel.{@link Ext.Container#setActiveItem setActiveItem}(1);
- *
- * Here we create a Panel with a Card Layout and later set the second item active (the active item index is zero-based,
- * so 1 corresponds to the second item). Normally you're better off using a {@link Ext.tab.Panel tab panel} or a
- * {@link Ext.carousel.Carousel carousel}.
- *
- * For a more detailed overview of what layouts are and the types of layouts shipped with Sencha Touch 2, check out the
- * [Layout Guide](#!/guide/layouts).
- */
- Ext.define('Ext.layout.Card', {
- extend: 'Ext.layout.Default',
- alias: 'layout.card',
- isCard: true,
- /**
- * @event activeitemchange
- * @preventable doActiveItemChange
- * Fires when an card is made active
- * @param {Ext.layout.Card} this The layout instance
- * @param {Mixed} newActiveItem The new active item
- * @param {Mixed} oldActiveItem The old active item
- */
-
- layoutClass: 'x-layout-card',
- itemClass: 'x-layout-card-item',
- requires: [
- 'Ext.fx.layout.Card'
- ],
- /**
- * @private
- */
- applyAnimation: function(animation) {
- return new Ext.fx.layout.Card(animation);
- },
- /**
- * @private
- */
- updateAnimation: function(animation, oldAnimation) {
- if (animation && animation.isAnimation) {
- animation.setLayout(this);
- }
- if (oldAnimation) {
- oldAnimation.destroy();
- }
- },
- setContainer: function(container) {
- this.callSuper(arguments);
- container.innerElement.addCls(this.layoutClass);
- container.onInitialized('onContainerInitialized', this);
- },
- onContainerInitialized: function() {
- var container = this.container,
- activeItem = container.getActiveItem();
- if (activeItem) {
- activeItem.show();
- }
- container.on('activeitemchange', 'onContainerActiveItemChange', this);
- },
- /**
- * @private
- */
- onContainerActiveItemChange: function(container) {
- this.relayEvent(arguments, 'doActiveItemChange');
- },
- onItemInnerStateChange: function(item, isInner, destroying) {
- this.callSuper(arguments);
- var container = this.container,
- activeItem = container.getActiveItem();
- item.toggleCls(this.itemClass, isInner);
- item.setLayoutSizeFlags(isInner ? container.LAYOUT_BOTH : 0);
- if (isInner) {
- if (activeItem !== container.innerIndexOf(item) && activeItem !== item && item !== container.pendingActiveItem) {
- item.hide();
- }
- }
- else {
- if (!destroying && !item.isDestroyed && item.isDestroying !== true) {
- item.show();
- }
- }
- },
- /**
- * @private
- */
- doActiveItemChange: function(me, newActiveItem, oldActiveItem) {
- if (oldActiveItem) {
- oldActiveItem.hide();
- }
- if (newActiveItem) {
- newActiveItem.show();
- }
- },
- destroy: function () {
- this.callParent(arguments);
- Ext.destroy(this.getAnimation());
- }
- });
- /**
- * Represents a filter that can be applied to a {@link Ext.util.MixedCollection MixedCollection}. Can either simply
- * filter on a property/value pair or pass in a filter function with custom logic. Filters are always used in the
- * context of MixedCollections, though {@link Ext.data.Store Store}s frequently create them when filtering and searching
- * on their records. Example usage:
- *
- * // Set up a fictional MixedCollection containing a few people to filter on
- * var allNames = new Ext.util.MixedCollection();
- * allNames.addAll([
- * { id: 1, name: 'Ed', age: 25 },
- * { id: 2, name: 'Jamie', age: 37 },
- * { id: 3, name: 'Abe', age: 32 },
- * { id: 4, name: 'Aaron', age: 26 },
- * { id: 5, name: 'David', age: 32 }
- * ]);
- *
- * var ageFilter = new Ext.util.Filter({
- * property: 'age',
- * value : 32
- * });
- *
- * var longNameFilter = new Ext.util.Filter({
- * filterFn: function(item) {
- * return item.name.length > 4;
- * }
- * });
- *
- * // a new MixedCollection with the 3 names longer than 4 characters
- * var longNames = allNames.filter(longNameFilter);
- *
- * // a new MixedCollection with the 2 people of age 32:
- * var youngFolk = allNames.filter(ageFilter);
- */
- Ext.define('Ext.util.Filter', {
- isFilter: true,
- config: {
- /**
- * @cfg {String} [property=null]
- * The property to filter on. Required unless a `filter` is passed
- */
- property: null,
- /**
- * @cfg {RegExp/Mixed} [value=null]
- * The value you want to match against. Can be a regular expression which will be used as matcher or any other
- * value.
- */
- value: null,
- /**
- * @cfg {Function} filterFn
- * A custom filter function which is passed each item in the {@link Ext.util.MixedCollection} in turn. Should
- * return true to accept each item or false to reject it
- */
- filterFn: Ext.emptyFn,
- /**
- * @cfg {Boolean} [anyMatch=false]
- * True to allow any match - no regex start/end line anchors will be added.
- */
- anyMatch: false,
- /**
- * @cfg {Boolean} [exactMatch=false]
- * True to force exact match (^ and $ characters added to the regex). Ignored if anyMatch is true.
- */
- exactMatch: false,
- /**
- * @cfg {Boolean} [caseSensitive=false]
- * True to make the regex case sensitive (adds 'i' switch to regex).
- */
- caseSensitive: false,
- /**
- * @cfg {String} [root=null]
- * Optional root property. This is mostly useful when filtering a Store, in which case we set the root to 'data'
- * to make the filter pull the {@link #property} out of the data object of each item
- */
- root: null,
- /**
- * @cfg {String} id
- * An optional id this filter can be keyed by in Collections. If no id is specified it will generate an id by
- * first trying a combination of property-value, and if none if these were specified (like when having a
- * filterFn) it will generate a random id.
- */
- id: undefined,
- /**
- * @cfg {Object} [scope=null]
- * The scope in which to run the filterFn
- */
- scope: null
- },
- applyId: function(id) {
- if (!id) {
- if (this.getProperty()) {
- id = this.getProperty() + '-' + String(this.getValue());
- }
- if (!id) {
- id = Ext.id(null, 'ext-filter-');
- }
- }
- return id;
- },
- /**
- * Creates new Filter.
- * @param {Object} config Config object
- */
- constructor: function(config) {
- this.initConfig(config);
- },
- applyFilterFn: function(filterFn) {
- if (filterFn === Ext.emptyFn) {
- filterFn = this.getInitialConfig('filter');
- if (filterFn) {
- return filterFn;
- }
- var value = this.getValue();
- if (!this.getProperty() && !value && value !== 0) {
- // <debug>
- Ext.Logger.error('A Filter requires either a property and value, or a filterFn to be set');
- // </debug>
- return Ext.emptyFn;
- }
- else {
- return this.createFilterFn();
- }
- }
- return filterFn;
- },
- /**
- * @private
- * Creates a filter function for the configured property/value/anyMatch/caseSensitive options for this Filter
- */
- createFilterFn: function() {
- var me = this,
- matcher = me.createValueMatcher();
- return function(item) {
- var root = me.getRoot(),
- property = me.getProperty();
- if (root) {
- item = item[root];
- }
- return matcher.test(item[property]);
- };
- },
- /**
- * @private
- * Returns a regular expression based on the given value and matching options
- */
- createValueMatcher: function() {
- var me = this,
- value = me.getValue(),
- anyMatch = me.getAnyMatch(),
- exactMatch = me.getExactMatch(),
- caseSensitive = me.getCaseSensitive(),
- escapeRe = Ext.String.escapeRegex;
- if (value === null || value === undefined || !value.exec) { // not a regex
- value = String(value);
- if (anyMatch === true) {
- value = escapeRe(value);
- } else {
- value = '^' + escapeRe(value);
- if (exactMatch === true) {
- value += '$';
- }
- }
- value = new RegExp(value, caseSensitive ? '' : 'i');
- }
- return value;
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.util.AbstractMixedCollection', {
- requires: ['Ext.util.Filter'],
- mixins: {
- observable: 'Ext.mixin.Observable'
- },
- /**
- * @event clear
- * Fires when the collection is cleared.
- */
- /**
- * @event add
- * Fires when an item is added to the collection.
- * @param {Number} index The index at which the item was added.
- * @param {Object} o The item added.
- * @param {String} key The key associated with the added item.
- */
- /**
- * @event replace
- * Fires when an item is replaced in the collection.
- * @param {String} key he key associated with the new added.
- * @param {Object} old The item being replaced.
- * @param {Object} new The new item.
- */
- /**
- * @event remove
- * Fires when an item is removed from the collection.
- * @param {Object} o The item being removed.
- * @param {String} key (optional) The key associated with the removed item.
- */
- /**
- * Creates new MixedCollection.
- * @param {Boolean} [allowFunctions=false] Specify `true` if the {@link #addAll}
- * function should add function references to the collection.
- * @param {Function} [keyFn] A function that can accept an item of the type(s) stored in this MixedCollection
- * and return the key value for that item. This is used when available to look up the key on items that
- * were passed without an explicit key parameter to a MixedCollection method. Passing this parameter is
- * equivalent to providing an implementation for the {@link #getKey} method.
- */
- constructor: function(allowFunctions, keyFn) {
- var me = this;
- me.items = [];
- me.map = {};
- me.keys = [];
- me.length = 0;
- me.allowFunctions = allowFunctions === true;
- if (keyFn) {
- me.getKey = keyFn;
- }
- me.mixins.observable.constructor.call(me);
- },
- /**
- * @cfg {Boolean} allowFunctions Specify `true` if the {@link #addAll}
- * function should add function references to the collection.
- */
- allowFunctions : false,
- /**
- * Adds an item to the collection. Fires the {@link #event-add} event when complete.
- * @param {String} key The key to associate with the item, or the new item.
- *
- * If a {@link #getKey} implementation was specified for this MixedCollection,
- * or if the key of the stored items is in a property called `id`,
- * the MixedCollection will be able to _derive_ the key for the new item.
- * In this case just pass the new item in this parameter.
- * @param {Object} o The item to add.
- * @return {Object} The item added.
- */
- add: function(key, obj){
- var me = this,
- myObj = obj,
- myKey = key,
- old;
- if (arguments.length == 1) {
- myObj = myKey;
- myKey = me.getKey(myObj);
- }
- if (typeof myKey != 'undefined' && myKey !== null) {
- old = me.map[myKey];
- if (typeof old != 'undefined') {
- return me.replace(myKey, myObj);
- }
- me.map[myKey] = myObj;
- }
- me.length++;
- me.items.push(myObj);
- me.keys.push(myKey);
- me.fireEvent('add', me.length - 1, myObj, myKey);
- return myObj;
- },
- /**
- * MixedCollection has a generic way to fetch keys if you implement `getKey`. The default implementation
- * simply returns `item.id` but you can provide your own implementation
- * to return a different value as in the following examples:
- *
- * // normal way
- * var mc = new Ext.util.MixedCollection();
- * mc.add(someEl.dom.id, someEl);
- * mc.add(otherEl.dom.id, otherEl);
- * //and so on
- *
- * // using getKey
- * var mc = new Ext.util.MixedCollection();
- * mc.getKey = function(el) {
- * return el.dom.id;
- * };
- * mc.add(someEl);
- * mc.add(otherEl);
- *
- * // or via the constructor
- * var mc = new Ext.util.MixedCollection(false, function(el) {
- * return el.dom.id;
- * });
- * mc.add(someEl);
- * mc.add(otherEl);
- *
- * @param {Object} item The item for which to find the key.
- * @return {Object} The key for the passed item.
- */
- getKey: function(o){
- return o.id;
- },
- /**
- * Replaces an item in the collection. Fires the {@link #event-replace} event when complete.
- * @param {String} key The key associated with the item to replace, or the replacement item.
- *
- * If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
- * of your stored items is in a property called `id`, then the MixedCollection
- * will be able to _derive_ the key of the replacement item. If you want to replace an item
- * with one having the same key value, then just pass the replacement item in this parameter.
- * @param {Object} o (optional) If the first parameter passed was a key, the item to associate
- * with that key.
- * @return {Object} The new item.
- */
- replace: function(key, o){
- var me = this,
- old,
- index;
- if (arguments.length == 1) {
- o = arguments[0];
- key = me.getKey(o);
- }
- old = me.map[key];
- if (typeof key == 'undefined' || key === null || typeof old == 'undefined') {
- return me.add(key, o);
- }
- index = me.indexOfKey(key);
- me.items[index] = o;
- me.map[key] = o;
- me.fireEvent('replace', key, old, o);
- return o;
- },
- /**
- * Adds all elements of an Array or an Object to the collection.
- * @param {Object/Array} objs An Object containing properties which will be added
- * to the collection, or an Array of values, each of which are added to the collection.
- * Functions references will be added to the collection if `{@link #allowFunctions}`
- * has been set to `true`.
- */
- addAll: function(objs){
- var me = this,
- i = 0,
- args,
- len,
- key;
- if (arguments.length > 1 || Ext.isArray(objs)) {
- args = arguments.length > 1 ? arguments : objs;
- for (len = args.length; i < len; i++) {
- me.add(args[i]);
- }
- } else {
- for (key in objs) {
- if (objs.hasOwnProperty(key)) {
- if (me.allowFunctions || typeof objs[key] != 'function') {
- me.add(key, objs[key]);
- }
- }
- }
- }
- },
- /**
- * Executes the specified function once for every item in the collection.
- *
- * @param {Function} fn The function to execute for each item.
- * @param {Mixed} fn.item The collection item.
- * @param {Number} fn.index The item's index.
- * @param {Number} fn.length The total number of items in the collection.
- * @param {Boolean} fn.return Returning `false` will stop the iteration.
- * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
- * Defaults to the current item in the iteration.
- */
- each: function(fn, scope){
- var items = [].concat(this.items), // each safe for removal
- i = 0,
- len = items.length,
- item;
- for (; i < len; i++) {
- item = items[i];
- if (fn.call(scope || item, item, i, len) === false) {
- break;
- }
- }
- },
- /**
- * Executes the specified function once for every key in the collection, passing each
- * key, and its associated item as the first two parameters.
- * @param {Function} fn The function to execute for each item.
- * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to the browser window.
- */
- eachKey: function(fn, scope){
- var keys = this.keys,
- items = this.items,
- i = 0,
- len = keys.length;
- for (; i < len; i++) {
- fn.call(scope || window, keys[i], items[i], i, len);
- }
- },
- /**
- * Returns the first item in the collection which elicits a `true` return value from the
- * passed selection function.
- * @param {Function} fn The selection function to execute for each item.
- * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to the browser window.
- * @return {Object} The first item in the collection which returned `true` from the selection function.
- */
- findBy: function(fn, scope) {
- var keys = this.keys,
- items = this.items,
- i = 0,
- len = items.length;
- for (; i < len; i++) {
- if (fn.call(scope || window, items[i], keys[i])) {
- return items[i];
- }
- }
- return null;
- },
- /**
- * Inserts an item at the specified index in the collection. Fires the `{@link #event-add}` event when complete.
- * @param {Number} index The index to insert the item at.
- * @param {String} key The key to associate with the new item, or the item itself.
- * @param {Object} o (optional) If the second parameter was a key, the new item.
- * @return {Object} The item inserted.
- */
- insert: function(index, key, obj){
- var me = this,
- myKey = key,
- myObj = obj;
- if (arguments.length == 2) {
- myObj = myKey;
- myKey = me.getKey(myObj);
- }
- if (me.containsKey(myKey)) {
- me.suspendEvents();
- me.removeAtKey(myKey);
- me.resumeEvents();
- }
- if (index >= me.length) {
- return me.add(myKey, myObj);
- }
- me.length++;
- Ext.Array.splice(me.items, index, 0, myObj);
- if (typeof myKey != 'undefined' && myKey !== null) {
- me.map[myKey] = myObj;
- }
- Ext.Array.splice(me.keys, index, 0, myKey);
- me.fireEvent('add', index, myObj, myKey);
- return myObj;
- },
- /**
- * Remove an item from the collection.
- * @param {Object} o The item to remove.
- * @return {Object} The item removed or `false` if no item was removed.
- */
- remove: function(o){
- return this.removeAt(this.indexOf(o));
- },
- /**
- * Remove all items in the passed array from the collection.
- * @param {Array} items An array of items to be removed.
- * @return {Ext.util.MixedCollection} this object
- */
- removeAll: function(items){
- Ext.each(items || [], function(item) {
- this.remove(item);
- }, this);
- return this;
- },
- /**
- * Remove an item from a specified index in the collection. Fires the `{@link #event-remove}` event when complete.
- * @param {Number} index The index within the collection of the item to remove.
- * @return {Object/Boolean} The item removed or `false` if no item was removed.
- */
- removeAt: function(index){
- var me = this,
- o,
- key;
- if (index < me.length && index >= 0) {
- me.length--;
- o = me.items[index];
- Ext.Array.erase(me.items, index, 1);
- key = me.keys[index];
- if (typeof key != 'undefined') {
- delete me.map[key];
- }
- Ext.Array.erase(me.keys, index, 1);
- me.fireEvent('remove', o, key);
- return o;
- }
- return false;
- },
- /**
- * Removed an item associated with the passed key from the collection.
- * @param {String} key The key of the item to remove.
- * @return {Object/Boolean} The item removed or `false` if no item was removed.
- */
- removeAtKey: function(key){
- return this.removeAt(this.indexOfKey(key));
- },
- /**
- * Returns the number of items in the collection.
- * @return {Number} the number of items in the collection.
- */
- getCount: function(){
- return this.length;
- },
- /**
- * Returns index within the collection of the passed Object.
- * @param {Object} o The item to find the index of.
- * @return {Number} index of the item. Returns -1 if not found.
- */
- indexOf: function(o){
- return Ext.Array.indexOf(this.items, o);
- },
- /**
- * Returns index within the collection of the passed key.
- * @param {String} key The key to find the index of.
- * @return {Number} The index of the key.
- */
- indexOfKey: function(key){
- return Ext.Array.indexOf(this.keys, key);
- },
- /**
- * Returns the item associated with the passed key OR index.
- * Key has priority over index. This is the equivalent
- * of calling {@link #getByKey} first, then if nothing matched calling {@link #getAt}.
- * @param {String/Number} key The key or index of the item.
- * @return {Object} If the item is found, returns the item. If the item was not found, returns `undefined`.
- * If an item was found, but is a Class, returns `null`.
- */
- get: function(key) {
- var me = this,
- mk = me.map[key],
- item = mk !== undefined ? mk : (typeof key == 'number') ? me.items[key] : undefined;
- return typeof item != 'function' || me.allowFunctions ? item : null; // for prototype!
- },
- /**
- * Returns the item at the specified index.
- * @param {Number} index The index of the item.
- * @return {Object} The item at the specified index.
- */
- getAt: function(index) {
- return this.items[index];
- },
- /**
- * Returns the item associated with the passed key.
- * @param {String/Number} key The key of the item.
- * @return {Object} The item associated with the passed key.
- */
- getByKey: function(key) {
- return this.map[key];
- },
- /**
- * Returns `true` if the collection contains the passed Object as an item.
- * @param {Object} o The Object to look for in the collection.
- * @return {Boolean} `true` if the collection contains the Object as an item.
- */
- contains: function(o){
- return Ext.Array.contains(this.items, o);
- },
- /**
- * Returns `true` if the collection contains the passed Object as a key.
- * @param {String} key The key to look for in the collection.
- * @return {Boolean} `true` if the collection contains the Object as a key.
- */
- containsKey: function(key){
- return typeof this.map[key] != 'undefined';
- },
- /**
- * Removes all items from the collection. Fires the `{@link #event-clear}` event when complete.
- */
- clear: function(){
- var me = this;
- me.length = 0;
- me.items = [];
- me.keys = [];
- me.map = {};
- me.fireEvent('clear');
- },
- /**
- * Returns the first item in the collection.
- * @return {Object} the first item in the collection..
- */
- first: function() {
- return this.items[0];
- },
- /**
- * Returns the last item in the collection.
- * @return {Object} the last item in the collection..
- */
- last: function() {
- return this.items[this.length - 1];
- },
- /**
- * Collects all of the values of the given property and returns their sum.
- * @param {String} property The property to sum by.
- * @param {String} [root] Optional 'root' property to extract the first argument from. This is used mainly when
- * summing fields in records, where the fields are all stored inside the `data` object
- * @param {Number} [start=0] (optional) The record index to start at.
- * @param {Number} [end=-1] (optional) The record index to end at.
- * @return {Number} The total
- */
- sum: function(property, root, start, end) {
- var values = this.extractValues(property, root),
- length = values.length,
- sum = 0,
- i;
- start = start || 0;
- end = (end || end === 0) ? end : length - 1;
- for (i = start; i <= end; i++) {
- sum += values[i];
- }
- return sum;
- },
- /**
- * Collects unique values of a particular property in this MixedCollection.
- * @param {String} property The property to collect on.
- * @param {String} [root] Optional 'root' property to extract the first argument from. This is used mainly when
- * summing fields in records, where the fields are all stored inside the `data` object.
- * @param {Boolean} allowBlank (optional) Pass `true` to allow `null`, `undefined`, or empty string values.
- * @return {Array} The unique values.
- */
- collect: function(property, root, allowNull) {
- var values = this.extractValues(property, root),
- length = values.length,
- hits = {},
- unique = [],
- value, strValue, i;
- for (i = 0; i < length; i++) {
- value = values[i];
- strValue = String(value);
- if ((allowNull || !Ext.isEmpty(value)) && !hits[strValue]) {
- hits[strValue] = true;
- unique.push(value);
- }
- }
- return unique;
- },
- /**
- * @private
- * Extracts all of the given property values from the items in the MixedCollection. Mainly used as a supporting method for
- * functions like `sum()` and `collect()`.
- * @param {String} property The property to extract.
- * @param {String} [root] Optional 'root' property to extract the first argument from. This is used mainly when
- * extracting field data from Model instances, where the fields are stored inside the `data` object.
- * @return {Array} The extracted values.
- */
- extractValues: function(property, root) {
- var values = this.items;
- if (root) {
- values = Ext.Array.pluck(values, root);
- }
- return Ext.Array.pluck(values, property);
- },
- /**
- * Returns a range of items in this collection.
- * @param {Number} [startIndex=0] (optional) The starting index.
- * @param {Number} [endIndex=-1] (optional) The ending index.
- * @return {Array} An array of items
- */
- getRange: function(start, end){
- var me = this,
- items = me.items,
- range = [],
- i;
- if (items.length < 1) {
- return range;
- }
- start = start || 0;
- end = Math.min(typeof end == 'undefined' ? me.length - 1 : end, me.length - 1);
- if (start <= end) {
- for (i = start; i <= end; i++) {
- range[range.length] = items[i];
- }
- } else {
- for (i = start; i >= end; i--) {
- range[range.length] = items[i];
- }
- }
- return range;
- },
- /**
- * Filters the objects in this collection by a set of {@link Ext.util.Filter Filter}s, or by a single
- * property/value pair with optional parameters for substring matching and case sensitivity. See
- * {@link Ext.util.Filter Filter} for an example of using Filter objects (preferred). Alternatively,
- * MixedCollection can be easily filtered by property like this:
- *
- * // create a simple store with a few people defined
- * var people = new Ext.util.MixedCollection();
- * people.addAll([
- * {id: 1, age: 25, name: 'Ed'},
- * {id: 2, age: 24, name: 'Tommy'},
- * {id: 3, age: 24, name: 'Arne'},
- * {id: 4, age: 26, name: 'Aaron'}
- * ]);
- *
- * // a new MixedCollection containing only the items where age == 24
- * var middleAged = people.filter('age', 24);
- *
- * @param {Ext.util.Filter[]/String} property A property on your objects, or an array of {@link Ext.util.Filter Filter} objects
- * @param {String/RegExp} value Either string that the property values
- * should start with or a RegExp to test against the property.
- * @param {Boolean} anyMatch (optional) `true` to match any part of the string, not just the beginning
- * @param {Boolean} [caseSensitive=false] (optional) `true` for case sensitive comparison.
- * @return {Ext.util.MixedCollection} The new filtered collection
- */
- filter: function(property, value, anyMatch, caseSensitive) {
- var filters = [],
- filterFn;
- //support for the simple case of filtering by property/value
- if (Ext.isString(property)) {
- filters.push(Ext.create('Ext.util.Filter', {
- property : property,
- value : value,
- anyMatch : anyMatch,
- caseSensitive: caseSensitive
- }));
- } else if (Ext.isArray(property) || property instanceof Ext.util.Filter) {
- filters = filters.concat(property);
- }
- //at this point we have an array of zero or more Ext.util.Filter objects to filter with,
- //so here we construct a function that combines these filters by ANDing them together
- filterFn = function(record) {
- var isMatch = true,
- length = filters.length,
- i;
- for (i = 0; i < length; i++) {
- var filter = filters[i],
- fn = filter.getFilterFn(),
- scope = filter.getScope();
- isMatch = isMatch && fn.call(scope, record);
- }
- return isMatch;
- };
- return this.filterBy(filterFn);
- },
- /**
- * Filter by a function. Returns a _new_ collection that has been filtered.
- * The passed function will be called with each object in the collection.
- * If the function returns `true`, the value is included otherwise it is filtered.
- * @param {Function} fn The function to be called, it will receive the args `o` (the object), `k` (the key)
- * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to this MixedCollection.
- * @return {Ext.util.MixedCollection} The new filtered collection.
- */
- filterBy: function(fn, scope) {
- var me = this,
- newMC = new this.self(),
- keys = me.keys,
- items = me.items,
- length = items.length,
- i;
- newMC.getKey = me.getKey;
- for (i = 0; i < length; i++) {
- if (fn.call(scope || me, items[i], keys[i])) {
- newMC.add(keys[i], items[i]);
- }
- }
- return newMC;
- },
- /**
- * Finds the index of the first matching object in this collection by a specific property/value.
- * @param {String} property The name of a property on your objects.
- * @param {String/RegExp} value A string that the property values.
- * should start with or a RegExp to test against the property.
- * @param {Number} [start=0] (optional) The index to start searching at.
- * @param {Boolean} anyMatch (optional) `true` to match any part of the string, not just the beginning.
- * @param {Boolean} caseSensitive (optional) `true` for case sensitive comparison.
- * @return {Number} The matched index or -1.
- */
- findIndex: function(property, value, start, anyMatch, caseSensitive){
- if(Ext.isEmpty(value, false)){
- return -1;
- }
- value = this.createValueMatcher(value, anyMatch, caseSensitive);
- return this.findIndexBy(function(o){
- return o && value.test(o[property]);
- }, null, start);
- },
- /**
- * Find the index of the first matching object in this collection by a function.
- * If the function returns `true` it is considered a match.
- * @param {Function} fn The function to be called, it will receive the args `o` (the object), `k` (the key).
- * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to this MixedCollection.
- * @param {Number} [start=0] (optional) The index to start searching at.
- * @return {Number} The matched index or -1.
- */
- findIndexBy: function(fn, scope, start){
- var me = this,
- keys = me.keys,
- items = me.items,
- i = start || 0,
- len = items.length;
- for (; i < len; i++) {
- if (fn.call(scope || me, items[i], keys[i])) {
- return i;
- }
- }
- return -1;
- },
- /**
- * Returns a regular expression based on the given value and matching options. This is used internally for finding and filtering,
- * and by Ext.data.Store#filter
- * @private
- * @param {String} value The value to create the regex for. This is escaped using Ext.escapeRe
- * @param {Boolean} [anyMatch=false] `true` to allow any match - no regex start/end line anchors will be added.
- * @param {Boolean} [caseSensitive=false] `true` to make the regex case sensitive (adds 'i' switch to regex).
- * @param {Boolean} [exactMatch=false] `true` to force exact match (^ and $ characters added to the regex). Ignored if `anyMatch` is `true`.
- */
- createValueMatcher: function(value, anyMatch, caseSensitive, exactMatch) {
- if (!value.exec) { // not a regex
- var er = Ext.String.escapeRegex;
- value = String(value);
- if (anyMatch === true) {
- value = er(value);
- } else {
- value = '^' + er(value);
- if (exactMatch === true) {
- value += '$';
- }
- }
- value = new RegExp(value, caseSensitive ? '' : 'i');
- }
- return value;
- },
- /**
- * Creates a shallow copy of this collection.
- * @return {Ext.util.MixedCollection}
- */
- clone: function() {
- var me = this,
- copy = new this.self(),
- keys = me.keys,
- items = me.items,
- i = 0,
- len = items.length;
- for(; i < len; i++){
- copy.add(keys[i], items[i]);
- }
- copy.getKey = me.getKey;
- return copy;
- }
- });
- /**
- * Represents a single sorter that can be used as part of the sorters configuration in Ext.mixin.Sortable.
- *
- * A common place for Sorters to be used are {@link Ext.data.Store Stores}. For example:
- *
- * @example miniphone
- * var store = Ext.create('Ext.data.Store', {
- * fields: ['firstName', 'lastName'],
- * sorters: 'lastName',
- *
- * data: [
- * { firstName: 'Tommy', lastName: 'Maintz' },
- * { firstName: 'Rob', lastName: 'Dougan' },
- * { firstName: 'Ed', lastName: 'Spencer'},
- * { firstName: 'Jamie', lastName: 'Avins' },
- * { firstName: 'Nick', lastName: 'Poulden'}
- * ]
- * });
- *
- * Ext.create('Ext.List', {
- * fullscreen: true,
- * itemTpl: '<div class="contact">{firstName} <strong>{lastName}</strong></div>',
- * store: store
- * });
- *
- * In the next example, we specify a custom sorter function:
- *
- * @example miniphone
- * var store = Ext.create('Ext.data.Store', {
- * fields: ['person'],
- * sorters: [
- * {
- * // Sort by first letter of last name, in descending order
- * sorterFn: function(record1, record2) {
- * var name1 = record1.data.person.name.split('-')[1].substr(0, 1),
- * name2 = record2.data.person.name.split('-')[1].substr(0, 1);
- *
- * return name1 > name2 ? 1 : (name1 === name2 ? 0 : -1);
- * },
- * direction: 'DESC'
- * }
- * ],
- * data: [
- * { person: { name: 'Tommy-Maintz' } },
- * { person: { name: 'Rob-Dougan' } },
- * { person: { name: 'Ed-Spencer' } },
- * { person: { name: 'Nick-Poulden' } },
- * { person: { name: 'Jamie-Avins' } }
- * ]
- * });
- *
- * Ext.create('Ext.List', {
- * fullscreen: true,
- * itemTpl: '{person.name}',
- * store: store
- * });
- */
- Ext.define('Ext.util.Sorter', {
- isSorter: true,
- config: {
- /**
- * @cfg {String} property The property to sort by. Required unless `sorterFn` is provided
- */
- property: null,
- /**
- * @cfg {Function} sorterFn A specific sorter function to execute. Can be passed instead of {@link #property}.
- * This function should compare the two passed arguments, returning -1, 0 or 1 depending on if item 1 should be
- * sorted before, at the same level, or after item 2.
- *
- * sorterFn: function(person1, person2) {
- * return (person1.age > person2.age) ? 1 : (person1.age === person2.age ? 0 : -1);
- * }
- */
- sorterFn: null,
- /**
- * @cfg {String} root Optional root property. This is mostly useful when sorting a Store, in which case we set the
- * root to 'data' to make the filter pull the {@link #property} out of the data object of each item
- */
- root: null,
- /**
- * @cfg {Function} transform A function that will be run on each value before
- * it is compared in the sorter. The function will receive a single argument,
- * the value.
- */
- transform: null,
- /**
- * @cfg {String} direction The direction to sort by. Valid values are "ASC", and "DESC".
- */
- direction: "ASC",
- /**
- * @cfg {Mixed} id An optional id this sorter can be keyed by in Collections. If
- * no id is specified it will use the property name used in this Sorter. If no
- * property is specified, e.g. when adding a custom sorter function we will generate
- * a random id.
- */
- id: undefined
- },
- constructor: function(config) {
- this.initConfig(config);
- },
- // <debug>
- applySorterFn: function(sorterFn) {
- if (!sorterFn && !this.getProperty()) {
- Ext.Logger.error("A Sorter requires either a property or a sorterFn.");
- }
- return sorterFn;
- },
- applyProperty: function(property) {
- if (!property && !this.getSorterFn()) {
- Ext.Logger.error("A Sorter requires either a property or a sorterFn.");
- }
- return property;
- },
- // </debug>
- applyId: function(id) {
- if (!id) {
- id = this.getProperty();
- if (!id) {
- id = Ext.id(null, 'ext-sorter-');
- }
- }
- return id;
- },
- /**
- * @private
- * Creates and returns a function which sorts an array by the given property and direction
- * @return {Function} A function which sorts by the property/direction combination provided
- */
- createSortFunction: function(sorterFn) {
- var me = this,
- modifier = me.getDirection().toUpperCase() == "DESC" ? -1 : 1;
- //create a comparison function. Takes 2 objects, returns 1 if object 1 is greater,
- //-1 if object 2 is greater or 0 if they are equal
- return function(o1, o2) {
- return modifier * sorterFn.call(me, o1, o2);
- };
- },
- /**
- * @private
- * Basic default sorter function that just compares the defined property of each object
- */
- defaultSortFn: function(item1, item2) {
- var me = this,
- transform = me._transform,
- root = me._root,
- value1, value2,
- property = me._property;
- if (root !== null) {
- item1 = item1[root];
- item2 = item2[root];
- }
- value1 = item1[property];
- value2 = item2[property];
- if (transform) {
- value1 = transform(value1);
- value2 = transform(value2);
- }
- return value1 > value2 ? 1 : (value1 < value2 ? -1 : 0);
- },
- updateDirection: function() {
- this.updateSortFn();
- },
- updateSortFn: function() {
- this.sort = this.createSortFunction(this.getSorterFn() || this.defaultSortFn);
- },
- /**
- * Toggles the direction of this Sorter. Note that when you call this function,
- * the Collection this Sorter is part of does not get refreshed automatically.
- */
- toggle: function() {
- this.setDirection(Ext.String.toggle(this.getDirection(), "ASC", "DESC"));
- }
- });
- /**
- * @docauthor Tommy Maintz <tommy@sencha.com>
- *
- * 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}.
- *
- * __Note:__ This mixin is mainly for internal library use and most users should not need to use it directly. It
- * is more likely you will want to use one of the component classes that import this mixin, such as
- * {@link Ext.data.Store} or {@link Ext.data.TreeStore}.
- */
- Ext.define("Ext.util.Sortable", {
- extend: 'Ext.mixin.Mixin',
- /**
- * @property {Boolean} isSortable
- * Flag denoting that this object is sortable. Always `true`.
- * @readonly
- */
- isSortable: true,
-
- mixinConfig: {
- hooks: {
- destroy: 'destroy'
- }
- },
-
- /**
- * @property {String} defaultSortDirection
- * The default sort direction to use if one is not specified.
- */
- defaultSortDirection: "ASC",
-
- requires: [
- 'Ext.util.Sorter'
- ],
- /**
- * @property {String} sortRoot
- * The property in each item that contains the data to sort.
- */
-
- /**
- * Performs initialization of this mixin. Component classes using this mixin should call this method during their
- * own initialization.
- */
- initSortable: function() {
- var me = this,
- sorters = me.sorters;
-
- /**
- * @property {Ext.util.MixedCollection} sorters
- * The collection of {@link Ext.util.Sorter Sorters} currently applied to this Store
- */
- me.sorters = Ext.create('Ext.util.AbstractMixedCollection', false, function(item) {
- return item.id || item.property;
- });
-
- if (sorters) {
- me.sorters.addAll(me.decodeSorters(sorters));
- }
- },
- /**
- * Sorts the data in the Store by one or more of its properties. Example usage:
- *
- * //sort by a single field
- * myStore.sort('myField', 'DESC');
- *
- * //sorting by multiple fields
- * myStore.sort([
- * {
- * property : 'age',
- * direction: 'ASC'
- * },
- * {
- * property : 'name',
- * direction: 'DESC'
- * }
- * ]);
- *
- * Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates
- * the actual sorting to its internal {@link Ext.util.MixedCollection}.
- *
- * When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:
- *
- * store.sort('myField');
- * store.sort('myField');
- *
- * Is equivalent to this code, because Store handles the toggling automatically:
- *
- * store.sort('myField', 'ASC');
- * store.sort('myField', 'DESC');
- *
- * @param {String/Ext.util.Sorter[]} sorters Either a string name of one of the fields in this Store's configured
- * {@link Ext.data.Model Model}, or an array of sorter configurations.
- * @param {String} [direction="ASC"] The overall direction to sort the data by.
- * @param {String} [where]
- * @param {Boolean} [doSort]
- * @return {Ext.util.Sorter[]}
- */
- sort: function(sorters, direction, where, doSort) {
- var me = this,
- sorter, sorterFn,
- newSorters;
-
- if (Ext.isArray(sorters)) {
- doSort = where;
- where = direction;
- newSorters = sorters;
- }
- else if (Ext.isObject(sorters)) {
- doSort = where;
- where = direction;
- newSorters = [sorters];
- }
- else if (Ext.isString(sorters)) {
- sorter = me.sorters.get(sorters);
- if (!sorter) {
- sorter = {
- property : sorters,
- direction: direction
- };
- newSorters = [sorter];
- }
- else if (direction === undefined) {
- sorter.toggle();
- }
- else {
- sorter.setDirection(direction);
- }
- }
-
- if (newSorters && newSorters.length) {
- newSorters = me.decodeSorters(newSorters);
- if (Ext.isString(where)) {
- if (where === 'prepend') {
- sorters = me.sorters.clone().items;
-
- me.sorters.clear();
- me.sorters.addAll(newSorters);
- me.sorters.addAll(sorters);
- }
- else {
- me.sorters.addAll(newSorters);
- }
- }
- else {
- me.sorters.clear();
- me.sorters.addAll(newSorters);
- }
-
- if (doSort !== false) {
- me.onBeforeSort(newSorters);
- }
- }
-
- if (doSort !== false) {
- sorters = me.sorters.items;
- if (sorters.length) {
- //construct an amalgamated sorter function which combines all of the Sorters passed
- sorterFn = function(r1, r2) {
- var result = sorters[0].sort(r1, r2),
- length = sorters.length,
- i;
- //if we have more than one sorter, OR any additional sorter functions together
- for (i = 1; i < length; i++) {
- result = result || sorters[i].sort.call(this, r1, r2);
- }
- return result;
- };
- me.doSort(sorterFn);
- }
- }
-
- return sorters;
- },
-
- onBeforeSort: Ext.emptyFn,
-
- /**
- * @private
- * Normalizes an array of sorter objects, ensuring that they are all {@link Ext.util.Sorter} instances.
- * @param {Array} sorters The sorters array.
- * @return {Array} Array of {@link Ext.util.Sorter} objects.
- */
- decodeSorters: function(sorters) {
- if (!Ext.isArray(sorters)) {
- if (sorters === undefined) {
- sorters = [];
- } else {
- sorters = [sorters];
- }
- }
- var length = sorters.length,
- Sorter = Ext.util.Sorter,
- fields = this.model ? this.model.prototype.fields : null,
- field,
- config, i;
- for (i = 0; i < length; i++) {
- config = sorters[i];
- if (!(config instanceof Sorter)) {
- if (Ext.isString(config)) {
- config = {
- property: config
- };
- }
-
- Ext.applyIf(config, {
- root : this.sortRoot,
- direction: "ASC"
- });
- if (config.fn) {
- config.sorterFn = config.fn;
- }
- //support a function to be passed as a sorter definition
- if (typeof config == 'function') {
- config = {
- sorterFn: config
- };
- }
- // ensure sortType gets pushed on if necessary
- if (fields && !config.transform) {
- field = fields.get(config.property);
- config.transform = field ? field.sortType : undefined;
- }
- sorters[i] = Ext.create('Ext.util.Sorter', config);
- }
- }
- return sorters;
- },
-
- getSorters: function() {
- return this.sorters.items;
- },
-
- destroy: function () {
- this.callSuper();
- Ext.destroy(this.sorters);
- }
- });
- /**
- * Represents a collection of a set of key and value pairs. Each key in the MixedCollection must be unique, the same key
- * cannot exist twice. This collection is ordered, items in the collection can be accessed by index or via the key.
- * Newly added items are added to the end of the collection. This class is similar to {@link Ext.util.HashMap} however
- * it is heavier and provides more functionality. Sample usage:
- *
- * @example
- * var coll = new Ext.util.MixedCollection();
- * coll.add('key1', 'val1');
- * coll.add('key2', 'val2');
- * coll.add('key3', 'val3');
- *
- * alert(coll.get('key1')); // 'val1'
- * alert(coll.indexOfKey('key3')); // 2
- *
- * The MixedCollection also has support for sorting and filtering of the values in the collection.
- *
- * @example
- * var coll = new Ext.util.MixedCollection();
- * coll.add('key1', 100);
- * coll.add('key2', -100);
- * coll.add('key3', 17);
- * coll.add('key4', 0);
- * var biggerThanZero = coll.filterBy(function(value){
- * return value > 0;
- * });
- * alert(biggerThanZero.getCount()); // 2
- */
- Ext.define('Ext.util.MixedCollection', {
- extend: 'Ext.util.AbstractMixedCollection',
- mixins: {
- sortable: 'Ext.util.Sortable'
- },
- /**
- * @event sort
- * Fires whenever MixedCollection is sorted.
- * @param {Ext.util.MixedCollection} this
- */
- constructor: function() {
- var me = this;
- me.callParent(arguments);
- me.mixins.sortable.initSortable.call(me);
- },
- doSort: function(sorterFn) {
- this.sortBy(sorterFn);
- },
- /**
- * @private
- * Performs the actual sorting based on a direction and a sorting function. Internally,
- * this creates a temporary array of all items in the MixedCollection, sorts it and then writes
- * the sorted array data back into `this.items` and `this.keys`.
- * @param {String} property Property to sort by ('key', 'value', or 'index')
- * @param {String} [dir=ASC] (optional) Direction to sort 'ASC' or 'DESC'.
- * @param {Function} fn (optional) Comparison function that defines the sort order.
- * Defaults to sorting by numeric value.
- */
- _sort: function(property, dir, fn){
- var me = this,
- i, len,
- dsc = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
- //this is a temporary array used to apply the sorting function
- c = [],
- keys = me.keys,
- items = me.items;
- //default to a simple sorter function if one is not provided
- fn = fn || function(a, b) {
- return a - b;
- };
- //copy all the items into a temporary array, which we will sort
- for(i = 0, len = items.length; i < len; i++){
- c[c.length] = {
- key : keys[i],
- value: items[i],
- index: i
- };
- }
- //sort the temporary array
- Ext.Array.sort(c, function(a, b){
- var v = fn(a[property], b[property]) * dsc;
- if(v === 0){
- v = (a.index < b.index ? -1 : 1);
- }
- return v;
- });
- //copy the temporary array back into the main this.items and this.keys objects
- for(i = 0, len = c.length; i < len; i++){
- items[i] = c[i].value;
- keys[i] = c[i].key;
- }
- me.fireEvent('sort', me);
- },
- /**
- * Sorts the collection by a single sorter function.
- * @param {Function} sorterFn The function to sort by.
- */
- sortBy: function(sorterFn) {
- var me = this,
- items = me.items,
- keys = me.keys,
- length = items.length,
- temp = [],
- i;
- //first we create a copy of the items array so that we can sort it
- for (i = 0; i < length; i++) {
- temp[i] = {
- key : keys[i],
- value: items[i],
- index: i
- };
- }
- Ext.Array.sort(temp, function(a, b) {
- var v = sorterFn(a.value, b.value);
- if (v === 0) {
- v = (a.index < b.index ? -1 : 1);
- }
- return v;
- });
- //copy the temporary array back into the main this.items and this.keys objects
- for (i = 0; i < length; i++) {
- items[i] = temp[i].value;
- keys[i] = temp[i].key;
- }
- me.fireEvent('sort', me, items, keys);
- },
- /**
- * Reorders each of the items based on a mapping from old index to new index. Internally this just translates into a
- * sort. The `sort` event is fired whenever reordering has occured.
- * @param {Object} mapping Mapping from old item index to new item index.
- */
- reorder: function(mapping) {
- var me = this,
- items = me.items,
- index = 0,
- length = items.length,
- order = [],
- remaining = [],
- oldIndex;
- me.suspendEvents();
- //object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
- for (oldIndex in mapping) {
- order[mapping[oldIndex]] = items[oldIndex];
- }
- for (index = 0; index < length; index++) {
- if (mapping[index] == undefined) {
- remaining.push(items[index]);
- }
- }
- for (index = 0; index < length; index++) {
- if (order[index] == undefined) {
- order[index] = remaining.shift();
- }
- }
- me.clear();
- me.addAll(order);
- me.resumeEvents();
- me.fireEvent('sort', me);
- },
- /**
- * Sorts this collection by **key**s.
- * @param {String} [direction=ASC] 'ASC' or 'DESC'.
- * @param {Function} [fn] Comparison function that defines the sort order. Defaults to sorting by case insensitive
- * string.
- */
- sortByKey: function(dir, fn){
- this._sort('key', dir, fn || function(a, b){
- var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
- return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
- });
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.ItemCollection', {
- extend: 'Ext.util.MixedCollection',
- getKey: function(item) {
- return item.getItemId();
- },
- has: function(item) {
- return this.map.hasOwnProperty(item.getId());
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.easing.Momentum', {
- extend: 'Ext.fx.easing.Abstract',
- config: {
- acceleration: 30,
- friction: 0,
- startVelocity: 0
- },
- alpha: 0,
- updateFriction: function(friction) {
- var theta = Math.log(1 - (friction / 10));
- this.theta = theta;
- this.alpha = theta / this.getAcceleration();
- },
- updateStartVelocity: function(velocity) {
- this.velocity = velocity * this.getAcceleration();
- },
- updateAcceleration: function(acceleration) {
- this.velocity = this.getStartVelocity() * acceleration;
- this.alpha = this.theta / acceleration;
- },
- getValue: function() {
- return this.getStartValue() - this.velocity * (1 - this.getFrictionFactor()) / this.theta;
- },
- getFrictionFactor: function() {
- var deltaTime = Ext.Date.now() - this.getStartTime();
- return Math.exp(deltaTime * this.alpha);
- },
- getVelocity: function() {
- return this.getFrictionFactor() * this.velocity;
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.easing.Bounce', {
- extend: 'Ext.fx.easing.Abstract',
- config: {
- springTension: 0.3,
- acceleration: 30,
- startVelocity: 0
- },
- getValue: function() {
- var deltaTime = Ext.Date.now() - this.getStartTime(),
- theta = (deltaTime / this.getAcceleration()),
- powTime = theta * Math.pow(Math.E, -this.getSpringTension() * theta);
- return this.getStartValue() + (this.getStartVelocity() * powTime);
- }
- });
- /**
- * @private
- *
- * This easing is typically used for {@link Ext.scroll.Scroller}. It's a combination of
- * {@link Ext.fx.easing.Momentum} and {@link Ext.fx.easing.Bounce}, which emulates deceleration when the animated element
- * is still within its boundary, then bouncing back (snapping) when it's out-of-bound.
- */
- Ext.define('Ext.fx.easing.BoundMomentum', {
- extend: 'Ext.fx.easing.Abstract',
- requires: [
- 'Ext.fx.easing.Momentum',
- 'Ext.fx.easing.Bounce'
- ],
- config: {
- /**
- * @cfg {Object} momentum
- * A valid config object for {@link Ext.fx.easing.Momentum}
- * @accessor
- */
- momentum: null,
- /**
- * @cfg {Object} bounce
- * A valid config object for {@link Ext.fx.easing.Bounce}
- * @accessor
- */
- bounce: null,
- minMomentumValue: 0,
- maxMomentumValue: 0,
- /**
- * @cfg {Number} minVelocity
- * The minimum velocity to end this easing
- * @accessor
- */
- minVelocity: 0.01,
- /**
- * @cfg {Number} startVelocity
- * The start velocity
- * @accessor
- */
- startVelocity: 0
- },
- applyMomentum: function(config, currentEasing) {
- return Ext.factory(config, Ext.fx.easing.Momentum, currentEasing);
- },
- applyBounce: function(config, currentEasing) {
- return Ext.factory(config, Ext.fx.easing.Bounce, currentEasing);
- },
- updateStartTime: function(startTime) {
- this.getMomentum().setStartTime(startTime);
- this.callParent(arguments);
- },
- updateStartVelocity: function(startVelocity) {
- this.getMomentum().setStartVelocity(startVelocity);
- },
- updateStartValue: function(startValue) {
- this.getMomentum().setStartValue(startValue);
- },
- reset: function() {
- this.lastValue = null;
- this.isBouncingBack = false;
- this.isOutOfBound = false;
- return this.callParent(arguments);
- },
- getValue: function() {
- var momentum = this.getMomentum(),
- bounce = this.getBounce(),
- startVelocity = momentum.getStartVelocity(),
- direction = startVelocity > 0 ? 1 : -1,
- minValue = this.getMinMomentumValue(),
- maxValue = this.getMaxMomentumValue(),
- boundedValue = (direction == 1) ? maxValue : minValue,
- lastValue = this.lastValue,
- value, velocity;
- if (startVelocity === 0) {
- return this.getStartValue();
- }
- if (!this.isOutOfBound) {
- value = momentum.getValue();
- velocity = momentum.getVelocity();
- if (Math.abs(velocity) < this.getMinVelocity()) {
- this.isEnded = true;
- }
- if (value >= minValue && value <= maxValue) {
- return value;
- }
- this.isOutOfBound = true;
- bounce.setStartTime(Ext.Date.now())
- .setStartVelocity(velocity)
- .setStartValue(boundedValue);
- }
- value = bounce.getValue();
- if (!this.isEnded) {
- if (!this.isBouncingBack) {
- if (lastValue !== null) {
- if ((direction == 1 && value < lastValue) || (direction == -1 && value > lastValue)) {
- this.isBouncingBack = true;
- }
- }
- }
- else {
- if (Math.round(value) == boundedValue) {
- this.isEnded = true;
- }
- }
- }
- this.lastValue = value;
- return value;
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.easing.EaseOut', {
- extend: 'Ext.fx.easing.Linear',
- alias: 'easing.ease-out',
- config: {
- exponent: 4,
- duration: 1500
- },
- getValue: function() {
- var deltaTime = Ext.Date.now() - this.getStartTime(),
- duration = this.getDuration(),
- startValue = this.getStartValue(),
- endValue = this.getEndValue(),
- distance = this.distance,
- theta = deltaTime / duration,
- thetaC = 1 - theta,
- thetaEnd = 1 - Math.pow(thetaC, this.getExponent()),
- currentValue = startValue + (thetaEnd * distance);
- if (deltaTime >= duration) {
- this.isEnded = true;
- return endValue;
- }
- return currentValue;
- }
- });
- /**
- * @class Ext.scroll.Scroller
- * @author Jacky Nguyen <jacky@sencha.com>
- *
- * Momentum scrolling is one of the most important part of the framework's UI layer. In Sencha Touch there are
- * several scroller implementations so we can have the best performance on all mobile devices and browsers.
- *
- * Scroller settings can be changed using the {@link Ext.Container#scrollable scrollable} configuration in
- * {@link Ext.Container}. Anything you pass to that method will be passed to the scroller when it is
- * instantiated in your container.
- *
- * Please note that the {@link Ext.Container#getScrollable} method returns an instance of {@link Ext.scroll.View}.
- * So if you need to get access to the scroller after your container has been instantiated, you must used the
- * {@link Ext.scroll.View#getScroller} method.
- *
- * // lets assume container is a container you have
- * // created which is scrollable
- * container.getScrollable().getScroller().setFps(10);
- *
- * ## Example
- *
- * Here is a simple example of how to adjust the scroller settings when using a {@link Ext.Container} (or anything
- * that extends it).
- *
- * @example
- * var container = Ext.create('Ext.Container', {
- * fullscreen: true,
- * html: 'This container is scrollable!',
- * scrollable: {
- * direction: 'vertical'
- * }
- * });
- *
- * As you can see, we are passing the {@link #direction} configuration into the scroller instance in our container.
- *
- * You can pass any of the configs below in that {@link Ext.Container#scrollable scrollable} configuration and it will
- * just work.
- *
- * Go ahead and try it in the live code editor above!
- */
- Ext.define('Ext.scroll.Scroller', {
- extend: 'Ext.Evented',
- requires: [
- 'Ext.fx.easing.BoundMomentum',
- 'Ext.fx.easing.EaseOut',
- 'Ext.util.Translatable'
- ],
- /**
- * @event maxpositionchange
- * Fires whenever the maximum position has changed.
- * @param {Ext.scroll.Scroller} this
- * @param {Number} maxPosition The new maximum position.
- */
- /**
- * @event refresh
- * Fires whenever the Scroller is refreshed.
- * @param {Ext.scroll.Scroller} this
- */
- /**
- * @event scrollstart
- * Fires whenever the scrolling is started.
- * @param {Ext.scroll.Scroller} this
- * @param {Number} x The current x position.
- * @param {Number} y The current y position.
- */
- /**
- * @event scrollend
- * Fires whenever the scrolling is ended.
- * @param {Ext.scroll.Scroller} this
- * @param {Number} x The current x position.
- * @param {Number} y The current y position.
- */
- /**
- * @event scroll
- * Fires whenever the Scroller is scrolled.
- * @param {Ext.scroll.Scroller} this
- * @param {Number} x The new x position.
- * @param {Number} y The new y position.
- */
- config: {
- /**
- * @cfg element
- * @private
- */
- element: null,
- /**
- * @cfg {String} direction
- * Possible values: 'auto', 'vertical', 'horizontal', or 'both'.
- * @accessor
- */
- direction: 'auto',
- /**
- * @cfg fps
- * @private
- */
- fps: 'auto',
- /**
- * @cfg {Boolean} disabled
- * Whether or not this component is disabled.
- * @accessor
- */
- disabled: null,
- /**
- * @cfg {Boolean} directionLock
- * `true` to lock the direction of the scroller when the user starts scrolling.
- * This is useful when putting a scroller inside a scroller or a {@link Ext.Carousel}.
- * @accessor
- */
- directionLock: false,
- /**
- * @cfg {Object} momentumEasing
- * A valid config for {@link Ext.fx.easing.BoundMomentum}. The default value is:
- *
- * {
- * momentum: {
- * acceleration: 30,
- * friction: 0.5
- * },
- * bounce: {
- * acceleration: 30,
- * springTension: 0.3
- * }
- * }
- *
- * Note that supplied object will be recursively merged with the default object. For example, you can simply
- * pass this to change the momentum acceleration only:
- *
- * {
- * momentum: {
- * acceleration: 10
- * }
- * }
- *
- * @accessor
- */
- momentumEasing: {
- momentum: {
- acceleration: 30,
- friction: 0.5
- },
- bounce: {
- acceleration: 30,
- springTension: 0.3
- },
- minVelocity: 1
- },
- /**
- * @cfg bounceEasing
- * @private
- */
- bounceEasing: {
- duration: 400
- },
- /**
- * @cfg outOfBoundRestrictFactor
- * @private
- */
- outOfBoundRestrictFactor: 0.5,
- /**
- * @cfg startMomentumResetTime
- * @private
- */
- startMomentumResetTime: 300,
- /**
- * @cfg maxAbsoluteVelocity
- * @private
- */
- maxAbsoluteVelocity: 6,
- /**
- * @cfg containerSize
- * @private
- */
- containerSize: 'auto',
- /**
- * @cfg size
- * @private
- */
- size: 'auto',
- /**
- * @cfg autoRefresh
- * @private
- */
- autoRefresh: true,
- /**
- * @cfg {Object/Number} initialOffset
- * The initial scroller position. When specified as Number,
- * both x and y will be set to that value.
- */
- initialOffset: {
- x: 0,
- y: 0
- },
- /**
- * @cfg {Number/Object} slotSnapSize
- * The size of each slot to snap to in 'px', can be either an object with `x` and `y` values, i.e:
- *
- * {
- * x: 50,
- * y: 100
- * }
- *
- * or a number value to be used for both directions. For example, a value of `50` will be treated as:
- *
- * {
- * x: 50,
- * y: 50
- * }
- *
- * @accessor
- */
- slotSnapSize: {
- x: 0,
- y: 0
- },
- /**
- * @cfg slotSnapOffset
- * @private
- */
- slotSnapOffset: {
- x: 0,
- y: 0
- },
- slotSnapEasing: {
- duration: 150
- },
- translatable: {
- translationMethod: 'auto',
- useWrapper: false
- }
- },
- cls: Ext.baseCSSPrefix + 'scroll-scroller',
- containerCls: Ext.baseCSSPrefix + 'scroll-container',
- dragStartTime: 0,
- dragEndTime: 0,
- isDragging: false,
- isAnimating: false,
- /**
- * @private
- * @constructor
- * @chainable
- */
- constructor: function(config) {
- var element = config && config.element;
- if (Ext.os.is.Android4 && !Ext.browser.is.Chrome) {
- this.onDrag = Ext.Function.createThrottled(this.onDrag, 20, this);
- }
- this.listeners = {
- scope: this,
- touchstart: 'onTouchStart',
- touchend: 'onTouchEnd',
- dragstart: 'onDragStart',
- drag: 'onDrag',
- dragend: 'onDragEnd'
- };
- this.minPosition = { x: 0, y: 0 };
- this.startPosition = { x: 0, y: 0 };
- this.position = { x: 0, y: 0 };
- this.velocity = { x: 0, y: 0 };
- this.isAxisEnabledFlags = { x: false, y: false };
- this.flickStartPosition = { x: 0, y: 0 };
- this.flickStartTime = { x: 0, y: 0 };
- this.lastDragPosition = { x: 0, y: 0 };
- this.dragDirection = { x: 0, y: 0};
- this.initialConfig = config;
- if (element) {
- this.setElement(element);
- }
- return this;
- },
- /**
- * @private
- */
- applyElement: function(element) {
- if (!element) {
- return;
- }
- return Ext.get(element);
- },
- /**
- * @private
- * @chainable
- */
- updateElement: function(element) {
- this.initialize();
- element.addCls(this.cls);
- if (!this.getDisabled()) {
- this.attachListeneners();
- }
- this.onConfigUpdate(['containerSize', 'size'], 'refreshMaxPosition');
- this.on('maxpositionchange', 'snapToBoundary');
- this.on('minpositionchange', 'snapToBoundary');
- return this;
- },
- applyTranslatable: function(config, translatable) {
- return Ext.factory(config, Ext.util.Translatable, translatable);
- },
- updateTranslatable: function(translatable) {
- translatable.setConfig({
- element: this.getElement(),
- listeners: {
- animationframe: 'onAnimationFrame',
- animationend: 'onAnimationEnd',
- scope: this
- }
- });
- },
- updateFps: function(fps) {
- if (fps !== 'auto') {
- this.getTranslatable().setFps(fps);
- }
- },
- /**
- * @private
- */
- attachListeneners: function() {
- this.getContainer().on(this.listeners);
- },
- /**
- * @private
- */
- detachListeners: function() {
- this.getContainer().un(this.listeners);
- },
- /**
- * @private
- */
- updateDisabled: function(disabled) {
- if (disabled) {
- this.detachListeners();
- }
- else {
- this.attachListeneners();
- }
- },
- updateInitialOffset: function(initialOffset) {
- if (typeof initialOffset == 'number') {
- initialOffset = {
- x: initialOffset,
- y: initialOffset
- };
- }
- var position = this.position,
- x, y;
- position.x = x = initialOffset.x;
- position.y = y = initialOffset.y;
- this.getTranslatable().translate(-x, -y);
- },
- /**
- * @private
- * @return {String}
- */
- applyDirection: function(direction) {
- var minPosition = this.getMinPosition(),
- maxPosition = this.getMaxPosition(),
- isHorizontal, isVertical;
- this.givenDirection = direction;
- if (direction === 'auto') {
- isHorizontal = maxPosition.x > minPosition.x;
- isVertical = maxPosition.y > minPosition.y;
- if (isHorizontal && isVertical) {
- direction = 'both';
- }
- else if (isHorizontal) {
- direction = 'horizontal';
- }
- else {
- direction = 'vertical';
- }
- }
- return direction;
- },
- /**
- * @private
- */
- updateDirection: function(direction) {
- var isAxisEnabled = this.isAxisEnabledFlags;
- isAxisEnabled.x = (direction === 'both' || direction === 'horizontal');
- isAxisEnabled.y = (direction === 'both' || direction === 'vertical');
- },
- /**
- * Returns `true` if a specified axis is enabled.
- * @param {String} axis The axis to check (`x` or `y`).
- * @return {Boolean} `true` if the axis is enabled.
- */
- isAxisEnabled: function(axis) {
- this.getDirection();
- return this.isAxisEnabledFlags[axis];
- },
- /**
- * @private
- * @return {Object}
- */
- applyMomentumEasing: function(easing) {
- var defaultClass = Ext.fx.easing.BoundMomentum;
- return {
- x: Ext.factory(easing, defaultClass),
- y: Ext.factory(easing, defaultClass)
- };
- },
- /**
- * @private
- * @return {Object}
- */
- applyBounceEasing: function(easing) {
- var defaultClass = Ext.fx.easing.EaseOut;
- return {
- x: Ext.factory(easing, defaultClass),
- y: Ext.factory(easing, defaultClass)
- };
- },
- updateBounceEasing: function(easing) {
- this.getTranslatable().setEasingX(easing.x).setEasingY(easing.y);
- },
- /**
- * @private
- * @return {Object}
- */
- applySlotSnapEasing: function(easing) {
- var defaultClass = Ext.fx.easing.EaseOut;
- return {
- x: Ext.factory(easing, defaultClass),
- y: Ext.factory(easing, defaultClass)
- };
- },
- /**
- * @private
- * @return {Object}
- */
- getMinPosition: function() {
- var minPosition = this.minPosition;
- if (!minPosition) {
- this.minPosition = minPosition = {
- x: 0,
- y: 0
- };
- this.fireEvent('minpositionchange', this, minPosition);
- }
- return minPosition;
- },
- /**
- * @private
- * @return {Object}
- */
- getMaxPosition: function() {
- var maxPosition = this.maxPosition,
- size, containerSize;
- if (!maxPosition) {
- size = this.getSize();
- containerSize = this.getContainerSize();
- this.maxPosition = maxPosition = {
- x: Math.max(0, size.x - containerSize.x),
- y: Math.max(0, size.y - containerSize.y)
- };
- this.fireEvent('maxpositionchange', this, maxPosition);
- }
- return maxPosition;
- },
- /**
- * @private
- */
- refreshMaxPosition: function() {
- this.maxPosition = null;
- this.getMaxPosition();
- },
- /**
- * @private
- * @return {Object}
- */
- applyContainerSize: function(size) {
- var containerDom = this.getContainer().dom,
- x, y;
- if (!containerDom) {
- return;
- }
- this.givenContainerSize = size;
- if (size === 'auto') {
- x = containerDom.offsetWidth;
- y = containerDom.offsetHeight;
- }
- else {
- x = size.x;
- y = size.y;
- }
- return {
- x: x,
- y: y
- };
- },
- /**
- * @private
- * @param {String/Object} size
- * @return {Object}
- */
- applySize: function(size) {
- var dom = this.getElement().dom,
- x, y;
- if (!dom) {
- return;
- }
- this.givenSize = size;
- if (size === 'auto') {
- x = dom.offsetWidth;
- y = dom.offsetHeight;
- }
- else if (typeof size == 'number') {
- x = size;
- y = size;
- }
- else {
- x = size.x;
- y = size.y;
- }
- return {
- x: x,
- y: y
- };
- },
- /**
- * @private
- */
- updateAutoRefresh: function(autoRefresh) {
- this.getElement().toggleListener(autoRefresh, 'resize', 'onElementResize', this);
- this.getContainer().toggleListener(autoRefresh, 'resize', 'onContainerResize', this);
- },
- applySlotSnapSize: function(snapSize) {
- if (typeof snapSize == 'number') {
- return {
- x: snapSize,
- y: snapSize
- };
- }
- return snapSize;
- },
- applySlotSnapOffset: function(snapOffset) {
- if (typeof snapOffset == 'number') {
- return {
- x: snapOffset,
- y: snapOffset
- };
- }
- return snapOffset;
- },
- /**
- * @private
- * Returns the container for this scroller
- */
- getContainer: function() {
- var container = this.container;
- if (!container) {
- this.container = container = this.getElement().getParent();
- //<debug error>
- if (!container) {
- Ext.Logger.error("Making an element scrollable that doesn't have any container");
- }
- //</debug>
- container.addCls(this.containerCls);
- }
- return container;
- },
- /**
- * @private
- * @return {Ext.scroll.Scroller} this
- * @chainable
- */
- refresh: function() {
- this.stopAnimation();
- this.getTranslatable().refresh();
- this.setSize(this.givenSize);
- this.setContainerSize(this.givenContainerSize);
- this.setDirection(this.givenDirection);
- this.fireEvent('refresh', this);
- return this;
- },
- onElementResize: function(element, info) {
- this.setSize({
- x: info.width,
- y: info.height
- });
- this.refresh();
- },
- onContainerResize: function(container, info) {
- this.setContainerSize({
- x: info.width,
- y: info.height
- });
- this.refresh();
- },
- /**
- * Scrolls to the given location.
- *
- * @param {Number} x The scroll position on the x axis.
- * @param {Number} y The scroll position on the y axis.
- * @param {Boolean/Object} animation (optional) Whether or not to animate the scrolling to the new position.
- *
- * @return {Ext.scroll.Scroller} this
- * @chainable
- */
- scrollTo: function(x, y, animation) {
- var translatable = this.getTranslatable(),
- position = this.position,
- positionChanged = false,
- translationX, translationY;
- if (this.isAxisEnabled('x')) {
- if (typeof x != 'number') {
- x = position.x;
- }
- else {
- if (position.x !== x) {
- position.x = x;
- positionChanged = true;
- }
- }
- translationX = -x;
- }
- if (this.isAxisEnabled('y')) {
- if (typeof y != 'number') {
- y = position.y;
- }
- else {
- if (position.y !== y) {
- position.y = y;
- positionChanged = true;
- }
- }
- translationY = -y;
- }
- if (positionChanged) {
- if (animation !== undefined) {
- translatable.translateAnimated(translationX, translationY, animation);
- }
- else {
- this.fireEvent('scroll', this, position.x, position.y);
- translatable.translate(translationX, translationY);
- }
- }
- return this;
- },
- /**
- * @private
- * @return {Ext.scroll.Scroller} this
- * @chainable
- */
- scrollToTop: function(animation) {
- var initialOffset = this.getInitialOffset();
- return this.scrollTo(initialOffset.x, initialOffset.y, animation);
- },
- /**
- * Scrolls to the end of the scrollable view.
- * @return {Ext.scroll.Scroller} this
- * @chainable
- */
- scrollToEnd: function(animation) {
- var size = this.getSize(),
- cntSize = this.getContainerSize();
- return this.scrollTo(size.x - cntSize.x, size.y - cntSize.y, animation);
- },
- /**
- * Change the scroll offset by the given amount.
- * @param {Number} x The offset to scroll by on the x axis.
- * @param {Number} y The offset to scroll by on the y axis.
- * @param {Boolean/Object} animation (optional) Whether or not to animate the scrolling to the new position.
- * @return {Ext.scroll.Scroller} this
- * @chainable
- */
- scrollBy: function(x, y, animation) {
- var position = this.position;
- x = (typeof x == 'number') ? x + position.x : null;
- y = (typeof y == 'number') ? y + position.y : null;
- return this.scrollTo(x, y, animation);
- },
- /**
- * @private
- */
- onTouchStart: function() {
- this.isTouching = true;
- this.stopAnimation();
- },
- /**
- * @private
- */
- onTouchEnd: function() {
- var position = this.position;
- this.isTouching = false;
- if (!this.isDragging && this.snapToSlot()) {
- this.fireEvent('scrollstart', this, position.x, position.y);
- }
- },
- /**
- * @private
- */
- onDragStart: function(e) {
- var direction = this.getDirection(),
- absDeltaX = e.absDeltaX,
- absDeltaY = e.absDeltaY,
- directionLock = this.getDirectionLock(),
- startPosition = this.startPosition,
- flickStartPosition = this.flickStartPosition,
- flickStartTime = this.flickStartTime,
- lastDragPosition = this.lastDragPosition,
- currentPosition = this.position,
- dragDirection = this.dragDirection,
- x = currentPosition.x,
- y = currentPosition.y,
- now = Ext.Date.now();
- this.isDragging = true;
- if (directionLock && direction !== 'both') {
- if ((direction === 'horizontal' && absDeltaX > absDeltaY)
- || (direction === 'vertical' && absDeltaY > absDeltaX)) {
- e.stopPropagation();
- }
- else {
- this.isDragging = false;
- return;
- }
- }
- lastDragPosition.x = x;
- lastDragPosition.y = y;
- flickStartPosition.x = x;
- flickStartPosition.y = y;
- startPosition.x = x;
- startPosition.y = y;
- flickStartTime.x = now;
- flickStartTime.y = now;
- dragDirection.x = 0;
- dragDirection.y = 0;
- this.dragStartTime = now;
- this.isDragging = true;
- this.fireEvent('scrollstart', this, x, y);
- },
- /**
- * @private
- */
- onAxisDrag: function(axis, delta) {
- if (!this.isAxisEnabled(axis)) {
- return;
- }
- var flickStartPosition = this.flickStartPosition,
- flickStartTime = this.flickStartTime,
- lastDragPosition = this.lastDragPosition,
- dragDirection = this.dragDirection,
- old = this.position[axis],
- min = this.getMinPosition()[axis],
- max = this.getMaxPosition()[axis],
- start = this.startPosition[axis],
- last = lastDragPosition[axis],
- current = start - delta,
- lastDirection = dragDirection[axis],
- restrictFactor = this.getOutOfBoundRestrictFactor(),
- startMomentumResetTime = this.getStartMomentumResetTime(),
- now = Ext.Date.now(),
- distance;
- if (current < min) {
- current *= restrictFactor;
- }
- else if (current > max) {
- distance = current - max;
- current = max + distance * restrictFactor;
- }
- if (current > last) {
- dragDirection[axis] = 1;
- }
- else if (current < last) {
- dragDirection[axis] = -1;
- }
- if ((lastDirection !== 0 && (dragDirection[axis] !== lastDirection))
- || (now - flickStartTime[axis]) > startMomentumResetTime) {
- flickStartPosition[axis] = old;
- flickStartTime[axis] = now;
- }
- lastDragPosition[axis] = current;
- },
- /**
- * @private
- */
- onDrag: function(e) {
- if (!this.isDragging) {
- return;
- }
- var lastDragPosition = this.lastDragPosition;
- this.onAxisDrag('x', e.deltaX);
- this.onAxisDrag('y', e.deltaY);
- this.scrollTo(lastDragPosition.x, lastDragPosition.y);
- },
- /**
- * @private
- */
- onDragEnd: function(e) {
- var easingX, easingY;
- if (!this.isDragging) {
- return;
- }
- this.dragEndTime = Ext.Date.now();
- this.onDrag(e);
- this.isDragging = false;
- easingX = this.getAnimationEasing('x');
- easingY = this.getAnimationEasing('y');
- if (easingX || easingY) {
- this.getTranslatable().animate(easingX, easingY);
- }
- else {
- this.onScrollEnd();
- }
- },
- /**
- * @private
- */
- getAnimationEasing: function(axis) {
- if (!this.isAxisEnabled(axis)) {
- return null;
- }
- var currentPosition = this.position[axis],
- flickStartPosition = this.flickStartPosition[axis],
- flickStartTime = this.flickStartTime[axis],
- minPosition = this.getMinPosition()[axis],
- maxPosition = this.getMaxPosition()[axis],
- maxAbsVelocity = this.getMaxAbsoluteVelocity(),
- boundValue = null,
- dragEndTime = this.dragEndTime,
- easing, velocity, duration;
- if (currentPosition < minPosition) {
- boundValue = minPosition;
- }
- else if (currentPosition > maxPosition) {
- boundValue = maxPosition;
- }
- // Out of bound, to be pulled back
- if (boundValue !== null) {
- easing = this.getBounceEasing()[axis];
- easing.setConfig({
- startTime: dragEndTime,
- startValue: -currentPosition,
- endValue: -boundValue
- });
- return easing;
- }
- // Still within boundary, start deceleration
- duration = dragEndTime - flickStartTime;
- if (duration === 0) {
- return null;
- }
- velocity = (currentPosition - flickStartPosition) / (dragEndTime - flickStartTime);
- if (velocity === 0) {
- return null;
- }
- if (velocity < -maxAbsVelocity) {
- velocity = -maxAbsVelocity;
- }
- else if (velocity > maxAbsVelocity) {
- velocity = maxAbsVelocity;
- }
- easing = this.getMomentumEasing()[axis];
- easing.setConfig({
- startTime: dragEndTime,
- startValue: -currentPosition,
- startVelocity: -velocity,
- minMomentumValue: -maxPosition,
- maxMomentumValue: 0
- });
- return easing;
- },
- /**
- * @private
- */
- onAnimationFrame: function(translatable, x, y) {
- var position = this.position;
- position.x = -x;
- position.y = -y;
- this.fireEvent('scroll', this, position.x, position.y);
- },
- /**
- * @private
- */
- onAnimationEnd: function() {
- this.snapToBoundary();
- this.onScrollEnd();
- },
- /**
- * @private
- * Stops the animation of the scroller at any time.
- */
- stopAnimation: function() {
- this.getTranslatable().stopAnimation();
- },
- /**
- * @private
- */
- onScrollEnd: function() {
- var position = this.position;
- if (this.isTouching || !this.snapToSlot()) {
- this.fireEvent('scrollend', this, position.x, position.y);
- }
- },
- /**
- * @private
- * @return {Boolean}
- */
- snapToSlot: function() {
- var snapX = this.getSnapPosition('x'),
- snapY = this.getSnapPosition('y'),
- easing = this.getSlotSnapEasing();
- if (snapX !== null || snapY !== null) {
- this.scrollTo(snapX, snapY, {
- easingX: easing.x,
- easingY: easing.y
- });
- return true;
- }
- return false;
- },
- /**
- * @private
- * @return {Number/null}
- */
- getSnapPosition: function(axis) {
- var snapSize = this.getSlotSnapSize()[axis],
- snapPosition = null,
- position, snapOffset, maxPosition, mod;
- if (snapSize !== 0 && this.isAxisEnabled(axis)) {
- position = this.position[axis];
- snapOffset = this.getSlotSnapOffset()[axis];
- maxPosition = this.getMaxPosition()[axis];
- mod = (position - snapOffset) % snapSize;
- if (mod !== 0) {
- if (Math.abs(mod) > snapSize / 2) {
- snapPosition = position + ((mod > 0) ? snapSize - mod : mod - snapSize);
- if (snapPosition > maxPosition) {
- snapPosition = position - mod;
- }
- }
- else {
- snapPosition = position - mod;
- }
- }
- }
- return snapPosition;
- },
- /**
- * @private
- */
- snapToBoundary: function() {
- var position = this.position,
- minPosition = this.getMinPosition(),
- maxPosition = this.getMaxPosition(),
- minX = minPosition.x,
- minY = minPosition.y,
- maxX = maxPosition.x,
- maxY = maxPosition.y,
- x = Math.round(position.x),
- y = Math.round(position.y);
- if (x < minX) {
- x = minX;
- }
- else if (x > maxX) {
- x = maxX;
- }
- if (y < minY) {
- y = minY;
- }
- else if (y > maxY) {
- y = maxY;
- }
- this.scrollTo(x, y);
- },
- destroy: function() {
- var element = this.getElement(),
- sizeMonitors = this.sizeMonitors;
- if (sizeMonitors) {
- sizeMonitors.element.destroy();
- sizeMonitors.container.destroy();
- }
- if (element && !element.isDestroyed) {
- element.removeCls(this.cls);
- this.getContainer().removeCls(this.containerCls);
- }
- Ext.destroy(this.getTranslatable());
- this.callParent(arguments);
- }
- }, function() {
- });
- /**
- * @private
- */
- Ext.define('Ext.scroll.indicator.Abstract', {
- extend: 'Ext.Component',
- config: {
- baseCls: 'x-scroll-indicator',
- axis: 'x',
- value: 0,
- length: null,
- minLength: 6,
- hidden: true,
- ui: 'dark'
- },
- cachedConfig: {
- ratio: 1,
- barCls: 'x-scroll-bar',
- active: true
- },
- barElement: null,
- barLength: 0,
- gapLength: 0,
- getElementConfig: function() {
- return {
- reference: 'barElement',
- children: [this.callParent()]
- };
- },
- applyRatio: function(ratio) {
- if (isNaN(ratio)) {
- ratio = 1;
- }
- return ratio;
- },
- refresh: function() {
- var bar = this.barElement,
- barDom = bar.dom,
- ratio = this.getRatio(),
- axis = this.getAxis(),
- barLength = (axis === 'x') ? barDom.offsetWidth : barDom.offsetHeight,
- length = barLength * ratio;
- this.barLength = barLength;
- this.gapLength = barLength - length;
- this.setLength(length);
- this.updateValue(this.getValue());
- },
- updateBarCls: function(barCls) {
- this.barElement.addCls(barCls);
- },
- updateAxis: function(axis) {
- this.element.addCls(this.getBaseCls(), null, axis);
- this.barElement.addCls(this.getBarCls(), null, axis);
- },
- updateValue: function(value) {
- this.setOffset(this.gapLength * value);
- },
- updateActive: function(active) {
- this.barElement[active ? 'addCls' : 'removeCls']('active');
- },
- doSetHidden: function(hidden) {
- var elementDomStyle = this.element.dom.style;
- if (hidden) {
- elementDomStyle.opacity = '0';
- }
- else {
- elementDomStyle.opacity = '';
- }
- },
- applyLength: function(length) {
- return Math.max(this.getMinLength(), length);
- },
- updateLength: function(length) {
- if (!this.isDestroyed) {
- var axis = this.getAxis(),
- element = this.element;
- if (axis === 'x') {
- element.setWidth(length);
- }
- else {
- element.setHeight(length);
- }
- }
- },
- setOffset: function(offset) {
- var axis = this.getAxis(),
- element = this.element;
- if (axis === 'x') {
- element.setLeft(offset);
- }
- else {
- element.setTop(offset);
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.scroll.indicator.Default', {
- extend: 'Ext.scroll.indicator.Abstract',
- config: {
- cls: 'default'
- },
- setOffset: function(offset) {
- var axis = this.getAxis(),
- domStyle = this.element.dom.style;
- if (axis === 'x') {
- domStyle.webkitTransform = 'translate3d(' + offset + 'px, 0, 0)';
- }
- else {
- domStyle.webkitTransform = 'translate3d(0, ' + offset + 'px, 0)';
- }
- },
- updateValue: function(value) {
- var barLength = this.barLength,
- gapLength = this.gapLength,
- length = this.getLength(),
- newLength, offset, extra;
- if (value <= 0) {
- offset = 0;
- this.updateLength(this.applyLength(length + value * barLength));
- }
- else if (value >= 1) {
- extra = Math.round((value - 1) * barLength);
- newLength = this.applyLength(length - extra);
- extra = length - newLength;
- this.updateLength(newLength);
- offset = gapLength + extra;
- }
- else {
- offset = gapLength * value;
- }
- this.setOffset(offset);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.scroll.indicator.ScrollPosition', {
- extend: 'Ext.scroll.indicator.Abstract',
- config: {
- cls: 'scrollposition'
- },
- getElementConfig: function() {
- var config = this.callParent(arguments);
- config.children.unshift({
- className: 'x-scroll-bar-stretcher'
- });
- return config;
- },
- updateValue: function(value) {
- if (this.gapLength === 0) {
- if (value > 1) {
- value = value - 1;
- }
- this.setOffset(this.barLength * value);
- }
- else {
- this.setOffset(this.gapLength * value);
- }
- },
- updateLength: function() {
- var scrollOffset = this.barLength,
- barDom = this.barElement.dom,
- element = this.element;
- this.callParent(arguments);
- if (this.getAxis() === 'x') {
- barDom.scrollLeft = scrollOffset;
- element.setLeft(scrollOffset);
- }
- else {
- barDom.scrollTop = scrollOffset;
- element.setTop(scrollOffset);
- }
- },
- setOffset: function(offset) {
- var barLength = this.barLength,
- minLength = this.getMinLength(),
- barDom = this.barElement.dom;
- offset = Math.min(barLength - minLength, Math.max(offset, minLength - this.getLength()));
- offset = barLength - offset;
- if (this.getAxis() === 'x') {
- barDom.scrollLeft = offset;
- }
- else {
- barDom.scrollTop = offset;
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.scroll.indicator.CssTransform', {
- extend: 'Ext.scroll.indicator.Abstract',
- config: {
- cls: 'csstransform'
- },
- getElementConfig: function() {
- var config = this.callParent();
- config.children[0].children = [
- {
- reference: 'startElement'
- },
- {
- reference: 'middleElement'
- },
- {
- reference: 'endElement'
- }
- ];
- return config;
- },
- refresh: function() {
- var axis = this.getAxis(),
- startElementDom = this.startElement.dom,
- endElementDom = this.endElement.dom,
- middleElement = this.middleElement,
- startElementLength, endElementLength;
- if (axis === 'x') {
- startElementLength = startElementDom.offsetWidth;
- endElementLength = endElementDom.offsetWidth;
- middleElement.setLeft(startElementLength);
- }
- else {
- startElementLength = startElementDom.offsetHeight;
- endElementLength = endElementDom.offsetHeight;
- middleElement.setTop(startElementLength);
- }
- this.startElementLength = startElementLength;
- this.endElementLength = endElementLength;
- this.callParent();
- },
- updateLength: function(length) {
- var axis = this.getAxis(),
- endElementStyle = this.endElement.dom.style,
- middleElementStyle = this.middleElement.dom.style,
- endElementLength = this.endElementLength,
- endElementOffset = length - endElementLength,
- middleElementLength = endElementOffset - this.startElementLength;
- if (axis === 'x') {
- endElementStyle.webkitTransform = 'translate3d(' + endElementOffset + 'px, 0, 0)';
- middleElementStyle.webkitTransform = 'translate3d(0, 0, 0) scaleX(' + middleElementLength + ')';
- }
- else {
- endElementStyle.webkitTransform = 'translate3d(0, ' + endElementOffset + 'px, 0)';
- middleElementStyle.webkitTransform = 'translate3d(0, 0, 0) scaleY(' + middleElementLength + ')';
- }
- },
- updateValue: function(value) {
- var barLength = this.barLength,
- gapLength = this.gapLength,
- length = this.getLength(),
- newLength, offset, extra;
- if (value <= 0) {
- offset = 0;
- this.updateLength(this.applyLength(length + value * barLength));
- }
- else if (value >= 1) {
- extra = Math.round((value - 1) * barLength);
- newLength = this.applyLength(length - extra);
- extra = length - newLength;
- this.updateLength(newLength);
- offset = gapLength + extra;
- }
- else {
- offset = gapLength * value;
- }
- this.setOffset(offset);
- },
- setOffset: function(offset) {
- var axis = this.getAxis(),
- elementStyle = this.element.dom.style;
- offset = Math.round(offset);
- if (axis === 'x') {
- elementStyle.webkitTransform = 'translate3d(' + offset + 'px, 0, 0)';
- }
- else {
- elementStyle.webkitTransform = 'translate3d(0, ' + offset + 'px, 0)';
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.scroll.indicator.Throttled', {
- extend:'Ext.scroll.indicator.Default',
- config: {
- cls: 'throttled'
- },
- constructor: function() {
- this.callParent(arguments);
- this.updateLength = Ext.Function.createThrottled(this.updateLength, 75, this);
- this.setOffset = Ext.Function.createThrottled(this.setOffset, 50, this);
- },
- doSetHidden: function(hidden) {
- if (hidden) {
- this.setOffset(-10000);
- } else {
- delete this.lastLength;
- delete this.lastOffset;
- this.updateValue(this.getValue());
- }
- },
- updateLength: function(length) {
- length = Math.round(length);
- if (this.lastLength === length || this.lastOffset === -10000) {
- return;
- }
- this.lastLength = length;
- Ext.TaskQueue.requestWrite('doUpdateLength', this,[length]);
- },
- doUpdateLength: function(length){
- if (!this.isDestroyed) {
- var axis = this.getAxis(),
- element = this.element;
- if (axis === 'x') {
- element.setWidth(length);
- }
- else {
- element.setHeight(length);
- }
- }
- },
- setOffset: function(offset) {
- offset = Math.round(offset);
- if (this.lastOffset === offset || this.lastOffset === -10000) {
- return;
- }
- this.lastOffset = offset;
- Ext.TaskQueue.requestWrite('doSetOffset', this,[offset]);
- },
- doSetOffset: function(offset) {
- if (!this.isDestroyed) {
- var axis = this.getAxis(),
- domStyle = this.element.dom.style;
- if (axis === 'x') {
- domStyle.webkitTransform = 'translate3d(' + offset + 'px, 0, 0)';
- }
- else {
- domStyle.webkitTransform = 'translate3d(0, ' + offset + 'px, 0)';
- }
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.scroll.Indicator', {
- requires: [
- 'Ext.scroll.indicator.Default',
- 'Ext.scroll.indicator.ScrollPosition',
- 'Ext.scroll.indicator.CssTransform',
- 'Ext.scroll.indicator.Throttled'
- ],
- alternateClassName: 'Ext.util.Indicator',
- constructor: function(config) {
- if (Ext.os.is.Android2 || Ext.os.is.Android3 || Ext.browser.is.ChromeMobile) {
- return new Ext.scroll.indicator.ScrollPosition(config);
- }
- else if (Ext.os.is.iOS) {
- return new Ext.scroll.indicator.CssTransform(config);
- }
- else if (Ext.os.is.Android4) {
- return new Ext.scroll.indicator.Throttled(config);
- }
- else {
- return new Ext.scroll.indicator.Default(config);
- }
- }
- });
- /**
- * This is a simple container that is used to compile content and a {@link Ext.scroll.View} instance. It also
- * provides scroll indicators.
- *
- * 99% of the time all you need to use in this class is {@link #getScroller}.
- *
- * This should never should be extended.
- */
- Ext.define('Ext.scroll.View', {
- extend: 'Ext.Evented',
- alternateClassName: 'Ext.util.ScrollView',
- requires: [
- 'Ext.scroll.Scroller',
- 'Ext.scroll.Indicator'
- ],
- config: {
- /**
- * @cfg {String} indicatorsUi
- * The style of the indicators of this view. Available options are `dark` or `light`.
- */
- indicatorsUi: 'dark',
- element: null,
- scroller: {},
- indicators: {
- x: {
- axis: 'x'
- },
- y: {
- axis: 'y'
- }
- },
- indicatorsHidingDelay: 100,
- cls: Ext.baseCSSPrefix + 'scroll-view'
- },
- /**
- * @method getScroller
- * Returns the scroller instance in this view. Checkout the documentation of {@link Ext.scroll.Scroller} and
- * {@link Ext.Container#getScrollable} for more information.
- * @return {Ext.scroll.View} The scroller
- */
- /**
- * @private
- */
- processConfig: function(config) {
- if (!config) {
- return null;
- }
- if (typeof config == 'string') {
- config = {
- direction: config
- };
- }
- config = Ext.merge({}, config);
- var scrollerConfig = config.scroller,
- name;
- if (!scrollerConfig) {
- config.scroller = scrollerConfig = {};
- }
- for (name in config) {
- if (config.hasOwnProperty(name)) {
- if (!this.hasConfig(name)) {
- scrollerConfig[name] = config[name];
- delete config[name];
- }
- }
- }
- return config;
- },
- constructor: function(config) {
- config = this.processConfig(config);
- this.useIndicators = { x: true, y: true };
- this.doHideIndicators = Ext.Function.bind(this.doHideIndicators, this);
- this.initConfig(config);
- },
- setConfig: function(config) {
- return this.callParent([this.processConfig(config)]);
- },
- updateIndicatorsUi: function(newUi) {
- var indicators = this.getIndicators();
- indicators.x.setUi(newUi);
- indicators.y.setUi(newUi);
- },
- applyScroller: function(config, currentScroller) {
- return Ext.factory(config, Ext.scroll.Scroller, currentScroller);
- },
- applyIndicators: function(config, indicators) {
- var defaultClass = Ext.scroll.Indicator,
- useIndicators = this.useIndicators;
- if (!config) {
- config = {};
- }
- if (!config.x) {
- useIndicators.x = false;
- config.x = {};
- }
- if (!config.y) {
- useIndicators.y = false;
- config.y = {};
- }
- return {
- x: Ext.factory(config.x, defaultClass, indicators && indicators.x),
- y: Ext.factory(config.y, defaultClass, indicators && indicators.y)
- };
- },
- updateIndicators: function(indicators) {
- this.indicatorsGrid = Ext.Element.create({
- className: 'x-scroll-bar-grid-wrapper',
- children: [{
- className: 'x-scroll-bar-grid',
- children: [
- {
- children: [{}, {
- children: [indicators.y.barElement]
- }]
- },
- {
- children: [{
- children: [indicators.x.barElement]
- }, {}]
- }
- ]
- }]
- });
- },
- updateScroller: function(scroller) {
- scroller.on({
- scope: this,
- scrollstart: 'onScrollStart',
- scroll: 'onScroll',
- scrollend: 'onScrollEnd',
- refresh: 'refreshIndicators'
- });
- },
- isAxisEnabled: function(axis) {
- return this.getScroller().isAxisEnabled(axis) && this.useIndicators[axis];
- },
- applyElement: function(element) {
- if (element) {
- return Ext.get(element);
- }
- },
- updateElement: function(element) {
- var scrollerElement = element.getFirstChild().getFirstChild(),
- scroller = this.getScroller();
- element.addCls(this.getCls());
- element.insertFirst(this.indicatorsGrid);
- scroller.setElement(scrollerElement);
- this.refreshIndicators();
- return this;
- },
- showIndicators: function() {
- var indicators = this.getIndicators();
- if (this.hasOwnProperty('indicatorsHidingTimer')) {
- clearTimeout(this.indicatorsHidingTimer);
- delete this.indicatorsHidingTimer;
- }
- if (this.isAxisEnabled('x')) {
- indicators.x.show();
- }
- if (this.isAxisEnabled('y')) {
- indicators.y.show();
- }
- },
- hideIndicators: function() {
- var delay = this.getIndicatorsHidingDelay();
- if (delay > 0) {
- this.indicatorsHidingTimer = setTimeout(this.doHideIndicators, delay);
- }
- else {
- this.doHideIndicators();
- }
- },
- doHideIndicators: function() {
- var indicators = this.getIndicators();
- if (this.isAxisEnabled('x')) {
- indicators.x.hide();
- }
- if (this.isAxisEnabled('y')) {
- indicators.y.hide();
- }
- },
- onScrollStart: function() {
- this.onScroll.apply(this, arguments);
- this.showIndicators();
- },
- onScrollEnd: function() {
- this.hideIndicators();
- },
- onScroll: function(scroller, x, y) {
- this.setIndicatorValue('x', x);
- this.setIndicatorValue('y', y);
- //<debug>
- if (this.isBenchmarking) {
- this.framesCount++;
- }
- //</debug>
- },
- //<debug>
- isBenchmarking: false,
- framesCount: 0,
- getCurrentFps: function() {
- var now = Date.now(),
- fps;
- if (!this.isBenchmarking) {
- this.isBenchmarking = true;
- fps = 0;
- }
- else {
- fps = Math.round(this.framesCount * 1000 / (now - this.framesCountStartTime));
- }
- this.framesCountStartTime = now;
- this.framesCount = 0;
- return fps;
- },
- //</debug>
- setIndicatorValue: function(axis, scrollerPosition) {
- if (!this.isAxisEnabled(axis)) {
- return this;
- }
- var scroller = this.getScroller(),
- scrollerMaxPosition = scroller.getMaxPosition()[axis],
- scrollerContainerSize = scroller.getContainerSize()[axis],
- value;
- if (scrollerMaxPosition === 0) {
- value = scrollerPosition / scrollerContainerSize;
- if (scrollerPosition >= 0) {
- value += 1;
- }
- }
- else {
- if (scrollerPosition > scrollerMaxPosition) {
- value = 1 + ((scrollerPosition - scrollerMaxPosition) / scrollerContainerSize);
- }
- else if (scrollerPosition < 0) {
- value = scrollerPosition / scrollerContainerSize;
- }
- else {
- value = scrollerPosition / scrollerMaxPosition;
- }
- }
- this.getIndicators()[axis].setValue(value);
- },
- refreshIndicator: function(axis) {
- if (!this.isAxisEnabled(axis)) {
- return this;
- }
- var scroller = this.getScroller(),
- indicator = this.getIndicators()[axis],
- scrollerContainerSize = scroller.getContainerSize()[axis],
- scrollerSize = scroller.getSize()[axis],
- ratio = scrollerContainerSize / scrollerSize;
- indicator.setRatio(ratio);
- indicator.refresh();
- },
- refresh: function() {
- return this.getScroller().refresh();
- },
- refreshIndicators: function() {
- var indicators = this.getIndicators();
- indicators.x.setActive(this.isAxisEnabled('x'));
- indicators.y.setActive(this.isAxisEnabled('y'));
- this.refreshIndicator('x');
- this.refreshIndicator('y');
- },
- destroy: function() {
- var element = this.getElement(),
- indicators = this.getIndicators();
- Ext.destroy(this.getScroller(), this.indicatorsGrid);
- if (this.hasOwnProperty('indicatorsHidingTimer')) {
- clearTimeout(this.indicatorsHidingTimer);
- delete this.indicatorsHidingTimer;
- }
- if (element && !element.isDestroyed) {
- element.removeCls(this.getCls());
- }
- indicators.x.destroy();
- indicators.y.destroy();
- delete this.indicatorsGrid;
- this.callParent(arguments);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.behavior.Scrollable', {
- extend: 'Ext.behavior.Behavior',
- requires: [
- 'Ext.scroll.View'
- ],
- constructor: function() {
- this.listeners = {
- painted: 'onComponentPainted',
- scope: this
- };
- this.callParent(arguments);
- },
- onComponentPainted: function() {
- this.scrollView.refresh();
- },
- setConfig: function(config) {
- var scrollView = this.scrollView,
- component = this.component,
- scrollerElement;
- if (config) {
- if (!scrollView) {
- this.scrollView = scrollView = new Ext.scroll.View(config);
- scrollView.on('destroy', 'onScrollViewDestroy', this);
- component.setUseBodyElement(true);
- this.scrollerElement = scrollerElement = component.innerElement;
- this.scrollContainer = scrollerElement.wrap();
- scrollView.setElement(component.bodyElement);
- if (component.isPainted()) {
- this.onComponentPainted(component);
- }
- component.on(this.listeners);
- }
- else if (Ext.isString(config) || Ext.isObject(config)) {
- scrollView.setConfig(config);
- }
- }
- else if (scrollView) {
- scrollView.destroy();
- }
- return this;
- },
- getScrollView: function() {
- return this.scrollView;
- },
- onScrollViewDestroy: function() {
- var component = this.component,
- scrollerElement = this.scrollerElement;
- if (!scrollerElement.isDestroyed) {
- this.scrollerElement.unwrap();
- }
- this.scrollContainer.destroy();
- if (!component.isDestroyed) {
- component.un(this.listeners);
- }
- delete this.scrollerElement;
- delete this.scrollView;
- delete this.scrollContainer;
- },
- onComponentDestroy: function() {
- var scrollView = this.scrollView;
- if (scrollView) {
- scrollView.destroy();
- }
- }
- });
- /**
- * A simple class used to mask any {@link Ext.Container}.
- *
- * This should rarely be used directly, instead look at the {@link Ext.Container#masked} configuration.
- *
- * ## Example
- *
- * @example miniphone
- * // Create our container
- * var container = Ext.create('Ext.Container', {
- * html: 'My container!'
- * });
- *
- * // Add the container to the Viewport
- * Ext.Viewport.add(container);
- *
- * // Mask the container
- * container.setMasked(true);
- */
- Ext.define('Ext.Mask', {
- extend: 'Ext.Component',
- xtype: 'mask',
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'mask',
- /**
- * @cfg {Boolean} transparent True to make this mask transparent.
- */
- transparent: false,
- /**
- * @cfg
- * @hide
- */
- top: 0,
- /**
- * @cfg
- * @hide
- */
- left: 0,
- /**
- * @cfg
- * @hide
- */
- right: 0,
- /**
- * @cfg
- * @hide
- */
- bottom: 0
- },
- /**
- * @event tap
- * A tap event fired when a user taps on this mask
- * @param {Ext.Mask} this The mask instance
- * @param {Ext.EventObject} e The event object
- */
- initialize: function() {
- this.callSuper();
- this.element.on('*', 'onEvent', this);
- },
- onEvent: function(e) {
- var controller = arguments[arguments.length - 1];
- if (controller.info.eventName === 'tap') {
- this.fireEvent('tap', this, e);
- return false;
- }
- if (e && e.stopEvent) {
- e.stopEvent();
- }
- return false;
- },
- updateTransparent: function(newTransparent) {
- this[newTransparent ? 'addCls' : 'removeCls'](this.getBaseCls() + '-transparent');
- }
- });
- /**
- * A Container has all of the abilities of {@link Ext.Component Component}, but lets you nest other Components inside
- * it. Applications are made up of lots of components, usually nested inside one another. Containers allow you to
- * render and arrange child Components inside them. Most apps have a single top-level Container called a Viewport,
- * which takes up the entire screen. Inside of this are child components, for example in a mail app the Viewport
- * Container's two children might be a message List and an email preview pane.
- *
- * Containers give the following extra functionality:
- *
- * - Adding child Components at instantiation and run time
- * - Removing child Components
- * - Specifying a [Layout](#!/guide/layouts)
- *
- * Layouts determine how the child Components should be laid out on the screen. In our mail app example we'd use an
- * HBox layout so that we can pin the email list to the left hand edge of the screen and allow the preview pane to
- * occupy the rest. There are several layouts in Sencha Touch 2, each of which help you achieve your desired
- * application structure, further explained in the [Layout guide](#!/guide/layouts).
- *
- * ## Adding Components to Containers
- *
- * As we mentioned above, Containers are special Components that can have child Components arranged by a Layout. One of
- * the code samples above showed how to create a Panel with 2 child Panels already defined inside it but it's easy to
- * do this at run time too:
- *
- * @example miniphone
- * //this is the Panel we'll be adding below
- * var aboutPanel = Ext.create('Ext.Panel', {
- * html: 'About this app'
- * });
- *
- * //this is the Panel we'll be adding to
- * var mainPanel = Ext.create('Ext.Panel', {
- * fullscreen: true,
- *
- * layout: 'hbox',
- * defaults: {
- * flex: 1
- * },
- *
- * items: {
- * html: 'First Panel',
- * style: 'background-color: #5E99CC;'
- * }
- * });
- *
- * //now we add the first panel inside the second
- * mainPanel.add(aboutPanel);
- *
- * Here we created three Panels in total. First we made the aboutPanel, which we might use to tell the user a little
- * about the app. Then we create one called mainPanel, which already contains a third Panel in its
- * {@link Ext.Container#cfg-items items} configuration, with some dummy text ("First Panel"). Finally, we add the first
- * panel to the second by calling the {@link Ext.Container#method-add add} method on `mainPanel`.
- *
- * In this case we gave our mainPanel another hbox layout, but we also introduced some
- * {@link Ext.Container#defaults defaults}. These are applied to every item in the Panel, so in this case every child
- * inside `mainPanel` will be given a `flex: 1` configuration. The effect of this is that when we first render the screen
- * only a single child is present inside `mainPanel`, so that child takes up the full width available to it. Once the
- * `mainPanel.add` line is called though, the `aboutPanel` is rendered inside of it and also given a `flex` of 1, which will
- * cause it and the first panel to both receive half the full width of the `mainPanel`.
- *
- * Likewise, it's easy to remove items from a Container:
- *
- * mainPanel.remove(aboutPanel);
- *
- * After this line is run everything is back to how it was, with the first child panel once again taking up the full
- * width inside `mainPanel`.
- *
- * ## Further Reading
- *
- * See the [Component & Container Guide](#!/guide/components) for more information, and check out the
- * {@link Ext.Container} class docs also.
- *
- * @aside guide components
- * @aside guide layouts
- */
- Ext.define('Ext.Container', {
- extend: 'Ext.Component',
- alternateClassName: 'Ext.lib.Container',
- requires: [
- 'Ext.layout.*',
- 'Ext.ItemCollection',
- 'Ext.behavior.Scrollable',
- 'Ext.Mask'
- ],
- xtype: 'container',
- /**
- * @event add
- * Fires whenever item added to the Container.
- * @param {Ext.Container} this The Container instance.
- * @param {Object} item The item added to the Container.
- * @param {Number} index The index of the item within the Container.
- */
- /**
- * @event remove
- * Fires whenever item removed from the Container.
- * @param {Ext.Container} this The Container instance.
- * @param {Object} item The item removed from the Container.
- * @param {Number} index The index of the item that was removed.
- */
- /**
- * @event move
- * Fires whenever item moved within the Container.
- * @param {Ext.Container} this The Container instance.
- * @param {Object} item The item moved within the Container.
- * @param {Number} toIndex The new index of the item.
- * @param {Number} fromIndex The old index of the item.
- */
- /**
- * @private
- * @event renderedchange
- * Fires whenever an item is rendered into a container or derendered
- * from a Container.
- * @param {Ext.Container} this The Container instance.
- * @param {Object} item The item in the Container.
- * @param {Boolean} rendered The current rendered status of the item.
- */
- /**
- * @event activate
- * Fires whenever item within the Container is activated.
- * @param {Ext.Container} this The Container instance.
- * @param {Object} newActiveItem The new active item within the container.
- * @param {Object} oldActiveItem The old active item within the container.
- */
- /**
- * @event deactivate
- * Fires whenever item within the Container is deactivated.
- * @param {Ext.Container} this The Container instance.
- * @param {Object} newActiveItem The new active item within the container.
- * @param {Object} oldActiveItem The old active item within the container.
- */
- eventedConfig: {
- /**
- * @cfg {Object/String/Number} activeItem The item from the {@link #cfg-items} collection that will be active first. This is
- * usually only meaningful in a {@link Ext.layout.Card card layout}, where only one item can be active at a
- * time. If passes a string, it will be assumed to be a {@link Ext.ComponentQuery} selector.
- * @accessor
- * @evented
- */
- activeItem: 0,
- /**
- * @cfg {Boolean/String/Object} scrollable
- * Configuration options to make this Container scrollable. Acceptable values are:
- *
- * - `'horizontal'`, `'vertical'`, `'both'` to enabling scrolling for that direction.
- * - `true`/`false` to explicitly enable/disable scrolling.
- *
- * Alternatively, you can give it an object which is then passed to the scroller instance:
- *
- * scrollable: {
- * direction: 'vertical',
- * directionLock: true
- * }
- *
- * Please look at the {@link Ext.scroll.Scroller} documentation for more example on how to use this.
- * @accessor
- * @evented
- */
- scrollable: null
- },
- config: {
- /**
- * @cfg {String/Object/Boolean} cardSwitchAnimation
- * Animation to be used during transitions of cards.
- * @removed 2.0.0 Please use {@link Ext.layout.Card#animation} instead
- */
- /**
- * @cfg {Object/String} layout Configuration for this Container's layout. Example:
- *
- * Ext.create('Ext.Container', {
- * layout: {
- * type: 'hbox',
- * align: 'middle'
- * },
- * items: [
- * {
- * xtype: 'panel',
- * flex: 1,
- * style: 'background-color: red;'
- * },
- * {
- * xtype: 'panel',
- * flex: 2,
- * style: 'background-color: green'
- * }
- * ]
- * });
- *
- * See the [Layouts Guide](#!/guide/layouts) for more information.
- *
- * @accessor
- */
- layout: null,
- /**
- * @cfg {Object} control Enables you to easily control Components inside this Container by listening to their
- * events and taking some action. For example, if we had a container with a nested Disable button, and we
- * wanted to hide the Container when the Disable button is tapped, we could do this:
- *
- * Ext.create('Ext.Container', {
- * control: {
- * 'button[text=Disable]': {
- * tap: 'hideMe'
- * }
- * },
- *
- * hideMe: function () {
- * this.hide();
- * }
- * });
- *
- * We used a {@link Ext.ComponentQuery} selector to listen to the {@link Ext.Button#tap tap} event on any
- * {@link Ext.Button button} anywhere inside the Container that has the {@link Ext.Button#text text} 'Disable'.
- * Whenever a Component matching that selector fires the `tap` event our `hideMe` function is called. `hideMe` is
- * called with scope: `this` (e.g. `this` is the Container instance).
- *
- */
- control: {},
- /**
- * @cfg {Object} defaults A set of default configurations to apply to all child Components in this Container.
- * It's often useful to specify defaults when creating more than one items with similar configurations. For
- * example here we can specify that each child is a panel and avoid repeating the xtype declaration for each
- * one:
- *
- * Ext.create('Ext.Container', {
- * defaults: {
- * xtype: 'panel'
- * },
- * items: [
- * {
- * html: 'Panel 1'
- * },
- * {
- * html: 'Panel 2'
- * }
- * ]
- * });
- *
- * @accessor
- */
- defaults: null,
- /**
- * @cfg {Array/Object} items The child items to add to this Container. This is usually an array of Component
- * configurations or instances, for example:
- *
- * Ext.create('Ext.Container', {
- * items: [
- * {
- * xtype: 'panel',
- * html: 'This is an item'
- * }
- * ]
- * });
- * @accessor
- */
- items: null,
- /**
- * @cfg {Boolean} autoDestroy If `true`, child items will be destroyed as soon as they are {@link #method-remove removed}
- * from this container.
- * @accessor
- */
- autoDestroy: true,
- /** @cfg {String} defaultType
- * The default {@link Ext.Component xtype} of child Components to create in this Container when a child item
- * is specified as a raw configuration object, rather than as an instantiated Component.
- * @accessor
- */
- defaultType: null,
- //@private
- useBodyElement: null,
- /**
- * @cfg {Boolean/Object/Ext.Mask/Ext.LoadMask} masked
- * A configuration to allow you to mask this container.
- * You can optionally pass an object block with and xtype of `loadmask`, and an optional `message` value to
- * display a loading mask. Please refer to the {@link Ext.LoadMask} component to see other configurations.
- *
- * masked: {
- * xtype: 'loadmask',
- * message: 'My message'
- * }
- *
- * Alternatively, you can just call the setter at any time with `true`/`false` to show/hide the mask:
- *
- * setMasked(true); //show the mask
- * setMasked(false); //hides the mask
- *
- * There are also two convenient methods, {@link #method-mask} and {@link #unmask}, to allow you to mask and unmask
- * this container at any time.
- *
- * Remember, the {@link Ext.Viewport} is always a container, so if you want to mask your whole application at anytime,
- * can call:
- *
- * Ext.Viewport.setMasked({
- * xtype: 'loadmask',
- * message: 'Hello'
- * });
- *
- * @accessor
- */
- masked: null,
- /**
- * @cfg {Boolean} modal `true` to make this Container modal. This will create a mask underneath the Container
- * that covers its parent and does not allow the user to interact with any other Components until this
- * Container is dismissed.
- * @accessor
- */
- modal: null,
- /**
- * @cfg {Boolean} hideOnMaskTap When using a {@link #modal} Component, setting this to `true` will hide the modal
- * mask and the Container when the mask is tapped on.
- * @accessor
- */
- hideOnMaskTap: null
- },
- isContainer: true,
- constructor: function(config) {
- var me = this;
- me._items = me.items = new Ext.ItemCollection();
- me.innerItems = [];
- me.onItemAdd = me.onFirstItemAdd;
- me.callParent(arguments);
- },
- getElementConfig: function() {
- return {
- reference: 'element',
- classList: ['x-container', 'x-unsized'],
- children: [{
- reference: 'innerElement',
- className: 'x-inner'
- }]
- };
- },
- /**
- * Changes the {@link #masked} configuration when its setter is called, which will convert the value
- * into a proper object/instance of {@link Ext.Mask}/{@link Ext.LoadMask}. If a mask already exists,
- * it will use that instead.
- * @param masked
- * @param currentMask
- * @return {Object}
- */
- applyMasked: function(masked, currentMask) {
- var isVisible = true;
- if (masked === false) {
- masked = true;
- isVisible = false;
- }
- currentMask = Ext.factory(masked, Ext.Mask, currentMask);
- if (currentMask) {
- this.add(currentMask);
- currentMask.setHidden(!isVisible);
- }
- return currentMask;
- },
- /**
- * Convenience method which calls {@link #setMasked} with a value of `true` (to show the mask). For additional
- * functionality, call the {@link #setMasked} function direction (See the {@link #masked} configuration documentation
- * for more information).
- */
- mask: function(mask) {
- this.setMasked(mask || true);
- },
- /**
- * Convenience method which calls {@link #setMasked} with a value of false (to hide the mask). For additional
- * functionality, call the {@link #setMasked} function direction (See the {@link #masked} configuration documentation
- * for more information).
- */
- unmask: function() {
- this.setMasked(false);
- },
- setParent: function(container) {
- this.callSuper(arguments);
- if (container) {
- var modal = this.getModal();
- if (modal) {
- container.insertBefore(modal, this);
- modal.setZIndex(this.getZIndex() - 1);
- }
- }
- },
- applyModal: function(modal, currentModal) {
- var isVisible = true;
- if (modal === false) {
- modal = true;
- isVisible = false;
- }
- currentModal = Ext.factory(modal, Ext.Mask, currentModal);
- if (currentModal) {
- currentModal.setVisibility(isVisible);
- }
- return currentModal;
- },
- updateModal: function(modal) {
- var container = this.getParent();
- if (container) {
- if (modal) {
- container.insertBefore(modal, this);
- modal.setZIndex(this.getZIndex() - 1);
- }
- else {
- container.remove(modal);
- }
- }
- },
- updateHideOnMaskTap : function(hide) {
- var mask = this.getModal();
- if (mask) {
- mask[hide ? 'on' : 'un'].call(mask, 'tap', 'hide', this);
- }
- },
- updateZIndex: function(zIndex) {
- var modal = this.getModal();
- this.callParent(arguments);
- if (modal) {
- modal.setZIndex(zIndex - 1);
- }
- },
- updateBaseCls: function(newBaseCls, oldBaseCls) {
- var me = this,
- ui = me.getUi();
- if (newBaseCls) {
- this.element.addCls(newBaseCls);
- this.innerElement.addCls(newBaseCls, null, 'inner');
- if (ui) {
- this.element.addCls(newBaseCls, null, ui);
- }
- }
- if (oldBaseCls) {
- this.element.removeCls(oldBaseCls);
- this.innerElement.removeCls(newBaseCls, null, 'inner');
- if (ui) {
- this.element.removeCls(oldBaseCls, null, ui);
- }
- }
- },
- updateUseBodyElement: function(useBodyElement) {
- if (useBodyElement) {
- this.link('bodyElement', this.innerElement.wrap({
- cls: 'x-body'
- }));
- }
- },
- applyItems: function(items, collection) {
- if (items) {
- var me = this;
- me.getDefaultType();
- me.getDefaults();
- if (me.initialized && collection.length > 0) {
- me.removeAll();
- }
- me.add(items);
- //Don't need to call setActiveItem when Container is first initialized
- if (me.initialized) {
- var activeItem = me.initialConfig.activeItem || me.config.activeItem || 0;
- me.setActiveItem(activeItem);
- }
- }
- },
- /**
- * @private
- */
- applyControl: function(selectors) {
- var selector, key, listener, listeners;
- for (selector in selectors) {
- listeners = selectors[selector];
- for (key in listeners) {
- listener = listeners[key];
- if (Ext.isObject(listener)) {
- listener.delegate = selector;
- }
- }
- listeners.delegate = selector;
- this.addListener(listeners);
- }
- return selectors;
- },
- /**
- * Initialize layout and event listeners the very first time an item is added
- * @private
- */
- onFirstItemAdd: function() {
- delete this.onItemAdd;
- if (this.innerHtmlElement && !this.getHtml()) {
- this.innerHtmlElement.destroy();
- delete this.innerHtmlElement;
- }
- this.on('innerstatechange', 'onItemInnerStateChange', this, {
- delegate: '> component'
- });
- return this.onItemAdd.apply(this, arguments);
- },
- //<debug error>
- updateLayout: function(newLayout, oldLayout) {
- if (oldLayout && oldLayout.isLayout) {
- Ext.Logger.error('Replacing a layout after one has already been initialized is not currently supported.');
- }
- },
- //</debug>
- getLayout: function() {
- var layout = this.layout;
- if (!layout) {
- layout = this.link('_layout', this.link('layout', Ext.factory(this._layout || 'default', Ext.layout.Default, null, 'layout')));
- layout.setContainer(this);
- }
- return layout;
- },
- updateDefaultType: function(defaultType) {
- // Cache the direct reference to the default item class here for performance
- this.defaultItemClass = Ext.ClassManager.getByAlias('widget.' + defaultType);
- //<debug error>
- if (!this.defaultItemClass) {
- Ext.Logger.error("Invalid defaultType of: '" + defaultType + "', must be a valid component xtype");
- }
- //</debug>
- },
- applyDefaults: function(defaults) {
- if (defaults) {
- this.factoryItem = this.factoryItemWithDefaults;
- return defaults;
- }
- },
- factoryItem: function(item) {
- //<debug error>
- if (!item) {
- Ext.Logger.error("Invalid item given: " + item + ", must be either the config object to factory a new item, " +
- "or an existing component instance");
- }
- //</debug>
- return Ext.factory(item, this.defaultItemClass);
- },
- factoryItemWithDefaults: function(item) {
- //<debug error>
- if (!item) {
- Ext.Logger.error("Invalid item given: " + item + ", must be either the config object to factory a new item, " +
- "or an existing component instance");
- }
- //</debug>
- var me = this,
- defaults = me.getDefaults(),
- instance;
- if (!defaults) {
- return Ext.factory(item, me.defaultItemClass);
- }
- // Existing instance
- if (item.isComponent) {
- instance = item;
- // Apply defaults only if this is not already an item of this container
- if (defaults && item.isInnerItem() && !me.has(instance)) {
- instance.setConfig(defaults, true);
- }
- }
- // Config object
- else {
- if (defaults && !item.ignoreDefaults) {
- // Note:
- // - defaults is only applied to inner items
- // - we merge the given config together with defaults into a new object so that the original object stays intact
- if (!(
- item.hasOwnProperty('left') &&
- item.hasOwnProperty('right') &&
- item.hasOwnProperty('top') &&
- item.hasOwnProperty('bottom') &&
- item.hasOwnProperty('docked') &&
- item.hasOwnProperty('centered')
- )) {
- item = Ext.mergeIf({}, item, defaults);
- }
- }
- instance = Ext.factory(item, me.defaultItemClass);
- }
- return instance;
- },
- /**
- * Adds one or more Components to this Container. Example:
- *
- * var myPanel = Ext.create('Ext.Panel', {
- * html: 'This will be added to a Container'
- * });
- *
- * myContainer.add([myPanel]);
- *
- * @param {Object/Object[]/Ext.Component/Ext.Component[]} newItems The new items to add to the Container.
- * @return {Ext.Component} The last item added to the Container from the `newItems` array.
- */
- add: function(newItems) {
- var me = this,
- i, ln, item, newActiveItem;
- newItems = Ext.Array.from(newItems);
- ln = newItems.length;
- for (i = 0; i < ln; i++) {
- item = me.factoryItem(newItems[i]);
- this.doAdd(item);
- if (!newActiveItem && !this.getActiveItem() && this.innerItems.length > 0 && item.isInnerItem()) {
- newActiveItem = item;
- }
- }
- if (newActiveItem) {
- this.setActiveItem(newActiveItem);
- }
- return item;
- },
- /**
- * @private
- * @param item
- */
- doAdd: function(item) {
- var me = this,
- items = me.getItems(),
- index;
- if (!items.has(item)) {
- index = items.length;
- items.add(item);
- if (item.isInnerItem()) {
- me.insertInner(item);
- }
- item.setParent(me);
- me.onItemAdd(item, index);
- }
- },
- /**
- * Removes an item from this Container, optionally destroying it.
- * @param {Object} item The item to remove.
- * @param {Boolean} destroy Calls the Component's {@link Ext.Component#destroy destroy} method if `true`.
- * @return {Ext.Component} this
- */
- remove: function(item, destroy) {
- var me = this,
- index = me.indexOf(item),
- innerItems = me.getInnerItems();
- if (destroy === undefined) {
- destroy = me.getAutoDestroy();
- }
- if (index !== -1) {
- if (!me.removingAll && innerItems.length > 1 && item === me.getActiveItem()) {
- me.on({
- activeitemchange: 'doRemove',
- scope: me,
- single: true,
- order: 'after',
- args: [item, index, destroy]
- });
- me.doResetActiveItem(innerItems.indexOf(item));
- }
- else {
- me.doRemove(item, index, destroy);
- if (innerItems.length === 0) {
- me.setActiveItem(null);
- }
- }
- }
- return me;
- },
- doResetActiveItem: function(innerIndex) {
- if (innerIndex === 0) {
- this.setActiveItem(1);
- }
- else {
- this.setActiveItem(0);
- }
- },
- doRemove: function(item, index, destroy) {
- var me = this;
- me.items.remove(item);
- if (item.isInnerItem()) {
- me.removeInner(item);
- }
- me.onItemRemove(item, index, destroy);
- item.setParent(null);
- if (destroy) {
- item.destroy();
- }
- },
- /**
- * Removes all items currently in the Container, optionally destroying them all.
- * @param {Boolean} destroy If `true`, {@link Ext.Component#destroy destroys} each removed Component.
- * @param {Boolean} everything If `true`, completely remove all items including docked / centered and floating items.
- * @return {Ext.Component} this
- */
- removeAll: function(destroy, everything) {
- var items = this.items,
- ln = items.length,
- i = 0,
- item;
- if (destroy === undefined) {
- destroy = this.getAutoDestroy();
- }
- everything = Boolean(everything);
- // removingAll flag is used so we don't unnecessarily change activeItem while removing all items.
- this.removingAll = true;
- for (; i < ln; i++) {
- item = items.getAt(i);
- if (item && (everything || item.isInnerItem())) {
- this.doRemove(item, i, destroy);
- i--;
- ln--;
- }
- }
- this.setActiveItem(null);
- this.removingAll = false;
- return this;
- },
- /**
- * Returns the Component for a given index in the Container's {@link #property-items}.
- * @param {Number} index The index of the Component to return.
- * @return {Ext.Component} The item at the specified `index`, if found.
- */
- getAt: function(index) {
- return this.items.getAt(index);
- },
- getInnerAt: function(index) {
- return this.innerItems[index];
- },
- /**
- * Removes the Component at the specified index:
- *
- * myContainer.removeAt(0); // removes the first item
- *
- * @param {Number} index The index of the Component to remove.
- */
- removeAt: function(index) {
- var item = this.getAt(index);
- if (item) {
- this.remove(item);
- }
- return this;
- },
- /**
- * Removes an inner Component at the specified index:
- *
- * myContainer.removeInnerAt(0); // removes the first item of the innerItems property
- *
- * @param {Number} index The index of the Component to remove.
- */
- removeInnerAt: function(index) {
- var item = this.getInnerItems()[index];
- if (item) {
- this.remove(item);
- }
- return this;
- },
- /**
- * @private
- */
- has: function(item) {
- return this.getItems().indexOf(item) != -1;
- },
- /**
- * @private
- */
- hasInnerItem: function(item) {
- return this.innerItems.indexOf(item) != -1;
- },
- /**
- * @private
- */
- indexOf: function(item) {
- return this.getItems().indexOf(item);
- },
- innerIndexOf: function(item) {
- return this.innerItems.indexOf(item);
- },
- /**
- * @private
- * @param item
- * @param index
- */
- insertInner: function(item, index) {
- var items = this.getItems().items,
- innerItems = this.innerItems,
- currentInnerIndex = innerItems.indexOf(item),
- newInnerIndex = -1,
- nextSibling;
- if (currentInnerIndex !== -1) {
- innerItems.splice(currentInnerIndex, 1);
- }
- if (typeof index == 'number') {
- do {
- nextSibling = items[++index];
- } while (nextSibling && !nextSibling.isInnerItem());
- if (nextSibling) {
- newInnerIndex = innerItems.indexOf(nextSibling);
- innerItems.splice(newInnerIndex, 0, item);
- }
- }
- if (newInnerIndex === -1) {
- innerItems.push(item);
- newInnerIndex = innerItems.length - 1;
- }
- if (currentInnerIndex !== -1) {
- this.onInnerItemMove(item, newInnerIndex, currentInnerIndex);
- }
- return this;
- },
- onInnerItemMove: Ext.emptyFn,
- /**
- * @private
- * @param item
- */
- removeInner: function(item) {
- Ext.Array.remove(this.innerItems, item);
- return this;
- },
- /**
- * Adds a child Component at the given index. For example, here's how we can add a new item, making it the first
- * child Component of this Container:
- *
- * myContainer.insert(0, {xtype: 'panel', html: 'new item'});
- *
- * @param {Number} index The index to insert the Component at.
- * @param {Object} item The Component to insert.
- */
- insert: function(index, item) {
- var me = this,
- i;
- if (Ext.isArray(item)) {
- for (i = item.length - 1; i >= 0; i--) {
- me.insert(index, item[i]);
- }
- return me;
- }
- item = this.factoryItem(item);
- this.doInsert(index, item);
- return item;
- },
- /**
- * @private
- * @param index
- * @param item
- */
- doInsert: function(index, item) {
- var me = this,
- items = me.items,
- itemsLength = items.length,
- currentIndex, isInnerItem;
- isInnerItem = item.isInnerItem();
- if (index > itemsLength) {
- index = itemsLength;
- }
- if (items[index - 1] === item) {
- return me;
- }
- currentIndex = me.indexOf(item);
- if (currentIndex !== -1) {
- if (currentIndex < index) {
- index -= 1;
- }
- items.removeAt(currentIndex);
- }
- else {
- item.setParent(me);
- }
- items.insert(index, item);
- if (isInnerItem) {
- me.insertInner(item, index);
- }
- if (currentIndex !== -1) {
- me.onItemMove(item, index, currentIndex);
- }
- else {
- me.onItemAdd(item, index);
- }
- },
- /**
- * @private
- */
- insertFirst: function(item) {
- return this.insert(0, item);
- },
- /**
- * @private
- */
- insertLast: function(item) {
- return this.insert(this.getItems().length, item);
- },
- /**
- * @private
- */
- insertBefore: function(item, relativeToItem) {
- var index = this.indexOf(relativeToItem);
- if (index !== -1) {
- this.insert(index, item);
- }
- return this;
- },
- /**
- * @private
- */
- insertAfter: function(item, relativeToItem) {
- var index = this.indexOf(relativeToItem);
- if (index !== -1) {
- this.insert(index + 1, item);
- }
- return this;
- },
- /**
- * @private
- */
- onItemAdd: function(item, index) {
- this.doItemLayoutAdd(item, index);
- if (this.initialized) {
- this.fireEvent('add', this, item, index);
- }
- },
- doItemLayoutAdd: function(item, index) {
- var layout = this.getLayout();
- if (this.isRendered() && item.setRendered(true)) {
- item.fireAction('renderedchange', [this, item, true], 'onItemAdd', layout, { args: [item, index] });
- }
- else {
- layout.onItemAdd(item, index);
- }
- },
- /**
- * @private
- */
- onItemRemove: function(item, index) {
- this.doItemLayoutRemove(item, index);
- this.fireEvent('remove', this, item, index);
- },
- doItemLayoutRemove: function(item, index) {
- var layout = this.getLayout();
- if (this.isRendered() && item.setRendered(false)) {
- item.fireAction('renderedchange', [this, item, false], 'onItemRemove', layout, { args: [item, index, undefined] });
- }
- else {
- layout.onItemRemove(item, index);
- }
- },
- /**
- * @private
- */
- onItemMove: function(item, toIndex, fromIndex) {
- if (item.isDocked()) {
- item.setDocked(null);
- }
- this.doItemLayoutMove(item, toIndex, fromIndex);
- this.fireEvent('move', this, item, toIndex, fromIndex);
- },
- doItemLayoutMove: function(item, toIndex, fromIndex) {
- this.getLayout().onItemMove(item, toIndex, fromIndex);
- },
- onItemInnerStateChange: function(item, isInner) {
- var layout = this.getLayout();
- if (isInner) {
- this.insertInner(item, this.items.indexOf(item));
- }
- else {
- this.removeInner(item);
- }
- layout.onItemInnerStateChange.apply(layout, arguments);
- },
- /**
- * Returns all inner {@link #property-items} of this container. `inner` means that the item is not `docked` or
- * `floating`.
- * @return {Array} The inner items of this container.
- */
- getInnerItems: function() {
- return this.innerItems;
- },
- /**
- * Returns all the {@link Ext.Component#docked} items in this container.
- * @return {Array} The docked items of this container.
- */
- getDockedItems: function() {
- var items = this.getItems().items,
- dockedItems = [],
- ln = items.length,
- item, i;
- for (i = 0; i < ln; i++) {
- item = items[i];
- if (item.isDocked()) {
- dockedItems.push(item);
- }
- }
- return dockedItems;
- },
- /**
- * @private
- */
- applyActiveItem: function(activeItem, currentActiveItem) {
- var innerItems = this.getInnerItems();
- // Make sure the items are already initialized
- this.getItems();
- // No items left to be active, reset back to 0 on falsy changes
- if (!activeItem && innerItems.length === 0) {
- return 0;
- }
- else if (typeof activeItem == 'number') {
- activeItem = Math.max(0, Math.min(activeItem, innerItems.length - 1));
- activeItem = innerItems[activeItem];
- if (activeItem) {
- return activeItem;
- }
- else if (currentActiveItem) {
- return null;
- }
- }
- else if (activeItem) {
- var item;
- //ComponentQuery selector?
- if (typeof activeItem == 'string') {
- item = this.child(activeItem);
- activeItem = {
- xtype : activeItem
- };
- }
- if (!item || !item.isComponent) {
- item = this.factoryItem(activeItem);
- }
- this.pendingActiveItem = item;
- //<debug error>
- if (!item.isInnerItem()) {
- Ext.Logger.error("Setting activeItem to be a non-inner item");
- }
- //</debug>
- if (!this.has(item)) {
- this.add(item);
- }
- return item;
- }
- },
- /**
- * Animates to the supplied `activeItem` with a specified animation. Currently this only works
- * with a Card layout. This passed animation will override any default animations on the
- * container, for a single card switch. The animation will be destroyed when complete.
- * @param {Object/Number} activeItem The item or item index to make active.
- * @param {Object/Ext.fx.layout.Card} animation Card animation configuration or instance.
- */
- animateActiveItem: function(activeItem, animation) {
- var layout = this.getLayout(),
- defaultAnimation;
- if (this.activeItemAnimation) {
- this.activeItemAnimation.destroy();
- }
- this.activeItemAnimation = animation = new Ext.fx.layout.Card(animation);
- if (animation && layout.isCard) {
- animation.setLayout(layout);
- defaultAnimation = layout.getAnimation();
- if (defaultAnimation) {
- defaultAnimation.disable();
- animation.on('animationend', function() {
- defaultAnimation.enable();
- animation.destroy();
- }, this);
- }
- }
- return this.setActiveItem(activeItem);
- },
- /**
- * @private
- */
- doSetActiveItem: function(newActiveItem, oldActiveItem) {
- delete this.pendingActiveItem;
- if (oldActiveItem) {
- oldActiveItem.fireEvent('deactivate', oldActiveItem, this, newActiveItem);
- }
- if (newActiveItem) {
- newActiveItem.fireEvent('activate', newActiveItem, this, oldActiveItem);
- }
- },
- doSetHidden: function(hidden) {
- var modal = this.getModal();
- if (modal) {
- modal.setHidden(hidden);
- }
- this.callSuper(arguments);
- },
- /**
- * @private
- */
- setRendered: function(rendered) {
- if (this.callParent(arguments)) {
- var items = this.items.items,
- i, ln;
- for (i = 0,ln = items.length; i < ln; i++) {
- items[i].setRendered(rendered);
- }
- return true;
- }
- return false;
- },
- /**
- * @private
- */
- getScrollableBehavior: function() {
- var behavior = this.scrollableBehavior;
- if (!behavior) {
- behavior = this.scrollableBehavior = new Ext.behavior.Scrollable(this);
- }
- return behavior;
- },
- /**
- * @private
- */
- applyScrollable: function(config) {
- if (config && !config.isObservable) {
- this.getScrollableBehavior().setConfig(config);
- }
- return config;
- },
- doSetScrollable: function() {
- // Used for plugins when they need to reinitialize scroller listeners
- },
- /**
- * Returns an the scrollable instance for this container, which is a {@link Ext.scroll.View} class.
- *
- * Please checkout the documentation for {@link Ext.scroll.View}, {@link Ext.scroll.View#getScroller}
- * and {@link Ext.scroll.Scroller} for more information.
- * @return {Ext.scroll.View} The scroll view.
- */
- getScrollable: function() {
- return this.getScrollableBehavior().getScrollView();
- },
- // Used by ComponentQuery to retrieve all of the items
- // which can potentially be considered a child of this Container.
- // This should be overridden by components which have child items
- // that are not contained in items. For example `dockedItems`, `menu`, etc
- // @private
- getRefItems: function(deep) {
- var items = this.getItems().items.slice(),
- ln = items.length,
- i, item;
- if (deep) {
- for (i = 0; i < ln; i++) {
- item = items[i];
- if (item.getRefItems) {
- items = items.concat(item.getRefItems(true));
- }
- }
- }
- return items;
- },
- /**
- * Examines this container's `{@link #property-items}` property
- * and gets a direct child component of this container.
- * @param {String/Number} component This parameter may be any of the following:
- *
- * - {String} : representing the `itemId`
- * or `{@link Ext.Component#getId id}` of the child component.
- * - {Number} : representing the position of the child component
- * within the `{@link #property-items}` property.
- *
- * For additional information see {@link Ext.util.MixedCollection#get}.
- * @return {Ext.Component} The component (if found).
- */
- getComponent: function(component) {
- if (Ext.isObject(component)) {
- component = component.getItemId();
- }
- return this.getItems().get(component);
- },
- /**
- * Finds a docked item of this container using a reference, `id `or an `index` of its location
- * in {@link #getDockedItems}.
- * @param {String/Number} component The `id` or `index` of the component to find.
- * @return {Ext.Component/Boolean} The docked component, if found.
- */
- getDockedComponent: function(component) {
- if (Ext.isObject(component)) {
- component = component.getItemId();
- }
- var dockedItems = this.getDockedItems(),
- ln = dockedItems.length,
- item, i;
- if (Ext.isNumber(component)) {
- return dockedItems[component];
- }
- for (i = 0; i < ln; i++) {
- item = dockedItems[i];
- if (item.id == component) {
- return item;
- }
- }
- return false;
- },
- /**
- * Retrieves all descendant components which match the passed selector.
- * Executes an Ext.ComponentQuery.query using this container as its root.
- * @param {String} selector Selector complying to an Ext.ComponentQuery selector.
- * @return {Array} Ext.Component's which matched the selector.
- */
- query: function(selector) {
- return Ext.ComponentQuery.query(selector, this);
- },
- /**
- * Retrieves the first direct child of this container which matches the passed selector.
- * The passed in selector must comply with an {@link Ext.ComponentQuery} selector.
- * @param {String} selector An {@link Ext.ComponentQuery} selector.
- * @return {Ext.Component}
- */
- child: function(selector) {
- return this.query('> ' + selector)[0] || null;
- },
- /**
- * Retrieves the first descendant of this container which matches the passed selector.
- * The passed in selector must comply with an {@link Ext.ComponentQuery} selector.
- * @param {String} selector An {@link Ext.ComponentQuery} selector.
- * @return {Ext.Component}
- */
- down: function(selector) {
- return this.query(selector)[0] || null;
- },
- destroy: function() {
- var me = this,
- modal = me.getModal();
- if (modal) {
- modal.destroy();
- }
- me.removeAll(true, true);
- me.unlink('_scrollable');
- Ext.destroy(me.items);
- me.callSuper();
- }
- }, function() {
- this.addMember('defaultItemClass', this);
- });
- /**
- * Represents a 2D point with x and y properties, useful for comparison and instantiation
- * from an event:
- *
- * var point = Ext.util.Point.fromEvent(e);
- */
- Ext.define('Ext.util.Point', {
- radianToDegreeConstant: 180 / Math.PI,
- statics: {
- /**
- * Returns a new instance of {@link Ext.util.Point} based on the `pageX` / `pageY` values of the given event.
- * @static
- * @param {Event} e The event.
- * @return {Ext.util.Point}
- */
- fromEvent: function(e) {
- var changedTouches = e.changedTouches,
- touch = (changedTouches && changedTouches.length > 0) ? changedTouches[0] : e;
- return this.fromTouch(touch);
- },
- /**
- * Returns a new instance of {@link Ext.util.Point} based on the `pageX` / `pageY` values of the given touch.
- * @static
- * @param {Event} touch
- * @return {Ext.util.Point}
- */
- fromTouch: function(touch) {
- return new this(touch.pageX, touch.pageY);
- },
- /**
- * Returns a new point from an object that has `x` and `y` properties, if that object is not an instance
- * of {@link Ext.util.Point}. Otherwise, returns the given point itself.
- * @param object
- * @return {Ext.util.Point}
- */
- from: function(object) {
- if (!object) {
- return new this(0, 0);
- }
- if (!(object instanceof this)) {
- return new this(object.x, object.y);
- }
- return object;
- }
- },
- /**
- * Creates point on 2D plane.
- * @param {Number} [x=0] X coordinate.
- * @param {Number} [y=0] Y coordinate.
- */
- constructor: function(x, y) {
- if (typeof x == 'undefined') {
- x = 0;
- }
- if (typeof y == 'undefined') {
- y = 0;
- }
- this.x = x;
- this.y = y;
- return this;
- },
- /**
- * Copy a new instance of this point.
- * @return {Ext.util.Point} The new point.
- */
- clone: function() {
- return new this.self(this.x, this.y);
- },
- /**
- * Clones this Point.
- * @deprecated 2.0.0 Please use {@link #clone} instead.
- * @return {Ext.util.Point} The new point.
- */
- copy: function() {
- return this.clone.apply(this, arguments);
- },
- /**
- * Copy the `x` and `y` values of another point / object to this point itself.
- * @param {Ext.util.Point/Object} point.
- * @return {Ext.util.Point} This point.
- */
- copyFrom: function(point) {
- this.x = point.x;
- this.y = point.y;
- return this;
- },
- /**
- * Returns a human-eye-friendly string that represents this point,
- * useful for debugging.
- * @return {String} For example `Point[12,8]`.
- */
- toString: function() {
- return "Point[" + this.x + "," + this.y + "]";
- },
- /**
- * Compare this point and another point.
- * @param {Ext.util.Point/Object} The point to compare with, either an instance
- * of {@link Ext.util.Point} or an object with `x` and `y` properties.
- * @return {Boolean} Returns whether they are equivalent.
- */
- equals: function(point) {
- return (this.x === point.x && this.y === point.y);
- },
- /**
- * Whether the given point is not away from this point within the given threshold amount.
- * @param {Ext.util.Point/Object} The point to check with, either an instance
- * of {@link Ext.util.Point} or an object with `x` and `y` properties.
- * @param {Object/Number} threshold Can be either an object with `x` and `y` properties or a number.
- * @return {Boolean}
- */
- isCloseTo: function(point, threshold) {
- if (typeof threshold == 'number') {
- threshold = {x: threshold};
- threshold.y = threshold.x;
- }
- var x = point.x,
- y = point.y,
- thresholdX = threshold.x,
- thresholdY = threshold.y;
- return (this.x <= x + thresholdX && this.x >= x - thresholdX &&
- this.y <= y + thresholdY && this.y >= y - thresholdY);
- },
- /**
- * Returns `true` if this point is close to another one.
- * @deprecated 2.0.0 Please use {@link #isCloseTo} instead.
- * @return {Boolean}
- */
- isWithin: function() {
- return this.isCloseTo.apply(this, arguments);
- },
- /**
- * Translate this point by the given amounts.
- * @param {Number} x Amount to translate in the x-axis.
- * @param {Number} y Amount to translate in the y-axis.
- * @return {Boolean}
- */
- translate: function(x, y) {
- this.x += x;
- this.y += y;
- return this;
- },
- /**
- * Compare this point with another point when the `x` and `y` values of both points are rounded. For example:
- * [100.3,199.8] will equals to [100, 200].
- * @param {Ext.util.Point/Object} point The point to compare with, either an instance
- * of Ext.util.Point or an object with `x` and `y` properties.
- * @return {Boolean}
- */
- roundedEquals: function(point) {
- return (Math.round(this.x) === Math.round(point.x) &&
- Math.round(this.y) === Math.round(point.y));
- },
- getDistanceTo: function(point) {
- var deltaX = this.x - point.x,
- deltaY = this.y - point.y;
- return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
- },
- getAngleTo: function(point) {
- var deltaX = this.x - point.x,
- deltaY = this.y - point.y;
- return Math.atan2(deltaY, deltaX) * this.radianToDegreeConstant;
- }
- });
- /**
- * @class Ext.util.LineSegment
- *
- * Utility class that represents a line segment, constructed by two {@link Ext.util.Point}
- */
- Ext.define('Ext.util.LineSegment', {
- requires: ['Ext.util.Point'],
- /**
- * Creates new LineSegment out of two points.
- * @param {Ext.util.Point} point1
- * @param {Ext.util.Point} point2
- */
- constructor: function(point1, point2) {
- var Point = Ext.util.Point;
- this.point1 = Point.from(point1);
- this.point2 = Point.from(point2);
- },
- /**
- * Returns the point where two lines intersect.
- * @param {Ext.util.LineSegment} lineSegment The line to intersect with.
- * @return {Ext.util.Point}
- */
- intersects: function(lineSegment) {
- var point1 = this.point1,
- point2 = this.point2,
- point3 = lineSegment.point1,
- point4 = lineSegment.point2,
- x1 = point1.x,
- x2 = point2.x,
- x3 = point3.x,
- x4 = point4.x,
- y1 = point1.y,
- y2 = point2.y,
- y3 = point3.y,
- y4 = point4.y,
- d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4),
- xi, yi;
- if (d == 0) {
- return null;
- }
- xi = ((x3 - x4) * (x1 * y2 - y1 * x2) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d;
- yi = ((y3 - y4) * (x1 * y2 - y1 * x2) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d;
- if (xi < Math.min(x1, x2) || xi > Math.max(x1, x2)
- || xi < Math.min(x3, x4) || xi > Math.max(x3, x4)
- || yi < Math.min(y1, y2) || yi > Math.max(y1, y2)
- || yi < Math.min(y3, y4) || yi > Math.max(y3, y4)) {
- return null;
- }
- return new Ext.util.Point(xi, yi);
- },
- /**
- * Returns string representation of the line. Useful for debugging.
- * @return {String} For example `Point[12,8] Point[0,0]`
- */
- toString: function() {
- return this.point1.toString() + " " + this.point2.toString();
- }
- });
- /**
- * @aside guide floating_components
- *
- * Panels are most useful as Overlays - containers that float over your application. They contain extra styling such
- * that when you {@link #showBy} another component, the container will appear in a rounded black box with a 'tip'
- * pointing to a reference component.
- *
- * If you don't need this extra functionality, you should use {@link Ext.Container} instead. See the
- * [Overlays example](#!/example/overlays) for more use cases.
- *
- * @example miniphone preview
- *
- * var button = Ext.create('Ext.Button', {
- * text: 'Button',
- * id: 'rightButton'
- * });
- *
- * Ext.create('Ext.Container', {
- * fullscreen: true,
- * items: [
- * {
- * docked: 'top',
- * xtype: 'titlebar',
- * items: [
- * button
- * ]
- * }
- * ]
- * });
- *
- * Ext.create('Ext.Panel', {
- * html: 'Floating Panel',
- * left: 0,
- * padding: 10
- * }).showBy(button);
- *
- */
- Ext.define('Ext.Panel', {
- extend: 'Ext.Container',
- requires: ['Ext.util.LineSegment'],
- alternateClassName: 'Ext.lib.Panel',
- xtype: 'panel',
- isPanel: true,
- config: {
- baseCls: Ext.baseCSSPrefix + 'panel',
- /**
- * @cfg {Number/Boolean/String} bodyPadding
- * A shortcut for setting a padding style on the body element. The value can either be
- * a number to be applied to all sides, or a normal CSS string describing padding.
- * @deprecated 2.0.0
- */
- bodyPadding: null,
- /**
- * @cfg {Number/Boolean/String} bodyMargin
- * A shortcut for setting a margin style on the body element. The value can either be
- * a number to be applied to all sides, or a normal CSS string describing margins.
- * @deprecated 2.0.0
- */
- bodyMargin: null,
- /**
- * @cfg {Number/Boolean/String} bodyBorder
- * A shortcut for setting a border style on the body element. The value can either be
- * a number to be applied to all sides, or a normal CSS string describing borders.
- * @deprecated 2.0.0
- */
- bodyBorder: null
- },
- getElementConfig: function() {
- var config = this.callParent();
- config.children.push({
- reference: 'tipElement',
- className: 'x-anchor',
- hidden: true
- });
- return config;
- },
- applyBodyPadding: function(bodyPadding) {
- if (bodyPadding === true) {
- bodyPadding = 5;
- }
- if (bodyPadding) {
- bodyPadding = Ext.dom.Element.unitizeBox(bodyPadding);
- }
- return bodyPadding;
- },
- updateBodyPadding: function(newBodyPadding) {
- this.element.setStyle('padding', newBodyPadding);
- },
- applyBodyMargin: function(bodyMargin) {
- if (bodyMargin === true) {
- bodyMargin = 5;
- }
- if (bodyMargin) {
- bodyMargin = Ext.dom.Element.unitizeBox(bodyMargin);
- }
- return bodyMargin;
- },
- updateBodyMargin: function(newBodyMargin) {
- this.element.setStyle('margin', newBodyMargin);
- },
- applyBodyBorder: function(bodyBorder) {
- if (bodyBorder === true) {
- bodyBorder = 1;
- }
- if (bodyBorder) {
- bodyBorder = Ext.dom.Element.unitizeBox(bodyBorder);
- }
- return bodyBorder;
- },
- updateBodyBorder: function(newBodyBorder) {
- this.element.setStyle('border-width', newBodyBorder);
- },
- alignTo: function(component) {
- var tipElement = this.tipElement;
- tipElement.hide();
- if (this.currentTipPosition) {
- tipElement.removeCls('x-anchor-' + this.currentTipPosition);
- }
- this.callParent(arguments);
- var LineSegment = Ext.util.LineSegment,
- alignToElement = component.isComponent ? component.renderElement : component,
- element = this.renderElement,
- alignToBox = alignToElement.getPageBox(),
- box = element.getPageBox(),
- left = box.left,
- top = box.top,
- right = box.right,
- bottom = box.bottom,
- centerX = left + (box.width / 2),
- centerY = top + (box.height / 2),
- leftTopPoint = { x: left, y: top },
- rightTopPoint = { x: right, y: top },
- leftBottomPoint = { x: left, y: bottom },
- rightBottomPoint = { x: right, y: bottom },
- boxCenterPoint = { x: centerX, y: centerY },
- alignToCenterX = alignToBox.left + (alignToBox.width / 2),
- alignToCenterY = alignToBox.top + (alignToBox.height / 2),
- alignToBoxCenterPoint = { x: alignToCenterX, y: alignToCenterY },
- centerLineSegment = new LineSegment(boxCenterPoint, alignToBoxCenterPoint),
- offsetLeft = 0,
- offsetTop = 0,
- tipSize, tipWidth, tipHeight, tipPosition, tipX, tipY;
- tipElement.setVisibility(false);
- tipElement.show();
- tipSize = tipElement.getSize();
- tipWidth = tipSize.width;
- tipHeight = tipSize.height;
- if (centerLineSegment.intersects(new LineSegment(leftTopPoint, rightTopPoint))) {
- tipX = Math.min(Math.max(alignToCenterX, left + tipWidth), right - (tipWidth));
- tipY = top;
- offsetTop = tipHeight + 10;
- tipPosition = 'top';
- }
- else if (centerLineSegment.intersects(new LineSegment(leftTopPoint, leftBottomPoint))) {
- tipX = left;
- tipY = Math.min(Math.max(alignToCenterY + (tipWidth / 2), tipWidth * 1.6), bottom - (tipWidth / 2.2));
- offsetLeft = tipHeight + 10;
- tipPosition = 'left';
- }
- else if (centerLineSegment.intersects(new LineSegment(leftBottomPoint, rightBottomPoint))) {
- tipX = Math.min(Math.max(alignToCenterX, left + tipWidth), right - tipWidth);
- tipY = bottom;
- offsetTop = -tipHeight - 10;
- tipPosition = 'bottom';
- }
- else if (centerLineSegment.intersects(new LineSegment(rightTopPoint, rightBottomPoint))) {
- tipX = right;
- tipY = Math.max(Math.min(alignToCenterY - tipHeight, bottom - tipWidth * 1.3), tipWidth / 2);
- offsetLeft = -tipHeight - 10;
- tipPosition = 'right';
- }
- if (tipX || tipY) {
- this.currentTipPosition = tipPosition;
- tipElement.addCls('x-anchor-' + tipPosition);
- tipElement.setLeft(tipX - left);
- tipElement.setTop(tipY - top);
- tipElement.setVisibility(true);
- this.setLeft(this.getLeft() + offsetLeft);
- this.setTop(this.getTop() + offsetTop);
- }
- }
- });
- /**
- * A general sheet class. This renderable container provides base support for orientation-aware transitions for popup or
- * side-anchored sliding Panels.
- *
- * In most cases, you should use {@link Ext.ActionSheet}, {@link Ext.MessageBox}, {@link Ext.picker.Picker}, or {@link Ext.picker.Date}.
- */
- Ext.define('Ext.Sheet', {
- extend: 'Ext.Panel',
- xtype: 'sheet',
- requires: ['Ext.fx.Animation'],
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'sheet',
- /**
- * @cfg
- * @inheritdoc
- */
- modal: true,
- /**
- * @cfg {Boolean} centered
- * Whether or not this component is absolutely centered inside its container.
- * @accessor
- * @evented
- */
- centered: true,
- /**
- * @cfg {Boolean} stretchX `true` to stretch this sheet horizontally.
- */
- stretchX: null,
- /**
- * @cfg {Boolean} stretchY `true` to stretch this sheet vertically.
- */
- stretchY: null,
- /**
- * @cfg {String} enter
- * The viewport side used as the enter point when shown. Valid values are 'top', 'bottom', 'left', and 'right'.
- * Applies to sliding animation effects only.
- */
- enter: 'bottom',
- /**
- * @cfg {String} exit
- * The viewport side used as the exit point when hidden. Valid values are 'top', 'bottom', 'left', and 'right'.
- * Applies to sliding animation effects only.
- */
- exit: 'bottom',
- /**
- * @cfg
- * @inheritdoc
- */
- showAnimation: !Ext.os.is.Android2 ? {
- type: 'slideIn',
- duration: 250,
- easing: 'ease-out'
- } : null,
- /**
- * @cfg
- * @inheritdoc
- */
- hideAnimation: !Ext.os.is.Android2 ? {
- type: 'slideOut',
- duration: 250,
- easing: 'ease-in'
- } : null
- },
- applyHideAnimation: function(config) {
- var exit = this.getExit(),
- direction = exit;
- if (exit === null) {
- return null;
- }
- if (config === true) {
- config = {
- type: 'slideOut'
- };
- }
- if (Ext.isString(config)) {
- config = {
- type: config
- };
- }
- var anim = Ext.factory(config, Ext.fx.Animation);
- if (anim) {
- if (exit == 'bottom') {
- direction = 'down';
- }
- if (exit == 'top') {
- direction = 'up';
- }
- anim.setDirection(direction);
- }
- return anim;
- },
- applyShowAnimation: function(config) {
- var enter = this.getEnter(),
- direction = enter;
- if (enter === null) {
- return null;
- }
- if (config === true) {
- config = {
- type: 'slideIn'
- };
- }
- if (Ext.isString(config)) {
- config = {
- type: config
- };
- }
- var anim = Ext.factory(config, Ext.fx.Animation);
- if (anim) {
- if (enter == 'bottom') {
- direction = 'down';
- }
- if (enter == 'top') {
- direction = 'up';
- }
- anim.setBefore({
- display: null
- });
- anim.setReverse(true);
- anim.setDirection(direction);
- }
- return anim;
- },
- updateStretchX: function(newStretchX) {
- this.getLeft();
- this.getRight();
- if (newStretchX) {
- this.setLeft(0);
- this.setRight(0);
- }
- },
- updateStretchY: function(newStretchY) {
- this.getTop();
- this.getBottom();
- if (newStretchY) {
- this.setTop(0);
- this.setBottom(0);
- }
- }
- });
- /**
- * A simple class to display a button in Sencha Touch.
- *
- * There are various different styles of Button you can create by using the {@link #icon},
- * {@link #iconCls}, {@link #iconAlign}, {@link #iconMask}, {@link #ui}, and {@link #text}
- * configurations.
- *
- * ## Simple Button
- *
- * Here is a Button in it's simplest form:
- *
- * @example miniphone
- * var button = Ext.create('Ext.Button', {
- * text: 'Button'
- * });
- * Ext.Viewport.add({ xtype: 'container', padding: 10, items: [button] });
- *
- * ## Icons
- *
- * You can also create a Button with just an icon using the {@link #iconCls} configuration:
- *
- * @example miniphone
- * var button = Ext.create('Ext.Button', {
- * iconCls: 'refresh',
- * iconMask: true
- * });
- * Ext.Viewport.add({ xtype: 'container', padding: 10, items: [button] });
- *
- * Note that the {@link #iconMask} configuration is required when you want to use any of the
- * bundled Pictos icons.
- *
- * Here are the included icons available (if {@link Global_CSS#$include-default-icons $include-default-icons}
- * is set to `true`):
- *
- * -  action
- * -  add
- * -  arrow_down
- * -  arrow_left
- * -  arrow_right
- * -  arrow_up
- * -  bookmarks
- * -  compose
- * -  delete
- * -  download
- * -  favorites
- * -  home
- * -  info
- * -  locate
- * -  maps
- * -  more
- * -  organize
- * -  refresh
- * -  reply
- * -  search
- * -  settings
- * -  star
- * -  team
- * -  time
- * -  trash
- * -  user
- *
- * You can also use other pictos icons by using the {@link Global_CSS#pictos-iconmask pictos-iconmask} mixin in your Sass.
- *
- * ## Badges
- *
- * Buttons can also have a badge on them, by using the {@link #badgeText} configuration:
- *
- * @example
- * Ext.create('Ext.Container', {
- * fullscreen: true,
- * padding: 10,
- * items: {
- * xtype: 'button',
- * text: 'My Button',
- * badgeText: '2'
- * }
- * });
- *
- * ## UI
- *
- * Buttons also come with a range of different default UIs. Here are the included UIs
- * available (if {@link #$include-button-uis $include-button-uis} is set to `true`):
- *
- * - **normal** - a basic gray button
- * - **back** - a back button
- * - **forward** - a forward button
- * - **round** - a round button
- * - **action** - shaded using the {@link Global_CSS#$active-color $active-color} (dark blue by default)
- * - **decline** - shaded using the {@link Global_CSS#$alert-color $alert-color} (red by default)
- * - **confirm** - shaded using the {@link Global_CSS#$confirm-color $confirm-color} (green by default)
- *
- * And setting them is very simple:
- *
- * var uiButton = Ext.create('Ext.Button', {
- * text: 'My Button',
- * ui: 'action'
- * });
- *
- * And how they look:
- *
- * @example miniphone preview
- * Ext.create('Ext.Container', {
- * fullscreen: true,
- * padding: 4,
- * defaults: {
- * xtype: 'button',
- * margin: 5
- * },
- * layout: {
- * type: 'vbox',
- * align: 'center'
- * },
- * items: [
- * { ui: 'normal', text: 'normal' },
- * { ui: 'round', text: 'round' },
- * { ui: 'action', text: 'action' },
- * { ui: 'decline', text: 'decline' },
- * { ui: 'confirm', text: 'confirm' }
- * ]
- * });
- *
- * Note that the default {@link #ui} is **normal**.
- *
- * You can also use the {@link #sencha-button-ui sencha-button-ui} CSS Mixin to create your own UIs.
- *
- * ## Example
- *
- * This example shows a bunch of icons on the screen in two toolbars. When you click on the center
- * button, it switches the {@link #iconCls} on every button on the page.
- *
- * @example preview
- * Ext.createWidget('container', {
- * fullscreen: true,
- * layout: {
- * type: 'vbox',
- * pack:'center',
- * align: 'center'
- * },
- * items: [
- * {
- * xtype: 'button',
- * text: 'Change iconCls',
- * handler: function() {
- * // classes for all the icons to loop through.
- * var availableIconCls = [
- * 'action', 'add', 'arrow_down', 'arrow_left',
- * 'arrow_right', 'arrow_up', 'compose', 'delete',
- * 'organize', 'refresh', 'reply', 'search',
- * 'settings', 'star', 'trash', 'maps', 'locate',
- * 'home'
- * ];
- * // get the text of this button,
- * // so we know which button we don't want to change
- * var text = this.getText();
- *
- * // use ComponentQuery to find all buttons on the page
- * // and loop through all of them
- * Ext.Array.forEach(Ext.ComponentQuery.query('button'), function(button) {
- * // if the button is the change iconCls button, continue
- * if (button.getText() === text) {
- * return;
- * }
- *
- * // get the index of the new available iconCls
- * var index = availableIconCls.indexOf(button.getIconCls()) + 1;
- *
- * // update the iconCls of the button with the next iconCls, if one exists.
- * // if not, use the first one
- * button.setIconCls(availableIconCls[(index === availableIconCls.length) ? 0 : index]);
- * });
- * }
- * },
- * {
- * xtype: 'toolbar',
- * docked: 'top',
- * defaults: {
- * iconMask: true
- * },
- * items: [
- * { xtype: 'spacer' },
- * { iconCls: 'action' },
- * { iconCls: 'add' },
- * { iconCls: 'arrow_down' },
- * { iconCls: 'arrow_left' },
- * { iconCls: 'arrow_up' },
- * { iconCls: 'compose' },
- * { iconCls: 'delete' },
- * { iconCls: 'organize' },
- * { iconCls: 'refresh' },
- * { xtype: 'spacer' }
- * ]
- * },
- * {
- * xtype: 'toolbar',
- * docked: 'bottom',
- * ui: 'light',
- * defaults: {
- * iconMask: true
- * },
- * items: [
- * { xtype: 'spacer' },
- * { iconCls: 'reply' },
- * { iconCls: 'search' },
- * { iconCls: 'settings' },
- * { iconCls: 'star' },
- * { iconCls: 'trash' },
- * { iconCls: 'maps' },
- * { iconCls: 'locate' },
- * { iconCls: 'home' },
- * { xtype: 'spacer' }
- * ]
- * }
- * ]
- * });
- *
- */
- Ext.define('Ext.Button', {
- extend: 'Ext.Component',
- xtype: 'button',
- /**
- * @event tap
- * @preventable doTap
- * Fires whenever a button is tapped.
- * @param {Ext.Button} this The item added to the Container.
- * @param {Ext.EventObject} e The event object.
- */
- /**
- * @event release
- * @preventable doRelease
- * Fires whenever the button is released.
- * @param {Ext.Button} this The item added to the Container.
- * @param {Ext.EventObject} e The event object.
- */
- cachedConfig: {
- /**
- * @cfg {String} pressedCls
- * The CSS class to add to the Button when it is pressed.
- * @accessor
- */
- pressedCls: Ext.baseCSSPrefix + 'button-pressing',
- /**
- * @cfg {String} badgeCls
- * The CSS class to add to the Button's badge, if it has one.
- * @accessor
- */
- badgeCls: Ext.baseCSSPrefix + 'badge',
- /**
- * @cfg {String} hasBadgeCls
- * The CSS class to add to the Button if it has a badge (note that this goes on the
- * Button element itself, not on the badge element).
- * @private
- * @accessor
- */
- hasBadgeCls: Ext.baseCSSPrefix + 'hasbadge',
- /**
- * @cfg {String} labelCls
- * The CSS class to add to the field's label element.
- * @accessor
- */
- labelCls: Ext.baseCSSPrefix + 'button-label',
- /**
- * @cfg {String} iconMaskCls
- * @private
- * The CSS class to add to the icon element as allowed by {@link #iconMask}.
- * @accessor
- */
- iconMaskCls: Ext.baseCSSPrefix + 'icon-mask',
- /**
- * @cfg {String} iconCls
- * Optional CSS class to add to the icon element. This is useful if you want to use a CSS
- * background image to create your Button icon.
- * @accessor
- */
- iconCls: null
- },
- config: {
- /**
- * @cfg {String} badgeText
- * Optional badge text.
- * @accessor
- */
- badgeText: null,
- /**
- * @cfg {String} text
- * The Button text.
- * @accessor
- */
- text: null,
- /**
- * @cfg {String} icon
- * Url to the icon image to use if you want an icon to appear on your button.
- * @accessor
- */
- icon: null,
- /**
- * @cfg {String} iconAlign
- * The position within the Button to render the icon Options are: `top`, `right`, `bottom`, `left` and `center` (when you have
- * no {@link #text} set).
- * @accessor
- */
- iconAlign: 'left',
- /**
- * @cfg {Number/Boolean} pressedDelay
- * The amount of delay between the `tapstart` and the moment we add the `pressedCls` (in milliseconds).
- * Settings this to `true` defaults to 100ms.
- */
- pressedDelay: 0,
- /**
- * @cfg {Boolean} iconMask
- * Whether or not to mask the icon with the `iconMask` configuration.
- * This is needed if you want to use any of the bundled pictos icons in the Sencha Touch Sass.
- * @accessor
- */
- iconMask: null,
- /**
- * @cfg {Function} handler
- * The handler function to run when the Button is tapped on.
- * @accessor
- */
- handler: null,
- /**
- * @cfg {Object} scope
- * The scope to fire the configured {@link #handler} in.
- * @accessor
- */
- scope: null,
- /**
- * @cfg {String} autoEvent
- * Optional event name that will be fired instead of `tap` when the Button is tapped on.
- * @accessor
- */
- autoEvent: null,
- /**
- * @cfg {String} ui
- * The ui style to render this button with. The valid default options are:
- *
- * - `'normal'` - a basic gray button (default).
- * - `'back'` - a back button.
- * - `'forward'` - a forward button.
- * - `'round'` - a round button.
- * - `'action'` - shaded using the {@link Global_CSS#$active-color $active-color} (dark blue by default).
- * - `'decline'` - shaded using the {@link Global_CSS#$alert-color $alert-color} (red by default).
- * - `'confirm'` - shaded using the {@link Global_CSS#$confirm-color $confirm-color} (green by default).
- * - `'plain'`
- *
- * @accessor
- */
- ui: 'normal',
- /**
- * @cfg {String} html The HTML to put in this button.
- *
- * If you want to just add text, please use the {@link #text} configuration.
- */
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'button'
- },
- template: [
- {
- tag: 'span',
- reference: 'badgeElement',
- hidden: true
- },
- {
- tag: 'span',
- className: Ext.baseCSSPrefix + 'button-icon',
- reference: 'iconElement',
- hidden: true
- },
- {
- tag: 'span',
- reference: 'textElement',
- hidden: true
- }
- ],
- initialize: function() {
- this.callParent();
- this.element.on({
- scope : this,
- tap : 'onTap',
- touchstart : 'onPress',
- touchend : 'onRelease'
- });
- },
- /**
- * @private
- */
- updateBadgeText: function(badgeText) {
- var element = this.element,
- badgeElement = this.badgeElement;
- if (badgeText) {
- badgeElement.show();
- badgeElement.setText(badgeText);
- }
- else {
- badgeElement.hide();
- }
- element[(badgeText) ? 'addCls' : 'removeCls'](this.getHasBadgeCls());
- },
- /**
- * @private
- */
- updateText: function(text) {
- var textElement = this.textElement;
- if (textElement) {
- if (text) {
- textElement.show();
- textElement.setHtml(text);
- }
- else {
- textElement.hide();
- }
- }
- },
- /**
- * @private
- */
- updateHtml: function(html) {
- var textElement = this.textElement;
- if (html) {
- textElement.show();
- textElement.setHtml(html);
- }
- else {
- textElement.hide();
- }
- },
- /**
- * @private
- */
- updateBadgeCls: function(badgeCls, oldBadgeCls) {
- this.badgeElement.replaceCls(oldBadgeCls, badgeCls);
- },
- /**
- * @private
- */
- updateHasBadgeCls: function(hasBadgeCls, oldHasBadgeCls) {
- var element = this.element;
- if (element.hasCls(oldHasBadgeCls)) {
- element.replaceCls(oldHasBadgeCls, hasBadgeCls);
- }
- },
- /**
- * @private
- */
- updateLabelCls: function(labelCls, oldLabelCls) {
- this.textElement.replaceCls(oldLabelCls, labelCls);
- },
- /**
- * @private
- */
- updatePressedCls: function(pressedCls, oldPressedCls) {
- var element = this.element;
- if (element.hasCls(oldPressedCls)) {
- element.replaceCls(oldPressedCls, pressedCls);
- }
- },
- /**
- * @private
- */
- updateIcon: function(icon) {
- var me = this,
- element = me.iconElement;
- if (icon) {
- me.showIconElement();
- element.setStyle('background-image', icon ? 'url(' + icon + ')' : '');
- me.refreshIconAlign();
- me.refreshIconMask();
- }
- else {
- me.hideIconElement();
- me.setIconAlign(false);
- }
- },
- /**
- * @private
- */
- updateIconCls: function(iconCls, oldIconCls) {
- var me = this,
- element = me.iconElement;
- if (iconCls) {
- me.showIconElement();
- element.replaceCls(oldIconCls, iconCls);
- me.refreshIconAlign();
- me.refreshIconMask();
- }
- else {
- me.hideIconElement();
- me.setIconAlign(false);
- }
- },
- /**
- * @private
- */
- updateIconAlign: function(alignment, oldAlignment) {
- var element = this.element,
- baseCls = Ext.baseCSSPrefix + 'iconalign-';
- if (!this.getText()) {
- alignment = "center";
- }
- element.removeCls(baseCls + "center");
- element.removeCls(baseCls + oldAlignment);
- if (this.getIcon() || this.getIconCls()) {
- element.addCls(baseCls + alignment);
- }
- },
- refreshIconAlign: function() {
- this.updateIconAlign(this.getIconAlign());
- },
- /**
- * @private
- */
- updateIconMaskCls: function(iconMaskCls, oldIconMaskCls) {
- var element = this.iconElement;
- if (this.getIconMask()) {
- element.replaceCls(oldIconMaskCls, iconMaskCls);
- }
- },
- /**
- * @private
- */
- updateIconMask: function(iconMask) {
- this.iconElement[iconMask ? "addCls" : "removeCls"](this.getIconMaskCls());
- },
- refreshIconMask: function() {
- this.updateIconMask(this.getIconMask());
- },
- applyAutoEvent: function(autoEvent) {
- var me = this;
- if (typeof autoEvent == 'string') {
- autoEvent = {
- name : autoEvent,
- scope: me.scope || me
- };
- }
- return autoEvent;
- },
- /**
- * @private
- */
- updateAutoEvent: function(autoEvent) {
- var name = autoEvent.name,
- scope = autoEvent.scope;
- this.setHandler(function() {
- scope.fireEvent(name, scope, this);
- });
- this.setScope(scope);
- },
- /**
- * Used by `icon` and `iconCls` configurations to hide the icon element.
- * We do this because Tab needs to change the visibility of the icon, not make
- * it `display:none;`.
- * @private
- */
- hideIconElement: function() {
- this.iconElement.hide();
- },
- /**
- * Used by `icon` and `iconCls` configurations to show the icon element.
- * We do this because Tab needs to change the visibility of the icon, not make
- * it `display:node;`.
- * @private
- */
- showIconElement: function() {
- this.iconElement.show();
- },
- /**
- * 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.
- * The ui class, and the back class:
- *
- * `ui: 'action-back'` would turn into:
- *
- * `class="x-button-action x-button-back"`
- *
- * But `ui: 'action'` would turn into:
- *
- * `class="x-button-action"`
- *
- * So we just split it up into an array and add both of them as a UI, when it has `back`.
- * @private
- */
- applyUi: function(config) {
- if (config && Ext.isString(config)) {
- var array = config.split('-');
- if (array && (array[1] == "back" || array[1] == "forward")) {
- return array;
- }
- }
- return config;
- },
- getUi: function() {
- //Now that the UI can sometimes be an array, we need to check if it an array and return the proper value.
- var ui = this._ui;
- if (Ext.isArray(ui)) {
- return ui.join('-');
- }
- return ui;
- },
- applyPressedDelay: function(delay) {
- if (Ext.isNumber(delay)) {
- return delay;
- }
- return (delay) ? 100 : 0;
- },
- // @private
- onPress: function() {
- var me = this,
- element = me.element,
- pressedDelay = me.getPressedDelay(),
- pressedCls = me.getPressedCls();
- if (!me.getDisabled()) {
- if (pressedDelay > 0) {
- me.pressedTimeout = setTimeout(function() {
- delete me.pressedTimeout;
- if (element) {
- element.addCls(pressedCls);
- }
- }, pressedDelay);
- }
- else {
- element.addCls(pressedCls);
- }
- }
- },
- // @private
- onRelease: function(e) {
- this.fireAction('release', [this, e], 'doRelease');
- },
- // @private
- doRelease: function(me, e) {
- if (!me.getDisabled()) {
- if (me.hasOwnProperty('pressedTimeout')) {
- clearTimeout(me.pressedTimeout);
- delete me.pressedTimeout;
- }
- else {
- me.element.removeCls(me.getPressedCls());
- }
- }
- },
- // @private
- onTap: function(e) {
- if (this.getDisabled()) {
- return false;
- }
- this.fireAction('tap', [this, e], 'doTap');
- },
- /**
- * @private
- */
- doTap: function(me, e) {
- var handler = me.getHandler(),
- scope = me.getScope() || me;
- if (!handler) {
- return;
- }
- if (typeof handler == 'string') {
- handler = scope[handler];
- }
- //this is done so if you hide the button in the handler, the tap event will not fire on the new element
- //where the button was.
- if (e && e.preventDefault) {
- e.preventDefault();
- }
- handler.apply(scope, arguments);
- }
- }, function() {
- });
- /**
- * {@link Ext.ActionSheet ActionSheets} are used to display a list of {@link Ext.Button buttons} in a popup dialog.
- *
- * The key difference between ActionSheet and {@link Ext.Sheet} is that ActionSheets are docked at the bottom of the
- * screen, and the {@link #defaultType} is set to {@link Ext.Button button}.
- *
- * ## Example
- *
- * @example preview miniphone
- * var actionSheet = Ext.create('Ext.ActionSheet', {
- * items: [
- * {
- * text: 'Delete draft',
- * ui : 'decline'
- * },
- * {
- * text: 'Save draft'
- * },
- * {
- * text: 'Cancel',
- * ui : 'confirm'
- * }
- * ]
- * });
- *
- * Ext.Viewport.add(actionSheet);
- * actionSheet.show();
- *
- * 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},
- * because the {@link #defaultType} is set to {@link Ext.Button button}.
- *
- */
- Ext.define('Ext.ActionSheet', {
- extend: 'Ext.Sheet',
- alias : 'widget.actionsheet',
- requires: ['Ext.Button'],
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'sheet-action',
- /**
- * @cfg
- * @inheritdoc
- */
- left: 0,
- /**
- * @cfg
- * @inheritdoc
- */
- right: 0,
- /**
- * @cfg
- * @inheritdoc
- */
- bottom: 0,
- // @hide
- centered: false,
- /**
- * @cfg
- * @inheritdoc
- */
- height: 'auto',
- /**
- * @cfg
- * @inheritdoc
- */
- defaultType: 'button'
- }
- });
- /**
- * The Connection class encapsulates a connection to the page's originating domain, allowing requests to be made either
- * to a configured URL, or to a URL specified at request time.
- *
- * Requests made by this class are asynchronous, and will return immediately. No data from the server will be available
- * to the statement immediately following the {@link #request} call. To process returned data, use a success callback
- * in the request options object, or an {@link #requestcomplete event listener}.
- *
- * # File Uploads
- *
- * File uploads are not performed using normal "Ajax" techniques, that is they are not performed using XMLHttpRequests.
- * Instead the form is submitted in the standard manner with the DOM `<form>` element temporarily modified to have its
- * target set to refer to a dynamically generated, hidden `<iframe>` which is inserted into the document but removed
- * after the return data has been gathered.
- *
- * The server response is parsed by the browser to create the document for the IFRAME. If the server is using JSON to
- * send the return object, then the Content-Type header must be set to "text/html" in order to tell the browser to
- * insert the text unchanged into the document body.
- *
- * Characters which are significant to an HTML parser must be sent as HTML entities, so encode `<` as `<`, `&` as
- * `&` etc.
- *
- * The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing a
- * responseText property in order to conform to the requirements of event handlers and callbacks.
- *
- * Be aware that file upload packets are sent with the content type multipart/form and some server technologies
- * (notably JEE) may require some custom processing in order to retrieve parameter names and parameter values from the
- * packet content.
- *
- * __Note:__ It is not possible to check the response code of the hidden iframe, so the success handler will _always_ fire.
- */
- Ext.define('Ext.data.Connection', {
- mixins: {
- observable: 'Ext.mixin.Observable'
- },
- statics: {
- requestId: 0
- },
- config: {
- /**
- * @cfg {String} url
- * The default URL to be used for requests to the server.
- * @accessor
- */
- url: null,
- async: true,
- /**
- * @cfg {String} [method=undefined]
- * The default HTTP method to be used for requests.
- *
- * __Note:__ This is case-sensitive and should be all caps.
- *
- * Defaults to `undefined`; if not set but params are present will use "POST", otherwise "GET".
- */
- method: null,
- username: '',
- password: '',
- /**
- * @cfg {Boolean} disableCaching
- * `true` to add a unique cache-buster param to GET requests.
- * @accessor
- */
- disableCaching: true,
- /**
- * @cfg {String} disableCachingParam
- * Change the parameter which is sent went disabling caching through a cache buster.
- * @accessor
- */
- disableCachingParam: '_dc',
- /**
- * @cfg {Number} timeout
- * The timeout in milliseconds to be used for requests.
- * @accessor
- */
- timeout : 30000,
- /**
- * @cfg {Object} extraParams
- * Any parameters to be appended to the request.
- * @accessor
- */
- extraParams: null,
- /**
- * @cfg {Object} defaultHeaders
- * An object containing request headers which are added to each request made by this object.
- * @accessor
- */
- defaultHeaders: null,
- useDefaultHeader : true,
- defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8',
- /**
- * @cfg {Boolean} useDefaultXhrHeader
- * Set this to false to not send the default Xhr header (X-Requested-With) with every request.
- * This should be set to false when making CORS (cross-domain) requests.
- * @accessor
- */
- useDefaultXhrHeader : true,
- /**
- * @cfg {String} defaultXhrHeader
- * The value of the default Xhr header (X-Requested-With). This is only used when {@link #useDefaultXhrHeader}
- * is set to `true`.
- */
- defaultXhrHeader : 'XMLHttpRequest',
- autoAbort: false
- },
- textAreaRe: /textarea/i,
- multiPartRe: /multipart\/form-data/i,
- lineBreakRe: /\r\n/g,
- constructor : function(config) {
- this.initConfig(config);
- /**
- * @event beforerequest
- * Fires before a network request is made to retrieve a data object.
- * @param {Ext.data.Connection} conn This Connection object.
- * @param {Object} options The options config object passed to the {@link #request} method.
- */
- /**
- * @event requestcomplete
- * Fires if the request was successfully completed.
- * @param {Ext.data.Connection} conn This Connection object.
- * @param {Object} response The XHR object containing the response data.
- * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
- * @param {Object} options The options config object passed to the {@link #request} method.
- */
- /**
- * @event requestexception
- * Fires if an error HTTP status was returned from the server.
- * See [HTTP Status Code Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
- * for details of HTTP status codes.
- * @param {Ext.data.Connection} conn This Connection object.
- * @param {Object} response The XHR object containing the response data.
- * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details.
- * @param {Object} options The options config object passed to the {@link #request} method.
- */
- this.requests = {};
- },
- /**
- * Sends an HTTP request to a remote server.
- *
- * **Important:** Ajax server requests are asynchronous, and this call will
- * return before the response has been received. Process any returned data
- * in a callback function.
- *
- * Ext.Ajax.request({
- * url: 'ajax_demo/sample.json',
- * success: function(response, opts) {
- * var obj = Ext.decode(response.responseText);
- * console.dir(obj);
- * },
- * failure: function(response, opts) {
- * console.log('server-side failure with status code ' + response.status);
- * }
- * });
- *
- * To execute a callback function in the correct scope, use the `scope` option.
- *
- * @param {Object} options An object which may contain the following properties:
- *
- * (The options object may also contain any other property which might be needed to perform
- * post-processing in a callback because it is passed to callback functions.)
- *
- * @param {String/Function} options.url The URL to which to send the request, or a function
- * to call which returns a URL string. The scope of the function is specified by the `scope` option.
- * Defaults to the configured `url`.
- *
- * @param {Object/String/Function} options.params An object containing properties which are
- * used as parameters to the request, a url encoded string or a function to call to get either. The scope
- * of the function is specified by the `scope` option.
- *
- * @param {String} options.method The HTTP method to use
- * for the request. Defaults to the configured method, or if no method was configured,
- * "GET" if no parameters are being sent, and "POST" if parameters are being sent.
- *
- * __Note:__ The method name is case-sensitive and should be all caps.
- *
- * @param {Function} options.callback The function to be called upon receipt of the HTTP response.
- * The callback is called regardless of success or failure and is passed the following parameters:
- * @param {Object} options.callback.options The parameter to the request call.
- * @param {Boolean} options.callback.success `true` if the request succeeded.
- * @param {Object} options.callback.response The XMLHttpRequest object containing the response data.
- * See [www.w3.org/TR/XMLHttpRequest/](http://www.w3.org/TR/XMLHttpRequest/) for details about
- * accessing elements of the response.
- *
- * @param {Function} options.success The function to be called upon success of the request.
- * The callback is passed the following parameters:
- * @param {Object} options.success.response The XMLHttpRequest object containing the response data.
- * @param {Object} options.success.options The parameter to the request call.
- *
- * @param {Function} options.failure The function to be called upon failure of the request.
- * The callback is passed the following parameters:
- * @param {Object} options.failure.response The XMLHttpRequest object containing the response data.
- * @param {Object} options.failure.options The parameter to the request call.
- *
- * @param {Object} options.scope The scope in which to execute the callbacks: The "this" object for
- * the callback function. If the `url`, or `params` options were specified as functions from which to
- * draw values, then this also serves as the scope for those function calls. Defaults to the browser
- * window.
- *
- * @param {Number} [options.timeout=30000] The timeout in milliseconds to be used for this request.
- *
- * @param {HTMLElement/HTMLElement/String} options.form The `<form>` Element or the id of the `<form>`
- * to pull parameters from.
- *
- * @param {Boolean} options.isUpload **Only meaningful when used with the `form` option.**
- *
- * True if the form object is a file upload (will be set automatically if the form was configured
- * with **`enctype`** `"multipart/form-data"`).
- *
- * File uploads are not performed using normal "Ajax" techniques, that is they are **not**
- * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
- * DOM `<form>` element temporarily modified to have its [target][] set to refer to a dynamically
- * generated, hidden `<iframe>` which is inserted into the document but removed after the return data
- * has been gathered.
- *
- * The server response is parsed by the browser to create the document for the IFRAME. If the
- * server is using JSON to send the return object, then the [Content-Type][] header must be set to
- * "text/html" in order to tell the browser to insert the text unchanged into the document body.
- *
- * The response text is retrieved from the document, and a fake XMLHttpRequest object is created
- * containing a `responseText` property in order to conform to the requirements of event handlers
- * and callbacks.
- *
- * Be aware that file upload packets are sent with the content type [multipart/form][] and some server
- * technologies (notably JEE) may require some custom processing in order to retrieve parameter names
- * and parameter values from the packet content.
- *
- * [target]: http://www.w3.org/TR/REC-html40/present/frames.html#adef-target
- * [Content-Type]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
- * [multipart/form]: http://www.faqs.org/rfcs/rfc2388.html
- *
- * @param {Object} options.headers Request headers to set for the request.
- *
- * @param {Object} options.xmlData XML document to use for the post.
- *
- * __Note:__ This will be used instead
- * of params for the post data. Any params will be appended to the URL.
- *
- * @param {Object/String} options.jsonData JSON data to use as the post.
- *
- * __Note:__ This will be used
- * instead of params for the post data. Any params will be appended to the URL.
- *
- * @param {Boolean} options.disableCaching True to add a unique cache-buster param to GET requests.
- *
- * @return {Object/null} The request object. This may be used to cancel the request.
- */
- request : function(options) {
- options = options || {};
- var me = this,
- scope = options.scope || window,
- username = options.username || me.getUsername(),
- password = options.password || me.getPassword() || '',
- async, requestOptions, request, headers, xhr;
- if (me.fireEvent('beforerequest', me, options) !== false) {
- requestOptions = me.setOptions(options, scope);
- if (this.isFormUpload(options) === true) {
- this.upload(options.form, requestOptions.url, requestOptions.data, options);
- return null;
- }
- // if autoabort is set, cancel the current transactions
- if (options.autoAbort === true || me.getAutoAbort()) {
- me.abort();
- }
- // create a connection object
- xhr = this.getXhrInstance();
- async = options.async !== false ? (options.async || me.getAsync()) : false;
- // open the request
- if (username) {
- xhr.open(requestOptions.method, requestOptions.url, async, username, password);
- } else {
- xhr.open(requestOptions.method, requestOptions.url, async);
- }
- headers = me.setupHeaders(xhr, options, requestOptions.data, requestOptions.params);
- // create the transaction object
- request = {
- id: ++Ext.data.Connection.requestId,
- xhr: xhr,
- headers: headers,
- options: options,
- async: async,
- timeout: setTimeout(function() {
- request.timedout = true;
- me.abort(request);
- }, options.timeout || me.getTimeout())
- };
- me.requests[request.id] = request;
- // bind our statechange listener
- if (async) {
- xhr.onreadystatechange = Ext.Function.bind(me.onStateChange, me, [request]);
- }
- // start the request!
- xhr.send(requestOptions.data);
- if (!async) {
- return this.onComplete(request);
- }
- return request;
- } else {
- Ext.callback(options.callback, options.scope, [options, undefined, undefined]);
- return null;
- }
- },
- /**
- * Uploads a form using a hidden iframe.
- * @param {String/HTMLElement/Ext.Element} form The form to upload.
- * @param {String} url The url to post to.
- * @param {String} params Any extra parameters to pass.
- * @param {Object} options The initial options.
- */
- upload: function(form, url, params, options) {
- form = Ext.getDom(form);
- options = options || {};
- var id = Ext.id(),
- frame = document.createElement('iframe'),
- hiddens = [],
- encoding = 'multipart/form-data',
- buf = {
- target: form.target,
- method: form.method,
- encoding: form.encoding,
- enctype: form.enctype,
- action: form.action
- }, addField = function(name, value) {
- hiddenItem = document.createElement('input');
- Ext.fly(hiddenItem).set({
- type: 'hidden',
- value: value,
- name: name
- });
- form.appendChild(hiddenItem);
- hiddens.push(hiddenItem);
- }, hiddenItem;
- /*
- * Originally this behavior was modified for Opera 10 to apply the secure URL after
- * the frame had been added to the document. It seems this has since been corrected in
- * Opera so the behavior has been reverted, the URL will be set before being added.
- */
- Ext.fly(frame).set({
- id: id,
- name: id,
- cls: Ext.baseCSSPrefix + 'hide-display',
- src: Ext.SSL_SECURE_URL
- });
- document.body.appendChild(frame);
- // This is required so that IE doesn't pop the response up in a new window.
- if (document.frames) {
- document.frames[id].name = id;
- }
- Ext.fly(form).set({
- target: id,
- method: 'POST',
- enctype: encoding,
- encoding: encoding,
- action: url || buf.action
- });
- // add dynamic params
- if (params) {
- Ext.iterate(Ext.Object.fromQueryString(params), function(name, value) {
- if (Ext.isArray(value)) {
- Ext.each(value, function(v) {
- addField(name, v);
- });
- } else {
- addField(name, value);
- }
- });
- }
- Ext.fly(frame).on('load', Ext.Function.bind(this.onUploadComplete, this, [frame, options]), null, {single: true});
- form.submit();
- Ext.fly(form).set(buf);
- Ext.each(hiddens, function(h) {
- Ext.removeNode(h);
- });
- },
- onUploadComplete: function(frame, options) {
- var me = this,
- // bogus response object
- response = {
- responseText: '',
- responseXML: null
- }, doc, firstChild;
- try {
- doc = frame.contentWindow.document || frame.contentDocument || window.frames[id].document;
- if (doc) {
- if (doc.body) {
- if (this.textAreaRe.test((firstChild = doc.body.firstChild || {}).tagName)) { // json response wrapped in textarea
- response.responseText = firstChild.value;
- } else {
- response.responseText = doc.body.innerHTML;
- }
- }
- //in IE the document may still have a body even if returns XML.
- response.responseXML = doc.XMLDocument || doc;
- }
- } catch (e) {
- }
- me.fireEvent('requestcomplete', me, response, options);
- Ext.callback(options.success, options.scope, [response, options]);
- Ext.callback(options.callback, options.scope, [options, true, response]);
- setTimeout(function() {
- Ext.removeNode(frame);
- }, 100);
- },
- /**
- * Detects whether the form is intended to be used for an upload.
- * @private
- */
- isFormUpload: function(options) {
- var form = this.getForm(options);
- if (form) {
- return (options.isUpload || (this.multiPartRe).test(form.getAttribute('enctype')));
- }
- return false;
- },
- /**
- * Gets the form object from options.
- * @private
- * @param {Object} options The request options.
- * @return {HTMLElement/null} The form, `null` if not passed.
- */
- getForm: function(options) {
- return Ext.getDom(options.form) || null;
- },
- /**
- * Sets various options such as the url, params for the request.
- * @param {Object} options The initial options.
- * @param {Object} scope The scope to execute in.
- * @return {Object} The params for the request.
- */
- setOptions: function(options, scope) {
- var me = this,
- params = options.params || {},
- extraParams = me.getExtraParams(),
- urlParams = options.urlParams,
- url = options.url || me.getUrl(),
- jsonData = options.jsonData,
- method,
- disableCache,
- data;
- // allow params to be a method that returns the params object
- if (Ext.isFunction(params)) {
- params = params.call(scope, options);
- }
- // allow url to be a method that returns the actual url
- if (Ext.isFunction(url)) {
- url = url.call(scope, options);
- }
- url = this.setupUrl(options, url);
- //<debug>
- if (!url) {
- Ext.Logger.error('No URL specified');
- }
- //</debug>
- // check for xml or json data, and make sure json data is encoded
- data = options.rawData || options.xmlData || jsonData || null;
- if (jsonData && !Ext.isPrimitive(jsonData)) {
- data = Ext.encode(data);
- }
- // make sure params are a url encoded string and include any extraParams if specified
- if (Ext.isObject(params)) {
- params = Ext.Object.toQueryString(params);
- }
- if (Ext.isObject(extraParams)) {
- extraParams = Ext.Object.toQueryString(extraParams);
- }
- params = params + ((extraParams) ? ((params) ? '&' : '') + extraParams : '');
- urlParams = Ext.isObject(urlParams) ? Ext.Object.toQueryString(urlParams) : urlParams;
- params = this.setupParams(options, params);
- // decide the proper method for this request
- method = (options.method || me.getMethod() || ((params || data) ? 'POST' : 'GET')).toUpperCase();
- this.setupMethod(options, method);
- disableCache = options.disableCaching !== false ? (options.disableCaching || me.getDisableCaching()) : false;
- // append date to prevent caching
- if (disableCache) {
- url = Ext.urlAppend(url, (options.disableCachingParam || me.getDisableCachingParam()) + '=' + (new Date().getTime()));
- }
- // if the method is get or there is json/xml data append the params to the url
- if ((method == 'GET' || data) && params) {
- url = Ext.urlAppend(url, params);
- params = null;
- }
- // allow params to be forced into the url
- if (urlParams) {
- url = Ext.urlAppend(url, urlParams);
- }
- return {
- url: url,
- method: method,
- data: data || params || null
- };
- },
- /**
- * Template method for overriding url.
- * @private
- * @param {Object} options
- * @param {String} url
- * @return {String} The modified url
- */
- setupUrl: function(options, url) {
- var form = this.getForm(options);
- if (form) {
- url = url || form.action;
- }
- return url;
- },
- /**
- * Template method for overriding params.
- * @private
- * @param {Object} options
- * @param {String} params
- * @return {String} The modified params.
- */
- setupParams: function(options, params) {
- var form = this.getForm(options),
- serializedForm;
- if (form && !this.isFormUpload(options)) {
- serializedForm = Ext.Element.serializeForm(form);
- params = params ? (params + '&' + serializedForm) : serializedForm;
- }
- return params;
- },
- /**
- * Template method for overriding method.
- * @private
- * @param {Object} options
- * @param {String} method
- * @return {String} The modified method.
- */
- setupMethod: function(options, method) {
- if (this.isFormUpload(options)) {
- return 'POST';
- }
- return method;
- },
- /**
- * Setup all the headers for the request.
- * @private
- * @param {Object} xhr The xhr object.
- * @param {Object} options The options for the request.
- * @param {Object} data The data for the request.
- * @param {Object} params The params for the request.
- */
- setupHeaders: function(xhr, options, data, params) {
- var me = this,
- headers = Ext.apply({}, options.headers || {}, me.getDefaultHeaders() || {}),
- contentType = me.getDefaultPostHeader(),
- jsonData = options.jsonData,
- xmlData = options.xmlData,
- key,
- header;
- if (!headers['Content-Type'] && (data || params)) {
- if (data) {
- if (options.rawData) {
- contentType = 'text/plain';
- } else {
- if (xmlData && Ext.isDefined(xmlData)) {
- contentType = 'text/xml';
- } else if (jsonData && Ext.isDefined(jsonData)) {
- contentType = 'application/json';
- }
- }
- }
- headers['Content-Type'] = contentType;
- }
- if (((me.getUseDefaultXhrHeader() && options.useDefaultXhrHeader !== false) || options.useDefaultXhrHeader) && !headers['X-Requested-With']) {
- headers['X-Requested-With'] = me.getDefaultXhrHeader();
- }
- // set up all the request headers on the xhr object
- try {
- for (key in headers) {
- if (headers.hasOwnProperty(key)) {
- header = headers[key];
- xhr.setRequestHeader(key, header);
- }
- }
- } catch(e) {
- me.fireEvent('exception', key, header);
- }
- if (options.withCredentials) {
- xhr.withCredentials = options.withCredentials;
- }
- return headers;
- },
- /**
- * Creates the appropriate XHR transport for the browser.
- * @private
- */
- getXhrInstance: (function() {
- var options = [function() {
- return new XMLHttpRequest();
- }, function() {
- return new ActiveXObject('MSXML2.XMLHTTP.3.0');
- }, function() {
- return new ActiveXObject('MSXML2.XMLHTTP');
- }, function() {
- return new ActiveXObject('Microsoft.XMLHTTP');
- }], i = 0,
- len = options.length,
- xhr;
- for (; i < len; ++i) {
- try {
- xhr = options[i];
- xhr();
- break;
- } catch(e) {
- }
- }
- return xhr;
- })(),
- /**
- * Determines whether this object has a request outstanding.
- * @param {Object} request The request to check.
- * @return {Boolean} True if there is an outstanding request.
- */
- isLoading : function(request) {
- if (!(request && request.xhr)) {
- return false;
- }
- // if there is a connection and readyState is not 0 or 4
- var state = request.xhr.readyState;
- return !(state === 0 || state == 4);
- },
- /**
- * Aborts any outstanding request.
- * @param {Object} request (Optional) Defaults to the last request.
- */
- abort : function(request) {
- var me = this,
- requests = me.requests,
- id;
- if (request && me.isLoading(request)) {
- /*
- * Clear out the onreadystatechange here, this allows us
- * greater control, the browser may/may not fire the function
- * depending on a series of conditions.
- */
- request.xhr.onreadystatechange = null;
- request.xhr.abort();
- me.clearTimeout(request);
- if (!request.timedout) {
- request.aborted = true;
- }
- me.onComplete(request);
- me.cleanup(request);
- } else if (!request) {
- for (id in requests) {
- if (requests.hasOwnProperty(id)) {
- me.abort(requests[id]);
- }
- }
- }
- },
- /**
- * Aborts all outstanding requests.
- */
- abortAll: function() {
- this.abort();
- },
- /**
- * Fires when the state of the XHR changes.
- * @private
- * @param {Object} request The request
- */
- onStateChange : function(request) {
- if (request.xhr.readyState == 4) {
- this.clearTimeout(request);
- this.onComplete(request);
- this.cleanup(request);
- }
- },
- /**
- * Clears the timeout on the request.
- * @private
- * @param {Object} The request
- */
- clearTimeout: function(request) {
- clearTimeout(request.timeout);
- delete request.timeout;
- },
- /**
- * Cleans up any left over information from the request.
- * @private
- * @param {Object} The request.
- */
- cleanup: function(request) {
- request.xhr = null;
- delete request.xhr;
- },
- /**
- * To be called when the request has come back from the server.
- * @private
- * @param {Object} request
- * @return {Object} The response.
- */
- onComplete : function(request) {
- var me = this,
- options = request.options,
- result,
- success,
- response;
- try {
- result = me.parseStatus(request.xhr.status, request.xhr);
- if (request.timedout) {
- result.success = false;
- }
- } catch (e) {
- // in some browsers we can't access the status if the readyState is not 4, so the request has failed
- result = {
- success : false,
- isException : false
- };
- }
- success = result.success;
- if (success) {
- response = me.createResponse(request);
- me.fireEvent('requestcomplete', me, response, options);
- Ext.callback(options.success, options.scope, [response, options]);
- } else {
- if (result.isException || request.aborted || request.timedout) {
- response = me.createException(request);
- } else {
- response = me.createResponse(request);
- }
- me.fireEvent('requestexception', me, response, options);
- Ext.callback(options.failure, options.scope, [response, options]);
- }
- Ext.callback(options.callback, options.scope, [options, success, response]);
- delete me.requests[request.id];
- return response;
- },
- /**
- * Checks if the response status was successful.
- * @param {Number} status The status code.
- * @param xhr
- * @return {Object} An object containing success/status state.
- */
- parseStatus: function(status, xhr) {
- // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223
- status = status == 1223 ? 204 : status;
- var success = (status >= 200 && status < 300) || status == 304 || (status == 0 && xhr.responseText.length > 0),
- isException = false;
- if (!success) {
- switch (status) {
- case 12002:
- case 12029:
- case 12030:
- case 12031:
- case 12152:
- case 13030:
- isException = true;
- break;
- }
- }
- return {
- success: success,
- isException: isException
- };
- },
- /**
- * Creates the response object.
- * @private
- * @param {Object} request
- */
- createResponse : function(request) {
- var xhr = request.xhr,
- headers = {},
- lines, count, line, index, key, response;
- //we need to make this check here because if a request times out an exception is thrown
- //when calling getAllResponseHeaders() because the response never came back to populate it
- if (request.timedout || request.aborted) {
- request.success = false;
- lines = [];
- } else {
- lines = xhr.getAllResponseHeaders().replace(this.lineBreakRe, '\n').split('\n');
- }
- count = lines.length;
- while (count--) {
- line = lines[count];
- index = line.indexOf(':');
- if (index >= 0) {
- key = line.substr(0, index).toLowerCase();
- if (line.charAt(index + 1) == ' ') {
- ++index;
- }
- headers[key] = line.substr(index + 1);
- }
- }
- request.xhr = null;
- delete request.xhr;
- response = {
- request: request,
- requestId : request.id,
- status : xhr.status,
- statusText : xhr.statusText,
- getResponseHeader : function(header) {
- return headers[header.toLowerCase()];
- },
- getAllResponseHeaders : function() {
- return headers;
- },
- responseText : xhr.responseText,
- responseXML : xhr.responseXML
- };
- // If we don't explicitly tear down the xhr reference, IE6/IE7 will hold this in the closure of the
- // functions created with getResponseHeader/getAllResponseHeaders
- xhr = null;
- return response;
- },
- /**
- * Creates the exception object.
- * @private
- * @param {Object} request
- */
- createException : function(request) {
- return {
- request : request,
- requestId : request.id,
- status : request.aborted ? -1 : 0,
- statusText : request.aborted ? 'transaction aborted' : 'communication failure',
- aborted: request.aborted,
- timedout: request.timedout
- };
- }
- });
- /**
- * @aside guide ajax
- *
- * A singleton instance of an {@link Ext.data.Connection}. This class
- * is used to communicate with your server side code. It can be used as follows:
- *
- * Ext.Ajax.request({
- * url: 'page.php',
- * params: {
- * id: 1
- * },
- * success: function(response){
- * var text = response.responseText;
- * // process server response here
- * }
- * });
- *
- * Default options for all requests can be set by changing a property on the Ext.Ajax class:
- *
- * Ext.Ajax.setTimeout(60000); // 60 seconds
- *
- * Any options specified in the request method for the Ajax request will override any
- * defaults set on the Ext.Ajax class. In the code sample below, the timeout for the
- * request will be 60 seconds.
- *
- * Ext.Ajax.setTimeout(120000); // 120 seconds
- * Ext.Ajax.request({
- * url: 'page.aspx',
- * timeout: 60000
- * });
- *
- * In general, this class will be used for all Ajax requests in your application.
- * The main reason for creating a separate {@link Ext.data.Connection} is for a
- * series of requests that share common settings that are different to all other
- * requests in the application.
- */
- Ext.define('Ext.Ajax', {
- extend: 'Ext.data.Connection',
- singleton: true,
- /**
- * @property {Boolean} autoAbort
- * Whether a new request should abort any pending requests.
- */
- autoAbort : false
- });
- /**
- * Ext.Anim is used to execute simple animations defined in {@link Ext.anims}. The {@link #run} method can take any of the
- * properties defined below.
- *
- * Ext.Anim.run(this, 'fade', {
- * out: false,
- * autoClear: true
- * });
- *
- * When using {@link Ext.Anim#run}, ensure you require {@link Ext.Anim} in your application. Either do this using {@link Ext#require}:
- *
- * Ext.requires('Ext.Anim');
- *
- * when using {@link Ext#setup}:
- *
- * Ext.setup({
- * requires: ['Ext.Anim'],
- * onReady: function() {
- * //do something
- * }
- * });
- *
- * or when using {@link Ext#application}:
- *
- * Ext.application({
- * requires: ['Ext.Anim'],
- * launch: function() {
- * //do something
- * }
- * });
- *
- * @singleton
- */
- Ext.define('Ext.Anim', {
- isAnim: true,
- /**
- * @cfg {Boolean} disableAnimations
- * `true` to disable animations.
- */
- disableAnimations: false,
- defaultConfig: {
- /**
- * @cfg {Object} from
- * An object of CSS values which the animation begins with. If you define a CSS property here, you must also
- * define it in the {@link #to} config.
- */
- from: {},
- /**
- * @cfg {Object} to
- * An object of CSS values which the animation ends with. If you define a CSS property here, you must also
- * define it in the {@link #from} config.
- */
- to: {},
- /**
- * @cfg {Number} duration
- * Time in milliseconds for the animation to last.
- */
- duration: 250,
- /**
- * @cfg {Number} delay Time to delay before starting the animation.
- */
- delay: 0,
- /**
- * @cfg {String} easing
- * Valid values are 'ease', 'linear', ease-in', 'ease-out', 'ease-in-out', or a cubic-bezier curve as defined by CSS.
- */
- easing: 'ease-in-out',
- /**
- * @cfg {Boolean} autoClear
- * `true` to remove all custom CSS defined in the {@link #to} config when the animation is over.
- */
- autoClear: true,
- /**
- * @cfg {Boolean} out
- * `true` if you want the animation to slide out of the screen.
- */
- out: true,
- /**
- * @cfg {String} direction
- * Valid values are: 'left', 'right', 'up', 'down', and `null`.
- */
- direction: null,
- /**
- * @cfg {Boolean} reverse
- * `true` to reverse the animation direction. For example, if the animation direction was set to 'left', it would
- * then use 'right'.
- */
- reverse: false
- },
- /**
- * @cfg {Function} before
- * Code to execute before starting the animation.
- */
- /**
- * @cfg {Function} after
- * Code to execute after the animation ends.
- */
- /**
- * @cfg {Object} scope
- * Scope to run the {@link #before} function in.
- */
- opposites: {
- 'left': 'right',
- 'right': 'left',
- 'up': 'down',
- 'down': 'up'
- },
- constructor: function(config) {
- config = Ext.apply({}, config || {}, this.defaultConfig);
- this.config = config;
- this.callSuper([config]);
- this.running = [];
- },
- initConfig: function(el, runConfig) {
- var me = this,
- config = Ext.apply({}, runConfig || {}, me.config);
- config.el = el = Ext.get(el);
- if (config.reverse && me.opposites[config.direction]) {
- config.direction = me.opposites[config.direction];
- }
- if (me.config.before) {
- me.config.before.call(config, el, config);
- }
- if (runConfig.before) {
- runConfig.before.call(config.scope || config, el, config);
- }
- return config;
- },
- /**
- * @ignore
- */
- run: function(el, config) {
- el = Ext.get(el);
- config = config || {};
- var me = this,
- style = el.dom.style,
- property,
- after = config.after;
- if (me.running[el.id]) {
- me.onTransitionEnd(null, el, {
- config: config,
- after: after
- });
- }
- config = this.initConfig(el, config);
- if (this.disableAnimations) {
- for (property in config.to) {
- if (!config.to.hasOwnProperty(property)) {
- continue;
- }
- style[property] = config.to[property];
- }
- this.onTransitionEnd(null, el, {
- config: config,
- after: after
- });
- return me;
- }
- el.un('transitionend', me.onTransitionEnd, me);
- style.webkitTransitionDuration = '0ms';
- for (property in config.from) {
- if (!config.from.hasOwnProperty(property)) {
- continue;
- }
- style[property] = config.from[property];
- }
- setTimeout(function() {
- // If this element has been destroyed since the timeout started, do nothing
- if (!el.dom) {
- return;
- }
- // If this is a 3d animation we have to set the perspective on the parent
- if (config.is3d === true) {
- el.parent().setStyle({
- // See https://sencha.jira.com/browse/TOUCH-1498
- '-webkit-perspective': '1200',
- '-webkit-transform-style': 'preserve-3d'
- });
- }
- style.webkitTransitionDuration = config.duration + 'ms';
- style.webkitTransitionProperty = 'all';
- style.webkitTransitionTimingFunction = config.easing;
- // Bind our listener that fires after the animation ends
- el.on('transitionend', me.onTransitionEnd, me, {
- single: true,
- config: config,
- after: after
- });
- for (property in config.to) {
- if (!config.to.hasOwnProperty(property)) {
- continue;
- }
- style[property] = config.to[property];
- }
- }, config.delay || 5);
- me.running[el.id] = config;
- return me;
- },
- onTransitionEnd: function(ev, el, o) {
- el = Ext.get(el);
- if (this.running[el.id] === undefined) {
- return;
- }
- var style = el.dom.style,
- config = o.config,
- me = this,
- property;
- if (config.autoClear) {
- for (property in config.to) {
- if (!config.to.hasOwnProperty(property) || config[property] === false) {
- continue;
- }
- style[property] = '';
- }
- }
- style.webkitTransitionDuration = null;
- style.webkitTransitionProperty = null;
- style.webkitTransitionTimingFunction = null;
- if (config.is3d) {
- el.parent().setStyle({
- '-webkit-perspective': '',
- '-webkit-transform-style': ''
- });
- }
- if (me.config.after) {
- me.config.after.call(config, el, config);
- }
- if (o.after) {
- o.after.call(config.scope || me, el, config);
- }
- delete me.running[el.id];
- }
- }, function() {
- Ext.Anim.seed = 1000;
- /**
- * Used to run an animation on a specific element. Use the config argument to customize the animation.
- * @param {Ext.Element/HTMLElement} el The element to animate.
- * @param {String} anim The animation type, defined in {@link Ext.anims}.
- * @param {Object} config The config object for the animation.
- * @method run
- */
- Ext.Anim.run = function(el, anim, config) {
- if (el.isComponent) {
- el = el.element;
- }
- config = config || {};
- if (anim.isAnim) {
- anim.run(el, config);
- }
- else {
- if (Ext.isObject(anim)) {
- if (config.before && anim.before) {
- config.before = Ext.createInterceptor(config.before, anim.before, anim.scope);
- }
- if (config.after && anim.after) {
- config.after = Ext.createInterceptor(config.after, anim.after, anim.scope);
- }
- config = Ext.apply({}, config, anim);
- anim = anim.type;
- }
- if (!Ext.anims[anim]) {
- throw anim + ' is not a valid animation type.';
- }
- else {
- // add el check to make sure dom exists.
- if (el && el.dom) {
- Ext.anims[anim].run(el, config);
- }
- }
- }
- };
- /**
- * @class Ext.anims
- * Defines different types of animations.
- *
- * __Note:__ _flip_, _cube_, and _wipe_ animations do not work on Android.
- *
- * Please refer to {@link Ext.Anim} on how to use animations.
- * @singleton
- */
- Ext.anims = {
- /**
- * Fade Animation
- */
- fade: new Ext.Anim({
- type: 'fade',
- before: function(el) {
- var fromOpacity = 1,
- toOpacity = 1,
- curZ = el.getStyle('z-index') == 'auto' ? 0 : el.getStyle('z-index'),
- zIndex = curZ;
- if (this.out) {
- toOpacity = 0;
- } else {
- zIndex = Math.abs(curZ) + 1;
- fromOpacity = 0;
- }
- this.from = {
- 'opacity': fromOpacity,
- 'z-index': zIndex
- };
- this.to = {
- 'opacity': toOpacity,
- 'z-index': zIndex
- };
- }
- }),
- /**
- * Slide Animation
- */
- slide: new Ext.Anim({
- direction: 'left',
- cover: false,
- reveal: false,
- opacity: false,
- 'z-index': false,
- before: function(el) {
- var currentZIndex = el.getStyle('z-index') == 'auto' ? 0 : el.getStyle('z-index'),
- currentOpacity = el.getStyle('opacity'),
- zIndex = currentZIndex + 1,
- out = this.out,
- direction = this.direction,
- toX = 0,
- toY = 0,
- fromX = 0,
- fromY = 0,
- elH = el.getHeight(),
- elW = el.getWidth();
- if (direction == 'left' || direction == 'right') {
- if (out) {
- toX = -elW;
- }
- else {
- fromX = elW;
- }
- }
- else if (direction == 'up' || direction == 'down') {
- if (out) {
- toY = -elH;
- }
- else {
- fromY = elH;
- }
- }
- if (direction == 'right' || direction == 'down') {
- toY *= -1;
- toX *= -1;
- fromY *= -1;
- fromX *= -1;
- }
- if (this.cover && out) {
- toX = 0;
- toY = 0;
- zIndex = currentZIndex;
- }
- else if (this.reveal && !out) {
- fromX = 0;
- fromY = 0;
- zIndex = currentZIndex;
- }
- this.from = {
- '-webkit-transform': 'translate3d(' + fromX + 'px, ' + fromY + 'px, 0)',
- 'z-index': zIndex,
- 'opacity': currentOpacity - 0.01
- };
- this.to = {
- '-webkit-transform': 'translate3d(' + toX + 'px, ' + toY + 'px, 0)',
- 'z-index': zIndex,
- 'opacity': currentOpacity
- };
- }
- }),
- /**
- * Pop Animation
- */
- pop: new Ext.Anim({
- scaleOnExit: true,
- before: function(el) {
- var fromScale = 1,
- toScale = 1,
- fromOpacity = 1,
- toOpacity = 1,
- curZ = el.getStyle('z-index') == 'auto' ? 0 : el.getStyle('z-index'),
- fromZ = curZ,
- toZ = curZ;
- if (!this.out) {
- fromScale = 0.01;
- fromZ = curZ + 1;
- toZ = curZ + 1;
- fromOpacity = 0;
- }
- else {
- if (this.scaleOnExit) {
- toScale = 0.01;
- toOpacity = 0;
- } else {
- toOpacity = 0.8;
- }
- }
- this.from = {
- '-webkit-transform': 'scale(' + fromScale + ')',
- '-webkit-transform-origin': '50% 50%',
- 'opacity': fromOpacity,
- 'z-index': fromZ
- };
- this.to = {
- '-webkit-transform': 'scale(' + toScale + ')',
- '-webkit-transform-origin': '50% 50%',
- 'opacity': toOpacity,
- 'z-index': toZ
- };
- }
- }),
- /**
- * Flip Animation
- */
- flip: new Ext.Anim({
- is3d: true,
- direction: 'left',
- before: function(el) {
- var rotateProp = 'Y',
- fromScale = 1,
- toScale = 1,
- fromRotate = 0,
- toRotate = 0;
- if (this.out) {
- toRotate = -180;
- toScale = 0.8;
- }
- else {
- fromRotate = 180;
- fromScale = 0.8;
- }
- if (this.direction == 'up' || this.direction == 'down') {
- rotateProp = 'X';
- }
- if (this.direction == 'right' || this.direction == 'left') {
- toRotate *= -1;
- fromRotate *= -1;
- }
- this.from = {
- '-webkit-transform': 'rotate' + rotateProp + '(' + fromRotate + 'deg) scale(' + fromScale + ')',
- '-webkit-backface-visibility': 'hidden'
- };
- this.to = {
- '-webkit-transform': 'rotate' + rotateProp + '(' + toRotate + 'deg) scale(' + toScale + ')',
- '-webkit-backface-visibility': 'hidden'
- };
- }
- }),
- /**
- * Cube Animation
- */
- cube: new Ext.Anim({
- is3d: true,
- direction: 'left',
- style: 'outer',
- before: function(el) {
- var origin = '0% 0%',
- fromRotate = 0,
- toRotate = 0,
- rotateProp = 'Y',
- fromZ = 0,
- toZ = 0,
- elW = el.getWidth(),
- elH = el.getHeight(),
- showTranslateZ = true,
- fromTranslate = ' translateX(0)',
- toTranslate = '';
- if (this.direction == 'left' || this.direction == 'right') {
- if (this.out) {
- origin = '100% 100%';
- toZ = elW;
- toRotate = -90;
- } else {
- origin = '0% 0%';
- fromZ = elW;
- fromRotate = 90;
- }
- } else if (this.direction == 'up' || this.direction == 'down') {
- rotateProp = 'X';
- if (this.out) {
- origin = '100% 100%';
- toZ = elH;
- toRotate = 90;
- } else {
- origin = '0% 0%';
- fromZ = elH;
- fromRotate = -90;
- }
- }
- if (this.direction == 'down' || this.direction == 'right') {
- fromRotate *= -1;
- toRotate *= -1;
- origin = (origin == '0% 0%') ? '100% 100%': '0% 0%';
- }
- if (this.style == 'inner') {
- fromZ *= -1;
- toZ *= -1;
- fromRotate *= -1;
- toRotate *= -1;
- if (!this.out) {
- toTranslate = ' translateX(0px)';
- origin = '0% 50%';
- } else {
- toTranslate = fromTranslate;
- origin = '100% 50%';
- }
- }
- this.from = {
- '-webkit-transform': 'rotate' + rotateProp + '(' + fromRotate + 'deg)' + (showTranslateZ ? ' translateZ(' + fromZ + 'px)': '') + fromTranslate,
- '-webkit-transform-origin': origin
- };
- this.to = {
- '-webkit-transform': 'rotate' + rotateProp + '(' + toRotate + 'deg) translateZ(' + toZ + 'px)' + toTranslate,
- '-webkit-transform-origin': origin
- };
- },
- duration: 250
- }),
- /**
- * Wipe Animation.
- * Because of the amount of calculations involved, this animation is best used on small display
- * changes or specifically for phone environments. Does not currently accept any parameters.
- */
- wipe: new Ext.Anim({
- before: function(el) {
- var curZ = el.getStyle('z-index'),
- zIndex,
- mask = '';
- if (!this.out) {
- zIndex = curZ + 1;
- mask = '-webkit-gradient(linear, left bottom, right bottom, from(transparent), to(#000), color-stop(66%, #000), color-stop(33%, transparent))';
- this.from = {
- '-webkit-mask-image': mask,
- '-webkit-mask-size': el.getWidth() * 3 + 'px ' + el.getHeight() + 'px',
- 'z-index': zIndex,
- '-webkit-mask-position-x': 0
- };
- this.to = {
- '-webkit-mask-image': mask,
- '-webkit-mask-size': el.getWidth() * 3 + 'px ' + el.getHeight() + 'px',
- 'z-index': zIndex,
- '-webkit-mask-position-x': -el.getWidth() * 2 + 'px'
- };
- }
- },
- duration: 500
- })
- };
- });
- /**
- * Provides a base class for audio/visual controls. Should not be used directly.
- *
- * Please see the {@link Ext.Audio} and {@link Ext.Video} classes for more information.
- * @private
- */
- Ext.define('Ext.Media', {
- extend: 'Ext.Component',
- xtype: 'media',
- /**
- * @event play
- * Fires whenever the media is played.
- * @param {Ext.Media} this
- */
- /**
- * @event pause
- * Fires whenever the media is paused.
- * @param {Ext.Media} this
- * @param {Number} time The time at which the media was paused at in seconds.
- */
- /**
- * @event ended
- * Fires whenever the media playback has ended.
- * @param {Ext.Media} this
- * @param {Number} time The time at which the media ended at in seconds.
- */
- /**
- * @event stop
- * Fires whenever the media is stopped.
- * The `pause` event will also fire after the `stop` event if the media is currently playing.
- * The `timeupdate` event will also fire after the `stop` event regardless of playing status.
- * @param {Ext.Media} this
- */
- /**
- * @event volumechange
- * Fires whenever the volume is changed.
- * @param {Ext.Media} this
- * @param {Number} volume The volume level from 0 to 1.
- */
- /**
- * @event mutedchange
- * Fires whenever the muted status is changed.
- * The volumechange event will also fire after the `mutedchange` event fires.
- * @param {Ext.Media} this
- * @param {Boolean} muted The muted status.
- */
- /**
- * @event timeupdate
- * Fires when the media is playing every 15 to 250ms.
- * @param {Ext.Media} this
- * @param {Number} time The current time in seconds.
- */
- config: {
- /**
- * @cfg {String} url
- * Location of the media to play.
- * @accessor
- */
- url: '',
- /**
- * @cfg {Boolean} enableControls
- * Set this to `false` to turn off the native media controls.
- * Defaults to `false` when you are on Android, as it doesn't support controls.
- * @accessor
- */
- enableControls: Ext.os.is.Android ? false : true,
- /**
- * @cfg {Boolean} autoResume
- * Will automatically start playing the media when the container is activated.
- * @accessor
- */
- autoResume: false,
- /**
- * @cfg {Boolean} autoPause
- * Will automatically pause the media when the container is deactivated.
- * @accessor
- */
- autoPause: true,
- /**
- * @cfg {Boolean} preload
- * Will begin preloading the media immediately.
- * @accessor
- */
- preload: true,
- /**
- * @cfg {Boolean} loop
- * Will loop the media forever.
- * @accessor
- */
- loop: false,
- /**
- * @cfg {Ext.Element} media
- * A reference to the underlying audio/video element.
- * @accessor
- */
- media: null,
- /**
- * @cfg {Number} volume
- * The volume of the media from 0.0 to 1.0.
- * @accessor
- */
- volume: 1,
- /**
- * @cfg {Boolean} muted
- * Whether or not the media is muted. This will also set the volume to zero.
- * @accessor
- */
- muted: false
- },
- constructor: function() {
- this.mediaEvents = {};
- this.callSuper(arguments);
- },
- initialize: function() {
- var me = this;
- me.callParent();
- me.on({
- scope: me,
- activate : me.onActivate,
- deactivate: me.onDeactivate
- });
- me.addMediaListener({
- canplay: 'onCanPlay',
- play: 'onPlay',
- pause: 'onPause',
- ended: 'onEnd',
- volumechange: 'onVolumeChange',
- timeupdate: 'onTimeUpdate'
- });
- },
- addMediaListener: function(event, fn) {
- var me = this,
- dom = me.media.dom,
- bind = Ext.Function.bind;
- Ext.Object.each(event, function(e, fn) {
- fn = bind(me[fn], me);
- me.mediaEvents[e] = fn;
- dom.addEventListener(e, fn);
- });
- },
- onPlay: function() {
- this.fireEvent('play', this);
- },
- onCanPlay: function() {
- this.fireEvent('canplay', this);
- },
- onPause: function() {
- this.fireEvent('pause', this, this.getCurrentTime());
- },
- onEnd: function() {
- this.fireEvent('ended', this, this.getCurrentTime());
- },
- onVolumeChange: function() {
- this.fireEvent('volumechange', this, this.media.dom.volume);
- },
- onTimeUpdate: function() {
- this.fireEvent('timeupdate', this, this.getCurrentTime());
- },
- /**
- * Returns if the media is currently playing.
- * @return {Boolean} playing `true` if the media is playing.
- */
- isPlaying: function() {
- return !Boolean(this.media.dom.paused);
- },
- // @private
- onActivate: function() {
- var me = this;
- if (me.getAutoResume() && !me.isPlaying()) {
- me.play();
- }
- },
- // @private
- onDeactivate: function() {
- var me = this;
- if (me.getAutoPause() && me.isPlaying()) {
- me.pause();
- }
- },
- /**
- * Sets the URL of the media element. If the media element already exists, it is update the src attribute of the
- * element. If it is currently playing, it will start the new video.
- */
- updateUrl: function(newUrl) {
- var dom = this.media.dom;
- //when changing the src, we must call load:
- //http://developer.apple.com/library/safari/#documentation/AudioVideo/Conceptual/Using_HTML5_Audio_Video/ControllingMediaWithJavaScript/ControllingMediaWithJavaScript.html
- dom.src = newUrl;
- if ('load' in dom) {
- dom.load();
- }
- if (this.isPlaying()) {
- this.play();
- }
- },
- /**
- * Updates the controls of the video element.
- */
- updateEnableControls: function(enableControls) {
- this.media.dom.controls = enableControls ? 'controls' : false;
- },
- /**
- * Updates the loop setting of the media element.
- */
- updateLoop: function(loop) {
- this.media.dom.loop = loop ? 'loop' : false;
- },
- /**
- * Starts or resumes media playback.
- */
- play: function() {
- var dom = this.media.dom;
- if ('play' in dom) {
- dom.play();
- setTimeout(function() {
- dom.play();
- }, 10);
- }
- },
- /**
- * Pauses media playback.
- */
- pause: function() {
- var dom = this.media.dom;
- if ('pause' in dom) {
- dom.pause();
- }
- },
- /**
- * Toggles the media playback state.
- */
- toggle: function() {
- if (this.isPlaying()) {
- this.pause();
- } else {
- this.play();
- }
- },
- /**
- * Stops media playback and returns to the beginning.
- */
- stop: function() {
- var me = this;
- me.setCurrentTime(0);
- me.fireEvent('stop', me);
- me.pause();
- },
- //@private
- updateVolume: function(volume) {
- this.media.dom.volume = volume;
- },
- //@private
- updateMuted: function(muted) {
- this.fireEvent('mutedchange', this, muted);
- this.media.dom.muted = muted;
- },
- /**
- * Returns the current time of the media, in seconds.
- * @return {Number}
- */
- getCurrentTime: function() {
- return this.media.dom.currentTime;
- },
- /*
- * Set the current time of the media.
- * @param {Number} time The time, in seconds.
- * @return {Number}
- */
- setCurrentTime: function(time) {
- this.media.dom.currentTime = time;
- return time;
- },
- /**
- * Returns the duration of the media, in seconds.
- * @return {Number}
- */
- getDuration: function() {
- return this.media.dom.duration;
- },
- destroy: function() {
- var me = this,
- dom = me.media.dom,
- mediaEvents = me.mediaEvents;
- Ext.Object.each(mediaEvents, function(event, fn) {
- dom.removeEventListener(event, fn);
- });
- this.callSuper();
- }
- });
- /**
- * {@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).
- *
- * ## Recommended File Types/Compression:
- * * Uncompressed WAV and AIF audio
- * * MP3 audio
- * * AAC-LC
- * * HE-AAC audio
- *
- * ## Notes
- * On Android devices, the audio tags controls do not show. You must use the {@link #method-play}, {@link #method-pause} and
- * {@link #toggle} methods to control the audio (example below).
- *
- * ## Examples
- *
- * Here is an example of the {@link Ext.Audio} component in a fullscreen container:
- *
- * @example preview
- * Ext.create('Ext.Container', {
- * fullscreen: true,
- * layout: {
- * type : 'vbox',
- * pack : 'center',
- * align: 'stretch'
- * },
- * items: [
- * {
- * xtype : 'toolbar',
- * docked: 'top',
- * title : 'Ext.Audio'
- * },
- * {
- * xtype: 'audio',
- * url : 'touch/examples/audio/crash.mp3'
- * }
- * ]
- * });
- *
- * You can also set the {@link #hidden} configuration of the {@link Ext.Audio} component to true by default,
- * and then control the audio by using the {@link #method-play}, {@link #method-pause} and {@link #toggle} methods:
- *
- * @example preview
- * Ext.create('Ext.Container', {
- * fullscreen: true,
- * layout: {
- * type: 'vbox',
- * pack: 'center'
- * },
- * items: [
- * {
- * xtype : 'toolbar',
- * docked: 'top',
- * title : 'Ext.Audio'
- * },
- * {
- * xtype: 'toolbar',
- * docked: 'bottom',
- * defaults: {
- * xtype: 'button',
- * handler: function() {
- * var container = this.getParent().getParent(),
- * // use ComponentQuery to get the audio component (using its xtype)
- * audio = container.down('audio');
- *
- * audio.toggle();
- * this.setText(audio.isPlaying() ? 'Pause' : 'Play');
- * }
- * },
- * items: [
- * { text: 'Play', flex: 1 }
- * ]
- * },
- * {
- * html: 'Hidden audio!',
- * styleHtmlContent: true
- * },
- * {
- * xtype : 'audio',
- * hidden: true,
- * url : 'touch/examples/audio/crash.mp3'
- * }
- * ]
- * });
- * @aside example audio
- */
- Ext.define('Ext.Audio', {
- extend: 'Ext.Media',
- xtype : 'audio',
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- cls: Ext.baseCSSPrefix + 'audio'
- /**
- * @cfg {String} url
- * The location of the audio to play.
- *
- * ### Recommended file types are:
- * * Uncompressed WAV and AIF audio
- * * MP3 audio
- * * AAC-LC
- * * HE-AAC audio
- * @accessor
- */
- },
- // @private
- onActivate: function() {
- var me = this;
- me.callParent();
- if (Ext.os.is.Phone) {
- me.element.show();
- }
- },
- // @private
- onDeactivate: function() {
- var me = this;
- me.callParent();
- if (Ext.os.is.Phone) {
- me.element.hide();
- }
- },
- template: [{
- reference: 'media',
- preload: 'auto',
- tag: 'audio',
- cls: Ext.baseCSSPrefix + 'component'
- }]
- });
- /**
- * @class Ext.ComponentQuery
- * @extends Object
- * @singleton
- *
- * Provides searching of Components within {@link Ext.ComponentManager} (globally) or a specific
- * {@link Ext.Container} on the document with a similar syntax to a CSS selector.
- *
- * Components can be retrieved by using their {@link Ext.Component xtype} with an optional '.' prefix
- *
- * - `component` or `.component`
- * - `gridpanel` or `.gridpanel`
- *
- * An itemId or id must be prefixed with a #
- *
- * - `#myContainer`
- *
- * Attributes must be wrapped in brackets
- *
- * - `component[autoScroll]`
- * - `panel[title="Test"]`
- *
- * Member expressions from candidate Components may be tested. If the expression returns a *truthy* value,
- * the candidate Component will be included in the query:
- *
- * var disabledFields = myFormPanel.query("{isDisabled()}");
- *
- * Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:
- *
- * // Function receives array and returns a filtered array.
- * Ext.ComponentQuery.pseudos.invalid = function(items) {
- * var i = 0, l = items.length, c, result = [];
- * for (; i < l; i++) {
- * if (!(c = items[i]).isValid()) {
- * result.push(c);
- * }
- * }
- * return result;
- * };
- *
- * var invalidFields = myFormPanel.query('field:invalid');
- * if (invalidFields.length) {
- * invalidFields[0].getEl().scrollIntoView(myFormPanel.body);
- * for (var i = 0, l = invalidFields.length; i < l; i++) {
- * invalidFields[i].getEl().frame("red");
- * }
- * }
- *
- * Default pseudos include:
- *
- * - not
- *
- * Queries return an array of components.
- * Here are some example queries.
- *
- * // retrieve all Ext.Panels in the document by xtype
- * var panelsArray = Ext.ComponentQuery.query('panel');
- *
- * // retrieve all Ext.Panels within the container with an id myCt
- * var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
- *
- * // retrieve all direct children which are Ext.Panels within myCt
- * var directChildPanel = Ext.ComponentQuery.query('#myCt > panel');
- *
- * // retrieve all grids and trees
- * var gridsAndTrees = Ext.ComponentQuery.query('gridpanel, treepanel');
- *
- * For easy access to queries based from a particular Container see the {@link Ext.Container#query},
- * {@link Ext.Container#down} and {@link Ext.Container#child} methods. Also see
- * {@link Ext.Component#up}.
- */
- Ext.define('Ext.ComponentQuery', {
- singleton: true,
- uses: ['Ext.ComponentManager']
- }, function() {
- var cq = this,
- // A function source code pattern with a placeholder which accepts an expression which yields a truth value when applied
- // as a member on each item in the passed array.
- filterFnPattern = [
- 'var r = [],',
- 'i = 0,',
- 'it = items,',
- 'l = it.length,',
- 'c;',
- 'for (; i < l; i++) {',
- 'c = it[i];',
- 'if (c.{0}) {',
- 'r.push(c);',
- '}',
- '}',
- 'return r;'
- ].join(''),
- filterItems = function(items, operation) {
- // Argument list for the operation is [ itemsArray, operationArg1, operationArg2...]
- // The operation's method loops over each item in the candidate array and
- // returns an array of items which match its criteria
- return operation.method.apply(this, [ items ].concat(operation.args));
- },
- getItems = function(items, mode) {
- var result = [],
- i = 0,
- length = items.length,
- candidate,
- deep = mode !== '>';
- for (; i < length; i++) {
- candidate = items[i];
- if (candidate.getRefItems) {
- result = result.concat(candidate.getRefItems(deep));
- }
- }
- return result;
- },
- getAncestors = function(items) {
- var result = [],
- i = 0,
- length = items.length,
- candidate;
- for (; i < length; i++) {
- candidate = items[i];
- while (!!(candidate = (candidate.ownerCt || candidate.floatParent))) {
- result.push(candidate);
- }
- }
- return result;
- },
- // Filters the passed candidate array and returns only items which match the passed xtype
- filterByXType = function(items, xtype, shallow) {
- if (xtype === '*') {
- return items.slice();
- }
- else {
- var result = [],
- i = 0,
- length = items.length,
- candidate;
- for (; i < length; i++) {
- candidate = items[i];
- if (candidate.isXType(xtype, shallow)) {
- result.push(candidate);
- }
- }
- return result;
- }
- },
- // Filters the passed candidate array and returns only items which have the passed className
- filterByClassName = function(items, className) {
- var EA = Ext.Array,
- result = [],
- i = 0,
- length = items.length,
- candidate;
- for (; i < length; i++) {
- candidate = items[i];
- if (candidate.el ? candidate.el.hasCls(className) : EA.contains(candidate.initCls(), className)) {
- result.push(candidate);
- }
- }
- return result;
- },
- // Filters the passed candidate array and returns only items which have the specified property match
- filterByAttribute = function(items, property, operator, value) {
- var result = [],
- i = 0,
- length = items.length,
- candidate, getter, getValue;
- for (; i < length; i++) {
- candidate = items[i];
- getter = Ext.Class.getConfigNameMap(property).get;
- if (candidate[getter]) {
- getValue = candidate[getter]();
- if (!value ? !!getValue : (String(getValue) === value)) {
- result.push(candidate);
- }
- }
- else if (candidate.config && candidate.config[property]) {
- if (!value ? !!candidate.config[property] : (String(candidate.config[property]) === value)) {
- result.push(candidate);
- }
- }
- else if (!value ? !!candidate[property] : (String(candidate[property]) === value)) {
- result.push(candidate);
- }
- }
- return result;
- },
- // Filters the passed candidate array and returns only items which have the specified itemId or id
- filterById = function(items, id) {
- var result = [],
- i = 0,
- length = items.length,
- candidate;
- for (; i < length; i++) {
- candidate = items[i];
- if (candidate.getId() === id || candidate.getItemId() === id) {
- result.push(candidate);
- }
- }
- return result;
- },
- // Filters the passed candidate array and returns only items which the named pseudo class matcher filters in
- filterByPseudo = function(items, name, value) {
- return cq.pseudos[name](items, value);
- },
- // Determines leading mode
- // > for direct child, and ^ to switch to ownerCt axis
- modeRe = /^(\s?([>\^])\s?|\s|$)/,
- // Matches a token with possibly (true|false) appended for the "shallow" parameter
- tokenRe = /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,
- matchers = [{
- // Checks for .xtype with possibly (true|false) appended for the "shallow" parameter
- re: /^\.([\w\-]+)(?:\((true|false)\))?/,
- method: filterByXType
- },{
- // checks for [attribute=value]
- re: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]])/,
- method: filterByAttribute
- }, {
- // checks for #cmpItemId
- re: /^#([\w\-]+)/,
- method: filterById
- }, {
- // checks for :<pseudo_class>(<selector>)
- re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,
- method: filterByPseudo
- }, {
- // checks for {<member_expression>}
- re: /^(?:\{([^\}]+)\})/,
- method: filterFnPattern
- }];
- cq.Query = Ext.extend(Object, {
- constructor: function(cfg) {
- cfg = cfg || {};
- Ext.apply(this, cfg);
- },
- /**
- * @private
- * Executes this Query upon the selected root.
- * The root provides the initial source of candidate Component matches which are progressively
- * filtered by iterating through this Query's operations cache.
- * If no root is provided, all registered Components are searched via the ComponentManager.
- * root may be a Container who's descendant Components are filtered
- * root may be a Component with an implementation of getRefItems which provides some nested Components such as the
- * docked items within a Panel.
- * root may be an array of candidate Components to filter using this Query.
- */
- execute : function(root) {
- var operations = this.operations,
- i = 0,
- length = operations.length,
- operation,
- workingItems;
- // no root, use all Components in the document
- if (!root) {
- workingItems = Ext.ComponentManager.all.getArray();
- }
- // Root is a candidate Array
- else if (Ext.isArray(root)) {
- workingItems = root;
- }
- // We are going to loop over our operations and take care of them
- // one by one.
- for (; i < length; i++) {
- operation = operations[i];
- // The mode operation requires some custom handling.
- // All other operations essentially filter down our current
- // working items, while mode replaces our current working
- // items by getting children from each one of our current
- // working items. The type of mode determines the type of
- // children we get. (e.g. > only gets direct children)
- if (operation.mode === '^') {
- workingItems = getAncestors(workingItems || [root]);
- }
- else if (operation.mode) {
- workingItems = getItems(workingItems || [root], operation.mode);
- }
- else {
- workingItems = filterItems(workingItems || getItems([root]), operation);
- }
- // If this is the last operation, it means our current working
- // items are the final matched items. Thus return them!
- if (i === length -1) {
- return workingItems;
- }
- }
- return [];
- },
- is: function(component) {
- var operations = this.operations,
- components = Ext.isArray(component) ? component : [component],
- originalLength = components.length,
- lastOperation = operations[operations.length-1],
- ln, i;
- components = filterItems(components, lastOperation);
- if (components.length === originalLength) {
- if (operations.length > 1) {
- for (i = 0, ln = components.length; i < ln; i++) {
- if (Ext.Array.indexOf(this.execute(), components[i]) === -1) {
- return false;
- }
- }
- }
- return true;
- }
- return false;
- }
- });
- Ext.apply(this, {
- // private cache of selectors and matching ComponentQuery.Query objects
- cache: {},
- // private cache of pseudo class filter functions
- pseudos: {
- not: function(components, selector){
- var CQ = Ext.ComponentQuery,
- i = 0,
- length = components.length,
- results = [],
- index = -1,
- component;
- for(; i < length; ++i) {
- component = components[i];
- if (!CQ.is(component, selector)) {
- results[++index] = component;
- }
- }
- return results;
- }
- },
- /**
- * Returns an array of matched Components from within the passed root object.
- *
- * This method filters returned Components in a similar way to how CSS selector based DOM
- * queries work using a textual selector string.
- *
- * See class summary for details.
- *
- * @param {String} selector The selector string to filter returned Components
- * @param {Ext.Container} root The Container within which to perform the query.
- * If omitted, all Components within the document are included in the search.
- *
- * This parameter may also be an array of Components to filter according to the selector.</p>
- * @return {Ext.Component[]} The matched Components.
- *
- * @member Ext.ComponentQuery
- */
- query: function(selector, root) {
- var selectors = selector.split(','),
- length = selectors.length,
- i = 0,
- results = [],
- noDupResults = [],
- dupMatcher = {},
- query, resultsLn, cmp;
- for (; i < length; i++) {
- selector = Ext.String.trim(selectors[i]);
- query = this.parse(selector);
- // query = this.cache[selector];
- // if (!query) {
- // this.cache[selector] = query = this.parse(selector);
- // }
- results = results.concat(query.execute(root));
- }
- // multiple selectors, potential to find duplicates
- // lets filter them out.
- if (length > 1) {
- resultsLn = results.length;
- for (i = 0; i < resultsLn; i++) {
- cmp = results[i];
- if (!dupMatcher[cmp.id]) {
- noDupResults.push(cmp);
- dupMatcher[cmp.id] = true;
- }
- }
- results = noDupResults;
- }
- return results;
- },
- /**
- * Tests whether the passed Component matches the selector string.
- * @param {Ext.Component} component The Component to test.
- * @param {String} selector The selector string to test against.
- * @return {Boolean} `true` if the Component matches the selector.
- * @member Ext.ComponentQuery
- */
- is: function(component, selector) {
- if (!selector) {
- return true;
- }
- var query = this.cache[selector];
- if (!query) {
- this.cache[selector] = query = this.parse(selector);
- }
- return query.is(component);
- },
- parse: function(selector) {
- var operations = [],
- length = matchers.length,
- lastSelector,
- tokenMatch,
- matchedChar,
- modeMatch,
- selectorMatch,
- i, matcher, method;
- // We are going to parse the beginning of the selector over and
- // over again, slicing off the selector any portions we converted into an
- // operation, until it is an empty string.
- while (selector && lastSelector !== selector) {
- lastSelector = selector;
- // First we check if we are dealing with a token like #, * or an xtype
- tokenMatch = selector.match(tokenRe);
- if (tokenMatch) {
- matchedChar = tokenMatch[1];
- // If the token is prefixed with a # we push a filterById operation to our stack
- if (matchedChar === '#') {
- operations.push({
- method: filterById,
- args: [Ext.String.trim(tokenMatch[2])]
- });
- }
- // If the token is prefixed with a . we push a filterByClassName operation to our stack
- // FIXME: Not enabled yet. just needs \. adding to the tokenRe prefix
- else if (matchedChar === '.') {
- operations.push({
- method: filterByClassName,
- args: [Ext.String.trim(tokenMatch[2])]
- });
- }
- // If the token is a * or an xtype string, we push a filterByXType
- // operation to the stack.
- else {
- operations.push({
- method: filterByXType,
- args: [Ext.String.trim(tokenMatch[2]), Boolean(tokenMatch[3])]
- });
- }
- // Now we slice of the part we just converted into an operation
- selector = selector.replace(tokenMatch[0], '');
- }
- // If the next part of the query is not a space or > or ^, it means we
- // are going to check for more things that our current selection
- // has to comply to.
- while (!(modeMatch = selector.match(modeRe))) {
- // Lets loop over each type of matcher and execute it
- // on our current selector.
- for (i = 0; selector && i < length; i++) {
- matcher = matchers[i];
- selectorMatch = selector.match(matcher.re);
- method = matcher.method;
- // If we have a match, add an operation with the method
- // associated with this matcher, and pass the regular
- // expression matches are arguments to the operation.
- if (selectorMatch) {
- operations.push({
- method: Ext.isString(matcher.method)
- // Turn a string method into a function by formatting the string with our selector matche expression
- // A new method is created for different match expressions, eg {id=='textfield-1024'}
- // Every expression may be different in different selectors.
- ? Ext.functionFactory('items', Ext.String.format.apply(Ext.String, [method].concat(selectorMatch.slice(1))))
- : matcher.method,
- args: selectorMatch.slice(1)
- });
- selector = selector.replace(selectorMatch[0], '');
- break; // Break on match
- }
- //<debug>
- // Exhausted all matches: It's an error
- if (i === (length - 1)) {
- Ext.Error.raise('Invalid ComponentQuery selector: "' + arguments[0] + '"');
- }
- //</debug>
- }
- }
- // Now we are going to check for a mode change. This means a space
- // or a > to determine if we are going to select all the children
- // of the currently matched items, or a ^ if we are going to use the
- // ownerCt axis as the candidate source.
- if (modeMatch[1]) { // Assignment, and test for truthiness!
- operations.push({
- mode: modeMatch[2]||modeMatch[1]
- });
- selector = selector.replace(modeMatch[0], '');
- }
- }
- // Now that we have all our operations in an array, we are going
- // to create a new Query using these operations.
- return new cq.Query({
- operations: operations
- });
- }
- });
- });
- /**
- * @class Ext.Decorator
- * @extends Ext.Component
- *
- * In a few words, a Decorator is a Component that wraps around another Component. A typical example of a Decorator is a
- * {@link Ext.field.Field Field}. A form field is nothing more than a decorator around another component, and gives the
- * component a label, as well as extra styling to make it look good in a form.
- *
- * A Decorator can be thought of as a lightweight Container that has only one child item, and no layout overhead.
- * The look and feel of decorators can be styled purely in CSS.
- *
- * Another powerful feature that Decorator provides is config proxying. For example: all config items of a
- * {@link Ext.slider.Slider Slider} also exist in a {@link Ext.field.Slider Slider Field} for API convenience.
- * The {@link Ext.field.Slider Slider Field} simply proxies all corresponding getters and setters
- * to the actual {@link Ext.slider.Slider Slider} instance. Writing out all the setters and getters to do that is a tedious task
- * and a waste of code space. Instead, when you sub-class Ext.Decorator, all you need to do is to specify those config items
- * that you want to proxy to the Component using a special 'proxyConfig' class property. Here's how it may look like
- * in a Slider Field class:
- *
- * Ext.define('My.field.Slider', {
- * extend: 'Ext.Decorator',
- *
- * config: {
- * component: {
- * xtype: 'slider'
- * }
- * },
- *
- * proxyConfig: {
- * minValue: 0,
- * maxValue: 100,
- * increment: 1
- * }
- *
- * // ...
- * });
- *
- * Once `My.field.Slider` class is created, it will have all setters and getters methods for all items listed in `proxyConfig`
- * automatically generated. These methods all proxy to the same method names that exist within the Component instance.
- */
- Ext.define('Ext.Decorator', {
- extend: 'Ext.Component',
- isDecorator: true,
- config: {
- /**
- * @cfg {Object} component The config object to factory the Component that this Decorator wraps around
- */
- component: {}
- },
- statics: {
- generateProxySetter: function(name) {
- return function(value) {
- var component = this.getComponent();
- component[name].call(component, value);
- return this;
- }
- },
- generateProxyGetter: function(name) {
- return function() {
- var component = this.getComponent();
- return component[name].call(component);
- }
- }
- },
- onClassExtended: function(Class, members) {
- if (!members.hasOwnProperty('proxyConfig')) {
- return;
- }
- var ExtClass = Ext.Class,
- proxyConfig = members.proxyConfig,
- config = members.config;
- members.config = (config) ? Ext.applyIf(config, proxyConfig) : proxyConfig;
- var name, nameMap, setName, getName;
- for (name in proxyConfig) {
- if (proxyConfig.hasOwnProperty(name)) {
- nameMap = ExtClass.getConfigNameMap(name);
- setName = nameMap.set;
- getName = nameMap.get;
- members[setName] = this.generateProxySetter(setName);
- members[getName] = this.generateProxyGetter(getName);
- }
- }
- },
- // @private
- applyComponent: function(config) {
- return Ext.factory(config, Ext.Component);
- },
- // @private
- updateComponent: function(newComponent, oldComponent) {
- if (oldComponent) {
- if (this.isRendered() && oldComponent.setRendered(false)) {
- oldComponent.fireAction('renderedchange', [this, oldComponent, false],
- 'doUnsetComponent', this, { args: [oldComponent] });
- }
- else {
- this.doUnsetComponent(oldComponent);
- }
- }
- if (newComponent) {
- if (this.isRendered() && newComponent.setRendered(true)) {
- newComponent.fireAction('renderedchange', [this, newComponent, true],
- 'doSetComponent', this, { args: [newComponent] });
- }
- else {
- this.doSetComponent(newComponent);
- }
- }
- },
- // @private
- doUnsetComponent: function(component) {
- if (component.renderElement.dom) {
- component.setLayoutSizeFlags(0);
- this.innerElement.dom.removeChild(component.renderElement.dom);
- }
- },
- // @private
- doSetComponent: function(component) {
- if (component.renderElement.dom) {
- component.setLayoutSizeFlags(this.getSizeFlags());
- this.innerElement.dom.appendChild(component.renderElement.dom);
- }
- },
- // @private
- setRendered: function(rendered) {
- var component;
- if (this.callParent(arguments)) {
- component = this.getComponent();
- if (component) {
- component.setRendered(rendered);
- }
- return true;
- }
- return false;
- },
- // @private
- setDisabled: function(disabled) {
- this.callParent(arguments);
- this.getComponent().setDisabled(disabled);
- },
- destroy: function() {
- Ext.destroy(this.getComponent());
- this.callParent();
- }
- });
- /**
- * This is a simple way to add an image of any size to your application and have it participate in the layout system
- * like any other component. This component typically takes between 1 and 3 configurations - a {@link #src}, and
- * optionally a {@link #height} and a {@link #width}:
- *
- * @example miniphone
- * var img = Ext.create('Ext.Img', {
- * src: 'http://www.sencha.com/assets/images/sencha-avatar-64x64.png',
- * height: 64,
- * width: 64
- * });
- * Ext.Viewport.add(img);
- *
- * It's also easy to add an image into a panel or other container using its xtype:
- *
- * @example miniphone
- * Ext.create('Ext.Panel', {
- * fullscreen: true,
- * layout: 'hbox',
- * items: [
- * {
- * xtype: 'image',
- * src: 'http://www.sencha.com/assets/images/sencha-avatar-64x64.png',
- * flex: 1
- * },
- * {
- * xtype: 'panel',
- * flex: 2,
- * html: 'Sencha Inc.<br/>1700 Seaport Boulevard Suite 120, Redwood City, CA'
- * }
- * ]
- * });
- *
- * Here we created a panel which contains an image (a profile picture in this case) and a text area to allow the user
- * to enter profile information about themselves. In this case we used an {@link Ext.layout.HBox hbox layout} and
- * flexed the image to take up one third of the width and the text area to take two thirds of the width. See the
- * {@link Ext.layout.HBox hbox docs} for more information on flexing items.
- */
- Ext.define('Ext.Img', {
- extend: 'Ext.Component',
- xtype: ['image', 'img'],
- /**
- * @event tap
- * Fires whenever the component is tapped
- * @param {Ext.Img} this The Image instance
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event load
- * Fires when the image is loaded
- * @param {Ext.Img} this The Image instance
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event error
- * Fires if an error occured when trying to load the image
- * @param {Ext.Img} this The Image instance
- * @param {Ext.EventObject} e The event object
- */
- config: {
- /**
- * @cfg {String} src The source of this image
- * @accessor
- */
- src: null,
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls : Ext.baseCSSPrefix + 'img',
- /**
- * @cfg {String} imageCls The CSS class to be used when {@link #mode} is not set to 'background'
- * @accessor
- */
- imageCls : Ext.baseCSSPrefix + 'img-image',
- /**
- * @cfg {String} backgroundCls The CSS class to be used when {@link #mode} is set to 'background'
- * @accessor
- */
- backgroundCls : Ext.baseCSSPrefix + 'img-background',
- /**
- * @cfg {String} mode If set to 'background', uses a background-image CSS property instead of an
- * `<img>` tag to display the image.
- */
- mode: 'background'
- },
- beforeInitialize: function() {
- var me = this;
- me.onLoad = Ext.Function.bind(me.onLoad, me);
- me.onError = Ext.Function.bind(me.onError, me);
- },
- initialize: function() {
- var me = this;
- me.callParent();
- me.relayEvents(me.renderElement, '*');
- me.element.on({
- tap: 'onTap',
- scope: me
- });
- },
- hide: function() {
- this.callParent();
- this.hiddenSrc = this.hiddenSrc || this.getSrc();
- this.setSrc(null);
- },
- show: function() {
- this.callParent();
- if (this.hiddenSrc) {
- this.setSrc(this.hiddenSrc);
- delete this.hiddenSrc;
- }
- },
- updateMode: function(mode) {
- var me = this,
- imageCls = me.getImageCls(),
- backgroundCls = me.getBackgroundCls();
- if (mode === 'background') {
- if (me.imageElement) {
- me.imageElement.destroy();
- delete me.imageElement;
- me.updateSrc(me.getSrc());
- }
- me.replaceCls(imageCls, backgroundCls);
- } else {
- me.imageElement = me.element.createChild({ tag: 'img' });
- me.replaceCls(backgroundCls, imageCls);
- }
- },
- updateImageCls : function (newCls, oldCls) {
- this.replaceCls(oldCls, newCls);
- },
- updateBackgroundCls : function (newCls, oldCls) {
- this.replaceCls(oldCls, newCls);
- },
- onTap: function(e) {
- this.fireEvent('tap', this, e);
- },
- onAfterRender: function() {
- this.updateSrc(this.getSrc());
- },
- /**
- * @private
- */
- updateSrc: function(newSrc) {
- var me = this,
- dom;
- if (me.getMode() === 'background') {
- dom = this.imageObject || new Image();
- }
- else {
- dom = me.imageElement.dom;
- }
- this.imageObject = dom;
- dom.setAttribute('src', Ext.isString(newSrc) ? newSrc : '');
- dom.addEventListener('load', me.onLoad, false);
- dom.addEventListener('error', me.onError, false);
- },
- detachListeners: function() {
- var dom = this.imageObject;
- if (dom) {
- dom.removeEventListener('load', this.onLoad, false);
- dom.removeEventListener('error', this.onError, false);
- }
- },
- onLoad : function(e) {
- this.detachListeners();
- if (this.getMode() === 'background') {
- this.element.dom.style.backgroundImage = 'url("' + this.imageObject.src + '")';
- }
- this.fireEvent('load', this, e);
- },
- onError : function(e) {
- this.detachListeners();
- this.fireEvent('error', this, e);
- },
- doSetWidth: function(width) {
- var sizingElement = (this.getMode() === 'background') ? this.element : this.imageElement;
- sizingElement.setWidth(width);
- this.callParent(arguments);
- },
- doSetHeight: function(height) {
- var sizingElement = (this.getMode() === 'background') ? this.element : this.imageElement;
- sizingElement.setHeight(height);
- this.callParent(arguments);
- },
- destroy: function() {
- this.detachListeners();
- Ext.destroy(this.imageObject, this.imageElement);
- delete this.imageObject;
- delete this.imageElement;
- this.callParent();
- }
- });
- /**
- * A simple label component which allows you to insert content using {@link #html} configuration.
- *
- * @example miniphone
- * Ext.Viewport.add({
- * xtype: 'label',
- * html: 'My label!'
- * });
- */
- Ext.define('Ext.Label', {
- extend: 'Ext.Component',
- xtype: 'label',
- config: {
- baseCls: Ext.baseCSSPrefix + 'label'
- /**
- * @cfg {String} html
- * The label of this component.
- */
- }
- });
- /**
- * A simple class used to mask any {@link Ext.Container}.
- *
- * This should rarely be used directly, instead look at the {@link Ext.Container#masked} configuration.
- *
- * ## Example
- *
- * @example miniphone
- * Ext.Viewport.add({
- * masked: {
- * xtype: 'loadmask'
- * }
- * });
- *
- * You can customize the loading {@link #message} and whether or not you want to show the {@link #indicator}:
- *
- * @example miniphone
- * Ext.Viewport.add({
- * masked: {
- * xtype: 'loadmask',
- * message: 'A message..',
- * indicator: false
- * }
- * });
- *
- */
- Ext.define('Ext.LoadMask', {
- extend: 'Ext.Mask',
- xtype: 'loadmask',
- config: {
- /**
- * @cfg {String} message
- * The text to display in a centered loading message box.
- * @accessor
- */
- message: 'Loading...',
- /**
- * @cfg {String} messageCls
- * The CSS class to apply to the loading message element.
- * @accessor
- */
- messageCls: Ext.baseCSSPrefix + 'mask-message',
- /**
- * @cfg {Boolean} indicator
- * True to show the loading indicator on this {@link Ext.LoadMask}.
- * @accessor
- */
- indicator: true
- },
- getTemplate: function() {
- var prefix = Ext.baseCSSPrefix;
- return [
- {
- //it needs an inner so it can be centered within the mask, and have a background
- reference: 'innerElement',
- cls: prefix + 'mask-inner',
- children: [
- //the elements required for the CSS loading {@link #indicator}
- {
- reference: 'indicatorElement',
- cls: prefix + 'loading-spinner-outer',
- children: [
- {
- cls: prefix + 'loading-spinner',
- children: [
- { tag: 'span', cls: prefix + 'loading-top' },
- { tag: 'span', cls: prefix + 'loading-right' },
- { tag: 'span', cls: prefix + 'loading-bottom' },
- { tag: 'span', cls: prefix + 'loading-left' }
- ]
- }
- ]
- },
- //the element used to display the {@link #message}
- {
- reference: 'messageElement'
- }
- ]
- }
- ];
- },
- /**
- * Updates the message element with the new value of the {@link #message} configuration
- * @private
- */
- updateMessage: function(newMessage) {
- var cls = Ext.baseCSSPrefix + 'has-message';
- if (newMessage) {
- this.addCls(cls);
- } else {
- this.removeCls(cls);
- }
- this.messageElement.setHtml(newMessage);
- },
- /**
- * Replaces the cls of the message element with the value of the {@link #messageCls} configuration.
- * @private
- */
- updateMessageCls: function(newMessageCls, oldMessageCls) {
- this.messageElement.replaceCls(oldMessageCls, newMessageCls);
- },
- /**
- * Shows or hides the loading indicator when the {@link #indicator} configuration is changed.
- * @private
- */
- updateIndicator: function(newIndicator) {
- this[newIndicator ? 'removeCls' : 'addCls'](Ext.baseCSSPrefix + 'indicator-hidden');
- }
- }, function() {
- });
- /**
- * Provides a cross browser class for retrieving location information.
- *
- * Based on the [Geolocation API Specification](http://dev.w3.org/geo/api/spec-source.html)
- *
- * When instantiated, by default this class immediately begins tracking location information,
- * firing a {@link #locationupdate} event when new location information is available. To disable this
- * location tracking (which may be battery intensive on mobile devices), set {@link #autoUpdate} to `false`.
- *
- * When this is done, only calls to {@link #updateLocation} will trigger a location retrieval.
- *
- * A {@link #locationerror} event is raised when an error occurs retrieving the location, either due to a user
- * denying the application access to it, or the browser not supporting it.
- *
- * The below code shows a GeoLocation making a single retrieval of location information.
- *
- * var geo = Ext.create('Ext.util.Geolocation', {
- * autoUpdate: false,
- * listeners: {
- * locationupdate: function(geo) {
- * alert('New latitude: ' + geo.getLatitude());
- * },
- * locationerror: function(geo, bTimeout, bPermissionDenied, bLocationUnavailable, message) {
- * if(bTimeout){
- * alert('Timeout occurred.');
- * } else {
- * alert('Error occurred.');
- * }
- * }
- * }
- * });
- * geo.updateLocation();
- */
- Ext.define('Ext.util.Geolocation', {
- extend: 'Ext.Evented',
- alternateClassName: ['Ext.util.GeoLocation'],
- config: {
- /**
- * @event locationerror
- * Raised when a location retrieval operation failed.
- *
- * In the case of calling updateLocation, this event will be raised only once.
- *
- * If {@link #autoUpdate} is set to `true`, this event could be raised repeatedly.
- * The first error is relative to the moment {@link #autoUpdate} was set to `true`
- * (or this {@link Ext.util.Geolocation} was initialized with the {@link #autoUpdate} config option set to `true`).
- * Subsequent errors are relative to the moment when the device determines that it's position has changed.
- * @param {Ext.util.Geolocation} this
- * @param {Boolean} timeout
- * Boolean indicating a timeout occurred
- * @param {Boolean} permissionDenied
- * Boolean indicating the user denied the location request
- * @param {Boolean} locationUnavailable
- * Boolean indicating that the location of the device could not be determined.
- * For instance, one or more of the location providers used in the location acquisition
- * process reported an internal error that caused the process to fail entirely.
- * @param {String} message An error message describing the details of the error encountered.
- *
- * This attribute is primarily intended for debugging and should not be used
- * directly in an application user interface.
- */
- /**
- * @event locationupdate
- * Raised when a location retrieval operation has been completed successfully.
- * @param {Ext.util.Geolocation} this
- * Retrieve the current location information from the GeoLocation object by using the read-only
- * properties: {@link #latitude}, {@link #longitude}, {@link #accuracy}, {@link #altitude}, {@link #altitudeAccuracy}, {@link #heading}, and {@link #speed}.
- */
- /**
- * @cfg {Boolean} autoUpdate
- * When set to `true`, continually monitor the location of the device (beginning immediately)
- * and fire {@link #locationupdate} and {@link #locationerror} events.
- */
- autoUpdate: true,
- /**
- * @cfg {Number} frequency
- * The frequency of each update if {@link #autoUpdate} is set to `true`.
- */
- frequency: 10000,
- /**
- * Read-only property representing the last retrieved
- * geographical coordinate specified in degrees.
- * @type Number
- * @readonly
- */
- latitude: null,
- /**
- * Read-only property representing the last retrieved
- * geographical coordinate specified in degrees.
- * @type Number
- * @readonly
- */
- longitude: null,
- /**
- * Read-only property representing the last retrieved
- * accuracy level of the latitude and longitude coordinates,
- * specified in meters.
- *
- * This will always be a non-negative number.
- *
- * This corresponds to a 95% confidence level.
- * @type Number
- * @readonly
- */
- accuracy: null,
- /**
- * Read-only property representing the last retrieved
- * height of the position, specified in meters above the ellipsoid
- * [WGS84](http://dev.w3.org/geo/api/spec-source.html#ref-wgs).
- * @type Number
- * @readonly
- */
- altitude: null,
- /**
- * Read-only property representing the last retrieved
- * accuracy level of the altitude coordinate, specified in meters.
- *
- * If altitude is not null then this will be a non-negative number.
- * Otherwise this returns `null`.
- *
- * This corresponds to a 95% confidence level.
- * @type Number
- * @readonly
- */
- altitudeAccuracy: null,
- /**
- * Read-only property representing the last retrieved
- * direction of travel of the hosting device,
- * specified in non-negative degrees between 0 and 359,
- * counting clockwise relative to the true north.
- *
- * If speed is 0 (device is stationary), then this returns `NaN`.
- * @type Number
- * @readonly
- */
- heading: null,
- /**
- * Read-only property representing the last retrieved
- * current ground speed of the device, specified in meters per second.
- *
- * If this feature is unsupported by the device, this returns `null`.
- *
- * If the device is stationary, this returns 0,
- * otherwise it returns a non-negative number.
- * @type Number
- * @readonly
- */
- speed: null,
- /**
- * Read-only property representing when the last retrieved
- * positioning information was acquired by the device.
- * @type Date
- * @readonly
- */
- timestamp: null,
- //PositionOptions interface
- /**
- * @cfg {Boolean} allowHighAccuracy
- * When set to `true`, provide a hint that the application would like to receive
- * the best possible results. This may result in slower response times or increased power consumption.
- * The user might also deny this capability, or the device might not be able to provide more accurate
- * results than if this option was set to `false`.
- */
- allowHighAccuracy: false,
- /**
- * @cfg {Number} timeout
- * The maximum number of milliseconds allowed to elapse between a location update operation
- * and the corresponding {@link #locationupdate} event being raised. If a location was not successfully
- * acquired before the given timeout elapses (and no other internal errors have occurred in this interval),
- * then a {@link #locationerror} event will be raised indicating a timeout as the cause.
- *
- * Note that the time that is spent obtaining the user permission is **not** included in the period
- * covered by the timeout. The `timeout` attribute only applies to the location acquisition operation.
- *
- * In the case of calling `updateLocation`, the {@link #locationerror} event will be raised only once.
- *
- * If {@link #autoUpdate} is set to `true`, the {@link #locationerror} event could be raised repeatedly.
- * The first timeout is relative to the moment {@link #autoUpdate} was set to `true`
- * (or this {@link Ext.util.Geolocation} was initialized with the {@link #autoUpdate} config option set to `true`).
- * Subsequent timeouts are relative to the moment when the device determines that it's position has changed.
- */
- timeout: Infinity,
- /**
- * @cfg {Number} maximumAge
- * This option indicates that the application is willing to accept cached location information whose age
- * is no greater than the specified time in milliseconds. If `maximumAge` is set to 0, an attempt to retrieve
- * new location information is made immediately.
- *
- * Setting the `maximumAge` to Infinity returns a cached position regardless of its age.
- *
- * If the device does not have cached location information available whose age is no
- * greater than the specified `maximumAge`, then it must acquire new location information.
- *
- * For example, if location information no older than 10 minutes is required, set this property to 600000.
- */
- maximumAge: 0,
- // @private
- provider : undefined
- },
- updateMaximumAge: function() {
- if (this.watchOperation) {
- this.updateWatchOperation();
- }
- },
- updateTimeout: function() {
- if (this.watchOperation) {
- this.updateWatchOperation();
- }
- },
- updateAllowHighAccuracy: function() {
- if (this.watchOperation) {
- this.updateWatchOperation();
- }
- },
- applyProvider: function(config) {
- if (Ext.feature.has.Geolocation) {
- if (!config) {
- if (navigator && navigator.geolocation) {
- config = navigator.geolocation;
- }
- else if (window.google) {
- config = google.gears.factory.create('beta.geolocation');
- }
- }
- }
- else {
- this.fireEvent('locationerror', this, false, false, true, 'This device does not support Geolocation.');
- }
- return config;
- },
- updateAutoUpdate: function(newAutoUpdate, oldAutoUpdate) {
- var me = this,
- provider = me.getProvider();
- if (oldAutoUpdate && provider) {
- clearInterval(me.watchOperationId);
- me.watchOperationId = null;
- }
- if (newAutoUpdate) {
- if (!provider) {
- me.fireEvent('locationerror', me, false, false, true, null);
- return;
- }
- try {
- me.updateWatchOperation();
- }
- catch(e) {
- me.fireEvent('locationerror', me, false, false, true, e.message);
- }
- }
- },
- // @private
- updateWatchOperation: function() {
- var me = this,
- provider = me.getProvider();
- // The native watchPosition method is currently broken in iOS5...
- if (me.watchOperationId) {
- clearInterval(me.watchOperationId);
- }
- function pollPosition() {
- provider.getCurrentPosition(
- Ext.bind(me.fireUpdate, me),
- Ext.bind(me.fireError, me),
- me.parseOptions()
- );
- }
- pollPosition();
- me.watchOperationId = setInterval(pollPosition, this.getFrequency());
- },
- /**
- * Executes a onetime location update operation,
- * raising either a {@link #locationupdate} or {@link #locationerror} event.
- *
- * Does not interfere with or restart ongoing location monitoring.
- * @param {Function} callback
- * A callback method to be called when the location retrieval has been completed.
- *
- * Will be called on both success and failure.
- *
- * The method will be passed one parameter, {@link Ext.util.Geolocation} (**this** reference),
- * set to `null` on failure.
- *
- * geo.updateLocation(function (geo) {
- * alert('Latitude: ' + (geo !== null ? geo.latitude : 'failed'));
- * });
- *
- * @param {Object} scope (optional) The scope (**this** reference) in which the handler function is executed.
- *
- * **If omitted, defaults to the object which fired the event.**
- * <!--positonOptions undocumented param, see W3C spec-->
- */
- updateLocation: function(callback, scope, positionOptions) {
- var me = this,
- provider = me.getProvider();
- var failFunction = function(message, error) {
- if (error) {
- me.fireError(error);
- }
- else {
- me.fireEvent('locationerror', me, false, false, true, message);
- }
- if (callback) {
- callback.call(scope || me, null, me); //last parameter for legacy purposes
- }
- };
- if (!provider) {
- failFunction(null);
- return;
- }
- try {
- provider.getCurrentPosition(
- //success callback
- function(position) {
- me.fireUpdate(position);
- if (callback) {
- callback.call(scope || me, me, me); //last parameter for legacy purposes
- }
- },
- //error callback
- function(error) {
- failFunction(null, error);
- },
- positionOptions || me.parseOptions()
- );
- }
- catch(e) {
- failFunction(e.message);
- }
- },
- // @private
- fireUpdate: function(position) {
- var me = this,
- coords = position.coords;
- this.position = position;
- me.setConfig({
- timestamp: position.timestamp,
- latitude: coords.latitude,
- longitude: coords.longitude,
- accuracy: coords.accuracy,
- altitude: coords.altitude,
- altitudeAccuracy: coords.altitudeAccuracy,
- heading: coords.heading,
- speed: coords.speed
- });
- me.fireEvent('locationupdate', me);
- },
- // @private
- fireError: function(error) {
- var errorCode = error.code;
- this.fireEvent('locationerror', this,
- errorCode == error.TIMEOUT,
- errorCode == error.PERMISSION_DENIED,
- errorCode == error.POSITION_UNAVAILABLE,
- error.message == undefined ? null : error.message
- );
- },
- // @private
- parseOptions: function() {
- var timeout = this.getTimeout(),
- ret = {
- maximumAge: this.getMaximumAge(),
- enableHighAccuracy: this.getAllowHighAccuracy()
- };
- //Google doesn't like Infinity
- if (timeout !== Infinity) {
- ret.timeout = timeout;
- }
- return ret;
- },
- destroy : function() {
- this.setAutoUpdate(false);
- }
- });
- /**
- * Wraps a Google Map in an Ext.Component using the [Google Maps API](http://code.google.com/apis/maps/documentation/v3/introduction.html).
- *
- * To use this component you must include an additional JavaScript file from Google:
- *
- * <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true"></script>
- *
- * ## Example
- *
- * Ext.Viewport.add({
- * xtype: 'map',
- * useCurrentLocation: true
- * });
- *
- * @aside example maps
- */
- Ext.define('Ext.Map', {
- extend: 'Ext.Container',
- xtype : 'map',
- requires: ['Ext.util.Geolocation'],
- isMap: true,
- config: {
- /**
- * @event maprender
- * Fired when Map initially rendered.
- * @param {Ext.Map} this
- * @param {google.maps.Map} map The rendered google.map.Map instance
- */
- /**
- * @event centerchange
- * Fired when map is panned around.
- * @param {Ext.Map} this
- * @param {google.maps.Map} map The rendered google.map.Map instance
- * @param {google.maps.LatLng} center The current LatLng center of the map
- */
- /**
- * @event typechange
- * Fired when display type of the map changes.
- * @param {Ext.Map} this
- * @param {google.maps.Map} map The rendered google.map.Map instance
- * @param {Number} mapType The current display type of the map
- */
- /**
- * @event zoomchange
- * Fired when map is zoomed.
- * @param {Ext.Map} this
- * @param {google.maps.Map} map The rendered google.map.Map instance
- * @param {Number} zoomLevel The current zoom level of the map
- */
- /**
- * @cfg {String} baseCls
- * The base CSS class to apply to the Map's element
- * @accessor
- */
- baseCls: Ext.baseCSSPrefix + 'map',
- /**
- * @cfg {Boolean/Ext.util.Geolocation} useCurrentLocation
- * Pass in true to center the map based on the geolocation coordinates or pass a
- * {@link Ext.util.Geolocation GeoLocation} config to have more control over your GeoLocation options
- * @accessor
- */
- useCurrentLocation: false,
- /**
- * @cfg {google.maps.Map} map
- * The wrapped map.
- * @accessor
- */
- map: null,
- /**
- * @cfg {Ext.util.Geolocation} geo
- * Geolocation provider for the map.
- * @accessor
- */
- geo: null,
- /**
- * @cfg {Object} mapOptions
- * MapOptions as specified by the Google Documentation:
- * [http://code.google.com/apis/maps/documentation/v3/reference.html](http://code.google.com/apis/maps/documentation/v3/reference.html)
- * @accessor
- */
- mapOptions: {}
- },
- constructor: function() {
- this.callParent(arguments);
- // this.element.setVisibilityMode(Ext.Element.OFFSETS);
- if (!(window.google || {}).maps) {
- this.setHtml('Google Maps API is required');
- }
- },
- initialize: function() {
- this.callParent();
- this.on({
- painted: 'doResize',
- scope: this
- });
- this.innerElement.on('touchstart', 'onTouchStart', this);
- },
- getElementConfig: function() {
- return {
- reference: 'element',
- className: 'x-container',
- children: [{
- reference: 'innerElement',
- className: 'x-inner',
- children: [{
- reference: 'mapContainer',
- className: Ext.baseCSSPrefix + 'map-container'
- }]
- }]
- };
- },
- onTouchStart: function(e) {
- e.makeUnpreventable();
- },
- applyMapOptions: function(options) {
- return Ext.merge({}, this.options, options);
- },
- updateMapOptions: function(newOptions) {
- var me = this,
- gm = (window.google || {}).maps,
- map = this.getMap();
- if (gm && map) {
- map.setOptions(newOptions);
- }
- if (newOptions.center && !me.isPainted()) {
- me.un('painted', 'setMapCenter', this);
- me.on('painted', 'setMapCenter', this, { delay: 150, single: true, args: [newOptions.center] });
- }
- },
- getMapOptions: function() {
- return Ext.merge({}, this.options || this.getInitialConfig('mapOptions'));
- },
- updateUseCurrentLocation: function(useCurrentLocation) {
- this.setGeo(useCurrentLocation);
- if (!useCurrentLocation) {
- this.renderMap();
- }
- },
- applyGeo: function(config) {
- return Ext.factory(config, Ext.util.Geolocation, this.getGeo());
- },
- updateGeo: function(newGeo, oldGeo) {
- var events = {
- locationupdate : 'onGeoUpdate',
- locationerror : 'onGeoError',
- scope : this
- };
- if (oldGeo) {
- oldGeo.un(events);
- }
- if (newGeo) {
- newGeo.on(events);
- newGeo.updateLocation();
- }
- },
- doResize: function() {
- var gm = (window.google || {}).maps,
- map = this.getMap();
- if (gm && map) {
- gm.event.trigger(map, "resize");
- }
- },
- // @private
- renderMap: function() {
- var me = this,
- gm = (window.google || {}).maps,
- element = me.mapContainer,
- mapOptions = me.getMapOptions(),
- map = me.getMap(),
- event;
- if (gm) {
- if (Ext.os.is.iPad) {
- Ext.merge({
- navigationControlOptions: {
- style: gm.NavigationControlStyle.ZOOM_PAN
- }
- }, mapOptions);
- }
- mapOptions = Ext.merge({
- zoom: 12,
- mapTypeId: gm.MapTypeId.ROADMAP
- }, mapOptions);
- // This is done separately from the above merge so we don't have to instantiate
- // a new LatLng if we don't need to
- if (!mapOptions.hasOwnProperty('center')) {
- mapOptions.center = new gm.LatLng(37.381592, -122.135672); // Palo Alto
- }
- if (element.dom.firstChild) {
- Ext.fly(element.dom.firstChild).destroy();
- }
- if (map) {
- gm.event.clearInstanceListeners(map);
- }
- me.setMap(new gm.Map(element.dom, mapOptions));
- map = me.getMap();
- //Track zoomLevel and mapType changes
- event = gm.event;
- event.addListener(map, 'zoom_changed', Ext.bind(me.onZoomChange, me));
- event.addListener(map, 'maptypeid_changed', Ext.bind(me.onTypeChange, me));
- event.addListener(map, 'center_changed', Ext.bind(me.onCenterChange, me));
- me.fireEvent('maprender', me, map);
- }
- },
- // @private
- onGeoUpdate: function(geo) {
- if (geo) {
- this.setMapCenter(new google.maps.LatLng(geo.getLatitude(), geo.getLongitude()));
- }
- },
- // @private
- onGeoError: Ext.emptyFn,
- /**
- * Moves the map center to the designated coordinates hash of the form:
- *
- * { latitude: 37.381592, longitude: -122.135672 }
- *
- * or a google.maps.LatLng object representing to the target location.
- *
- * @param {Object/google.maps.LatLng} coordinates Object representing the desired Latitude and
- * longitude upon which to center the map.
- */
- setMapCenter: function(coordinates) {
- var me = this,
- map = me.getMap(),
- gm = (window.google || {}).maps;
- if (gm) {
- if (!me.isPainted()) {
- me.un('painted', 'setMapCenter', this);
- me.on('painted', 'setMapCenter', this, { delay: 150, single: true, args: [coordinates] });
- return;
- }
- coordinates = coordinates || new gm.LatLng(37.381592, -122.135672);
- if (coordinates && !(coordinates instanceof gm.LatLng) && 'longitude' in coordinates) {
- coordinates = new gm.LatLng(coordinates.latitude, coordinates.longitude);
- }
- if (!map) {
- me.renderMap();
- map = me.getMap();
- }
- if (map && coordinates instanceof gm.LatLng) {
- map.panTo(coordinates);
- }
- else {
- this.options = Ext.apply(this.getMapOptions(), {
- center: coordinates
- });
- }
- }
- },
- // @private
- onZoomChange : function() {
- var mapOptions = this.getMapOptions(),
- map = this.getMap(),
- zoom;
- zoom = (map && map.getZoom) ? map.getZoom() : mapOptions.zoom || 10;
- this.options = Ext.apply(mapOptions, {
- zoom: zoom
- });
- this.fireEvent('zoomchange', this, map, zoom);
- },
- // @private
- onTypeChange : function() {
- var mapOptions = this.getMapOptions(),
- map = this.getMap(),
- mapTypeId;
- mapTypeId = (map && map.getMapTypeId) ? map.getMapTypeId() : mapOptions.mapTypeId;
- this.options = Ext.apply(mapOptions, {
- mapTypeId: mapTypeId
- });
- this.fireEvent('typechange', this, map, mapTypeId);
- },
- // @private
- onCenterChange: function() {
- var mapOptions = this.getMapOptions(),
- map = this.getMap(),
- center;
- center = (map && map.getCenter) ? map.getCenter() : mapOptions.center;
- this.options = Ext.apply(mapOptions, {
- center: center
- });
- this.fireEvent('centerchange', this, map, center);
- },
- // @private
- destroy: function() {
- Ext.destroy(this.getGeo());
- var map = this.getMap();
- if (map && (window.google || {}).maps) {
- google.maps.event.clearInstanceListeners(map);
- }
- this.callParent();
- }
- }, function() {
- });
- /**
- * {@link Ext.Title} is used for the {@link Ext.Toolbar#title} configuration in the {@link Ext.Toolbar} component.
- * @private
- */
- Ext.define('Ext.Title', {
- extend: 'Ext.Component',
- xtype: 'title',
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: 'x-title',
- /**
- * @cfg {String} title The title text
- */
- title: ''
- },
- // @private
- updateTitle: function(newTitle) {
- this.setHtml(newTitle);
- }
- });
- /**
- The {@link Ext.Spacer} component is generally used to put space between items in {@link Ext.Toolbar} components.
- ## Examples
- By default the {@link #flex} configuration is set to 1:
- @example miniphone preview
- Ext.create('Ext.Container', {
- fullscreen: true,
- items: [
- {
- xtype : 'toolbar',
- docked: 'top',
- items: [
- {
- xtype: 'button',
- text : 'Button One'
- },
- {
- xtype: 'spacer'
- },
- {
- xtype: 'button',
- text : 'Button Two'
- }
- ]
- }
- ]
- });
- Alternatively you can just set the {@link #width} configuration which will get the {@link Ext.Spacer} a fixed width:
- @example preview
- Ext.create('Ext.Container', {
- fullscreen: true,
- layout: {
- type: 'vbox',
- pack: 'center',
- align: 'stretch'
- },
- items: [
- {
- xtype : 'toolbar',
- docked: 'top',
- items: [
- {
- xtype: 'button',
- text : 'Button One'
- },
- {
- xtype: 'spacer',
- width: 50
- },
- {
- xtype: 'button',
- text : 'Button Two'
- }
- ]
- },
- {
- xtype: 'container',
- items: [
- {
- xtype: 'button',
- text : 'Change Ext.Spacer width',
- handler: function() {
- //get the spacer using ComponentQuery
- var spacer = Ext.ComponentQuery.query('spacer')[0],
- from = 10,
- to = 250;
- //set the width to a random number
- spacer.setWidth(Math.floor(Math.random() * (to - from + 1) + from));
- }
- }
- ]
- }
- ]
- });
- You can also insert multiple {@link Ext.Spacer}'s:
- @example preview
- Ext.create('Ext.Container', {
- fullscreen: true,
- items: [
- {
- xtype : 'toolbar',
- docked: 'top',
- items: [
- {
- xtype: 'button',
- text : 'Button One'
- },
- {
- xtype: 'spacer'
- },
- {
- xtype: 'button',
- text : 'Button Two'
- },
- {
- xtype: 'spacer',
- width: 20
- },
- {
- xtype: 'button',
- text : 'Button Three'
- }
- ]
- }
- ]
- });
- */
- Ext.define('Ext.Spacer', {
- extend: 'Ext.Component',
- alias : 'widget.spacer',
- config: {
- /**
- * @cfg {Number} flex
- * The flex value of this spacer. This defaults to 1, if no width has been set.
- * @accessor
- */
-
- /**
- * @cfg {Number} width
- * The width of this spacer. If this is set, the value of {@link #flex} will be ignored.
- * @accessor
- */
- },
- // @private
- constructor: function(config) {
- config = config || {};
- if (!config.width) {
- config.flex = 1;
- }
- this.callParent([config]);
- }
- });
- /**
- * @aside video tabs-toolbars
- *
- * {@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.
- *
- * They allow you to insert items (normally {@link Ext.Button buttons}) and also add a {@link #title}.
- *
- * The {@link #defaultType} of {@link Ext.Toolbar} is {@link Ext.Button}.
- *
- * You can alternatively use {@link Ext.TitleBar} if you want the title to automatically adjust the size of its items.
- *
- * ## Examples
- *
- * @example miniphone preview
- * Ext.create('Ext.Container', {
- * fullscreen: true,
- * layout: {
- * type: 'vbox',
- * pack: 'center'
- * },
- * items: [
- * {
- * xtype : 'toolbar',
- * docked: 'top',
- * title: 'My Toolbar'
- * },
- * {
- * xtype: 'container',
- * defaults: {
- * xtype: 'button',
- * margin: '10 10 0 10'
- * },
- * items: [
- * {
- * text: 'Toggle docked',
- * handler: function() {
- * var toolbar = Ext.ComponentQuery.query('toolbar')[0],
- * newDocked = (toolbar.getDocked() === 'top') ? 'bottom' : 'top';
- *
- * toolbar.setDocked(newDocked);
- * }
- * },
- * {
- * text: 'Toggle UI',
- * handler: function() {
- * var toolbar = Ext.ComponentQuery.query('toolbar')[0],
- * newUi = (toolbar.getUi() === 'light') ? 'dark' : 'light';
- *
- * toolbar.setUi(newUi);
- * }
- * },
- * {
- * text: 'Change title',
- * handler: function() {
- * var toolbar = Ext.ComponentQuery.query('toolbar')[0],
- * titles = ['My Toolbar', 'Ext.Toolbar', 'Configurations are awesome!', 'Beautiful.'],
- //internally, the title configuration gets converted into a {@link Ext.Title} component,
- //so you must get the title configuration of that component
- * title = toolbar.getTitle().getTitle(),
- * newTitle = titles[titles.indexOf(title) + 1] || titles[0];
- *
- * toolbar.setTitle(newTitle);
- * }
- * }
- * ]
- * }
- * ]
- * });
- *
- * ## Notes
- *
- * You must use a HTML5 doctype for {@link #docked} `bottom` to work. To do this, simply add the following code to the HTML file:
- *
- * <!doctype html>
- *
- * So your index.html file should look a little like this:
- *
- * <!doctype html>
- * <html>
- * <head>
- * <title>MY application title</title>
- * ...
- *
- */
- Ext.define('Ext.Toolbar', {
- extend: 'Ext.Container',
- xtype : 'toolbar',
- requires: [
- 'Ext.Button',
- 'Ext.Title',
- 'Ext.Spacer',
- 'Ext.layout.HBox'
- ],
- // @private
- isToolbar: true,
- config: {
- /**
- * @cfg baseCls
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'toolbar',
- /**
- * @cfg {String} ui
- * 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}
- * @accessor
- */
- ui: 'dark',
- /**
- * @cfg {String/Ext.Title} title
- * The title of the toolbar.
- * @accessor
- */
- title: null,
- /**
- * @cfg {String} defaultType
- * The default xtype to create.
- * @accessor
- */
- defaultType: 'button',
- /**
- * @cfg {String} docked
- * The docked position for this {@link Ext.Toolbar}.
- * If you specify `left` or `right`, the {@link #layout} configuration will automatically change to a `vbox`. It's also
- * recommended to adjust the {@link #width} of the toolbar if you do this.
- * @accessor
- */
- /**
- * @cfg {String} minHeight
- * The minimum height height of the Toolbar.
- * @accessor
- */
- minHeight: '2.6em',
- /**
- * @cfg {Object/String} layout Configuration for this Container's layout. Example:
- *
- * Ext.create('Ext.Container', {
- * layout: {
- * type: 'hbox',
- * align: 'middle'
- * },
- * items: [
- * {
- * xtype: 'panel',
- * flex: 1,
- * style: 'background-color: red;'
- * },
- * {
- * xtype: 'panel',
- * flex: 2,
- * style: 'background-color: green'
- * }
- * ]
- * });
- *
- * See the [layouts guide](#!/guides/layouts) for more information
- *
- * __Note:__ If you set the {@link #docked} configuration to `left` or `right`, the default layout will change from the
- * `hbox` to a `vbox`.
- *
- * @accessor
- */
- layout: {
- type: 'hbox',
- align: 'center'
- }
- },
- constructor: function(config) {
- config = config || {};
- if (config.docked == "left" || config.docked == "right") {
- config.layout = {
- type: 'vbox',
- align: 'stretch'
- };
- }
- this.callParent([config]);
- },
- // @private
- applyTitle: function(title) {
- if (typeof title == 'string') {
- title = {
- title: title,
- centered: true
- };
- }
- return Ext.factory(title, Ext.Title, this.getTitle());
- },
- // @private
- updateTitle: function(newTitle, oldTitle) {
- if (newTitle) {
- this.add(newTitle);
- }
- if (oldTitle) {
- oldTitle.destroy();
- }
- },
- /**
- * Shows the title, if it exists.
- */
- showTitle: function() {
- var title = this.getTitle();
- if (title) {
- title.show();
- }
- },
- /**
- * Hides the title, if it exists.
- */
- hideTitle: function() {
- var title = this.getTitle();
- if (title) {
- title.hide();
- }
- }
- /**
- * Returns an {@link Ext.Title} component.
- * @member Ext.Toolbar
- * @method getTitle
- * @return {Ext.Title}
- */
- /**
- * Use this to update the {@link #title} configuration.
- * @member Ext.Toolbar
- * @method setTitle
- * @param {String/Ext.Title} title You can either pass a String, or a config/instance of {@link Ext.Title}.
- */
- }, function() {
- });
- /**
- * @private
- */
- Ext.define('Ext.field.Input', {
- extend: 'Ext.Component',
- xtype : 'input',
- /**
- * @event clearicontap
- * Fires whenever the clear icon is tapped.
- * @param {Ext.field.Input} this
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event masktap
- * @preventable doMaskTap
- * Fires whenever a mask is tapped.
- * @param {Ext.field.Input} this
- * @param {Ext.EventObject} e The event object.
- */
- /**
- * @event focus
- * @preventable doFocus
- * Fires whenever the input get focus.
- * @param {Ext.EventObject} e The event object.
- */
- /**
- * @event blur
- * @preventable doBlur
- * Fires whenever the input loses focus.
- * @param {Ext.EventObject} e The event object.
- */
- /**
- * @event click
- * Fires whenever the input is clicked.
- * @param {Ext.EventObject} e The event object.
- */
- /**
- * @event keyup
- * Fires whenever keyup is detected.
- * @param {Ext.EventObject} e The event object.
- */
- /**
- * @event paste
- * Fires whenever paste is detected.
- * @param {Ext.EventObject} e The event object.
- */
- /**
- * @event mousedown
- * Fires whenever the input has a mousedown occur.
- * @param {Ext.EventObject} e The event object.
- */
- /**
- * @property {String} tag The el tag.
- * @private
- */
- tag: 'input',
- cachedConfig: {
- /**
- * @cfg {String} cls The `className` to be applied to this input.
- * @accessor
- */
- cls: Ext.baseCSSPrefix + 'form-field',
- /**
- * @cfg {String} focusCls The CSS class to use when the field receives focus.
- * @accessor
- */
- focusCls: Ext.baseCSSPrefix + 'field-focus',
- // @private
- maskCls: Ext.baseCSSPrefix + 'field-mask',
- /**
- * @cfg {String/Boolean} useMask
- * `true` to use a mask on this field, or `auto` to automatically select when you should use it.
- * @private
- * @accessor
- */
- useMask: 'auto',
- /**
- * @cfg {String} type The type attribute for input fields -- e.g. radio, text, password, file (defaults
- * to 'text'). The types 'file' and 'password' must be used to render those field types currently -- there are
- * no separate Ext components for those.
- * @accessor
- */
- type: 'text',
- /**
- * @cfg {Boolean} checked `true` if the checkbox should render initially checked.
- * @accessor
- */
- checked: false
- },
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'field-input',
- /**
- * @cfg {String} name The field's HTML name attribute.
- * __Note:__ This property must be set if this field is to be automatically included with
- * {@link Ext.form.Panel#method-submit form submit()}.
- * @accessor
- */
- name: null,
- /**
- * @cfg {Mixed} value A value to initialize this field with.
- * @accessor
- */
- value: null,
- /**
- * @property {Boolean} `true` if the field currently has focus.
- * @accessor
- */
- isFocused: false,
- /**
- * @cfg {Number} tabIndex The `tabIndex` for this field.
- *
- * __Note:__ This only applies to fields that are rendered, not those which are built via `applyTo`.
- * @accessor
- */
- tabIndex: null,
- /**
- * @cfg {String} placeHolder A string value displayed in the input (if supported) when the control is empty.
- * @accessor
- */
- placeHolder: null,
- /**
- * @cfg {Number} [minValue=undefined] The minimum value that this Number field can accept (defaults to `undefined`, e.g. no minimum).
- * @accessor
- */
- minValue: null,
- /**
- * @cfg {Number} [maxValue=undefined] The maximum value that this Number field can accept (defaults to `undefined`, e.g. no maximum).
- * @accessor
- */
- maxValue: null,
- /**
- * @cfg {Number} [stepValue=undefined] The amount by which the field is incremented or decremented each time the spinner is tapped.
- * Defaults to `undefined`, which means that the field goes up or down by 1 each time the spinner is tapped.
- * @accessor
- */
- stepValue: null,
- /**
- * @cfg {Number} [maxLength=0] The maximum number of permitted input characters.
- * @accessor
- */
- maxLength: null,
- /**
- * @cfg {Boolean} [autoComplete=undefined]
- * `true` to set the field's DOM element `autocomplete` attribute to `"on"`, `false` to set to `"off"`. Defaults to `undefined`, leaving the attribute unset.
- * @accessor
- */
- autoComplete: null,
- /**
- * @cfg {Boolean} [autoCapitalize=undefined]
- * `true` to set the field's DOM element `autocapitalize` attribute to `"on"`, `false` to set to `"off"`. Defaults to `undefined`, leaving the attribute unset
- * @accessor
- */
- autoCapitalize: null,
- /**
- * `true` to set the field DOM element `autocorrect` attribute to `"on"`, `false` to set to `"off"`. Defaults to `undefined`, leaving the attribute unset.
- * @cfg {Boolean} autoCorrect
- * @accessor
- */
- autoCorrect: null,
- /**
- * @cfg {Boolean} [readOnly=undefined]
- * `true` to set the field DOM element `readonly` attribute to `"true"`. Defaults to `undefined`, leaving the attribute unset.
- * @accessor
- */
- readOnly: null,
- /**
- * @cfg {Number} [maxRows=undefined]
- * Sets the field DOM element `maxRows` attribute. Defaults to `undefined`, leaving the attribute unset.
- * @accessor
- */
- maxRows: null,
- /**
- * @cfg {String} pattern The value for the HTML5 `pattern` attribute.
- * You can use this to change which keyboard layout will be used.
- *
- * Ext.define('Ux.field.Pattern', {
- * extend : 'Ext.field.Text',
- * xtype : 'patternfield',
- *
- * config : {
- * component : {
- * pattern : '[0-9]*'
- * }
- * }
- * });
- *
- * Even though it extends {@link Ext.field.Text}, it will display the number keyboard.
- *
- * @accessor
- */
- pattern: null,
- /**
- * @cfg {Boolean} [disabled=false] `true` to disable the field.
- *
- * Be aware that conformant with the [HTML specification](http://www.w3.org/TR/html401/interact/forms.html),
- * disabled Fields will not be {@link Ext.form.Panel#method-submit submitted}.
- * @accessor
- */
- /**
- * @cfg {Mixed} startValue
- * The value that the Field had at the time it was last focused. This is the value that is passed
- * to the {@link Ext.field.Text#change} event which is fired if the value has been changed when the Field is blurred.
- *
- * __This will be `undefined` until the Field has been visited.__ Compare {@link #originalValue}.
- * @accessor
- */
- startValue: false
- },
- /**
- * @cfg {String/Number} originalValue The original value when the input is rendered.
- * @private
- */
- // @private
- getTemplate: function() {
- var items = [
- {
- reference: 'input',
- tag: this.tag
- },
- {
- reference: 'clearIcon',
- cls: 'x-clear-icon'
- }
- ];
- items.push({
- reference: 'mask',
- classList: [this.config.maskCls]
- });
- return items;
- },
- initElement: function() {
- var me = this;
- me.callParent();
- me.input.on({
- scope: me,
- keyup: 'onKeyUp',
- keydown: 'onKeyDown',
- focus: 'onFocus',
- blur: 'onBlur',
- input: 'onInput',
- paste: 'onPaste'
- });
- me.mask.on({
- tap: 'onMaskTap',
- scope: me
- });
- if (me.clearIcon) {
- me.clearIcon.on({
- tap: 'onClearIconTap',
- scope: me
- });
- }
- },
- applyUseMask: function(useMask) {
- if (useMask === 'auto') {
- useMask = Ext.os.is.iOS && Ext.os.version.lt('5');
- }
- return Boolean(useMask);
- },
- /**
- * Updates the useMask configuration
- */
- updateUseMask: function(newUseMask) {
- this.mask[newUseMask ? 'show' : 'hide']();
- },
- updatePattern : function (pattern) {
- this.updateFieldAttribute('pattern', pattern);
- },
- /**
- * Helper method to update a specified attribute on the `fieldEl`, or remove the attribute all together.
- * @private
- */
- updateFieldAttribute: function(attribute, newValue) {
- var input = this.input;
- if (newValue) {
- input.dom.setAttribute(attribute, newValue);
- } else {
- input.dom.removeAttribute(attribute);
- }
- },
- /**
- * Updates the {@link #cls} configuration.
- */
- updateCls: function(newCls, oldCls) {
- this.input.addCls(Ext.baseCSSPrefix + 'input-el');
- this.input.replaceCls(oldCls, newCls);
- },
- /**
- * Updates the type attribute with the {@link #type} configuration.
- * @private
- */
- updateType: function(newType, oldType) {
- var prefix = Ext.baseCSSPrefix + 'input-';
- this.input.replaceCls(prefix + oldType, prefix + newType);
- this.updateFieldAttribute('type', newType);
- },
- /**
- * Updates the name attribute with the {@link #name} configuration.
- * @private
- */
- updateName: function(newName) {
- this.updateFieldAttribute('name', newName);
- },
- /**
- * Returns the field data value.
- * @return {Mixed} value The field value.
- */
- getValue: function() {
- var input = this.input;
- if (input) {
- this._value = input.dom.value;
- }
- return this._value;
- },
- // @private
- applyValue: function(value) {
- return (Ext.isEmpty(value)) ? '' : value;
- },
- /**
- * Updates the {@link #value} configuration.
- * @private
- */
- updateValue: function(newValue) {
- var input = this.input;
- if (input) {
- input.dom.value = newValue;
- }
- },
- setValue: function(newValue) {
- var oldValue = this._value;
- this.updateValue(this.applyValue(newValue));
- newValue = this.getValue();
- if (String(newValue) != String(oldValue) && this.initialized) {
- this.onChange(this, newValue, oldValue);
- }
- return this;
- },
- //<debug>
- // @private
- applyTabIndex: function(tabIndex) {
- if (tabIndex !== null && typeof tabIndex != 'number') {
- throw new Error("Ext.field.Field: [applyTabIndex] trying to pass a value which is not a number");
- }
- return tabIndex;
- },
- //</debug>
- /**
- * Updates the tabIndex attribute with the {@link #tabIndex} configuration
- * @private
- */
- updateTabIndex: function(newTabIndex) {
- this.updateFieldAttribute('tabIndex', newTabIndex);
- },
- // @private
- testAutoFn: function(value) {
- return [true, 'on'].indexOf(value) !== -1;
- },
- //<debug>
- applyMaxLength: function(maxLength) {
- if (maxLength !== null && typeof maxLength != 'number') {
- throw new Error("Ext.field.Text: [applyMaxLength] trying to pass a value which is not a number");
- }
- return maxLength;
- },
- //</debug>
- /**
- * Updates the `maxlength` attribute with the {@link #maxLength} configuration.
- * @private
- */
- updateMaxLength: function(newMaxLength) {
- this.updateFieldAttribute('maxlength', newMaxLength);
- },
- /**
- * Updates the `placeholder` attribute with the {@link #placeHolder} configuration.
- * @private
- */
- updatePlaceHolder: function(newPlaceHolder) {
- this.updateFieldAttribute('placeholder', newPlaceHolder);
- },
- // @private
- applyAutoComplete: function(autoComplete) {
- return this.testAutoFn(autoComplete);
- },
- /**
- * Updates the `autocomplete` attribute with the {@link #autoComplete} configuration.
- * @private
- */
- updateAutoComplete: function(newAutoComplete) {
- var value = newAutoComplete ? 'on' : 'off';
- this.updateFieldAttribute('autocomplete', value);
- },
- // @private
- applyAutoCapitalize: function(autoCapitalize) {
- return this.testAutoFn(autoCapitalize);
- },
- /**
- * Updates the `autocapitalize` attribute with the {@link #autoCapitalize} configuration.
- * @private
- */
- updateAutoCapitalize: function(newAutoCapitalize) {
- var value = newAutoCapitalize ? 'on' : 'off';
- this.updateFieldAttribute('autocapitalize', value);
- },
- // @private
- applyAutoCorrect: function(autoCorrect) {
- return this.testAutoFn(autoCorrect);
- },
- /**
- * Updates the `autocorrect` attribute with the {@link #autoCorrect} configuration.
- * @private
- */
- updateAutoCorrect: function(newAutoCorrect) {
- var value = newAutoCorrect ? 'on' : 'off';
- this.updateFieldAttribute('autocorrect', value);
- },
- /**
- * Updates the `min` attribute with the {@link #minValue} configuration.
- * @private
- */
- updateMinValue: function(newMinValue) {
- this.updateFieldAttribute('min', newMinValue);
- },
- /**
- * Updates the `max` attribute with the {@link #maxValue} configuration.
- * @private
- */
- updateMaxValue: function(newMaxValue) {
- this.updateFieldAttribute('max', newMaxValue);
- },
- /**
- * Updates the `step` attribute with the {@link #stepValue} configuration
- * @private
- */
- updateStepValue: function(newStepValue) {
- this.updateFieldAttribute('step', newStepValue);
- },
- // @private
- checkedRe: /^(true|1|on)/i,
- /**
- * Returns the `checked` value of this field
- * @return {Mixed} value The field value
- */
- getChecked: function() {
- var el = this.input,
- checked;
- if (el) {
- checked = el.dom.checked;
- this._checked = checked;
- }
- return checked;
- },
- // @private
- applyChecked: function(checked) {
- return !!this.checkedRe.test(String(checked));
- },
- setChecked: function(newChecked) {
- this.updateChecked(this.applyChecked(newChecked));
- this._checked = newChecked;
- },
- /**
- * Updates the `autocorrect` attribute with the {@link #autoCorrect} configuration
- * @private
- */
- updateChecked: function(newChecked) {
- this.input.dom.checked = newChecked;
- },
- /**
- * Updates the `readonly` attribute with the {@link #readOnly} configuration
- * @private
- */
- updateReadOnly: function(readOnly) {
- this.updateFieldAttribute('readonly', readOnly);
- },
- //<debug>
- // @private
- applyMaxRows: function(maxRows) {
- if (maxRows !== null && typeof maxRows !== 'number') {
- throw new Error("Ext.field.Input: [applyMaxRows] trying to pass a value which is not a number");
- }
- return maxRows;
- },
- //</debug>
- updateMaxRows: function(newRows) {
- this.updateFieldAttribute('rows', newRows);
- },
- doSetDisabled: function(disabled) {
- this.callParent(arguments);
- this.input.dom.disabled = disabled;
- if (!disabled) {
- this.blur();
- }
- },
- /**
- * Returns `true` if the value of this Field has been changed from its original value.
- * Will return `false` if the field is disabled or has not been rendered yet.
- * @return {Boolean}
- */
- isDirty: function() {
- if (this.getDisabled()) {
- return false;
- }
- return String(this.getValue()) !== String(this.originalValue);
- },
- /**
- * Resets the current field value to the original value.
- */
- reset: function() {
- this.setValue(this.originalValue);
- },
- // @private
- onMaskTap: function(e) {
- this.fireAction('masktap', [this, e], 'doMaskTap');
- },
- // @private
- doMaskTap: function(me, e) {
- if (me.getDisabled()) {
- return false;
- }
- me.maskCorrectionTimer = Ext.defer(me.showMask, 1000, me);
- me.hideMask();
- },
- // @private
- showMask: function(e) {
- if (this.mask) {
- this.mask.setStyle('display', 'block');
- }
- },
- // @private
- hideMask: function(e) {
- if (this.mask) {
- this.mask.setStyle('display', 'none');
- }
- },
- /**
- * Attempts to set the field as the active input focus.
- * @return {Ext.field.Input} this
- */
- focus: function() {
- var me = this,
- el = me.input;
- if (el && el.dom.focus) {
- el.dom.focus();
- }
- return me;
- },
- /**
- * Attempts to forcefully blur input focus for the field.
- * @return {Ext.field.Input} this
- * @chainable
- */
- blur: function() {
- var me = this,
- el = this.input;
- if (el && el.dom.blur) {
- el.dom.blur();
- }
- return me;
- },
- /**
- * Attempts to forcefully select all the contents of the input field.
- * @return {Ext.field.Input} this
- * @chainable
- */
- select: function() {
- var me = this,
- el = me.input;
- if (el && el.dom.setSelectionRange) {
- el.dom.setSelectionRange(0, 9999);
- }
- return me;
- },
- onFocus: function(e) {
- this.fireAction('focus', [e], 'doFocus');
- },
- // @private
- doFocus: function(e) {
- var me = this;
- if (me.mask) {
- if (me.maskCorrectionTimer) {
- clearTimeout(me.maskCorrectionTimer);
- }
- me.hideMask();
- }
- if (!me.getIsFocused()) {
- me.setIsFocused(true);
- me.setStartValue(me.getValue());
- }
- },
- onBlur: function(e) {
- this.fireAction('blur', [e], 'doBlur');
- },
- // @private
- doBlur: function(e) {
- var me = this,
- value = me.getValue(),
- startValue = me.getStartValue();
- me.setIsFocused(false);
- if (String(value) != String(startValue)) {
- me.onChange(me, value, startValue);
- }
- me.showMask();
- },
- // @private
- onClearIconTap: function(e) {
- this.fireEvent('clearicontap', this, e);
- //focus the field after cleartap happens, but only on android.
- //this is to stop the keyboard from hiding. TOUCH-2064
- if (Ext.os.is.Android) {
- this.focus();
- }
- },
- onClick: function(e) {
- this.fireEvent('click', e);
- },
- onChange: function(me, value, startValue) {
- this.fireEvent('change', me, value, startValue);
- },
- onPaste: function(e) {
- this.fireEvent('paste', e);
- },
- onKeyUp: function(e) {
- this.fireEvent('keyup', e);
- },
- onKeyDown: function() {
- // tell the class to ignore the input event. this happens when we want to listen to the field change
- // when the input autocompletes
- this.ignoreInput = true;
- },
- onInput: function(e) {
- var me = this;
- // if we should ignore input, stop now.
- if (me.ignoreInput) {
- me.ignoreInput = false;
- return;
- }
- // set a timeout for 10ms to check if we want to stop the input event.
- // if not, then continue with the event (keyup)
- setTimeout(function() {
- if (!me.ignoreInput) {
- me.fireEvent('keyup', e);
- me.ignoreInput = false;
- }
- }, 10);
- },
- onMouseDown: function(e) {
- this.fireEvent('mousedown', e);
- }
- });
- /**
- * @aside guide forms
- *
- * Field is the base class for all form fields used in Sencha Touch. It provides a lot of shared functionality to all
- * field subclasses (for example labels, simple validation, {@link #clearIcon clearing} and tab index management), but
- * is rarely used directly. Instead, it is much more common to use one of the field subclasses:
- *
- * xtype Class
- * ---------------------------------------
- * textfield {@link Ext.field.Text}
- * numberfield {@link Ext.field.Number}
- * textareafield {@link Ext.field.TextArea}
- * hiddenfield {@link Ext.field.Hidden}
- * radiofield {@link Ext.field.Radio}
- * checkboxfield {@link Ext.field.Checkbox}
- * selectfield {@link Ext.field.Select}
- * togglefield {@link Ext.field.Toggle}
- * fieldset {@link Ext.form.FieldSet}
- *
- * Fields are normally used within the context of a form and/or fieldset. See the {@link Ext.form.Panel FormPanel}
- * and {@link Ext.form.FieldSet FieldSet} docs for examples on how to put those together, or the list of links above
- * for usage of individual field types. If you wish to create your own Field subclasses you can extend this class,
- * though it is sometimes more useful to extend {@link Ext.field.Text} as this provides additional text entry
- * functionality.
- */
- Ext.define('Ext.field.Field', {
- extend: 'Ext.Decorator',
- alternateClassName: 'Ext.form.Field',
- xtype: 'field',
- requires: [
- 'Ext.field.Input'
- ],
- /**
- * Set to `true` on all Ext.field.Field subclasses. This is used by {@link Ext.form.Panel#getValues} to determine which
- * components inside a form are fields.
- * @property isField
- * @type Boolean
- */
- isField: true,
- // @private
- isFormField: true,
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'field',
- /**
- * The label of this field
- * @cfg {String} label
- * @accessor
- */
- label: null,
- /**
- * @cfg {String} labelAlign The position to render the label relative to the field input.
- * Available options are: 'top', 'left', 'bottom' and 'right'
- * @accessor
- */
- labelAlign: 'left',
- /**
- * @cfg {Number/String} labelWidth The width to make this field's label.
- * @accessor
- */
- labelWidth: '30%',
- /**
- * @cfg {Boolean} labelWrap `true` to allow the label to wrap. If set to `false`, the label will be truncated with
- * an ellipsis.
- * @accessor
- */
- labelWrap: false,
- /**
- * @cfg {Boolean} clearIcon `true` to use a clear icon in this field.
- * @accessor
- */
- clearIcon: null,
- /**
- * @cfg {Boolean} required `true` to make this field required.
- *
- * __Note:__ this only causes a visual indication.
- *
- * Doesn't prevent user from submitting the form.
- * @accessor
- */
- required: false,
- /**
- * The label Element associated with this Field.
- *
- * __Note:__ Only available if a {@link #label} is specified.
- * @type Ext.Element
- * @property labelEl
- * @deprecated 2.0
- */
- /**
- * @cfg {String} [inputType='text'] The type attribute for input fields -- e.g. radio, text, password, file.
- * The types 'file' and 'password' must be used to render those field types currently -- there are
- * no separate Ext components for those.
- * @deprecated 2.0 Please use `input.type` instead.
- * @accessor
- */
- inputType: null,
- /**
- * @cfg {String} name The field's HTML name attribute.
- *
- * __Note:__ this property must be set if this field is to be automatically included with.
- * {@link Ext.form.Panel#method-submit form submit()}.
- * @accessor
- */
- name: null,
- /**
- * @cfg {Mixed} value A value to initialize this field with.
- * @accessor
- */
- value: null,
- /**
- * @cfg {Number} tabIndex The `tabIndex` for this field. Note this only applies to fields that are rendered,
- * not those which are built via `applyTo`.
- * @accessor
- */
- tabIndex: null
- /**
- * @cfg {Object} component The inner component for this field.
- */
- /**
- * @cfg {Boolean} fullscreen
- * @hide
- */
- },
- cachedConfig: {
- /**
- * @cfg {String} labelCls Optional CSS class to add to the Label element.
- * @accessor
- */
- labelCls: null,
- /**
- * @cfg {String} requiredCls The `className` to be applied to this Field when the {@link #required} configuration is set to `true`.
- * @accessor
- */
- requiredCls: Ext.baseCSSPrefix + 'field-required',
- /**
- * @cfg {String} inputCls CSS class to add to the input element of this fields {@link #component}
- */
- inputCls: null
- },
- /**
- * @cfg {Boolean} isFocused
- * `true` if this field is currently focused.
- * @private
- */
- getElementConfig: function() {
- var prefix = Ext.baseCSSPrefix;
- return {
- reference: 'element',
- className: 'x-container',
- children: [
- {
- reference: 'label',
- cls: prefix + 'form-label',
- children: [{
- reference: 'labelspan',
- tag: 'span'
- }]
- },
- {
- reference: 'innerElement',
- cls: prefix + 'component-outer'
- }
- ]
- };
- },
- // @private
- updateLabel: function(newLabel, oldLabel) {
- var renderElement = this.renderElement,
- prefix = Ext.baseCSSPrefix;
- if (newLabel) {
- this.labelspan.setHtml(newLabel);
- renderElement.addCls(prefix + 'field-labeled');
- } else {
- renderElement.removeCls(prefix + 'field-labeled');
- }
- },
- // @private
- updateLabelAlign: function(newLabelAlign, oldLabelAlign) {
- var renderElement = this.renderElement,
- prefix = Ext.baseCSSPrefix;
- if (newLabelAlign) {
- renderElement.addCls(prefix + 'label-align-' + newLabelAlign);
- if (newLabelAlign == "top" || newLabelAlign == "bottom") {
- this.label.setWidth('100%');
- } else {
- this.updateLabelWidth(this.getLabelWidth());
- }
- }
- if (oldLabelAlign) {
- renderElement.removeCls(prefix + 'label-align-' + oldLabelAlign);
- }
- },
- // @private
- updateLabelCls: function(newLabelCls, oldLabelCls) {
- if (newLabelCls) {
- this.label.addCls(newLabelCls);
- }
- if (oldLabelCls) {
- this.label.removeCls(oldLabelCls);
- }
- },
- // @private
- updateLabelWidth: function(newLabelWidth) {
- var labelAlign = this.getLabelAlign();
- if (newLabelWidth) {
- if (labelAlign == "top" || labelAlign == "bottom") {
- this.label.setWidth('100%');
- } else {
- this.label.setWidth(newLabelWidth);
- }
- }
- },
- // @private
- updateLabelWrap: function(newLabelWrap, oldLabelWrap) {
- var cls = Ext.baseCSSPrefix + 'form-label-nowrap';
- if (!newLabelWrap) {
- this.addCls(cls);
- } else {
- this.removeCls(cls);
- }
- },
- /**
- * Updates the {@link #required} configuration.
- * @private
- */
- updateRequired: function(newRequired) {
- this.renderElement[newRequired ? 'addCls' : 'removeCls'](this.getRequiredCls());
- },
- /**
- * Updates the {@link #required} configuration
- * @private
- */
- updateRequiredCls: function(newRequiredCls, oldRequiredCls) {
- if (this.getRequired()) {
- this.renderElement.replaceCls(oldRequiredCls, newRequiredCls);
- }
- },
- // @private
- initialize: function() {
- var me = this;
- me.callParent();
- me.doInitValue();
- },
- /**
- * @private
- */
- doInitValue: function() {
- /**
- * @property {Mixed} originalValue
- * The original value of the field as configured in the {@link #value} configuration.
- * setting is `true`.
- */
- this.originalValue = this.getInitialConfig().value;
- },
- /**
- * Resets the current field value back to the original value on this field when it was created.
- *
- * // This will create a field with an original value
- * var field = Ext.Viewport.add({
- * xtype: 'textfield',
- * value: 'first value'
- * });
- *
- * // Update the value
- * field.setValue('new value');
- *
- * // Now you can reset it back to the `first value`
- * field.reset();
- *
- * @return {Ext.field.Field} this
- */
- reset: function() {
- this.setValue(this.originalValue);
- return this;
- },
- /**
- * Returns `true` if the value of this Field has been changed from its {@link #originalValue}.
- * Will return `false` if the field is disabled or has not been rendered yet.
- *
- * @return {Boolean} `true` if this field has been changed from its original value (and
- * is not disabled), `false` otherwise.
- */
- isDirty: function() {
- return false;
- }
- }, function() {
- });
- /**
- * @aside guide forms
- *
- * The text field is the basis for most of the input fields in Sencha Touch. It provides a baseline of shared
- * functionality such as input validation, standard events, state management and look and feel. Typically we create
- * text fields inside a form, like this:
- *
- * @example
- * Ext.create('Ext.form.Panel', {
- * fullscreen: true,
- * items: [
- * {
- * xtype: 'fieldset',
- * title: 'Enter your name',
- * items: [
- * {
- * xtype: 'textfield',
- * label: 'First Name',
- * name: 'firstName'
- * },
- * {
- * xtype: 'textfield',
- * label: 'Last Name',
- * name: 'lastName'
- * }
- * ]
- * }
- * ]
- * });
- *
- * This creates two text fields inside a form. Text Fields can also be created outside of a Form, like this:
- *
- * Ext.create('Ext.field.Text', {
- * label: 'Your Name',
- * value: 'Ed Spencer'
- * });
- *
- * ## Configuring
- *
- * Text field offers several configuration options, including {@link #placeHolder}, {@link #maxLength},
- * {@link #autoComplete}, {@link #autoCapitalize} and {@link #autoCorrect}. For example, here is how we would configure
- * a text field to have a maximum length of 10 characters, with placeholder text that disappears when the field is
- * focused:
- *
- * Ext.create('Ext.field.Text', {
- * label: 'Username',
- * maxLength: 10,
- * placeHolder: 'Enter your username'
- * });
- *
- * The autoComplete, autoCapitalize and autoCorrect configs simply set those attributes on the text field and allow the
- * native browser to provide those capabilities. For example, to enable auto complete and auto correct, simply
- * configure your text field like this:
- *
- * Ext.create('Ext.field.Text', {
- * label: 'Username',
- * autoComplete: true,
- * autoCorrect: true
- * });
- *
- * These configurations will be picked up by the native browser, which will enable the options at the OS level.
- *
- * Text field inherits from {@link Ext.field.Field}, which is the base class for all fields in Sencha Touch and provides
- * a lot of shared functionality for all fields, including setting values, clearing and basic validation. See the
- * {@link Ext.field.Field} documentation to see how to leverage its capabilities.
- */
- Ext.define('Ext.field.Text', {
- extend: 'Ext.field.Field',
- xtype: 'textfield',
- alternateClassName: 'Ext.form.Text',
- /**
- * @event focus
- * Fires when this field receives input focus
- * @param {Ext.field.Text} this This field
- * @param {Ext.event.Event} e
- */
- /**
- * @event blur
- * Fires when this field loses input focus
- * @param {Ext.field.Text} this This field
- * @param {Ext.event.Event} e
- */
- /**
- * @event paste
- * Fires when this field is pasted.
- * @param {Ext.field.Text} this This field
- * @param {Ext.event.Event} e
- */
- /**
- * @event mousedown
- * Fires when this field receives a mousedown
- * @param {Ext.field.Text} this This field
- * @param {Ext.event.Event} e
- */
- /**
- * @event keyup
- * @preventable doKeyUp
- * Fires when a key is released on the input element
- * @param {Ext.field.Text} this This field
- * @param {Ext.event.Event} e
- */
- /**
- * @event clearicontap
- * @preventable doClearIconTap
- * Fires when the clear icon is tapped
- * @param {Ext.field.Text} this This field
- * @param {Ext.event.Event} e
- */
- /**
- * @event change
- * Fires just before the field blurs if the field value has changed
- * @param {Ext.field.Text} this This field
- * @param {Mixed} newValue The new value
- * @param {Mixed} oldValue The original value
- */
- /**
- * @event action
- * @preventable doAction
- * Fires whenever the return key or go is pressed. FormPanel listeners
- * for this event, and submits itself whenever it fires. Also note
- * that this event bubbles up to parent containers.
- * @param {Ext.field.Text} this This field
- * @param {Mixed} e The key event object
- */
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- ui: 'text',
- /**
- * @cfg
- * @inheritdoc
- */
- clearIcon: true,
- /**
- * @cfg {String} placeHolder A string value displayed in the input (if supported) when the control is empty.
- * @accessor
- */
- placeHolder: null,
- /**
- * @cfg {Number} maxLength The maximum number of permitted input characters.
- * @accessor
- */
- maxLength: null,
- /**
- * True to set the field's DOM element autocomplete attribute to "on", false to set to "off".
- * @cfg {Boolean} autoComplete
- * @accessor
- */
- autoComplete: null,
- /**
- * True to set the field's DOM element autocapitalize attribute to "on", false to set to "off".
- * @cfg {Boolean} autoCapitalize
- * @accessor
- */
- autoCapitalize: null,
- /**
- * True to set the field DOM element autocorrect attribute to "on", false to set to "off".
- * @cfg {Boolean} autoCorrect
- * @accessor
- */
- autoCorrect: null,
- /**
- * True to set the field DOM element readonly attribute to true.
- * @cfg {Boolean} readOnly
- * @accessor
- */
- readOnly: null,
- /**
- * @cfg {Object} component The inner component for this field, which defaults to an input text.
- * @accessor
- */
- component: {
- xtype: 'input',
- type : 'text'
- },
- bubbleEvents: ['action']
- },
- // @private
- initialize: function() {
- var me = this;
- me.callParent();
- me.getComponent().on({
- scope: this,
- keyup : 'onKeyUp',
- change : 'onChange',
- focus : 'onFocus',
- blur : 'onBlur',
- paste : 'onPaste',
- mousedown : 'onMouseDown',
- clearicontap: 'onClearIconTap'
- });
- // set the originalValue of the textfield, if one exists
- me.originalValue = me.originalValue || "";
- me.getComponent().originalValue = me.originalValue;
- me.syncEmptyCls();
- },
- syncEmptyCls: function() {
- var empty = (this._value) ? this._value.length : false,
- cls = Ext.baseCSSPrefix + 'empty';
- if (empty) {
- this.removeCls(cls);
- } else {
- this.addCls(cls);
- }
- },
- // @private
- updateValue: function(newValue) {
- var component = this.getComponent(),
- // allows newValue to be zero but not undefined, null or an empty string (other falsey values)
- valueValid = newValue !== undefined && newValue !== null && newValue !== '';
- if (component) {
- component.setValue(newValue);
- }
- this[valueValid ? 'showClearIcon' : 'hideClearIcon']();
- this.syncEmptyCls();
- },
- getValue: function() {
- var me = this;
- me._value = me.getComponent().getValue();
- me.syncEmptyCls();
- return me._value;
- },
- // @private
- updatePlaceHolder: function(newPlaceHolder) {
- this.getComponent().setPlaceHolder(newPlaceHolder);
- },
- // @private
- updateMaxLength: function(newMaxLength) {
- this.getComponent().setMaxLength(newMaxLength);
- },
- // @private
- updateAutoComplete: function(newAutoComplete) {
- this.getComponent().setAutoComplete(newAutoComplete);
- },
- // @private
- updateAutoCapitalize: function(newAutoCapitalize) {
- this.getComponent().setAutoCapitalize(newAutoCapitalize);
- },
- // @private
- updateAutoCorrect: function(newAutoCorrect) {
- this.getComponent().setAutoCorrect(newAutoCorrect);
- },
- // @private
- updateReadOnly: function(newReadOnly) {
- if (newReadOnly) {
- this.hideClearIcon();
- } else {
- this.showClearIcon();
- }
- this.getComponent().setReadOnly(newReadOnly);
- },
- // @private
- updateInputType: function(newInputType) {
- var component = this.getComponent();
- if (component) {
- component.setType(newInputType);
- }
- },
- // @private
- updateName: function(newName) {
- var component = this.getComponent();
- if (component) {
- component.setName(newName);
- }
- },
- // @private
- updateTabIndex: function(newTabIndex) {
- var component = this.getComponent();
- if (component) {
- component.setTabIndex(newTabIndex);
- }
- },
- /**
- * Updates the {@link #inputCls} configuration on this fields {@link #component}
- * @private
- */
- updateInputCls: function(newInputCls, oldInputCls) {
- var component = this.getComponent();
- if (component) {
- component.replaceCls(oldInputCls, newInputCls);
- }
- },
- doSetDisabled: function(disabled) {
- var me = this;
- me.callParent(arguments);
- var component = me.getComponent();
- if (component) {
- component.setDisabled(disabled);
- }
- if (disabled) {
- me.hideClearIcon();
- } else {
- me.showClearIcon();
- }
- },
- // @private
- showClearIcon: function() {
- var me = this,
- value = me.getValue(),
- // allows value to be zero but not undefined, null or an empty string (other falsey values)
- valueValid = value !== undefined && value !== null && value !== '';
- if (me.getClearIcon() && !me.getDisabled() && !me.getReadOnly() && valueValid) {
- me.element.addCls(Ext.baseCSSPrefix + 'field-clearable');
- }
- return me;
- },
- // @private
- hideClearIcon: function() {
- if (this.getClearIcon()) {
- this.element.removeCls(Ext.baseCSSPrefix + 'field-clearable');
- }
- },
- onKeyUp: function(e) {
- this.fireAction('keyup', [this, e], 'doKeyUp');
- },
- /**
- * Called when a key has been pressed in the `<input>`
- * @private
- */
- doKeyUp: function(me, e) {
- // getValue to ensure that we are in sync with the dom
- var value = me.getValue(),
- // allows value to be zero but not undefined, null or an empty string (other falsey values)
- valueValid = value !== undefined && value !== null && value !== '';
- this[valueValid ? 'showClearIcon' : 'hideClearIcon']();
- if (e.browserEvent.keyCode === 13) {
- me.fireAction('action', [me, e], 'doAction');
- }
- },
- doAction: function() {
- this.blur();
- },
- onClearIconTap: function(e) {
- this.fireAction('clearicontap', [this, e], 'doClearIconTap');
- },
- // @private
- doClearIconTap: function(me, e) {
- me.setValue('');
- //sync with the input
- me.getValue();
- },
- onChange: function(me, value, startValue) {
- me.fireEvent('change', this, value, startValue);
- },
- onFocus: function(e) {
- this.isFocused = true;
- this.fireEvent('focus', this, e);
- },
- onBlur: function(e) {
- var me = this;
- this.isFocused = false;
- me.fireEvent('blur', me, e);
- setTimeout(function() {
- me.isFocused = false;
- }, 50);
- },
- onPaste: function(e) {
- this.fireEvent('paste', this, e);
- },
- onMouseDown: function(e) {
- this.fireEvent('mousedown', this, e);
- },
- /**
- * Attempts to set the field as the active input focus.
- * @return {Ext.field.Text} This field
- */
- focus: function() {
- this.getComponent().focus();
- return this;
- },
- /**
- * Attempts to forcefully blur input focus for the field.
- * @return {Ext.field.Text} This field
- */
- blur: function() {
- this.getComponent().blur();
- return this;
- },
- /**
- * Attempts to forcefully select all the contents of the input field.
- * @return {Ext.field.Text} this
- */
- select: function() {
- this.getComponent().select();
- return this;
- },
- reset: function() {
- this.getComponent().reset();
- //we need to call this to sync the input with this field
- this.getValue();
- this[this._value ? 'showClearIcon' : 'hideClearIcon']();
- },
- isDirty: function() {
- var component = this.getComponent();
- if (component) {
- return component.isDirty();
- }
- return false;
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.field.TextAreaInput', {
- extend: 'Ext.field.Input',
- xtype : 'textareainput',
- tag: 'textarea'
- });
- /**
- * @aside guide forms
- *
- * Creates an HTML textarea field on the page. This is useful whenever you need the user to enter large amounts of text
- * (i.e. more than a few words). Typically, text entry on mobile devices is not a pleasant experience for the user so
- * it's good to limit your use of text areas to only those occasions when free form text is required or alternative
- * input methods like select boxes or radio buttons are not possible. Text Areas are usually created inside forms, like
- * this:
- *
- * @example
- * Ext.create('Ext.form.Panel', {
- * fullscreen: true,
- * items: [
- * {
- * xtype: 'fieldset',
- * title: 'About you',
- * items: [
- * {
- * xtype: 'textfield',
- * label: 'Name',
- * name: 'name'
- * },
- * {
- * xtype: 'textareafield',
- * label: 'Bio',
- * maxRows: 4,
- * name: 'bio'
- * }
- * ]
- * }
- * ]
- * });
- *
- * 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
- * area for their bio. We used the {@link #maxRows} configuration on the text area to tell it to grow to a maximum of 4
- * rows of text before it starts using a scroll bar inside the text area to scroll the text.
- *
- * We can also create a text area outside the context of a form, like this:
- *
- * This creates two text fields inside a form. Text Fields can also be created outside of a Form, like this:
- *
- * Ext.create('Ext.field.TextArea', {
- * label: 'About You',
- * {@link #placeHolder}: 'Tell us about yourself...'
- * });
- */
- Ext.define('Ext.field.TextArea', {
- extend: 'Ext.field.Text',
- xtype: 'textareafield',
- requires: ['Ext.field.TextAreaInput'],
- alternateClassName: 'Ext.form.TextArea',
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- ui: 'textarea',
- /**
- * @cfg
- * @inheritdoc
- */
- autoCapitalize: false,
- /**
- * @cfg
- * @inheritdoc
- */
- component: {
- xtype: 'textareainput'
- },
- /**
- * @cfg {Number} maxRows The maximum number of lines made visible by the input.
- * @accessor
- */
- maxRows: null
- },
- // @private
- updateMaxRows: function(newRows) {
- this.getComponent().setMaxRows(newRows);
- },
- doSetHeight: function(newHeight) {
- this.callParent(arguments);
- var component = this.getComponent();
- component.input.setHeight(newHeight);
- },
- doSetWidth: function(newWidth) {
- this.callParent(arguments);
- var component = this.getComponent();
- component.input.setWidth(newWidth);
- },
- /**
- * Called when a key has been pressed in the `<input>`
- * @private
- */
- doKeyUp: function(me) {
- // getValue to ensure that we are in sync with the dom
- var value = me.getValue();
- // show the {@link #clearIcon} if it is being used
- me[value ? 'showClearIcon' : 'hideClearIcon']();
- }
- });
- /**
- * Utility class for generating different styles of message boxes. The framework provides a global singleton
- * {@link Ext.Msg} for common usage which you should use in most cases.
- *
- * If you want to use {@link Ext.MessageBox} directly, just think of it as a modal {@link Ext.Container}.
- *
- * Note that the MessageBox is asynchronous. Unlike a regular JavaScript `alert` (which will halt browser execution),
- * showing a MessageBox will not cause the code to stop. For this reason, if you have code that should only run _after_
- * some user feedback from the MessageBox, you must use a callback function (see the `fn` configuration option parameter
- * for the {@link #method-show show} method for more details).
- *
- * @example preview
- * Ext.Msg.alert('Title', 'The quick brown fox jumped over the lazy dog.', Ext.emptyFn);
- *
- * Checkout {@link Ext.Msg} for more examples.
- *
- */
- Ext.define('Ext.MessageBox', {
- extend : 'Ext.Sheet',
- requires: [
- 'Ext.Toolbar',
- 'Ext.field.Text',
- 'Ext.field.TextArea'
- ],
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- ui: 'dark',
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'msgbox',
- /**
- * @cfg {String} iconCls
- * CSS class for the icon. The icon should be 40px x 40px.
- * @accessor
- */
- iconCls: null,
- /**
- * @cfg
- * @inheritdoc
- */
- showAnimation: {
- type: 'popIn',
- duration: 250,
- easing: 'ease-out'
- },
- /**
- * @cfg
- * @inheritdoc
- */
- hideAnimation: {
- type: 'popOut',
- duration: 250,
- easing: 'ease-out'
- },
- /**
- * Override the default `zIndex` so it is normally always above floating components.
- */
- zIndex: 999,
- /**
- * @cfg {Number} defaultTextHeight
- * The default height in pixels of the message box's multiline textarea if displayed.
- * @accessor
- */
- defaultTextHeight: 75,
- /**
- * @cfg {String} title
- * The title of this {@link Ext.MessageBox}.
- * @accessor
- */
- title: null,
- /**
- * @cfg {Array/Object} buttons
- * An array of buttons, or an object of a button to be displayed in the toolbar of this {@link Ext.MessageBox}.
- */
- buttons: null,
- /**
- * @cfg {String} message
- * The message to be displayed in the {@link Ext.MessageBox}.
- * @accessor
- */
- message: null,
- /**
- * @cfg {String} msg
- * The message to be displayed in the {@link Ext.MessageBox}.
- * @removed 2.0.0 Please use {@link #message} instead.
- */
- /**
- * @cfg {Object} prompt
- * The configuration to be passed if you want an {@link Ext.field.Text} or {@link Ext.field.TextArea} field
- * in your {@link Ext.MessageBox}.
- *
- * Pass an object with the property `multiLine` with a value of `true`, if you want the prompt to use a TextArea.
- *
- * Alternatively, you can just pass in an object which has an xtype/xclass of another component.
- *
- * prompt: {
- * xtype: 'textareafield',
- * value: 'test'
- * }
- *
- * @accessor
- */
- prompt: null,
- /**
- * @private
- */
- modal: true,
- /**
- * @cfg
- * @inheritdoc
- */
- layout: {
- type: 'vbox',
- pack: 'center'
- }
- },
- statics: {
- OK : {text: 'OK', itemId: 'ok', ui: 'action'},
- YES : {text: 'Yes', itemId: 'yes', ui: 'action'},
- NO : {text: 'No', itemId: 'no'},
- CANCEL: {text: 'Cancel', itemId: 'cancel'},
- INFO : Ext.baseCSSPrefix + 'msgbox-info',
- WARNING : Ext.baseCSSPrefix + 'msgbox-warning',
- QUESTION: Ext.baseCSSPrefix + 'msgbox-question',
- ERROR : Ext.baseCSSPrefix + 'msgbox-error',
- OKCANCEL: [
- {text: 'Cancel', itemId: 'cancel'},
- {text: 'OK', itemId: 'ok', ui : 'action'}
- ],
- YESNOCANCEL: [
- {text: 'Cancel', itemId: 'cancel'},
- {text: 'No', itemId: 'no'},
- {text: 'Yes', itemId: 'yes', ui: 'action'}
- ],
- YESNO: [
- {text: 'No', itemId: 'no'},
- {text: 'Yes', itemId: 'yes', ui: 'action'}
- ]
- },
- constructor: function(config) {
- config = config || {};
- if (config.hasOwnProperty('promptConfig')) {
- //<debug warn>
- Ext.Logger.deprecate("'promptConfig' config is deprecated, please use 'prompt' config instead", this);
- //</debug>
- Ext.applyIf(config, {
- prompt: config.promptConfig
- });
- delete config.promptConfig;
- }
- if (config.hasOwnProperty('multiline') || config.hasOwnProperty('multiLine')) {
- config.prompt = config.prompt || {};
- Ext.applyIf(config.prompt, {
- multiLine: config.multiline || config.multiLine
- });
- delete config.multiline;
- delete config.multiLine;
- }
- this.defaultAllowedConfig = {};
- var allowedConfigs = ['ui', 'showAnimation', 'hideAnimation', 'title', 'message', 'prompt', 'iconCls', 'buttons', 'defaultTextHeight'],
- ln = allowedConfigs.length,
- i, allowedConfig;
- for (i = 0; i < ln; i++) {
- allowedConfig = allowedConfigs[i];
- this.defaultAllowedConfig[allowedConfig] = this.defaultConfig[allowedConfig];
- }
- this.callParent([config]);
- },
- /**
- * Creates a new {@link Ext.Toolbar} instance using {@link Ext#factory}.
- * @private
- */
- applyTitle: function(config) {
- if (typeof config == "string") {
- config = {
- title: config
- };
- }
- Ext.applyIf(config, {
- docked: 'top',
- minHeight: '1.3em',
- cls : this.getBaseCls() + '-title'
- });
- return Ext.factory(config, Ext.Toolbar, this.getTitle());
- },
- /**
- * Adds the new {@link Ext.Toolbar} instance into this container.
- * @private
- */
- updateTitle: function(newTitle) {
- if (newTitle) {
- this.add(newTitle);
- }
- },
- /**
- * Adds the new {@link Ext.Toolbar} instance into this container.
- * @private
- */
- updateButtons: function(newButtons) {
- var me = this;
- if (newButtons) {
- if (me.buttonsToolbar) {
- me.buttonsToolbar.removeAll();
- me.buttonsToolbar.setItems(newButtons);
- } else {
- me.buttonsToolbar = Ext.create('Ext.Toolbar', {
- docked : 'bottom',
- defaultType: 'button',
- layout : {
- type: 'hbox',
- pack: 'center'
- },
- ui : me.getUi(),
- cls : me.getBaseCls() + '-buttons',
- items : newButtons
- });
- me.add(me.buttonsToolbar);
- }
- }
- },
- /**
- * @private
- */
- applyMessage: function(config) {
- config = {
- html : config,
- cls : this.getBaseCls() + '-text'
- };
- return Ext.factory(config, Ext.Component, this._message);
- },
- /**
- * @private
- */
- updateMessage: function(newMessage) {
- if (newMessage) {
- this.add(newMessage);
- }
- },
- getMessage: function() {
- if (this._message) {
- return this._message.getHtml();
- }
- return null;
- },
- /**
- * @private
- */
- applyIconCls: function(config) {
- config = {
- xtype : 'component',
- docked: 'left',
- width : 40,
- height: 40,
- baseCls: Ext.baseCSSPrefix + 'icon',
- hidden: (config) ? false : true,
- cls: config
- };
- return Ext.factory(config, Ext.Component, this._iconCls);
- },
- /**
- * @private
- */
- updateIconCls: function(newIconCls, oldIconCls) {
- var me = this;
- //ensure the title and button elements are added first
- this.getTitle();
- this.getButtons();
- if (newIconCls) {
- this.add(newIconCls);
- } else {
- this.remove(oldIconCls);
- }
- },
- getIconCls: function() {
- var icon = this._iconCls,
- iconCls;
- if (icon) {
- iconCls = icon.getCls();
- return (iconCls) ? iconCls[0] : null;
- }
- return null;
- },
- /**
- * @private
- */
- applyPrompt: function(prompt) {
- if (prompt) {
- var config = {
- label: false
- };
- if (Ext.isObject(prompt)) {
- Ext.apply(config, prompt);
- }
- if (config.multiLine) {
- config.height = Ext.isNumber(config.multiLine) ? parseFloat(config.multiLine) : this.getDefaultTextHeight();
- return Ext.factory(config, Ext.field.TextArea, this.getPrompt());
- } else {
- return Ext.factory(config, Ext.field.Text, this.getPrompt());
- }
- }
- return prompt;
- },
- /**
- * @private
- */
- updatePrompt: function(newPrompt, oldPrompt) {
- if (newPrompt) {
- this.add(newPrompt);
- }
- if (oldPrompt) {
- this.remove(oldPrompt);
- }
- },
- // @private
- // pass `fn` config to show method instead
- onClick: function(button) {
- if (button) {
- var config = button.config.userConfig || {},
- initialConfig = button.getInitialConfig(),
- prompt = this.getPrompt();
- if (typeof config.fn == 'function') {
- this.on({
- hiddenchange: function() {
- config.fn.call(
- config.scope || null,
- initialConfig.itemId || initialConfig.text,
- prompt ? prompt.getValue() : null,
- config
- );
- },
- single: true,
- scope: this
- });
- }
- if (config.input) {
- config.input.dom.blur();
- }
- }
- this.hide();
- },
- /**
- * Displays the {@link Ext.MessageBox} with a specified configuration. All
- * display functions (e.g. {@link #method-prompt}, {@link #alert}, {@link #confirm})
- * on MessageBox call this function internally, although those calls
- * are basic shortcuts and do not support all of the config options allowed here.
- *
- * Example usage:
- *
- * @example
- * Ext.Msg.show({
- * title: 'Address',
- * message: 'Please enter your address:',
- * width: 300,
- * buttons: Ext.MessageBox.OKCANCEL,
- * multiLine: true,
- * prompt : { maxlength : 180, autocapitalize : true },
- * fn: function(buttonId) {
- * alert('You pressed the "' + buttonId + '" button.');
- * }
- * });
- *
- * @param {Object} config An object with the following config options:
- *
- * @param {Object/Array} [config.buttons=false]
- * A button config object or Array of the same(e.g., `Ext.MessageBox.OKCANCEL` or `{text:'Foo', itemId:'cancel'}`),
- * or false to not show any buttons.
- *
- * @param {String} config.cls
- * A custom CSS class to apply to the message box's container element.
- *
- * @param {Function} config.fn
- * A callback function which is called when the dialog is dismissed by clicking on the configured buttons.
- *
- * @param {String} config.fn.buttonId The `itemId` of the button pressed, one of: 'ok', 'yes', 'no', 'cancel'.
- * @param {String} config.fn.value Value of the input field if either `prompt` or `multiline` option is `true`.
- * @param {Object} config.fn.opt The config object passed to show.
- *
- * @param {Number} [config.width=auto]
- * A fixed width for the MessageBox.
- *
- * @param {Number} [config.height=auto]
- * A fixed height for the MessageBox.
- *
- * @param {Object} config.scope
- * The scope of the callback function
- *
- * @param {String} config.icon
- * A CSS class that provides a background image to be used as the body icon for the dialog
- * (e.g. Ext.MessageBox.WARNING or 'custom-class').
- *
- * @param {Boolean} [config.modal=true]
- * `false` to allow user interaction with the page while the message box is displayed.
- *
- * @param {String} [config.message= ]
- * A string that will replace the existing message box body text.
- * Defaults to the XHTML-compliant non-breaking space character ` `.
- *
- * @param {Number} [config.defaultTextHeight=75]
- * The default height in pixels of the message box's multiline textarea if displayed.
- *
- * @param {Boolean} [config.prompt=false]
- * `true` to prompt the user to enter single-line text. Please view the {@link Ext.MessageBox#method-prompt} documentation in {@link Ext.MessageBox}.
- * for more information.
- *
- * @param {Boolean} [config.multiline=false]
- * `true` to prompt the user to enter multi-line text.
- *
- * @param {String} config.title
- * The title text.
- *
- * @param {String} config.value
- * The string value to set into the active textbox element if displayed.
- *
- * @return {Ext.MessageBox} this
- */
- show: function(initialConfig) {
- //if it has not been added to a container, add it to the Viewport.
- if (!this.getParent() && Ext.Viewport) {
- Ext.Viewport.add(this);
- }
- if (!initialConfig) {
- return this.callParent();
- }
- var config = Ext.Object.merge({}, {
- value: ''
- }, initialConfig);
- var buttons = initialConfig.buttons || Ext.MessageBox.OK || [],
- buttonBarItems = [],
- userConfig = initialConfig;
- Ext.each(buttons, function(buttonConfig) {
- if (!buttonConfig) {
- return;
- }
- buttonBarItems.push(Ext.apply({
- userConfig: userConfig,
- scope : this,
- handler : 'onClick'
- }, buttonConfig));
- }, this);
- config.buttons = buttonBarItems;
- if (config.promptConfig) {
- //<debug warn>
- Ext.Logger.deprecate("'promptConfig' config is deprecated, please use 'prompt' config instead", this);
- //</debug>
- }
- config.prompt = (config.promptConfig || config.prompt) || null;
- if (config.multiLine) {
- config.prompt = config.prompt || {};
- config.prompt.multiLine = config.multiLine;
- delete config.multiLine;
- }
- config = Ext.merge({}, this.defaultAllowedConfig, config);
- this.setConfig(config);
- var prompt = this.getPrompt();
- if (prompt) {
- prompt.setValue(initialConfig.value || '');
- }
- this.callParent();
- return this;
- },
- /**
- * Displays a standard read-only message box with an OK button (comparable to the basic JavaScript alert prompt). If
- * a callback function is passed it will be called after the user clicks the button, and the `itemId` of the button
- * that was clicked will be passed as the only parameter to the callback.
- *
- * @param {String} title The title bar text.
- * @param {String} message The message box body text.
- * @param {Function} fn A callback function which is called when the dialog is dismissed by clicking on the configured buttons.
- * @param {String} fn.buttonId The `itemId` of the button pressed, one of: 'ok', 'yes', 'no', 'cancel'.
- * @param {String} fn.value Value of the input field if either `prompt` or `multiLine` option is `true`.
- * @param {Object} fn.opt The config object passed to show.
- * @param {Object} scope The scope (`this` reference) in which the callback is executed.
- * Defaults to: the browser window
- *
- * @return {Ext.MessageBox} this
- */
- alert: function(title, message, fn, scope) {
- return this.show({
- title: title || null,
- message: message || null,
- buttons: Ext.MessageBox.OK,
- promptConfig: false,
- fn: function() {
- if (fn) {
- fn.apply(scope, arguments);
- }
- },
- scope: scope
- });
- },
- /**
- * Displays a confirmation message box with Yes and No buttons (comparable to JavaScript's confirm). If a callback
- * function is passed it will be called after the user clicks either button, and the id of the button that was
- * clicked will be passed as the only parameter to the callback (could also be the top-right close button).
- *
- * @param {String} title The title bar text.
- * @param {String} message The message box body text.
- * @param {Function} fn A callback function which is called when the dialog is dismissed by clicking on the configured buttons.
- * @param {String} fn.buttonId The `itemId` of the button pressed, one of: 'ok', 'yes', 'no', 'cancel'.
- * @param {String} fn.value Value of the input field if either `prompt` or `multiLine` option is `true`.
- * @param {Object} fn.opt The config object passed to show.
- * @param {Object} [scope] The scope (`this` reference) in which the callback is executed.
- *
- * Defaults to: the browser window
- *
- * @return {Ext.MessageBox} this
- */
- confirm: function(title, message, fn, scope) {
- return this.show({
- title : title || null,
- message : message || null,
- buttons : Ext.MessageBox.YESNO,
- promptConfig: false,
- scope : scope,
- fn: function() {
- if (fn) {
- fn.apply(scope, arguments);
- }
- }
- });
- },
- /**
- * Displays a message box with OK and Cancel buttons prompting the user to enter some text (comparable to
- * JavaScript's prompt). The prompt can be a single-line or multi-line textbox. If a callback function is passed it
- * will be called after the user clicks either button, and the id of the button that was clicked (could also be the
- * top-right close button) and the text that was entered will be passed as the two parameters to the callback.
- *
- * Example usage:
- *
- * @example
- * Ext.Msg.prompt(
- * 'Welcome!',
- * 'What\'s your name going to be today?',
- * function (buttonId, value) {
- * console.log(value);
- * },
- * null,
- * false,
- * null,
- * {
- * autoCapitalize: true,
- * placeHolder: 'First-name please...'
- * }
- * );
- *
- * @param {String} title The title bar text.
- * @param {String} message The message box body text.
- * @param {Function} fn A callback function which is called when the dialog is dismissed by clicking on the configured buttons.
- * @param {String} fn.buttonId The `itemId` of the button pressed, one of: 'ok', 'yes', 'no', 'cancel'.
- * @param {String} fn.value Value of the input field if either `prompt` or `multiLine` option is `true`.
- * @param {Object} fn.opt The config object passed to show.
- * @param {Object} scope The scope (`this` reference) in which the callback is executed.
- *
- * Defaults to: the browser window.
- *
- * @param {Boolean/Number} [multiLine=false] `true` to create a multiline textbox using the `defaultTextHeight` property,
- * or the height in pixels to create the textbox.
- *
- * @param {String} [value] Default value of the text input element.
- *
- * @param {Object} [prompt=true]
- * The configuration for the prompt. See the {@link Ext.MessageBox#cfg-prompt prompt} documentation in {@link Ext.MessageBox}
- * for more information.
- *
- * @return {Ext.MessageBox} this
- */
- prompt: function(title, message, fn, scope, multiLine, value, prompt) {
- return this.show({
- title : title || null,
- message : message || null,
- buttons : Ext.MessageBox.OKCANCEL,
- scope : scope,
- prompt : prompt || true,
- multiLine: multiLine,
- value : value,
- fn: function() {
- if (fn) {
- fn.apply(scope, arguments);
- }
- }
- });
- }
- }, function(MessageBox) {
- Ext.onSetup(function() {
- /**
- * @class Ext.Msg
- * @extends Ext.MessageBox
- * @singleton
- *
- * A global shared singleton instance of the {@link Ext.MessageBox} class.
- *
- * Allows for simple creation of various different alerts and notifications.
- *
- * To change any configurations on this singleton instance, you must change the
- * `defaultAllowedConfig` object. For example to remove all animations on `Msg`:
- *
- * Ext.Msg.defaultAllowedConfig.showAnimation = false;
- * Ext.Msg.defaultAllowedConfig.hideAnimation = false;
- *
- * ## Examples
- *
- * ### Alert
- * Use the {@link #alert} method to show a basic alert:
- *
- * @example preview
- * Ext.Msg.alert('Title', 'The quick brown fox jumped over the lazy dog.', Ext.emptyFn);
- *
- * ### Prompt
- * Use the {@link #method-prompt} method to show an alert which has a textfield:
- *
- * @example preview
- * Ext.Msg.prompt('Name', 'Please enter your name:', function(text) {
- * // process text value and close...
- * });
- *
- * ### Confirm
- * Use the {@link #confirm} method to show a confirmation alert (shows yes and no buttons).
- *
- * @example preview
- * Ext.Msg.confirm("Confirmation", "Are you sure you want to do that?", Ext.emptyFn);
- */
- Ext.Msg = new MessageBox;
- });
- });
- /**
- * SegmentedButton is a container for a group of {@link Ext.Button}s. Generally a SegmentedButton would be
- * a child of a {@link Ext.Toolbar} and would be used to switch between different views.
- *
- * ## Example usage:
- *
- * @example
- * var segmentedButton = Ext.create('Ext.SegmentedButton', {
- * allowMultiple: true,
- * items: [
- * {
- * text: 'Option 1'
- * },
- * {
- * text: 'Option 2',
- * pressed: true
- * },
- * {
- * text: 'Option 3'
- * }
- * ],
- * listeners: {
- * toggle: function(container, button, pressed){
- * alert("User toggled the '" + button.getText() + "' button: " + (pressed ? 'on' : 'off'));
- * }
- * }
- * });
- * Ext.Viewport.add({ xtype: 'container', padding: 10, items: [segmentedButton] });
- */
- Ext.define('Ext.SegmentedButton', {
- extend: 'Ext.Container',
- xtype : 'segmentedbutton',
- requires: ['Ext.Button'],
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'segmentedbutton',
- /**
- * @cfg {String} pressedCls
- * CSS class when a button is in pressed state.
- * @accessor
- */
- pressedCls: Ext.baseCSSPrefix + 'button-pressed',
- /**
- * @cfg {Boolean} allowMultiple
- * Allow multiple pressed buttons.
- * @accessor
- */
- allowMultiple: false,
- /**
- * @cfg {Boolean} allowDepress
- * Allow toggling the pressed state of each button.
- * Defaults to `true` when {@link #allowMultiple} is `true`.
- * @accessor
- */
- allowDepress: false,
- /**
- * @cfg {Boolean} allowToggle Allow child buttons to be pressed when tapped on. Set to `false` to allow tapping but not toggling of the buttons.
- * @accessor
- */
- allowToggle: true,
- /**
- * @cfg {Array} pressedButtons
- * The pressed buttons for this segmented button.
- *
- * You can remove all pressed buttons by calling {@link #setPressedButtons} with an empty array.
- * @accessor
- */
- pressedButtons: [],
- /**
- * @cfg
- * @inheritdoc
- */
- layout: {
- type : 'hbox',
- align: 'stretch'
- },
- /**
- * @cfg
- * @inheritdoc
- */
- defaultType: 'button'
- },
- /**
- * @event toggle
- * Fires when any child button's pressed state has changed.
- * @param {Ext.SegmentedButton} this
- * @param {Ext.Button} button The toggled button.
- * @param {Boolean} isPressed Boolean to indicate if the button was pressed or not.
- */
- initialize: function() {
- var me = this;
- me.callParent();
- me.on({
- delegate: '> button',
- scope : me,
- tap: 'onButtonRelease'
- });
- me.onAfter({
- delegate: '> button',
- scope : me,
- hiddenchange: 'onButtonHiddenChange'
- });
- },
- updateAllowMultiple: function(allowMultiple) {
- if (!this.initialized && !this.getInitialConfig().hasOwnProperty('allowDepress') && allowMultiple) {
- this.setAllowDepress(true);
- }
- },
- /**
- * We override `initItems` so we can check for the pressed config.
- */
- applyItems: function() {
- var me = this,
- pressedButtons = [],
- ln, i, item, items;
- //call the parent first so the items get converted into a MixedCollection
- me.callParent(arguments);
- items = this.getItems();
- ln = items.length;
- for (i = 0; i < ln; i++) {
- item = items.items[i];
- if (item.getInitialConfig('pressed')) {
- pressedButtons.push(items.items[i]);
- }
- }
- me.updateFirstAndLastCls(items);
- me.setPressedButtons(pressedButtons);
- },
- /**
- * Button sets a timeout of 10ms to remove the {@link #pressedCls} on the release event.
- * We don't want this to happen, so lets return `false` and cancel the event.
- * @private
- */
- onButtonRelease: function(button) {
- if (!this.getAllowToggle()) {
- return;
- }
- var me = this,
- pressedButtons = me.getPressedButtons() || [],
- buttons = [],
- alreadyPressed;
- if (!me.getDisabled() && !button.getDisabled()) {
- //if we allow for multiple pressed buttons, use the existing pressed buttons
- if (me.getAllowMultiple()) {
- buttons = pressedButtons.concat(buttons);
- }
- alreadyPressed = (buttons.indexOf(button) !== -1) || (pressedButtons.indexOf(button) !== -1);
- //if we allow for depressing buttons, and the new pressed button is currently pressed, remove it
- if (alreadyPressed && me.getAllowDepress()) {
- Ext.Array.remove(buttons, button);
- } else if (!alreadyPressed || !me.getAllowDepress()) {
- buttons.push(button);
- }
- me.setPressedButtons(buttons);
- }
- },
- onItemAdd: function() {
- this.callParent(arguments);
- this.updateFirstAndLastCls(this.getItems());
- },
- onItemRemove: function() {
- this.callParent(arguments);
- this.updateFirstAndLastCls(this.getItems());
- },
- // @private
- onButtonHiddenChange: function() {
- this.updateFirstAndLastCls(this.getItems());
- },
- // @private
- updateFirstAndLastCls: function(items) {
- var ln = items.length,
- basePrefix = Ext.baseCSSPrefix,
- firstCls = basePrefix + 'first',
- lastCls = basePrefix + 'last',
- item, i;
- //remove all existing classes
- for (i = 0; i < ln; i++) {
- item = items.items[i];
- item.removeCls(firstCls);
- item.removeCls(lastCls);
- }
- //add a first cls to the first non-hidden button
- for (i = 0; i < ln; i++) {
- item = items.items[i];
- if (!item.isHidden()) {
- item.addCls(firstCls);
- break;
- }
- }
- //add a last cls to the last non-hidden button
- for (i = ln - 1; i >= 0; i--) {
- item = items.items[i];
- if (!item.isHidden()) {
- item.addCls(lastCls);
- break;
- }
- }
- },
- /**
- * @private
- */
- applyPressedButtons: function(newButtons) {
- var me = this,
- array = [],
- button, ln, i;
- if (me.getAllowToggle()) {
- if (Ext.isArray(newButtons)) {
- ln = newButtons.length;
- for (i = 0; i< ln; i++) {
- button = me.getComponent(newButtons[i]);
- if (button && array.indexOf(button) === -1) {
- array.push(button);
- }
- }
- } else {
- button = me.getComponent(newButtons);
- if (button && array.indexOf(button) === -1) {
- array.push(button);
- }
- }
- }
- return array;
- },
- /**
- * Updates the pressed buttons.
- * @private
- */
- updatePressedButtons: function(newButtons, oldButtons) {
- var me = this,
- items = me.getItems(),
- pressedCls = me.getPressedCls(),
- events = [],
- item, button, ln, i, e;
- //loop through existing items and remove the pressed cls from them
- ln = items.length;
- if (oldButtons && oldButtons.length) {
- for (i = 0; i < ln; i++) {
- item = items.items[i];
- if (oldButtons.indexOf(item) != -1 && newButtons.indexOf(item) == -1) {
- item.removeCls([pressedCls, item.getPressedCls()]);
- events.push({
- item: item,
- toggle: false
- });
- }
- }
- }
- //loop through the new pressed buttons and add the pressed cls to them
- ln = newButtons.length;
- for (i = 0; i < ln; i++) {
- button = newButtons[i];
- if (!oldButtons || oldButtons.indexOf(button) == -1) {
- button.addCls(pressedCls);
- events.push({
- item: button,
- toggle: true
- });
- }
- }
- //loop through each of the events and fire them after a delay
- ln = events.length;
- if (ln && oldButtons !== undefined) {
- Ext.defer(function() {
- for (i = 0; i < ln; i++) {
- e = events[i];
- me.fireEvent('toggle', me, e.item, e.toggle);
- }
- }, 50);
- }
- },
- /**
- * Returns `true` if a specified {@link Ext.Button} is pressed.
- * @param {Ext.Button} button The button to check if pressed.
- * @return {Boolean} pressed
- */
- isPressed: function(button) {
- var pressedButtons = this.getPressedButtons();
- return pressedButtons.indexOf(button) != -1;
- },
- /**
- * @private
- */
- doSetDisabled: function(disabled) {
- var me = this;
- me.items.each(function(item) {
- item.setDisabled(disabled);
- }, me);
- me.callParent(arguments);
- }
- }, function() {
- });
- /**
- * A mixin which allows a data component to be sorted
- * @ignore
- */
- Ext.define('Ext.Sortable', {
- mixins: {
- observable: 'Ext.mixin.Observable'
- },
- requires: ['Ext.util.Draggable'],
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'sortable',
- /**
- * @cfg {Number} delay
- * How many milliseconds a user must hold the draggable before starting a
- * drag operation.
- * @private
- * @accessor
- */
- delay: 0
- },
- /**
- * @cfg {String} direction
- * Possible values: 'vertical', 'horizontal'.
- */
- direction: 'vertical',
- /**
- * @cfg {String} cancelSelector
- * A simple CSS selector that represents elements within the draggable
- * that should NOT initiate a drag.
- */
- cancelSelector: null,
- // not yet implemented
- //indicator: true,
- //proxy: true,
- //tolerance: null,
- /**
- * @cfg {HTMLElement/Boolean} constrain
- * An Element to constrain the Sortable dragging to.
- * If `true` is specified, the dragging will be constrained to the element
- * of the sortable.
- */
- constrain: window,
- /**
- * @cfg {String} group
- * Draggable and Droppable objects can participate in a group which are
- * capable of interacting.
- */
- group: 'base',
- /**
- * @cfg {Boolean} revert
- * This should NOT be changed.
- * @private
- */
- revert: true,
- /**
- * @cfg {String} itemSelector
- * A simple CSS selector that represents individual items within the Sortable.
- */
- itemSelector: null,
- /**
- * @cfg {String} handleSelector
- * A simple CSS selector to indicate what is the handle to drag the Sortable.
- */
- handleSelector: null,
- /**
- * @cfg {Boolean} disabled
- * Passing in `true` will disable this Sortable.
- */
- disabled: false,
- // Properties
- /**
- * Read-only property that indicates whether a Sortable is currently sorting.
- * @type Boolean
- * @private
- * @readonly
- */
- sorting: false,
- /**
- * Read-only value representing whether the Draggable can be moved vertically.
- * This is automatically calculated by Draggable by the direction configuration.
- * @type Boolean
- * @private
- * @readonly
- */
- vertical: false,
- /**
- * Creates new Sortable.
- * @param {Mixed} el
- * @param {Object} config
- */
- constructor : function(el, config) {
- config = config || {};
- Ext.apply(this, config);
- this.addEvents(
- /**
- * @event sortstart
- * @param {Ext.Sortable} this
- * @param {Ext.event.Event} e
- */
- 'sortstart',
- /**
- * @event sortend
- * @param {Ext.Sortable} this
- * @param {Ext.event.Event} e
- */
- 'sortend',
- /**
- * @event sortchange
- * @param {Ext.Sortable} this
- * @param {Ext.Element} el The Element being dragged.
- * @param {Number} index The index of the element after the sort change.
- */
- 'sortchange'
- // not yet implemented.
- // 'sortupdate',
- // 'sortreceive',
- // 'sortremove',
- // 'sortenter',
- // 'sortleave',
- // 'sortactivate',
- // 'sortdeactivate'
- );
- this.el = Ext.get(el);
- this.callParent();
- this.mixins.observable.constructor.call(this);
- if (this.direction == 'horizontal') {
- this.horizontal = true;
- }
- else if (this.direction == 'vertical') {
- this.vertical = true;
- }
- else {
- this.horizontal = this.vertical = true;
- }
- this.el.addCls(this.baseCls);
- this.startEventName = (this.getDelay() > 0) ? 'taphold' : 'tapstart';
- if (!this.disabled) {
- this.enable();
- }
- },
- // @private
- onStart : function(e, t) {
- if (this.cancelSelector && e.getTarget(this.cancelSelector)) {
- return;
- }
- if (this.handleSelector && !e.getTarget(this.handleSelector)) {
- return;
- }
- if (!this.sorting) {
- this.onSortStart(e, t);
- }
- },
- // @private
- onSortStart : function(e, t) {
- this.sorting = true;
- var draggable = Ext.create('Ext.util.Draggable', t, {
- threshold: 0,
- revert: this.revert,
- direction: this.direction,
- constrain: this.constrain === true ? this.el : this.constrain,
- animationDuration: 100
- });
- draggable.on({
- drag: this.onDrag,
- dragend: this.onDragEnd,
- scope: this
- });
- this.dragEl = t;
- this.calculateBoxes();
- if (!draggable.dragging) {
- draggable.onStart(e);
- }
- this.fireEvent('sortstart', this, e);
- },
- // @private
- calculateBoxes : function() {
- this.items = [];
- var els = this.el.select(this.itemSelector, false),
- ln = els.length, i, item, el, box;
- for (i = 0; i < ln; i++) {
- el = els[i];
- if (el != this.dragEl) {
- item = Ext.fly(el).getPageBox(true);
- item.el = el;
- this.items.push(item);
- }
- }
- },
- // @private
- onDrag : function(draggable, e) {
- var items = this.items,
- ln = items.length,
- region = draggable.region,
- sortChange = false,
- i, intersect, overlap, item;
- for (i = 0; i < ln; i++) {
- item = items[i];
- intersect = region.intersect(item);
- if (intersect) {
- if (this.vertical && Math.abs(intersect.top - intersect.bottom) > (region.bottom - region.top) / 2) {
- if (region.bottom > item.top && item.top > region.top) {
- draggable.el.insertAfter(item.el);
- }
- else {
- draggable.el.insertBefore(item.el);
- }
- sortChange = true;
- }
- else if (this.horizontal && Math.abs(intersect.left - intersect.right) > (region.right - region.left) / 2) {
- if (region.right > item.left && item.left > region.left) {
- draggable.el.insertAfter(item.el);
- }
- else {
- draggable.el.insertBefore(item.el);
- }
- sortChange = true;
- }
- if (sortChange) {
- // We reset the draggable (initializes all the new start values)
- draggable.reset();
- // Move the draggable to its current location (since the transform is now
- // different)
- draggable.moveTo(region.left, region.top);
- // Finally lets recalculate all the items boxes
- this.calculateBoxes();
- this.fireEvent('sortchange', this, draggable.el, this.el.select(this.itemSelector, false).indexOf(draggable.el.dom));
- return;
- }
- }
- }
- },
- // @private
- onDragEnd : function(draggable, e) {
- draggable.destroy();
- this.sorting = false;
- this.fireEvent('sortend', this, draggable, e);
- },
- /**
- * Enables sorting for this Sortable.
- * This method is invoked immediately after construction of a Sortable unless
- * the disabled configuration is set to `true`.
- */
- enable : function() {
- this.el.on(this.startEventName, this.onStart, this, {delegate: this.itemSelector, holdThreshold: this.getDelay()});
- this.disabled = false;
- },
- /**
- * Disables sorting for this Sortable.
- */
- disable : function() {
- this.el.un(this.startEventName, this.onStart, this);
- this.disabled = true;
- },
- /**
- * Method to determine whether this Sortable is currently disabled.
- * @return {Boolean} The disabled state of this Sortable.
- */
- isDisabled: function() {
- return this.disabled;
- },
- /**
- * Method to determine whether this Sortable is currently sorting.
- * @return {Boolean} The sorting state of this Sortable.
- */
- isSorting : function() {
- return this.sorting;
- },
- /**
- * Method to determine whether this Sortable is currently disabled.
- * @return {Boolean} The disabled state of this Sortable.
- */
- isVertical : function() {
- return this.vertical;
- },
- /**
- * Method to determine whether this Sortable is currently sorting.
- * @return {Boolean} The sorting state of this Sortable.
- */
- isHorizontal : function() {
- return this.horizontal;
- }
- });
- (function() {
- var lastTime = 0,
- vendors = ['ms', 'moz', 'webkit', 'o'],
- ln = vendors.length,
- i, vendor;
- for (i = 0; i < ln && !window.requestAnimationFrame; ++i) {
- vendor = vendors[i];
- window.requestAnimationFrame = window[vendor + 'RequestAnimationFrame'];
- window.cancelAnimationFrame = window[vendor + 'CancelAnimationFrame'] || window[vendor + 'CancelRequestAnimationFrame'];
- }
- if (!window.requestAnimationFrame) {
- window.requestAnimationFrame = function(callback, element) {
- var currTime = new Date().getTime(),
- timeToCall = Math.max(0, 16 - (currTime - lastTime)),
- id = window.setTimeout(function() {
- callback(currTime + timeToCall);
- }, timeToCall);
- lastTime = currTime + timeToCall;
- return id;
- };
- }
- if (!window.cancelAnimationFrame) {
- window.cancelAnimationFrame = function(id) {
- clearTimeout(id);
- };
- }
- }());
- /**
- * @private
- * Handle batch read / write of DOMs, currently used in SizeMonitor + PaintMonitor
- */
- Ext.define('Ext.TaskQueue', {
- singleton: true,
- pending: false,
- mode: true,
- constructor: function() {
- this.readQueue = [];
- this.writeQueue = [];
- this.run = Ext.Function.bind(this.run, this);
- },
- requestRead: function(fn, scope, args) {
- this.request(true);
- this.readQueue.push(arguments);
- },
- requestWrite: function(fn, scope, args) {
- this.request(false);
- this.writeQueue.push(arguments);
- },
- request: function(mode) {
- if (!this.pending) {
- this.pending = true;
- this.mode = mode;
- requestAnimationFrame(this.run);
- }
- },
- run: function() {
- this.pending = false;
- var readQueue = this.readQueue,
- writeQueue = this.writeQueue,
- request = null,
- queue;
- if (this.mode) {
- queue = readQueue;
- if (writeQueue.length > 0) {
- request = false;
- }
- }
- else {
- queue = writeQueue;
- if (readQueue.length > 0) {
- request = true;
- }
- }
- var tasks = queue.slice(),
- i, ln, task, fn, scope;
- queue.length = 0;
- for (i = 0, ln = tasks.length; i < ln; i++) {
- task = tasks[i];
- fn = task[0];
- scope = task[1];
- if (typeof fn == 'string') {
- fn = scope[fn];
- }
- if (task.length > 2) {
- fn.apply(scope, task[2]);
- }
- else {
- fn.call(scope);
- }
- }
- tasks.length = 0;
- if (request !== null) {
- this.request(request);
- }
- }
- });
- /**
- * {@link Ext.TitleBar}'s are most commonly used as a docked item within an {@link Ext.Container}.
- *
- * The main difference between a {@link Ext.TitleBar} and an {@link Ext.Toolbar} is that
- * the {@link #title} configuration is **always** centered horizontally in a {@link Ext.TitleBar} between
- * any items aligned left or right.
- *
- * You can also give items of a {@link Ext.TitleBar} an `align` configuration of `left` or `right`
- * which will dock them to the `left` or `right` of the bar.
- *
- * ## Examples
- *
- * @example preview
- * Ext.Viewport.add({
- * xtype: 'titlebar',
- * docked: 'top',
- * title: 'Navigation',
- * items: [
- * {
- * iconCls: 'add',
- * iconMask: true,
- * align: 'left'
- * },
- * {
- * iconCls: 'home',
- * iconMask: true,
- * align: 'right'
- * }
- * ]
- * });
- *
- * Ext.Viewport.setStyleHtmlContent(true);
- * Ext.Viewport.setHtml('This shows the title being centered and buttons using align <i>left</i> and <i>right</i>.');
- *
- * <br />
- *
- * @example preview
- * Ext.Viewport.add({
- * xtype: 'titlebar',
- * docked: 'top',
- * title: 'Navigation',
- * items: [
- * {
- * align: 'left',
- * text: 'This button has a super long title'
- * },
- * {
- * iconCls: 'home',
- * iconMask: true,
- * align: 'right'
- * }
- * ]
- * });
- *
- * Ext.Viewport.setStyleHtmlContent(true);
- * Ext.Viewport.setHtml('This shows how the title is automatically moved to the right when one of the aligned buttons is very wide.');
- *
- * <br />
- *
- * @example preview
- * Ext.Viewport.add({
- * xtype: 'titlebar',
- * docked: 'top',
- * title: 'A very long title',
- * items: [
- * {
- * align: 'left',
- * text: 'This button has a super long title'
- * },
- * {
- * align: 'right',
- * text: 'Another button'
- * }
- * ]
- * });
- *
- * Ext.Viewport.setStyleHtmlContent(true);
- * Ext.Viewport.setHtml('This shows how the title and buttons will automatically adjust their size when the width of the items are too wide..');
- *
- * The {@link #defaultType} of Toolbar's is {@link Ext.Button button}.
- */
- Ext.define('Ext.TitleBar', {
- extend: 'Ext.Container',
- xtype: 'titlebar',
- requires: [
- 'Ext.Button',
- 'Ext.Title',
- 'Ext.Spacer'
- ],
- // @private
- isToolbar: true,
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'toolbar',
- /**
- * @cfg
- * @inheritdoc
- */
- cls: Ext.baseCSSPrefix + 'navigation-bar',
- /**
- * @cfg {String} ui
- * Style options for Toolbar. Either 'light' or 'dark'.
- * @accessor
- */
- ui: 'dark',
- /**
- * @cfg {String} title
- * The title of the toolbar.
- * @accessor
- */
- title: null,
- /**
- * @cfg {String} defaultType
- * The default xtype to create.
- * @accessor
- */
- defaultType: 'button',
- height: '2.6em',
- /**
- * @cfg
- * @hide
- */
- layout: {
- type: 'hbox'
- },
- /**
- * @cfg {Array/Object} items The child items to add to this TitleBar. The {@link #defaultType} of
- * a TitleBar is {@link Ext.Button}, so you do not need to specify an `xtype` if you are adding
- * buttons.
- *
- * You can also give items a `align` configuration which will align the item to the `left` or `right` of
- * the TitleBar.
- * @accessor
- */
- items: []
- },
- /**
- * The max button width in this toolbar
- * @private
- */
- maxButtonWidth: '40%',
- constructor: function() {
- this.refreshTitlePosition = Ext.Function.createThrottled(this.refreshTitlePosition, 50, this);
- this.callParent(arguments);
- },
- beforeInitialize: function() {
- this.applyItems = this.applyInitialItems;
- },
- initialize: function() {
- delete this.applyItems;
- this.add(this.initialItems);
- delete this.initialItems;
- this.on({
- painted: 'refreshTitlePosition',
- single: true
- });
- },
- applyInitialItems: function(items) {
- var me = this,
- defaults = me.getDefaults() || {};
- me.initialItems = items;
- me.leftBox = me.add({
- xtype: 'container',
- style: 'position: relative',
- layout: {
- type: 'hbox',
- align: 'center'
- },
- listeners: {
- resize: 'refreshTitlePosition',
- scope: me
- }
- });
- me.spacer = me.add({
- xtype: 'component',
- style: 'position: relative',
- flex: 1,
- listeners: {
- resize: 'refreshTitlePosition',
- scope: me
- }
- });
- me.rightBox = me.add({
- xtype: 'container',
- style: 'position: relative',
- layout: {
- type: 'hbox',
- align: 'center'
- },
- listeners: {
- resize: 'refreshTitlePosition',
- scope: me
- }
- });
- me.titleComponent = me.add({
- xtype: 'title',
- hidden: defaults.hidden,
- centered: true
- });
- me.doAdd = me.doBoxAdd;
- me.remove = me.doBoxRemove;
- me.doInsert = me.doBoxInsert;
- },
- doBoxAdd: function(item) {
- if (item.config.align == 'right') {
- this.rightBox.add(item);
- }
- else {
- this.leftBox.add(item);
- }
- },
- doBoxRemove: function(item) {
- if (item.config.align == 'right') {
- this.rightBox.remove(item);
- }
- else {
- this.leftBox.remove(item);
- }
- },
- doBoxInsert: function(index, item) {
- if (item.config.align == 'right') {
- this.rightBox.add(item);
- }
- else {
- this.leftBox.add(item);
- }
- },
- getMaxButtonWidth: function() {
- var value = this.maxButtonWidth;
- //check if it is a percentage
- if (Ext.isString(this.maxButtonWidth)) {
- value = parseInt(value.replace('%', ''), 10);
- value = Math.round((this.element.getWidth() / 100) * value);
- }
- return value;
- },
- refreshTitlePosition: function() {
- var titleElement = this.titleComponent.renderElement;
- titleElement.setWidth(null);
- titleElement.setLeft(null);
- //set the min/max width of the left button
- var leftBox = this.leftBox,
- leftButton = leftBox.down('button'),
- singleButton = leftBox.getItems().getCount() == 1,
- leftBoxWidth, maxButtonWidth;
- if (leftButton && singleButton) {
- if (leftButton.getWidth() == null) {
- leftButton.renderElement.setWidth('auto');
- }
- leftBoxWidth = leftBox.renderElement.getWidth();
- maxButtonWidth = this.getMaxButtonWidth();
- if (leftBoxWidth > maxButtonWidth) {
- leftButton.renderElement.setWidth(maxButtonWidth);
- }
- }
- var spacerBox = this.spacer.renderElement.getPageBox(),
- titleBox = titleElement.getPageBox(),
- widthDiff = titleBox.width - spacerBox.width,
- titleLeft = titleBox.left,
- titleRight = titleBox.right,
- halfWidthDiff, leftDiff, rightDiff;
- if (widthDiff > 0) {
- titleElement.setWidth(spacerBox.width);
- halfWidthDiff = widthDiff / 2;
- titleLeft += halfWidthDiff;
- titleRight -= halfWidthDiff;
- }
- leftDiff = spacerBox.left - titleLeft;
- rightDiff = titleRight - spacerBox.right;
- if (leftDiff > 0) {
- titleElement.setLeft(leftDiff);
- }
- else if (rightDiff > 0) {
- titleElement.setLeft(-rightDiff);
- }
- titleElement.repaint();
- },
- // @private
- updateTitle: function(newTitle) {
- this.titleComponent.setTitle(newTitle);
- if (this.isPainted()) {
- this.refreshTitlePosition();
- }
- }
- });
- /**
- * @aside example video
- * Provides a simple Container for HTML5 Video.
- *
- * ## Notes
- *
- * - There are quite a few issues with the `<video>` tag on Android devices. On Android 2+, the video will
- * appear and play on first attempt, but any attempt afterwards will not work.
- *
- * ## Useful Properties
- *
- * - {@link #url}
- * - {@link #autoPause}
- * - {@link #autoResume}
- *
- * ## Useful Methods
- *
- * - {@link #method-pause}
- * - {@link #method-play}
- * - {@link #toggle}
- *
- * ## Example
- *
- * var panel = Ext.create('Ext.Panel', {
- * fullscreen: true,
- * items: [
- * {
- * xtype : 'video',
- * x : 600,
- * y : 300,
- * width : 175,
- * height : 98,
- * url : "porsche911.mov",
- * posterUrl: 'porsche.png'
- * }
- * ]
- * });
- */
- Ext.define('Ext.Video', {
- extend: 'Ext.Media',
- xtype: 'video',
- config: {
- /**
- * @cfg {String/Array} urls
- * Location of the video to play. This should be in H.264 format and in a .mov file format.
- * @accessor
- */
- /**
- * @cfg {String} posterUrl
- * Location of a poster image to be shown before showing the video.
- * @accessor
- */
- posterUrl: null,
- /**
- * @cfg
- * @inheritdoc
- */
- cls: Ext.baseCSSPrefix + 'video'
- },
- template: [{
- /**
- * @property {Ext.dom.Element} ghost
- * @private
- */
- reference: 'ghost',
- classList: [Ext.baseCSSPrefix + 'video-ghost']
- }, {
- tag: 'video',
- reference: 'media',
- classList: [Ext.baseCSSPrefix + 'media']
- }],
- initialize: function() {
- var me = this;
- me.callParent();
- me.media.hide();
- me.onBefore({
- erased: 'onErased',
- scope: me
- });
- me.ghost.on({
- tap: 'onGhostTap',
- scope: me
- });
- me.media.on({
- pause: 'onPause',
- scope: me
- });
- if (Ext.os.is.Android4 || Ext.os.is.iPad) {
- this.isInlineVideo = true;
- }
- },
- applyUrl: function(url) {
- return [].concat(url);
- },
- updateUrl: function(newUrl) {
- var me = this,
- media = me.media,
- newLn = newUrl.length,
- existingSources = media.query('source'),
- oldLn = existingSources.length,
- i;
- for (i = 0; i < oldLn; i++) {
- Ext.fly(existingSources[i]).destroy();
- }
- for (i = 0; i < newLn; i++) {
- media.appendChild(Ext.Element.create({
- tag: 'source',
- src: newUrl[i]
- }));
- }
- if (me.isPlaying()) {
- me.play();
- }
- },
- onErased: function() {
- this.pause();
- this.media.setTop(-2000);
- this.ghost.show();
- },
- /**
- * @private
- * Called when the {@link #ghost} element is tapped.
- */
- onGhostTap: function() {
- var me = this,
- media = this.media,
- ghost = this.ghost;
- media.show();
- if (Ext.os.is.Android2) {
- setTimeout(function() {
- me.play();
- setTimeout(function() {
- media.hide();
- }, 10);
- }, 10);
- } else {
- // Browsers which support native video tag display only, move the media down so
- // we can control the Viewport
- ghost.hide();
- me.play();
- }
- },
- /**
- * @private
- * native video tag display only, move the media down so we can control the Viewport
- */
- onPause: function() {
- this.callParent(arguments);
- if (!this.isInlineVideo) {
- this.media.setTop(-2000);
- this.ghost.show();
- }
- },
- /**
- * @private
- * native video tag display only, move the media down so we can control the Viewport
- */
- onPlay: function() {
- this.callParent(arguments);
- this.media.setTop(0);
- },
- /**
- * Updates the URL to the poster, even if it is rendered.
- * @param {Object} newUrl
- */
- updatePosterUrl: function(newUrl) {
- var ghost = this.ghost;
- if (ghost) {
- ghost.setStyle('background-image', 'url(' + newUrl + ')');
- }
- }
- });
- /**
- * @author Ed Spencer
- * @private
- *
- * Represents a single action as {@link Ext.app.Application#dispatch dispatched} by an Application. This is typically
- * generated as a result of a url change being matched by a Route, triggering Application's dispatch function.
- *
- * This is a private class and its functionality and existence may change in the future. Use at your own risk.
- *
- */
- Ext.define('Ext.app.Action', {
- config: {
- /**
- * @cfg {Object} scope The scope in which the {@link #action} should be called.
- */
- scope: null,
- /**
- * @cfg {Ext.app.Application} application The Application that this Action is bound to.
- */
- application: null,
- /**
- * @cfg {Ext.app.Controller} controller The {@link Ext.app.Controller controller} whose {@link #action} should
- * be called.
- */
- controller: null,
- /**
- * @cfg {String} action The name of the action on the {@link #controller} that should be called.
- */
- action: null,
- /**
- * @cfg {Array} args The set of arguments that will be passed to the controller's {@link #action}.
- */
- args: [],
- /**
- * @cfg {String} url The url that was decoded into the controller/action/args in this Action.
- */
- url: undefined,
- data: {},
- title: null,
- /**
- * @cfg {Array} beforeFilters The (optional) set of functions to call before the {@link #action} is called.
- * This is usually handled directly by the Controller or Application when an Ext.app.Action instance is
- * created, but is alterable before {@link #resume} is called.
- * @accessor
- */
- beforeFilters: [],
- /**
- * @private
- * Keeps track of which before filter is currently being executed by {@link #resume}
- */
- currentFilterIndex: -1
- },
- constructor: function(config) {
- this.initConfig(config);
- this.getUrl();
- },
- /**
- * Starts execution of this Action by calling each of the {@link #beforeFilters} in turn (if any are specified),
- * before calling the Controller {@link #action}. Same as calling {@link #resume}.
- */
- execute: function() {
- this.resume();
- },
- /**
- * Resumes the execution of this Action (or starts it if it had not been started already). This iterates over all
- * of the configured {@link #beforeFilters} and calls them. Each before filter is called with this Action as the
- * sole argument, and is expected to call `action.resume()` in order to allow the next filter to be called, or if
- * this is the final filter, the original {@link Ext.app.Controller Controller} function.
- */
- resume: function() {
- var index = this.getCurrentFilterIndex() + 1,
- filters = this.getBeforeFilters(),
- controller = this.getController(),
- nextFilter = filters[index];
- if (nextFilter) {
- this.setCurrentFilterIndex(index);
- nextFilter.call(controller, this);
- } else {
- controller[this.getAction()].apply(controller, this.getArgs());
- }
- },
- /**
- * @private
- */
- applyUrl: function(url) {
- if (url === null || url === undefined) {
- url = this.urlEncode();
- }
- return url;
- },
- /**
- * @private
- * If the controller config is a string, swap it for a reference to the actual controller instance.
- * @param {String} controller The controller name.
- */
- applyController: function(controller) {
- var app = this.getApplication(),
- profile = app.getCurrentProfile();
- if (Ext.isString(controller)) {
- controller = app.getController(controller, profile ? profile.getNamespace() : null);
- }
- return controller;
- },
- /**
- * @private
- */
- urlEncode: function() {
- var controller = this.getController(),
- splits;
- if (controller instanceof Ext.app.Controller) {
- splits = controller.$className.split('.');
- controller = splits[splits.length - 1];
- }
- return controller + "/" + this.getAction();
- }
- });
- /**
- * @author Ed Spencer
- *
- * @aside guide controllers
- * @aside guide apps_intro
- * @aside guide history_support
- * @aside video mvc-part-1
- * @aside video mvc-part-2
- *
- * Controllers are responsible for responding to events that occur within your app. If your app contains a Logout
- * {@link Ext.Button button} that your user can tap on, a Controller would listen to the Button's tap event and take
- * the appropriate action. It allows the View classes to handle the display of data and the Model classes to handle the
- * loading and saving of data - the Controller is the glue that binds them together.
- *
- * ## Relation to Ext.app.Application
- *
- * Controllers exist within the context of an {@link Ext.app.Application Application}. An Application usually consists
- * of a number of Controllers, each of which handle a specific part of the app. For example, an Application that
- * handles the orders for an online shopping site might have controllers for Orders, Customers and Products.
- *
- * All of the Controllers that an Application uses are specified in the Application's
- * {@link Ext.app.Application#controllers} config. The Application automatically instantiates each Controller and keeps
- * references to each, so it is unusual to need to instantiate Controllers directly. By convention each Controller is
- * named after the thing (usually the Model) that it deals with primarily, usually in the plural - for example if your
- * app is called 'MyApp' and you have a Controller that manages Products, convention is to create a
- * MyApp.controller.Products class in the file app/controller/Products.js.
- *
- * ## Refs and Control
- *
- * The centerpiece of Controllers is the twin configurations {@link #refs} and {@link #cfg-control}. These are used to
- * easily gain references to Components inside your app and to take action on them based on events that they fire.
- * Let's look at {@link #refs} first:
- *
- * ### Refs
- *
- * Refs leverage the powerful {@link Ext.ComponentQuery ComponentQuery} syntax to easily locate Components on your
- * page. We can define as many refs as we like for each Controller, for example here we define a ref called 'nav' that
- * finds a Component on the page with the ID 'mainNav'. We then use that ref in the addLogoutButton beneath it:
- *
- * Ext.define('MyApp.controller.Main', {
- * extend: 'Ext.app.Controller',
- *
- * config: {
- * refs: {
- * nav: '#mainNav'
- * }
- * },
- *
- * addLogoutButton: function() {
- * this.getNav().add({
- * text: 'Logout'
- * });
- * }
- * });
- *
- * Usually, a ref is just a key/value pair - the key ('nav' in this case) is the name of the reference that will be
- * generated, the value ('#mainNav' in this case) is the {@link Ext.ComponentQuery ComponentQuery} selector that will
- * be used to find the Component.
- *
- * Underneath that, we have created a simple function called addLogoutButton which uses this ref via its generated
- * 'getNav' function. These getter functions are generated based on the refs you define and always follow the same
- * format - 'get' followed by the capitalized ref name. In this case we're treating the nav reference as though it's a
- * {@link Ext.Toolbar Toolbar}, and adding a Logout button to it when our function is called. This ref would recognize
- * a Toolbar like this:
- *
- * Ext.create('Ext.Toolbar', {
- * id: 'mainNav',
- *
- * items: [
- * {
- * text: 'Some Button'
- * }
- * ]
- * });
- *
- * Assuming this Toolbar has already been created by the time we run our 'addLogoutButton' function (we'll see how that
- * is invoked later), it will get the second button added to it.
- *
- * ### Advanced Refs
- *
- * Refs can also be passed a couple of additional options, beyond name and selector. These are autoCreate and xtype,
- * which are almost always used together:
- *
- * Ext.define('MyApp.controller.Main', {
- * extend: 'Ext.app.Controller',
- *
- * config: {
- * refs: {
- * nav: '#mainNav',
- *
- * infoPanel: {
- * selector: 'tabpanel panel[name=fish] infopanel',
- * xtype: 'infopanel',
- * autoCreate: true
- * }
- * }
- * }
- * });
- *
- * We've added a second ref to our Controller. Again the name is the key, 'infoPanel' in this case, but this time we've
- * passed an object as the value instead. This time we've used a slightly more complex selector query - in this example
- * imagine that your app contains a {@link Ext.tab.Panel tab panel} and that one of the items in the tab panel has been
- * given the name 'fish'. Our selector matches any Component with the xtype 'infopanel' inside that tab panel item.
- *
- * The difference here is that if that infopanel does not exist already inside the 'fish' panel, it will be
- * automatically created when you call this.getInfoPanel inside your Controller. The Controller is able to do this
- * because we provided the xtype to instantiate with in the event that the selector did not return anything.
- *
- * ### Control
- *
- * The sister config to {@link #refs} is {@link #cfg-control}. {@link #cfg-control Control} is the means by which your listen
- * to events fired by Components and have your Controller react in some way. Control accepts both ComponentQuery
- * selectors and refs as its keys, and listener objects as values - for example:
- *
- * Ext.define('MyApp.controller.Main', {
- * extend: 'Ext.app.Controller',
- *
- * config: {
- * control: {
- * loginButton: {
- * tap: 'doLogin'
- * },
- * 'button[action=logout]': {
- * tap: 'doLogout'
- * }
- * },
- *
- * refs: {
- * loginButton: 'button[action=login]'
- * }
- * },
- *
- * doLogin: function() {
- * //called whenever the Login button is tapped
- * },
- *
- * doLogout: function() {
- * //called whenever any Button with action=logout is tapped
- * }
- * });
- *
- * Here we have set up two control declarations - one for our loginButton ref and the other for any Button on the page
- * that has been given the action 'logout'. For each declaration we passed in a single event handler - in each case
- * listening for the 'tap' event, specifying the action that should be called when that Button fires the tap event.
- * Note that we specified the 'doLogin' and 'doLogout' methods as strings inside the control block - this is important.
- *
- * You can listen to as many events as you like in each control declaration, and mix and match ComponentQuery selectors
- * and refs as the keys.
- *
- * ## Routes
- *
- * As of Sencha Touch 2, Controllers can now directly specify which routes they are interested in. This enables us to
- * provide history support within our app, as well as the ability to deeply link to any part of the application that we
- * provide a route for.
- *
- * For example, let's say we have a Controller responsible for logging in and viewing user profiles, and want to make
- * those screens accessible via urls. We could achieve that like this:
- *
- * Ext.define('MyApp.controller.Users', {
- * extend: 'Ext.app.Controller',
- *
- * config: {
- * routes: {
- * 'login': 'showLogin',
- * 'user/:id': 'showUserById'
- * },
- *
- * refs: {
- * main: '#mainTabPanel'
- * }
- * },
- *
- * //uses our 'main' ref above to add a loginpanel to our main TabPanel (note that
- * //'loginpanel' is a custom xtype created for this application)
- * showLogin: function() {
- * this.getMain().add({
- * xtype: 'loginpanel'
- * });
- * },
- *
- * //Loads the User then adds a 'userprofile' view to the main TabPanel
- * showUserById: function(id) {
- * MyApp.model.User.load(id, {
- * scope: this,
- * success: function(user) {
- * this.getMain().add({
- * xtype: 'userprofile',
- * user: user
- * });
- * }
- * });
- * }
- * });
- *
- * The routes we specified above simply map the contents of the browser address bar to a Controller function to call
- * when that route is matched. The routes can be simple text like the login route, which matches against
- * http://myapp.com/#login, or contain wildcards like the 'user/:id' route, which matches urls like
- * http://myapp.com/#user/123. Whenever the address changes the Controller automatically calls the function specified.
- *
- * Note that in the showUserById function we had to first load the User instance. Whenever you use a route, the
- * function that is called by that route is completely responsible for loading its data and restoring state. This is
- * because your user could either send that url to another person or simply refresh the page, which we wipe clear any
- * cached data you had already loaded. There is a more thorough discussion of restoring state with routes in the
- * application architecture guides.
- *
- * ## Advanced Usage
- *
- * See [the Controllers guide](#!/guide/controllers) for advanced Controller usage including before filters
- * and customizing for different devices.
- */
- Ext.define('Ext.app.Controller', {
- mixins: {
- observable: "Ext.mixin.Observable"
- },
- config: {
- /**
- * @cfg {Object} refs A collection of named {@link Ext.ComponentQuery ComponentQuery} selectors that makes it
- * easy to get references to key Components on your page. Example usage:
- *
- * refs: {
- * main: '#mainTabPanel',
- * loginButton: '#loginWindow button[action=login]',
- *
- * infoPanel: {
- * selector: 'infopanel',
- * xtype: 'infopanel',
- * autoCreate: true
- * }
- * }
- *
- * The first two are simple ComponentQuery selectors, the third (infoPanel) also passes in the autoCreate and
- * xtype options, which will first run the ComponentQuery to see if a Component matching that selector exists
- * on the page. If not, it will automatically create one using the xtype provided:
- *
- * someControllerFunction: function() {
- * //if the info panel didn't exist before, calling its getter will instantiate
- * //it automatically and return the new instance
- * this.getInfoPanel().show();
- * }
- *
- * @accessor
- */
- refs: {},
- /**
- * @cfg {Object} routes Provides a mapping of urls to Controller actions. Whenever the specified url is matched
- * in the address bar, the specified Controller action is called. Example usage:
- *
- * routes: {
- * 'login': 'showLogin',
- * 'users/:id': 'showUserById'
- * }
- *
- * The first route will match against http://myapp.com/#login and call the Controller's showLogin function. The
- * second route contains a wildcard (':id') and will match all urls like http://myapp.com/#users/123, calling
- * the showUserById function with the matched ID as the first argument.
- *
- * @accessor
- */
- routes: {},
- /**
- * @cfg {Object} control Provides a mapping of Controller functions that should be called whenever certain
- * Component events are fired. The Components can be specified using {@link Ext.ComponentQuery ComponentQuery}
- * selectors or {@link #refs}. Example usage:
- *
- * control: {
- * 'button[action=logout]': {
- * tap: 'doLogout'
- * },
- * main: {
- * activeitemchange: 'doUpdate'
- * }
- * }
- *
- * The first item uses a ComponentQuery selector to run the Controller's doLogout function whenever any Button
- * with action=logout is tapped on. The second calls the Controller's doUpdate function whenever the
- * activeitemchange event is fired by the Component referenced by our 'main' ref. In this case main is a tab
- * panel (see {@link #refs} for how to set that reference up).
- *
- * @accessor
- */
- control: {},
- /**
- * @cfg {Object} before Provides a mapping of Controller functions to filter functions that are run before them
- * when dispatched to from a route. These are usually used to run pre-processing functions like authentication
- * before a certain function is executed. They are only called when dispatching from a route. Example usage:
- *
- * Ext.define('MyApp.controller.Products', {
- * config: {
- * before: {
- * editProduct: 'authenticate'
- * },
- *
- * routes: {
- * 'product/edit/:id': 'editProduct'
- * }
- * },
- *
- * //this is not directly because our before filter is called first
- * editProduct: function() {
- * //... performs the product editing logic
- * },
- *
- * //this is run before editProduct
- * authenticate: function(action) {
- * MyApp.authenticate({
- * success: function() {
- * action.resume();
- * },
- * failure: function() {
- * Ext.Msg.alert('Not Logged In', "You can't do that, you're not logged in");
- * }
- * });
- * }
- * });
- *
- * @accessor
- */
- before: {},
- /**
- * @cfg {Ext.app.Application} application The Application instance this Controller is attached to. This is
- * automatically provided when using the MVC architecture so should rarely need to be set directly.
- * @accessor
- */
- application: {},
- /**
- * @cfg {String[]} stores The set of stores to load for this Application. Each store is expected to
- * exist inside the *app/store* directory and define a class following the convention
- * AppName.store.StoreName. For example, in the code below, the *AppName.store.Users* class will be loaded.
- * Note that we are able to specify either the full class name (as with *AppName.store.Groups*) or just the
- * final part of the class name and leave Application to automatically prepend *AppName.store.'* to each:
- *
- * stores: [
- * 'Users',
- * 'AppName.store.Groups',
- * 'SomeCustomNamespace.store.Orders'
- * ]
- * @accessor
- */
- stores: [],
- /**
- * @cfg {String[]} models The set of models to load for this Application. Each model is expected to exist inside the
- * *app/model* directory and define a class following the convention AppName.model.ModelName. For example, in the
- * code below, the classes *AppName.model.User*, *AppName.model.Group* and *AppName.model.Product* will be loaded.
- * Note that we are able to specify either the full class name (as with *AppName.model.Product*) or just the
- * final part of the class name and leave Application to automatically prepend *AppName.model.* to each:
- *
- * models: [
- * 'User',
- * 'Group',
- * 'AppName.model.Product',
- * 'SomeCustomNamespace.model.Order'
- * ]
- * @accessor
- */
- models: [],
- /**
- * @cfg {Array} views The set of views to load for this Application. Each view is expected to exist inside the
- * *app/view* directory and define a class following the convention AppName.view.ViewName. For example, in the
- * code below, the classes *AppName.view.Users*, *AppName.view.Groups* and *AppName.view.Products* will be loaded.
- * Note that we are able to specify either the full class name (as with *AppName.view.Products*) or just the
- * final part of the class name and leave Application to automatically prepend *AppName.view.* to each:
- *
- * views: [
- * 'Users',
- * 'Groups',
- * 'AppName.view.Products',
- * 'SomeCustomNamespace.view.Orders'
- * ]
- * @accessor
- */
- views: []
- },
- /**
- * Constructs a new Controller instance
- */
- constructor: function(config) {
- this.initConfig(config);
- this.mixins.observable.constructor.call(this, config);
- },
- /**
- * @cfg
- * Called by the Controller's {@link #application} to initialize the Controller. This is always called before the
- * {@link Ext.app.Application Application} launches, giving the Controller a chance to run any pre-launch logic.
- * See also {@link #launch}, which is called after the {@link Ext.app.Application#launch Application's launch function}
- */
- init: Ext.emptyFn,
- /**
- * @cfg
- * Called by the Controller's {@link #application} immediately after the Application's own
- * {@link Ext.app.Application#launch launch function} has been called. This is usually a good place to run any
- * logic that has to run after the app UI is initialized. See also {@link #init}, which is called before the
- * {@link Ext.app.Application#launch Application's launch function}.
- */
- launch: Ext.emptyFn,
- /**
- * Convenient way to redirect to a new url. See {@link Ext.app.Application#redirectTo} for full usage information.
- * @return {Object}
- */
- redirectTo: function(place) {
- return this.getApplication().redirectTo(place);
- },
- /**
- * @private
- * Executes an Ext.app.Action by giving it the correct before filters and kicking off execution
- */
- execute: function(action, skipFilters) {
- action.setBeforeFilters(this.getBefore()[action.getAction()]);
- action.execute();
- },
- /**
- * @private
- * Massages the before filters into an array of function references for each controller action
- */
- applyBefore: function(before) {
- var filters, name, length, i;
- for (name in before) {
- filters = Ext.Array.from(before[name]);
- length = filters.length;
- for (i = 0; i < length; i++) {
- filters[i] = this[filters[i]];
- }
- before[name] = filters;
- }
- return before;
- },
- /**
- * @private
- */
- applyControl: function(config) {
- this.control(config, this);
- return config;
- },
- /**
- * @private
- */
- applyRefs: function(refs) {
- //<debug>
- if (Ext.isArray(refs)) {
- Ext.Logger.deprecate("In Sencha Touch 2 the refs config accepts an object but you have passed it an array.");
- }
- //</debug>
- this.ref(refs);
- return refs;
- },
- /**
- * @private
- * Adds any routes specified in this Controller to the global Application router
- */
- applyRoutes: function(routes) {
- var app = this instanceof Ext.app.Application ? this : this.getApplication(),
- router = app.getRouter(),
- route, url, config;
- for (url in routes) {
- route = routes[url];
- config = {
- controller: this.$className
- };
- if (Ext.isString(route)) {
- config.action = route;
- } else {
- Ext.apply(config, route);
- }
- router.connect(url, config);
- }
- return routes;
- },
- /**
- * @private
- * As a convenience developers can locally qualify store names (e.g. 'MyStore' vs
- * 'MyApp.store.MyStore'). This just makes sure everything ends up fully qualified
- */
- applyStores: function(stores) {
- return this.getFullyQualified(stores, 'store');
- },
- /**
- * @private
- * As a convenience developers can locally qualify model names (e.g. 'MyModel' vs
- * 'MyApp.model.MyModel'). This just makes sure everything ends up fully qualified
- */
- applyModels: function(models) {
- return this.getFullyQualified(models, 'model');
- },
- /**
- * @private
- * As a convenience developers can locally qualify view names (e.g. 'MyView' vs
- * 'MyApp.view.MyView'). This just makes sure everything ends up fully qualified
- */
- applyViews: function(views) {
- return this.getFullyQualified(views, 'view');
- },
- /**
- * @private
- * Returns the fully qualified name for any class name variant. This is used to find the FQ name for the model,
- * view, controller, store and profiles listed in a Controller or Application.
- * @param {String[]} items The array of strings to get the FQ name for
- * @param {String} namespace If the name happens to be an application class, add it to this namespace
- * @return {String} The fully-qualified name of the class
- */
- getFullyQualified: function(items, namespace) {
- var length = items.length,
- appName = this.getApplication().getName(),
- name, i;
- for (i = 0; i < length; i++) {
- name = items[i];
- //we check name === appName to allow MyApp.profile.MyApp to exist
- if (Ext.isString(name) && (Ext.Loader.getPrefix(name) === "" || name === appName)) {
- items[i] = appName + '.' + namespace + '.' + name;
- }
- }
- return items;
- },
- /**
- * @private
- */
- control: function(selectors) {
- this.getApplication().control(selectors, this);
- },
- /**
- * @private
- * 1.x-inspired ref implementation
- */
- ref: function(refs) {
- var me = this,
- refName, getterName, selector, info;
- for (refName in refs) {
- selector = refs[refName];
- getterName = "get" + Ext.String.capitalize(refName);
- if (!this[getterName]) {
- if (Ext.isString(refs[refName])) {
- info = {
- ref: refName,
- selector: selector
- };
- } else {
- info = refs[refName];
- }
- this[getterName] = function(refName, info) {
- var args = [refName, info];
- return function() {
- return me.getRef.apply(me, args.concat.apply(args, arguments));
- };
- }(refName, info);
- }
- this.references = this.references || [];
- this.references.push(refName.toLowerCase());
- }
- },
- /**
- * @private
- */
- getRef: function(ref, info, config) {
- this.refCache = this.refCache || {};
- info = info || {};
- config = config || {};
- Ext.apply(info, config);
- if (info.forceCreate) {
- return Ext.ComponentManager.create(info, 'component');
- }
- var me = this,
- cached = me.refCache[ref];
- if (!cached) {
- me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
- if (!cached && info.autoCreate) {
- me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
- }
- if (cached) {
- cached.on('destroy', function() {
- me.refCache[ref] = null;
- });
- }
- }
- return cached;
- },
- /**
- * @private
- */
- hasRef: function(ref) {
- return this.references && this.references.indexOf(ref.toLowerCase()) !== -1;
- }
- }, function() {
- });
- /**
- * @author Ed Spencer
- * @private
- *
- * Manages the stack of {@link Ext.app.Action} instances that have been decoded, pushes new urls into the browser's
- * location object and listens for changes in url, firing the {@link #change} event when a change is detected.
- *
- * This is tied to an {@link Ext.app.Application Application} instance. The Application performs all of the
- * interactions with the History object, no additional integration should be required.
- */
- Ext.define('Ext.app.History', {
- mixins: ['Ext.mixin.Observable'],
- /**
- * @event change
- * Fires when a change in browser url is detected
- * @param {String} url The new url, after the hash (e.g. http://myapp.com/#someUrl returns 'someUrl')
- */
- config: {
- /**
- * @cfg {Array} actions The stack of {@link Ext.app.Action action} instances that have occurred so far
- */
- actions: [],
- /**
- * @cfg {Boolean} updateUrl `true` to automatically update the browser's url when {@link #add} is called.
- */
- updateUrl: true,
- /**
- * @cfg {String} token The current token as read from the browser's location object.
- */
- token: ''
- },
- constructor: function(config) {
- if (Ext.feature.has.History) {
- window.addEventListener('hashchange', Ext.bind(this.detectStateChange, this));
- }
- else {
- this.setToken(window.location.hash.substr(1));
- setInterval(Ext.bind(this.detectStateChange, this), 100);
- }
- this.initConfig(config);
- },
- /**
- * Adds an {@link Ext.app.Action Action} to the stack, optionally updating the browser's url and firing the
- * {@link #change} event.
- * @param {Ext.app.Action} action The Action to add to the stack.
- * @param {Boolean} silent Cancels the firing of the {@link #change} event if `true`.
- */
- add: function(action, silent) {
- this.getActions().push(Ext.factory(action, Ext.app.Action));
- var url = action.getUrl();
- if (this.getUpdateUrl()) {
- // history.pushState({}, action.getTitle(), "#" + action.getUrl());
- this.setToken(url);
- window.location.hash = url;
- }
- if (silent !== true) {
- this.fireEvent('change', url);
- }
- this.setToken(url);
- },
- /**
- * Navigate to the previous active action. This changes the page url.
- */
- back: function() {
- var actions = this.getActions(),
- previousAction = actions[actions.length - 2],
- app = previousAction.getController().getApplication();
- actions.pop();
- app.redirectTo(previousAction.getUrl());
- },
- /**
- * @private
- */
- applyToken: function(token) {
- return token[0] == '#' ? token.substr(1) : token;
- },
- /**
- * @private
- */
- detectStateChange: function() {
- var newToken = this.applyToken(window.location.hash),
- oldToken = this.getToken();
- if (newToken != oldToken) {
- this.onStateChange();
- this.setToken(newToken);
- }
- },
- /**
- * @private
- */
- onStateChange: function() {
- this.fireEvent('change', window.location.hash.substr(1));
- }
- });
- /**
- * @author Ed Spencer
- *
- * A Profile represents a range of devices that fall under a common category. For the vast majority of apps that use
- * device profiles, the app defines a Phone profile and a Tablet profile. Doing this enables you to easily customize
- * the experience for the different sized screens offered by those device types.
- *
- * Only one Profile can be active at a time, and each Profile defines a simple {@link #isActive} function that should
- * return either true or false. The first Profile to return true from its isActive function is set as your Application's
- * {@link Ext.app.Application#currentProfile current profile}.
- *
- * A Profile can define any number of {@link #models}, {@link #views}, {@link #controllers} and {@link #stores} which
- * will be loaded if the Profile is activated. It can also define a {@link #launch} function that will be called after
- * all of its dependencies have been loaded, just before the {@link Ext.app.Application#launch application launch}
- * function is called.
- *
- * ## Sample Usage
- *
- * First you need to tell your Application about your Profile(s):
- *
- * Ext.application({
- * name: 'MyApp',
- * profiles: ['Phone', 'Tablet']
- * });
- *
- * This will load app/profile/Phone.js and app/profile/Tablet.js. Here's how we might define the Phone profile:
- *
- * Ext.define('MyApp.profile.Phone', {
- * extend: 'Ext.app.Profile',
- *
- * views: ['Main'],
- *
- * isActive: function() {
- * return Ext.os.is.Phone;
- * }
- * });
- *
- * The isActive function returns true if we detect that we are running on a phone device. If that is the case the
- * Application will set this Profile active and load the 'Main' view specified in the Profile's {@link #views} config.
- *
- * ## Class Specializations
- *
- * Because Profiles are specializations of an application, all of the models, views, controllers and stores defined
- * in a Profile are expected to be namespaced under the name of the Profile. Here's an expanded form of the example
- * above:
- *
- * Ext.define('MyApp.profile.Phone', {
- * extend: 'Ext.app.Profile',
- *
- * views: ['Main'],
- * controllers: ['Signup'],
- * models: ['MyApp.model.Group'],
- *
- * isActive: function() {
- * return Ext.os.is.Phone;
- * }
- * });
- *
- * In this case, the Profile is going to load *app/view/phone/Main.js*, *app/controller/phone/Signup.js* and
- * *app/model/Group.js*. Notice that in each of the first two cases the name of the profile ('phone' in this case) was
- * injected into the class names. In the third case we specified the full Model name (for Group) so the Profile name
- * was not injected.
- *
- * For a fuller understanding of the ideas behind Profiles and how best to use them in your app, we suggest you read
- * the [device profiles guide](#!/guide/profiles).
- *
- * @aside guide profiles
- */
- Ext.define('Ext.app.Profile', {
- mixins: {
- observable: "Ext.mixin.Observable"
- },
- config: {
- /**
- * @cfg {String} namespace The namespace that this Profile's classes can be found in. Defaults to the lowercased
- * Profile {@link #name}, for example a Profile called MyApp.profile.Phone will by default have a 'phone'
- * namespace, which means that this Profile's additional models, stores, views and controllers will be loaded
- * from the MyApp.model.phone.*, MyApp.store.phone.*, MyApp.view.phone.* and MyApp.controller.phone.* namespaces
- * respectively.
- * @accessor
- */
- namespace: 'auto',
- /**
- * @cfg {String} name The name of this Profile. Defaults to the last section of the class name (e.g. a profile
- * called MyApp.profile.Phone will default the name to 'Phone').
- * @accessor
- */
- name: 'auto',
- /**
- * @cfg {Array} controllers Any additional {@link Ext.app.Application#controllers Controllers} to load for this
- * profile. Note that each item here will be prepended with the Profile namespace when loaded. Example usage:
- *
- * controllers: [
- * 'Users',
- * 'MyApp.controller.Products'
- * ]
- *
- * This will load *MyApp.controller.tablet.Users* and *MyApp.controller.Products*.
- * @accessor
- */
- controllers: [],
- /**
- * @cfg {Array} models Any additional {@link Ext.app.Application#models Models} to load for this profile. Note
- * that each item here will be prepended with the Profile namespace when loaded. Example usage:
- *
- * models: [
- * 'Group',
- * 'MyApp.model.User'
- * ]
- *
- * This will load *MyApp.model.tablet.Group* and *MyApp.model.User*.
- * @accessor
- */
- models: [],
- /**
- * @cfg {Array} views Any additional {@link Ext.app.Application#views views} to load for this profile. Note
- * that each item here will be prepended with the Profile namespace when loaded. Example usage:
- *
- * views: [
- * 'Main',
- * 'MyApp.view.Login'
- * ]
- *
- * This will load *MyApp.view.tablet.Main* and *MyApp.view.Login*.
- * @accessor
- */
- views: [],
- /**
- * @cfg {Array} stores Any additional {@link Ext.app.Application#stores Stores} to load for this profile. Note
- * that each item here will be prepended with the Profile namespace when loaded. Example usage:
- *
- * stores: [
- * 'Users',
- * 'MyApp.store.Products'
- * ]
- *
- * This will load *MyApp.store.tablet.Users* and *MyApp.store.Products*.
- * @accessor
- */
- stores: [],
- /**
- * @cfg {Ext.app.Application} application The {@link Ext.app.Application Application} instance that this
- * Profile is bound to. This is set automatically.
- * @accessor
- * @readonly
- */
- application: null
- },
- /**
- * Creates a new Profile instance
- */
- constructor: function(config) {
- this.initConfig(config);
- this.mixins.observable.constructor.apply(this, arguments);
- },
- /**
- * Determines whether or not this Profile is active on the device isActive is executed on. Should return true if
- * this profile is meant to be active on this device, false otherwise. Each Profile should implement this function
- * (the default implementation just returns false).
- * @return {Boolean} True if this Profile should be activated on the device it is running on, false otherwise
- */
- isActive: function() {
- return false;
- },
- /**
- * @method
- * The launch function is called by the {@link Ext.app.Application Application} if this Profile's {@link #isActive}
- * function returned true. This is typically the best place to run any profile-specific app launch code. Example
- * usage:
- *
- * launch: function() {
- * Ext.create('MyApp.view.tablet.Main');
- * }
- */
- launch: Ext.emptyFn,
- /**
- * @private
- */
- applyNamespace: function(name) {
- if (name == 'auto') {
- name = this.getName();
- }
- return name.toLowerCase();
- },
- /**
- * @private
- */
- applyName: function(name) {
- if (name == 'auto') {
- var pieces = this.$className.split('.');
- name = pieces[pieces.length - 1];
- }
- return name;
- },
- /**
- * @private
- * Computes the full class names of any specified model, view, controller and store dependencies, returns them in
- * an object map for easy loading
- */
- getDependencies: function() {
- var allClasses = [],
- format = Ext.String.format,
- appName = this.getApplication().getName(),
- namespace = this.getNamespace(),
- map = {
- model: this.getModels(),
- view: this.getViews(),
- controller: this.getControllers(),
- store: this.getStores()
- },
- classType, classNames, fullyQualified;
- for (classType in map) {
- classNames = [];
- Ext.each(map[classType], function(className) {
- if (Ext.isString(className)) {
- //we check name === appName to allow MyApp.profile.MyApp to exist
- if (Ext.isString(className) && (Ext.Loader.getPrefix(className) === "" || className === appName)) {
- className = appName + '.' + classType + '.' + namespace + '.' + className;
- }
- classNames.push(className);
- allClasses.push(className);
- }
- }, this);
- map[classType] = classNames;
- }
- map.all = allClasses;
- return map;
- }
- });
- /**
- * @author Ed Spencer
- * @private
- *
- * Represents a mapping between a url and a controller/action pair. May also contain additional params. This is a
- * private internal class that should not need to be used by end-developer code. Its API and existence are subject to
- * change so use at your own risk.
- *
- * For information on how to use routes we suggest reading the following guides:
- *
- * - [Using History Support](#!/guide/history_support)
- * - [Intro to Applications](#!/guide/apps_intro)
- * - [Using Controllers](#!/guide/controllers)
- *
- */
- Ext.define('Ext.app.Route', {
- config: {
- /**
- * @cfg {Object} conditions Optional set of conditions for each token in the url string. Each key should be one
- * of the tokens, each value should be a regex that the token should accept. For example, if you have a Route
- * with a url like "files/:fileName" and you want it to match urls like "files/someImage.jpg" then you can set
- * these conditions to allow the :fileName token to accept strings containing a period ("."):
- *
- * conditions: {
- * ':fileName': "[0-9a-zA-Z\.]+"
- * }
- *
- */
- conditions: {},
- /**
- * @cfg {String} url (required) The url regex to match against.
- */
- url: null,
- /**
- * @cfg {String} controller The name of the Controller whose {@link #action} will be called if this route is
- * matched.
- */
- controller: null,
- /**
- * @cfg {String} action The name of the action that will be called on the {@link #controller} if this route is
- * matched.
- */
- action: null,
- /**
- * @private
- * @cfg {Boolean} initialized Indicates whether or not this Route has been initialized. We don't initialize
- * straight away so as to save unnecessary processing.
- */
- initialized: false
- },
- constructor: function(config) {
- this.initConfig(config);
- },
- /**
- * Attempts to recognize a given url string and return controller/action pair for it.
- * @param {String} url The url to recognize.
- * @return {Object/Boolean} The matched data, or `false` if no match.
- */
- recognize: function(url) {
- if (!this.getInitialized()) {
- this.initialize();
- }
- if (this.recognizes(url)) {
- var matches = this.matchesFor(url),
- args = url.match(this.matcherRegex);
- args.shift();
- return Ext.applyIf(matches, {
- controller: this.getController(),
- action : this.getAction(),
- historyUrl: url,
- args : args
- });
- }
- },
- /**
- * @private
- * Sets up the relevant regular expressions used to match against this route.
- */
- initialize: function() {
- /*
- * The regular expression we use to match a segment of a route mapping
- * this will recognize segments starting with a colon,
- * e.g. on 'namespace/:controller/:action', :controller and :action will be recognized
- */
- this.paramMatchingRegex = new RegExp(/:([0-9A-Za-z\_]*)/g);
- /*
- * Converts a route string into an array of symbols starting with a colon. e.g.
- * ":controller/:action/:id" => [':controller', ':action', ':id']
- */
- this.paramsInMatchString = this.getUrl().match(this.paramMatchingRegex) || [];
- this.matcherRegex = this.createMatcherRegex(this.getUrl());
- this.setInitialized(true);
- },
- /**
- * @private
- * Returns true if this Route matches the given url string
- * @param {String} url The url to test
- * @return {Boolean} True if this Route recognizes the url
- */
- recognizes: function(url) {
- return this.matcherRegex.test(url);
- },
- /**
- * @private
- * Returns a hash of matching url segments for the given url.
- * @param {String} url The url to extract matches for
- * @return {Object} matching url segments
- */
- matchesFor: function(url) {
- var params = {},
- keys = this.paramsInMatchString,
- values = url.match(this.matcherRegex),
- length = keys.length,
- i;
- //first value is the entire match so reject
- values.shift();
- for (i = 0; i < length; i++) {
- params[keys[i].replace(":", "")] = values[i];
- }
- return params;
- },
- /**
- * @private
- * Returns an array of matching url segments for the given url.
- * @param {String} url The url to extract matches for
- * @return {Array} matching url segments
- */
- argsFor: function(url) {
- var args = [],
- keys = this.paramsInMatchString,
- values = url.match(this.matcherRegex),
- length = keys.length,
- i;
- //first value is the entire match so reject
- values.shift();
- for (i = 0; i < length; i++) {
- args.push(keys[i].replace(':', ""));
- params[keys[i].replace(":", "")] = values[i];
- }
- return params;
- },
- /**
- * @private
- * Constructs a url for the given config object by replacing wildcard placeholders in the Route's url
- * @param {Object} config The config object
- * @return {String} The constructed url
- */
- urlFor: function(config) {
- var url = this.getUrl();
- for (var key in config) {
- url = url.replace(":" + key, config[key]);
- }
- return url;
- },
- /**
- * @private
- * Takes the configured url string including wildcards and returns a regex that can be used to match
- * against a url
- * @param {String} url The url string
- * @return {RegExp} The matcher regex
- */
- createMatcherRegex: function(url) {
- /**
- * Converts a route string into an array of symbols starting with a colon. e.g.
- * ":controller/:action/:id" => [':controller', ':action', ':id']
- */
- var paramsInMatchString = this.paramsInMatchString,
- length = paramsInMatchString.length,
- i, cond, matcher;
- for (i = 0; i < length; i++) {
- cond = this.getConditions()[paramsInMatchString[i]];
- matcher = Ext.util.Format.format("({0})", cond || "[%a-zA-Z0-9\-\\_\\s,]+");
- url = url.replace(new RegExp(paramsInMatchString[i]), matcher);
- }
- //we want to match the whole string, so include the anchors
- return new RegExp("^" + url + "$");
- }
- });
- /**
- * @author Ed Spencer
- * @private
- *
- * The Router is an ordered set of route definitions that decode a url into a controller function to execute. Each
- * route defines a type of url to match, along with the controller function to call if it is matched. The Router is
- * usually managed exclusively by an {@link Ext.app.Application Application}, which also uses a
- * {@link Ext.app.History History} instance to find out when the browser's url has changed.
- *
- * Routes are almost always defined inside a {@link Ext.app.Controller Controller}, as opposed to on the Router itself.
- * End-developers should not usually need to interact directly with the Router as the Application and Controller
- * classes manage everything automatically. See the {@link Ext.app.Controller Controller documentation} for more
- * information on specifying routes.
- */
- Ext.define('Ext.app.Router', {
- requires: ['Ext.app.Route'],
- config: {
- /**
- * @cfg {Array} routes The set of routes contained within this Router.
- * @readonly
- */
- routes: [],
- /**
- * @cfg {Object} defaults Default configuration options for each Route connected to this Router.
- */
- defaults: {
- action: 'index'
- }
- },
- constructor: function(config) {
- this.initConfig(config);
- },
- /**
- * Connects a url-based route to a controller/action pair plus additional params.
- * @param {String} url The url to recognize.
- */
- connect: function(url, params) {
- params = Ext.apply({url: url}, params || {}, this.getDefaults());
- var route = Ext.create('Ext.app.Route', params);
- this.getRoutes().push(route);
- return route;
- },
- /**
- * Recognizes a url string connected to the Router, return the controller/action pair plus any additional
- * config associated with it.
- * @param {String} url The url to recognize.
- * @return {Object/undefined} If the url was recognized, the controller and action to call, else `undefined`.
- */
- recognize: function(url) {
- var routes = this.getRoutes(),
- length = routes.length,
- i, result;
- for (i = 0; i < length; i++) {
- result = routes[i].recognize(url);
- if (result !== undefined) {
- return result;
- }
- }
- return undefined;
- },
- /**
- * Convenience method which just calls the supplied function with the Router instance. Example usage:
- *
- * Ext.Router.draw(function(map) {
- * map.connect('activate/:token', {controller: 'users', action: 'activate'});
- * map.connect('home', {controller: 'index', action: 'home'});
- * });
- *
- * @param {Function} fn The fn to call
- */
- draw: function(fn) {
- fn.call(this, this);
- },
- /**
- * @private
- */
- clear: function() {
- this.setRoutes([]);
- }
- }, function() {
- });
- /**
- * @author Ed Spencer
- *
- * @aside guide apps_intro
- * @aside guide first_app
- * @aside video mvc-part-1
- * @aside video mvc-part-2
- *
- * Ext.app.Application defines the set of {@link Ext.data.Model Models}, {@link Ext.app.Controller Controllers},
- * {@link Ext.app.Profile Profiles}, {@link Ext.data.Store Stores} and {@link Ext.Component Views} that an application
- * consists of. It automatically loads all of those dependencies and can optionally specify a {@link #launch} function
- * that will be called when everything is ready.
- *
- * Sample usage:
- *
- * Ext.application({
- * name: 'MyApp',
- *
- * models: ['User', 'Group'],
- * stores: ['Users'],
- * controllers: ['Users'],
- * views: ['Main', 'ShowUser'],
- *
- * launch: function() {
- * Ext.create('MyApp.view.Main');
- * }
- * });
- *
- * Creating an Application instance is the only time in Sencha Touch 2 that we don't use Ext.create to create the new
- * instance. Instead, the {@link Ext#application} function instantiates an Ext.app.Application internally,
- * automatically loading the Ext.app.Application class if it is not present on the page already and hooking in to
- * {@link Ext#onReady} before creating the instance itself. An alternative is to use Ext.create inside an Ext.onReady
- * callback, but Ext.application is preferred.
- *
- * ## Dependencies
- *
- * Application follows a simple convention when it comes to specifying the controllers, views, models, stores and
- * profiles it requires. By default it expects each of them to be found inside the *app/controller*, *app/view*,
- * *app/model*, *app/store* and *app/profile* directories in your app - if you follow this convention you can just
- * specify the last part of each class name and Application will figure out the rest for you:
- *
- * Ext.application({
- * name: 'MyApp',
- *
- * controllers: ['Users'],
- * models: ['User', 'Group'],
- * stores: ['Users'],
- * views: ['Main', 'ShowUser']
- * });
- *
- * The example above will load 6 files:
- *
- * - app/model/User.js
- * - app/model/Group.js
- * - app/store/Users.js
- * - app/controller/Users.js
- * - app/view/Main.js
- * - app/view/ShowUser.js
- *
- * ### Nested Dependencies
- *
- * For larger apps it's common to split the models, views and controllers into subfolders so keep the project
- * organized. This is especially true of views - it's not unheard of for large apps to have over a hundred separate
- * view classes so organizing them into folders can make maintenance much simpler.
- *
- * To specify dependencies in subfolders just use a period (".") to specify the folder:
- *
- * Ext.application({
- * name: 'MyApp',
- *
- * controllers: ['Users', 'nested.MyController'],
- * views: ['products.Show', 'products.Edit', 'user.Login']
- * });
- *
- * In this case these 5 files will be loaded:
- *
- * - app/controller/Users.js
- * - app/controller/nested/MyController.js
- * - app/view/products/Show.js
- * - app/view/products/Edit.js
- * - app/view/user/Login.js
- *
- * Note that we can mix and match within each configuration here - for each model, view, controller, profile or store
- * you can specify either just the final part of the class name (if you follow the directory conventions), or the full
- * class name.
- *
- * ### External Dependencies
- *
- * Finally, we can specify application dependencies from outside our application by fully-qualifying the classes we
- * want to load. A common use case for this is sharing authentication logic between multiple applications. Perhaps you
- * have several apps that login via a common user database and you want to share that code between them. An easy way to
- * do this is to create a folder alongside your app folder and then add its contents as dependencies for your app.
- *
- * For example, let's say our shared login code contains a login controller, a user model and a login form view. We
- * want to use all of these in our application:
- *
- * Ext.Loader.setPath({
- * 'Auth': 'Auth'
- * });
- *
- * Ext.application({
- * views: ['Auth.view.LoginForm', 'Welcome'],
- * controllers: ['Auth.controller.Sessions', 'Main'],
- * models: ['Auth.model.User']
- * });
- *
- * This will load the following files:
- *
- * - Auth/view/LoginForm.js
- * - Auth/controller/Sessions.js
- * - Auth/model/User.js
- * - app/view/Welcome.js
- * - app/controller/Main.js
- *
- * The first three were loaded from outside our application, the last two from the application itself. Note how we can
- * still mix and match application files and external dependency files.
- *
- * Note that to enable the loading of external dependencies we just have to tell the Loader where to find those files,
- * which is what we do with the Ext.Loader.setPath call above. In this case we're telling the Loader to find any class
- * starting with the 'Auth' namespace inside our 'Auth' folder. This means we can drop our common Auth code into our
- * application alongside the app folder and the framework will be able to figure out how to load everything.
- *
- * ## Launching
- *
- * Each Application can define a {@link Ext.app.Application#launch launch} function, which is called as soon as all of
- * your app's classes have been loaded and the app is ready to be launched. This is usually the best place to put any
- * application startup logic, typically creating the main view structure for your app.
- *
- * In addition to the Application launch function, there are two other places you can put app startup logic. Firstly,
- * each Controller is able to define an {@link Ext.app.Controller#init init} function, which is called before the
- * Application launch function. Secondly, if you are using Device Profiles, each Profile can define a
- * {@link Ext.app.Profile#launch launch} function, which is called after the Controller init functions but before the
- * Application launch function.
- *
- * Note that only the active Profile has its launch function called - for example if you define profiles for Phone and
- * Tablet and then launch the app on a tablet, only the Tablet Profile's launch function is called.
- *
- * 1. Controller#init functions called
- * 2. Profile#launch function called
- * 3. Application#launch function called
- * 4. Controller#launch functions called
- *
- * When using Profiles it is common to place most of the bootup logic inside the Profile launch function because each
- * Profile has a different set of views that need to be constructed at startup.
- *
- * ## Adding to Home Screen
- *
- * iOS devices allow your users to add your app to their home screen for easy access. iOS allows you to customize
- * several aspects of this, including the icon that will appear on the home screen and the startup image. These can be
- * specified in the Ext.application setup block:
- *
- * Ext.application({
- * name: 'MyApp',
- *
- * {@link #icon}: 'resources/img/icon.png',
- * {@link #isIconPrecomposed}: false,
- * {@link #startupImage}: {
- * '320x460': 'resources/startup/320x460.jpg',
- * '640x920': 'resources/startup/640x920.png',
- * '640x1096': 'resources/startup/640x1096.png',
- * '768x1004': 'resources/startup/768x1004.png',
- * '748x1024': 'resources/startup/748x1024.png',
- * '1536x2008': 'resources/startup/1536x2008.png',
- * '1496x2048': 'resources/startup/1496x2048.png'
- * }
- * });
- *
- * When the user adds your app to the home screen, your resources/img/icon.png file will be used as the application
- * {@link #icon}. We also used the {@link #isIconPrecomposed} configuration to turn off the gloss effect that is automatically added
- * to icons in iOS. Finally we used the {@link #startupImage} configuration to provide the images that will be displayed
- * while your application is starting up. See also {@link #statusBarStyle}.
- *
- * ## Find out more
- *
- * If you are not already familiar with writing applications with Sencha Touch 2 we recommend reading the
- * [intro to applications guide](#!/guide/apps_intro), which lays out the core principles of writing apps
- * with Sencha Touch 2.
- */
- Ext.define('Ext.app.Application', {
- extend: 'Ext.app.Controller',
- requires: [
- 'Ext.app.History',
- 'Ext.app.Profile',
- 'Ext.app.Router',
- 'Ext.app.Action'
- ],
- config: {
- /**
- * @cfg {String/Object} icon
- * Specifies a set of URLs to the application icon for different device form factors. This icon is displayed
- * when the application is added to the device's Home Screen.
- *
- * Ext.setup({
- * icon: {
- * 57: 'resources/icons/Icon.png',
- * 72: 'resources/icons/Icon~ipad.png',
- * 114: 'resources/icons/Icon@2x.png',
- * 144: 'resources/icons/Icon~ipad@2x.png'
- * },
- * onReady: function() {
- * // ...
- * }
- * });
- *
- * Each key represents the dimension of the icon as a square shape. For example: '57' is the key for a 57 x 57
- * icon image. Here is the breakdown of each dimension and its device target:
- *
- * - 57: Non-retina iPhone, iPod touch, and all Android devices
- * - 72: Retina iPhone and iPod touch
- * - 114: Non-retina iPad (first and second generation)
- * - 144: Retina iPad (third generation)
- *
- * Note that the dimensions of the icon images must be exactly 57x57, 72x72, 114x114 and 144x144 respectively.
- *
- * It is highly recommended that you provide all these different sizes to accommodate a full range of
- * devices currently available. However if you only have one icon in one size, make it 57x57 in size and
- * specify it as a string value. This same icon will be used on all supported devices.
- *
- * Ext.application({
- * icon: 'resources/icons/Icon.png',
- * launch: function() {
- * // ...
- * }
- * });
- */
-
- /**
- * @cfg {Object} startupImage
- * Specifies a set of URLs to the application startup images for different device form factors. This image is
- * displayed when the application is being launched from the Home Screen icon. Note that this currently only applies
- * to iOS devices.
- *
- * Ext.application({
- * startupImage: {
- * '320x460': 'resources/startup/320x460.jpg',
- * '640x920': 'resources/startup/640x920.png',
- * '640x1096': 'resources/startup/640x1096.png',
- * '768x1004': 'resources/startup/768x1004.png',
- * '748x1024': 'resources/startup/748x1024.png',
- * '1536x2008': 'resources/startup/1536x2008.png',
- * '1496x2048': 'resources/startup/1496x2048.png'
- * },
- * launch: function() {
- * // ...
- * }
- * });
- *
- * Each key represents the dimension of the image. For example: '320x460' is the key for a 320px x 460px image.
- * Here is the breakdown of each dimension and its device target:
- *
- * - 320x460: Non-retina iPhone, iPod touch, and all Android devices
- * - 640x920: Retina iPhone and iPod touch
- * - 640x1096: iPhone 5 and iPod touch (fifth generation)
- * - 768x1004: Non-retina iPad (first and second generation) in portrait orientation
- * - 748x1024: Non-retina iPad (first and second generation) in landscape orientation
- * - 1536x2008: Retina iPad (third generation) in portrait orientation
- * - 1496x2048: Retina iPad (third generation) in landscape orientation
- *
- * Please note that there's no automatic fallback mechanism for the startup images. In other words, if you don't specify
- * a valid image for a certain device, nothing will be displayed while the application is being launched on that device.
- */
-
- /**
- * @cfg {Boolean} isIconPrecomposed
- * `true` to not having a glossy effect added to the icon by the OS, which will preserve its exact look. This currently
- * only applies to iOS devices.
- */
- /**
- * @cfg {String} [statusBarStyle='black'] Allows you to set the style of the status bar when your app is added to the
- * home screen on iOS devices. Alternative is to set to 'black-translucent', which turns
- * the status bar semi-transparent and overlaps the app content. This is usually not a good option for web apps
- */
-
- /**
- * @cfg {String} tabletIcon Path to the _.png_ image file to use when your app is added to the home screen on an
- * iOS **tablet** device (iPad).
- * @deprecated 2.0.0 Please use the {@link #icon} configuration instead.
- */
- /**
- * @cfg {String} phoneIcon Path to the _.png_ image file to use when your app is added to the home screen on an
- * iOS **phone** device (iPhone or iPod).
- * @deprecated 2.0.0 Please use the {@link #icon} configuration instead.
- */
- /**
- * @cfg {Boolean} glossOnIcon If set to `false`, the 'gloss' effect added to home screen {@link #icon icons} on
- * iOS devices will be removed.
- * @deprecated 2.0.0 Please use the {@link #isIconPrecomposed} configuration instead.
- */
- /**
- * @cfg {String} phoneStartupScreen Path to the _.png_ image file that will be displayed while the app is
- * starting up once it has been added to the home screen of an iOS phone device (iPhone or iPod). This _.png_
- * file should be 320px wide and 460px high.
- * @deprecated 2.0.0 Please use the {@link #startupImage} configuration instead.
- */
- /**
- * @cfg {String} tabletStartupScreen Path to the _.png_ image file that will be displayed while the app is
- * starting up once it has been added to the home screen of an iOS tablet device (iPad). This _.png_ file should
- * be 768px wide and 1004px high.
- * @deprecated 2.0.0 Please use the {@link #startupImage} configuration instead.
- */
- /**
- * @cfg {Array} profiles The set of profiles to load for this Application. Each profile is expected to
- * exist inside the *app/profile* directory and define a class following the convention
- * AppName.profile.ProfileName. For example, in the code below, the classes *AppName.profile.Phone*
- * and *AppName.profile.Tablet* will be loaded. Note that we are able to specify
- * either the full class name (as with *AppName.profile.Tablet*) or just the final part of the class name
- * and leave Application to automatically prepend *AppName.profile.'* to each:
- *
- * profiles: [
- * 'Phone',
- * 'AppName.profile.Tablet',
- * 'SomeCustomNamespace.profile.Desktop'
- * ]
- * @accessor
- */
- profiles: [],
- /**
- * @cfg {Array} controllers The set of controllers to load for this Application. Each controller is expected to
- * exist inside the *app/controller* directory and define a class following the convention
- * AppName.controller.ControllerName. For example, in the code below, the classes *AppName.controller.Users*,
- * *AppName.controller.Groups* and *AppName.controller.Products* will be loaded. Note that we are able to specify
- * either the full class name (as with *AppName.controller.Products*) or just the final part of the class name
- * and leave Application to automatically prepend *AppName.controller.'* to each:
- *
- * controllers: [
- * 'Users',
- * 'Groups',
- * 'AppName.controller.Products',
- * 'SomeCustomNamespace.controller.Orders'
- * ]
- * @accessor
- */
- controllers: [],
- /**
- * @cfg {Ext.app.History} history The global {@link Ext.app.History History} instance attached to this
- * Application.
- * @accessor
- * @readonly
- */
- history: {},
- /**
- * @cfg {String} name The name of the Application. This should be a single word without spaces or periods
- * because it is used as the Application's global namespace. All classes in your application should be
- * namespaced undef the Application's name - for example if your application name is 'MyApp', your classes
- * should be named 'MyApp.model.User', 'MyApp.controller.Users', 'MyApp.view.Main' etc
- * @accessor
- */
- name: null,
- /**
- * @cfg {String} appFolder The path to the directory which contains all application's classes.
- * This path will be registered via {@link Ext.Loader#setPath} for the namespace specified in the {@link #name name} config.
- * @accessor
- */
- appFolder : 'app',
- /**
- * @cfg {Ext.app.Router} router The global {@link Ext.app.Router Router} instance attached to this Application.
- * @accessor
- * @readonly
- */
- router: {},
- /**
- * @cfg {Array} controllerInstances Used internally as the collection of instantiated controllers. Use {@link #getController} instead.
- * @private
- * @accessor
- */
- controllerInstances: [],
- /**
- * @cfg {Array} profileInstances Used internally as the collection of instantiated profiles.
- * @private
- * @accessor
- */
- profileInstances: [],
- /**
- * @cfg {Ext.app.Profile} currentProfile The {@link Ext.app.Profile Profile} that is currently active for the
- * Application. This is set once, automatically by the Application before launch.
- * @accessor
- * @readonly
- */
- currentProfile: null,
- /**
- * @cfg {Function} launch An optional function that will be called when the Application is ready to be
- * launched. This is normally used to render any initial UI required by your application
- * @accessor
- */
- launch: Ext.emptyFn,
- /**
- * @private
- * @cfg {Boolean} enableLoader Private config to disable loading of Profiles at application construct time.
- * This is used by Sencha's unit test suite to test _Application.js_ in isolation and is likely to be removed
- * in favor of a more pleasing solution by the time you use it.
- * @accessor
- */
- enableLoader: true,
- /**
- * @private
- * @cfg {String[]} requires An array of extra dependencies, to be required after this application's {@link #name} config
- * has been processed properly, but before anything else to ensure overrides get executed first.
- * @accessor
- */
- requires: []
- },
- /**
- * Constructs a new Application instance.
- */
- constructor: function(config) {
- config = config || {};
- Ext.applyIf(config, {
- application: this
- });
- this.initConfig(config);
- //it's common to pass in functions to an application but because they are not predictable config names they
- //aren't ordinarily placed onto this so we need to do it manually
- for (var key in config) {
- this[key] = config[key];
- }
- //<debug>
- Ext.Loader.setConfig({ enabled: true });
- //</debug>
- Ext.require(this.getRequires(), function() {
- if (this.getEnableLoader() !== false) {
- Ext.require(this.getProfiles(), this.onProfilesLoaded, this);
- }
- }, this);
- },
- /**
- * Dispatches a given {@link Ext.app.Action} to the relevant Controller instance. This is not usually called
- * directly by the developer, instead Sencha Touch's History support picks up on changes to the browser's url
- * and calls dispatch automatically.
- * @param {Ext.app.Action} action The action to dispatch.
- * @param {Boolean} [addToHistory=true] Sets the browser's url to the action's url.
- */
- dispatch: function(action, addToHistory) {
- action = action || {};
- Ext.applyIf(action, {
- application: this
- });
- action = Ext.factory(action, Ext.app.Action);
- if (action) {
- var profile = this.getCurrentProfile(),
- profileNS = profile ? profile.getNamespace() : undefined,
- controller = this.getController(action.getController(), profileNS);
- if (controller) {
- if (addToHistory !== false) {
- this.getHistory().add(action, true);
- }
- controller.execute(action);
- }
- }
- },
- /**
- * Redirects the browser to the given url. This only affects the url after the '#'. You can pass in either a String
- * or a Model instance - if a Model instance is defined its {@link Ext.data.Model#toUrl toUrl} function is called,
- * which returns a string representing the url for that model. Internally, this uses your application's
- * {@link Ext.app.Router Router} to decode the url into a matching controller action and then calls
- * {@link #dispatch}.
- * @param {String/Ext.data.Model} url The String url to redirect to.
- */
- redirectTo: function(url) {
- if (Ext.data && Ext.data.Model && url instanceof Ext.data.Model) {
- var record = url;
- url = record.toUrl();
- }
- var decoded = this.getRouter().recognize(url);
- if (decoded) {
- decoded.url = url;
- if (record) {
- decoded.data = {};
- decoded.data.record = record;
- }
- return this.dispatch(decoded);
- }
- },
- /**
- * @private
- * (documented on Controller's control config)
- */
- control: function(selectors, controller) {
- //if the controller is not defined, use this instead (the application instance)
- controller = controller || this;
- var dispatcher = this.getEventDispatcher(),
- refs = (controller) ? controller.getRefs() : {},
- selector, eventName, listener, listeners, ref;
- for (selector in selectors) {
- if (selectors.hasOwnProperty(selector)) {
- listeners = selectors[selector];
- ref = refs[selector];
- //refs can be used in place of selectors
- if (ref) {
- selector = ref.selector || ref;
- }
- for (eventName in listeners) {
- listener = listeners[eventName];
- if (Ext.isString(listener)) {
- listener = controller[listener];
- }
- dispatcher.addListener('component', selector, eventName, listener, controller);
- }
- }
- }
- },
- /**
- * Returns the Controller instance for the given controller name.
- * @param {String} name The name of the Controller.
- * @param {String} [profileName] Optional profile name. If passed, this is the same as calling
- * `getController('profileName.controllerName')`.
- */
- getController: function(name, profileName) {
- var instances = this.getControllerInstances(),
- appName = this.getName(),
- format = Ext.String.format,
- topLevelName;
- if (name instanceof Ext.app.Controller) {
- return name;
- }
- if (instances[name]) {
- return instances[name];
- } else {
- topLevelName = format("{0}.controller.{1}", appName, name);
- profileName = format("{0}.controller.{1}.{2}", appName, profileName, name);
- return instances[profileName] || instances[topLevelName];
- }
- },
- /**
- * @private
- * Callback that is invoked when all of the configured Profiles have been loaded. Detects the current profile and
- * gathers any additional dependencies from that profile, then loads all of those dependencies.
- */
- onProfilesLoaded: function() {
- var profiles = this.getProfiles(),
- length = profiles.length,
- instances = [],
- requires = this.gatherDependencies(),
- current, i, profileDeps;
- for (i = 0; i < length; i++) {
- instances[i] = Ext.create(profiles[i], {
- application: this
- });
- /*
- * Note that we actually require all of the dependencies for all Profiles - this is so that we can produce
- * a single build file that will work on all defined Profiles. Although the other classes will be loaded,
- * the correct Profile will still be identified and the other classes ignored. While this feels somewhat
- * inefficient, the majority of the bulk of an application is likely to be the framework itself. The bigger
- * the app though, the bigger the effect of this inefficiency so ideally we will create a way to create and
- * load Profile-specific builds in a future release.
- */
- profileDeps = instances[i].getDependencies();
- requires = requires.concat(profileDeps.all);
- if (instances[i].isActive() && !current) {
- current = instances[i];
- this.setCurrentProfile(current);
- this.setControllers(this.getControllers().concat(profileDeps.controller));
- this.setModels(this.getModels().concat(profileDeps.model));
- this.setViews(this.getViews().concat(profileDeps.view));
- this.setStores(this.getStores().concat(profileDeps.store));
- }
- }
- this.setProfileInstances(instances);
- Ext.require(requires, this.loadControllerDependencies, this);
- },
- /**
- * @private
- * Controllers can also specify dependencies, so we grab them all here and require them.
- */
- loadControllerDependencies: function() {
- this.instantiateControllers();
- var controllers = this.getControllerInstances(),
- classes = [],
- stores = [],
- i, controller, controllerStores, name;
- for (name in controllers) {
- controller = controllers[name];
- controllerStores = controller.getStores();
- stores = stores.concat(controllerStores);
- classes = classes.concat(controller.getModels().concat(controller.getViews()).concat(controllerStores));
- }
- this.setStores(this.getStores().concat(stores));
- Ext.require(classes, this.onDependenciesLoaded, this);
- },
- /**
- * @private
- * Callback that is invoked when all of the Application, Controller and Profile dependencies have been loaded.
- * Launches the controllers, then the profile and application.
- */
- onDependenciesLoaded: function() {
- var me = this,
- profile = this.getCurrentProfile(),
- launcher = this.getLaunch(),
- controllers, name;
- this.instantiateStores();
- controllers = this.getControllerInstances();
- for (name in controllers) {
- controllers[name].init(this);
- }
- if (profile) {
- profile.launch();
- }
- launcher.call(me);
- for (name in controllers) {
- //<debug warn>
- if (controllers[name] && !(controllers[name] instanceof Ext.app.Controller)) {
- Ext.Logger.warn("The controller '" + name + "' doesn't have a launch method. Are you sure it extends from Ext.app.Controller?");
- } else {
- //</debug>
- controllers[name].launch(this);
- //<debug warn>
- }
- //</debug>
- }
- me.redirectTo(window.location.hash.substr(1));
- },
- /**
- * @private
- * Gathers up all of the previously computed MVCS dependencies into a single array that we can pass to {@link Ext#require}.
- */
- gatherDependencies: function() {
- var classes = this.getModels().concat(this.getViews()).concat(this.getControllers());
- Ext.each(this.getStores(), function(storeName) {
- if (Ext.isString(storeName)) {
- classes.push(storeName);
- }
- }, this);
- return classes;
- },
- /**
- * @private
- * Should be called after dependencies are loaded, instantiates all of the Stores specified in the {@link #stores}
- * config. For each item in the stores array we make sure the Store is instantiated. When strings are specified,
- * the corresponding _app/store/StoreName.js_ was loaded so we now instantiate a `MyApp.store.StoreName`, giving it the
- * id `StoreName`.
- */
- instantiateStores: function() {
- var stores = this.getStores(),
- length = stores.length,
- store, storeClass, storeName, splits, i;
- for (i = 0; i < length; i++) {
- store = stores[i];
- if (Ext.data && Ext.data.Store && !(store instanceof Ext.data.Store)) {
- if (Ext.isString(store)) {
- storeName = store;
- storeClass = Ext.ClassManager.classes[store];
- store = {
- xclass: store
- };
- //we don't want to wipe out a configured storeId in the app's Store subclass so need
- //to check for this first
- if (storeClass.prototype.defaultConfig.storeId === undefined) {
- splits = storeName.split('.');
- store.id = splits[splits.length - 1];
- }
- }
- stores[i] = Ext.factory(store, Ext.data.Store);
- }
- }
- this.setStores(stores);
- },
- /**
- * @private
- * Called once all of our controllers have been loaded
- */
- instantiateControllers: function() {
- var controllerNames = this.getControllers(),
- instances = {},
- length = controllerNames.length,
- name, i;
- for (i = 0; i < length; i++) {
- name = controllerNames[i];
- instances[name] = Ext.create(name, {
- application: this
- });
- }
- return this.setControllerInstances(instances);
- },
- /**
- * @private
- * As a convenience developers can locally qualify controller names (e.g. 'MyController' vs
- * 'MyApp.controller.MyController'). This just makes sure everything ends up fully qualified
- */
- applyControllers: function(controllers) {
- return this.getFullyQualified(controllers, 'controller');
- },
- /**
- * @private
- * As a convenience developers can locally qualify profile names (e.g. 'MyProfile' vs
- * 'MyApp.profile.MyProfile'). This just makes sure everything ends up fully qualified
- */
- applyProfiles: function(profiles) {
- return this.getFullyQualified(profiles, 'profile');
- },
- /**
- * @private
- * Checks that the name configuration has any whitespace, and trims them if found.
- */
- applyName: function(name) {
- var oldName;
- if (name && name.match(/ /g)) {
- oldName = name;
- name = name.replace(/ /g, "");
- // <debug>
- Ext.Logger.warn('Attempting to create an application with a name which contains whitespace ("' + oldName + '"). Renamed to "' + name + '".');
- // </debug>
- }
- return name;
- },
- /**
- * @private
- * Makes sure the app namespace exists, sets the `app` property of the namespace to this application and sets its
- * loading path (checks to make sure the path hadn't already been set via Ext.Loader.setPath)
- */
- updateName: function(newName) {
- Ext.ClassManager.setNamespace(newName + '.app', this);
- if (!Ext.Loader.config.paths[newName]) {
- Ext.Loader.setPath(newName, this.getAppFolder());
- }
- },
- /**
- * @private
- */
- applyRouter: function(config) {
- return Ext.factory(config, Ext.app.Router, this.getRouter());
- },
- /**
- * @private
- */
- applyHistory: function(config) {
- var history = Ext.factory(config, Ext.app.History, this.getHistory());
- history.on('change', this.onHistoryChange, this);
- return history;
- },
- /**
- * @private
- */
- onHistoryChange: function(url) {
- this.dispatch(this.getRouter().recognize(url), false);
- }
- }, function() {
- });
- /**
- * A class to replicate the behavior of the Contextual menu in BlackBerry 10.
- *
- * More information: http://docs.blackberry.com/en/developers/deliverables/41577/contextual_menus.jsp
- *
- * var menu = Ext.create('Ext.bb.CrossCut', {
- * items: [
- * {
- * text: 'New',
- * iconMask: true,
- * iconCls: 'compose'
- * },
- * {
- * text: 'Reply',
- * iconMask: true,
- * iconCls: 'reply'
- * },
- * {
- * text: 'Settings',
- * iconMask: true,
- * iconCls: 'settings'
- * }
- * ]
- * });
- */
- Ext.define('Ext.bb.CrossCut', {
- extend: 'Ext.Sheet',
- xtype: 'crosscut',
- requires: [
- 'Ext.Button'
- ],
- config: {
- /**
- * @hide
- */
- top: 0,
- /**
- * @hide
- */
- right: 0,
- /**
- * @hide
- */
- bottom: 0,
- /**
- * @hide
- */
- left: null,
- /**
- * @hide
- */
- enter: 'right',
- /**
- * @hide
- */
- exit: 'right',
- /**
- * @hide
- */
- hideOnMaskTap: true,
- /**
- * @hide
- */
- baseCls: 'bb-crosscut',
- /**
- * @hide
- */
- layout: {
- type: 'vbox',
- pack: 'middle'
- },
- /**
- * @hide
- */
- defaultType: 'button',
- /**
- * @hide
- */
- showAnimation: {
- preserveEndState: true,
- to: {
- width: 275
- }
- },
- /**
- * @hide
- */
- hideAnimation: {
- preserveEndState: true,
- to: {
- width: 68
- }
- },
- defaults: {
- baseCls: 'bb-crosscut-item'
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.carousel.Item', {
- extend: 'Ext.Decorator',
- config: {
- baseCls: 'x-carousel-item',
- component: null,
- translatable: true
- }
- });
- /**
- * A private utility class used by Ext.Carousel to create indicators.
- * @private
- */
- Ext.define('Ext.carousel.Indicator', {
- extend: 'Ext.Component',
- xtype : 'carouselindicator',
- alternateClassName: 'Ext.Carousel.Indicator',
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'carousel-indicator',
- direction: 'horizontal'
- },
- /**
- * @event previous
- * Fires when this indicator is tapped on the left half
- * @param {Ext.carousel.Indicator} this
- */
- /**
- * @event next
- * Fires when this indicator is tapped on the right half
- * @param {Ext.carousel.Indicator} this
- */
- initialize: function() {
- this.callParent();
- this.indicators = [];
- this.element.on({
- tap: 'onTap',
- scope: this
- });
- },
- updateDirection: function(newDirection, oldDirection) {
- var baseCls = this.getBaseCls();
- this.element.replaceCls(oldDirection, newDirection, baseCls);
- if (newDirection === 'horizontal') {
- this.setBottom(0);
- this.setRight(null);
- }
- else {
- this.setRight(0);
- this.setBottom(null);
- }
- },
- addIndicator: function() {
- this.indicators.push(this.element.createChild({
- tag: 'span'
- }));
- },
- removeIndicator: function() {
- var indicators = this.indicators;
- if (indicators.length > 0) {
- indicators.pop().destroy();
- }
- },
- setActiveIndex: function(index) {
- var indicators = this.indicators,
- currentActiveIndex = this.activeIndex,
- currentActiveItem = indicators[currentActiveIndex],
- activeItem = indicators[index],
- baseCls = this.getBaseCls();
- if (currentActiveItem) {
- currentActiveItem.removeCls(baseCls, null, 'active');
- }
- if (activeItem) {
- activeItem.addCls(baseCls, null, 'active');
- }
- this.activeIndex = index;
- return this;
- },
- // @private
- onTap: function(e) {
- var touch = e.touch,
- box = this.element.getPageBox(),
- centerX = box.left + (box.width / 2),
- centerY = box.top + (box.height / 2),
- direction = this.getDirection();
- if ((direction === 'horizontal' && touch.pageX >= centerX) || (direction === 'vertical' && touch.pageY >= centerY)) {
- this.fireEvent('next', this);
- }
- else {
- this.fireEvent('previous', this);
- }
- },
- destroy: function() {
- var indicators = this.indicators,
- i, ln, indicator;
- for (i = 0,ln = indicators.length; i < ln; i++) {
- indicator = indicators[i];
- indicator.destroy();
- }
- indicators.length = 0;
- this.callParent();
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.util.TranslatableGroup', {
- extend: 'Ext.util.translatable.Abstract',
- config: {
- items: [],
- activeIndex: 0,
- itemLength: {
- x: 0,
- y: 0
- }
- },
- applyItems: function(items) {
- return Ext.Array.from(items);
- },
- doTranslate: function(x, y) {
- var items = this.getItems(),
- activeIndex = this.getActiveIndex(),
- itemLength = this.getItemLength(),
- itemLengthX = itemLength.x,
- itemLengthY = itemLength.y,
- useX = typeof x == 'number',
- useY = typeof y == 'number',
- offset, i, ln, item, translateX, translateY;
- for (i = 0, ln = items.length; i < ln; i++) {
- item = items[i];
- if (item) {
- offset = (i - activeIndex);
- if (useX) {
- translateX = x + offset * itemLengthX;
- }
- if (useY) {
- translateY = y + offset * itemLengthY;
- }
- item.translate(translateX, translateY);
- }
- }
- }
- });
- /**
- * @class Ext.carousel.Carousel
- * @author Jacky Nguyen <jacky@sencha.com>
- *
- * Carousels, like [tabs](#!/guide/tabs), are a great way to allow the user to swipe through multiple full-screen pages.
- * A Carousel shows only one of its pages at a time but allows you to swipe through with your finger.
- *
- * Carousels can be oriented either horizontally or vertically and are easy to configure - they just work like any other
- * Container. Here's how to set up a simple horizontal Carousel:
- *
- * @example
- * Ext.create('Ext.Carousel', {
- * fullscreen: true,
- *
- * defaults: {
- * styleHtmlContent: true
- * },
- *
- * items: [
- * {
- * html : 'Item 1',
- * style: 'background-color: #5E99CC'
- * },
- * {
- * html : 'Item 2',
- * style: 'background-color: #759E60'
- * },
- * {
- * html : 'Item 3'
- * }
- * ]
- * });
- *
- * We can also make Carousels orient themselves vertically:
- *
- * @example preview
- * Ext.create('Ext.Carousel', {
- * fullscreen: true,
- * direction: 'vertical',
- *
- * defaults: {
- * styleHtmlContent: true
- * },
- *
- * items: [
- * {
- * html : 'Item 1',
- * style: 'background-color: #759E60'
- * },
- * {
- * html : 'Item 2',
- * style: 'background-color: #5E99CC'
- * }
- * ]
- * });
- *
- * ### Common Configurations
- * * {@link #ui} defines the style of the carousel
- * * {@link #direction} defines the direction of the carousel
- * * {@link #indicator} defines if the indicator show be shown
- *
- * ### Useful Methods
- * * {@link #next} moves to the next card
- * * {@link #previous} moves to the previous card
- * * {@link #setActiveItem} moves to the passed card
- *
- * ## Further Reading
- *
- * For more information about Carousels see the [Carousel guide](#!/guide/carousel).
- *
- * @aside guide carousel
- * @aside example carousel
- */
- Ext.define('Ext.carousel.Carousel', {
- extend: 'Ext.Container',
- alternateClassName: 'Ext.Carousel',
- xtype: 'carousel',
- requires: [
- 'Ext.fx.easing.EaseOut',
- 'Ext.carousel.Item',
- 'Ext.carousel.Indicator',
- 'Ext.util.TranslatableGroup'
- ],
- config: {
- /**
- * @cfg layout
- * Hide layout config in Carousel. It only causes confusion.
- * @accessor
- * @private
- */
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: 'x-carousel',
- /**
- * @cfg {String} direction
- * The direction of the Carousel, either 'horizontal' or 'vertical'.
- * @accessor
- */
- direction: 'horizontal',
- directionLock: false,
- animation: {
- duration: 250,
- easing: {
- type: 'ease-out'
- }
- },
- /**
- * @cfg {Boolean} indicator
- * Provides an indicator while toggling between child items to let the user
- * know where they are in the card stack.
- * @accessor
- */
- indicator: true,
- /**
- * @cfg {String} ui
- * Style options for Carousel. Default is 'dark'. 'light' is also available.
- * @accessor
- */
- ui: 'dark',
- itemConfig: {},
- bufferSize: 1,
- itemLength: null
- },
- itemLength: 0,
- offset: 0,
- flickStartOffset: 0,
- flickStartTime: 0,
- dragDirection: 0,
- count: 0,
- painted: false,
- activeIndex: -1,
- beforeInitialize: function() {
- this.element.on({
- dragstart: 'onDragStart',
- drag: 'onDrag',
- dragend: 'onDragEnd',
- scope: this
- });
- this.element.on('resize', 'onSizeChange', this);
- this.carouselItems = [];
- this.orderedCarouselItems = [];
- this.inactiveCarouselItems = [];
- this.hiddenTranslation = 0;
- },
- updateBufferSize: function(size) {
- var ItemClass = Ext.carousel.Item,
- total = size * 2 + 1,
- isRendered = this.isRendered(),
- innerElement = this.innerElement,
- items = this.carouselItems,
- ln = items.length,
- itemConfig = this.getItemConfig(),
- itemLength = this.getItemLength(),
- direction = this.getDirection(),
- setterName = direction === 'horizontal' ? 'setWidth' : 'setHeight',
- i, item;
- for (i = ln; i < total; i++) {
- item = Ext.factory(itemConfig, ItemClass);
- if (itemLength) {
- item[setterName].call(item, itemLength);
- }
- item.setLayoutSizeFlags(this.LAYOUT_BOTH);
- items.push(item);
- innerElement.append(item.renderElement);
- if (isRendered && item.setRendered(true)) {
- item.fireEvent('renderedchange', this, item, true);
- }
- }
- this.getTranslatable().setActiveIndex(size);
- },
- setRendered: function(rendered) {
- var wasRendered = this.rendered;
- if (rendered !== wasRendered) {
- this.rendered = rendered;
- var items = this.items.items,
- carouselItems = this.carouselItems,
- i, ln, item;
- for (i = 0,ln = items.length; i < ln; i++) {
- item = items[i];
- if (!item.isInnerItem()) {
- item.setRendered(rendered);
- }
- }
- for (i = 0,ln = carouselItems.length; i < ln; i++) {
- carouselItems[i].setRendered(rendered);
- }
- return true;
- }
- return false;
- },
- onSizeChange: function() {
- this.refreshSizing();
- this.refreshCarouselItems();
- this.refreshActiveItem();
- },
- onItemAdd: function(item, index) {
- this.callParent(arguments);
- var innerIndex = this.getInnerItems().indexOf(item),
- indicator = this.getIndicator();
- if (indicator && item.isInnerItem()) {
- indicator.addIndicator();
- }
- if (innerIndex <= this.getActiveIndex()) {
- this.refreshActiveIndex();
- }
- if (this.isIndexDirty(innerIndex) && !this.isItemsInitializing) {
- this.refreshActiveItem();
- }
- },
- doItemLayoutAdd: function(item) {
- if (item.isInnerItem()) {
- return;
- }
- this.callParent(arguments);
- },
- onItemRemove: function(item, index) {
- this.callParent(arguments);
- var innerIndex = this.getInnerItems().indexOf(item),
- indicator = this.getIndicator(),
- carouselItems = this.carouselItems,
- i, ln, carouselItem;
- if (item.isInnerItem() && indicator) {
- indicator.removeIndicator();
- }
- if (innerIndex <= this.getActiveIndex()) {
- this.refreshActiveIndex();
- }
- if (this.isIndexDirty(innerIndex)) {
- for (i = 0,ln = carouselItems.length; i < ln; i++) {
- carouselItem = carouselItems[i];
- if (carouselItem.getComponent() === item) {
- carouselItem.setComponent(null);
- }
- }
- this.refreshActiveItem();
- }
- },
- doItemLayoutRemove: function(item) {
- if (item.isInnerItem()) {
- return;
- }
- this.callParent(arguments);
- },
- onInnerItemMove: function(item, toIndex, fromIndex) {
- if ((this.isIndexDirty(toIndex) || this.isIndexDirty(fromIndex))) {
- this.refreshActiveItem();
- }
- },
- doItemLayoutMove: function(item) {
- if (item.isInnerItem()) {
- return;
- }
- this.callParent(arguments);
- },
- isIndexDirty: function(index) {
- var activeIndex = this.getActiveIndex(),
- bufferSize = this.getBufferSize();
- return (index >= activeIndex - bufferSize && index <= activeIndex + bufferSize);
- },
- getTranslatable: function() {
- var translatable = this.translatable;
- if (!translatable) {
- this.translatable = translatable = new Ext.util.TranslatableGroup;
- translatable.setItems(this.orderedCarouselItems);
- translatable.on('animationend', 'onAnimationEnd', this);
- }
- return translatable;
- },
- onDragStart: function(e) {
- var direction = this.getDirection(),
- absDeltaX = e.absDeltaX,
- absDeltaY = e.absDeltaY,
- directionLock = this.getDirectionLock();
- this.isDragging = true;
- if (directionLock) {
- if ((direction === 'horizontal' && absDeltaX > absDeltaY)
- || (direction === 'vertical' && absDeltaY > absDeltaX)) {
- e.stopPropagation();
- }
- else {
- this.isDragging = false;
- return;
- }
- }
- this.getTranslatable().stopAnimation();
- this.dragStartOffset = this.offset;
- this.dragDirection = 0;
- },
- onDrag: function(e) {
- if (!this.isDragging) {
- return;
- }
- var startOffset = this.dragStartOffset,
- direction = this.getDirection(),
- delta = direction === 'horizontal' ? e.deltaX : e.deltaY,
- lastOffset = this.offset,
- flickStartTime = this.flickStartTime,
- dragDirection = this.dragDirection,
- now = Ext.Date.now(),
- currentActiveIndex = this.getActiveIndex(),
- maxIndex = this.getMaxItemIndex(),
- lastDragDirection = dragDirection,
- offset;
- if ((currentActiveIndex === 0 && delta > 0) || (currentActiveIndex === maxIndex && delta < 0)) {
- delta *= 0.5;
- }
- offset = startOffset + delta;
- if (offset > lastOffset) {
- dragDirection = 1;
- }
- else if (offset < lastOffset) {
- dragDirection = -1;
- }
- if (dragDirection !== lastDragDirection || (now - flickStartTime) > 300) {
- this.flickStartOffset = lastOffset;
- this.flickStartTime = now;
- }
- this.dragDirection = dragDirection;
- this.setOffset(offset);
- },
- onDragEnd: function(e) {
- if (!this.isDragging) {
- return;
- }
- this.onDrag(e);
- this.isDragging = false;
- var now = Ext.Date.now(),
- itemLength = this.itemLength,
- threshold = itemLength / 2,
- offset = this.offset,
- activeIndex = this.getActiveIndex(),
- maxIndex = this.getMaxItemIndex(),
- animationDirection = 0,
- flickDistance = offset - this.flickStartOffset,
- flickDuration = now - this.flickStartTime,
- indicator = this.getIndicator(),
- velocity;
- if (flickDuration > 0 && Math.abs(flickDistance) >= 10) {
- velocity = flickDistance / flickDuration;
- if (Math.abs(velocity) >= 1) {
- if (velocity < 0 && activeIndex < maxIndex) {
- animationDirection = -1;
- }
- else if (velocity > 0 && activeIndex > 0) {
- animationDirection = 1;
- }
- }
- }
- if (animationDirection === 0) {
- if (activeIndex < maxIndex && offset < -threshold) {
- animationDirection = -1;
- }
- else if (activeIndex > 0 && offset > threshold) {
- animationDirection = 1;
- }
- }
- if (indicator) {
- indicator.setActiveIndex(activeIndex - animationDirection);
- }
- this.animationDirection = animationDirection;
- this.setOffsetAnimated(animationDirection * itemLength);
- },
- applyAnimation: function(animation) {
- animation.easing = Ext.factory(animation.easing, Ext.fx.easing.EaseOut);
- return animation;
- },
- updateDirection: function(direction) {
- var indicator = this.getIndicator();
- this.currentAxis = (direction === 'horizontal') ? 'x' : 'y';
- if (indicator) {
- indicator.setDirection(direction);
- }
- },
- /**
- * @private
- * @chainable
- */
- setOffset: function(offset) {
- this.offset = offset;
- this.getTranslatable().translateAxis(this.currentAxis, offset + this.itemOffset);
- return this;
- },
- /**
- * @private
- * @return {Ext.carousel.Carousel} this
- * @chainable
- */
- setOffsetAnimated: function(offset) {
- var indicator = this.getIndicator();
- if (indicator) {
- indicator.setActiveIndex(this.getActiveIndex() - this.animationDirection);
- }
- this.offset = offset;
- this.getTranslatable().translateAxis(this.currentAxis, offset + this.itemOffset, this.getAnimation());
- return this;
- },
- onAnimationEnd: function(translatable) {
- var currentActiveIndex = this.getActiveIndex(),
- animationDirection = this.animationDirection,
- axis = this.currentAxis,
- currentOffset = translatable[axis],
- itemLength = this.itemLength,
- offset;
- if (animationDirection === -1) {
- offset = itemLength + currentOffset;
- }
- else if (animationDirection === 1) {
- offset = currentOffset - itemLength;
- }
- else {
- offset = currentOffset;
- }
- offset -= this.itemOffset;
- this.offset = offset;
- this.setActiveItem(currentActiveIndex - animationDirection);
- },
- refresh: function() {
- this.refreshSizing();
- this.refreshActiveItem();
- },
- refreshSizing: function() {
- var element = this.element,
- itemLength = this.getItemLength(),
- translatableItemLength = {
- x: 0,
- y: 0
- },
- itemOffset, containerSize;
- if (this.getDirection() === 'horizontal') {
- containerSize = element.getWidth();
- }
- else {
- containerSize = element.getHeight();
- }
- this.hiddenTranslation = -containerSize;
- if (itemLength === null) {
- itemLength = containerSize;
- itemOffset = 0;
- }
- else {
- itemOffset = (containerSize - itemLength) / 2;
- }
- this.itemLength = itemLength;
- this.itemOffset = itemOffset;
- translatableItemLength[this.currentAxis] = itemLength;
- this.getTranslatable().setItemLength(translatableItemLength);
- },
- refreshOffset: function() {
- this.setOffset(this.offset);
- },
- refreshActiveItem: function() {
- this.doSetActiveItem(this.getActiveItem());
- },
- /**
- * Returns the index of the currently active card.
- * @return {Number} The index of the currently active card.
- */
- getActiveIndex: function() {
- return this.activeIndex;
- },
- refreshActiveIndex: function() {
- this.activeIndex = this.getInnerItemIndex(this.getActiveItem());
- },
- refreshCarouselItems: function() {
- var items = this.carouselItems,
- i, ln, item;
- for (i = 0,ln = items.length; i < ln; i++) {
- item = items[i];
- item.getTranslatable().refresh();
- }
- this.refreshInactiveCarouselItems();
- },
- refreshInactiveCarouselItems: function() {
- var items = this.inactiveCarouselItems,
- hiddenTranslation = this.hiddenTranslation,
- axis = this.currentAxis,
- i, ln, item;
- for (i = 0,ln = items.length; i < ln; i++) {
- item = items[i];
- item.translateAxis(axis, hiddenTranslation);
- }
- },
- /**
- * @private
- * @return {Number}
- */
- getMaxItemIndex: function() {
- return this.innerItems.length - 1;
- },
- /**
- * @private
- * @return {Number}
- */
- getInnerItemIndex: function(item) {
- return this.innerItems.indexOf(item);
- },
- /**
- * @private
- * @return {Object}
- */
- getInnerItemAt: function(index) {
- return this.innerItems[index];
- },
- /**
- * @private
- * @return {Object}
- */
- applyActiveItem: function() {
- var activeItem = this.callParent(arguments),
- activeIndex;
- if (activeItem) {
- activeIndex = this.getInnerItemIndex(activeItem);
- if (activeIndex !== -1) {
- this.activeIndex = activeIndex;
- return activeItem;
- }
- }
- },
- doSetActiveItem: function(activeItem) {
- var activeIndex = this.getActiveIndex(),
- maxIndex = this.getMaxItemIndex(),
- indicator = this.getIndicator(),
- bufferSize = this.getBufferSize(),
- carouselItems = this.carouselItems.slice(),
- orderedCarouselItems = this.orderedCarouselItems,
- visibleIndexes = {},
- visibleItems = {},
- visibleItem, component, id, i, index, ln, carouselItem;
- if (carouselItems.length === 0) {
- return;
- }
- this.callParent(arguments);
- orderedCarouselItems.length = 0;
- if (activeItem) {
- id = activeItem.getId();
- visibleItems[id] = activeItem;
- visibleIndexes[id] = bufferSize;
- if (activeIndex > 0) {
- for (i = 1; i <= bufferSize; i++) {
- index = activeIndex - i;
- if (index >= 0) {
- visibleItem = this.getInnerItemAt(index);
- id = visibleItem.getId();
- visibleItems[id] = visibleItem;
- visibleIndexes[id] = bufferSize - i;
- }
- else {
- break;
- }
- }
- }
- if (activeIndex < maxIndex) {
- for (i = 1; i <= bufferSize; i++) {
- index = activeIndex + i;
- if (index <= maxIndex) {
- visibleItem = this.getInnerItemAt(index);
- id = visibleItem.getId();
- visibleItems[id] = visibleItem;
- visibleIndexes[id] = bufferSize + i;
- }
- else {
- break;
- }
- }
- }
- for (i = 0,ln = carouselItems.length; i < ln; i++) {
- carouselItem = carouselItems[i];
- component = carouselItem.getComponent();
- if (component) {
- id = component.getId();
- if (visibleIndexes.hasOwnProperty(id)) {
- carouselItems.splice(i, 1);
- i--;
- ln--;
- delete visibleItems[id];
- orderedCarouselItems[visibleIndexes[id]] = carouselItem;
- }
- }
- }
- for (id in visibleItems) {
- if (visibleItems.hasOwnProperty(id)) {
- visibleItem = visibleItems[id];
- carouselItem = carouselItems.pop();
- carouselItem.setComponent(visibleItem);
- orderedCarouselItems[visibleIndexes[id]] = carouselItem;
- }
- }
- }
- this.inactiveCarouselItems.length = 0;
- this.inactiveCarouselItems = carouselItems;
- this.refreshOffset();
- this.refreshInactiveCarouselItems();
- if (indicator) {
- indicator.setActiveIndex(activeIndex);
- }
- },
- /**
- * Switches to the next card.
- * @return {Ext.carousel.Carousel} this
- * @chainable
- */
- next: function() {
- this.setOffset(0);
- if (this.activeIndex === this.getMaxItemIndex()) {
- return this;
- }
- this.animationDirection = -1;
- this.setOffsetAnimated(-this.itemLength);
- return this;
- },
- /**
- * Switches to the previous card.
- * @return {Ext.carousel.Carousel} this
- * @chainable
- */
- previous: function() {
- this.setOffset(0);
- if (this.activeIndex === 0) {
- return this;
- }
- this.animationDirection = 1;
- this.setOffsetAnimated(this.itemLength);
- return this;
- },
- // @private
- applyIndicator: function(indicator, currentIndicator) {
- return Ext.factory(indicator, Ext.carousel.Indicator, currentIndicator);
- },
- // @private
- updateIndicator: function(indicator) {
- if (indicator) {
- this.insertFirst(indicator);
- indicator.setUi(this.getUi());
- indicator.on({
- next: 'next',
- previous: 'previous',
- scope: this
- });
- }
- },
- destroy: function() {
- var carouselItems = this.carouselItems.slice();
- this.carouselItems.length = 0;
- Ext.destroy(carouselItems, this.getIndicator(), this.translatable);
- this.callParent();
- delete this.carouselItems;
- }
- }, function() {
- });
- /**
- * @class Ext.carousel.Infinite
- * @author Jacky Nguyen <jacky@sencha.com>
- * @private
- *
- * The true infinite implementation of Carousel, private for now until it's stable to be public
- */
- Ext.define('Ext.carousel.Infinite', {
- extend: 'Ext.carousel.Carousel',
- config: {
- indicator: null,
- maxItemIndex: Infinity,
- innerItemConfig: {}
- },
- applyIndicator: function(indicator) {
- //<debug error>
- if (indicator) {
- Ext.Logger.error("'indicator' in Infinite Carousel implementation is not currently supported", this);
- }
- //</debug>
- return;
- },
- updateBufferSize: function(size) {
- this.callParent(arguments);
- var total = size * 2 + 1,
- ln = this.innerItems.length,
- innerItemConfig = this.getInnerItemConfig(),
- i;
- this.isItemsInitializing = true;
- for (i = ln; i < total; i++) {
- this.doAdd(this.factoryItem(innerItemConfig));
- }
- this.isItemsInitializing = false;
- this.rebuildInnerIndexes();
- this.refreshActiveItem();
- },
- updateMaxItemIndex: function(maxIndex, oldMaxIndex) {
- if (oldMaxIndex !== undefined) {
- var activeIndex = this.getActiveIndex();
- if (activeIndex > maxIndex) {
- this.setActiveItem(maxIndex);
- }
- else {
- this.rebuildInnerIndexes(activeIndex);
- this.refreshActiveItem();
- }
- }
- },
- rebuildInnerIndexes: function(activeIndex) {
- var indexToItem = this.innerIndexToItem,
- idToIndex = this.innerIdToIndex,
- items = this.innerItems.slice(),
- ln = items.length,
- bufferSize = this.getBufferSize(),
- maxIndex = this.getMaxItemIndex(),
- changedIndexes = [],
- i, oldIndex, index, id, item;
- if (activeIndex === undefined) {
- this.innerIndexToItem = indexToItem = {};
- this.innerIdToIndex = idToIndex = {};
- for (i = 0; i < ln; i++) {
- item = items[i];
- id = item.getId();
- idToIndex[id] = i;
- indexToItem[i] = item;
- this.fireEvent('itemindexchange', this, item, i, -1);
- }
- }
- else {
- for (i = activeIndex - bufferSize; i <= activeIndex + bufferSize; i++) {
- if (i >= 0 && i <= maxIndex) {
- if (indexToItem.hasOwnProperty(i)) {
- Ext.Array.remove(items, indexToItem[i]);
- continue;
- }
- changedIndexes.push(i);
- }
- }
- for (i = 0,ln = changedIndexes.length; i < ln; i++) {
- item = items[i];
- id = item.getId();
- index = changedIndexes[i];
- oldIndex = idToIndex[id];
- delete indexToItem[oldIndex];
- idToIndex[id] = index;
- indexToItem[index] = item;
- this.fireEvent('itemindexchange', this, item, index, oldIndex);
- }
- }
- },
- reset: function() {
- this.rebuildInnerIndexes();
- this.setActiveItem(0);
- },
- refreshItems: function() {
- var items = this.innerItems,
- idToIndex = this.innerIdToIndex,
- index, item, i, ln;
- for (i = 0,ln = items.length; i < ln; i++) {
- item = items[i];
- index = idToIndex[item.getId()];
- this.fireEvent('itemindexchange', this, item, index, -1);
- }
- },
- getInnerItemIndex: function(item) {
- var index = this.innerIdToIndex[item.getId()];
- return (typeof index == 'number') ? index : -1;
- },
- getInnerItemAt: function(index) {
- return this.innerIndexToItem[index];
- },
- applyActiveItem: function(activeItem) {
- this.getItems();
- this.getBufferSize();
- var maxIndex = this.getMaxItemIndex(),
- currentActiveIndex = this.getActiveIndex();
- if (typeof activeItem == 'number') {
- activeItem = Math.max(0, Math.min(activeItem, maxIndex));
- if (activeItem === currentActiveIndex) {
- return;
- }
- this.activeIndex = activeItem;
- this.rebuildInnerIndexes(activeItem);
- activeItem = this.getInnerItemAt(activeItem);
- }
- if (activeItem) {
- return this.callParent([activeItem]);
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.mixin.Sortable', {
- extend: 'Ext.mixin.Mixin',
- requires: [
- 'Ext.util.Sorter'
- ],
- mixinConfig: {
- id: 'sortable'
- },
- config: {
- /**
- * @cfg {Array} sorters
- * An array with sorters. A sorter can be an instance of {@link Ext.util.Sorter}, a string
- * indicating a property name, an object representing an Ext.util.Sorter configuration,
- * or a sort function.
- */
- sorters: null,
- /**
- * @cfg {String} defaultSortDirection
- * The default sort direction to use if one is not specified.
- */
- defaultSortDirection: "ASC",
- /**
- * @cfg {String} sortRoot
- * The root inside each item in which the properties exist that we want to sort on.
- * This is useful for sorting records in which the data exists inside a `data` property.
- */
- sortRoot: null
- },
- /**
- * @property {Boolean} dirtySortFn
- * A flag indicating whether the currently cashed sort function is still valid.
- * @readonly
- */
- dirtySortFn: false,
- /**
- * @property currentSortFn
- * This is the cached sorting function which is a generated function that calls all the
- * configured sorters in the correct order.
- * @readonly
- */
- sortFn: null,
- /**
- * @property {Boolean} sorted
- * A read-only flag indicating if this object is sorted.
- * @readonly
- */
- sorted: false,
- applySorters: function(sorters, collection) {
- if (!collection) {
- collection = this.createSortersCollection();
- }
- collection.clear();
- this.sorted = false;
- if (sorters) {
- this.addSorters(sorters);
- }
- return collection;
- },
- createSortersCollection: function() {
- this._sorters = Ext.create('Ext.util.Collection', function(sorter) {
- return sorter.getId();
- });
- return this._sorters;
- },
- /**
- * This method adds a sorter.
- * @param {Ext.util.Sorter/String/Function/Object} sorter Can be an instance of
- * Ext.util.Sorter, a string indicating a property name, an object representing an Ext.util.Sorter
- * configuration, or a sort function.
- * @param {String} defaultDirection The default direction for each sorter in the array. Defaults
- * to the value of {@link #defaultSortDirection}. Can be either 'ASC' or 'DESC'.
- */
- addSorter: function(sorter, defaultDirection) {
- this.addSorters([sorter], defaultDirection);
- },
- /**
- * This method adds all the sorters in a passed array.
- * @param {Array} sorters An array with sorters. A sorter can be an instance of Ext.util.Sorter, a string
- * indicating a property name, an object representing an Ext.util.Sorter configuration,
- * or a sort function.
- * @param {String} defaultDirection The default direction for each sorter in the array. Defaults
- * to the value of {@link #defaultSortDirection}. Can be either 'ASC' or 'DESC'.
- */
- addSorters: function(sorters, defaultDirection) {
- var currentSorters = this.getSorters();
- return this.insertSorters(currentSorters ? currentSorters.length : 0, sorters, defaultDirection);
- },
- /**
- * This method adds a sorter at a given index.
- * @param {Number} index The index at which to insert the sorter.
- * @param {Ext.util.Sorter/String/Function/Object} sorter Can be an instance of Ext.util.Sorter,
- * a string indicating a property name, an object representing an Ext.util.Sorter configuration,
- * or a sort function.
- * @param {String} defaultDirection The default direction for each sorter in the array. Defaults
- * to the value of {@link #defaultSortDirection}. Can be either 'ASC' or 'DESC'.
- */
- insertSorter: function(index, sorter, defaultDirection) {
- return this.insertSorters(index, [sorter], defaultDirection);
- },
- /**
- * This method inserts all the sorters in the passed array at the given index.
- * @param {Number} index The index at which to insert the sorters.
- * @param {Array} sorters Can be an instance of Ext.util.Sorter, a string indicating a property name,
- * an object representing an Ext.util.Sorter configuration, or a sort function.
- * @param {String} defaultDirection The default direction for each sorter in the array. Defaults
- * to the value of {@link #defaultSortDirection}. Can be either 'ASC' or 'DESC'.
- */
- insertSorters: function(index, sorters, defaultDirection) {
- // We begin by making sure we are dealing with an array of sorters
- if (!Ext.isArray(sorters)) {
- sorters = [sorters];
- }
- var ln = sorters.length,
- direction = defaultDirection || this.getDefaultSortDirection(),
- sortRoot = this.getSortRoot(),
- currentSorters = this.getSorters(),
- newSorters = [],
- sorterConfig, i, sorter, currentSorter;
- if (!currentSorters) {
- // This will guarantee that we get the collection
- currentSorters = this.createSortersCollection();
- }
- // We first have to convert every sorter into a proper Sorter instance
- for (i = 0; i < ln; i++) {
- sorter = sorters[i];
- sorterConfig = {
- direction: direction,
- root: sortRoot
- };
- // If we are dealing with a string we assume it is a property they want to sort on.
- if (typeof sorter === 'string') {
- currentSorter = currentSorters.get(sorter);
- if (!currentSorter) {
- sorterConfig.property = sorter;
- } else {
- if (defaultDirection) {
- currentSorter.setDirection(defaultDirection);
- } else {
- // If we already have a sorter for this property we just toggle its direction.
- currentSorter.toggle();
- }
- continue;
- }
- }
- // If it is a function, we assume its a sorting function.
- else if (Ext.isFunction(sorter)) {
- sorterConfig.sorterFn = sorter;
- }
- // If we are dealing with an object, we assume its a Sorter configuration. In this case
- // we create an instance of Sorter passing this configuration.
- else if (Ext.isObject(sorter)) {
- if (!sorter.isSorter) {
- if (sorter.fn) {
- sorter.sorterFn = sorter.fn;
- delete sorter.fn;
- }
- sorterConfig = Ext.apply(sorterConfig, sorter);
- }
- else {
- newSorters.push(sorter);
- if (!sorter.getRoot()) {
- sorter.setRoot(sortRoot);
- }
- continue;
- }
- }
- // Finally we get to the point where it has to be invalid
- // <debug>
- else {
- Ext.Logger.warn('Invalid sorter specified:', sorter);
- }
- // </debug>
- // If a sorter config was created, make it an instance
- sorter = Ext.create('Ext.util.Sorter', sorterConfig);
- newSorters.push(sorter);
- }
- // Now lets add the newly created sorters.
- for (i = 0, ln = newSorters.length; i < ln; i++) {
- currentSorters.insert(index + i, newSorters[i]);
- }
- this.dirtySortFn = true;
- if (currentSorters.length) {
- this.sorted = true;
- }
- return currentSorters;
- },
- /**
- * This method removes a sorter.
- * @param {Ext.util.Sorter/String/Function/Object} sorter Can be an instance of Ext.util.Sorter,
- * a string indicating a property name, an object representing an Ext.util.Sorter configuration,
- * or a sort function.
- */
- removeSorter: function(sorter) {
- return this.removeSorters([sorter]);
- },
- /**
- * This method removes all the sorters in a passed array.
- * @param {Array} sorters Each value in the array can be a string (property name),
- * function (sorterFn) or {@link Ext.util.Sorter Sorter} instance.
- */
- removeSorters: function(sorters) {
- // We begin by making sure we are dealing with an array of sorters
- if (!Ext.isArray(sorters)) {
- sorters = [sorters];
- }
- var ln = sorters.length,
- currentSorters = this.getSorters(),
- i, sorter;
- for (i = 0; i < ln; i++) {
- sorter = sorters[i];
- if (typeof sorter === 'string') {
- currentSorters.removeAtKey(sorter);
- }
- else if (typeof sorter === 'function') {
- currentSorters.each(function(item) {
- if (item.getSorterFn() === sorter) {
- currentSorters.remove(item);
- }
- });
- }
- else if (sorter.isSorter) {
- currentSorters.remove(sorter);
- }
- }
- if (!currentSorters.length) {
- this.sorted = false;
- }
- },
- /**
- * This updates the cached sortFn based on the current sorters.
- * @return {Function} The generated sort function.
- * @private
- */
- updateSortFn: function() {
- var sorters = this.getSorters().items;
- this.sortFn = function(r1, r2) {
- var ln = sorters.length,
- result, i;
- // We loop over each sorter and check if r1 should be before or after r2
- for (i = 0; i < ln; i++) {
- result = sorters[i].sort.call(this, r1, r2);
- // If the result is -1 or 1 at this point it means that the sort is done.
- // Only if they are equal (0) we continue to see if a next sort function
- // actually might find a winner.
- if (result !== 0) {
- break;
- }
- }
- return result;
- };
- this.dirtySortFn = false;
- return this.sortFn;
- },
- /**
- * Returns an up to date sort function.
- * @return {Function} The sort function.
- */
- getSortFn: function() {
- if (this.dirtySortFn) {
- return this.updateSortFn();
- }
- return this.sortFn;
- },
- /**
- * This method will sort an array based on the currently configured {@link #sorters}.
- * @param {Array} data The array you want to have sorted.
- * @return {Array} The array you passed after it is sorted.
- */
- sort: function(data) {
- Ext.Array.sort(data, this.getSortFn());
- return data;
- },
- /**
- * This method returns the index that a given item would be inserted into a given array based
- * on the current sorters.
- * @param {Array} items The array that you want to insert the item into.
- * @param {Mixed} item The item that you want to insert into the items array.
- * @return {Number} The index for the given item in the given array based on the current sorters.
- */
- findInsertionIndex: function(items, item, sortFn) {
- var start = 0,
- end = items.length - 1,
- sorterFn = sortFn || this.getSortFn(),
- middle,
- comparison;
- while (start <= end) {
- middle = (start + end) >> 1;
- comparison = sorterFn(item, items[middle]);
- if (comparison >= 0) {
- start = middle + 1;
- } else if (comparison < 0) {
- end = middle - 1;
- }
- }
- return start;
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.mixin.Filterable', {
- extend: 'Ext.mixin.Mixin',
- requires: [
- 'Ext.util.Filter'
- ],
- mixinConfig: {
- id: 'filterable'
- },
- config: {
- /**
- * @cfg {Array} filters
- * An array with filters. A filter can be an instance of Ext.util.Filter,
- * an object representing an Ext.util.Filter configuration, or a filter function.
- */
- filters: null,
- /**
- * @cfg {String} filterRoot
- * The root inside each item in which the properties exist that we want to filter on.
- * This is useful for filtering records in which the data exists inside a 'data' property.
- */
- filterRoot: null
- },
- /**
- * @property {Boolean} dirtyFilterFn
- * A flag indicating whether the currently cashed filter function is still valid.
- * @readonly
- */
- dirtyFilterFn: false,
- /**
- * @property currentSortFn
- * This is the cached sorting function which is a generated function that calls all the
- * configured sorters in the correct order.
- * @readonly
- */
- filterFn: null,
- /**
- * @property {Boolean} filtered
- * A read-only flag indicating if this object is filtered.
- * @readonly
- */
- filtered: false,
- applyFilters: function(filters, collection) {
- if (!collection) {
- collection = this.createFiltersCollection();
- }
- collection.clear();
- this.filtered = false;
- this.dirtyFilterFn = true;
- if (filters) {
- this.addFilters(filters);
- }
- return collection;
- },
- createFiltersCollection: function() {
- this._filters = Ext.create('Ext.util.Collection', function(filter) {
- return filter.getId();
- });
- return this._filters;
- },
- /**
- * This method adds a filter.
- * @param {Ext.util.Sorter/Function/Object} filter Can be an instance of Ext.util.Filter,
- * an object representing an Ext.util.Filter configuration, or a filter function.
- */
- addFilter: function(filter) {
- this.addFilters([filter]);
- },
- /**
- * This method adds all the filters in a passed array.
- * @param {Array} filters An array with filters. A filter can be an instance of {@link Ext.util.Filter},
- * an object representing an Ext.util.Filter configuration, or a filter function.
- * @return {Object}
- */
- addFilters: function(filters) {
- var currentFilters = this.getFilters();
- return this.insertFilters(currentFilters ? currentFilters.length : 0, filters);
- },
- /**
- * This method adds a filter at a given index.
- * @param {Number} index The index at which to insert the filter.
- * @param {Ext.util.Sorter/Function/Object} filter Can be an instance of {@link Ext.util.Filter},
- * an object representing an Ext.util.Filter configuration, or a filter function.
- * @return {Object}
- */
- insertFilter: function(index, filter) {
- return this.insertFilters(index, [filter]);
- },
- /**
- * This method inserts all the filters in the passed array at the given index.
- * @param {Number} index The index at which to insert the filters.
- * @param {Array} filters Each filter can be an instance of {@link Ext.util.Filter},
- * an object representing an Ext.util.Filter configuration, or a filter function.
- * @return {Array}
- */
- insertFilters: function(index, filters) {
- // We begin by making sure we are dealing with an array of sorters
- if (!Ext.isArray(filters)) {
- filters = [filters];
- }
- var ln = filters.length,
- filterRoot = this.getFilterRoot(),
- currentFilters = this.getFilters(),
- newFilters = [],
- filterConfig, i, filter;
- if (!currentFilters) {
- currentFilters = this.createFiltersCollection();
- }
- // We first have to convert every sorter into a proper Sorter instance
- for (i = 0; i < ln; i++) {
- filter = filters[i];
- filterConfig = {
- root: filterRoot
- };
- if (Ext.isFunction(filter)) {
- filterConfig.filterFn = filter;
- }
- // If we are dealing with an object, we assume its a Sorter configuration. In this case
- // we create an instance of Sorter passing this configuration.
- else if (Ext.isObject(filter)) {
- if (!filter.isFilter) {
- if (filter.fn) {
- filter.filterFn = filter.fn;
- delete filter.fn;
- }
- filterConfig = Ext.apply(filterConfig, filter);
- }
- else {
- newFilters.push(filter);
- if (!filter.getRoot()) {
- filter.setRoot(filterRoot);
- }
- continue;
- }
- }
- // Finally we get to the point where it has to be invalid
- // <debug>
- else {
- Ext.Logger.warn('Invalid filter specified:', filter);
- }
- // </debug>
- // If a sorter config was created, make it an instance
- filter = Ext.create('Ext.util.Filter', filterConfig);
- newFilters.push(filter);
- }
- // Now lets add the newly created sorters.
- for (i = 0, ln = newFilters.length; i < ln; i++) {
- currentFilters.insert(index + i, newFilters[i]);
- }
- this.dirtyFilterFn = true;
- if (currentFilters.length) {
- this.filtered = true;
- }
- return currentFilters;
- },
- /**
- * This method removes all the filters in a passed array.
- * @param {Array} filters Each value in the array can be a string (property name),
- * function (sorterFn), an object containing a property and value keys or
- * {@link Ext.util.Sorter Sorter} instance.
- */
- removeFilters: function(filters) {
- // We begin by making sure we are dealing with an array of sorters
- if (!Ext.isArray(filters)) {
- filters = [filters];
- }
- var ln = filters.length,
- currentFilters = this.getFilters(),
- i, filter;
- for (i = 0; i < ln; i++) {
- filter = filters[i];
- if (typeof filter === 'string') {
- currentFilters.each(function(item) {
- if (item.getProperty() === filter) {
- currentFilters.remove(item);
- }
- });
- }
- else if (typeof filter === 'function') {
- currentFilters.each(function(item) {
- if (item.getFilterFn() === filter) {
- currentFilters.remove(item);
- }
- });
- }
- else {
- if (filter.isFilter) {
- currentFilters.remove(filter);
- }
- else if (filter.property !== undefined && filter.value !== undefined) {
- currentFilters.each(function(item) {
- if (item.getProperty() === filter.property && item.getValue() === filter.value) {
- currentFilters.remove(item);
- }
- });
- }
- }
- }
- if (!currentFilters.length) {
- this.filtered = false;
- }
- },
- /**
- * This updates the cached sortFn based on the current sorters.
- * @return {Function} sortFn The generated sort function.
- * @private
- */
- updateFilterFn: function() {
- var filters = this.getFilters().items;
- this.filterFn = function(item) {
- var isMatch = true,
- length = filters.length,
- i;
- for (i = 0; i < length; i++) {
- var filter = filters[i],
- fn = filter.getFilterFn(),
- scope = filter.getScope() || this;
- isMatch = isMatch && fn.call(scope, item);
- }
- return isMatch;
- };
- this.dirtyFilterFn = false;
- return this.filterFn;
- },
- /**
- * This method will sort an array based on the currently configured {@link Ext.data.Store#sorters sorters}.
- * @param {Array} data The array you want to have sorted.
- * @return {Array} The array you passed after it is sorted.
- */
- filter: function(data) {
- return this.getFilters().length ? Ext.Array.filter(data, this.getFilterFn()) : data;
- },
- isFiltered: function(item) {
- return this.getFilters().length ? !this.getFilterFn()(item) : false;
- },
- /**
- * Returns an up to date sort function.
- * @return {Function} sortFn The sort function.
- */
- getFilterFn: function() {
- if (this.dirtyFilterFn) {
- return this.updateFilterFn();
- }
- return this.filterFn;
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.util.Collection', {
- /**
- * @cfg {Object[]} filters
- * Array of {@link Ext.util.Filter Filters} for this collection.
- */
- /**
- * @cfg {Object[]} sorters
- * Array of {@link Ext.util.Sorter Sorters} for this collection.
- */
- config: {
- autoFilter: true,
- autoSort: true
- },
- mixins: {
- sortable: 'Ext.mixin.Sortable',
- filterable: 'Ext.mixin.Filterable'
- },
- constructor: function(keyFn, config) {
- var me = this;
- /**
- * @property {Array} [all=[]]
- * An array containing all the items (unsorted, unfiltered)
- */
- me.all = [];
- /**
- * @property {Array} [items=[]]
- * An array containing the filtered items (sorted)
- */
- me.items = [];
- /**
- * @property {Array} [keys=[]]
- * An array containing all the filtered keys (sorted)
- */
- me.keys = [];
- /**
- * @property {Object} [indices={}]
- * An object used as map to get a sorted and filtered index of an item
- */
- me.indices = {};
- /**
- * @property {Object} [map={}]
- * An object used as map to get an object based on its key
- */
- me.map = {};
- /**
- * @property {Number} [length=0]
- * The count of items in the collection filtered and sorted
- */
- me.length = 0;
- if (keyFn) {
- me.getKey = keyFn;
- }
- this.initConfig(config);
- },
- updateAutoSort: function(autoSort, oldAutoSort) {
- if (oldAutoSort === false && autoSort && this.items.length) {
- this.sort();
- }
- },
- updateAutoFilter: function(autoFilter, oldAutoFilter) {
- if (oldAutoFilter === false && autoFilter && this.all.length) {
- this.filter();
- }
- },
- insertSorters: function() {
- // We override the insertSorters method that exists on the Sortable mixin. This method always
- // gets called whenever you add or insert a new sorter. We do this because we actually want
- // to sort right after this happens.
- this.mixins.sortable.insertSorters.apply(this, arguments);
- if (this.getAutoSort() && this.items.length) {
- this.sort();
- }
- return this;
- },
- removeSorters: function(sorters) {
- // We override the removeSorters method that exists on the Sortable mixin. This method always
- // gets called whenever you remove a sorter. If we are still sorted after we removed this sorter,
- // then we have to resort the whole collection.
- this.mixins.sortable.removeSorters.call(this, sorters);
- if (this.sorted && this.getAutoSort() && this.items.length) {
- this.sort();
- }
- return this;
- },
- applyFilters: function(filters) {
- var collection = this.mixins.filterable.applyFilters.call(this, filters);
- if (!filters && this.all.length && this.getAutoFilter()) {
- this.filter();
- }
- return collection;
- },
- addFilters: function(filters) {
- // We override the insertFilters method that exists on the Filterable mixin. This method always
- // gets called whenever you add or insert a new filter. We do this because we actually want
- // to filter right after this happens.
- this.mixins.filterable.addFilters.call(this, filters);
- if (this.items.length && this.getAutoFilter()) {
- this.filter();
- }
- return this;
- },
- removeFilters: function(filters) {
- // We override the removeFilters method that exists on the Filterable mixin. This method always
- // gets called whenever you remove a filter. If we are still filtered after we removed this filter,
- // then we have to re-filter the whole collection.
- this.mixins.filterable.removeFilters.call(this, filters);
- if (this.filtered && this.all.length && this.getAutoFilter()) {
- this.filter();
- }
- return this;
- },
- /**
- * This method will sort a collection based on the currently configured sorters.
- * @param {Object} property
- * @param {Object} value
- * @param {Object} anyMatch
- * @param {Object} caseSensitive
- * @return {Array}
- */
- filter: function(property, value, anyMatch, caseSensitive) {
- // Support for the simple case of filtering by property/value
- if (property) {
- if (Ext.isString(property)) {
- this.addFilters({
- property : property,
- value : value,
- anyMatch : anyMatch,
- caseSensitive: caseSensitive
- });
- return this.items;
- }
- else {
- this.addFilters(property);
- return this.items;
- }
- }
- this.items = this.mixins.filterable.filter.call(this, this.all.slice());
- this.updateAfterFilter();
- if (this.sorted && this.getAutoSort()) {
- this.sort();
- }
- },
- updateAfterFilter: function() {
- var items = this.items,
- keys = this.keys,
- indices = this.indices = {},
- ln = items.length,
- i, item, key;
- keys.length = 0;
- for (i = 0; i < ln; i++) {
- item = items[i];
- key = this.getKey(item);
- indices[key] = i;
- keys[i] = key;
- }
- this.length = items.length;
- this.dirtyIndices = false;
- },
- sort: function(sorters, defaultDirection) {
- var items = this.items,
- keys = this.keys,
- indices = this.indices,
- ln = items.length,
- i, item, key;
- // If we pass sorters to this method we have to add them first.
- // Because adding a sorter automatically sorts the items collection
- // we can just return items after we have added the sorters
- if (sorters) {
- this.addSorters(sorters, defaultDirection);
- return this.items;
- }
- // We save the keys temporarily on each item
- for (i = 0; i < ln; i++) {
- items[i]._current_key = keys[i];
- }
- // Now we sort our items array
- this.handleSort(items);
- // And finally we update our keys and indices
- for (i = 0; i < ln; i++) {
- item = items[i];
- key = item._current_key;
- keys[i] = key;
- indices[key] = i;
- delete item._current_key;
- }
- this.dirtyIndices = true;
- },
- handleSort: function(items) {
- this.mixins.sortable.sort.call(this, items);
- },
- /**
- * Adds an item to the collection. Fires the {@link #add} event when complete.
- * @param {String} key
- *
- * The key to associate with the item, or the new item.
- *
- * If a {@link #getKey} implementation was specified for this MixedCollection, or if the key of the stored items is
- * in a property called **id**, the MixedCollection will be able to _derive_ the key for the new item. In this case
- * just pass the new item in this parameter.
- * @param {Object} item The item to add.
- * @return {Object} The item added.
- */
- add: function(key, item) {
- var me = this,
- filtered = this.filtered,
- sorted = this.sorted,
- all = this.all,
- items = this.items,
- keys = this.keys,
- indices = this.indices,
- filterable = this.mixins.filterable,
- currentLength = items.length,
- index = currentLength;
- if (arguments.length == 1) {
- item = key;
- key = me.getKey(item);
- }
- if (typeof key != 'undefined' && key !== null) {
- if (typeof me.map[key] != 'undefined') {
- return me.replace(key, item);
- }
- me.map[key] = item;
- }
- all.push(item);
- if (filtered && this.getAutoFilter() && filterable.isFiltered.call(me, item)) {
- return null;
- }
- me.length++;
- if (sorted && this.getAutoSort()) {
- index = this.findInsertionIndex(items, item);
- }
- if (index !== currentLength) {
- this.dirtyIndices = true;
- Ext.Array.splice(keys, index, 0, key);
- Ext.Array.splice(items, index, 0, item);
- } else {
- indices[key] = currentLength;
- keys.push(key);
- items.push(item);
- }
- return item;
- },
- /**
- * MixedCollection has a generic way to fetch keys if you implement getKey. The default implementation simply
- * returns **`item.id`** but you can provide your own implementation to return a different value as in the following
- * examples:
- *
- * // normal way
- * var mc = new Ext.util.MixedCollection();
- * mc.add(someEl.dom.id, someEl);
- * mc.add(otherEl.dom.id, otherEl);
- * //and so on
- *
- * // using getKey
- * var mc = new Ext.util.MixedCollection();
- * mc.getKey = function(el){
- * return el.dom.id;
- * };
- * mc.add(someEl);
- * mc.add(otherEl);
- *
- * // or via the constructor
- * var mc = new Ext.util.MixedCollection(false, function(el){
- * return el.dom.id;
- * });
- * mc.add(someEl);
- * mc.add(otherEl);
- * @param {Object} item The item for which to find the key.
- * @return {Object} The key for the passed item.
- */
- getKey: function(item) {
- return item.id;
- },
- /**
- * Replaces an item in the collection. Fires the {@link #replace} event when complete.
- * @param {String} oldKey
- *
- * The key associated with the item to replace, or the replacement item.
- *
- * If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key of your stored items is
- * in a property called **id**, then the MixedCollection will be able to _derive_ the key of the replacement item.
- * If you want to replace an item with one having the same key value, then just pass the replacement item in this
- * parameter.
- * @param {Object} item {Object} item (optional) If the first parameter passed was a key, the item to associate with
- * that key.
- * @return {Object} The new item.
- */
- replace: function(oldKey, item) {
- var me = this,
- sorted = me.sorted,
- filtered = me.filtered,
- filterable = me.mixins.filterable,
- items = me.items,
- keys = me.keys,
- all = me.all,
- map = me.map,
- returnItem = null,
- oldItemsLn = items.length,
- oldItem, index, newKey;
- if (arguments.length == 1) {
- item = oldKey;
- oldKey = newKey = me.getKey(item);
- } else {
- newKey = me.getKey(item);
- }
- oldItem = map[oldKey];
- if (typeof oldKey == 'undefined' || oldKey === null || typeof oldItem == 'undefined') {
- return me.add(newKey, item);
- }
- me.map[newKey] = item;
- if (newKey !== oldKey) {
- delete me.map[oldKey];
- }
- if (sorted && me.getAutoSort()) {
- Ext.Array.remove(items, oldItem);
- Ext.Array.remove(keys, oldKey);
- Ext.Array.remove(all, oldItem);
- all.push(item);
- me.dirtyIndices = true;
- if (filtered && me.getAutoFilter()) {
- // If the item is now filtered we check if it was not filtered
- // before. If that is the case then we subtract from the length
- if (filterable.isFiltered.call(me, item)) {
- if (oldItemsLn !== items.length) {
- me.length--;
- }
- return null;
- }
- // If the item was filtered, but now it is not anymore then we
- // add to the length
- else if (oldItemsLn === items.length) {
- me.length++;
- returnItem = item;
- }
- }
- index = this.findInsertionIndex(items, item);
- Ext.Array.splice(keys, index, 0, newKey);
- Ext.Array.splice(items, index, 0, item);
- } else {
- if (filtered) {
- if (me.getAutoFilter() && filterable.isFiltered.call(me, item)) {
- if (items.indexOf(oldItem) !== -1) {
- Ext.Array.remove(items, oldItem);
- Ext.Array.remove(keys, oldKey);
- me.length--;
- me.dirtyIndices = true;
- }
- return null;
- }
- else if (items.indexOf(oldItem) === -1) {
- items.push(item);
- keys.push(newKey);
- me.indices[newKey] = me.length;
- me.length++;
- return item;
- }
- }
- index = me.items.indexOf(oldItem);
- keys[index] = newKey;
- items[index] = item;
- this.dirtyIndices = true;
- }
- return returnItem;
- },
- /**
- * Adds all elements of an Array or an Object to the collection.
- * @param {Object/Array} objs An Object containing properties which will be added to the collection, or an Array of
- * values, each of which are added to the collection. Functions references will be added to the collection if {@link
- * Ext.util.MixedCollection#allowFunctions allowFunctions} has been set to `true`.
- */
- addAll: function(addItems) {
- var me = this,
- filtered = me.filtered,
- sorted = me.sorted,
- all = me.all,
- items = me.items,
- keys = me.keys,
- map = me.map,
- autoFilter = me.getAutoFilter(),
- autoSort = me.getAutoSort(),
- newKeys = [],
- newItems = [],
- filterable = me.mixins.filterable,
- addedItems = [],
- ln, key, i, item;
- if (Ext.isObject(addItems)) {
- for (key in addItems) {
- if (addItems.hasOwnProperty(key)) {
- newItems.push(items[key]);
- newKeys.push(key);
- }
- }
- } else {
- newItems = addItems;
- ln = addItems.length;
- for (i = 0; i < ln; i++) {
- newKeys.push(me.getKey(addItems[i]));
- }
- }
- for (i = 0; i < ln; i++) {
- key = newKeys[i];
- item = newItems[i];
- if (typeof key != 'undefined' && key !== null) {
- if (typeof map[key] != 'undefined') {
- me.replace(key, item);
- continue;
- }
- map[key] = item;
- }
- all.push(item);
- if (filtered && autoFilter && filterable.isFiltered.call(me, item)) {
- continue;
- }
- me.length++;
- keys.push(key);
- items.push(item);
- addedItems.push(item);
- }
- if (addedItems.length) {
- me.dirtyIndices = true;
- if (sorted && autoSort) {
- me.sort();
- }
- return addedItems;
- }
- return null;
- },
- /**
- * Executes the specified function once for every item in the collection.
- * The function should return a Boolean value. Returning `false` from the function will stop the iteration.
- * @param {Function} fn The function to execute for each item.
- * @param {Mixed} fn.item The collection item.
- * @param {Number} fn.index The item's index.
- * @param {Number} fn.length The total number of items in the collection.
- * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to the current
- * item in the iteration.
- */
- each: function(fn, scope) {
- var items = this.items.slice(), // each safe for removal
- i = 0,
- len = items.length,
- item;
- for (; i < len; i++) {
- item = items[i];
- if (fn.call(scope || item, item, i, len) === false) {
- break;
- }
- }
- },
- /**
- * Executes the specified function once for every key in the collection, passing each key, and its associated item
- * as the first two parameters.
- * @param {Function} fn The function to execute for each item.
- * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to the browser
- * window.
- */
- eachKey: function(fn, scope) {
- var keys = this.keys,
- items = this.items,
- ln = keys.length, i;
- for (i = 0; i < ln; i++) {
- fn.call(scope || window, keys[i], items[i], i, ln);
- }
- },
- /**
- * Returns the first item in the collection which elicits a `true` return value from the passed selection function.
- * @param {Function} fn The selection function to execute for each item.
- * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to the browser
- * window.
- * @return {Object} The first item in the collection which returned `true` from the selection function.
- */
- findBy: function(fn, scope) {
- var keys = this.keys,
- items = this.items,
- i = 0,
- len = items.length;
- for (; i < len; i++) {
- if (fn.call(scope || window, items[i], keys[i])) {
- return items[i];
- }
- }
- return null;
- },
- /**
- * Filter by a function. Returns a _new_ collection that has been filtered. The passed function will be called with
- * each object in the collection. If the function returns `true`, the value is included otherwise it is filtered.
- * @param {Function} fn The function to be called.
- * @param fn.o The object.
- * @param fn.k The key.
- * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to this
- * MixedCollection.
- * @return {Ext.util.MixedCollection} The new filtered collection
- */
- filterBy: function(fn, scope) {
- var me = this,
- newCollection = new this.self(),
- keys = me.keys,
- items = me.all,
- length = items.length,
- i;
- newCollection.getKey = me.getKey;
- for (i = 0; i < length; i++) {
- if (fn.call(scope || me, items[i], me.getKey(items[i]))) {
- newCollection.add(keys[i], items[i]);
- }
- }
- return newCollection;
- },
- /**
- * Inserts an item at the specified index in the collection. Fires the {@link #add} event when complete.
- * @param {Number} index The index to insert the item at.
- * @param {String} key The key to associate with the new item, or the item itself.
- * @param {Object} item If the second parameter was a key, the new item.
- * @return {Object} The item inserted.
- */
- insert: function(index, key, item) {
- var me = this,
- sorted = this.sorted,
- map = this.map,
- filtered = this.filtered;
- if (arguments.length == 2) {
- item = key;
- key = me.getKey(item);
- }
- if (index >= me.length || (sorted && me.getAutoSort())) {
- return me.add(key, item);
- }
- if (typeof key != 'undefined' && key !== null) {
- if (typeof map[key] != 'undefined') {
- me.replace(key, item);
- return false;
- }
- map[key] = item;
- }
- this.all.push(item);
- if (filtered && this.getAutoFilter() && this.mixins.filterable.isFiltered.call(me, item)) {
- return null;
- }
- me.length++;
- Ext.Array.splice(me.items, index, 0, item);
- Ext.Array.splice(me.keys, index, 0, key);
- me.dirtyIndices = true;
- return item;
- },
- insertAll: function(index, insertItems) {
- if (index >= this.items.length || (this.sorted && this.getAutoSort())) {
- return this.addAll(insertItems);
- }
- var me = this,
- filtered = this.filtered,
- sorted = this.sorted,
- all = this.all,
- items = this.items,
- keys = this.keys,
- map = this.map,
- autoFilter = this.getAutoFilter(),
- autoSort = this.getAutoSort(),
- newKeys = [],
- newItems = [],
- addedItems = [],
- filterable = this.mixins.filterable,
- insertedUnfilteredItem = false,
- ln, key, i, item;
- if (sorted && this.getAutoSort()) {
- // <debug>
- Ext.Logger.error('Inserting a collection of items into a sorted Collection is invalid. Please just add these items or remove the sorters.');
- // </debug>
- }
- if (Ext.isObject(insertItems)) {
- for (key in insertItems) {
- if (insertItems.hasOwnProperty(key)) {
- newItems.push(items[key]);
- newKeys.push(key);
- }
- }
- } else {
- newItems = insertItems;
- ln = insertItems.length;
- for (i = 0; i < ln; i++) {
- newKeys.push(me.getKey(insertItems[i]));
- }
- }
- for (i = 0; i < ln; i++) {
- key = newKeys[i];
- item = newItems[i];
- if (typeof key != 'undefined' && key !== null) {
- if (typeof map[key] != 'undefined') {
- me.replace(key, item);
- continue;
- }
- map[key] = item;
- }
- all.push(item);
- if (filtered && autoFilter && filterable.isFiltered.call(me, item)) {
- continue;
- }
- me.length++;
- Ext.Array.splice(items, index + i, 0, item);
- Ext.Array.splice(keys, index + i, 0, key);
- insertedUnfilteredItem = true;
- addedItems.push(item);
- }
- if (insertedUnfilteredItem) {
- this.dirtyIndices = true;
- if (sorted && autoSort) {
- this.sort();
- }
- return addedItems;
- }
- return null;
- },
- /**
- * Remove an item from the collection.
- * @param {Object} item The item to remove.
- * @return {Object} The item removed or `false` if no item was removed.
- */
- remove: function(item) {
- var index = this.items.indexOf(item);
- if (index === -1) {
- Ext.Array.remove(this.all, item);
- if (typeof this.getKey == 'function') {
- var key = this.getKey(item);
- if (key !== undefined) {
- delete this.map[key];
- }
- }
- return item;
- }
- return this.removeAt(this.items.indexOf(item));
- },
- /**
- * Remove all items in the passed array from the collection.
- * @param {Array} items An array of items to be removed.
- * @return {Ext.util.MixedCollection} this object
- */
- removeAll: function(items) {
- if (items) {
- var ln = items.length, i;
- for (i = 0; i < ln; i++) {
- this.remove(items[i]);
- }
- }
- return this;
- },
- /**
- * Remove an item from a specified index in the collection. Fires the {@link #remove} event when complete.
- * @param {Number} index The index within the collection of the item to remove.
- * @return {Object} The item removed or `false` if no item was removed.
- */
- removeAt: function(index) {
- var me = this,
- items = me.items,
- keys = me.keys,
- all = me.all,
- item, key;
- if (index < me.length && index >= 0) {
- item = items[index];
- key = keys[index];
- if (typeof key != 'undefined') {
- delete me.map[key];
- }
- Ext.Array.erase(items, index, 1);
- Ext.Array.erase(keys, index, 1);
- Ext.Array.remove(all, item);
- delete me.indices[key];
- me.length--;
- this.dirtyIndices = true;
- return item;
- }
- return false;
- },
- /**
- * Removed an item associated with the passed key from the collection.
- * @param {String} key The key of the item to remove.
- * @return {Object/Boolean} The item removed or `false` if no item was removed.
- */
- removeAtKey: function(key) {
- return this.removeAt(this.indexOfKey(key));
- },
- /**
- * Returns the number of items in the collection.
- * @return {Number} the number of items in the collection.
- */
- getCount: function() {
- return this.length;
- },
- /**
- * Returns index within the collection of the passed Object.
- * @param {Object} item The item to find the index of.
- * @return {Number} Index of the item. Returns -1 if not found.
- */
- indexOf: function(item) {
- if (this.dirtyIndices) {
- this.updateIndices();
- }
- var index = this.indices[this.getKey(item)];
- return (index === undefined) ? -1 : index;
- },
- /**
- * Returns index within the collection of the passed key.
- * @param {String} key The key to find the index of.
- * @return {Number} Index of the key.
- */
- indexOfKey: function(key) {
- if (this.dirtyIndices) {
- this.updateIndices();
- }
- var index = this.indices[key];
- return (index === undefined) ? -1 : index;
- },
- updateIndices: function() {
- var items = this.items,
- ln = items.length,
- indices = this.indices = {},
- i, item, key;
- for (i = 0; i < ln; i++) {
- item = items[i];
- key = this.getKey(item);
- indices[key] = i;
- }
- this.dirtyIndices = false;
- },
- /**
- * Returns the item associated with the passed key OR index. Key has priority over index. This is the equivalent of
- * calling {@link #getByKey} first, then if nothing matched calling {@link #getAt}.
- * @param {String/Number} key The key or index of the item.
- * @return {Object} If the item is found, returns the item. If the item was not found, returns `undefined`. If an item
- * was found, but is a Class, returns `null`.
- */
- get: function(key) {
- var me = this,
- fromMap = me.map[key],
- item;
- if (fromMap !== undefined) {
- item = fromMap;
- }
- else if (typeof key == 'number') {
- item = me.items[key];
- }
- return typeof item != 'function' || me.getAllowFunctions() ? item : null; // for prototype!
- },
- /**
- * Returns the item at the specified index.
- * @param {Number} index The index of the item.
- * @return {Object} The item at the specified index.
- */
- getAt: function(index) {
- return this.items[index];
- },
- /**
- * Returns the item associated with the passed key.
- * @param {String/Number} key The key of the item.
- * @return {Object} The item associated with the passed key.
- */
- getByKey: function(key) {
- return this.map[key];
- },
- /**
- * Returns `true` if the collection contains the passed Object as an item.
- * @param {Object} item The Object to look for in the collection.
- * @return {Boolean} `true` if the collection contains the Object as an item.
- */
- contains: function(item) {
- var key = this.getKey(item);
- if (key) {
- return this.containsKey(key);
- } else {
- return Ext.Array.contains(this.items, item);
- }
- },
- /**
- * Returns `true` if the collection contains the passed Object as a key.
- * @param {String} key The key to look for in the collection.
- * @return {Boolean} `true` if the collection contains the Object as a key.
- */
- containsKey: function(key) {
- return typeof this.map[key] != 'undefined';
- },
- /**
- * Removes all items from the collection. Fires the {@link #clear} event when complete.
- */
- clear: function(){
- var me = this;
- me.length = 0;
- me.items.length = 0;
- me.keys.length = 0;
- me.all.length = 0;
- me.dirtyIndices = true;
- me.indices = {};
- me.map = {};
- },
- /**
- * Returns the first item in the collection.
- * @return {Object} the first item in the collection.
- */
- first: function() {
- return this.items[0];
- },
- /**
- * Returns the last item in the collection.
- * @return {Object} the last item in the collection.
- */
- last: function() {
- return this.items[this.length - 1];
- },
- /**
- * Returns a range of items in this collection
- * @param {Number} [startIndex=0] The starting index.
- * @param {Number} [endIndex=-1] The ending index. Defaults to the last item.
- * @return {Array} An array of items.
- */
- getRange: function(start, end) {
- var me = this,
- items = me.items,
- range = [],
- i;
- if (items.length < 1) {
- return range;
- }
- start = start || 0;
- end = Math.min(typeof end == 'undefined' ? me.length - 1 : end, me.length - 1);
- if (start <= end) {
- for (i = start; i <= end; i++) {
- range[range.length] = items[i];
- }
- } else {
- for (i = start; i >= end; i--) {
- range[range.length] = items[i];
- }
- }
- return range;
- },
- /**
- * Find the index of the first matching object in this collection by a function. If the function returns `true` it
- * is considered a match.
- * @param {Function} fn The function to be called.
- * @param fn.o The object.
- * @param fn.k The key.
- * @param {Object} scope The scope (`this` reference) in which the function is executed. Defaults to this
- * MixedCollection.
- * @param {Number} [start=0] The index to start searching at.
- * @return {Number} The matched index, or -1 if the item was not found.
- */
- findIndexBy: function(fn, scope, start) {
- var me = this,
- keys = me.keys,
- items = me.items,
- i = start || 0,
- ln = items.length;
- for (; i < ln; i++) {
- if (fn.call(scope || me, items[i], keys[i])) {
- return i;
- }
- }
- return -1;
- },
- /**
- * Creates a shallow copy of this collection
- * @return {Ext.util.MixedCollection}
- */
- clone: function() {
- var me = this,
- copy = new this.self(),
- keys = me.keys,
- items = me.items,
- i = 0,
- ln = items.length;
- for(; i < ln; i++) {
- copy.add(keys[i], items[i]);
- }
- copy.getKey = me.getKey;
- return copy;
- },
- destroy: function() {
- this.callSuper();
- this.clear();
- }
- });
- /**
- * @docauthor Evan Trimboli <evan@sencha.com>
- * @aside guide stores
- *
- * Contains a collection of all stores that are created that have an identifier. An identifier can be assigned by
- * setting the {@link Ext.data.Store#storeId storeId} property. When a store is in the StoreManager, it can be
- * referred to via it's identifier:
- *
- * Ext.create('Ext.data.Store', {
- * model: 'SomeModel',
- * storeId: 'myStore'
- * });
- *
- * var store = Ext.data.StoreManager.lookup('myStore');
- *
- * Also note that the {@link #lookup} method is aliased to {@link Ext#getStore} for convenience.
- *
- * If a store is registered with the StoreManager, you can also refer to the store by it's identifier when registering
- * it with any Component that consumes data from a store:
- *
- * Ext.create('Ext.data.Store', {
- * model: 'SomeModel',
- * storeId: 'myStore'
- * });
- *
- * Ext.create('Ext.view.View', {
- * store: 'myStore'
- * // other configuration here
- * });
- */
- Ext.define('Ext.data.StoreManager', {
- extend: 'Ext.util.Collection',
- alternateClassName: ['Ext.StoreMgr', 'Ext.data.StoreMgr', 'Ext.StoreManager'],
- singleton: true,
- uses: ['Ext.data.ArrayStore', 'Ext.data.Store'],
- /**
- * Registers one or more Stores with the StoreManager. You do not normally need to register stores manually. Any
- * store initialized with a {@link Ext.data.Store#storeId} will be auto-registered.
- * @param {Ext.data.Store...} stores Any number of Store instances.
- */
- register : function() {
- for (var i = 0, s; (s = arguments[i]); i++) {
- this.add(s);
- }
- },
- /**
- * Unregisters one or more Stores with the StoreManager.
- * @param {String/Object...} stores Any number of Store instances or ID-s.
- */
- unregister : function() {
- for (var i = 0, s; (s = arguments[i]); i++) {
- this.remove(this.lookup(s));
- }
- },
- /**
- * Gets a registered Store by id.
- * @param {String/Object} store The `id` of the Store, or a Store instance, or a store configuration.
- * @return {Ext.data.Store}
- */
- lookup : function(store) {
- // handle the case when we are given an array or an array of arrays.
- if (Ext.isArray(store)) {
- var fields = ['field1'],
- expand = !Ext.isArray(store[0]),
- data = store,
- i,
- len;
- if (expand) {
- data = [];
- for (i = 0, len = store.length; i < len; ++i) {
- data.push([store[i]]);
- }
- } else {
- for(i = 2, len = store[0].length; i <= len; ++i){
- fields.push('field' + i);
- }
- }
- return Ext.create('Ext.data.ArrayStore', {
- data : data,
- fields: fields,
- // See https://sencha.jira.com/browse/TOUCH-1541
- autoDestroy: true,
- autoCreated: true,
- expanded: expand
- });
- }
- if (Ext.isString(store)) {
- // store id
- return this.get(store);
- } else {
- // store instance or store config
- if (store instanceof Ext.data.Store) {
- return store;
- } else {
- return Ext.factory(store, Ext.data.Store, null, 'store');
- }
- }
- },
- // getKey implementation for MixedCollection
- getKey : function(o) {
- return o.getStoreId();
- }
- }, function() {
- /**
- * Creates a new store for the given id and config, then registers it with the {@link Ext.data.StoreManager Store Manager}.
- * Sample usage:
- *
- * Ext.regStore('AllUsers', {
- * model: 'User'
- * });
- *
- * // the store can now easily be used throughout the application
- * new Ext.List({
- * store: 'AllUsers'
- * // ...
- * });
- *
- * @param {String} id The id to set on the new store.
- * @param {Object} config The store config.
- * @member Ext
- * @method regStore
- */
- Ext.regStore = function(name, config) {
- var store;
- if (Ext.isObject(name)) {
- config = name;
- } else {
- if (config instanceof Ext.data.Store) {
- config.setStoreId(name);
- } else {
- config.storeId = name;
- }
- }
- if (config instanceof Ext.data.Store) {
- store = config;
- } else {
- store = Ext.create('Ext.data.Store', config);
- }
- return Ext.data.StoreManager.register(store);
- };
- /**
- * Shortcut to {@link Ext.data.StoreManager#lookup}.
- * @member Ext
- * @method getStore
- * @alias Ext.data.StoreManager#lookup
- */
- Ext.getStore = function(name) {
- return Ext.data.StoreManager.lookup(name);
- };
- });
- /**
- * A DataItem is a container for {@link Ext.dataview.DataView} with useComponents: true. It ties together
- * {@link Ext.data.Model records} to its contained Components via a {@link #dataMap dataMap} configuration.
- *
- * For example, lets say you have a `text` configuration which, when applied, gets turned into an instance of an
- * Ext.Component. We want to update the {@link #html} of a sub-component when the 'text' field of the record gets
- * changed.
- *
- * As you can see below, it is simply a matter of setting the key of the object to be the getter of the config
- * (getText), and then give that property a value of an object, which then has 'setHtml' (the html setter) as the key,
- * and 'text' (the field name) as the value. You can continue this for a as many sub-components as you wish.
- *
- * dataMap: {
- * // When the record is updated, get the text configuration, and
- * // call {@link #setHtml} with the 'text' field of the record.
- * getText: {
- * setHtml: 'text'
- * },
- *
- * // When the record is updated, get the userName configuration, and
- * // call {@link #setHtml} with the 'from_user' field of the record.
- * getUserName: {
- * setHtml: 'from_user'
- * },
- *
- * // When the record is updated, get the avatar configuration, and
- * // call `setSrc` with the 'profile_image_url' field of the record.
- * getAvatar: {
- * setSrc: 'profile_image_url'
- * }
- * }
- */
- Ext.define('Ext.dataview.component.DataItem', {
- extend: 'Ext.Container',
- xtype : 'dataitem',
- config: {
- baseCls: Ext.baseCSSPrefix + 'data-item',
- defaultType: 'component',
- /**
- * @cfg {Ext.data.Model} record The model instance of this DataItem. It is controlled by the Component DataView.
- * @accessor
- */
- record: null,
- /**
- * @cfg {String} itemCls
- * An additional CSS class to apply to items within the DataView.
- * @accessor
- */
- itemCls: null,
- /**
- * @cfg dataMap
- * The dataMap allows you to map {@link #record} fields to specific configurations in this component.
- *
- * For example, lets say you have a `text` configuration which, when applied, gets turned into an instance of an Ext.Component.
- * We want to update the {@link #html} of this component when the 'text' field of the record gets changed.
- * For example:
- *
- * dataMap: {
- * getText: {
- * setHtml: 'text'
- * }
- * }
- *
- * 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
- * property a value of an object, which then has `setHtml` (the html setter) as the key, and `text` (the field name) as the value.
- */
- dataMap: {},
- /*
- * @private dataview
- */
- dataview: null,
- items: [{
- xtype: 'component'
- }]
- },
- updateBaseCls: function(newBaseCls, oldBaseCls) {
- var me = this;
- me.callParent(arguments);
- },
- updateItemCls: function(newCls, oldCls) {
- if (oldCls) {
- this.removeCls(oldCls);
- }
- if (newCls) {
- this.addCls(newCls);
- }
- },
- doMapData: function(dataMap, data, item) {
- var componentName, component, setterMap, setterName;
- for (componentName in dataMap) {
- setterMap = dataMap[componentName];
- component = this[componentName]();
- if (component) {
- for (setterName in setterMap) {
- if (data && component[setterName] && data[setterMap[setterName]]) {
- component[setterName](data[setterMap[setterName]]);
- }
- }
- }
- }
- if (item) {
- // Bypassing setter because sometimes we pass the same object (different properties)
- item.updateData(data);
- }
- },
- /**
- * Updates this container's child items, passing through the `dataMap`.
- * @param newRecord
- * @private
- */
- updateRecord: function(newRecord) {
- if (!newRecord) {
- return;
- }
- this._record = newRecord;
- var me = this,
- dataview = me.dataview || this.getDataview(),
- data = dataview.prepareData(newRecord.getData(true), dataview.getStore().indexOf(newRecord), newRecord),
- items = me.getItems(),
- item = items.first(),
- dataMap = me.getDataMap();
- if (!item) {
- return;
- }
- if (dataMap) {
- this.doMapData(dataMap, data, item);
- }
- /**
- * @event updatedata
- * Fires whenever the data of the DataItem is updated.
- * @param {Ext.dataview.component.DataItem} this The DataItem instance.
- * @param {Object} newData The new data.
- */
- me.fireEvent('updatedata', me, data);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.dataview.component.Container', {
- extend: 'Ext.Container',
- requires: [
- 'Ext.dataview.component.DataItem'
- ],
- /**
- * @event itemtouchstart
- * Fires whenever an item is touched
- * @param {Ext.dataview.component.Container} this
- * @param {Ext.dataview.component.DataItem} item The item touched
- * @param {Number} index The index of the item touched
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemtouchmove
- * Fires whenever an item is moved
- * @param {Ext.dataview.component.Container} this
- * @param {Ext.dataview.component.DataItem} item The item moved
- * @param {Number} index The index of the item moved
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemtouchend
- * Fires whenever an item is touched
- * @param {Ext.dataview.component.Container} this
- * @param {Ext.dataview.component.DataItem} item The item touched
- * @param {Number} index The index of the item touched
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemtap
- * Fires whenever an item is tapped
- * @param {Ext.dataview.component.Container} this
- * @param {Ext.dataview.component.DataItem} item The item tapped
- * @param {Number} index The index of the item tapped
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemtaphold
- * Fires whenever an item is tapped
- * @param {Ext.dataview.component.Container} this
- * @param {Ext.dataview.component.DataItem} item The item tapped
- * @param {Number} index The index of the item tapped
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemsingletap
- * Fires whenever an item is doubletapped
- * @param {Ext.dataview.component.Container} this
- * @param {Ext.dataview.component.DataItem} item The item singletapped
- * @param {Number} index The index of the item singletapped
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemdoubletap
- * Fires whenever an item is doubletapped
- * @param {Ext.dataview.component.Container} this
- * @param {Ext.dataview.component.DataItem} item The item doubletapped
- * @param {Number} index The index of the item doubletapped
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemswipe
- * Fires whenever an item is swiped
- * @param {Ext.dataview.component.Container} this
- * @param {Ext.dataview.component.DataItem} item The item swiped
- * @param {Number} index The index of the item swiped
- * @param {Ext.EventObject} e The event object
- */
- constructor: function() {
- this.itemCache = [];
- this.callParent(arguments);
- },
- //@private
- doInitialize: function() {
- this.innerElement.on({
- touchstart: 'onItemTouchStart',
- touchend: 'onItemTouchEnd',
- tap: 'onItemTap',
- taphold: 'onItemTapHold',
- touchmove: 'onItemTouchMove',
- singletap: 'onItemSingleTap',
- doubletap: 'onItemDoubleTap',
- swipe: 'onItemSwipe',
- delegate: '> .' + Ext.baseCSSPrefix + 'data-item',
- scope: this
- });
- },
- //@private
- initialize: function() {
- this.callParent();
- this.doInitialize();
- },
- onItemTouchStart: function(e) {
- var me = this,
- target = e.getTarget(),
- item = Ext.getCmp(target.id);
- item.on({
- touchmove: 'onItemTouchMove',
- scope : me,
- single: true
- });
- me.fireEvent('itemtouchstart', me, item, me.indexOf(item), e);
- },
- onItemTouchMove: function(e) {
- var me = this,
- target = e.getTarget(),
- item = Ext.getCmp(target.id);
- me.fireEvent('itemtouchmove', me, item, me.indexOf(item), e);
- },
- onItemTouchEnd: function(e) {
- var me = this,
- target = e.getTarget(),
- item = Ext.getCmp(target.id);
- item.un({
- touchmove: 'onItemTouchMove',
- scope : me
- });
- me.fireEvent('itemtouchend', me, item, me.indexOf(item), e);
- },
- onItemTap: function(e) {
- var me = this,
- target = e.getTarget(),
- item = Ext.getCmp(target.id);
- me.fireEvent('itemtap', me, item, me.indexOf(item), e);
- },
- onItemTapHold: function(e) {
- var me = this,
- target = e.getTarget(),
- item = Ext.getCmp(target.id);
- me.fireEvent('itemtaphold', me, item, me.indexOf(item), e);
- },
- onItemSingleTap: function(e) {
- var me = this,
- target = e.getTarget(),
- item = Ext.getCmp(target.id);
- me.fireEvent('itemsingletap', me, item, me.indexOf(item), e);
- },
- onItemDoubleTap: function(e) {
- var me = this,
- target = e.getTarget(),
- item = Ext.getCmp(target.id);
- me.fireEvent('itemdoubletap', me, item, me.indexOf(item), e);
- },
- onItemSwipe: function(e) {
- var me = this,
- target = e.getTarget(),
- item = Ext.getCmp(target.id);
- me.fireEvent('itemswipe', me, item, me.indexOf(item), e);
- },
- moveItemsToCache: function(from, to) {
- var me = this,
- dataview = me.dataview,
- maxItemCache = dataview.getMaxItemCache(),
- items = me.getViewItems(),
- itemCache = me.itemCache,
- cacheLn = itemCache.length,
- pressedCls = dataview.getPressedCls(),
- selectedCls = dataview.getSelectedCls(),
- i = to - from,
- item;
- for (; i >= 0; i--) {
- item = items[from + i];
- if (cacheLn !== maxItemCache) {
- me.remove(item, false);
- item.removeCls([pressedCls, selectedCls]);
- itemCache.push(item);
- cacheLn++;
- }
- else {
- item.destroy();
- }
- }
- if (me.getViewItems().length == 0) {
- this.dataview.showEmptyText();
- }
- },
- moveItemsFromCache: function(records) {
- var me = this,
- dataview = me.dataview,
- store = dataview.getStore(),
- ln = records.length,
- xtype = dataview.getDefaultType(),
- itemConfig = dataview.getItemConfig(),
- itemCache = me.itemCache,
- cacheLn = itemCache.length,
- items = [],
- i, item, record;
- if (ln) {
- dataview.hideEmptyText();
- }
- for (i = 0; i < ln; i++) {
- records[i]._tmpIndex = store.indexOf(records[i]);
- }
- Ext.Array.sort(records, function(record1, record2) {
- return record1._tmpIndex > record2._tmpIndex ? 1 : -1;
- });
- for (i = 0; i < ln; i++) {
- record = records[i];
- if (cacheLn) {
- cacheLn--;
- item = itemCache.pop();
- this.updateListItem(record, item);
- }
- else {
- item = me.getDataItemConfig(xtype, record, itemConfig);
- }
- item = this.insert(record._tmpIndex, item);
- delete record._tmpIndex;
- }
- return items;
- },
- getViewItems: function() {
- return this.getInnerItems();
- },
- updateListItem: function(record, item) {
- if (item.updateRecord) {
- if (item.getRecord() === record) {
- item.updateRecord(record);
- } else {
- item.setRecord(record);
- }
- }
- },
- getDataItemConfig: function(xtype, record, itemConfig) {
- var dataview = this.dataview,
- dataItemConfig = {
- xtype: xtype,
- record: record,
- itemCls: dataview.getItemCls(),
- defaults: itemConfig,
- dataview: dataview
- };
- return Ext.merge(dataItemConfig, itemConfig);
- },
- doRemoveItemCls: function(cls) {
- var items = this.getViewItems(),
- ln = items.length,
- i = 0;
- for (; i < ln; i++) {
- items[i].removeCls(cls);
- }
- },
- doAddItemCls: function(cls) {
- var items = this.getViewItems(),
- ln = items.length,
- i = 0;
- for (; i < ln; i++) {
- items[i].addCls(cls);
- }
- },
- updateAtNewIndex: function(oldIndex, newIndex, record) {
- this.moveItemsToCache(oldIndex, oldIndex);
- this.moveItemsFromCache([record]);
- },
- destroy: function() {
- var me = this,
- itemCache = me.itemCache,
- ln = itemCache.length,
- i = 0;
- for (; i < ln; i++) {
- itemCache[i].destroy();
- }
- this.callParent();
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.dataview.element.Container', {
- extend: 'Ext.Component',
- /**
- * @event itemtouchstart
- * Fires whenever an item is touched
- * @param {Ext.dataview.element.Container} this
- * @param {Ext.dom.Element} item The item touched
- * @param {Number} index The index of the item touched
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemtouchmove
- * Fires whenever an item is moved
- * @param {Ext.dataview.element.Container} this
- * @param {Ext.dom.Element} item The item moved
- * @param {Number} index The index of the item moved
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemtouchend
- * Fires whenever an item is touched
- * @param {Ext.dataview.element.Container} this
- * @param {Ext.dom.Element} item The item touched
- * @param {Number} index The index of the item touched
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemtap
- * Fires whenever an item is tapped
- * @param {Ext.dataview.element.Container} this
- * @param {Ext.dom.Element} item The item tapped
- * @param {Number} index The index of the item tapped
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemtaphold
- * Fires whenever an item is tapped
- * @param {Ext.dataview.element.Container} this
- * @param {Ext.dom.Element} item The item tapped
- * @param {Number} index The index of the item tapped
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemsingletap
- * Fires whenever an item is singletapped
- * @param {Ext.dataview.element.Container} this
- * @param {Ext.dom.Element} item The item singletapped
- * @param {Number} index The index of the item singletapped
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemdoubletap
- * Fires whenever an item is doubletapped
- * @param {Ext.dataview.element.Container} this
- * @param {Ext.dom.Element} item The item doubletapped
- * @param {Number} index The index of the item doubletapped
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemswipe
- * Fires whenever an item is swiped
- * @param {Ext.dataview.element.Container} this
- * @param {Ext.dom.Element} item The item swiped
- * @param {Number} index The index of the item swiped
- * @param {Ext.EventObject} e The event object
- */
- doInitialize: function() {
- this.element.on({
- touchstart: 'onItemTouchStart',
- touchend: 'onItemTouchEnd',
- tap: 'onItemTap',
- taphold: 'onItemTapHold',
- touchmove: 'onItemTouchMove',
- singletap: 'onItemSingleTap',
- doubletap: 'onItemDoubleTap',
- swipe: 'onItemSwipe',
- delegate: '> div',
- scope: this
- });
- },
- //@private
- initialize: function() {
- this.callParent();
- this.doInitialize();
- },
- updateBaseCls: function(newBaseCls, oldBaseCls) {
- var me = this;
- me.callParent([newBaseCls + '-container', oldBaseCls]);
- },
- onItemTouchStart: function(e) {
- var me = this,
- target = e.getTarget(),
- index = me.getViewItems().indexOf(target);
- Ext.get(target).on({
- touchmove: 'onItemTouchMove',
- scope : me,
- single: true
- });
- me.fireEvent('itemtouchstart', me, Ext.get(target), index, e);
- },
- onItemTouchEnd: function(e) {
- var me = this,
- target = e.getTarget(),
- index = me.getViewItems().indexOf(target);
- Ext.get(target).un({
- touchmove: 'onItemTouchMove',
- scope : me
- });
- me.fireEvent('itemtouchend', me, Ext.get(target), index, e);
- },
- onItemTouchMove: function(e) {
- var me = this,
- target = e.getTarget(),
- index = me.getViewItems().indexOf(target);
- me.fireEvent('itemtouchmove', me, Ext.get(target), index, e);
- },
- onItemTap: function(e) {
- var me = this,
- target = e.getTarget(),
- index = me.getViewItems().indexOf(target);
- me.fireEvent('itemtap', me, Ext.get(target), index, e);
- },
- onItemTapHold: function(e) {
- var me = this,
- target = e.getTarget(),
- index = me.getViewItems().indexOf(target);
- me.fireEvent('itemtaphold', me, Ext.get(target), index, e);
- },
- onItemDoubleTap: function(e) {
- var me = this,
- target = e.getTarget(),
- index = me.getViewItems().indexOf(target);
- me.fireEvent('itemdoubletap', me, Ext.get(target), index, e);
- },
- onItemSingleTap: function(e) {
- var me = this,
- target = e.getTarget(),
- index = me.getViewItems().indexOf(target);
- me.fireEvent('itemsingletap', me, Ext.get(target), index, e);
- },
- onItemSwipe: function(e) {
- var me = this,
- target = e.getTarget(),
- index = me.getViewItems().indexOf(target);
- me.fireEvent('itemswipe', me, Ext.get(target), index, e);
- },
- updateListItem: function(record, item) {
- var me = this,
- dataview = me.dataview,
- store = dataview.getStore(),
- index = store.indexOf(record),
- data = dataview.prepareData(record.getData(true), index, record);
- data.xcount = store.getCount();
- data.xindex = typeof data.xindex === 'number' ? data.xindex : index;
- item.innerHTML = dataview.getItemTpl().apply(data);
- },
- addListItem: function(index, record) {
- var me = this,
- dataview = me.dataview,
- store = dataview.getStore(),
- data = dataview.prepareData(record.getData(true), index, record),
- element = me.element,
- childNodes = element.dom.childNodes,
- ln = childNodes.length,
- wrapElement;
- data.xcount = typeof data.xcount === 'number' ? data.xcount : store.getCount();
- data.xindex = typeof data.xindex === 'number' ? data.xindex : index;
- wrapElement = Ext.Element.create(this.getItemElementConfig(index, data));
- if (!ln || index == ln) {
- wrapElement.appendTo(element);
- } else {
- wrapElement.insertBefore(childNodes[index]);
- }
- },
- getItemElementConfig: function(index, data) {
- var dataview = this.dataview,
- itemCls = dataview.getItemCls(),
- cls = dataview.getBaseCls() + '-item';
- if (itemCls) {
- cls += ' ' + itemCls;
- }
- return {
- cls: cls,
- html: dataview.getItemTpl().apply(data)
- };
- },
- doRemoveItemCls: function(cls) {
- var elements = this.getViewItems(),
- ln = elements.length,
- i = 0;
- for (; i < ln; i++) {
- Ext.fly(elements[i]).removeCls(cls);
- }
- },
- doAddItemCls: function(cls) {
- var elements = this.getViewItems(),
- ln = elements.length,
- i = 0;
- for (; i < ln; i++) {
- Ext.fly(elements[i]).addCls(cls);
- }
- },
- // Remove
- moveItemsToCache: function(from, to) {
- var me = this,
- items = me.getViewItems(),
- i = to - from,
- item;
- for (; i >= 0; i--) {
- item = items[from + i];
- Ext.get(item).destroy();
- }
- if (me.getViewItems().length == 0) {
- this.dataview.showEmptyText();
- }
- },
- // Add
- moveItemsFromCache: function(records) {
- var me = this,
- dataview = me.dataview,
- store = dataview.getStore(),
- ln = records.length,
- i, record;
- if (ln) {
- dataview.hideEmptyText();
- }
- for (i = 0; i < ln; i++) {
- records[i]._tmpIndex = store.indexOf(records[i]);
- }
- Ext.Array.sort(records, function(record1, record2) {
- return record1._tmpIndex > record2._tmpIndex ? 1 : -1;
- });
- for (i = 0; i < ln; i++) {
- record = records[i];
- me.addListItem(record._tmpIndex, record);
- delete record._tmpIndex;
- }
- },
- // Transform ChildNodes into a proper Array so we can do indexOf...
- getViewItems: function() {
- return Array.prototype.slice.call(this.element.dom.childNodes);
- },
- updateAtNewIndex: function(oldIndex, newIndex, record) {
- this.moveItemsToCache(oldIndex, oldIndex);
- this.moveItemsFromCache([record]);
- },
- destroy: function() {
- var elements = this.getViewItems(),
- ln = elements.length,
- i = 0;
- for (; i < ln; i++) {
- Ext.get(elements[i]).destroy();
- }
- this.callParent();
- }
- });
- /**
- * Tracks what records are currently selected in a databound widget. This class is mixed in to {@link Ext.dataview.DataView} and
- * all subclasses.
- * @private
- */
- Ext.define('Ext.mixin.Selectable', {
- extend: 'Ext.mixin.Mixin',
- mixinConfig: {
- id: 'selectable',
- hooks: {
- updateStore: 'updateStore'
- }
- },
- /**
- * @event beforeselectionchange
- * Fires before an item is selected.
- * @param {Ext.mixin.Selectable} this
- * @preventable selectionchange
- * @deprecated 2.0.0 Please listen to the {@link #selectionchange} event with an order of `before` instead.
- */
- /**
- * @event selectionchange
- * Fires when a selection changes.
- * @param {Ext.mixin.Selectable} this
- * @param {Ext.data.Model[]} records The records whose selection has changed.
- */
- config: {
- /**
- * @cfg {Boolean} disableSelection `true` to disable selection.
- * This configuration will lock the selection model that the DataView uses.
- * @accessor
- */
- disableSelection: null,
- /**
- * @cfg {String} mode
- * Modes of selection.
- * Valid values are `'SINGLE'`, `'SIMPLE'`, and `'MULTI'`.
- * @accessor
- */
- mode: 'SINGLE',
- /**
- * @cfg {Boolean} allowDeselect
- * Allow users to deselect a record in a DataView, List or Grid. Only applicable when the Selectable's `mode` is
- * `'SINGLE'`.
- * @accessor
- */
- allowDeselect: false,
- /**
- * @cfg {Ext.data.Model} lastSelected
- * @private
- * @accessor
- */
- lastSelected: null,
- /**
- * @cfg {Ext.data.Model} lastFocused
- * @private
- * @accessor
- */
- lastFocused: null,
- /**
- * @cfg {Boolean} deselectOnContainerClick `true` to deselect current selection when the container body is
- * clicked.
- * @accessor
- */
- deselectOnContainerClick: true
- },
- modes: {
- SINGLE: true,
- SIMPLE: true,
- MULTI: true
- },
- selectableEventHooks: {
- addrecords: 'onSelectionStoreAdd',
- removerecords: 'onSelectionStoreRemove',
- updaterecord: 'onSelectionStoreUpdate',
- load: 'refreshSelection',
- refresh: 'refreshSelection'
- },
- constructor: function() {
- this.selected = new Ext.util.MixedCollection();
- this.callParent(arguments);
- },
- /**
- * @private
- */
- applyMode: function(mode) {
- mode = mode ? mode.toUpperCase() : 'SINGLE';
- // set to mode specified unless it doesnt exist, in that case
- // use single.
- return this.modes[mode] ? mode : 'SINGLE';
- },
- /**
- * @private
- */
- updateStore: function(newStore, oldStore) {
- var me = this,
- bindEvents = Ext.apply({}, me.selectableEventHooks, { scope: me });
- if (oldStore && Ext.isObject(oldStore) && oldStore.isStore) {
- if (oldStore.autoDestroy) {
- oldStore.destroy();
- }
- else {
- oldStore.un(bindEvents);
- newStore.un('clear', 'onSelectionStoreClear', this);
- }
- }
- if (newStore) {
- newStore.on(bindEvents);
- newStore.onBefore('clear', 'onSelectionStoreClear', this);
- me.refreshSelection();
- }
- },
- /**
- * Selects all records.
- * @param {Boolean} silent `true` to suppress all select events.
- */
- selectAll: function(silent) {
- var me = this,
- selections = me.getStore().getRange(),
- ln = selections.length,
- i = 0;
- for (; i < ln; i++) {
- me.select(selections[i], true, silent);
- }
- },
- /**
- * Deselects all records.
- */
- deselectAll: function(supress) {
- var me = this,
- selections = me.getStore().getRange();
- me.deselect(selections, supress);
- me.selected.clear();
- me.setLastSelected(null);
- me.setLastFocused(null);
- },
- // Provides differentiation of logic between MULTI, SIMPLE and SINGLE
- // selection modes.
- selectWithEvent: function(record) {
- var me = this,
- isSelected = me.isSelected(record);
- switch (me.getMode()) {
- case 'MULTI':
- case 'SIMPLE':
- if (isSelected) {
- me.deselect(record);
- }
- else {
- me.select(record, true);
- }
- break;
- case 'SINGLE':
- if (me.getAllowDeselect() && isSelected) {
- // if allowDeselect is on and this record isSelected, deselect it
- me.deselect(record);
- } else {
- // select the record and do NOT maintain existing selections
- me.select(record, false);
- }
- break;
- }
- },
- /**
- * Selects a range of rows if the selection model {@link Ext.mixin.Selectable#getDisableSelection} is not locked.
- * All rows in between `startRow` and `endRow` are also selected.
- * @param {Number} startRow The index of the first row in the range.
- * @param {Number} endRow The index of the last row in the range.
- * @param {Boolean} keepExisting (optional) `true` to retain existing selections.
- */
- selectRange: function(startRecord, endRecord, keepExisting) {
- var me = this,
- store = me.getStore(),
- records = [],
- tmp, i;
- if (me.getDisableSelection()) {
- return;
- }
- // swap values
- if (startRecord > endRecord) {
- tmp = endRecord;
- endRecord = startRecord;
- startRecord = tmp;
- }
- for (i = startRecord; i <= endRecord; i++) {
- records.push(store.getAt(i));
- }
- this.doMultiSelect(records, keepExisting);
- },
- /**
- * Adds the given records to the currently selected set.
- * @param {Ext.data.Model/Array/Number} records The records to select.
- * @param {Boolean} keepExisting If `true`, the existing selection will be added to (if not, the old selection is replaced).
- * @param {Boolean} suppressEvent If `true`, the `select` event will not be fired.
- */
- select: function(records, keepExisting, suppressEvent) {
- var me = this,
- record;
- if (me.getDisableSelection()) {
- return;
- }
- if (typeof records === "number") {
- records = [me.getStore().getAt(records)];
- }
- if (!records) {
- return;
- }
- if (me.getMode() == "SINGLE" && records) {
- record = records.length ? records[0] : records;
- me.doSingleSelect(record, suppressEvent);
- } else {
- me.doMultiSelect(records, keepExisting, suppressEvent);
- }
- },
- /**
- * Selects a single record.
- * @private
- */
- doSingleSelect: function(record, suppressEvent) {
- var me = this,
- selected = me.selected;
- if (me.getDisableSelection()) {
- return;
- }
- // already selected.
- // should we also check beforeselect?
- if (me.isSelected(record)) {
- return;
- }
- if (selected.getCount() > 0) {
- me.deselect(me.getLastSelected(), suppressEvent);
- }
- selected.add(record);
- me.setLastSelected(record);
- me.onItemSelect(record, suppressEvent);
- me.setLastFocused(record);
- if (!suppressEvent) {
- me.fireSelectionChange([record]);
- }
- },
- /**
- * Selects a set of multiple records.
- * @private
- */
- doMultiSelect: function(records, keepExisting, suppressEvent) {
- if (records === null || this.getDisableSelection()) {
- return;
- }
- records = !Ext.isArray(records) ? [records] : records;
- var me = this,
- selected = me.selected,
- ln = records.length,
- change = false,
- i = 0,
- record;
- if (!keepExisting && selected.getCount() > 0) {
- change = true;
- me.deselect(me.getSelection(), true);
- }
- for (; i < ln; i++) {
- record = records[i];
- if (keepExisting && me.isSelected(record)) {
- continue;
- }
- change = true;
- me.setLastSelected(record);
- selected.add(record);
- if (!suppressEvent) {
- me.setLastFocused(record);
- }
- me.onItemSelect(record, suppressEvent);
- }
- if (change && !suppressEvent) {
- this.fireSelectionChange(records);
- }
- },
- /**
- * Deselects the given record(s). If many records are currently selected, it will only deselect those you pass in.
- * @param {Number/Array/Ext.data.Model} records The record(s) to deselect. Can also be a number to reference by index.
- * @param {Boolean} suppressEvent If `true` the `deselect` event will not be fired.
- */
- deselect: function(records, suppressEvent) {
- var me = this;
- if (me.getDisableSelection()) {
- return;
- }
- records = Ext.isArray(records) ? records : [records];
- var selected = me.selected,
- change = false,
- i = 0,
- store = me.getStore(),
- ln = records.length,
- record;
- for (; i < ln; i++) {
- record = records[i];
- if (typeof record === 'number') {
- record = store.getAt(record);
- }
- if (selected.remove(record)) {
- if (me.getLastSelected() == record) {
- me.setLastSelected(selected.last());
- }
- change = true;
- }
- if (record) {
- me.onItemDeselect(record, suppressEvent);
- }
- }
- if (change && !suppressEvent) {
- me.fireSelectionChange(records);
- }
- },
- /**
- * Sets a record as the last focused record. This does NOT mean
- * that the record has been selected.
- * @param {Ext.data.Record} newRecord
- * @param {Ext.data.Record} oldRecord
- */
- updateLastFocused: function(newRecord, oldRecord) {
- this.onLastFocusChanged(oldRecord, newRecord);
- },
- fireSelectionChange: function(records) {
- var me = this;
- me.fireAction('selectionchange', [me, records], 'getSelection');
- },
- /**
- * Returns an array of the currently selected records.
- * @return {Array} An array of selected records.
- */
- getSelection: function() {
- return this.selected.getRange();
- },
- /**
- * Returns `true` if the specified row is selected.
- * @param {Ext.data.Model/Number} record The record or index of the record to check.
- * @return {Boolean}
- */
- isSelected: function(record) {
- record = Ext.isNumber(record) ? this.getStore().getAt(record) : record;
- return this.selected.indexOf(record) !== -1;
- },
- /**
- * Returns `true` if there is a selected record.
- * @return {Boolean}
- */
- hasSelection: function() {
- return this.selected.getCount() > 0;
- },
- /**
- * @private
- */
- refreshSelection: function() {
- var me = this,
- selections = me.getSelection();
- me.deselectAll(true);
- if (selections.length) {
- me.select(selections, false, true);
- }
- },
- // prune records from the SelectionModel if
- // they were selected at the time they were
- // removed.
- onSelectionStoreRemove: function(store, records) {
- var me = this,
- selected = me.selected,
- ln = records.length,
- record, i;
- if (me.getDisableSelection()) {
- return;
- }
- for (i = 0; i < ln; i++) {
- record = records[i];
- if (selected.remove(record)) {
- if (me.getLastSelected() == record) {
- me.setLastSelected(null);
- }
- if (me.getLastFocused() == record) {
- me.setLastFocused(null);
- }
- me.fireSelectionChange([record]);
- }
- }
- },
- onSelectionStoreClear: function(store) {
- var records = store.getData().items;
- this.onSelectionStoreRemove(store, records);
- },
- /**
- * Returns the number of selections.
- * @return {Number}
- */
- getSelectionCount: function() {
- return this.selected.getCount();
- },
- onSelectionStoreAdd: Ext.emptyFn,
- onSelectionStoreUpdate: Ext.emptyFn,
- onItemSelect: Ext.emptyFn,
- onItemDeselect: Ext.emptyFn,
- onLastFocusChanged: Ext.emptyFn,
- onEditorKey: Ext.emptyFn
- }, function() {
- /**
- * Selects a record instance by record instance or index.
- * @member Ext.mixin.Selectable
- * @method doSelect
- * @param {Ext.data.Model/Number} records An array of records or an index.
- * @param {Boolean} keepExisting
- * @param {Boolean} suppressEvent Set to `false` to not fire a select event.
- * @deprecated 2.0.0 Please use {@link #select} instead.
- */
- /**
- * Deselects a record instance by record instance or index.
- * @member Ext.mixin.Selectable
- * @method doDeselect
- * @param {Ext.data.Model/Number} records An array of records or an index.
- * @param {Boolean} suppressEvent Set to `false` to not fire a deselect event.
- * @deprecated 2.0.0 Please use {@link #deselect} instead.
- */
- /**
- * Returns the selection mode currently used by this Selectable.
- * @member Ext.mixin.Selectable
- * @method getSelectionMode
- * @return {String} The current mode.
- * @deprecated 2.0.0 Please use {@link #getMode} instead.
- */
- /**
- * Returns the array of previously selected items.
- * @member Ext.mixin.Selectable
- * @method getLastSelected
- * @return {Array} The previous selection.
- * @deprecated 2.0.0
- */
- /**
- * Returns `true` if the Selectable is currently locked.
- * @member Ext.mixin.Selectable
- * @method isLocked
- * @return {Boolean} True if currently locked
- * @deprecated 2.0.0 Please use {@link #getDisableSelection} instead.
- */
- /**
- * This was an internal function accidentally exposed in 1.x and now deprecated. Calling it has no effect
- * @member Ext.mixin.Selectable
- * @method setLastFocused
- * @deprecated 2.0.0
- */
- /**
- * Deselects any currently selected records and clears all stored selections.
- * @member Ext.mixin.Selectable
- * @method clearSelections
- * @deprecated 2.0.0 Please use {@link #deselectAll} instead.
- */
- /**
- * Returns the number of selections.
- * @member Ext.mixin.Selectable
- * @method getCount
- * @return {Number}
- * @deprecated 2.0.0 Please use {@link #getSelectionCount} instead.
- */
- /**
- * @cfg {Boolean} locked
- * @inheritdoc Ext.mixin.Selectable#disableSelection
- * @deprecated 2.0.0 Please use {@link #disableSelection} instead.
- */
- });
- /**
- * @aside guide dataview
- *
- * DataView makes it easy to create lots of components dynamically, usually based off a {@link Ext.data.Store Store}.
- * It's great for rendering lots of data from your server backend or any other data source and is what powers
- * components like {@link Ext.List}.
- *
- * Use DataView whenever you want to show sets of the same component many times, for examples in apps like these:
- *
- * - List of messages in an email app
- * - Showing latest news/tweets
- * - Tiled set of albums in an HTML5 music player
- *
- * # Creating a Simple DataView
- *
- * At its simplest, a DataView is just a Store full of data and a simple template that we use to render each item:
- *
- * @example miniphone preview
- * var touchTeam = Ext.create('Ext.DataView', {
- * fullscreen: true,
- * store: {
- * fields: ['name', 'age'],
- * data: [
- * {name: 'Jamie', age: 100},
- * {name: 'Rob', age: 21},
- * {name: 'Tommy', age: 24},
- * {name: 'Jacky', age: 24},
- * {name: 'Ed', age: 26}
- * ]
- * },
- *
- * itemTpl: '<div>{name} is {age} years old</div>'
- * });
- *
- * Here we just defined everything inline so it's all local with nothing being loaded from a server. For each of the 5
- * data items defined in our Store, DataView will render a {@link Ext.Component Component} and pass in the name and age
- * data. The component will use the tpl we provided above, rendering the data in the curly bracket placeholders we
- * provided.
- *
- * Because DataView is integrated with Store, any changes to the Store are immediately reflected on the screen. For
- * example, if we add a new record to the Store it will be rendered into our DataView:
- *
- * touchTeam.getStore().add({
- * name: 'Abe Elias',
- * age: 33
- * });
- *
- * We didn't have to manually update the DataView, it's just automatically updated. The same happens if we modify one
- * of the existing records in the Store:
- *
- * touchTeam.getStore().getAt(0).set('age', 42);
- *
- * This will get the first record in the Store (Jamie), change the age to 42 and automatically update what's on the
- * screen.
- *
- * @example miniphone
- * var touchTeam = Ext.create('Ext.DataView', {
- * fullscreen: true,
- * store: {
- * fields: ['name', 'age'],
- * data: [
- * {name: 'Jamie', age: 100},
- * {name: 'Rob', age: 21},
- * {name: 'Tommy', age: 24},
- * {name: 'Jacky', age: 24},
- * {name: 'Ed', age: 26}
- * ]
- * },
- *
- * itemTpl: '<div>{name} is {age} years old</div>'
- * });
- *
- * touchTeam.getStore().add({
- * name: 'Abe Elias',
- * age: 33
- * });
- *
- * touchTeam.getStore().getAt(0).set('age', 42);
- *
- * # Loading data from a server
- *
- * 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
- * locally. Let's say we want to load all of the latest tweets about Sencha Touch into a DataView, and for each one
- * render the user's profile picture, user name and tweet message. To do this all we have to do is modify the
- * {@link #store} and {@link #itemTpl} a little:
- *
- * @example portrait
- * Ext.create('Ext.DataView', {
- * fullscreen: true,
- * cls: 'twitterView',
- * store: {
- * autoLoad: true,
- * fields: ['from_user', 'text', 'profile_image_url'],
- *
- * proxy: {
- * type: 'jsonp',
- * url: 'http://search.twitter.com/search.json?q=Sencha Touch',
- *
- * reader: {
- * type: 'json',
- * rootProperty: 'results'
- * }
- * }
- * },
- *
- * itemTpl: '<img src="{profile_image_url}" /><h2>{from_user}</h2><p>{text}</p><div style="clear: both"></div>'
- * });
- *
- * The Store no longer has hard coded data, instead we've provided a {@link Ext.data.proxy.Proxy Proxy}, which fetches
- * 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
- * specified the fields present for each tweet, and used Store's {@link Ext.data.Store#autoLoad autoLoad} configuration
- * to load automatically. Finally, we configured a Reader to decode the response from Twitter, telling it to expect
- * JSON and that the tweets can be found in the 'results' part of the JSON response.
- *
- * The last thing we did is update our template to render the image, Twitter username and message. All we need to do
- * 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
- * the preview button on the example above to see it in action.
- */
- Ext.define('Ext.dataview.DataView', {
- extend: 'Ext.Container',
- alternateClassName: 'Ext.DataView',
- mixins: ['Ext.mixin.Selectable'],
- xtype: 'dataview',
- requires: [
- 'Ext.LoadMask',
- 'Ext.data.StoreManager',
- 'Ext.dataview.component.Container',
- 'Ext.dataview.element.Container'
- ],
- /**
- * @event containertap
- * Fires when a tap occurs and it is not on a template node.
- * @removed 2.0.0
- */
- /**
- * @event itemtouchstart
- * Fires whenever an item is touched
- * @param {Ext.dataview.DataView} this
- * @param {Number} index The index of the item touched
- * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem touched
- * @param {Ext.data.Model} record The record associated to the item
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemtouchmove
- * Fires whenever an item is moved
- * @param {Ext.dataview.DataView} this
- * @param {Number} index The index of the item moved
- * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem moved
- * @param {Ext.data.Model} record The record associated to the item
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemtouchend
- * Fires whenever an item is touched
- * @param {Ext.dataview.DataView} this
- * @param {Number} index The index of the item touched
- * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem touched
- * @param {Ext.data.Model} record The record associated to the item
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemtap
- * Fires whenever an item is tapped
- * @param {Ext.dataview.DataView} this
- * @param {Number} index The index of the item tapped
- * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem tapped
- * @param {Ext.data.Model} record The record associated to the item
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemtaphold
- * Fires whenever an item's taphold event fires
- * @param {Ext.dataview.DataView} this
- * @param {Number} index The index of the item touched
- * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem touched
- * @param {Ext.data.Model} record The record associated to the item
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemsingletap
- * Fires whenever an item is singletapped
- * @param {Ext.dataview.DataView} this
- * @param {Number} index The index of the item singletapped
- * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem singletapped
- * @param {Ext.data.Model} record The record associated to the item
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemdoubletap
- * Fires whenever an item is doubletapped
- * @param {Ext.dataview.DataView} this
- * @param {Number} index The index of the item doubletapped
- * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem doubletapped
- * @param {Ext.data.Model} record The record associated to the item
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event itemswipe
- * Fires whenever an item is swiped
- * @param {Ext.dataview.DataView} this
- * @param {Number} index The index of the item swiped
- * @param {Ext.Element/Ext.dataview.component.DataItem} target The element or DataItem swiped
- * @param {Ext.data.Model} record The record associated to the item
- * @param {Ext.EventObject} e The event object
- */
- /**
- * @event select
- * @preventable doItemSelect
- * Fires whenever an item is selected
- * @param {Ext.dataview.DataView} this
- * @param {Ext.data.Model} record The record associated to the item
- */
- /**
- * @event deselect
- * @preventable doItemDeselect
- * Fires whenever an item is deselected
- * @param {Ext.dataview.DataView} this
- * @param {Ext.data.Model} record The record associated to the item
- * @param {Boolean} supressed Flag to suppress the event
- */
- /**
- * @event refresh
- * @preventable doRefresh
- * Fires whenever the DataView is refreshed
- * @param {Ext.dataview.DataView} this
- */
- /**
- * @hide
- * @event add
- */
- /**
- * @hide
- * @event remove
- */
- /**
- * @hide
- * @event move
- */
- config: {
- /**
- * @cfg layout
- * Hide layout config in DataView. It only causes confusion.
- * @accessor
- * @private
- */
- /**
- * @cfg {Ext.data.Store/Object} store
- * Can be either a Store instance or a configuration object that will be turned into a Store. The Store is used
- * to populate the set of items that will be rendered in the DataView. See the DataView intro documentation for
- * more information about the relationship between Store and DataView.
- * @accessor
- */
- store: null,
- /**
- * @cfg baseCls
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'dataview',
- /**
- * @cfg {String} emptyText
- * The text to display in the view when there is no data to display
- */
- emptyText: null,
- /**
- * @cfg {Boolean} deferEmptyText `true` to defer `emptyText` being applied until the store's first load.
- */
- deferEmptyText: true,
- /**
- * @cfg {String/String[]/Ext.XTemplate} itemTpl
- * The `tpl` to use for each of the items displayed in this DataView.
- */
- itemTpl: '<div>{text}</div>',
- /**
- * @cfg {String} pressedCls
- * The CSS class to apply to an item on the view while it is being pressed.
- * @accessor
- */
- pressedCls: 'x-item-pressed',
- /**
- * @cfg {String} itemCls
- * An additional CSS class to apply to items within the DataView.
- * @accessor
- */
- itemCls: null,
- /**
- * @cfg {String} selectedCls
- * The CSS class to apply to an item on the view while it is selected.
- * @accessor
- */
- selectedCls: 'x-item-selected',
- /**
- * @cfg {String} triggerEvent
- * Determines what type of touch event causes an item to be selected.
- * Valid options are: 'itemtap', 'itemsingletap', 'itemdoubletap', 'itemswipe', 'itemtaphold'.
- * @accessor
- */
- triggerEvent: 'itemtap',
- /**
- * @cfg {String} triggerCtEvent
- * Determines what type of touch event is recognized as a touch on the container.
- * Valid options are 'tap' and 'singletap'.
- * @accessor
- */
- triggerCtEvent: 'tap',
- /**
- * @cfg {Boolean} deselectOnContainerClick
- * When set to true, tapping on the DataView's background (i.e. not on
- * an item in the DataView) will deselect any currently selected items.
- * @accessor
- */
- deselectOnContainerClick: true,
- /**
- * @cfg scrollable
- * @inheritdoc
- */
- scrollable: true,
- /**
- * @cfg {Boolean/Object} inline
- * When set to `true` the items within the DataView will have their display set to inline-block
- * and be arranged horizontally. By default the items will wrap to the width of the DataView.
- * Passing an object with `{ wrap: false }` will turn off this wrapping behavior and overflowed
- * items will need to be scrolled to horizontally.
- * @accessor
- */
- inline: null,
- /**
- * @cfg {Number} pressedDelay
- * The amount of delay between the `tapstart` and the moment we add the `pressedCls`.
- *
- * Settings this to `true` defaults to 100ms.
- * @accessor
- */
- pressedDelay: 100,
- /**
- * @cfg {String} loadingText
- * A string to display during data load operations. If specified, this text will be
- * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's
- * contents will continue to display normally until the new data is loaded and the contents are replaced.
- */
- loadingText: 'Loading...',
- /**
- * @cfg {Boolean} useComponents
- * Flag the use a component based DataView implementation. This allows the full use of components in the
- * DataView at the cost of some performance.
- *
- * Checkout the [DataView Guide](#!/guide/dataview) for more information on using this configuration.
- * @accessor
- */
- useComponents: null,
- /**
- * @cfg {Object} itemConfig
- * A configuration object that is passed to every item created by a component based DataView. Because each
- * item that a DataView renders is a Component, we can pass configuration options to each component to
- * easily customize how each child component behaves.
- *
- * __Note:__ this is only used when `{@link #useComponents}` is `true`.
- * @accessor
- */
- itemConfig: {},
- /**
- * @cfg {Number} maxItemCache
- * Maintains a cache of reusable components when using a component based DataView. Improving performance at
- * the cost of memory.
- *
- * __Note:__ this is currently only used when `{@link #useComponents}` is `true`.
- * @accessor
- */
- maxItemCache: 20,
- /**
- * @cfg {String} defaultType
- * The xtype used for the component based DataView.
- *
- * __Note:__ this is only used when `{@link #useComponents}` is `true`.
- * @accessor
- */
- defaultType: 'dataitem',
- /**
- * @cfg {Boolean} scrollToTopOnRefresh
- * Scroll the DataView to the top when the DataView is refreshed.
- * @accessor
- */
- scrollToTopOnRefresh: true
- },
- constructor: function(config) {
- var me = this,
- layout;
- me.hasLoadedStore = false;
- me.mixins.selectable.constructor.apply(me, arguments);
- me.indexOffset = 0;
- me.callParent(arguments);
- //<debug>
- layout = this.getLayout();
- if (layout && !layout.isAuto) {
- Ext.Logger.error('The base layout for a DataView must always be an Auto Layout');
- }
- //</debug>
- },
- updateItemCls: function(newCls, oldCls) {
- var container = this.container;
- if (container) {
- if (oldCls) {
- container.doRemoveItemCls(oldCls);
- }
- if (newCls) {
- container.doAddItemCls(newCls);
- }
- }
- },
- storeEventHooks: {
- beforeload: 'onBeforeLoad',
- load: 'onLoad',
- refresh: 'refresh',
- addrecords: 'onStoreAdd',
- removerecords: 'onStoreRemove',
- updaterecord: 'onStoreUpdate'
- },
- initialize: function() {
- this.callParent();
- var me = this,
- container;
- me.on(me.getTriggerCtEvent(), me.onContainerTrigger, me);
- container = me.container = this.add(new Ext.dataview[me.getUseComponents() ? 'component' : 'element'].Container({
- baseCls: this.getBaseCls()
- }));
- container.dataview = me;
- me.on(me.getTriggerEvent(), me.onItemTrigger, me);
- container.on({
- itemtouchstart: 'onItemTouchStart',
- itemtouchend: 'onItemTouchEnd',
- itemtap: 'onItemTap',
- itemtaphold: 'onItemTapHold',
- itemtouchmove: 'onItemTouchMove',
- itemsingletap: 'onItemSingleTap',
- itemdoubletap: 'onItemDoubleTap',
- itemswipe: 'onItemSwipe',
- scope: me
- });
- if (me.getStore()) {
- if (me.isPainted()) {
- me.refresh();
- }
- else {
- me.on({
- painted: 'refresh',
- single: true
- });
- }
- }
- },
- applyInline: function(config) {
- if (Ext.isObject(config)) {
- config = Ext.apply({}, config);
- }
- return config;
- },
- updateInline: function(newInline, oldInline) {
- var baseCls = this.getBaseCls();
- if (oldInline) {
- this.removeCls([baseCls + '-inlineblock', baseCls + '-nowrap']);
- }
- if (newInline) {
- this.addCls(baseCls + '-inlineblock');
- if (Ext.isObject(newInline) && newInline.wrap === false) {
- this.addCls(baseCls + '-nowrap');
- }
- else {
- this.removeCls(baseCls + '-nowrap');
- }
- }
- },
- /**
- * Function which can be overridden to provide custom formatting for each Record that is used by this
- * DataView's {@link #tpl template} to render each node.
- * @param {Object/Object[]} data The raw data object that was used to create the Record.
- * @param {Number} recordIndex the index number of the Record being prepared for rendering.
- * @param {Ext.data.Model} record The Record being prepared for rendering.
- * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s `overwrite()` method.
- * (either an array if your params are numeric (i.e. `{0}`) or an object (i.e. `{foo: 'bar'}`))
- */
- prepareData: function(data, index, record) {
- return data;
- },
- // apply to the selection model to maintain visual UI cues
- onContainerTrigger: function(e) {
- var me = this;
- if (e.target != me.element.dom) {
- return;
- }
- if (me.getDeselectOnContainerClick() && me.getStore()) {
- me.deselectAll();
- }
- },
- // apply to the selection model to maintain visual UI cues
- onItemTrigger: function(me, index) {
- this.selectWithEvent(this.getStore().getAt(index));
- },
- doAddPressedCls: function(record) {
- var me = this,
- item = me.getItemAt(me.getStore().indexOf(record));
- if (Ext.isElement(item)) {
- item = Ext.get(item);
- }
- if (item) {
- item.addCls(me.getPressedCls());
- }
- },
- onItemTouchStart: function(container, target, index, e) {
- var me = this,
- store = me.getStore(),
- record = store && store.getAt(index);
- me.fireAction('itemtouchstart', [me, index, target, record, e], 'doItemTouchStart');
- },
- doItemTouchStart: function(me, index, target, record) {
- var pressedDelay = me.getPressedDelay();
- if (record) {
- if (pressedDelay > 0) {
- me.pressedTimeout = Ext.defer(me.doAddPressedCls, pressedDelay, me, [record]);
- }
- else {
- me.doAddPressedCls(record);
- }
- }
- },
- onItemTouchEnd: function(container, target, index, e) {
- var me = this,
- store = me.getStore(),
- record = store && store.getAt(index);
- if (this.hasOwnProperty('pressedTimeout')) {
- clearTimeout(this.pressedTimeout);
- delete this.pressedTimeout;
- }
- if (record && target) {
- target.removeCls(me.getPressedCls());
- }
- me.fireEvent('itemtouchend', me, index, target, record, e);
- },
- onItemTouchMove: function(container, target, index, e) {
- var me = this,
- store = me.getStore(),
- record = store && store.getAt(index);
- if (me.hasOwnProperty('pressedTimeout')) {
- clearTimeout(me.pressedTimeout);
- delete me.pressedTimeout;
- }
- if (record && target) {
- target.removeCls(me.getPressedCls());
- }
- me.fireEvent('itemtouchmove', me, index, target, record, e);
- },
- onItemTap: function(container, target, index, e) {
- var me = this,
- store = me.getStore(),
- record = store && store.getAt(index);
- me.fireEvent('itemtap', me, index, target, record, e);
- },
- onItemTapHold: function(container, target, index, e) {
- var me = this,
- store = me.getStore(),
- record = store && store.getAt(index);
- me.fireEvent('itemtaphold', me, index, target, record, e);
- },
- onItemSingleTap: function(container, target, index, e) {
- var me = this,
- store = me.getStore(),
- record = store && store.getAt(index);
- me.fireEvent('itemsingletap', me, index, target, record, e);
- },
- onItemDoubleTap: function(container, target, index, e) {
- var me = this,
- store = me.getStore(),
- record = store && store.getAt(index);
- me.fireEvent('itemdoubletap', me, index, target, record, e);
- },
- onItemSwipe: function(container, target, index, e) {
- var me = this,
- store = me.getStore(),
- record = store && store.getAt(index);
- me.fireEvent('itemswipe', me, index, target, record, e);
- },
- // invoked by the selection model to maintain visual UI cues
- onItemSelect: function(record, suppressEvent) {
- var me = this;
- if (suppressEvent) {
- me.doItemSelect(me, record);
- } else {
- me.fireAction('select', [me, record], 'doItemSelect');
- }
- },
- // invoked by the selection model to maintain visual UI cues
- doItemSelect: function(me, record) {
- if (me.container && !me.isDestroyed) {
- var item = me.getItemAt(me.getStore().indexOf(record));
- if (Ext.isElement(item)) {
- item = Ext.get(item);
- }
- if (item) {
- item.removeCls(me.getPressedCls());
- item.addCls(me.getSelectedCls());
- }
- }
- },
- // invoked by the selection model to maintain visual UI cues
- onItemDeselect: function(record, suppressEvent) {
- var me = this;
- if (me.container && !me.isDestroyed) {
- if (suppressEvent) {
- me.doItemDeselect(me, record);
- }
- else {
- me.fireAction('deselect', [me, record, suppressEvent], 'doItemDeselect');
- }
- }
- },
- doItemDeselect: function(me, record) {
- var item = me.getItemAt(me.getStore().indexOf(record));
- if (Ext.isElement(item)) {
- item = Ext.get(item);
- }
- if (item) {
- item.removeCls([me.getPressedCls(), me.getSelectedCls()]);
- }
- },
- updateData: function(data) {
- var store = this.getStore();
- if (!store) {
- this.setStore(Ext.create('Ext.data.Store', {
- data: data
- }));
- } else {
- store.add(data);
- }
- },
- applyStore: function(store) {
- var me = this,
- bindEvents = Ext.apply({}, me.storeEventHooks, { scope: me }),
- proxy, reader;
- if (store) {
- store = Ext.data.StoreManager.lookup(store);
- if (store && Ext.isObject(store) && store.isStore) {
- store.on(bindEvents);
- proxy = store.getProxy();
- if (proxy) {
- reader = proxy.getReader();
- if (reader) {
- reader.on('exception', 'handleException', this);
- }
- }
- }
- //<debug warn>
- else {
- Ext.Logger.warn("The specified Store cannot be found", this);
- }
- //</debug>
- }
- return store;
- },
- /**
- * Method called when the Store's Reader throws an exception
- * @method handleException
- */
- handleException: function() {
- this.setMasked(false);
- },
- updateStore: function(newStore, oldStore) {
- var me = this,
- bindEvents = Ext.apply({}, me.storeEventHooks, { scope: me }),
- proxy, reader;
- if (oldStore && Ext.isObject(oldStore) && oldStore.isStore) {
- me.onStoreClear();
- if (oldStore.getAutoDestroy()) {
- oldStore.destroy();
- }
- else {
- oldStore.un(bindEvents);
- proxy = oldStore.getProxy();
- if (proxy) {
- reader = proxy.getReader();
- if (reader) {
- reader.un('exception', 'handleException', this);
- }
- }
- }
- }
- if (newStore) {
- if (newStore.isLoaded()) {
- this.hasLoadedStore = true;
- }
- if (newStore.isLoading()) {
- me.onBeforeLoad();
- }
- if (me.container) {
- me.refresh();
- }
- }
- },
- onBeforeLoad: function() {
- var loadingText = this.getLoadingText();
- if (loadingText && this.isPainted()) {
- this.setMasked({
- xtype: 'loadmask',
- message: loadingText
- });
- }
- this.hideEmptyText();
- },
- updateEmptyText: function(newEmptyText, oldEmptyText) {
- var me = this,
- store;
- if (oldEmptyText && me.emptyTextCmp) {
- me.remove(me.emptyTextCmp, true);
- delete me.emptyTextCmp;
- }
- if (newEmptyText) {
- me.emptyTextCmp = me.add({
- xtype: 'component',
- cls: me.getBaseCls() + '-emptytext',
- html: newEmptyText,
- hidden: true
- });
- store = me.getStore();
- if (store && me.hasLoadedStore && !store.getCount()) {
- this.showEmptyText();
- }
- }
- },
- onLoad: function(store) {
- //remove any masks on the store
- this.hasLoadedStore = true;
- this.setMasked(false);
- if (!store.getCount()) {
- this.showEmptyText();
- }
- },
- /**
- * Refreshes the view by reloading the data from the store and re-rendering the template.
- */
- refresh: function() {
- var me = this,
- container = me.container;
- if (!me.getStore()) {
- if (!me.hasLoadedStore && !me.getDeferEmptyText()) {
- me.showEmptyText();
- }
- return;
- }
- if (container) {
- me.fireAction('refresh', [me], 'doRefresh');
- }
- },
- applyItemTpl: function(config) {
- return (Ext.isObject(config) && config.isTemplate) ? config : new Ext.XTemplate(config);
- },
- onAfterRender: function() {
- var me = this;
- me.callParent(arguments);
- me.updateStore(me.getStore());
- },
- /**
- * Returns an item at the specified index.
- * @param {Number} index Index of the item.
- * @return {Ext.dom.Element/Ext.dataview.component.DataItem} item Item at the specified index.
- */
- getItemAt: function(index) {
- return this.getViewItems()[index - this.indexOffset];
- },
- /**
- * Returns an index for the specified item.
- * @param {Number} item The item to locate.
- * @return {Number} Index for the specified item.
- */
- getItemIndex: function(item) {
- var index = this.getViewItems().indexOf(item);
- return (index === -1) ? index : this.indexOffset + index;
- },
- /**
- * Returns an array of the current items in the DataView.
- * @return {Ext.dom.Element[]/Ext.dataview.component.DataItem[]} Array of Items.
- */
- getViewItems: function() {
- return this.container.getViewItems();
- },
- doRefresh: function(me) {
- var container = me.container,
- store = me.getStore(),
- records = store.getRange(),
- items = me.getViewItems(),
- recordsLn = records.length,
- itemsLn = items.length,
- deltaLn = recordsLn - itemsLn,
- scrollable = me.getScrollable(),
- i, item;
- if (this.getScrollToTopOnRefresh() && scrollable) {
- scrollable.getScroller().scrollToTop();
- }
- // No items, hide all the items from the collection.
- if (recordsLn < 1) {
- me.onStoreClear();
- return;
- } else {
- me.hideEmptyText();
- }
- // Too many items, hide the unused ones
- if (deltaLn < 0) {
- container.moveItemsToCache(itemsLn + deltaLn, itemsLn - 1);
- // Items can changed, we need to refresh our references
- items = me.getViewItems();
- itemsLn = items.length;
- }
- // Not enough items, create new ones
- else if (deltaLn > 0) {
- container.moveItemsFromCache(store.getRange(itemsLn));
- }
- // Update Data and insert the new html for existing items
- for (i = 0; i < itemsLn; i++) {
- item = items[i];
- container.updateListItem(records[i], item);
- }
- },
- showEmptyText: function() {
- if (this.getEmptyText() && (this.hasLoadedStore || !this.getDeferEmptyText()) ) {
- this.emptyTextCmp.show();
- }
- },
- hideEmptyText: function() {
- if (this.getEmptyText()) {
- this.emptyTextCmp.hide();
- }
- },
- destroy: function() {
- var store = this.getStore();
- if (store && store.getAutoDestroy()) {
- store.destroy();
- }
- this.callParent(arguments);
- },
- onStoreClear: function() {
- var me = this,
- container = me.container,
- items = me.getViewItems();
- container.moveItemsToCache(0, items.length - 1);
- this.showEmptyText();
- },
- /**
- * @private
- * @param store
- * @param records
- */
- onStoreAdd: function(store, records) {
- if (records) {
- this.hideEmptyText();
- this.container.moveItemsFromCache(records);
- }
- },
- /**
- * @private
- * @param store
- * @param records
- * @param indices
- */
- onStoreRemove: function(store, records, indices) {
- var container = this.container,
- ln = records.length,
- i;
- for (i = 0; i < ln; i++) {
- container.moveItemsToCache(indices[i], indices[i]);
- }
- },
- /**
- * @private
- * @param store
- * @param record
- * @param {Number} newIndex
- * @param {Number} oldIndex
- */
- onStoreUpdate: function(store, record, newIndex, oldIndex) {
- var me = this,
- container = me.container;
- oldIndex = (typeof oldIndex === 'undefined') ? newIndex : oldIndex;
- if (oldIndex !== newIndex) {
- container.updateAtNewIndex(oldIndex, newIndex, record);
- if (me.isSelected(record)) {
- me.doItemSelect(me, record);
- }
- }
- else {
- // Bypassing setter because sometimes we pass the same record (different data)
- container.updateListItem(record, me.getViewItems()[newIndex]);
- }
- }
- });
- /**
- * @author Ed Spencer
- *
- * Represents a single read or write operation performed by a {@link Ext.data.proxy.Proxy Proxy}. Operation objects are
- * used to enable communication between Stores and Proxies. Application developers should rarely need to interact with
- * Operation objects directly.
- *
- * Note that when you define an Operation directly, you need to specify at least the {@link #model} configuration.
- *
- * Several Operations can be batched together in a {@link Ext.data.Batch batch}.
- */
- Ext.define('Ext.data.Operation', {
- config: {
- /**
- * @cfg {Boolean} synchronous
- * True if this Operation is to be executed synchronously. This property is inspected by a
- * {@link Ext.data.Batch Batch} to see if a series of Operations can be executed in parallel or not.
- * @accessor
- */
- synchronous: true,
- /**
- * @cfg {String} action
- * The action being performed by this Operation. Should be one of 'create', 'read', 'update' or 'destroy'.
- * @accessor
- */
- action: null,
- /**
- * @cfg {Ext.util.Filter[]} filters
- * Optional array of filter objects. Only applies to 'read' actions.
- * @accessor
- */
- filters: null,
- /**
- * @cfg {Ext.util.Sorter[]} sorters
- * Optional array of sorter objects. Only applies to 'read' actions.
- * @accessor
- */
- sorters: null,
- /**
- * @cfg {Ext.util.Grouper} grouper
- * Optional grouping configuration. Only applies to 'read' actions where grouping is desired.
- * @accessor
- */
- grouper: null,
- /**
- * @cfg {Number} start
- * The start index (offset), used in paging when running a 'read' action.
- * @accessor
- */
- start: null,
- /**
- * @cfg {Number} limit
- * The number of records to load. Used on 'read' actions when paging is being used.
- * @accessor
- */
- limit: null,
- /**
- * @cfg {Ext.data.Batch} batch
- * The batch that this Operation is a part of.
- * @accessor
- */
- batch: null,
- /**
- * @cfg {Function} callback
- * Function to execute when operation completed.
- * @cfg {Ext.data.Model[]} callback.records Array of records.
- * @cfg {Ext.data.Operation} callback.operation The Operation itself.
- * @accessor
- */
- callback: null,
- /**
- * @cfg {Object} scope
- * Scope for the {@link #callback} function.
- * @accessor
- */
- scope: null,
- /**
- * @cfg {Ext.data.ResultSet} resultSet
- * The ResultSet for this operation.
- * @accessor
- */
- resultSet: null,
- /**
- * @cfg {Array} records
- * The records associated to this operation. Before an operation starts, these
- * are the records you are updating, creating, or destroying. After an operation
- * is completed, a Proxy usually sets these records on the Operation to become
- * the processed records. If you don't set these records on your operation in
- * your proxy, then the getter will return the ones defined on the {@link #resultSet}
- * @accessor
- */
- records: null,
- /**
- * @cfg {Ext.data.Request} request
- * The request used for this Operation. Operations don't usually care about Request and Response data, but in the
- * ServerProxy and any of its subclasses they may be useful for further processing.
- * @accessor
- */
- request: null,
- /**
- * @cfg {Object} response
- * The response that was gotten from the server if there was one.
- * @accessor
- */
- response: null,
- /**
- * @cfg {Boolean} withCredentials
- * This field is necessary when using cross-origin resource sharing.
- * @accessor
- */
- withCredentials: null,
- /**
- * @cfg {Object} params
- * The params send along with this operation. These usually apply to a Server proxy if you are
- * creating your own custom proxy,
- * @accessor
- */
- params: null,
- url: null,
- page: null,
- node: null,
- /**
- * @cfg {Ext.data.Model} model
- * The Model that this Operation will be dealing with. This configuration is required when defining any Operation.
- * Since Operations take care of creating, updating, destroying and reading records, it needs access to the Model.
- * @accessor
- */
- model: undefined,
- addRecords: false
- },
- /**
- * @property {Boolean} started
- * Property tracking the start status of this Operation. Use {@link #isStarted}.
- * @private
- * @readonly
- */
- started: false,
- /**
- * @property {Boolean} running
- * Property tracking the run status of this Operation. Use {@link #isRunning}.
- * @private
- * @readonly
- */
- running: false,
- /**
- * @property {Boolean} complete
- * Property tracking the completion status of this Operation. Use {@link #isComplete}.
- * @private
- * @readonly
- */
- complete: false,
- /**
- * @property {Boolean} success
- * Property tracking whether the Operation was successful or not. This starts as undefined and is set to `true`
- * or `false` by the Proxy that is executing the Operation. It is also set to false by {@link #setException}. Use
- * {@link #wasSuccessful} to query success status.
- * @private
- * @readonly
- */
- success: undefined,
- /**
- * @property {Boolean} exception
- * Property tracking the exception status of this Operation. Use {@link #hasException} and see {@link #getError}.
- * @private
- * @readonly
- */
- exception: false,
- /**
- * @property {String/Object} error
- * The error object passed when {@link #setException} was called. This could be any object or primitive.
- * @private
- */
- error: undefined,
- /**
- * Creates new Operation object.
- * @param {Object} config (optional) Config object.
- */
- constructor: function(config) {
- this.initConfig(config);
- },
- applyModel: function(model) {
- if (typeof model == 'string') {
- model = Ext.data.ModelManager.getModel(model);
- if (!model) {
- Ext.Logger.error('Model with name ' + arguments[0] + ' doesnt exist.');
- }
- }
- if (model && !model.prototype.isModel && Ext.isObject(model)) {
- model = Ext.data.ModelManager.registerType(model.storeId || model.id || Ext.id(), model);
- }
- // <debug>
- if (!model) {
- Ext.Logger.warn('Unless you define your model using metadata, an Operation needs to have a model defined.');
- }
- // </debug>
- return model;
- },
- getRecords: function() {
- var resultSet = this.getResultSet();
- return this._records || (resultSet ? resultSet.getRecords() : []);
- },
- /**
- * Marks the Operation as started.
- */
- setStarted: function() {
- this.started = true;
- this.running = true;
- },
- /**
- * Marks the Operation as completed.
- */
- setCompleted: function() {
- this.complete = true;
- this.running = false;
- },
- /**
- * Marks the Operation as successful.
- */
- setSuccessful: function() {
- this.success = true;
- },
- /**
- * Marks the Operation as having experienced an exception. Can be supplied with an option error message/object.
- * @param {String/Object} error (optional) error string/object
- */
- setException: function(error) {
- this.exception = true;
- this.success = false;
- this.running = false;
- this.error = error;
- },
- /**
- * Returns `true` if this Operation encountered an exception (see also {@link #getError}).
- * @return {Boolean} `true` if there was an exception.
- */
- hasException: function() {
- return this.exception === true;
- },
- /**
- * Returns the error string or object that was set using {@link #setException}.
- * @return {String/Object} The error object.
- */
- getError: function() {
- return this.error;
- },
- /**
- * Returns `true` if the Operation has been started. Note that the Operation may have started AND completed, see
- * {@link #isRunning} to test if the Operation is currently running.
- * @return {Boolean} `true` if the Operation has started
- */
- isStarted: function() {
- return this.started === true;
- },
- /**
- * Returns `true` if the Operation has been started but has not yet completed.
- * @return {Boolean} `true` if the Operation is currently running
- */
- isRunning: function() {
- return this.running === true;
- },
- /**
- * Returns `true` if the Operation has been completed
- * @return {Boolean} `true` if the Operation is complete
- */
- isComplete: function() {
- return this.complete === true;
- },
- /**
- * Returns `true` if the Operation has completed and was successful
- * @return {Boolean} `true` if successful
- */
- wasSuccessful: function() {
- return this.isComplete() && this.success === true;
- },
- /**
- * Checks whether this operation should cause writing to occur.
- * @return {Boolean} Whether the operation should cause a write to occur.
- */
- allowWrite: function() {
- return this.getAction() != 'read';
- },
- process: function(action, resultSet, request, response) {
- if (resultSet.getSuccess() !== false) {
- this.setResponse(response);
- this.setResultSet(resultSet);
- this.setCompleted();
- this.setSuccessful();
- } else {
- return false;
- }
- return this['process' + Ext.String.capitalize(action)].call(this, resultSet, request, response);
- },
- processRead: function(resultSet) {
- var records = resultSet.getRecords(),
- processedRecords = [],
- Model = this.getModel(),
- ln = records.length,
- i, record;
- for (i = 0; i < ln; i++) {
- record = records[i];
- processedRecords.push(new Model(record.data, record.id, record.node));
- }
- this.setRecords(processedRecords);
- resultSet.setRecords(processedRecords);
- return true;
- },
- processCreate: function(resultSet) {
- var updatedRecords = resultSet.getRecords(),
- currentRecords = this.getRecords(),
- ln = updatedRecords.length,
- i, currentRecord, updatedRecord;
- for (i = 0; i < ln; i++) {
- updatedRecord = updatedRecords[i];
- if (updatedRecord.clientId === null && currentRecords.length == 1 && updatedRecords.length == 1) {
- currentRecord = currentRecords[i];
- } else {
- currentRecord = this.findCurrentRecord(updatedRecord.clientId);
- }
- if (currentRecord) {
- this.updateRecord(currentRecord, updatedRecord);
- }
- // <debug>
- else {
- Ext.Logger.warn('Unable to match the record that came back from the server.');
- }
- // </debug>
- }
- return true;
- },
- processUpdate: function(resultSet) {
- var updatedRecords = resultSet.getRecords(),
- currentRecords = this.getRecords(),
- ln = updatedRecords.length,
- i, currentRecord, updatedRecord;
- for (i = 0; i < ln; i++) {
- updatedRecord = updatedRecords[i];
- currentRecord = currentRecords[i];
- if (currentRecord) {
- this.updateRecord(currentRecord, updatedRecord);
- }
- // <debug>
- else {
- Ext.Logger.warn('Unable to match the updated record that came back from the server.');
- }
- // </debug>
- }
- return true;
- },
- processDestroy: function(resultSet) {
- var updatedRecords = resultSet.getRecords(),
- ln = updatedRecords.length,
- i, currentRecord, updatedRecord;
- for (i = 0; i < ln; i++) {
- updatedRecord = updatedRecords[i];
- currentRecord = this.findCurrentRecord(updatedRecord.id);
- if (currentRecord) {
- currentRecord.setIsErased(true);
- currentRecord.notifyStores('afterErase', currentRecord);
- }
- // <debug>
- else {
- Ext.Logger.warn('Unable to match the destroyed record that came back from the server.');
- }
- // </debug>
- }
- },
- findCurrentRecord: function(clientId) {
- var currentRecords = this.getRecords(),
- ln = currentRecords.length,
- i, currentRecord;
- for (i = 0; i < ln; i++) {
- currentRecord = currentRecords[i];
- if (currentRecord.getId() === clientId) {
- return currentRecord;
- }
- }
- },
- updateRecord: function(currentRecord, updatedRecord) {
- var recordData = updatedRecord.data,
- recordId = updatedRecord.id;
- currentRecord.beginEdit();
- currentRecord.set(recordData);
- if (recordId !== null) {
- currentRecord.setId(recordId);
- }
- // We call endEdit with silent: true because the commit below already makes
- // sure any store is notified of the record being updated.
- currentRecord.endEdit(true);
- currentRecord.commit();
- }
- });
- /**
- * @author Ed Spencer
- *
- * Simple wrapper class that represents a set of records returned by a Proxy.
- */
- Ext.define('Ext.data.ResultSet', {
- config: {
- /**
- * @cfg {Boolean} loaded
- * True if the records have already been loaded. This is only meaningful when dealing with
- * SQL-backed proxies.
- */
- loaded: true,
- /**
- * @cfg {Number} count
- * The number of records in this ResultSet. Note that total may differ from this number.
- */
- count: null,
- /**
- * @cfg {Number} total
- * The total number of records reported by the data source. This ResultSet may form a subset of
- * those records (see {@link #count}).
- */
- total: null,
- /**
- * @cfg {Boolean} success
- * True if the ResultSet loaded successfully, false if any errors were encountered.
- */
- success: false,
- /**
- * @cfg {Ext.data.Model[]} records (required)
- * The array of record instances.
- */
- records: null,
- /**
- * @cfg {String} message
- * The message that was read in from the data
- */
- message: null
- },
- /**
- * Creates the resultSet
- * @param {Object} [config] Config object.
- */
- constructor: function(config) {
- this.initConfig(config);
- },
- applyCount: function(count) {
- if (!count && count !== 0) {
- return this.getRecords().length;
- }
- return count;
- },
-
- /**
- * @private
- * Make sure we set the right count when new records have been sent in
- */
- updateRecords: function(records) {
- this.setCount(records.length);
- }
- });
- /**
- * @author Ed Spencer
- *
- * Readers are used to interpret data to be loaded into a {@link Ext.data.Model Model} instance or a {@link
- * Ext.data.Store Store} - often in response to an AJAX request. In general there is usually no need to create
- * a Reader instance directly, since a Reader is almost always used together with a {@link Ext.data.proxy.Proxy Proxy},
- * and is configured using the Proxy's {@link Ext.data.proxy.Proxy#cfg-reader reader} configuration property:
- *
- * Ext.define("User", {
- * extend: "Ext.data.Model",
- * config: {
- * fields: [
- * "id",
- * "name"
- * ]
- * }
- * });
- *
- * Ext.create("Ext.data.Store", {
- * model: "User",
- * autoLoad: true,
- * storeId: "usersStore",
- * proxy: {
- * type: "ajax",
- * url : "users.json",
- * reader: {
- * type: "json",
- * rootProperty: "users"
- * }
- * }
- * });
- *
- * Ext.create("Ext.List", {
- * fullscreen: true,
- * itemTpl: "{name} (id: '{id}')",
- * store: "usersStore"
- * });
- *
- * The above reader is configured to consume a JSON string that looks something like this:
- *
- * {
- * "success": true,
- * "users": [
- * { "name": "User 1" },
- * { "name": "User 2" }
- * ]
- * }
- *
- *
- * # Loading Nested Data
- *
- * Readers have the ability to automatically load deeply-nested data objects based on the {@link Ext.data.association.Association
- * associations} configured on each Model. Below is an example demonstrating the flexibility of these associations in a
- * fictional CRM system which manages a User, their Orders, OrderItems and Products. First we'll define the models:
- *
- * Ext.define("User", {
- * extend: "Ext.data.Model",
- * config: {
- * fields: [
- * "id",
- * "name"
- * ],
- * hasMany: {
- * model: "Order",
- * name: "orders"
- * },
- * proxy: {
- * type: "rest",
- * url : "users.json",
- * reader: {
- * type: "json",
- * rootProperty: "users"
- * }
- * }
- * }
- * });
- *
- * Ext.define("Order", {
- * extend: "Ext.data.Model",
- * config: {
- * fields: [
- * "id", "total"
- * ],
- * hasMany: {
- * model: "OrderItem",
- * name: "orderItems",
- * associationKey: "order_items"
- * },
- * belongsTo: "User"
- * }
- * });
- *
- * Ext.define("OrderItem", {
- * extend: "Ext.data.Model",
- * config: {
- * fields: [
- * "id",
- * "price",
- * "quantity",
- * "order_id",
- * "product_id"
- * ],
- * belongsTo: [
- * "Order", {
- * model: "Product",
- * associationKey: "product"
- * }
- * ]
- * }
- * });
- *
- * Ext.define("Product", {
- * extend: "Ext.data.Model",
- * config: {
- * fields: [
- * "id",
- * "name"
- * ]
- * },
- * hasMany: "OrderItem"
- * });
- *
- * var store = Ext.create('Ext.data.Store', {
- * model: "User"
- * });
- *
- * store.load({
- * callback: function() {
- * var output = [];
- *
- * // the user that was loaded
- * var user = store.first();
- *
- * output.push("Orders for " + user.get('name') + ":");
- *
- * // iterate over the Orders for each User
- * user.orders().each(function(order) {
- * output.push("Order ID: " + order.get('id') + ", which contains items:");
- *
- * // iterate over the OrderItems for each Order
- * order.orderItems().each(function(orderItem) {
- * // We know that the Product data is already loaded, so we can use the
- * // synchronous getProduct() method. Usually, we would use the
- * // asynchronous version (see Ext.data.association.BelongsTo).
- * var product = orderItem.getProduct();
- * output.push(orderItem.get("quantity") + " orders of " + product.get("name"));
- * });
- * });
- * Ext.Msg.alert('Output:', output.join("<br/>"));
- * }
- * });
- *
- * This may be a lot to take in - basically a User has many Orders, each of which is composed of several OrderItems.
- * Finally, each OrderItem has a single Product. This allows us to consume data like this (_users.json_):
- *
- * {
- * "users": [
- * {
- * "id": 123,
- * "name": "Ed",
- * "orders": [
- * {
- * "id": 50,
- * "total": 100,
- * "order_items": [
- * {
- * "id" : 20,
- * "price" : 40,
- * "quantity": 2,
- * "product" : {
- * "id": 1000,
- * "name": "MacBook Pro"
- * }
- * },
- * {
- * "id" : 21,
- * "price" : 20,
- * "quantity": 3,
- * "product" : {
- * "id": 1001,
- * "name": "iPhone"
- * }
- * }
- * ]
- * }
- * ]
- * }
- * ]
- * }
- *
- * The JSON response is deeply nested - it returns all Users (in this case just 1 for simplicity's sake), all of the
- * Orders for each User (again just 1 in this case), all of the OrderItems for each Order (2 order items in this case),
- * and finally the Product associated with each OrderItem.
- *
- * Running the code above results in the following:
- *
- * Orders for Ed:
- * Order ID: 50, which contains items:
- * 2 orders of MacBook Pro
- * 3 orders of iPhone
- */
- Ext.define('Ext.data.reader.Reader', {
- requires: [
- 'Ext.data.ResultSet'
- ],
- alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
- mixins: ['Ext.mixin.Observable'],
- // @private
- isReader: true,
- config: {
- /**
- * @cfg {String} idProperty
- * Name of the property within a raw object that contains a record identifier value. Defaults to The id of the
- * model. If an `idProperty` is explicitly specified it will override that of the one specified on the model
- */
- idProperty: undefined,
- /**
- * @cfg {String} clientIdProperty
- * The name of the property with a response that contains the existing client side id for a record that we are reading.
- */
- clientIdProperty: 'clientId',
- /**
- * @cfg {String} totalProperty
- * Name of the property from which to retrieve the total number of records in the dataset. This is only needed if
- * the whole dataset is not passed in one go, but is being paged from the remote server.
- */
- totalProperty: 'total',
- /**
- * @cfg {String} successProperty
- * Name of the property from which to retrieve the success attribute. See
- * {@link Ext.data.proxy.Server}.{@link Ext.data.proxy.Server#exception exception} for additional information.
- */
- successProperty: 'success',
- /**
- * @cfg {String} messageProperty (optional)
- * The name of the property which contains a response message. This property is optional.
- */
- messageProperty: null,
- /**
- * @cfg {String} rootProperty
- * The name of the property which contains the Array of row objects. For JSON reader it's dot-separated list
- * of property names. For XML reader it's a CSS selector. For array reader it's not applicable.
- *
- * By default the natural root of the data will be used. The root JSON array, the root XML element, or the array.
- *
- * The data packet value for this property should be an empty array to clear the data or show no data.
- */
- rootProperty: '',
- /**
- * @cfg {Boolean} implicitIncludes
- * `true` to automatically parse models nested within other models in a response object. See the
- * {@link Ext.data.reader.Reader} intro docs for full explanation.
- */
- implicitIncludes: true,
- model: undefined
- },
- constructor: function(config) {
- this.initConfig(config);
- },
- /**
- * @property {Object} metaData
- * The raw meta data that was most recently read, if any. Meta data can include existing
- * Reader config options like {@link #idProperty}, {@link #totalProperty}, etc. that get
- * automatically applied to the Reader, and those can still be accessed directly from the Reader
- * if needed. However, meta data is also often used to pass other custom data to be processed
- * by application code. For example, it is common when reconfiguring the data model of a grid to
- * also pass a corresponding column model config to be applied to the grid. Any such data will
- * not get applied to the Reader directly (it just gets passed through and is ignored by Ext).
- * This `metaData` property gives you access to all meta data that was passed, including any such
- * custom data ignored by the reader.
- *
- * This is a read-only property, and it will get replaced each time a new meta data object is
- * passed to the reader.
- * @readonly
- */
- fieldCount: 0,
- applyModel: function(model) {
- if (typeof model == 'string') {
- model = Ext.data.ModelManager.getModel(model);
- if (!model) {
- Ext.Logger.error('Model with name ' + arguments[0] + ' doesnt exist.');
- }
- }
- if (model && !model.prototype.isModel && Ext.isObject(model)) {
- model = Ext.data.ModelManager.registerType(model.storeId || model.id || Ext.id(), model);
- }
- return model;
- },
- applyIdProperty: function(idProperty) {
- if (!idProperty && this.getModel()) {
- idProperty = this.getModel().getIdProperty();
- }
- return idProperty;
- },
- updateModel: function(model) {
- if (model) {
- if (!this.getIdProperty()) {
- this.setIdProperty(model.getIdProperty());
- }
- this.buildExtractors();
- }
- },
- createAccessor: Ext.emptyFn,
- createFieldAccessExpression: function() {
- return 'undefined';
- },
- /**
- * @private
- * This builds optimized functions for retrieving record data and meta data from an object.
- * Subclasses may need to implement their own getRoot function.
- */
- buildExtractors: function() {
- if (!this.getModel()) {
- return;
- }
- var me = this,
- totalProp = me.getTotalProperty(),
- successProp = me.getSuccessProperty(),
- messageProp = me.getMessageProperty();
- //build the extractors for all the meta data
- if (totalProp) {
- me.getTotal = me.createAccessor(totalProp);
- }
- if (successProp) {
- me.getSuccess = me.createAccessor(successProp);
- }
- if (messageProp) {
- me.getMessage = me.createAccessor(messageProp);
- }
- me.extractRecordData = me.buildRecordDataExtractor();
- },
- /**
- * @private
- * Return a function which will read a raw row object in the format this Reader accepts, and populates
- * a record's data object with converted data values.
- *
- * The returned function must be passed the following parameters:
- *
- * - `dest` - A record's empty data object into which the new field value properties are injected.
- * - `source` - A raw row data object of whatever type this Reader consumes
- * - `record - The record which is being populated.
- */
- buildRecordDataExtractor: function() {
- var me = this,
- model = me.getModel(),
- fields = model.getFields(),
- ln = fields.length,
- fieldVarName = [],
- clientIdProp = me.getModel().getClientIdProperty(),
- prefix = '__field',
- code = [
- 'var me = this,\n',
- ' fields = me.getModel().getFields(),\n',
- ' idProperty = me.getIdProperty(),\n',
- ' idPropertyIsFn = (typeof idProperty == "function"),',
- ' value,\n',
- ' internalId'
- ], i, field, varName, fieldName;
- fields = fields.items;
- for (i = 0; i < ln; i++) {
- field = fields[i];
- fieldName = field.getName();
- if (fieldName === model.getIdProperty()) {
- fieldVarName[i] = 'idField';
- } else {
- fieldVarName[i] = prefix + i;
- }
- code.push(',\n ', fieldVarName[i], ' = fields.get("', field.getName(), '")');
- }
- code.push(';\n\n return function(source) {\n var dest = {};\n');
- code.push(' if (idPropertyIsFn) {\n');
- code.push(' idField.setMapping(idProperty);\n');
- code.push(' }\n');
- for (i = 0; i < ln; i++) {
- field = fields[i];
- varName = fieldVarName[i];
- fieldName = field.getName();
- if (fieldName === model.getIdProperty() && field.getMapping() === null && model.getIdProperty() !== this.getIdProperty()) {
- field.setMapping(this.getIdProperty());
- }
- // createFieldAccessExpression must be implemented in subclasses to extract data from the source object in the correct way.
- code.push(' try {\n');
- code.push(' value = ', me.createFieldAccessExpression(field, varName, 'source'), ';\n');
- code.push(' if (value !== undefined) {\n');
- code.push(' dest["' + field.getName() + '"] = value;\n');
- code.push(' }\n');
- code.push(' } catch(e){}\n');
- }
- // set the client id as the internalId of the record.
- // clientId handles the case where a client side record did not previously exist on the server,
- // so the server is passing back a client id that can be used to pair the server side record up with the client record
- if (clientIdProp) {
- code.push(' internalId = ' + me.createFieldAccessExpression(Ext.create('Ext.data.Field', {name: clientIdProp}), null, 'source') + ';\n');
- code.push(' if (internalId !== undefined) {\n');
- code.push(' dest["_clientId"] = internalId;\n }\n');
- }
- code.push(' return dest;\n');
- code.push(' };');
- // Here we are creating a new Function and invoking it immediately in the scope of this Reader
- // It declares several vars capturing the configured context of this Reader, and returns a function
- // which, when passed a record data object, a raw data row in the format this Reader is configured to read,
- // and the record which is being created, will populate the record's data object from the raw row data.
- return Ext.functionFactory(code.join('')).call(me);
- },
- getFields: function() {
- return this.getModel().getFields().items;
- },
- /**
- * @private
- * By default this function just returns what is passed to it. It can be overridden in a subclass
- * to return something else. See XmlReader for an example.
- * @param {Object} data The data object
- * @return {Object} The normalized data object
- */
- getData: function(data) {
- return data;
- },
- /**
- * Takes a raw response object (as passed to this.read) and returns the useful data segment of it.
- * This must be implemented by each subclass
- * @param {Object} response The response object
- * @return {Object} The useful data from the response
- */
- getResponseData: function(response) {
- return response;
- },
- /**
- * @private
- * This will usually need to be implemented in a subclass. Given a generic data object (the type depends on the type
- * of data we are reading), this function should return the object as configured by the Reader's 'rootProperty' meta data config.
- * See XmlReader's getRoot implementation for an example. By default the same data object will simply be returned.
- * @param {Object} data The data object
- * @return {Object} The same data object
- */
- getRoot: function(data) {
- return data;
- },
- /**
- * Reads the given response object. This method normalizes the different types of response object that may be passed
- * to it, before handing off the reading of records to the {@link #readRecords} function.
- * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object
- * @return {Ext.data.ResultSet} The parsed ResultSet object
- */
- read: function(response) {
- var data = response,
- Model = this.getModel(),
- resultSet, records, i, ln, record;
- if (response) {
- data = this.getResponseData(response);
- }
- if (data) {
- resultSet = this.readRecords(data);
- records = resultSet.getRecords();
- for (i = 0, ln = records.length; i < ln; i++) {
- record = records[i];
- records[i] = new Model(record.data, record.id, record.node);
- }
- return resultSet;
- } else {
- return this.nullResultSet;
- }
- },
- process: function(response) {
- var data = response;
- if (response) {
- data = this.getResponseData(response);
- }
- if (data) {
- return this.readRecords(data);
- } else {
- return this.nullResultSet;
- }
- },
- /**
- * Abstracts common functionality used by all Reader subclasses. Each subclass is expected to call this function
- * before running its own logic and returning the Ext.data.ResultSet instance. For most Readers additional
- * processing should not be needed.
- * @param {Object} data The raw data object
- * @return {Ext.data.ResultSet} A ResultSet object
- */
- readRecords: function(data) {
- var me = this;
- /**
- * @property {Object} rawData
- * The raw data object that was last passed to readRecords. Stored for further processing if needed
- */
- me.rawData = data;
- data = me.getData(data);
- if (data.metaData) {
- me.onMetaChange(data.metaData);
- }
- // <debug>
- if (!me.getModel()) {
- Ext.Logger.warn('In order to read record data, a Reader needs to have a Model defined on it.');
- }
- // </debug>
- // If we pass an array as the data, we don't use getRoot on the data.
- // Instead the root equals to the data.
- var isArray = Ext.isArray(data),
- root = isArray ? data : me.getRoot(data),
- success = true,
- recordCount = 0,
- total, value, records, message;
- if (isArray && !data.length) {
- return me.nullResultSet;
- }
- // buildExtractors should have put getTotal, getSuccess, or getMessage methods on the instance.
- // So we can check them directly
- if (me.getTotal) {
- value = parseInt(me.getTotal(data), 10);
- if (!isNaN(value)) {
- total = value;
- }
- }
- if (me.getSuccess) {
- value = me.getSuccess(data);
- if (value === false || value === 'false') {
- success = false;
- }
- }
- if (me.getMessage) {
- message = me.getMessage(data);
- }
- if (root) {
- records = me.extractData(root);
- recordCount = records.length;
- } else {
- recordCount = 0;
- records = [];
- }
- return new Ext.data.ResultSet({
- total : total,
- count : recordCount,
- records: records,
- success: success,
- message: message
- });
- },
- /**
- * Returns extracted, type-cast rows of data.
- * @param {Object[]/Object} root from server response
- * @private
- */
- extractData : function(root) {
- var me = this,
- records = [],
- length = root.length,
- model = me.getModel(),
- idProperty = model.getIdProperty(),
- fieldsCollection = model.getFields(),
- node, i, data, id, clientId;
- /*
- * We check here whether the fields are dirty since the last read.
- * This works around an issue when a Model is used for both a Tree and another
- * source, because the tree decorates the model with extra fields and it causes
- * issues because the readers aren't notified.
- */
- if (fieldsCollection.isDirty) {
- me.buildExtractors(true);
- delete fieldsCollection.isDirty;
- }
- if (!root.length && Ext.isObject(root)) {
- root = [root];
- length = 1;
- }
- for (i = 0; i < length; i++) {
- clientId = null;
- id = null;
- node = root[i];
- // When you use a Memory proxy, and you set data: [] to contain record instances
- // this node will already be a record. In this case we should not try to extract
- // the record data from the object, but just use the record data attribute.
- if (node.isModel) {
- data = node.data;
- } else {
- data = me.extractRecordData(node);
- }
- if (data._clientId !== undefined) {
- clientId = data._clientId;
- delete data._clientId;
- }
- if (data[idProperty] !== undefined) {
- id = data[idProperty];
- }
- if (me.getImplicitIncludes()) {
- me.readAssociated(data, node);
- }
- records.push({
- clientId: clientId,
- id: id,
- data: data,
- node: node
- });
- }
- return records;
- },
- /**
- * @private
- * Loads a record's associations from the data object. This pre-populates `hasMany` and `belongsTo` associations
- * on the record provided.
- * @param {Ext.data.Model} record The record to load associations for
- * @param {Object} data The data object
- */
- readAssociated: function(record, data) {
- var associations = this.getModel().associations.items,
- length = associations.length,
- i = 0,
- association, associationData, associationKey;
- for (; i < length; i++) {
- association = associations[i];
- associationKey = association.getAssociationKey();
- associationData = this.getAssociatedDataRoot(data, associationKey);
- if (associationData) {
- record[associationKey] = associationData;
- }
- }
- },
- /**
- * @private
- * Used internally by `readAssociated`. Given a data object (which could be json, xml etc) for a specific
- * record, this should return the relevant part of that data for the given association name. If a complex
- * mapping, this will traverse arrays and objects to resolve the data.
- * @param {Object} data The raw data object
- * @param {String} associationName The name of the association to get data for (uses associationKey if present)
- * @return {Object} The root
- */
- getAssociatedDataRoot: function(data, associationName) {
- var re = /[\[\.]/,
- i = String(associationName).search(re);
- if (i >= 0) {
- return Ext.functionFactory('obj', 'return obj' + (i > 0 ? '.' : '') + associationName)(data);
- }
- return data[associationName];
- },
- /**
- * @private
- * Reconfigures the meta data tied to this Reader
- */
- onMetaChange : function(meta) {
- var fields = meta.fields,
- me = this,
- newModel, config, idProperty;
- // save off the raw meta data
- me.metaData = meta;
- // set any reader-specific configs from meta if available
- if (meta.rootProperty !== undefined) {
- me.setRootProperty(meta.rootProperty);
- }
- else if (meta.root !== undefined) {
- me.setRootProperty(meta.root);
- }
- if (meta.idProperty !== undefined) {
- me.setIdProperty(meta.idProperty);
- }
- if (meta.totalProperty !== undefined) {
- me.setTotalProperty(meta.totalProperty);
- }
- if (meta.successProperty !== undefined) {
- me.setSuccessProperty(meta.successProperty);
- }
- if (meta.messageProperty !== undefined) {
- me.setMessageProperty(meta.messageProperty);
- }
- if (fields) {
- if (me.getModel()) {
- me.getModel().setFields(fields);
- me.buildExtractors();
- }
- else {
- idProperty = me.getIdProperty();
- config = {fields: fields};
- if (idProperty) {
- config.idProperty = idProperty;
- }
- newModel = Ext.define("Ext.data.reader.MetaModel" + Ext.id(), {
- extend: 'Ext.data.Model',
- config: config
- });
- me.setModel(newModel);
- }
- }
- else {
- me.buildExtractors();
- }
- }
- // Convert old properties in data into a config object
- }, function() {
- Ext.apply(this.prototype, {
- // @private
- // Empty ResultSet to return when response is falsy (null|undefined|empty string)
- nullResultSet: new Ext.data.ResultSet({
- total : 0,
- count : 0,
- records: [],
- success: false
- })
- });
- });
- /**
- * The JSON Reader is used by a Proxy to read a server response that is sent back in JSON format. This usually happens
- * as a result of loading a Store - for example we might create something like this:
- *
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: ['id', 'name', 'email']
- * }
- * });
- *
- * var store = Ext.create('Ext.data.Store', {
- * model: 'User',
- * proxy: {
- * type: 'ajax',
- * url : 'users.json',
- * reader: {
- * type: 'json'
- * }
- * }
- * });
- *
- * The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're not
- * already familiar with them.
- *
- * We created the simplest type of JSON Reader possible by simply telling our {@link Ext.data.Store Store}'s {@link
- * Ext.data.proxy.Proxy Proxy} that we want a JSON Reader. The Store automatically passes the configured model to the
- * Store, so it is as if we passed this instead:
- *
- * reader: {
- * type : 'json',
- * model: 'User'
- * }
- *
- * The reader we set up is ready to read data from our server - at the moment it will accept a response like this:
- *
- * [
- * {
- * "id": 1,
- * "name": "Ed Spencer",
- * "email": "ed@sencha.com"
- * },
- * {
- * "id": 2,
- * "name": "Abe Elias",
- * "email": "abe@sencha.com"
- * }
- * ]
- *
- * ## Reading other JSON formats
- *
- * If you already have your JSON format defined and it doesn't look quite like what we have above, you can usually pass
- * JsonReader a couple of configuration options to make it parse your format. For example, we can use the
- * {@link #rootProperty} configuration to parse data that comes back like this:
- *
- * {
- * "users": [
- * {
- * "id": 1,
- * "name": "Ed Spencer",
- * "email": "ed@sencha.com"
- * },
- * {
- * "id": 2,
- * "name": "Abe Elias",
- * "email": "abe@sencha.com"
- * }
- * ]
- * }
- *
- * To parse this we just pass in a {@link #rootProperty} configuration that matches the 'users' above:
- *
- * reader: {
- * type: 'json',
- * rootProperty: 'users'
- * }
- *
- * Sometimes the JSON structure is even more complicated. Document databases like CouchDB often provide metadata around
- * each record inside a nested structure like this:
- *
- * {
- * "total": 122,
- * "offset": 0,
- * "users": [
- * {
- * "id": "ed-spencer-1",
- * "value": 1,
- * "user": {
- * "id": 1,
- * "name": "Ed Spencer",
- * "email": "ed@sencha.com"
- * }
- * }
- * ]
- * }
- *
- * In the case above the record data is nested an additional level inside the "users" array as each "user" item has
- * additional metadata surrounding it ('id' and 'value' in this case). To parse data out of each "user" item in the JSON
- * above we need to specify the {@link #record} configuration like this:
- *
- * reader: {
- * type: 'json',
- * record: 'user',
- * rootProperty: 'users'
- * }
- *
- * ## Response MetaData
- *
- * The server can return metadata in its response, in addition to the record data, that describe attributes
- * of the data set itself or are used to reconfigure the Reader. To pass metadata in the response you simply
- * add a `metaData` attribute to the root of the response data. The metaData attribute can contain anything,
- * but supports a specific set of properties that are handled by the Reader if they are present:
- *
- * - {@link #idProperty}: property name for the primary key field of the data
- * - {@link #rootProperty}: the property name of the root response node containing the record data
- * - {@link #totalProperty}: property name for the total number of records in the data
- * - {@link #successProperty}: property name for the success status of the response
- * - {@link #messageProperty}: property name for an optional response message
- * - {@link Ext.data.Model#cfg-fields fields}: Config used to reconfigure the Model's fields before converting the
- * response data into records
- *
- * An initial Reader configuration containing all of these properties might look like this ("fields" would be
- * included in the Model definition, not shown):
- *
- * reader: {
- * type: 'json',
- * idProperty: 'id',
- * rootProperty: 'root',
- * totalProperty: 'total',
- * successProperty: 'success',
- * messageProperty: 'message'
- * }
- *
- * If you were to pass a response object containing attributes different from those initially defined above, you could
- * use the `metaData` attribute to reconfigure the Reader on the fly. For example:
- *
- * {
- * "count": 1,
- * "ok": true,
- * "msg": "Users found",
- * "users": [{
- * "userId": 123,
- * "name": "Ed Spencer",
- * "email": "ed@sencha.com"
- * }],
- * "metaData": {
- * "idProperty": 'userId',
- * "rootProperty": "users",
- * "totalProperty": 'count',
- * "successProperty": 'ok',
- * "messageProperty": 'msg'
- * }
- * }
- *
- * You can also place any other arbitrary data you need into the `metaData` attribute which will be ignored by the Reader,
- * but will be accessible via the Reader's {@link #metaData} property. Application code can then process the passed
- * metadata in any way it chooses.
- *
- * 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
- * the `fields` property the Model will be automatically updated by the Reader internally, but that change will not be
- * reflected automatically in the grid unless you also update the column configuration. You could do this manually, or you
- * could simply pass a standard grid column config object as part of the `metaData` attribute
- * and then pass that along to the grid. Here's a very simple example for how that could be accomplished:
- *
- * // response format:
- * {
- * ...
- * "metaData": {
- * "fields": [
- * { "name": "userId", "type": "int" },
- * { "name": "name", "type": "string" },
- * { "name": "birthday", "type": "date", "dateFormat": "Y-j-m" },
- * ],
- * "columns": [
- * { "text": "User ID", "dataIndex": "userId", "width": 40 },
- * { "text": "User Name", "dataIndex": "name", "flex": 1 },
- * { "text": "Birthday", "dataIndex": "birthday", "flex": 1, "format": 'Y-j-m', "xtype": "datecolumn" }
- * ]
- * }
- * }
- */
- Ext.define('Ext.data.reader.Json', {
- extend: 'Ext.data.reader.Reader',
- alternateClassName: 'Ext.data.JsonReader',
- alias : 'reader.json',
- config: {
- /**
- * @cfg {String} [record=null]
- * The optional location within the JSON response that the record data itself can be found at. See the
- * JsonReader intro docs for more details. This is not often needed.
- */
- record: null,
- /**
- * @cfg {Boolean} [useSimpleAccessors=false]
- * `true` to ensure that field names/mappings are treated as literals when reading values. For
- * example, by default, using the mapping "foo.bar.baz" will try and read a property foo from the root, then a
- * property bar from foo, then a property baz from bar. Setting the simple accessors to `true` will read the
- * property with the name "foo.bar.baz" direct from the root object.
- */
- useSimpleAccessors: false
- },
- objectRe: /[\[\.]/,
- // @inheritdoc
- getResponseData: function(response) {
- var responseText = response;
- // Handle an XMLHttpRequest object
- if (response && response.responseText) {
- responseText = response.responseText;
- }
- // Handle the case where data has already been decoded
- if (typeof responseText !== 'string') {
- return responseText;
- }
- var data;
- try {
- data = Ext.decode(responseText);
- }
- catch (ex) {
- /**
- * @event exception Fires whenever the reader is unable to parse a response.
- * @param {Ext.data.reader.Xml} reader A reference to this reader.
- * @param {XMLHttpRequest} response The XMLHttpRequest response object.
- * @param {String} error The error message.
- */
- this.fireEvent('exception', this, response, 'Unable to parse the JSON returned by the server: ' + ex.toString());
- Ext.Logger.warn('Unable to parse the JSON returned by the server: ' + ex.toString());
- }
- //<debug>
- if (!data) {
- this.fireEvent('exception', this, response, 'JSON object not found');
- Ext.Logger.error('JSON object not found');
- }
- //</debug>
- return data;
- },
- // @inheritdoc
- buildExtractors: function() {
- var me = this,
- root = me.getRootProperty();
- me.callParent(arguments);
- if (root) {
- me.rootAccessor = me.createAccessor(root);
- } else {
- delete me.rootAccessor;
- }
- },
- /**
- * We create this method because `root` is now a config so `getRoot` is already defined, but in the old
- * data package `getRoot` was passed a data argument and it would return the data inside of the `root`
- * property. This method handles both cases.
- * @param data (Optional)
- * @return {String/Object} Returns the config root value if this method was called without passing
- * data. Else it returns the object in the data bound to the root.
- * @private
- */
- getRoot: function(data) {
- var fieldsCollection = this.getModel().getFields();
- /*
- * We check here whether the fields are dirty since the last read.
- * This works around an issue when a Model is used for both a Tree and another
- * source, because the tree decorates the model with extra fields and it causes
- * issues because the readers aren't notified.
- */
- if (fieldsCollection.isDirty) {
- this.buildExtractors(true);
- delete fieldsCollection.isDirty;
- }
- if (this.rootAccessor) {
- return this.rootAccessor.call(this, data);
- } else {
- return data;
- }
- },
- /**
- * @private
- * We're just preparing the data for the superclass by pulling out the record objects we want. If a {@link #record}
- * was specified we have to pull those out of the larger JSON object, which is most of what this function is doing
- * @param {Object} root The JSON root node
- * @return {Ext.data.Model[]} The records
- */
- extractData: function(root) {
- var recordName = this.getRecord(),
- data = [],
- length, i;
- if (recordName) {
- length = root.length;
- if (!length && Ext.isObject(root)) {
- length = 1;
- root = [root];
- }
- for (i = 0; i < length; i++) {
- data[i] = root[i][recordName];
- }
- } else {
- data = root;
- }
- return this.callParent([data]);
- },
- /**
- * @private
- * Returns an accessor function for the given property string. Gives support for properties such as the following:
- * 'someProperty'
- * 'some.property'
- * 'some["property"]'
- * This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
- */
- createAccessor: function() {
- var re = /[\[\.]/;
- return function(expr) {
- if (Ext.isEmpty(expr)) {
- return Ext.emptyFn;
- }
- if (Ext.isFunction(expr)) {
- return expr;
- }
- if (this.getUseSimpleAccessors() !== true) {
- var i = String(expr).search(re);
- if (i >= 0) {
- return Ext.functionFactory('obj', 'var value; try {value = obj' + (i > 0 ? '.' : '') + expr + '} catch(e) {}; return value;');
- }
- }
- return function(obj) {
- return obj[expr];
- };
- };
- }(),
- /**
- * @private
- * Returns an accessor expression for the passed Field. Gives support for properties such as the following:
- * 'someProperty'
- * 'some.property'
- * 'some["property"]'
- * This is used by buildExtractors to create optimized on extractor function which converts raw data into model instances.
- */
- createFieldAccessExpression: function(field, fieldVarName, dataName) {
- var me = this,
- re = me.objectRe,
- hasMap = (field.getMapping() !== null),
- map = hasMap ? field.getMapping() : field.getName(),
- result, operatorSearch;
- if (typeof map === 'function') {
- result = fieldVarName + '.getMapping()(' + dataName + ', this)';
- }
- else if (me.getUseSimpleAccessors() === true || ((operatorSearch = String(map).search(re)) < 0)) {
- if (!hasMap || isNaN(map)) {
- // If we don't provide a mapping, we may have a field name that is numeric
- map = '"' + map + '"';
- }
- result = dataName + "[" + map + "]";
- }
- else {
- result = dataName + (operatorSearch > 0 ? '.' : '') + map;
- }
- return result;
- }
- });
- /**
- * @author Ed Spencer
- *
- * Base Writer class used by most subclasses of {@link Ext.data.proxy.Server}. This class is
- * responsible for taking a set of {@link Ext.data.Operation} objects and a {@link Ext.data.Request}
- * object and modifying that request based on the Operations.
- *
- * For example a Ext.data.writer.Json would format the Operations and their {@link Ext.data.Model}
- * instances based on the config options passed to the JsonWriter's constructor.
- *
- * Writers are not needed for any kind of local storage - whether via a
- * {@link Ext.data.proxy.WebStorage Web Storage proxy} (see {@link Ext.data.proxy.LocalStorage localStorage})
- * or just in memory via a {@link Ext.data.proxy.Memory MemoryProxy}.
- */
- Ext.define('Ext.data.writer.Writer', {
- alias: 'writer.base',
- alternateClassName: ['Ext.data.DataWriter', 'Ext.data.Writer'],
- config: {
- /**
- * @cfg {Boolean} writeAllFields `true` to write all fields from the record to the server. If set to `false` it
- * will only send the fields that were modified. Note that any fields that have
- * {@link Ext.data.Field#persist} set to false will still be ignored.
- */
- writeAllFields: true,
- /**
- * @cfg {String} nameProperty This property is used to read the key for each value that will be sent to the server.
- * For example:
- *
- * Ext.define('Person', {
- * extend: 'Ext.data.Model',
- * fields: [{
- * name: 'first',
- * mapping: 'firstName'
- * }, {
- * name: 'last',
- * mapping: 'lastName'
- * }, {
- * name: 'age'
- * }]
- * });
- *
- * new Ext.data.writer.Writer({
- * writeAllFields: true,
- * nameProperty: 'mapping'
- * });
- *
- * The following data will be sent to the server:
- *
- * {
- * firstName: 'first name value',
- * lastName: 'last name value',
- * age: 1
- * }
- *
- * If the value is not present, the field name will always be used.
- */
- nameProperty: 'name'
- },
- /**
- * Creates new Writer.
- * @param {Object} config (optional) Config object.
- */
- constructor: function(config) {
- this.initConfig(config);
- },
- /**
- * Prepares a Proxy's Ext.data.Request object.
- * @param {Ext.data.Request} request The request object.
- * @return {Ext.data.Request} The modified request object.
- */
- write: function(request) {
- var operation = request.getOperation(),
- records = operation.getRecords() || [],
- len = records.length,
- i = 0,
- data = [];
- for (; i < len; i++) {
- data.push(this.getRecordData(records[i]));
- }
- return this.writeRecords(request, data);
- },
- writeDate: function(field, date) {
- var dateFormat = field.getDateFormat() || 'timestamp';
- switch (dateFormat) {
- case 'timestamp':
- return date.getTime()/1000;
- case 'time':
- return date.getTime();
- default:
- return Ext.Date.format(date, dateFormat);
- }
- },
- /**
- * Formats the data for each record before sending it to the server. This
- * method should be overridden to format the data in a way that differs from the default.
- * @param {Object} record The record that we are writing to the server.
- * @return {Object} An object literal of name/value keys to be written to the server.
- * By default this method returns the data property on the record.
- */
- getRecordData: function(record) {
- var isPhantom = record.phantom === true,
- writeAll = this.getWriteAllFields() || isPhantom,
- nameProperty = this.getNameProperty(),
- fields = record.getFields(),
- data = {},
- changes, name, field, key, value;
- if (writeAll) {
- fields.each(function(field) {
- if (field.getPersist()) {
- name = field.config[nameProperty] || field.getName();
- value = record.get(field.getName());
- if (field.getType().type == 'date') {
- value = this.writeDate(field, value);
- }
- data[name] = value;
- }
- }, this);
- } else {
- // Only write the changes
- changes = record.getChanges();
- for (key in changes) {
- if (changes.hasOwnProperty(key)) {
- field = fields.get(key);
- if (field.getPersist()) {
- name = field.config[nameProperty] || field.getName();
- value = changes[key];
- if (field.getType().type == 'date') {
- value = this.writeDate(field, value);
- }
- data[name] = value;
- }
- }
- }
- if (!isPhantom) {
- // always include the id for non phantoms
- data[record.getIdProperty()] = record.getId();
- }
- }
- return data;
- }
- // Convert old properties in data into a config object
- });
- /**
- * This class is used to write {@link Ext.data.Model} data to the server in a JSON format.
- * The {@link #allowSingle} configuration can be set to false to force the records to always be
- * encoded in an array, even if there is only a single record being sent.
- */
- Ext.define('Ext.data.writer.Json', {
- extend: 'Ext.data.writer.Writer',
- alternateClassName: 'Ext.data.JsonWriter',
- alias: 'writer.json',
- config: {
- /**
- * @cfg {String} rootProperty
- * The key under which the records in this Writer will be placed. If you specify {@link #encode} to be true,
- * we default this to 'records'.
- *
- * Example generated request, using root: 'records':
- *
- * {'records': [{name: 'my record'}, {name: 'another record'}]}
- *
- */
- rootProperty: undefined,
- /**
- * @cfg {Boolean} encode
- * True to use Ext.encode() on the data before sending. The encode option should only be set to true when a
- * {@link #root} is defined, because the values will be sent as part of the request parameters as opposed to
- * a raw post. The root will be the name of the parameter sent to the server.
- */
- encode: false,
- /**
- * @cfg {Boolean} allowSingle
- * False to ensure that records are always wrapped in an array, even if there is only one record being sent.
- * When there is more than one record, they will always be encoded into an array.
- *
- * Example:
- *
- * // with allowSingle: true
- * "root": {
- * "first": "Mark",
- * "last": "Corrigan"
- * }
- *
- * // with allowSingle: false
- * "root": [{
- * "first": "Mark",
- * "last": "Corrigan"
- * }]
- */
- allowSingle: true,
- encodeRequest: false
- },
- applyRootProperty: function(root) {
- if (!root && (this.getEncode() || this.getEncodeRequest())) {
- root = 'data';
- }
- return root;
- },
- //inherit docs
- writeRecords: function(request, data) {
- var root = this.getRootProperty(),
- params = request.getParams(),
- allowSingle = this.getAllowSingle(),
- jsonData;
- if (this.getAllowSingle() && data && data.length == 1) {
- // convert to single object format
- data = data[0];
- }
- if (this.getEncodeRequest()) {
- jsonData = request.getJsonData() || {};
- if (data && (data.length || (allowSingle && Ext.isObject(data)))) {
- jsonData[root] = data;
- }
- request.setJsonData(Ext.apply(jsonData, params || {}));
- request.setParams(null);
- request.setMethod('POST');
- return request;
- }
- if (!data || !(data.length || (allowSingle && Ext.isObject(data)))) {
- return request;
- }
- if (this.getEncode()) {
- if (root) {
- // sending as a param, need to encode
- params[root] = Ext.encode(data);
- } else {
- //<debug>
- Ext.Logger.error('Must specify a root when using encode');
- //</debug>
- }
- } else {
- // send as jsonData
- jsonData = request.getJsonData() || {};
- if (root) {
- jsonData[root] = data;
- } else {
- jsonData = data;
- }
- request.setJsonData(jsonData);
- }
- return request;
- }
- });
- /*
- * @allowSingle: true
- * @encodeRequest: false
- * Url: update.json?param1=test
- * {'field1': 'test': 'field2': 'test'}
- *
- * @allowSingle: false
- * @encodeRequest: false
- * Url: update.json?param1=test
- * [{'field1': 'test', 'field2': 'test'}]
- *
- * @allowSingle: true
- * @root: 'data'
- * @encodeRequest: true
- * Url: update.json
- * {
- * 'param1': 'test',
- * 'data': {'field1': 'test', 'field2': 'test'}
- * }
- *
- * @allowSingle: false
- * @root: 'data'
- * @encodeRequest: true
- * Url: update.json
- * {
- * 'param1': 'test',
- * 'data': [{'field1': 'test', 'field2': 'test'}]
- * }
- *
- * @allowSingle: true
- * @root: data
- * @encodeRequest: false
- * Url: update.json
- * param1=test&data={'field1': 'test', 'field2': 'test'}
- *
- * @allowSingle: false
- * @root: data
- * @encodeRequest: false
- * @ncode: true
- * Url: update.json
- * param1=test&data=[{'field1': 'test', 'field2': 'test'}]
- *
- * @allowSingle: true
- * @root: data
- * @encodeRequest: false
- * Url: update.json?param1=test&data={'field1': 'test', 'field2': 'test'}
- *
- * @allowSingle: false
- * @root: data
- * @encodeRequest: false
- * Url: update.json?param1=test&data=[{'field1': 'test', 'field2': 'test'}]
- */
- /**
- * @author Ed Spencer
- * @class Ext.data.Batch
- *
- * Provides a mechanism to run one or more {@link Ext.data.Operation operations} in a given order. Fires the `operationcomplete` event
- * after the completion of each Operation, and the `complete` event when all Operations have been successfully executed. Fires an `exception`
- * event if any of the Operations encounter an exception.
- *
- * Usually these are only used internally by {@link Ext.data.proxy.Proxy} classes.
- */
- Ext.define('Ext.data.Batch', {
- mixins: {
- observable: 'Ext.mixin.Observable'
- },
- config: {
- /**
- * @cfg {Boolean} autoStart `true` to immediately start processing the batch as soon as it is constructed.
- */
- autoStart: false,
- /**
- * @cfg {Boolean} pauseOnException `true` to automatically pause the execution of the batch if any operation encounters an exception.
- */
- pauseOnException: true,
- /**
- * @cfg {Ext.data.Proxy} proxy The proxy this Batch belongs to. Used to make the requests for each operation in the Batch.
- */
- proxy: null
- },
- /**
- * The index of the current operation being executed.
- * @property current
- * @type Number
- */
- current: -1,
- /**
- * The total number of operations in this batch.
- * @property total
- * @type Number
- * @readonly
- */
- total: 0,
- /**
- * `true` if the batch is currently running.
- * @property isRunning
- * @type Boolean
- */
- isRunning: false,
- /**
- * `true` if this batch has been executed completely.
- * @property isComplete
- * @type Boolean
- */
- isComplete: false,
- /**
- * `true` if this batch has encountered an exception. This is cleared at the start of each operation.
- * @property hasException
- * @type Boolean
- */
- hasException: false,
- /**
- * @event complete
- * Fired when all operations of this batch have been completed.
- * @param {Ext.data.Batch} batch The batch object.
- * @param {Object} operation The last operation that was executed.
- */
- /**
- * @event exception
- * Fired when a operation encountered an exception.
- * @param {Ext.data.Batch} batch The batch object.
- * @param {Object} operation The operation that encountered the exception.
- */
- /**
- * @event operationcomplete
- * Fired when each operation of the batch completes.
- * @param {Ext.data.Batch} batch The batch object.
- * @param {Object} operation The operation that just completed.
- */
- /**
- * Creates new Batch object.
- * @param {Object} config (optional) Config object.
- */
- constructor: function(config) {
- var me = this;
- me.initConfig(config);
- /**
- * Ordered array of operations that will be executed by this batch
- * @property {Ext.data.Operation[]} operations
- */
- me.operations = [];
- },
- /**
- * Adds a new operation to this batch.
- * @param {Object} operation The {@link Ext.data.Operation Operation} object.
- */
- add: function(operation) {
- this.total++;
- operation.setBatch(this);
- this.operations.push(operation);
- },
- /**
- * Kicks off the execution of the batch, continuing from the next operation if the previous
- * operation encountered an exception, or if execution was paused.
- */
- start: function() {
- this.hasException = false;
- this.isRunning = true;
- this.runNextOperation();
- },
- /**
- * @private
- * Runs the next operation, relative to `this.current`.
- */
- runNextOperation: function() {
- this.runOperation(this.current + 1);
- },
- /**
- * Pauses execution of the batch, but does not cancel the current operation.
- */
- pause: function() {
- this.isRunning = false;
- },
- /**
- * Executes a operation by its numeric index.
- * @param {Number} index The operation index to run.
- */
- runOperation: function(index) {
- var me = this,
- operations = me.operations,
- operation = operations[index],
- onProxyReturn;
- if (operation === undefined) {
- me.isRunning = false;
- me.isComplete = true;
- me.fireEvent('complete', me, operations[operations.length - 1]);
- } else {
- me.current = index;
- onProxyReturn = function(operation) {
- var hasException = operation.hasException();
- if (hasException) {
- me.hasException = true;
- me.fireEvent('exception', me, operation);
- } else {
- me.fireEvent('operationcomplete', me, operation);
- }
- if (hasException && me.getPauseOnException()) {
- me.pause();
- } else {
- operation.setCompleted();
- me.runNextOperation();
- }
- };
- operation.setStarted();
- me.getProxy()[operation.getAction()](operation, onProxyReturn, me);
- }
- }
- });
- /**
- * @author Ed Spencer
- * @aside guide proxies
- *
- * Proxies are used by {@link Ext.data.Store Stores} to handle the loading and saving of {@link Ext.data.Model Model}
- * data. Usually developers will not need to create or interact with proxies directly.
- *
- * # Types of Proxy
- *
- * There are two main types of Proxy - {@link Ext.data.proxy.Client Client} and {@link Ext.data.proxy.Server Server}.
- * The Client proxies save their data locally and include the following subclasses:
- *
- * - {@link Ext.data.proxy.LocalStorage LocalStorageProxy} - saves its data to localStorage if the browser supports it
- * - {@link Ext.data.proxy.Memory MemoryProxy} - holds data in memory only, any data is lost when the page is refreshed
- *
- * The Server proxies save their data by sending requests to some remote server. These proxies include:
- *
- * - {@link Ext.data.proxy.Ajax Ajax} - sends requests to a server on the same domain
- * - {@link Ext.data.proxy.JsonP JsonP} - uses JSON-P to send requests to a server on a different domain
- *
- * Proxies operate on the principle that all operations performed are either Create, Read, Update or Delete. These four
- * operations are mapped to the methods {@link #create}, {@link #read}, {@link #update} and {@link #destroy}
- * respectively. Each Proxy subclass implements these functions.
- *
- * The CRUD methods each expect an {@link Ext.data.Operation Operation} object as the sole argument. The Operation
- * encapsulates information about the action the Store wishes to perform, the {@link Ext.data.Model model} instances
- * that are to be modified, etc. See the {@link Ext.data.Operation Operation} documentation for more details. Each CRUD
- * method also accepts a callback function to be called asynchronously on completion.
- *
- * Proxies also support batching of Operations via a {@link Ext.data.Batch batch} object, invoked by the {@link #batch}
- * method.
- */
- Ext.define('Ext.data.proxy.Proxy', {
- extend: 'Ext.Evented',
- alias: 'proxy.proxy',
- alternateClassName: ['Ext.data.DataProxy', 'Ext.data.Proxy'],
- requires: [
- 'Ext.data.reader.Json',
- 'Ext.data.writer.Json',
- 'Ext.data.Batch',
- 'Ext.data.Operation'
- ],
- config: {
- /**
- * @cfg {String} batchOrder
- * Comma-separated ordering 'create', 'update' and 'destroy' actions when batching. Override this to set a different
- * order for the batched CRUD actions to be executed in.
- * @accessor
- */
- batchOrder: 'create,update,destroy',
- /**
- * @cfg {Boolean} batchActions
- * True to batch actions of a particular type when synchronizing the store.
- * @accessor
- */
- batchActions: true,
- /**
- * @cfg {String/Ext.data.Model} model (required)
- * The name of the Model to tie to this Proxy. Can be either the string name of the Model, or a reference to the
- * Model constructor.
- * @accessor
- */
- model: null,
- /**
- * @cfg {Object/String/Ext.data.reader.Reader} reader
- * The Ext.data.reader.Reader to use to decode the server's response or data read from client. This can either be a
- * Reader instance, a config object or just a valid Reader type name (e.g. 'json', 'xml').
- * @accessor
- */
- reader: {
- type: 'json'
- },
- /**
- * @cfg {Object/String/Ext.data.writer.Writer} writer
- * The Ext.data.writer.Writer to use to encode any request sent to the server or saved to client. This can either be
- * a Writer instance, a config object or just a valid Writer type name (e.g. 'json', 'xml').
- * @accessor
- */
- writer: {
- type: 'json'
- }
- },
- isProxy: true,
- applyModel: function(model) {
- if (typeof model == 'string') {
- model = Ext.data.ModelManager.getModel(model);
- if (!model) {
- Ext.Logger.error('Model with name ' + arguments[0] + ' doesnt exist.');
- }
- }
- if (model && !model.prototype.isModel && Ext.isObject(model)) {
- model = Ext.data.ModelManager.registerType(model.storeId || model.id || Ext.id(), model);
- }
- return model;
- },
- updateModel: function(model) {
- if (model) {
- var reader = this.getReader();
- if (reader && !reader.getModel()) {
- reader.setModel(model);
- }
- }
- },
- applyReader: function(reader, currentReader) {
- return Ext.factory(reader, Ext.data.Reader, currentReader, 'reader');
- },
- updateReader: function(reader) {
- if (reader) {
- var model = this.getModel();
- if (!model) {
- model = reader.getModel();
- if (model) {
- this.setModel(model);
- }
- } else {
- reader.setModel(model);
- }
- if (reader.onMetaChange) {
- reader.onMetaChange = Ext.Function.createSequence(reader.onMetaChange, this.onMetaChange, this);
- }
- }
- },
- onMetaChange: function(data) {
- var model = this.getReader().getModel();
- if (!this.getModel() && model) {
- this.setModel(model);
- }
- /**
- * @event metachange
- * Fires whenever the server has sent back new metadata to reconfigure the Reader.
- * @param {Ext.data.Proxy} this
- * @param {Object} data The metadata sent back from the server
- */
- this.fireEvent('metachange', this, data);
- },
- applyWriter: function(writer, currentWriter) {
- return Ext.factory(writer, Ext.data.Writer, currentWriter, 'writer');
- },
- /**
- * Performs the given create operation. If you override this method in a custom Proxy, remember to always call the provided
- * callback method when you are done with your operation.
- * @param {Ext.data.Operation} operation The Operation to perform
- * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
- * @param {Object} scope Scope to execute the callback function in
- * @method
- */
- create: Ext.emptyFn,
- /**
- * Performs the given read operation. If you override this method in a custom Proxy, remember to always call the provided
- * callback method when you are done with your operation.
- * @param {Ext.data.Operation} operation The Operation to perform
- * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
- * @param {Object} scope Scope to execute the callback function in
- * @method
- */
- read: Ext.emptyFn,
- /**
- * Performs the given update operation. If you override this method in a custom Proxy, remember to always call the provided
- * callback method when you are done with your operation.
- * @param {Ext.data.Operation} operation The Operation to perform
- * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
- * @param {Object} scope Scope to execute the callback function in
- * @method
- */
- update: Ext.emptyFn,
- /**
- * Performs the given destroy operation. If you override this method in a custom Proxy, remember to always call the provided
- * callback method when you are done with your operation.
- * @param {Ext.data.Operation} operation The Operation to perform
- * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
- * @param {Object} scope Scope to execute the callback function in
- * @method
- */
- destroy: Ext.emptyFn,
- onDestroy: function() {
- Ext.destroy(this.getReader(), this.getWriter());
- },
- /**
- * Performs a batch of {@link Ext.data.Operation Operations}, in the order specified by {@link #batchOrder}. Used
- * internally by {@link Ext.data.Store}'s {@link Ext.data.Store#sync sync} method. Example usage:
- *
- * myProxy.batch({
- * create : [myModel1, myModel2],
- * update : [myModel3],
- * destroy: [myModel4, myModel5]
- * });
- *
- * Where the myModel* above are {@link Ext.data.Model Model} instances - in this case 1 and 2 are new instances and
- * have not been saved before, 3 has been saved previously but needs to be updated, and 4 and 5 have already been
- * saved but should now be destroyed.
- *
- * @param {Object} options Object containing one or more properties supported by the batch method:
- *
- * @param {Object} options.operations Object containing the Model instances to act upon, keyed by action name
- *
- * @param {Object} [options.listeners] Event listeners object passed straight through to the Batch -
- * see {@link Ext.data.Batch} for details
- *
- * @param {Ext.data.Batch/Object} [options.batch] A {@link Ext.data.Batch} object (or batch config to apply
- * to the created batch). If unspecified a default batch will be auto-created.
- *
- * @param {Function} [options.callback] The function to be called upon completion of processing the batch.
- * The callback is called regardless of success or failure and is passed the following parameters:
- * @param {Ext.data.Batch} options.callback.batch The {@link Ext.data.Batch batch} that was processed,
- * containing all operations in their current state after processing
- * @param {Object} options.callback.options The options argument that was originally passed into batch
- *
- * @param {Function} [options.success] The function to be called upon successful completion of the batch. The
- * success function is called only if no exceptions were reported in any operations. If one or more exceptions
- * occurred then the `failure` function will be called instead. The success function is called
- * with the following parameters:
- * @param {Ext.data.Batch} options.success.batch The {@link Ext.data.Batch batch} that was processed,
- * containing all operations in their current state after processing
- * @param {Object} options.success.options The options argument that was originally passed into batch
- *
- * @param {Function} [options.failure] The function to be called upon unsuccessful completion of the batch. The
- * failure function is called when one or more operations returns an exception during processing (even if some
- * operations were also successful). The failure function is called with the following parameters:
- * @param {Ext.data.Batch} options.failure.batch The {@link Ext.data.Batch batch} that was processed,
- * containing all operations in their current state after processing
- * @param {Object} options.failure.options The options argument that was originally passed into batch
- *
- * @param {Object} [options.scope] The scope in which to execute any callbacks (i.e. the `this` object inside
- * the callback, success and/or failure functions). Defaults to the proxy.
- *
- * @return {Ext.data.Batch} The newly created Batch
- */
- batch: function(options, /* deprecated */listeners) {
- var me = this,
- useBatch = me.getBatchActions(),
- model = me.getModel(),
- batch,
- records;
- if (options.operations === undefined) {
- // the old-style (operations, listeners) signature was called
- // so convert to the single options argument syntax
- options = {
- operations: options,
- listeners: listeners
- };
- // <debug warn>
- Ext.Logger.deprecate('Passes old-style signature to Proxy.batch (operations, listeners). Please convert to single options argument syntax.');
- // </debug>
- }
- if (options.batch && options.batch.isBatch) {
- batch = options.batch;
- } else {
- batch = new Ext.data.Batch(options.batch || {});
- }
- batch.setProxy(me);
- batch.on('complete', Ext.bind(me.onBatchComplete, me, [options], 0));
- if (options.listeners) {
- batch.on(options.listeners);
- }
- Ext.each(me.getBatchOrder().split(','), function(action) {
- records = options.operations[action];
- if (records) {
- if (useBatch) {
- batch.add(new Ext.data.Operation({
- action: action,
- records: records,
- model: model
- }));
- } else {
- Ext.each(records, function(record) {
- batch.add(new Ext.data.Operation({
- action : action,
- records: [record],
- model: model
- }));
- });
- }
- }
- }, me);
- batch.start();
- return batch;
- },
- /**
- * @private
- * The internal callback that the proxy uses to call any specified user callbacks after completion of a batch
- */
- onBatchComplete: function(batchOptions, batch) {
- var scope = batchOptions.scope || this;
- if (batch.hasException) {
- if (Ext.isFunction(batchOptions.failure)) {
- Ext.callback(batchOptions.failure, scope, [batch, batchOptions]);
- }
- } else if (Ext.isFunction(batchOptions.success)) {
- Ext.callback(batchOptions.success, scope, [batch, batchOptions]);
- }
- if (Ext.isFunction(batchOptions.callback)) {
- Ext.callback(batchOptions.callback, scope, [batch, batchOptions]);
- }
- }
- }, function() {
- // Ext.data2.proxy.ProxyMgr.registerType('proxy', this);
- //backwards compatibility
- // Ext.data.DataProxy = this;
- // Ext.deprecate('platform', '2.0', function() {
- // Ext.data2.DataProxy = this;
- // }, this);
- });
- /**
- * @author Ed Spencer
- *
- * Base class for any client-side storage. Used as a superclass for {@link Ext.data.proxy.Memory Memory} and
- * {@link Ext.data.proxy.WebStorage Web Storage} proxies. Do not use directly, use one of the subclasses instead.
- * @private
- */
- Ext.define('Ext.data.proxy.Client', {
- extend: 'Ext.data.proxy.Proxy',
- alternateClassName: 'Ext.proxy.ClientProxy',
- /**
- * Abstract function that must be implemented by each ClientProxy subclass. This should purge all record data
- * from the client side storage, as well as removing any supporting data (such as lists of record IDs)
- */
- clear: function() {
- //<debug>
- 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.");
- //</debug>
- }
- });
- /**
- * @author Ed Spencer
- * @aside guide proxies
- *
- * In-memory proxy. This proxy simply uses a local variable for data storage/retrieval, so its contents are lost on
- * every page refresh.
- *
- * Usually this Proxy isn't used directly, serving instead as a helper to a {@link Ext.data.Store Store} where a reader
- * is required to load data. For example, say we have a Store for a User model and have some inline data we want to
- * load, but this data isn't in quite the right format: we can use a MemoryProxy with a JsonReader to read it into our
- * Store:
- *
- * //this is the model we will be using in the store
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * {name: 'id', type: 'int'},
- * {name: 'name', type: 'string'},
- * {name: 'phone', type: 'string', mapping: 'phoneNumber'}
- * ]
- * }
- * });
- *
- * //this data does not line up to our model fields - the phone field is called phoneNumber
- * var data = {
- * users: [
- * {
- * id: 1,
- * name: 'Ed Spencer',
- * phoneNumber: '555 1234'
- * },
- * {
- * id: 2,
- * name: 'Abe Elias',
- * phoneNumber: '666 1234'
- * }
- * ]
- * };
- *
- * //note how we set the 'root' in the reader to match the data structure above
- * var store = Ext.create('Ext.data.Store', {
- * autoLoad: true,
- * model: 'User',
- * data : data,
- * proxy: {
- * type: 'memory',
- * reader: {
- * type: 'json',
- * root: 'users'
- * }
- * }
- * });
- */
- Ext.define('Ext.data.proxy.Memory', {
- extend: 'Ext.data.proxy.Client',
- alias: 'proxy.memory',
- alternateClassName: 'Ext.data.MemoryProxy',
- isMemoryProxy: true,
- config: {
- /**
- * @cfg {Object} data
- * Optional data to pass to configured Reader.
- */
- data: []
- },
- /**
- * @private
- * Fake processing function to commit the records, set the current operation
- * to successful and call the callback if provided. This function is shared
- * by the create, update and destroy methods to perform the bare minimum
- * processing required for the proxy to register a result from the action.
- */
- finishOperation: function(operation, callback, scope) {
- if (operation) {
- var i = 0,
- recs = operation.getRecords(),
- len = recs.length;
- for (i; i < len; i++) {
- recs[i].commit();
- }
- operation.setCompleted();
- operation.setSuccessful();
- Ext.callback(callback, scope || this, [operation]);
- }
- },
- /**
- * Currently this is a hard-coded method that simply commits any records and sets the operation to successful,
- * then calls the callback function, if provided. It is essentially mocking a server call in memory, but since
- * there is no real back end in this case there's not much else to do. This method can be easily overridden to
- * implement more complex logic if needed.
- * @param {Ext.data.Operation} operation The Operation to perform
- * @param {Function} callback Callback function to be called when the Operation has completed (whether
- * successful or not)
- * @param {Object} scope Scope to execute the callback function in
- * @method
- */
- create: function() {
- this.finishOperation.apply(this, arguments);
- },
- /**
- * Currently this is a hard-coded method that simply commits any records and sets the operation to successful,
- * then calls the callback function, if provided. It is essentially mocking a server call in memory, but since
- * there is no real back end in this case there's not much else to do. This method can be easily overridden to
- * implement more complex logic if needed.
- * @param {Ext.data.Operation} operation The Operation to perform
- * @param {Function} callback Callback function to be called when the Operation has completed (whether
- * successful or not)
- * @param {Object} scope Scope to execute the callback function in
- * @method
- */
- update: function() {
- this.finishOperation.apply(this, arguments);
- },
- /**
- * Currently this is a hard-coded method that simply commits any records and sets the operation to successful,
- * then calls the callback function, if provided. It is essentially mocking a server call in memory, but since
- * there is no real back end in this case there's not much else to do. This method can be easily overridden to
- * implement more complex logic if needed.
- * @param {Ext.data.Operation} operation The Operation to perform
- * @param {Function} callback Callback function to be called when the Operation has completed (whether
- * successful or not)
- * @param {Object} scope Scope to execute the callback function in
- * @method
- */
- destroy: function() {
- this.finishOperation.apply(this, arguments);
- },
- /**
- * Reads data from the configured {@link #data} object. Uses the Proxy's {@link #reader}, if present.
- * @param {Ext.data.Operation} operation The read Operation
- * @param {Function} callback The callback to call when reading has completed
- * @param {Object} scope The scope to call the callback function in
- */
- read: function(operation, callback, scope) {
- var me = this,
- reader = me.getReader();
- if (operation.process('read', reader.process(me.getData())) === false) {
- this.fireEvent('exception', this, null, operation);
- }
- Ext.callback(callback, scope || me, [operation]);
- },
- clear: Ext.emptyFn
- });
- /**
- * @class Ext.data.SortTypes
- * This class defines a series of static methods that are used on a
- * {@link Ext.data.Field} for performing sorting. The methods cast the
- * underlying values into a data type that is appropriate for sorting on
- * that particular field. If a {@link Ext.data.Field#type} is specified,
- * the `sortType` will be set to a sane default if the `sortType` is not
- * explicitly defined on the field. The `sortType` will make any necessary
- * modifications to the value and return it.
- *
- * - `asText` - Removes any tags and converts the value to a string.
- * - `asUCText` - Removes any tags and converts the value to an uppercase string.
- * - `asUCString` - Converts the value to an uppercase string.
- * - `asDate` - Converts the value into Unix epoch time.
- * - `asFloat` - Converts the value to a floating point number.
- * - `asInt` - Converts the value to an integer number.
- *
- * It is also possible to create a custom `sortType` that can be used throughout
- * an application.
- *
- * Ext.apply(Ext.data.SortTypes, {
- * asPerson: function(person){
- * // expects an object with a first and last name property
- * return person.lastName.toUpperCase() + person.firstName.toLowerCase();
- * }
- * });
- *
- * Ext.define('Employee', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [{
- * name: 'person',
- * sortType: 'asPerson'
- * }, {
- * name: 'salary',
- * type: 'float' // sortType set to asFloat
- * }]
- * }
- * });
- *
- * @singleton
- * @docauthor Evan Trimboli <evan@sencha.com>
- */
- Ext.define('Ext.data.SortTypes', {
- singleton: true,
- /**
- * The regular expression used to strip tags.
- * @type {RegExp}
- * @property
- */
- stripTagsRE : /<\/?[^>]+>/gi,
- /**
- * Default sort that does nothing.
- * @param {Object} value The value being converted.
- * @return {Object} The comparison value.
- */
- none : function(value) {
- return value;
- },
- /**
- * Strips all HTML tags to sort on text only.
- * @param {Object} value The value being converted.
- * @return {String} The comparison value.
- */
- asText : function(value) {
- return String(value).replace(this.stripTagsRE, "");
- },
- /**
- * Strips all HTML tags to sort on text only - case insensitive.
- * @param {Object} value The value being converted.
- * @return {String} The comparison value.
- */
- asUCText : function(value) {
- return String(value).toUpperCase().replace(this.stripTagsRE, "");
- },
- /**
- * Case insensitive string.
- * @param {Object} value The value being converted.
- * @return {String} The comparison value.
- */
- asUCString : function(value) {
- return String(value).toUpperCase();
- },
- /**
- * Date sorting.
- * @param {Object} value The value being converted.
- * @return {Number} The comparison value.
- */
- asDate : function(value) {
- if (!value) {
- return 0;
- }
- if (Ext.isDate(value)) {
- return value.getTime();
- }
- return Date.parse(String(value));
- },
- /**
- * Float sorting.
- * @param {Object} value The value being converted.
- * @return {Number} The comparison value.
- */
- asFloat : function(value) {
- value = parseFloat(String(value).replace(/,/g, ""));
- return isNaN(value) ? 0 : value;
- },
- /**
- * Integer sorting.
- * @param {Object} value The value being converted.
- * @return {Number} The comparison value.
- */
- asInt : function(value) {
- value = parseInt(String(value).replace(/,/g, ""), 10);
- return isNaN(value) ? 0 : value;
- }
- });
- /**
- * @class Ext.data.Types
- *
- * This is a static class containing the system-supplied data types which may be given to a {@link Ext.data.Field Field}.
- *
- * The properties in this class are used as type indicators in the {@link Ext.data.Field Field} class, so to
- * test whether a Field is of a certain type, compare the {@link Ext.data.Field#type type} property against properties
- * of this class.
- *
- * Developers may add their own application-specific data types to this class. Definition names must be UPPERCASE.
- * each type definition must contain three properties:
- *
- * - `convert`: {Function} - A function to convert raw data values from a data block into the data
- * to be stored in the Field. The function is passed the following parameters:
- * + `v`: {Mixed} - The data value as read by the Reader, if `undefined` will use
- * the configured `{@link Ext.data.Field#defaultValue defaultValue}`.
- * + `rec`: {Mixed} - The data object containing the row as read by the Reader.
- * Depending on the Reader type, this could be an Array ({@link Ext.data.reader.Array ArrayReader}), an object
- * ({@link Ext.data.reader.Json JsonReader}), or an XML element.
- * - `sortType`: {Function} - A function to convert the stored data into comparable form, as defined by {@link Ext.data.SortTypes}.
- * - `type`: {String} - A textual data type name.
- *
- * 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
- * which contained the properties `lat` and `long`, you would define a new data type like this:
- *
- * // Add a new Field data type which stores a VELatLong object in the Record.
- * Ext.data.Types.VELATLONG = {
- * convert: function(v, data) {
- * return new VELatLong(data.lat, data.long);
- * },
- * sortType: function(v) {
- * return v.Latitude; // When sorting, order by latitude
- * },
- * type: 'VELatLong'
- * };
- *
- * Then, when declaring a Model, use:
- *
- * var types = Ext.data.Types; // allow shorthand type access
- * Ext.define('Unit', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * { name: 'unitName', mapping: 'UnitName' },
- * { name: 'curSpeed', mapping: 'CurSpeed', type: types.INT },
- * { name: 'latitude', mapping: 'lat', type: types.FLOAT },
- * { name: 'position', type: types.VELATLONG }
- * ]
- * }
- * });
- *
- * @singleton
- */
- Ext.define('Ext.data.Types', {
- singleton: true,
- requires: ['Ext.data.SortTypes'],
- /**
- * @property {RegExp} stripRe
- * A regular expression for stripping non-numeric characters from a numeric value.
- * This should be overridden for localization.
- */
- stripRe: /[\$,%]/g,
- dashesRe: /-/g,
- iso8601TestRe: /\d\dT\d\d/,
- iso8601SplitRe: /[- :T\.Z\+]/
- }, function() {
- var Types = this,
- sortTypes = Ext.data.SortTypes;
- Ext.apply(Types, {
- /**
- * @property {Object} AUTO
- * This data type means that no conversion is applied to the raw data before it is placed into a Record.
- */
- AUTO: {
- convert: function(value) {
- return value;
- },
- sortType: sortTypes.none,
- type: 'auto'
- },
- /**
- * @property {Object} STRING
- * This data type means that the raw data is converted into a String before it is placed into a Record.
- */
- STRING: {
- convert: function(value) {
- // 'this' is the actual field that calls this convert method
- return (value === undefined || value === null)
- ? (this.getAllowNull() ? null : '')
- : String(value);
- },
- sortType: sortTypes.asUCString,
- type: 'string'
- },
- /**
- * @property {Object} INT
- * This data type means that the raw data is converted into an integer before it is placed into a Record.
- *
- * The synonym `INTEGER` is equivalent.
- */
- INT: {
- convert: function(value) {
- return (value !== undefined && value !== null && value !== '')
- ? ((typeof value === 'number')
- ? parseInt(value, 10)
- : parseInt(String(value).replace(Types.stripRe, ''), 10)
- )
- : (this.getAllowNull() ? null : 0);
- },
- sortType: sortTypes.none,
- type: 'int'
- },
- /**
- * @property {Object} FLOAT
- * This data type means that the raw data is converted into a number before it is placed into a Record.
- *
- * The synonym `NUMBER` is equivalent.
- */
- FLOAT: {
- convert: function(value) {
- return (value !== undefined && value !== null && value !== '')
- ? ((typeof value === 'number')
- ? value
- : parseFloat(String(value).replace(Types.stripRe, ''), 10)
- )
- : (this.getAllowNull() ? null : 0);
- },
- sortType: sortTypes.none,
- type: 'float'
- },
- /**
- * @property {Object} BOOL
- * This data type means that the raw data is converted into a Boolean before it is placed into
- * a Record. The string "true" and the number 1 are converted to Boolean `true`.
- *
- * The synonym `BOOLEAN` is equivalent.
- */
- BOOL: {
- convert: function(value) {
- if ((value === undefined || value === null || value === '') && this.getAllowNull()) {
- return null;
- }
- return value !== 'false' && !!value;
- },
- sortType: sortTypes.none,
- type: 'bool'
- },
- /**
- * @property {Object} DATE
- * This data type means that the raw data is converted into a Date before it is placed into a Record.
- * The date format is specified in the constructor of the {@link Ext.data.Field} to which this type is
- * being applied.
- */
- DATE: {
- convert: function(value) {
- var dateFormat = this.getDateFormat(),
- parsed;
- if (!value) {
- return null;
- }
- if (Ext.isDate(value)) {
- return value;
- }
- if (dateFormat) {
- if (dateFormat == 'timestamp') {
- return new Date(value*1000);
- }
- if (dateFormat == 'time') {
- return new Date(parseInt(value, 10));
- }
- return Ext.Date.parse(value, dateFormat);
- }
- parsed = new Date(Date.parse(value));
- if (isNaN(parsed)) {
- // Dates with ISO 8601 format are not well supported by mobile devices, this can work around the issue.
- if (Types.iso8601TestRe.test(value)) {
- parsed = value.split(Types.iso8601SplitRe);
- parsed = new Date(parsed[0], parsed[1]-1, parsed[2], parsed[3], parsed[4], parsed[5]);
- }
- if (isNaN(parsed)) {
- // Dates with the format "2012-01-20" fail, but "2012/01/20" work in some browsers. We'll try and
- // get around that.
- parsed = new Date(Date.parse(value.replace(Types.dashesRe, "/")));
- //<debug>
- if (isNaN(parsed)) {
- Ext.Logger.warn("Cannot parse the passed value (" + value + ") into a valid date");
- }
- //</debug>
- }
- }
- return isNaN(parsed) ? null : parsed;
- },
- sortType: sortTypes.asDate,
- type: 'date'
- }
- });
- Ext.apply(Types, {
- /**
- * @property {Object} BOOLEAN
- * This data type means that the raw data is converted into a Boolean before it is placed into
- * a Record. The string "true" and the number 1 are converted to Boolean `true`.
- *
- * The synonym `BOOL` is equivalent.
- */
- BOOLEAN: this.BOOL,
- /**
- * @property {Object} INTEGER
- * This data type means that the raw data is converted into an integer before it is placed into a Record.
- *
- *The synonym `INT` is equivalent.
- */
- INTEGER: this.INT,
- /**
- * @property {Object} NUMBER
- * This data type means that the raw data is converted into a number before it is placed into a Record.
- *
- * The synonym `FLOAT` is equivalent.
- */
- NUMBER: this.FLOAT
- });
- });
- /**
- * @author Ed Spencer
- * @aside guide models
- *
- * Fields are used to define what a Model is. They aren't instantiated directly - instead, when we create a class that
- * extends {@link Ext.data.Model}, it will automatically create a Field instance for each field configured in a {@link
- * Ext.data.Model Model}. For example, we might set up a model like this:
- *
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * 'name', 'email',
- * {name: 'age', type: 'int'},
- * {name: 'gender', type: 'string', defaultValue: 'Unknown'}
- * ]
- * }
- * });
- *
- * Four fields will have been created for the User Model - name, email, age, and gender. Note that we specified a couple
- * of different formats here; if we only pass in the string name of the field (as with name and email), the field is set
- * up with the 'auto' type. It's as if we'd done this instead:
- *
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * {name: 'name', type: 'auto'},
- * {name: 'email', type: 'auto'},
- * {name: 'age', type: 'int'},
- * {name: 'gender', type: 'string', defaultValue: 'Unknown'}
- * ]
- * }
- * });
- *
- * # Types and conversion
- *
- * The {@link #type} is important - it's used to automatically convert data passed to the field into the correct format.
- * In our example above, the name and email fields used the 'auto' type and will just accept anything that is passed
- * into them. The 'age' field had an 'int' type however, so if we passed 25.4 this would be rounded to 25.
- *
- * Sometimes a simple type isn't enough, or we want to perform some processing when we load a Field's data. We can do
- * this using a {@link #convert} function. Here, we're going to create a new field based on another:
- *
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * 'name', 'email',
- * {name: 'age', type: 'int'},
- * {name: 'gender', type: 'string', defaultValue: 'Unknown'},
- *
- * {
- * name: 'firstName',
- * convert: function(value, record) {
- * var fullName = record.get('name'),
- * splits = fullName.split(" "),
- * firstName = splits[0];
- *
- * return firstName;
- * }
- * }
- * ]
- * }
- * });
- *
- * Now when we create a new User, the firstName is populated automatically based on the name:
- *
- * var ed = Ext.create('User', {name: 'Ed Spencer'});
- *
- * console.log(ed.get('firstName')); //logs 'Ed', based on our convert function
- *
- * In fact, if we log out all of the data inside ed, we'll see this:
- *
- * console.log(ed.data);
- *
- * //outputs this:
- * {
- * age: 0,
- * email: "",
- * firstName: "Ed",
- * gender: "Unknown",
- * name: "Ed Spencer"
- * }
- *
- * The age field has been given a default of zero because we made it an int type. As an auto field, email has defaulted
- * to an empty string. When we registered the User model we set gender's {@link #defaultValue} to 'Unknown' so we see
- * that now. Let's correct that and satisfy ourselves that the types work as we expect:
- *
- * ed.set('gender', 'Male');
- * ed.get('gender'); //returns 'Male'
- *
- * ed.set('age', 25.4);
- * ed.get('age'); //returns 25 - we wanted an int, not a float, so no decimal places allowed
- */
- Ext.define('Ext.data.Field', {
- requires: ['Ext.data.Types', 'Ext.data.SortTypes'],
- alias: 'data.field',
- isField: true,
- config: {
- /**
- * @cfg {String} name
- *
- * The name by which the field is referenced within the Model. This is referenced by, for example, the `dataIndex`
- * property in column definition objects passed to Ext.grid.property.HeaderContainer.
- *
- * Note: In the simplest case, if no properties other than `name` are required, a field definition may consist of
- * just a String for the field name.
- */
- name: null,
- /**
- * @cfg {String/Object} type
- *
- * The data type for automatic conversion from received data to the *stored* value if
- * `{@link Ext.data.Field#convert convert}` has not been specified. This may be specified as a string value.
- * Possible values are
- *
- * - auto (Default, implies no conversion)
- * - string
- * - int
- * - float
- * - boolean
- * - date
- *
- * This may also be specified by referencing a member of the {@link Ext.data.Types} class.
- *
- * Developers may create their own application-specific data types by defining new members of the {@link
- * Ext.data.Types} class.
- */
- type: 'auto',
- /**
- * @cfg {Function} convert
- *
- * A function which converts the value provided by the Reader into an object that will be stored in the Model.
- * It is passed the following parameters:
- *
- * - **v** : Mixed
- *
- * The data value as read by the Reader, if undefined will use the configured `{@link Ext.data.Field#defaultValue
- * defaultValue}`.
- *
- * - **rec** : Ext.data.Model
- *
- * The data object containing the Model as read so far by the Reader. Note that the Model may not be fully populated
- * at this point as the fields are read in the order that they are defined in your
- * {@link Ext.data.Model#cfg-fields fields} array.
- *
- * Example of convert functions:
- *
- * function fullName(v, record) {
- * return record.name.last + ', ' + record.name.first;
- * }
- *
- * function location(v, record) {
- * return !record.city ? '' : (record.city + ', ' + record.state);
- * }
- *
- * Ext.define('Dude', {
- * extend: 'Ext.data.Model',
- * fields: [
- * {name: 'fullname', convert: fullName},
- * {name: 'firstname', mapping: 'name.first'},
- * {name: 'lastname', mapping: 'name.last'},
- * {name: 'city', defaultValue: 'homeless'},
- * 'state',
- * {name: 'location', convert: location}
- * ]
- * });
- *
- * // create the data store
- * var store = Ext.create('Ext.data.Store', {
- * reader: {
- * type: 'json',
- * model: 'Dude',
- * idProperty: 'key',
- * rootProperty: 'daRoot',
- * totalProperty: 'total'
- * }
- * });
- *
- * var myData = [
- * { key: 1,
- * name: { first: 'Fat', last: 'Albert' }
- * // notice no city, state provided in data2 object
- * },
- * { key: 2,
- * name: { first: 'Barney', last: 'Rubble' },
- * city: 'Bedrock', state: 'Stoneridge'
- * },
- * { key: 3,
- * name: { first: 'Cliff', last: 'Claven' },
- * city: 'Boston', state: 'MA'
- * }
- * ];
- */
- convert: undefined,
- /**
- * @cfg {String} dateFormat
- *
- * Used when converting received data into a Date when the {@link #type} is specified as `"date"`.
- *
- * A format string for the {@link Ext.Date#parse Ext.Date.parse} function, or "timestamp" if the value provided by
- * the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a JavaScript millisecond
- * timestamp. See {@link Ext.Date}.
- */
- dateFormat: null,
- /**
- * @cfg {Boolean} allowNull
- *
- * Use when converting received data into a boolean, string or number type (either int or float). If the value cannot be
- * parsed, `null` will be used if `allowNull` is `true`, otherwise the value will be 0.
- */
- allowNull: true,
- /**
- * @cfg {Object} [defaultValue='']
- *
- * The default value used **when a Model is being created by a {@link Ext.data.reader.Reader Reader}**
- * when the item referenced by the `{@link Ext.data.Field#mapping mapping}` does not exist in the data object
- * (i.e. `undefined`).
- */
- defaultValue: undefined,
- /**
- * @cfg {String/Number} mapping
- *
- * (Optional) A path expression for use by the {@link Ext.data.reader.Reader} implementation that is creating the
- * {@link Ext.data.Model Model} to extract the Field value from the data object. If the path expression is the same
- * as the field name, the mapping may be omitted.
- *
- * The form of the mapping expression depends on the Reader being used.
- *
- * - {@link Ext.data.reader.Json}
- *
- * The mapping is a string containing the JavaScript expression to reference the data from an element of the data2
- * item's {@link Ext.data.reader.Json#rootProperty rootProperty} Array. Defaults to the field name.
- *
- * - {@link Ext.data.reader.Xml}
- *
- * The mapping is an {@link Ext.DomQuery} path to the data item relative to the DOM element that represents the
- * {@link Ext.data.reader.Xml#record record}. Defaults to the field name.
- *
- * - {@link Ext.data.reader.Array}
- *
- * The mapping is a number indicating the Array index of the field's value. Defaults to the field specification's
- * Array position.
- *
- * If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
- * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
- * return the desired data.
- */
- mapping: null,
- /**
- * @cfg {Function} sortType
- *
- * A function which converts a Field's value to a comparable value in order to ensure correct sort ordering.
- * Predefined functions are provided in {@link Ext.data.SortTypes}. A custom sort example:
- *
- * // current sort after sort we want
- * // +-+------+ +-+------+
- * // |1|First | |1|First |
- * // |2|Last | |3|Second|
- * // |3|Second| |2|Last |
- * // +-+------+ +-+------+
- *
- * sortType: function(value) {
- * switch (value.toLowerCase()) // native toLowerCase():
- * {
- * case 'first': return 1;
- * case 'second': return 2;
- * default: return 3;
- * }
- * }
- */
- sortType : undefined,
- /**
- * @cfg {String} sortDir
- *
- * Initial direction to sort (`"ASC"` or `"DESC"`).
- */
- sortDir : "ASC",
- /**
- * @cfg {Boolean} allowBlank
- * @private
- *
- * Used for validating a {@link Ext.data.Model model}. An empty value here will cause
- * {@link Ext.data.Model}.{@link Ext.data.Model#isValid isValid} to evaluate to `false`.
- */
- allowBlank : true,
- /**
- * @cfg {Boolean} persist
- *
- * `false` to exclude this field from being synchronized with the server or localStorage.
- * This option is useful when model fields are used to keep state on the client but do
- * not need to be persisted to the server.
- */
- persist: true,
- // Used in LocalStorage stuff
- encode: null,
- decode: null,
- bubbleEvents: 'action'
- },
- constructor : function(config) {
- // This adds support for just passing a string used as the field name
- if (Ext.isString(config)) {
- config = {name: config};
- }
- this.initConfig(config);
- },
- applyType: function(type) {
- var types = Ext.data.Types,
- autoType = types.AUTO;
- if (type) {
- if (Ext.isString(type)) {
- return types[type.toUpperCase()] || autoType;
- } else {
- // At this point we expect an actual type
- return type;
- }
- }
- return autoType;
- },
- updateType: function(newType, oldType) {
- var convert = this.getConvert();
- if (oldType && convert === oldType.convert) {
- this.setConvert(newType.convert);
- }
- },
- applySortType: function(sortType) {
- var sortTypes = Ext.data.SortTypes,
- type = this.getType(),
- defaultSortType = type.sortType;
- if (sortType) {
- if (Ext.isString(sortType)) {
- return sortTypes[sortType] || defaultSortType;
- } else {
- // At this point we expect a function
- return sortType;
- }
- }
- return defaultSortType;
- },
- applyConvert: function(convert) {
- var defaultConvert = this.getType().convert;
- if (convert && convert !== defaultConvert) {
- this._hasCustomConvert = true;
- return convert;
- } else {
- this._hasCustomConvert = false;
- return defaultConvert;
- }
- },
- hasCustomConvert: function() {
- return this._hasCustomConvert;
- }
- });
- /**
- * @author Tommy Maintz
- *
- * This class is the simple default id generator for Model instances.
- *
- * An example of a configured simple generator would be:
- *
- * Ext.define('MyApp.data.MyModel', {
- * extend: 'Ext.data.Model',
- * config: {
- * identifier: {
- * type: 'simple',
- * prefix: 'ID_'
- * }
- * }
- * });
- * // assign id's of ID_1, ID_2, ID_3, etc.
- *
- */
- Ext.define('Ext.data.identifier.Simple', {
- alias: 'data.identifier.simple',
-
- statics: {
- AUTO_ID: 1
- },
- config: {
- prefix: 'ext-record-'
- },
- constructor: function(config) {
- this.initConfig(config);
- },
- generate: function(record) {
- return this._prefix + this.self.AUTO_ID++;
- }
- });
- /**
- * @author Ed Spencer
- *
- * The ModelManager keeps track of all {@link Ext.data.Model} types defined in your application.
- *
- * ## Creating Model Instances
- *
- * Model instances can be created by using the {@link Ext.ClassManager#create Ext.create} method. Ext.create replaces
- * the deprecated {@link #create Ext.ModelManager.create} method. It is also possible to create a model instance
- * this by using the Model type directly. The following 3 snippets are equivalent:
- *
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: ['first', 'last']
- * }
- * });
- *
- * // method 1, create using Ext.create (recommended)
- * Ext.create('User', {
- * first: 'Ed',
- * last: 'Spencer'
- * });
- *
- * // method 2, create on the type directly
- * new User({
- * first: 'Ed',
- * last: 'Spencer'
- * });
- *
- * // method 3, create through the manager (deprecated)
- * Ext.ModelManager.create({
- * first: 'Ed',
- * last: 'Spencer'
- * }, 'User');
- *
- * ## Accessing Model Types
- *
- * A reference to a Model type can be obtained by using the {@link #getModel} function. Since models types
- * are normal classes, you can access the type directly. The following snippets are equivalent:
- *
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: ['first', 'last']
- * }
- * });
- *
- * // method 1, access model type through the manager
- * var UserType = Ext.ModelManager.getModel('User');
- *
- * // method 2, reference the type directly
- * var UserType = User;
- */
- Ext.define('Ext.data.ModelManager', {
- extend: 'Ext.AbstractManager',
- alternateClassName: ['Ext.ModelMgr', 'Ext.ModelManager'],
- singleton: true,
- /**
- * @property defaultProxyType
- * The string type of the default Model Proxy.
- * @removed 2.0.0
- */
- /**
- * @property associationStack
- * Private stack of associations that must be created once their associated model has been defined.
- * @removed 2.0.0
- */
- modelNamespace: null,
- /**
- * Registers a model definition. All model plugins marked with `isDefault: true` are bootstrapped
- * immediately, as are any addition plugins defined in the model config.
- * @param name
- * @param config
- * @return {Object}
- */
- registerType: function(name, config) {
- var proto = config.prototype,
- model;
- if (proto && proto.isModel) {
- // registering an already defined model
- model = config;
- } else {
- config = {
- extend: config.extend || 'Ext.data.Model',
- config: config
- };
- model = Ext.define(name, config);
- }
- this.types[name] = model;
- return model;
- },
- onModelDefined: Ext.emptyFn,
- // /**
- // * @private
- // * Private callback called whenever a model has just been defined. This sets up any associations
- // * that were waiting for the given model to be defined.
- // * @param {Function} model The model that was just created.
- // */
- // onModelDefined: function(model) {
- // var stack = this.associationStack,
- // length = stack.length,
- // create = [],
- // association, i, created;
- //
- // for (i = 0; i < length; i++) {
- // association = stack[i];
- //
- // if (association.associatedModel == model.modelName) {
- // create.push(association);
- // }
- // }
- //
- // for (i = 0, length = create.length; i < length; i++) {
- // created = create[i];
- // this.types[created.ownerModel].prototype.associations.add(Ext.data.association.Association.create(created));
- // Ext.Array.remove(stack, created);
- // }
- // },
- //
- // /**
- // * Registers an association where one of the models defined doesn't exist yet.
- // * The ModelManager will check when new models are registered if it can link them
- // * together.
- // * @private
- // * @param {Ext.data.association.Association} association The association.
- // */
- // registerDeferredAssociation: function(association){
- // this.associationStack.push(association);
- // },
- /**
- * Returns the {@link Ext.data.Model} for a given model name.
- * @param {String/Object} id The `id` of the model or the model instance.
- * @return {Ext.data.Model} A model class.
- */
- getModel: function(id) {
- var model = id;
- if (typeof model == 'string') {
- model = this.types[model];
- if (!model && this.modelNamespace) {
- model = this.types[this.modelNamespace + '.' + model];
- }
- }
- return model;
- },
- /**
- * Creates a new instance of a Model using the given data.
- *
- * __Note:__ This method is deprecated. Use {@link Ext.ClassManager#create Ext.create} instead. For example:
- *
- * Ext.create('User', {
- * first: 'Ed',
- * last: 'Spencer'
- * });
- *
- * @param {Object} data Data to initialize the Model's fields with.
- * @param {String} name The name of the model to create.
- * @param {Number} id (optional) Unique id of the Model instance (see {@link Ext.data.Model}).
- * @return {Object}
- */
- create: function(config, name, id) {
- var con = typeof name == 'function' ? name : this.types[name || config.name];
- return new con(config, id);
- }
- }, function() {
- /**
- * Old way for creating Model classes. Instead use:
- *
- * Ext.define("MyModel", {
- * extend: "Ext.data.Model",
- * fields: []
- * });
- *
- * @param {String} name Name of the Model class.
- * @param {Object} config A configuration object for the Model you wish to create.
- * @return {Ext.data.Model} The newly registered Model.
- * @member Ext
- * @deprecated 2.0.0 Please use {@link Ext#define} instead.
- */
- Ext.regModel = function() {
- //<debug>
- Ext.Logger.deprecate('Ext.regModel has been deprecated. Models can now be created by ' +
- 'extending Ext.data.Model: Ext.define("MyModel", {extend: "Ext.data.Model", fields: []});.');
- //</debug>
- return this.ModelManager.registerType.apply(this.ModelManager, arguments);
- };
- });
- /**
- * @author Ed Spencer
- *
- * Simple class that represents a Request that will be made by any {@link Ext.data.proxy.Server} subclass.
- * All this class does is standardize the representation of a Request as used by any ServerProxy subclass,
- * it does not contain any actual logic or perform the request itself.
- */
- Ext.define('Ext.data.Request', {
- config: {
- /**
- * @cfg {String} action
- * The name of the action this Request represents. Usually one of 'create', 'read', 'update' or 'destroy'.
- */
- action: null,
- /**
- * @cfg {Object} params
- * HTTP request params. The Proxy and its Writer have access to and can modify this object.
- */
- params: null,
- /**
- * @cfg {String} method
- * The HTTP method to use on this Request. Should be one of 'GET', 'POST', 'PUT' or 'DELETE'.
- */
- method: 'GET',
- /**
- * @cfg {String} url
- * The url to access on this Request.
- */
- url: null,
- /**
- * @cfg {Ext.data.Operation} operation
- * The operation this request belongs to.
- */
- operation: null,
- /**
- * @cfg {Ext.data.proxy.Proxy} proxy
- * The proxy this request belongs to.
- */
- proxy: null,
- /**
- * @cfg {Boolean} disableCaching
- * Whether or not to disable caching for this request.
- */
- disableCaching: false,
- /**
- * @cfg {Object} headers
- * Some requests (like XMLHttpRequests) want to send additional server headers.
- * This configuration can be set for those types of requests.
- */
- headers: {},
- /**
- * @cfg {String} callbackKey
- * Some requests (like JsonP) want to send an additional key that contains
- * the name of the callback function.
- */
- callbackKey: null,
- /**
- * @cfg {Ext.data.JsonP} jsonp
- * JsonP requests return a handle that might be useful in the callback function.
- */
- jsonP: null,
- /**
- * @cfg {Object} jsonData
- * This is used by some write actions to attach data to the request without encoding it
- * as a parameter.
- */
- jsonData: null,
- /**
- * @cfg {Object} xmlData
- * This is used by some write actions to attach data to the request without encoding it
- * as a parameter, but instead sending it as XML.
- */
- xmlData: null,
- /**
- * @cfg {Boolean} withCredentials
- * This field is necessary when using cross-origin resource sharing.
- */
- withCredentials: null,
- /**
- * @cfg {String} username
- * Most oData feeds require basic HTTP authentication. This configuration allows
- * you to specify the username.
- * @accessor
- */
- username: null,
- /**
- * @cfg {String} password
- * Most oData feeds require basic HTTP authentication. This configuration allows
- * you to specify the password.
- * @accessor
- */
- password: null,
- callback: null,
- scope: null,
- timeout: 30000,
- records: null,
- // The following two configurations are only used by Ext.data.proxy.Direct and are just
- // for being able to retrieve them after the request comes back from the server.
- directFn: null,
- args: null
- },
- /**
- * Creates the Request object.
- * @param {Object} [config] Config object.
- */
- constructor: function(config) {
- this.initConfig(config);
- }
- });
- /**
- * @author Ed Spencer
- *
- * ServerProxy is a superclass of {@link Ext.data.proxy.JsonP JsonPProxy} and {@link Ext.data.proxy.Ajax AjaxProxy}, and
- * would not usually be used directly.
- * @private
- */
- Ext.define('Ext.data.proxy.Server', {
- extend: 'Ext.data.proxy.Proxy',
- alias : 'proxy.server',
- alternateClassName: 'Ext.data.ServerProxy',
- requires : ['Ext.data.Request'],
- config: {
- /**
- * @cfg {String} url
- * The URL from which to request the data object.
- */
- url: null,
- /**
- * @cfg {String} pageParam
- * The name of the `page` parameter to send in a request. Set this to `false` if you don't
- * want to send a page parameter.
- */
- pageParam: 'page',
- /**
- * @cfg {String} startParam
- * The name of the `start` parameter to send in a request. Set this to `false` if you don't
- * want to send a start parameter.
- */
- startParam: 'start',
- /**
- * @cfg {String} limitParam
- * The name of the `limit` parameter to send in a request. Set this to `false` if you don't
- * want to send a limit parameter.
- */
- limitParam: 'limit',
- /**
- * @cfg {String} groupParam
- * The name of the `group` parameter to send in a request. Set this to `false` if you don't
- * want to send a group parameter.
- */
- groupParam: 'group',
- /**
- * @cfg {String} sortParam
- * The name of the `sort` parameter to send in a request. Set this to `undefined` if you don't
- * want to send a sort parameter.
- */
- sortParam: 'sort',
- /**
- * @cfg {String} filterParam
- * The name of the 'filter' parameter to send in a request. Set this to `undefined` if you don't
- * want to send a filter parameter.
- */
- filterParam: 'filter',
- /**
- * @cfg {String} directionParam
- * The name of the direction parameter to send in a request.
- *
- * __Note:__ This is only used when `simpleSortMode` is set to `true`.
- */
- directionParam: 'dir',
- /**
- * @cfg {Boolean} enablePagingParams This can be set to `false` if you want to prevent the paging params to be
- * sent along with the requests made by this proxy.
- */
- enablePagingParams: true,
- /**
- * @cfg {Boolean} simpleSortMode
- * Enabling `simpleSortMode` in conjunction with `remoteSort` will only send one sort property and a direction when a
- * remote sort is requested. The `directionParam` and `sortParam` will be sent with the property name and either 'ASC'
- * or 'DESC'.
- */
- simpleSortMode: false,
- /**
- * @cfg {Boolean} noCache
- * Disable caching by adding a unique parameter name to the request. Set to `false` to allow caching.
- */
- noCache : true,
- /**
- * @cfg {String} cacheString
- * The name of the cache param added to the url when using `noCache`.
- */
- cacheString: "_dc",
- /**
- * @cfg {Number} timeout
- * The number of milliseconds to wait for a response.
- */
- timeout : 30000,
- /**
- * @cfg {Object} api
- * Specific urls to call on CRUD action methods "create", "read", "update" and "destroy". Defaults to:
- *
- * api: {
- * create : undefined,
- * read : undefined,
- * update : undefined,
- * destroy : undefined
- * }
- *
- * The url is built based upon the action being executed [create|read|update|destroy] using the commensurate
- * {@link #api} property, or if undefined default to the configured
- * {@link Ext.data.Store}.{@link Ext.data.proxy.Server#url url}.
- *
- * For example:
- *
- * api: {
- * create : '/controller/new',
- * read : '/controller/load',
- * update : '/controller/update',
- * destroy : '/controller/destroy_action'
- * }
- *
- * If the specific URL for a given CRUD action is undefined, the CRUD action request will be directed to the
- * configured {@link Ext.data.proxy.Server#url url}.
- */
- api: {
- create : undefined,
- read : undefined,
- update : undefined,
- destroy : undefined
- },
- /**
- * @cfg {Object} extraParams
- * Extra parameters that will be included on every request. Individual requests with params of the same name
- * will override these params when they are in conflict.
- */
- extraParams: {}
- },
- constructor: function(config) {
- config = config || {};
- if (config.nocache !== undefined) {
- config.noCache = config.nocache;
- // <debug>
- Ext.Logger.warn('nocache configuration on Ext.data.proxy.Server has been deprecated. Please use noCache.');
- // </debug>
- }
- this.callParent([config]);
- },
- //in a ServerProxy all four CRUD operations are executed in the same manner, so we delegate to doRequest in each case
- create: function() {
- return this.doRequest.apply(this, arguments);
- },
- read: function() {
- return this.doRequest.apply(this, arguments);
- },
- update: function() {
- return this.doRequest.apply(this, arguments);
- },
- destroy: function() {
- return this.doRequest.apply(this, arguments);
- },
- /**
- * Sets a value in the underlying {@link #extraParams}.
- * @param {String} name The key for the new value
- * @param {Object} value The value
- */
- setExtraParam: function(name, value) {
- this.getExtraParams()[name] = value;
- },
- /**
- * Creates and returns an Ext.data.Request object based on the options passed by the {@link Ext.data.Store Store}
- * that this Proxy is attached to.
- * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
- * @return {Ext.data.Request} The request object
- */
- buildRequest: function(operation) {
- var me = this,
- params = Ext.applyIf(operation.getParams() || {}, me.getExtraParams() || {}),
- request;
- //copy any sorters, filters etc into the params so they can be sent over the wire
- params = Ext.applyIf(params, me.getParams(operation));
- request = Ext.create('Ext.data.Request', {
- params : params,
- action : operation.getAction(),
- records : operation.getRecords(),
- url : operation.getUrl(),
- operation: operation,
- proxy : me
- });
- request.setUrl(me.buildUrl(request));
- operation.setRequest(request);
- return request;
- },
- /**
- * This method handles the processing of the response and is usually overridden by subclasses to
- * do additional processing.
- * @param {Boolean} success Whether or not this request was successful
- * @param {Ext.data.Operation} operation The operation we made this request for
- * @param {Ext.data.Request} request The request that was made
- * @param {Object} response The response that we got
- * @param {Function} callback The callback to be fired onces the response is processed
- * @param {Object} scope The scope in which we call the callback
- * @protected
- */
- processResponse: function(success, operation, request, response, callback, scope) {
- var me = this,
- action = operation.getAction(),
- reader, resultSet;
- if (success === true) {
- reader = me.getReader();
- try {
- resultSet = reader.process(response);
- } catch(e) {
- operation.setException(e.message);
- me.fireEvent('exception', this, response, operation);
- return;
- }
- // This could happen if the model was configured using metaData
- if (!operation.getModel()) {
- operation.setModel(this.getModel());
- }
- if (operation.process(action, resultSet, request, response) === false) {
- this.fireEvent('exception', this, response, operation);
- }
- } else {
- me.setException(operation, response);
- /**
- * @event exception
- * Fires when the server returns an exception
- * @param {Ext.data.proxy.Proxy} this
- * @param {Object} response The response from the AJAX request
- * @param {Ext.data.Operation} operation The operation that triggered request
- */
- me.fireEvent('exception', this, response, operation);
- }
- //this callback is the one that was passed to the 'read' or 'write' function above
- if (typeof callback == 'function') {
- callback.call(scope || me, operation);
- }
- me.afterRequest(request, success);
- },
- /**
- * Sets up an exception on the operation
- * @private
- * @param {Ext.data.Operation} operation The operation
- * @param {Object} response The response
- */
- setException: function(operation, response) {
- if (Ext.isObject(response)) {
- operation.setException({
- status: response.status,
- statusText: response.statusText
- });
- }
- },
- /**
- * Encode any values being sent to the server. Can be overridden in subclasses.
- * @private
- * @param {Array} value An array of sorters/filters.
- * @return {Object} The encoded value
- */
- applyEncoding: function(value) {
- return Ext.encode(value);
- },
- /**
- * Encodes the array of {@link Ext.util.Sorter} objects into a string to be sent in the request url. By default,
- * this simply JSON-encodes the sorter data
- * @param {Ext.util.Sorter[]} sorters The array of {@link Ext.util.Sorter Sorter} objects
- * @return {String} The encoded sorters
- */
- encodeSorters: function(sorters) {
- var min = [],
- length = sorters.length,
- i = 0;
- for (; i < length; i++) {
- min[i] = {
- property : sorters[i].getProperty(),
- direction: sorters[i].getDirection()
- };
- }
- return this.applyEncoding(min);
- },
- /**
- * Encodes the array of {@link Ext.util.Filter} objects into a string to be sent in the request url. By default,
- * this simply JSON-encodes the filter data
- * @param {Ext.util.Filter[]} filters The array of {@link Ext.util.Filter Filter} objects
- * @return {String} The encoded filters
- */
- encodeFilters: function(filters) {
- var min = [],
- length = filters.length,
- i = 0;
- for (; i < length; i++) {
- min[i] = {
- property: filters[i].getProperty(),
- value : filters[i].getValue()
- };
- }
- return this.applyEncoding(min);
- },
- /**
- * @private
- * Copy any sorters, filters etc into the params so they can be sent over the wire
- */
- getParams: function(operation) {
- var me = this,
- params = {},
- grouper = operation.getGrouper(),
- sorters = operation.getSorters(),
- filters = operation.getFilters(),
- page = operation.getPage(),
- start = operation.getStart(),
- limit = operation.getLimit(),
- simpleSortMode = me.getSimpleSortMode(),
- pageParam = me.getPageParam(),
- startParam = me.getStartParam(),
- limitParam = me.getLimitParam(),
- groupParam = me.getGroupParam(),
- sortParam = me.getSortParam(),
- filterParam = me.getFilterParam(),
- directionParam = me.getDirectionParam();
- if (me.getEnablePagingParams()) {
- if (pageParam && page !== null) {
- params[pageParam] = page;
- }
- if (startParam && start !== null) {
- params[startParam] = start;
- }
- if (limitParam && limit !== null) {
- params[limitParam] = limit;
- }
- }
- if (groupParam && grouper) {
- // Grouper is a subclass of sorter, so we can just use the sorter method
- params[groupParam] = me.encodeSorters([grouper]);
- }
- if (sortParam && sorters && sorters.length > 0) {
- if (simpleSortMode) {
- params[sortParam] = sorters[0].getProperty();
- params[directionParam] = sorters[0].getDirection();
- } else {
- params[sortParam] = me.encodeSorters(sorters);
- }
- }
- if (filterParam && filters && filters.length > 0) {
- params[filterParam] = me.encodeFilters(filters);
- }
- return params;
- },
- /**
- * Generates a url based on a given Ext.data.Request object. By default, ServerProxy's buildUrl will add the
- * cache-buster param to the end of the url. Subclasses may need to perform additional modifications to the url.
- * @param {Ext.data.Request} request The request object
- * @return {String} The url
- */
- buildUrl: function(request) {
- var me = this,
- url = me.getUrl(request);
- //<debug>
- if (!url) {
- Ext.Logger.error("You are using a ServerProxy but have not supplied it with a url.");
- }
- //</debug>
- if (me.getNoCache()) {
- url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.getCacheString(), Ext.Date.now()));
- }
- return url;
- },
- /**
- * Get the url for the request taking into account the order of priority,
- * - The request
- * - The api
- * - The url
- * @private
- * @param {Ext.data.Request} request The request
- * @return {String} The url
- */
- getUrl: function(request) {
- return request ? request.getUrl() || this.getApi()[request.getAction()] || this._url : this._url;
- },
- /**
- * In ServerProxy subclasses, the {@link #create}, {@link #read}, {@link #update} and {@link #destroy} methods all
- * pass through to doRequest. Each ServerProxy subclass must implement the doRequest method - see {@link
- * Ext.data.proxy.JsonP} and {@link Ext.data.proxy.Ajax} for examples. This method carries the same signature as
- * each of the methods that delegate to it.
- *
- * @param {Ext.data.Operation} operation The Ext.data.Operation object
- * @param {Function} callback The callback function to call when the Operation has completed
- * @param {Object} scope The scope in which to execute the callback
- * @protected
- * @template
- */
- doRequest: function(operation, callback, scope) {
- //<debug>
- Ext.Logger.error("The doRequest function has not been implemented on your Ext.data.proxy.Server subclass. See src/data/ServerProxy.js for details");
- //</debug>
- },
- /**
- * Optional callback function which can be used to clean up after a request has been completed.
- * @param {Ext.data.Request} request The Request object
- * @param {Boolean} success True if the request was successful
- * @method
- */
- afterRequest: Ext.emptyFn
- });
- /**
- * @author Ed Spencer
- * @aside guide proxies
- *
- * AjaxProxy is one of the most widely-used ways of getting data into your application. It uses AJAX
- * requests to load data from the server, usually to be placed into a {@link Ext.data.Store Store}.
- * Let's take a look at a typical setup. Here we're going to set up a Store that has an AjaxProxy.
- * To prepare, we'll also set up a {@link Ext.data.Model Model}:
- *
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: ['id', 'name', 'email']
- * }
- * });
- *
- * // The Store contains the AjaxProxy as an inline configuration
- * var store = Ext.create('Ext.data.Store', {
- * model: 'User',
- * proxy: {
- * type: 'ajax',
- * url : 'users.json'
- * }
- * });
- *
- * store.load();
- *
- * Our example is going to load user data into a Store, so we start off by defining a
- * {@link Ext.data.Model Model} with the fields that we expect the server to return. Next we set up
- * the Store itself, along with a {@link Ext.data.Store#proxy proxy} configuration. This
- * configuration was automatically turned into an Ext.data.proxy.Ajax instance, with the url we
- * specified being passed into AjaxProxy's constructor. It's as if we'd done this:
- *
- * Ext.create('Ext.data.proxy.Ajax', {
- * config: {
- * url: 'users.json',
- * model: 'User',
- * reader: 'json'
- * }
- * });
- *
- * A couple of extra configurations appeared here - {@link #model} and {@link #reader}. These are
- * set by default when we create the proxy via the Store - the Store already knows about the Model,
- * and Proxy's default {@link Ext.data.reader.Reader Reader} is {@link Ext.data.reader.Json JsonReader}.
- *
- * Now when we call store.load(), the AjaxProxy springs into action, making a request to the url we
- * configured ('users.json' in this case). As we're performing a read, it sends a GET request to
- * that url (see {@link #actionMethods} to customize this - by default any kind of read will be sent
- * as a GET request and any kind of write will be sent as a POST request).
- *
- * ## Limitations
- *
- * AjaxProxy cannot be used to retrieve data from other domains. If your application is running on
- * http://domainA.com it cannot load data from http://domainB.com because browsers have a built-in
- * security policy that prohibits domains talking to each other via AJAX.
- *
- * If you need to read data from another domain and can't set up a proxy server (some software that
- * runs on your own domain's web server and transparently forwards requests to http://domainB.com,
- * making it look like they actually came from http://domainA.com), you can use
- * {@link Ext.data.proxy.JsonP} and a technique known as JSON-P (JSON with Padding), which can help
- * you get around the problem so long as the server on http://domainB.com is set up to support
- * JSON-P responses. See {@link Ext.data.proxy.JsonP JsonPProxy}'s introduction docs for more details.
- *
- * ## Readers and Writers
- *
- * AjaxProxy can be configured to use any type of {@link Ext.data.reader.Reader Reader} to decode
- * the server's response. If no Reader is supplied, AjaxProxy will default to using a
- * {@link Ext.data.reader.Json JsonReader}. Reader configuration can be passed in as a simple
- * object, which the Proxy automatically turns into a {@link Ext.data.reader.Reader Reader} instance:
- *
- * var proxy = Ext.create('Ext.data.proxy.Ajax', {
- * config: {
- * model: 'User',
- * reader: {
- * type: 'xml',
- * root: 'users'
- * }
- * }
- * });
- *
- * proxy.getReader(); //returns an {@link Ext.data.reader.Xml XmlReader} instance based on the config we supplied
- *
- * ## Url generation
- *
- * AjaxProxy automatically inserts any sorting, filtering, paging and grouping options into the url
- * it generates for each request. These are controlled with the following configuration options:
- *
- * - {@link #pageParam} - controls how the page number is sent to the server (see also
- * {@link #startParam} and {@link #limitParam})
- * - {@link #sortParam} - controls how sort information is sent to the server
- * - {@link #groupParam} - controls how grouping information is sent to the server
- * - {@link #filterParam} - controls how filter information is sent to the server
- *
- * Each request sent by AjaxProxy is described by an {@link Ext.data.Operation Operation}. To see
- * how we can customize the generated urls, let's say we're loading the Proxy with the following
- * Operation:
- *
- * var operation = Ext.create('Ext.data.Operation', {
- * action: 'read',
- * page : 2
- * });
- *
- * Now we'll issue the request for this Operation by calling {@link #read}:
- *
- * var proxy = Ext.create('Ext.data.proxy.Ajax', {
- * url: '/users'
- * });
- *
- * proxy.read(operation); // GET /users?page=2
- *
- * Easy enough - the Proxy just copied the page property from the Operation. We can customize how
- * this page data is sent to the server:
- *
- * var proxy = Ext.create('Ext.data.proxy.Ajax', {
- * url: '/users',
- * pageParam: 'pageNumber'
- * });
- *
- * proxy.read(operation); // GET /users?pageNumber=2
- *
- * Alternatively, our Operation could have been configured to send start and limit parameters
- * instead of page:
- *
- * var operation = Ext.create('Ext.data.Operation', {
- * action: 'read',
- * start : 50,
- * limit : 25
- * });
- *
- * var proxy = Ext.create('Ext.data.proxy.Ajax', {
- * url: '/users'
- * });
- *
- * proxy.read(operation); // GET /users?start=50&limit;=25
- *
- * Again we can customize this url:
- *
- * var proxy = Ext.create('Ext.data.proxy.Ajax', {
- * url: '/users',
- * startParam: 'startIndex',
- * limitParam: 'limitIndex'
- * });
- *
- * proxy.read(operation); // GET /users?startIndex=50&limitIndex;=25
- *
- * AjaxProxy will also send sort and filter information to the server. Let's take a look at how this
- * looks with a more expressive Operation object:
- *
- * var operation = Ext.create('Ext.data.Operation', {
- * action: 'read',
- * sorters: [
- * Ext.create('Ext.util.Sorter', {
- * property : 'name',
- * direction: 'ASC'
- * }),
- * Ext.create('Ext.util.Sorter', {
- * property : 'age',
- * direction: 'DESC'
- * })
- * ],
- * filters: [
- * Ext.create('Ext.util.Filter', {
- * property: 'eyeColor',
- * value : 'brown'
- * })
- * ]
- * });
- *
- * This is the type of object that is generated internally when loading a {@link Ext.data.Store Store}
- * with sorters and filters defined. By default the AjaxProxy will JSON encode the sorters and
- * filters, resulting in something like this (note that the url is escaped before sending the
- * request, but is left unescaped here for clarity):
- *
- * var proxy = Ext.create('Ext.data.proxy.Ajax', {
- * url: '/users'
- * });
- *
- * proxy.read(operation); // GET /users?sort=[{"property":"name","direction":"ASC"},{"property":"age","direction":"DESC"}]&filter;=[{"property":"eyeColor","value":"brown"}]
- *
- * We can again customize how this is created by supplying a few configuration options. Let's say
- * our server is set up to receive sorting information is a format like "sortBy=name#ASC,age#DESC".
- * We can configure AjaxProxy to provide that format like this:
- *
- * var proxy = Ext.create('Ext.data.proxy.Ajax', {
- * url: '/users',
- * sortParam: 'sortBy',
- * filterParam: 'filterBy',
- *
- * // our custom implementation of sorter encoding - turns our sorters into "name#ASC,age#DESC"
- * encodeSorters: function(sorters) {
- * var length = sorters.length,
- * sortStrs = [],
- * sorter, i;
- *
- * for (i = 0; i < length; i++) {
- * sorter = sorters[i];
- *
- * sortStrs[i] = sorter.property + '#' + sorter.direction;
- * }
- *
- * return sortStrs.join(",");
- * }
- * });
- *
- * proxy.read(operation); // GET /users?sortBy=name#ASC,age#DESC&filterBy;=[{"property":"eyeColor","value":"brown"}]
- *
- * We can also provide a custom {@link #encodeFilters} function to encode our filters.
- *
- * @constructor
- * Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the Store's
- * call to {@link Ext.data.Store#method-load load} will override any specified callback and params
- * options. In this case, use the {@link Ext.data.Store Store}'s events to modify parameters, or
- * react to loading events.
- *
- * @param {Object} config (optional) Config object.
- * If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to
- * make the request.
- */
- Ext.define('Ext.data.proxy.Ajax', {
- extend: 'Ext.data.proxy.Server',
- requires: ['Ext.util.MixedCollection', 'Ext.Ajax'],
- alias: 'proxy.ajax',
- alternateClassName: ['Ext.data.HttpProxy', 'Ext.data.AjaxProxy'],
- config: {
- /**
- * @cfg {Boolean} withCredentials
- * This configuration is sometimes necessary when using cross-origin resource sharing.
- * @accessor
- */
- withCredentials: false,
- /**
- * @cfg {String} username
- * Most oData feeds require basic HTTP authentication. This configuration allows
- * you to specify the username.
- * @accessor
- */
- username: null,
- /**
- * @cfg {String} password
- * Most oData feeds require basic HTTP authentication. This configuration allows
- * you to specify the password.
- * @accessor
- */
- password: null,
- /**
- * @property {Object} actionMethods
- * Mapping of action name to HTTP request method. In the basic AjaxProxy these are set to
- * 'GET' for 'read' actions and 'POST' for 'create', 'update' and 'destroy' actions.
- * The {@link Ext.data.proxy.Rest} maps these to the correct RESTful methods.
- */
- actionMethods: {
- create : 'POST',
- read : 'GET',
- update : 'POST',
- destroy: 'POST'
- },
- /**
- * @cfg {Object} [headers=undefined]
- * Any headers to add to the Ajax request.
- */
- headers: {}
- },
- /**
- * Performs Ajax request.
- * @protected
- * @param operation
- * @param callback
- * @param scope
- * @return {Object}
- */
- doRequest: function(operation, callback, scope) {
- var me = this,
- writer = me.getWriter(),
- request = me.buildRequest(operation);
- request.setConfig({
- headers : me.getHeaders(),
- timeout : me.getTimeout(),
- method : me.getMethod(request),
- callback : me.createRequestCallback(request, operation, callback, scope),
- scope : me,
- proxy : me
- });
- if (operation.getWithCredentials() || me.getWithCredentials()) {
- request.setWithCredentials(true);
- request.setUsername(me.getUsername());
- request.setPassword(me.getPassword());
- }
- // We now always have the writer prepare the request
- request = writer.write(request);
- Ext.Ajax.request(request.getCurrentConfig());
- return request;
- },
- /**
- * Returns the HTTP method name for a given request. By default this returns based on a lookup on
- * {@link #actionMethods}.
- * @param {Ext.data.Request} request The request object.
- * @return {String} The HTTP method to use (should be one of 'GET', 'POST', 'PUT' or 'DELETE').
- */
- getMethod: function(request) {
- return this.getActionMethods()[request.getAction()];
- },
- /**
- * @private
- * @param {Ext.data.Request} request The Request object.
- * @param {Ext.data.Operation} operation The Operation being executed.
- * @param {Function} callback The callback function to be called when the request completes.
- * This is usually the callback passed to `doRequest`.
- * @param {Object} scope The scope in which to execute the callback function.
- * @return {Function} The callback function.
- */
- createRequestCallback: function(request, operation, callback, scope) {
- var me = this;
- return function(options, success, response) {
- me.processResponse(success, operation, request, response, callback, scope);
- };
- }
- });
- /**
- * @author Ed Spencer
- * @aside guide models
- *
- * Associations enable you to express relationships between different {@link Ext.data.Model Models}. Let's say we're
- * writing an ecommerce system where Users can make Orders - there's a relationship between these Models that we can
- * express like this:
- *
- * Ext.define('MyApp.model.User', {
- * extend: 'Ext.data.Model',
- *
- * config: {
- * fields: ['id', 'name', 'email'],
- * hasMany: {
- * model: 'MyApp.model.Order',
- * name: 'orders'
- * }
- * }
- * });
- *
- * Ext.define('MyApp.model.Order', {
- * extend: 'Ext.data.Model',
- *
- * config: {
- * fields: ['id', 'user_id', 'status', 'price'],
- * belongsTo: 'MyApp.model.User'
- * }
- * });
- *
- * We've set up two models - User and Order - and told them about each other. You can set up as many associations on
- * each Model as you need using the two default types - {@link Ext.data.association.HasMany hasMany} and
- * {@link Ext.data.association.BelongsTo belongsTo}. There's much more detail on the usage of each of those inside their
- * documentation pages. If you're not familiar with Models already, {@link Ext.data.Model there is plenty on those too}.
- *
- * ## Further Reading
- *
- * - {@link Ext.data.association.HasMany hasMany associations}
- * - {@link Ext.data.association.BelongsTo belongsTo associations}
- * - {@link Ext.data.association.HasOne hasOne associations}
- * - {@link Ext.data.Model using Models}
- *
- * ### Self-associating Models
- *
- * We can also have models that create parent/child associations between the same type. Below is an example, where
- * groups can be nested inside other groups:
- *
- * // Server Data
- * {
- * "groups": {
- * "id": 10,
- * "parent_id": 100,
- * "name": "Main Group",
- * "parent_group": {
- * "id": 100,
- * "parent_id": null,
- * "name": "Parent Group"
- * },
- * "nested" : {
- * "child_groups": [{
- * "id": 2,
- * "parent_id": 10,
- * "name": "Child Group 1"
- * },{
- * "id": 3,
- * "parent_id": 10,
- * "name": "Child Group 2"
- * },{
- * "id": 4,
- * "parent_id": 10,
- * "name": "Child Group 3"
- * }]
- * }
- * }
- * }
- *
- * // Client code
- * Ext.define('MyApp.model.Group', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: ['id', 'parent_id', 'name'],
- * proxy: {
- * type: 'ajax',
- * url: 'data.json',
- * reader: {
- * type: 'json',
- * root: 'groups'
- * }
- * },
- * associations: [{
- * type: 'hasMany',
- * model: 'MyApp.model.Group',
- * primaryKey: 'id',
- * foreignKey: 'parent_id',
- * autoLoad: true,
- * associationKey: 'nested.child_groups' // read child data from nested.child_groups
- * }, {
- * type: 'belongsTo',
- * model: 'MyApp.model.Group',
- * primaryKey: 'id',
- * foreignKey: 'parent_id',
- * associationKey: 'parent_group' // read parent data from parent_group
- * }]
- * }
- * });
- *
- *
- * Ext.onReady(function(){
- * MyApp.model.Group.load(10, {
- * success: function(group){
- * console.log(group.getGroup().get('name'));
- *
- * group.groups().each(function(rec){
- * console.log(rec.get('name'));
- * });
- * }
- * });
- *
- * });
- */
- Ext.define('Ext.data.association.Association', {
- alternateClassName: 'Ext.data.Association',
- requires: ['Ext.data.ModelManager'],
- config: {
- /**
- * @cfg {Ext.data.Model/String} ownerModel (required) The full class name or reference to the class that owns this
- * associations. This is a required configuration on every association.
- * @accessor
- */
- ownerModel: null,
- /*
- * @cfg {String} ownerName The name for the owner model. This defaults to the last part
- * of the class name of the {@link #ownerModel}.
- */
- ownerName: undefined,
- /**
- * @cfg {String} associatedModel (required) The full class name or reference to the class that the {@link #ownerModel}
- * is being associated with. This is a required configuration on every association.
- * @accessor
- */
- associatedModel: null,
- /**
- * @cfg {String} associatedName The name for the associated model. This defaults to the last part
- * of the class name of the {@link #associatedModel}.
- * @accessor
- */
- associatedName: undefined,
- /**
- * @cfg {String} associationKey The name of the property in the data to read the association from.
- * Defaults to the {@link #associatedName} plus '_id'.
- */
- associationKey: undefined,
- /**
- * @cfg {String} primaryKey The name of the primary key on the associated model.
- * In general this will be the {@link Ext.data.Model#idProperty} of the Model.
- */
- primaryKey: 'id',
- /**
- * @cfg {Ext.data.reader.Reader} reader A special reader to read associated data.
- */
- reader: null,
- /**
- * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
- * Use `hasMany` to create a HasMany association.
- *
- * associations: [{
- * type: 'hasMany',
- * model: 'User'
- * }]
- */
- type: null,
- name: undefined
- },
- statics: {
- create: function(association) {
- if (!association.isAssociation) {
- if (Ext.isString(association)) {
- association = {
- type: association
- };
- }
- association.type = association.type.toLowerCase();
- return Ext.factory(association, Ext.data.association.Association, null, 'association');
- }
- return association;
- }
- },
- /**
- * Creates the Association object.
- * @param {Object} config (optional) Config object.
- */
- constructor: function(config) {
- this.initConfig(config);
- },
- applyName: function(name) {
- if (!name) {
- name = this.getAssociatedName();
- }
- return name;
- },
- applyOwnerModel: function(ownerName) {
- var ownerModel = Ext.data.ModelManager.getModel(ownerName);
- if (ownerModel === undefined) {
- Ext.Logger.error('The configured ownerModel was not valid (you tried ' + ownerName + ')');
- }
- return ownerModel;
- },
- applyOwnerName: function(ownerName) {
- if (!ownerName) {
- ownerName = this.getOwnerModel().modelName;
- }
- ownerName = ownerName.slice(ownerName.lastIndexOf('.')+1);
- return ownerName;
- },
- updateOwnerModel: function(ownerModel, oldOwnerModel) {
- if (oldOwnerModel) {
- this.setOwnerName(ownerModel.modelName);
- }
- },
- applyAssociatedModel: function(associatedName) {
- var associatedModel = Ext.data.ModelManager.types[associatedName];
- if (associatedModel === undefined) {
- Ext.Logger.error('The configured associatedModel was not valid (you tried ' + associatedName + ')');
- }
- return associatedModel;
- },
- applyAssociatedName: function(associatedName) {
- if (!associatedName) {
- associatedName = this.getAssociatedModel().modelName;
- }
- associatedName = associatedName.slice(associatedName.lastIndexOf('.')+1);
- return associatedName;
- },
- updateAssociatedModel: function(associatedModel, oldAssociatedModel) {
- if (oldAssociatedModel) {
- this.setAssociatedName(associatedModel.modelName);
- }
- },
- applyReader: function(reader) {
- if (reader) {
- if (Ext.isString(reader)) {
- reader = {
- type: reader
- };
- }
- if (!reader.isReader) {
- Ext.applyIf(reader, {
- type: 'json'
- });
- }
- }
- return Ext.factory(reader, Ext.data.Reader, this.getReader(), 'reader');
- },
- updateReader: function(reader) {
- reader.setModel(this.getAssociatedModel());
- }
- // Convert old properties in data into a config object
- });
- /**
- * General purpose inflector class that {@link #pluralize pluralizes}, {@link #singularize singularizes} and
- * {@link #ordinalize ordinalizes} words. Sample usage:
- *
- * // turning singular words into plurals
- * Ext.util.Inflector.pluralize('word'); // 'words'
- * Ext.util.Inflector.pluralize('person'); // 'people'
- * Ext.util.Inflector.pluralize('sheep'); // 'sheep'
- *
- * // turning plurals into singulars
- * Ext.util.Inflector.singularize('words'); // 'word'
- * Ext.util.Inflector.singularize('people'); // 'person'
- * Ext.util.Inflector.singularize('sheep'); // 'sheep'
- *
- * // ordinalizing numbers
- * Ext.util.Inflector.ordinalize(11); // "11th"
- * Ext.util.Inflector.ordinalize(21); // "21st"
- * Ext.util.Inflector.ordinalize(1043); // "1043rd"
- *
- * ## Customization
- *
- * The Inflector comes with a default set of US English pluralization rules. These can be augmented with additional
- * rules if the default rules do not meet your application's requirements, or swapped out entirely for other languages.
- * Here is how we might add a rule that pluralizes "ox" to "oxen":
- *
- * Ext.util.Inflector.plural(/^(ox)$/i, "$1en");
- *
- * Each rule consists of two items - a regular expression that matches one or more rules, and a replacement string.
- * In this case, the regular expression will only match the string "ox", and will replace that match with "oxen".
- * Here's how we could add the inverse rule:
- *
- * Ext.util.Inflector.singular(/^(ox)en$/i, "$1");
- *
- * __Note:__ The ox/oxen rules are present by default.
- */
- Ext.define('Ext.util.Inflector', {
- /* Begin Definitions */
- singleton: true,
- /* End Definitions */
- /**
- * @private
- * The registered plural tuples. Each item in the array should contain two items - the first must be a regular
- * expression that matchers the singular form of a word, the second must be a String that replaces the matched
- * part of the regular expression. This is managed by the {@link #plural} method.
- * @property plurals
- * @type Array
- */
- plurals: [
- [(/(quiz)$/i), "$1zes" ],
- [(/^(ox)$/i), "$1en" ],
- [(/([m|l])ouse$/i), "$1ice" ],
- [(/(matr|vert|ind)ix|ex$/i), "$1ices" ],
- [(/(x|ch|ss|sh)$/i), "$1es" ],
- [(/([^aeiouy]|qu)y$/i), "$1ies" ],
- [(/(hive)$/i), "$1s" ],
- [(/(?:([^f])fe|([lr])f)$/i), "$1$2ves"],
- [(/sis$/i), "ses" ],
- [(/([ti])um$/i), "$1a" ],
- [(/(buffal|tomat|potat)o$/i), "$1oes" ],
- [(/(bu)s$/i), "$1ses" ],
- [(/(alias|status|sex)$/i), "$1es" ],
- [(/(octop|vir)us$/i), "$1i" ],
- [(/(ax|test)is$/i), "$1es" ],
- [(/^person$/), "people" ],
- [(/^man$/), "men" ],
- [(/^(child)$/), "$1ren" ],
- [(/s$/i), "s" ],
- [(/$/), "s" ]
- ],
-
- /**
- * @private
- * The set of registered singular matchers. Each item in the array should contain two items - the first must be a
- * regular expression that matches the plural form of a word, the second must be a String that replaces the
- * matched part of the regular expression. This is managed by the {@link #singular} method.
- * @property singulars
- * @type Array
- */
- singulars: [
- [(/(quiz)zes$/i), "$1" ],
- [(/(matr)ices$/i), "$1ix" ],
- [(/(vert|ind)ices$/i), "$1ex" ],
- [(/^(ox)en/i), "$1" ],
- [(/(alias|status)es$/i), "$1" ],
- [(/(octop|vir)i$/i), "$1us" ],
- [(/(cris|ax|test)es$/i), "$1is" ],
- [(/(shoe)s$/i), "$1" ],
- [(/(o)es$/i), "$1" ],
- [(/(bus)es$/i), "$1" ],
- [(/([m|l])ice$/i), "$1ouse" ],
- [(/(x|ch|ss|sh)es$/i), "$1" ],
- [(/(m)ovies$/i), "$1ovie" ],
- [(/(s)eries$/i), "$1eries"],
- [(/([^aeiouy]|qu)ies$/i), "$1y" ],
- [(/([lr])ves$/i), "$1f" ],
- [(/(tive)s$/i), "$1" ],
- [(/(hive)s$/i), "$1" ],
- [(/([^f])ves$/i), "$1fe" ],
- [(/(^analy)ses$/i), "$1sis" ],
- [(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i), "$1$2sis"],
- [(/([ti])a$/i), "$1um" ],
- [(/(n)ews$/i), "$1ews" ],
- [(/people$/i), "person" ],
- [(/s$/i), "" ]
- ],
-
- /**
- * @private
- * The registered uncountable words
- * @property uncountable
- * @type Array
- */
- uncountable: [
- "sheep",
- "fish",
- "series",
- "species",
- "money",
- "rice",
- "information",
- "equipment",
- "grass",
- "mud",
- "offspring",
- "deer",
- "means"
- ],
-
- /**
- * Adds a new singularization rule to the Inflector. See the intro docs for more information
- * @param {RegExp} matcher The matcher regex
- * @param {String} replacer The replacement string, which can reference matches from the matcher argument
- */
- singular: function(matcher, replacer) {
- this.singulars.unshift([matcher, replacer]);
- },
-
- /**
- * Adds a new pluralization rule to the Inflector. See the intro docs for more information
- * @param {RegExp} matcher The matcher regex
- * @param {String} replacer The replacement string, which can reference matches from the matcher argument
- */
- plural: function(matcher, replacer) {
- this.plurals.unshift([matcher, replacer]);
- },
-
- /**
- * Removes all registered singularization rules
- */
- clearSingulars: function() {
- this.singulars = [];
- },
-
- /**
- * Removes all registered pluralization rules
- */
- clearPlurals: function() {
- this.plurals = [];
- },
-
- /**
- * Returns true if the given word is transnumeral (the word is its own singular and plural form - e.g. sheep, fish)
- * @param {String} word The word to test
- * @return {Boolean} True if the word is transnumeral
- */
- isTransnumeral: function(word) {
- return Ext.Array.indexOf(this.uncountable, word) != -1;
- },
- /**
- * Returns the pluralized form of a word (e.g. Ext.util.Inflector.pluralize('word') returns 'words')
- * @param {String} word The word to pluralize
- * @return {String} The pluralized form of the word
- */
- pluralize: function(word) {
- if (this.isTransnumeral(word)) {
- return word;
- }
- var plurals = this.plurals,
- length = plurals.length,
- tuple, regex, i;
-
- for (i = 0; i < length; i++) {
- tuple = plurals[i];
- regex = tuple[0];
-
- if (regex == word || (regex.test && regex.test(word))) {
- return word.replace(regex, tuple[1]);
- }
- }
-
- return word;
- },
-
- /**
- * Returns the singularized form of a word (e.g. Ext.util.Inflector.singularize('words') returns 'word')
- * @param {String} word The word to singularize
- * @return {String} The singularized form of the word
- */
- singularize: function(word) {
- if (this.isTransnumeral(word)) {
- return word;
- }
- var singulars = this.singulars,
- length = singulars.length,
- tuple, regex, i;
-
- for (i = 0; i < length; i++) {
- tuple = singulars[i];
- regex = tuple[0];
-
- if (regex == word || (regex.test && regex.test(word))) {
- return word.replace(regex, tuple[1]);
- }
- }
-
- return word;
- },
-
- /**
- * Returns the correct {@link Ext.data.Model Model} name for a given string. Mostly used internally by the data
- * package
- * @param {String} word The word to classify
- * @return {String} The classified version of the word
- */
- classify: function(word) {
- return Ext.String.capitalize(this.singularize(word));
- },
-
- /**
- * Ordinalizes a given number by adding a prefix such as 'st', 'nd', 'rd' or 'th' based on the last digit of the
- * number. 21 -> 21st, 22 -> 22nd, 23 -> 23rd, 24 -> 24th etc
- * @param {Number} number The number to ordinalize
- * @return {String} The ordinalized number
- */
- ordinalize: function(number) {
- var parsed = parseInt(number, 10),
- mod10 = parsed % 10,
- mod100 = parsed % 100;
-
- //11 through 13 are a special case
- if (11 <= mod100 && mod100 <= 13) {
- return number + "th";
- } else {
- switch(mod10) {
- case 1 : return number + "st";
- case 2 : return number + "nd";
- case 3 : return number + "rd";
- default: return number + "th";
- }
- }
- }
- }, function() {
- //aside from the rules above, there are a number of words that have irregular pluralization so we add them here
- var irregulars = {
- alumnus: 'alumni',
- cactus : 'cacti',
- focus : 'foci',
- nucleus: 'nuclei',
- radius: 'radii',
- stimulus: 'stimuli',
- ellipsis: 'ellipses',
- paralysis: 'paralyses',
- oasis: 'oases',
- appendix: 'appendices',
- index: 'indexes',
- beau: 'beaux',
- bureau: 'bureaux',
- tableau: 'tableaux',
- woman: 'women',
- child: 'children',
- man: 'men',
- corpus: 'corpora',
- criterion: 'criteria',
- curriculum: 'curricula',
- genus: 'genera',
- memorandum: 'memoranda',
- phenomenon: 'phenomena',
- foot: 'feet',
- goose: 'geese',
- tooth: 'teeth',
- antenna: 'antennae',
- formula: 'formulae',
- nebula: 'nebulae',
- vertebra: 'vertebrae',
- vita: 'vitae'
- },
- singular;
-
- for (singular in irregulars) {
- this.plural(singular, irregulars[singular]);
- this.singular(irregulars[singular], singular);
- }
- });
- /**
- * @aside guide models
- *
- * Represents a one-to-many relationship between two models. Usually created indirectly via a model definition:
- *
- * Ext.define('Product', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * {name: 'id', type: 'int'},
- * {name: 'user_id', type: 'int'},
- * {name: 'name', type: 'string'}
- * ]
- * }
- * });
- *
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * {name: 'id', type: 'int'},
- * {name: 'name', type: 'string'}
- * ],
- * // we can use the hasMany shortcut on the model to create a hasMany association
- * hasMany: {model: 'Product', name: 'products'}
- * }
- * });
- *
- * `
- *
- * Above we created Product and User models, and linked them by saying that a User hasMany Products. This gives us a new
- * function on every User instance, in this case the function is called 'products' because that is the name we specified
- * in the association configuration above.
- *
- * This new function returns a specialized {@link Ext.data.Store Store} which is automatically filtered to load only
- * Products for the given model instance:
- *
- * //first, we load up a User with id of 1
- * var user = Ext.create('User', {id: 1, name: 'Ed'});
- *
- * //the user.products function was created automatically by the association and returns a {@link Ext.data.Store Store}
- * //the created store is automatically scoped to the set of Products for the User with id of 1
- * var products = user.products();
- *
- * //we still have all of the usual Store functions, for example it's easy to add a Product for this User
- * products.add({
- * name: 'Another Product'
- * });
- *
- * //saves the changes to the store - this automatically sets the new Product's user_id to 1 before saving
- * products.sync();
- *
- * The new Store is only instantiated the first time you call products() to conserve memory and processing time, though
- * calling products() a second time returns the same store instance.
- *
- * _Custom filtering_
- *
- * The Store is automatically furnished with a filter - by default this filter tells the store to only return records
- * where the associated model's foreign key matches the owner model's primary key. For example, if a User with ID = 100
- * hasMany Products, the filter loads only Products with user_id == 100.
- *
- * Sometimes we want to filter by another field - for example in the case of a Twitter search application we may have
- * models for Search and Tweet:
- *
- * Ext.define('Search', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * 'id', 'query'
- * ],
- *
- * hasMany: {
- * model: 'Tweet',
- * name : 'tweets',
- * filterProperty: 'query'
- * }
- * }
- * });
- *
- * Ext.define('Tweet', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * 'id', 'text', 'from_user'
- * ]
- * }
- * });
- *
- * //returns a Store filtered by the filterProperty
- * var store = new Search({query: 'Sencha Touch'}).tweets();
- *
- * The tweets association above is filtered by the query property by setting the {@link #filterProperty}, and is
- * equivalent to this:
- *
- * var store = Ext.create('Ext.data.Store', {
- * model: 'Tweet',
- * filters: [
- * {
- * property: 'query',
- * value : 'Sencha Touch'
- * }
- * ]
- * });
- */
- Ext.define('Ext.data.association.HasMany', {
- extend: 'Ext.data.association.Association',
- alternateClassName: 'Ext.data.HasManyAssociation',
- requires: ['Ext.util.Inflector'],
- alias: 'association.hasmany',
- config: {
- /**
- * @cfg {String} foreignKey
- * The name of the foreign key on the associated model that links it to the owner model. Defaults to the
- * lowercased name of the owner model plus "_id", e.g. an association with a model called Group hasMany Users
- * would create 'group_id' as the foreign key. When the remote store is loaded, the store is automatically
- * filtered so that only records with a matching foreign key are included in the resulting child store. This can
- * be overridden by specifying the {@link #filterProperty}.
- *
- * Ext.define('Group', {
- * extend: 'Ext.data.Model',
- * fields: ['id', 'name'],
- * hasMany: 'User'
- * });
- *
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- * fields: ['id', 'name', 'group_id'], // refers to the id of the group that this user belongs to
- * belongsTo: 'Group'
- * });
- */
- foreignKey: undefined,
- /**
- * @cfg {String} name
- * The name of the function to create on the owner model to retrieve the child store. If not specified, the
- * pluralized name of the child model is used.
- *
- * // This will create a users() method on any Group model instance
- * Ext.define('Group', {
- * extend: 'Ext.data.Model',
- * fields: ['id', 'name'],
- * hasMany: 'User'
- * });
- * var group = new Group();
- * console.log(group.users());
- *
- * // The method to retrieve the users will now be getUserList
- * Ext.define('Group', {
- * extend: 'Ext.data.Model',
- * fields: ['id', 'name'],
- * hasMany: {model: 'User', name: 'getUserList'}
- * });
- * var group = new Group();
- * console.log(group.getUserList());
- */
- /**
- * @cfg {Object} store
- * Optional configuration object that will be passed to the generated Store. Defaults to an empty Object.
- */
- store: undefined,
- /**
- * @cfg {String} storeName
- * Optional The name of the store by which you can reference it on this class as a property.
- */
- storeName: undefined,
- /**
- * @cfg {String} filterProperty
- * Optionally overrides the default filter that is set up on the associated Store. If this is not set, a filter
- * is automatically created which filters the association based on the configured {@link #foreignKey}. See intro
- * docs for more details.
- */
- filterProperty: null,
- /**
- * @cfg {Boolean} autoLoad
- * `true` to automatically load the related store from a remote source when instantiated.
- */
- autoLoad: false,
- /**
- * @cfg {Boolean} autoSync
- * true to automatically synchronize the related store with the remote source
- */
- autoSync: false
- },
- constructor: function(config) {
- config = config || {};
- if (config.storeConfig) {
- // <debug>
- Ext.Logger.warn('storeConfig is deprecated on an association. Instead use the store configuration.');
- // </debug>
- config.store = config.storeConfig;
- delete config.storeConfig;
- }
- this.callParent([config]);
- },
- applyName: function(name) {
- if (!name) {
- name = Ext.util.Inflector.pluralize(this.getAssociatedName().toLowerCase());
- }
- return name;
- },
- applyStoreName: function(name) {
- if (!name) {
- name = this.getName() + 'Store';
- }
- return name;
- },
- applyForeignKey: function(foreignKey) {
- if (!foreignKey) {
- var inverse = this.getInverseAssociation();
- if (inverse) {
- foreignKey = inverse.getForeignKey();
- } else {
- foreignKey = this.getOwnerName().toLowerCase() + '_id';
- }
- }
- return foreignKey;
- },
- applyAssociationKey: function(associationKey) {
- if (!associationKey) {
- var associatedName = this.getAssociatedName();
- associationKey = Ext.util.Inflector.pluralize(associatedName[0].toLowerCase() + associatedName.slice(1));
- }
- return associationKey;
- },
- updateForeignKey: function(foreignKey, oldForeignKey) {
- var fields = this.getAssociatedModel().getFields(),
- field = fields.get(foreignKey);
- if (!field) {
- field = new Ext.data.Field({
- name: foreignKey
- });
- fields.add(field);
- fields.isDirty = true;
- }
- if (oldForeignKey) {
- field = fields.get(oldForeignKey);
- if (field) {
- fields.remove(field);
- fields.isDirty = true;
- }
- }
- },
- /**
- * @private
- * Creates a function that returns an Ext.data.Store which is configured to load a set of data filtered
- * by the owner model's primary key - e.g. in a `hasMany` association where Group `hasMany` Users, this function
- * returns a Store configured to return the filtered set of a single Group's Users.
- * @return {Function} The store-generating function.
- */
- applyStore: function(storeConfig) {
- var me = this,
- association = me,
- associatedModel = me.getAssociatedModel(),
- storeName = me.getStoreName(),
- foreignKey = me.getForeignKey(),
- primaryKey = me.getPrimaryKey(),
- filterProperty = me.getFilterProperty(),
- autoLoad = me.getAutoLoad(),
- autoSync = me.getAutoSync();
- return function() {
- var record = this,
- config, filter, store,
- modelDefaults = {},
- listeners = {
- addrecords: me.onAddRecords,
- removerecords: me.onRemoveRecords,
- scope: me
- };
- if (record[storeName] === undefined) {
- if (filterProperty) {
- filter = {
- property : filterProperty,
- value : record.get(filterProperty),
- exactMatch: true
- };
- } else {
- filter = {
- property : foreignKey,
- value : record.get(primaryKey),
- exactMatch: true
- };
- }
- modelDefaults[foreignKey] = record.get(primaryKey);
- config = Ext.apply({}, storeConfig, {
- model : associatedModel,
- filters : [filter],
- remoteFilter : true,
- autoSync : autoSync,
- modelDefaults: modelDefaults,
- listeners : listeners
- });
- store = record[storeName] = Ext.create('Ext.data.Store', config);
- store.boundTo = record;
- if (autoLoad) {
- record[storeName].load();
- }
- }
- return record[storeName];
- };
- },
- onAddRecords: function(store, records) {
- var ln = records.length,
- id = store.boundTo.getId(),
- i, record;
- for (i = 0; i < ln; i++) {
- record = records[i];
- record.set(this.getForeignKey(), id);
- }
- this.updateInverseInstances(store.boundTo);
- },
- onRemoveRecords: function(store, records) {
- var ln = records.length,
- i, record;
- for (i = 0; i < ln; i++) {
- record = records[i];
- record.set(this.getForeignKey(), null);
- }
- },
- updateStore: function(store) {
- this.getOwnerModel().prototype[this.getName()] = store;
- },
- /**
- * Read associated data
- * @private
- * @param {Ext.data.Model} record The record we're writing to.
- * @param {Ext.data.reader.Reader} reader The reader for the associated model.
- * @param {Object} associationData The raw associated data.
- */
- read: function(record, reader, associationData) {
- var store = record[this.getName()](),
- records = reader.read(associationData).getRecords();
- store.add(records);
- },
- updateInverseInstances: function(record) {
- var store = record[this.getName()](),
- inverse = this.getInverseAssociation();
- //if the inverse association was found, set it now on each record we've just created
- if (inverse) {
- store.each(function(associatedRecord) {
- associatedRecord[inverse.getInstanceName()] = record;
- });
- }
- },
- getInverseAssociation: function() {
- var ownerName = this.getOwnerModel().modelName;
- //now that we've added the related records to the hasMany association, set the inverse belongsTo
- //association on each of them if it exists
- return this.getAssociatedModel().associations.findBy(function(assoc) {
- return assoc.getType().toLowerCase() === 'belongsto' && assoc.getAssociatedModel().modelName === ownerName;
- });
- }
- });
- /**
- * @author Ed Spencer
- * @aside guide models
- *
- * Represents a many to one association with another model. The owner model is expected to have
- * a foreign key which references the primary key of the associated model:
- *
- * Ext.define('Category', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * { name: 'id', type: 'int' },
- * { name: 'name', type: 'string' }
- * ]
- * }
- * });
- *
- * Ext.define('Product', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * { name: 'id', type: 'int' },
- * { name: 'category_id', type: 'int' },
- * { name: 'name', type: 'string' }
- * ],
- * // we can use the belongsTo shortcut on the model to create a belongsTo association
- * associations: { type: 'belongsTo', model: 'Category' }
- * }
- * });
- *
- * In the example above we have created models for Products and Categories, and linked them together
- * by saying that each Product belongs to a Category. This automatically links each Product to a Category
- * based on the Product's category_id, and provides new functions on the Product model:
- *
- * ## Generated getter function
- *
- * The first function that is added to the owner model is a getter function:
- *
- * var product = new Product({
- * id: 100,
- * category_id: 20,
- * name: 'Sneakers'
- * });
- *
- * product.getCategory(function(category, operation) {
- * // do something with the category object
- * alert(category.get('id')); // alerts 20
- * }, this);
- *
- * The getCategory function was created on the Product model when we defined the association. This uses the
- * Category's configured {@link Ext.data.proxy.Proxy proxy} to load the Category asynchronously, calling the provided
- * callback when it has loaded.
- *
- * The new getCategory function will also accept an object containing success, failure and callback properties
- * - callback will always be called, success will only be called if the associated model was loaded successfully
- * and failure will only be called if the associated model could not be loaded:
- *
- * product.getCategory({
- * reload: true, // force a reload if the owner model is already cached
- * callback: function(category, operation) {}, // a function that will always be called
- * success : function(category, operation) {}, // a function that will only be called if the load succeeded
- * failure : function(category, operation) {}, // a function that will only be called if the load did not succeed
- * scope : this // optionally pass in a scope object to execute the callbacks in
- * });
- *
- * In each case above the callbacks are called with two arguments - the associated model instance and the
- * {@link Ext.data.Operation operation} object that was executed to load that instance. The Operation object is
- * useful when the instance could not be loaded.
- *
- * Once the getter has been called on the model, it will be cached if the getter is called a second time. To
- * force the model to reload, specify reload: true in the options object.
- *
- * ## Generated setter function
- *
- * The second generated function sets the associated model instance - if only a single argument is passed to
- * the setter then the following two calls are identical:
- *
- * // this call...
- * product.setCategory(10);
- *
- * // is equivalent to this call:
- * product.set('category_id', 10);
- *
- * An instance of the owner model can also be passed as a parameter.
- *
- * If we pass in a second argument, the model will be automatically saved and the second argument passed to
- * the owner model's {@link Ext.data.Model#save save} method:
- *
- * product.setCategory(10, function(product, operation) {
- * // the product has been saved
- * alert(product.get('category_id')); //now alerts 10
- * });
- *
- * //alternative syntax:
- * product.setCategory(10, {
- * callback: function(product, operation) {}, // a function that will always be called
- * success : function(product, operation) {}, // a function that will only be called if the load succeeded
- * failure : function(product, operation) {}, // a function that will only be called if the load did not succeed
- * scope : this //optionally pass in a scope object to execute the callbacks in
- * });
- *
- * ## Customization
- *
- * Associations reflect on the models they are linking to automatically set up properties such as the
- * {@link #primaryKey} and {@link #foreignKey}. These can alternatively be specified:
- *
- * Ext.define('Product', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * // ...
- * ],
- *
- * associations: [
- * { type: 'belongsTo', model: 'Category', primaryKey: 'unique_id', foreignKey: 'cat_id' }
- * ]
- * }
- * });
- *
- * Here we replaced the default primary key (defaults to 'id') and foreign key (calculated as 'category_id')
- * with our own settings. Usually this will not be needed.
- */
- Ext.define('Ext.data.association.BelongsTo', {
- extend: 'Ext.data.association.Association',
- alternateClassName: 'Ext.data.BelongsToAssociation',
- alias: 'association.belongsto',
- config: {
- /**
- * @cfg {String} foreignKey The name of the foreign key on the owner model that links it to the associated
- * model. Defaults to the lowercased name of the associated model plus "_id", e.g. an association with a
- * model called Product would set up a product_id foreign key.
- *
- * Ext.define('Order', {
- * extend: 'Ext.data.Model',
- * fields: ['id', 'date'],
- * hasMany: 'Product'
- * });
- *
- * Ext.define('Product', {
- * extend: 'Ext.data.Model',
- * fields: ['id', 'name', 'order_id'], // refers to the id of the order that this product belongs to
- * belongsTo: 'Group'
- * });
- * var product = new Product({
- * id: 1,
- * name: 'Product 1',
- * order_id: 22
- * }, 1);
- * product.getOrder(); // Will make a call to the server asking for order_id 22
- *
- */
- foreignKey: undefined,
- /**
- * @cfg {String} getterName The name of the getter function that will be added to the local model's prototype.
- * Defaults to 'get' + the name of the foreign model, e.g. getCategory
- */
- getterName: undefined,
- /**
- * @cfg {String} setterName The name of the setter function that will be added to the local model's prototype.
- * Defaults to 'set' + the name of the foreign model, e.g. setCategory
- */
- setterName: undefined,
- instanceName: undefined
- },
- applyForeignKey: function(foreignKey) {
- if (!foreignKey) {
- foreignKey = this.getAssociatedName().toLowerCase() + '_id';
- }
- return foreignKey;
- },
- updateForeignKey: function(foreignKey, oldForeignKey) {
- var fields = this.getOwnerModel().getFields(),
- field = fields.get(foreignKey);
- if (!field) {
- field = new Ext.data.Field({
- name: foreignKey
- });
- fields.add(field);
- fields.isDirty = true;
- }
- if (oldForeignKey) {
- field = fields.get(oldForeignKey);
- if (field) {
- fields.isDirty = true;
- fields.remove(field);
- }
- }
- },
- applyInstanceName: function(instanceName) {
- if (!instanceName) {
- instanceName = this.getAssociatedName() + 'BelongsToInstance';
- }
- return instanceName;
- },
- applyAssociationKey: function(associationKey) {
- if (!associationKey) {
- var associatedName = this.getAssociatedName();
- associationKey = associatedName[0].toLowerCase() + associatedName.slice(1);
- }
- return associationKey;
- },
- applyGetterName: function(getterName) {
- if (!getterName) {
- var associatedName = this.getAssociatedName();
- getterName = 'get' + associatedName[0].toUpperCase() + associatedName.slice(1);
- }
- return getterName;
- },
- applySetterName: function(setterName) {
- if (!setterName) {
- var associatedName = this.getAssociatedName();
- setterName = 'set' + associatedName[0].toUpperCase() + associatedName.slice(1);
- }
- return setterName;
- },
- updateGetterName: function(getterName, oldGetterName) {
- var ownerProto = this.getOwnerModel().prototype;
- if (oldGetterName) {
- delete ownerProto[oldGetterName];
- }
- if (getterName) {
- ownerProto[getterName] = this.createGetter();
- }
- },
- updateSetterName: function(setterName, oldSetterName) {
- var ownerProto = this.getOwnerModel().prototype;
- if (oldSetterName) {
- delete ownerProto[oldSetterName];
- }
- if (setterName) {
- ownerProto[setterName] = this.createSetter();
- }
- },
- /**
- * @private
- * Returns a setter function to be placed on the owner model's prototype
- * @return {Function} The setter function
- */
- createSetter: function() {
- var me = this,
- foreignKey = me.getForeignKey(),
- associatedModel = me.getAssociatedModel(),
- currentOwner, newOwner, store;
- //'this' refers to the Model instance inside this function
- return function(value, options, scope) {
- var inverse = me.getInverseAssociation(),
- record = this;
- // If we pass in an instance, pull the id out
- if (value && value.isModel) {
- value = value.getId();
- }
- if (Ext.isFunction(options)) {
- options = {
- callback: options,
- scope: scope || record
- };
- }
- // Remove the current belongsToInstance
- delete record[me.getInstanceName()];
- currentOwner = Ext.data.Model.cache[Ext.data.Model.generateCacheId(associatedModel.modelName, this.get(foreignKey))];
- newOwner = Ext.data.Model.cache[Ext.data.Model.generateCacheId(associatedModel.modelName, value)];
- record.set(foreignKey, value);
- if (inverse) {
- // We first add it to the new owner so that the record wouldnt be destroyed if it was the last store it was in
- if (newOwner) {
- if (inverse.getType().toLowerCase() === 'hasmany') {
- store = newOwner[inverse.getName()]();
- store.add(record);
- } else {
- newOwner[inverse.getInstanceName()] = record;
- }
- }
- if (currentOwner) {
- if (inverse.getType().toLowerCase() === 'hasmany') {
- store = currentOwner[inverse.getName()]();
- store.remove(record);
- } else {
- delete value[inverse.getInstanceName()];
- }
- }
- }
- if (newOwner) {
- record[me.getInstanceName()] = newOwner;
- }
- if (Ext.isObject(options)) {
- return record.save(options);
- }
- return record;
- };
- },
- /**
- * @private
- * Returns a getter function to be placed on the owner model's prototype. We cache the loaded instance
- * the first time it is loaded so that subsequent calls to the getter always receive the same reference.
- * @return {Function} The getter function
- */
- createGetter: function() {
- var me = this,
- associatedModel = me.getAssociatedModel(),
- foreignKey = me.getForeignKey(),
- instanceName = me.getInstanceName();
- //'this' refers to the Model instance inside this function
- return function(options, scope) {
- options = options || {};
- var model = this,
- foreignKeyId = model.get(foreignKey),
- success,
- instance,
- args;
- instance = model[instanceName];
- if (!instance) {
- instance = Ext.data.Model.cache[Ext.data.Model.generateCacheId(associatedModel.modelName, foreignKeyId)];
- if (instance) {
- model[instanceName] = instance;
- }
- }
- if (options.reload === true || instance === undefined) {
- if (typeof options == 'function') {
- options = {
- callback: options,
- scope: scope || model
- };
- }
- // Overwrite the success handler so we can assign the current instance
- success = options.success;
- options.success = function(rec) {
- model[instanceName] = rec;
- if (success) {
- success.apply(this, arguments);
- }
- };
- associatedModel.load(foreignKeyId, options);
- } else {
- args = [instance];
- scope = scope || model;
- Ext.callback(options, scope, args);
- Ext.callback(options.success, scope, args);
- Ext.callback(options.failure, scope, args);
- Ext.callback(options.callback, scope, args);
- return instance;
- }
- };
- },
- /**
- * Read associated data
- * @private
- * @param {Ext.data.Model} record The record we're writing to
- * @param {Ext.data.reader.Reader} reader The reader for the associated model
- * @param {Object} associationData The raw associated data
- */
- read: function(record, reader, associationData){
- record[this.getInstanceName()] = reader.read([associationData]).getRecords()[0];
- },
- getInverseAssociation: function() {
- var ownerName = this.getOwnerModel().modelName,
- foreignKey = this.getForeignKey();
- return this.getAssociatedModel().associations.findBy(function(assoc) {
- var type = assoc.getType().toLowerCase();
- return (type === 'hasmany' || type === 'hasone') && assoc.getAssociatedModel().modelName === ownerName && assoc.getForeignKey() === foreignKey;
- });
- }
- });
- /**
- * @aside guide models
- *
- * Represents a one to one association with another model. The owner model is expected to have
- * a foreign key which references the primary key of the associated model:
- *
- * Ext.define('Person', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * { name: 'id', type: 'int' },
- * { name: 'name', type: 'string' },
- * { name: 'address_id', type: 'int'}
- * ],
- *
- * // we can use the hasOne shortcut on the model to create a hasOne association
- * associations: { type: 'hasOne', model: 'Address' }
- * }
- * });
- *
- * Ext.define('Address', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * { name: 'id', type: 'int' },
- * { name: 'number', type: 'string' },
- * { name: 'street', type: 'string' },
- * { name: 'city', type: 'string' },
- * { name: 'zip', type: 'string' }
- * ]
- * }
- * });
- *
- * In the example above we have created models for People and Addresses, and linked them together
- * by saying that each Person has a single Address. This automatically links each Person to an Address
- * based on the Persons address_id, and provides new functions on the Person model:
- *
- * ## Generated getter function
- *
- * The first function that is added to the owner model is a getter function:
- *
- * var person = Ext.create('Person', {
- * id: 100,
- * address_id: 20,
- * name: 'John Smith'
- * });
- *
- * person.getAddress(function(address, operation) {
- * // do something with the address object
- * alert(address.get('id')); // alerts 20
- * }, this);
- *
- * The getAddress function was created on the Person model when we defined the association. This uses the
- * Persons configured {@link Ext.data.proxy.Proxy proxy} to load the Address asynchronously, calling the provided
- * callback when it has loaded.
- *
- * The new getAddress function will also accept an object containing success, failure and callback properties
- * - callback will always be called, success will only be called if the associated model was loaded successfully
- * and failure will only be called if the associated model could not be loaded:
- *
- * person.getAddress({
- * reload: true, // force a reload if the owner model is already cached
- * callback: function(address, operation) {}, // a function that will always be called
- * success : function(address, operation) {}, // a function that will only be called if the load succeeded
- * failure : function(address, operation) {}, // a function that will only be called if the load did not succeed
- * scope : this // optionally pass in a scope object to execute the callbacks in
- * });
- *
- * In each case above the callbacks are called with two arguments - the associated model instance and the
- * {@link Ext.data.Operation operation} object that was executed to load that instance. The Operation object is
- * useful when the instance could not be loaded.
- *
- * Once the getter has been called on the model, it will be cached if the getter is called a second time. To
- * force the model to reload, specify reload: true in the options object.
- *
- * ## Generated setter function
- *
- * The second generated function sets the associated model instance - if only a single argument is passed to
- * the setter then the following two calls are identical:
- *
- * // this call...
- * person.setAddress(10);
- *
- * // is equivalent to this call:
- * person.set('address_id', 10);
- *
- * An instance of the owner model can also be passed as a parameter.
- *
- * If we pass in a second argument, the model will be automatically saved and the second argument passed to
- * the owner model's {@link Ext.data.Model#save save} method:
- *
- * person.setAddress(10, function(address, operation) {
- * // the address has been saved
- * alert(address.get('address_id')); //now alerts 10
- * });
- *
- * //alternative syntax:
- * person.setAddress(10, {
- * callback: function(address, operation) {}, // a function that will always be called
- * success : function(address, operation) {}, // a function that will only be called if the load succeeded
- * failure : function(address, operation) {}, // a function that will only be called if the load did not succeed
- * scope : this //optionally pass in a scope object to execute the callbacks in
- * });
- *
- * ## Customization
- *
- * Associations reflect on the models they are linking to automatically set up properties such as the
- * {@link #primaryKey} and {@link #foreignKey}. These can alternatively be specified:
- *
- * Ext.define('Person', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * // ...
- * ],
- *
- * associations: [
- * { type: 'hasOne', model: 'Address', primaryKey: 'unique_id', foreignKey: 'addr_id' }
- * ]
- * }
- * });
- *
- * Here we replaced the default primary key (defaults to 'id') and foreign key (calculated as 'address_id')
- * with our own settings. Usually this will not be needed.
- */
- Ext.define('Ext.data.association.HasOne', {
- extend: 'Ext.data.association.Association',
- alternateClassName: 'Ext.data.HasOneAssociation',
- alias: 'association.hasone',
- config: {
- /**
- * @cfg {String} foreignKey The name of the foreign key on the owner model that links it to the associated
- * model. Defaults to the lowercased name of the associated model plus "_id", e.g. an association with a
- * model called Person would set up a address_id foreign key.
- *
- * Ext.define('Person', {
- * extend: 'Ext.data.Model',
- * fields: ['id', 'name', 'address_id'], // refers to the id of the address object
- * hasOne: 'Address'
- * });
- *
- * Ext.define('Address', {
- * extend: 'Ext.data.Model',
- * fields: ['id', 'number', 'street', 'city', 'zip'],
- * belongsTo: 'Person'
- * });
- * var Person = new Person({
- * id: 1,
- * name: 'John Smith',
- * address_id: 13
- * }, 1);
- * person.getAddress(); // Will make a call to the server asking for address_id 13
- *
- */
- foreignKey: undefined,
- /**
- * @cfg {String} getterName The name of the getter function that will be added to the local model's prototype.
- * Defaults to 'get' + the name of the foreign model, e.g. getAddress
- */
- getterName: undefined,
- /**
- * @cfg {String} setterName The name of the setter function that will be added to the local model's prototype.
- * Defaults to 'set' + the name of the foreign model, e.g. setAddress
- */
- setterName: undefined,
- instanceName: undefined
- },
- applyForeignKey: function(foreignKey) {
- if (!foreignKey) {
- var inverse = this.getInverseAssociation();
- if (inverse) {
- foreignKey = inverse.getForeignKey();
- } else {
- foreignKey = this.getAssociatedName().toLowerCase() + '_id';
- }
- }
- return foreignKey;
- },
- updateForeignKey: function(foreignKey, oldForeignKey) {
- var fields = this.getOwnerModel().getFields(),
- field = fields.get(foreignKey);
- if (!field) {
- field = new Ext.data.Field({
- name: foreignKey
- });
- fields.add(field);
- fields.isDirty = true;
- }
- if (oldForeignKey) {
- field = fields.get(oldForeignKey);
- if (field) {
- fields.remove(field);
- fields.isDirty = true;
- }
- }
- },
- applyInstanceName: function(instanceName) {
- if (!instanceName) {
- instanceName = this.getAssociatedName() + 'HasOneInstance';
- }
- return instanceName;
- },
- applyAssociationKey: function(associationKey) {
- if (!associationKey) {
- var associatedName = this.getAssociatedName();
- associationKey = associatedName[0].toLowerCase() + associatedName.slice(1);
- }
- return associationKey;
- },
- applyGetterName: function(getterName) {
- if (!getterName) {
- var associatedName = this.getAssociatedName();
- getterName = 'get' + associatedName[0].toUpperCase() + associatedName.slice(1);
- }
- return getterName;
- },
- applySetterName: function(setterName) {
- if (!setterName) {
- var associatedName = this.getAssociatedName();
- setterName = 'set' + associatedName[0].toUpperCase() + associatedName.slice(1);
- }
- return setterName;
- },
- updateGetterName: function(getterName, oldGetterName) {
- var ownerProto = this.getOwnerModel().prototype;
- if (oldGetterName) {
- delete ownerProto[oldGetterName];
- }
- if (getterName) {
- ownerProto[getterName] = this.createGetter();
- }
- },
- updateSetterName: function(setterName, oldSetterName) {
- var ownerProto = this.getOwnerModel().prototype;
- if (oldSetterName) {
- delete ownerProto[oldSetterName];
- }
- if (setterName) {
- ownerProto[setterName] = this.createSetter();
- }
- },
- /**
- * @private
- * Returns a setter function to be placed on the owner model's prototype
- * @return {Function} The setter function
- */
- createSetter: function() {
- var me = this,
- foreignKey = me.getForeignKey(),
- instanceName = me.getInstanceName(),
- associatedModel = me.getAssociatedModel();
- //'this' refers to the Model instance inside this function
- return function(value, options, scope) {
- var Model = Ext.data.Model,
- record;
- if (value && value.isModel) {
- value = value.getId();
- }
- this.set(foreignKey, value);
- record = Model.cache[Model.generateCacheId(associatedModel.modelName, value)];
- if (record) {
- this[instanceName] = record;
- }
- if (Ext.isFunction(options)) {
- options = {
- callback: options,
- scope: scope || this
- };
- }
- if (Ext.isObject(options)) {
- return this.save(options);
- }
- return this;
- };
- },
- /**
- * @private
- * Returns a getter function to be placed on the owner model's prototype. We cache the loaded instance
- * the first time it is loaded so that subsequent calls to the getter always receive the same reference.
- * @return {Function} The getter function
- */
- createGetter: function() {
- var me = this,
- associatedModel = me.getAssociatedModel(),
- foreignKey = me.getForeignKey(),
- instanceName = me.getInstanceName();
- //'this' refers to the Model instance inside this function
- return function(options, scope) {
- options = options || {};
- var model = this,
- foreignKeyId = model.get(foreignKey),
- success, instance, args;
- if (options.reload === true || model[instanceName] === undefined) {
- if (typeof options == 'function') {
- options = {
- callback: options,
- scope: scope || model
- };
- }
- // Overwrite the success handler so we can assign the current instance
- success = options.success;
- options.success = function(rec){
- model[instanceName] = rec;
- if (success) {
- success.call(this, arguments);
- }
- };
- associatedModel.load(foreignKeyId, options);
- } else {
- instance = model[instanceName];
- args = [instance];
- scope = scope || model;
- Ext.callback(options, scope, args);
- Ext.callback(options.success, scope, args);
- Ext.callback(options.failure, scope, args);
- Ext.callback(options.callback, scope, args);
- return instance;
- }
- };
- },
- /**
- * Read associated data
- * @private
- * @param {Ext.data.Model} record The record we're writing to
- * @param {Ext.data.reader.Reader} reader The reader for the associated model
- * @param {Object} associationData The raw associated data
- */
- read: function(record, reader, associationData) {
- var inverse = this.getInverseAssociation(),
- newRecord = reader.read([associationData]).getRecords()[0];
- record[this.getInstanceName()] = newRecord;
- //if the inverse association was found, set it now on each record we've just created
- if (inverse) {
- newRecord[inverse.getInstanceName()] = record;
- }
- },
- getInverseAssociation: function() {
- var ownerName = this.getOwnerModel().modelName;
- return this.getAssociatedModel().associations.findBy(function(assoc) {
- return assoc.getType().toLowerCase() === 'belongsto' && assoc.getAssociatedModel().modelName === ownerName;
- });
- }
- });
- /**
- * @author Ed Spencer
- * @class Ext.data.Error
- *
- * This is used when validating a record. The validate method will return an {@link Ext.data.Errors} collection
- * containing Ext.data.Error instances. Each error has a field and a message.
- *
- * Usually this class does not need to be instantiated directly - instances are instead created
- * automatically when {@link Ext.data.Model#validate validate} on a model instance.
- */
- Ext.define('Ext.data.Error', {
- config: {
- /**
- * @cfg {String} field
- * The name of the field this error belongs to.
- */
- field: null,
- /**
- * @cfg {String} message
- * The message containing the description of the error.
- */
- message: ''
- },
- constructor: function(config) {
- this.initConfig(config);
- }
- });
- /**
- * @author Ed Spencer
- * @class Ext.data.Errors
- * @extends Ext.util.Collection
- *
- * Wraps a collection of validation error responses and provides convenient functions for
- * accessing and errors for specific fields.
- *
- * Usually this class does not need to be instantiated directly - instances are instead created
- * automatically when {@link Ext.data.Model#validate validate} on a model instance:
- *
- * //validate some existing model instance - in this case it returned two failures messages
- * var errors = myModel.validate();
- *
- * errors.isValid(); // false
- *
- * errors.length; // 2
- * errors.getByField('name'); // [{field: 'name', message: 'must be present'}]
- * errors.getByField('title'); // [{field: 'title', message: 'is too short'}]
- */
- Ext.define('Ext.data.Errors', {
- extend: 'Ext.util.Collection',
- requires: 'Ext.data.Error',
- /**
- * Returns `true` if there are no errors in the collection.
- * @return {Boolean}
- */
- isValid: function() {
- return this.length === 0;
- },
- /**
- * Returns all of the errors for the given field.
- * @param {String} fieldName The field to get errors for.
- * @return {Object[]} All errors for the given field.
- */
- getByField: function(fieldName) {
- var errors = [],
- error, i;
- for (i = 0; i < this.length; i++) {
- error = this.items[i];
- if (error.getField() == fieldName) {
- errors.push(error);
- }
- }
- return errors;
- },
-
- add: function() {
- var obj = arguments.length == 1 ? arguments[0] : arguments[1];
-
- if (!(obj instanceof Ext.data.Error)) {
- obj = Ext.create('Ext.data.Error', {
- field: obj.field || obj.name,
- message: obj.error || obj.message
- });
- }
-
- return this.callParent([obj]);
- }
- });
- /**
- * @author Ed Spencer
- * @aside guide models
- *
- * A Model represents some object that your application manages. For example, one might define a Model for Users,
- * Products, Cars, or any other real-world object that we want to model in the system. Models are registered via the
- * {@link Ext.data.ModelManager model manager}, and are used by {@link Ext.data.Store stores}, which are in turn used by many
- * of the data-bound components in Ext.
- *
- * Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:
- *
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- *
- * config: {
- * fields: [
- * {name: 'name', type: 'string'},
- * {name: 'age', type: 'int'},
- * {name: 'phone', type: 'string'},
- * {name: 'alive', type: 'boolean', defaultValue: true}
- * ]
- * },
- *
- * changeName: function() {
- * var oldName = this.get('name'),
- * newName = oldName + " The Barbarian";
- *
- * this.set('name', newName);
- * }
- * });
- *
- * The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link
- * Ext.data.ModelManager ModelManager}, and all other functions and properties are copied to the new Model's prototype.
- *
- * Now we can create instances of our User model and call any model logic we defined:
- *
- * var user = Ext.create('User', {
- * name : 'Conan',
- * age : 24,
- * phone: '555-555-5555'
- * });
- *
- * user.changeName();
- * user.get('name'); // returns "Conan The Barbarian"
- *
- * # Validations
- *
- * Models have built-in support for validations, which are executed against the validator functions in {@link
- * Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to
- * models:
- *
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- *
- * config: {
- * fields: [
- * {name: 'name', type: 'string'},
- * {name: 'age', type: 'int'},
- * {name: 'phone', type: 'string'},
- * {name: 'gender', type: 'string'},
- * {name: 'username', type: 'string'},
- * {name: 'alive', type: 'boolean', defaultValue: true}
- * ],
- *
- * validations: [
- * {type: 'presence', field: 'age'},
- * {type: 'length', field: 'name', min: 2},
- * {type: 'inclusion', field: 'gender', list: ['Male', 'Female']},
- * {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
- * {type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
- * ]
- * }
- * });
- *
- * The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
- * object:
- *
- * var instance = Ext.create('User', {
- * name: 'Ed',
- * gender: 'Male',
- * username: 'edspencer'
- * });
- *
- * var errors = instance.validate();
- *
- * # Associations
- *
- * Models can have associations with other Models via {@link Ext.data.association.HasOne},
- * {@link Ext.data.association.BelongsTo belongsTo} and {@link Ext.data.association.HasMany hasMany} associations.
- * For example, let's say we're writing a blog administration application which deals with Users, Posts and Comments.
- * We can express the relationships between these models like this:
- *
- * Ext.define('Post', {
- * extend: 'Ext.data.Model',
- *
- * config: {
- * fields: ['id', 'user_id'],
- * belongsTo: 'User',
- * hasMany : {model: 'Comment', name: 'comments'}
- * }
- * });
- *
- * Ext.define('Comment', {
- * extend: 'Ext.data.Model',
- *
- * config: {
- * fields: ['id', 'user_id', 'post_id'],
- * belongsTo: 'Post'
- * }
- * });
- *
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- *
- * config: {
- * fields: ['id'],
- * hasMany: [
- * 'Post',
- * {model: 'Comment', name: 'comments'}
- * ]
- * }
- * });
- *
- * See the docs for {@link Ext.data.association.HasOne}, {@link Ext.data.association.BelongsTo} and
- * {@link Ext.data.association.HasMany} for details on the usage and configuration of associations.
- * Note that associations can also be specified like this:
- *
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- *
- * config: {
- * fields: ['id'],
- * associations: [
- * {type: 'hasMany', model: 'Post', name: 'posts'},
- * {type: 'hasMany', model: 'Comment', name: 'comments'}
- * ]
- * }
- * });
- *
- * # Using a Proxy
- *
- * Models are great for representing types of data and relationships, but sooner or later we're going to want to load or
- * save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy}, which
- * can be set directly on the Model:
- *
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- *
- * config: {
- * fields: ['id', 'name', 'email'],
- * proxy: {
- * type: 'rest',
- * url : '/users'
- * }
- * }
- * });
- *
- * 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
- * RESTful backend. Let's see how this works:
- *
- * var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
- *
- * user.save(); //POST /users
- *
- * Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this Model's
- * data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't have an id,
- * and performs the appropriate action - in this case issuing a POST request to the url we configured (/users). We
- * configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full list.
- *
- * Loading data via the Proxy is equally easy:
- *
- * //get a reference to the User model class
- * var User = Ext.ModelManager.getModel('User');
- *
- * //Uses the configured RestProxy to make a GET request to /users/123
- * User.load(123, {
- * success: function(user) {
- * console.log(user.getId()); //logs 123
- * }
- * });
- *
- * Models can also be updated and destroyed easily:
- *
- * //the user Model we loaded in the last snippet:
- * user.set('name', 'Edward Spencer');
- *
- * //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
- * user.save({
- * success: function() {
- * console.log('The User was updated');
- * }
- * });
- *
- * //tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
- * user.erase({
- * success: function() {
- * console.log('The User was destroyed!');
- * }
- * });
- *
- * # Usage in Stores
- *
- * 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
- * creating a {@link Ext.data.Store Store}:
- *
- * var store = Ext.create('Ext.data.Store', {
- * model: 'User'
- * });
- *
- * //uses the Proxy we set up on Model to load the Store data
- * store.load();
- *
- * A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain a
- * set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the {@link
- * Ext.data.Store Store docs} for more information on Stores.
- */
- Ext.define('Ext.data.Model', {
- alternateClassName: 'Ext.data.Record',
- mixins: {
- observable: 'Ext.mixin.Observable'
- },
- /**
- * Provides an easy way to quickly determine if a given class is a Model
- * @property isModel
- * @type Boolean
- * @private
- */
- isModel: true,
- requires: [
- 'Ext.util.Collection',
- 'Ext.data.Field',
- 'Ext.data.identifier.Simple',
- 'Ext.data.ModelManager',
- 'Ext.data.proxy.Ajax',
- 'Ext.data.association.HasMany',
- 'Ext.data.association.BelongsTo',
- 'Ext.data.association.HasOne',
- 'Ext.data.Errors'
- ],
- config: {
- /**
- * @cfg {String} idProperty
- * The name of the field treated as this Model's unique `id`. Note that this field
- * needs to have a type of 'auto'. Setting the field type to anything else will be undone by the
- * framework. This is because new records that are created without an `id`, will have one generated.
- */
- idProperty: 'id',
- data: null,
- /**
- * @cfg {Object[]/String[]} fields
- * The {@link Ext.data.Model field} definitions for all instances of this Model.
- *
- * __Note:__ this does not set the *values* of each
- * field on an instance, it sets the collection of fields itself.
- *
- * Sample usage:
- *
- * Ext.define('MyApp.model.User', {
- * extend: 'Ext.data.Model',
- *
- * config: {
- * fields: [
- * 'id',
- * {name: 'age', type: 'int'},
- * {name: 'taxRate', type: 'float'}
- * ]
- * }
- * });
- * @accessor
- */
- fields: undefined,
- /**
- * @cfg {Object[]} validations
- * An array of {@link Ext.data.Validations validations} for this model.
- */
- validations: null,
- /**
- * @cfg {Object[]} associations
- * An array of {@link Ext.data.association.Association associations} for this model.
- */
- associations: null,
- /**
- * @cfg {String/Object/String[]/Object[]} hasMany
- * One or more {@link Ext.data.association.HasMany HasMany associations} for this model.
- */
- hasMany: null,
- /**
- * @cfg {String/Object/String[]/Object[]} hasOne
- * One or more {@link Ext.data.association.HasOne HasOne associations} for this model.
- */
- hasOne: null,
- /**
- * @cfg {String/Object/String[]/Object[]} belongsTo
- * One or more {@link Ext.data.association.BelongsTo BelongsTo associations} for this model.
- */
- belongsTo: null,
- /**
- * @cfg {Object/Ext.data.Proxy} proxy
- * The string type of the default Model Proxy.
- * @accessor
- */
- proxy: null,
- /**
- * @cfg {Object/String} identifier
- * The identifier strategy used when creating new instances of this Model that don't have an id defined.
- * By default this uses the simple identifier strategy that generates id's like 'ext-record-12'. If you are
- * saving these records in localstorage using a LocalStorage proxy you need to ensure that this identifier
- * strategy is set to something that always generates unique id's. We provide one strategy by default that
- * generates these unique id's which is the uuid strategy.
- */
- identifier: {
- type: 'simple'
- },
- /**
- * @cfg {String} clientIdProperty
- * The name of a property that is used for submitting this Model's unique client-side identifier
- * to the server when multiple phantom records are saved as part of the same {@link Ext.data.Operation Operation}.
- * In such a case, the server response should include the client id for each record
- * so that the server response data can be used to update the client-side records if necessary.
- * This property cannot have the same name as any of this Model's fields.
- * @accessor
- */
- clientIdProperty: 'clientId',
- /**
- * @method getIsErased Returns `true` if the record has been erased on the server.
- */
- isErased: false,
- /**
- * @cfg {Boolean} useCache
- * Change this to `false` if you want to ensure that new instances are created for each id. For example,
- * this is needed when adding the same tree nodes to multiple trees.
- */
- useCache: true
- },
- staticConfigs: [
- 'idProperty',
- 'fields',
- 'validations',
- 'associations',
- 'hasMany',
- 'hasOne',
- 'belongsTo',
- 'clientIdProperty',
- 'identifier',
- 'useCache',
- 'proxy'
- ],
- statics: {
- EDIT : 'edit',
- REJECT : 'reject',
- COMMIT : 'commit',
- cache: {},
- generateProxyMethod: function(name) {
- return function() {
- var prototype = this.prototype;
- return prototype[name].apply(prototype, arguments);
- };
- },
- generateCacheId: function(record, id) {
- var modelName;
- if (record && record.isModel) {
- modelName = record.modelName;
- if (id === undefined) {
- id = record.getId();
- }
- } else {
- modelName = record;
- }
- return modelName.replace(/\./g, '-').toLowerCase() + '-' + id;
- }
- },
- inheritableStatics: {
- /**
- * Asynchronously loads a model instance by id. Sample usage:
- *
- * MyApp.User = Ext.define('User', {
- * extend: 'Ext.data.Model',
- * fields: [
- * {name: 'id', type: 'int'},
- * {name: 'name', type: 'string'}
- * ]
- * });
- *
- * MyApp.User.load(10, {
- * scope: this,
- * failure: function(record, operation) {
- * //do something if the load failed
- * },
- * success: function(record, operation) {
- * //do something if the load succeeded
- * },
- * callback: function(record, operation) {
- * //do something whether the load succeeded or failed
- * }
- * });
- *
- * @param {Number} id The id of the model to load
- * @param {Object} config (optional) config object containing success, failure and callback functions, plus
- * optional scope
- * @static
- * @inheritable
- */
- load: function(id, config, scope) {
- var proxy = this.getProxy(),
- idProperty = this.getIdProperty(),
- record = null,
- params = {},
- callback, operation;
- scope = scope || (config && config.scope) || this;
- if (Ext.isFunction(config)) {
- config = {
- callback: config,
- scope: scope
- };
- }
- params[idProperty] = id;
- config = Ext.apply({}, config);
- config = Ext.applyIf(config, {
- action: 'read',
- params: params,
- model: this
- });
- operation = Ext.create('Ext.data.Operation', config);
- if (!proxy) {
- Ext.Logger.error('You are trying to load a model that doesn\'t have a Proxy specified');
- }
- callback = function(operation) {
- if (operation.wasSuccessful()) {
- record = operation.getRecords()[0] || null;
- Ext.callback(config.success, scope, [record, operation]);
- } else {
- Ext.callback(config.failure, scope, [record, operation]);
- }
- Ext.callback(config.callback, scope, [record, operation]);
- };
- proxy.read(operation, callback, this);
- }
- },
- /**
- * @property {Boolean} editing
- * @readonly
- * Internal flag used to track whether or not the model instance is currently being edited.
- */
- editing : false,
- /**
- * @property {Boolean} dirty
- * @readonly
- * `true` if this Record has been modified.
- */
- dirty : false,
- /**
- * @property {Boolean} phantom
- * `true` when the record does not yet exist in a server-side database (see {@link #setDirty}).
- * Any record which has a real database pk set as its id property is NOT a phantom -- it's real.
- */
- phantom : false,
- /**
- * Creates new Model instance.
- * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values.
- * @param {Number} id (optional) Unique ID to assign to this model instance.
- * @param [raw]
- * @param [convertedData]
- */
- constructor: function(data, id, raw, convertedData) {
- var me = this,
- cached = null,
- useCache = me.getUseCache(),
- idProperty = me.getIdProperty();
- /**
- * @property {Object} modified key/value pairs of all fields whose values have changed.
- * The value is the original value for the field.
- */
- me.modified = {};
- /**
- * @property {Object} raw The raw data used to create this model if created via a reader.
- */
- me.raw = raw || data || {};
- /**
- * @property {Array} stores
- * An array of {@link Ext.data.Store} objects that this record is bound to.
- */
- me.stores = [];
- data = data || convertedData || {};
- // We begin by checking if an id is passed to the constructor. If this is the case we override
- // any possible id value that was passed in the data.
- if (id || id === 0) {
- // Lets skip using set here since it's so much faster
- data[idProperty] = me.internalId = id;
- }
- id = data[idProperty];
- if (useCache && (id || id === 0)) {
- cached = Ext.data.Model.cache[Ext.data.Model.generateCacheId(this, id)];
- if (cached) {
- return cached.mergeData(convertedData || data || {});
- }
- }
- if (convertedData) {
- me.setConvertedData(data);
- } else {
- me.setData(data);
- }
- // If it does not have an id at this point, we generate it using the id strategy. This means
- // that we will treat this record as a phantom record from now on
- id = me.data[idProperty];
- if (!id && id !== 0) {
- me.data[idProperty] = me.internalId = me.id = me.getIdentifier().generate(me);
- me.phantom = true;
- if (this.associations.length) {
- this.handleInlineAssociationData(data);
- }
- } else {
- me.id = me.getIdentifier().generate(me);
- }
- if (useCache) {
- Ext.data.Model.cache[Ext.data.Model.generateCacheId(me)] = me;
- }
- if (this.init && typeof this.init == 'function') {
- this.init();
- }
- },
- /**
- * Private function that is used when you create a record that already exists in the model cache.
- * In this case we loop over each field, and apply any data to the current instance that is not already
- * marked as being dirty on that instance.
- * @param data
- * @return {Ext.data.Model} This record.
- * @private
- */
- mergeData: function(rawData) {
- var me = this,
- fields = me.getFields().items,
- ln = fields.length,
- modified = me.modified,
- data = me.data,
- i, field, fieldName, value, id;
- for (i = 0; i < ln; i++) {
- field = fields[i];
- fieldName = field._name;
- value = rawData[fieldName];
- if (value !== undefined && !modified.hasOwnProperty(fieldName)) {
- if (field._convert) {
- value = field._convert(value, me);
- }
- data[fieldName] = value;
- }
- }
- if (me.associations.length) {
- me.handleInlineAssociationData(rawData);
- }
- return this;
- },
- /**
- * This method is used to set the data for this Record instance.
- * Note that the existing data is removed. If a field is not specified
- * in the passed data it will use the field's default value. If a convert
- * method is specified for the field it will be called on the value.
- * @param rawData
- * @return {Ext.data.Model} This record.
- */
- setData: function(rawData) {
- var me = this,
- fields = me.fields.items,
- ln = fields.length,
- isArray = Ext.isArray(rawData),
- data = me._data = me.data = {},
- i, field, name, value, convert, id;
- if (!rawData) {
- return me;
- }
- for (i = 0; i < ln; i++) {
- field = fields[i];
- name = field._name;
- convert = field._convert;
- if (isArray) {
- value = rawData[i];
- }
- else {
- value = rawData[name];
- if (typeof value == 'undefined') {
- value = field._defaultValue;
- }
- }
- if (convert) {
- value = field._convert(value, me);
- }
- data[name] = value;
- }
- id = me.getId();
- if (me.associations.length && (id || id === 0)) {
- me.handleInlineAssociationData(rawData);
- }
- return me;
- },
- handleInlineAssociationData: function(data) {
- var associations = this.associations.items,
- ln = associations.length,
- i, association, associationData, reader, proxy, associationKey;
- for (i = 0; i < ln; i++) {
- association = associations[i];
- associationKey = association.getAssociationKey();
- associationData = data[associationKey];
- if (associationData) {
- reader = association.getReader();
- if (!reader) {
- proxy = association.getAssociatedModel().getProxy();
- // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
- if (proxy) {
- reader = proxy.getReader();
- } else {
- reader = new Ext.data.JsonReader({
- model: association.getAssociatedModel()
- });
- }
- }
- association.read(this, reader, associationData);
- }
- }
- },
- /**
- * Sets the model instance's id field to the given id.
- * @param {Number/String} id The new id
- */
- setId: function(id) {
- var currentId = this.getId();
- // Lets use the direct property instead of getter here
- this.set(this.getIdProperty(), id);
- // We don't update the this.id since we don't want to break listeners that already
- // exist on the record instance.
- this.internalId = id;
- if (this.getUseCache()) {
- delete Ext.data.Model.cache[Ext.data.Model.generateCacheId(this, currentId)];
- Ext.data.Model.cache[Ext.data.Model.generateCacheId(this)] = this;
- }
- },
- /**
- * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}.
- * @return {Number/String} The `id`.
- */
- getId: function() {
- // Lets use the direct property instead of getter here
- return this.get(this.getIdProperty());
- },
- /**
- * This sets the data directly without converting and applying default values.
- * This method is used when a Record gets instantiated by a Reader. Only use
- * this when you are sure you are passing correctly converted data.
- * @param data
- * @return {Ext.data.Model} This Record.
- */
- setConvertedData: function(data) {
- this._data = this.data = data;
- return this;
- },
- /**
- * Returns the value of the given field.
- * @param {String} fieldName The field to fetch the value for.
- * @return {Object} The value.
- */
- get: function(fieldName) {
- return this.data[fieldName];
- },
- /**
- * Sets the given field to the given value, marks the instance as dirty.
- * @param {String/Object} fieldName The field to set, or an object containing key/value pairs.
- * @param {Object} value The value to set.
- */
- set: function(fieldName, value) {
- var me = this,
- // We are using the fields map since it saves lots of function calls
- fieldMap = me.fields.map,
- modified = me.modified,
- notEditing = !me.editing,
- modifiedCount = 0,
- modifiedFieldNames = [],
- field, key, i, currentValue, ln, convert;
- /*
- * If we're passed an object, iterate over that object. NOTE: we pull out fields with a convert function and
- * set those last so that all other possible data is set before the convert function is called
- */
- if (arguments.length == 1) {
- for (key in fieldName) {
- if (fieldName.hasOwnProperty(key)) {
- //here we check for the custom convert function. Note that if a field doesn't have a convert function,
- //we default it to its type's convert function, so we have to check that here. This feels rather dirty.
- field = fieldMap[key];
- if (field && field.hasCustomConvert()) {
- modifiedFieldNames.push(key);
- continue;
- }
- if (!modifiedCount && notEditing) {
- me.beginEdit();
- }
- ++modifiedCount;
- me.set(key, fieldName[key]);
- }
- }
- ln = modifiedFieldNames.length;
- if (ln) {
- if (!modifiedCount && notEditing) {
- me.beginEdit();
- }
- modifiedCount += ln;
- for (i = 0; i < ln; i++) {
- field = modifiedFieldNames[i];
- me.set(field, fieldName[field]);
- }
- }
- if (notEditing && modifiedCount) {
- me.endEdit(false, modifiedFieldNames);
- }
- } else {
- field = fieldMap[fieldName];
- convert = field && field.getConvert();
- if (convert) {
- value = convert.call(field, value, me);
- }
- currentValue = me.data[fieldName];
- me.data[fieldName] = value;
- if (field && !me.isEqual(currentValue, value)) {
- if (modified.hasOwnProperty(fieldName)) {
- if (me.isEqual(modified[fieldName], value)) {
- // the original value in me.modified equals the new value, so the
- // field is no longer modified
- delete modified[fieldName];
- // we might have removed the last modified field, so check to see if
- // there are any modified fields remaining and correct me.dirty:
- me.dirty = false;
- for (key in modified) {
- if (modified.hasOwnProperty(key)) {
- me.dirty = true;
- break;
- }
- }
- }
- } else {
- me.dirty = true;
- // We only go one level back?
- modified[fieldName] = currentValue;
- }
- }
- if (notEditing) {
- me.afterEdit([fieldName], modified);
- }
- }
- },
- /**
- * Checks if two values are equal, taking into account certain
- * special factors, for example dates.
- * @private
- * @param {Object} a The first value.
- * @param {Object} b The second value.
- * @return {Boolean} `true` if the values are equal.
- */
- isEqual: function(a, b){
- if (Ext.isDate(a) && Ext.isDate(b)) {
- return a.getTime() === b.getTime();
- }
- return a === b;
- },
- /**
- * Begins an edit. While in edit mode, no events (e.g. the `update` event) are relayed to the containing store.
- * When an edit has begun, it must be followed by either {@link #endEdit} or {@link #cancelEdit}.
- */
- beginEdit: function() {
- var me = this;
- if (!me.editing) {
- me.editing = true;
- // We save the current states of dirty, data and modified so that when we
- // cancel the edit, we can put it back to this state
- me.dirtySave = me.dirty;
- me.dataSave = Ext.apply({}, me.data);
- me.modifiedSave = Ext.apply({}, me.modified);
- }
- },
- /**
- * Cancels all changes made in the current edit operation.
- */
- cancelEdit: function() {
- var me = this;
- if (me.editing) {
- me.editing = false;
- // Reset the modified state, nothing changed since the edit began
- me.modified = me.modifiedSave;
- me.data = me.dataSave;
- me.dirty = me.dirtySave;
- // Delete the saved states
- delete me.modifiedSave;
- delete me.dataSave;
- delete me.dirtySave;
- }
- },
- /**
- * Ends an edit. If any data was modified, the containing store is notified (ie, the store's `update` event will
- * fire).
- * @param {Boolean} silent `true` to not notify the store of the change.
- * @param {String[]} modifiedFieldNames Array of field names changed during edit.
- */
- endEdit: function(silent, modifiedFieldNames) {
- var me = this;
- if (me.editing) {
- me.editing = false;
- if (silent !== true && (me.changedWhileEditing())) {
- me.afterEdit(modifiedFieldNames || Ext.Object.getKeys(this.modified), this.modified);
- }
- delete me.modifiedSave;
- delete me.dataSave;
- delete me.dirtySave;
- }
- },
- /**
- * Checks if the underlying data has changed during an edit. This doesn't necessarily
- * mean the record is dirty, however we still need to notify the store since it may need
- * to update any views.
- * @private
- * @return {Boolean} `true` if the underlying data has changed during an edit.
- */
- changedWhileEditing: function() {
- var me = this,
- saved = me.dataSave,
- data = me.data,
- key;
- for (key in data) {
- if (data.hasOwnProperty(key)) {
- if (!me.isEqual(data[key], saved[key])) {
- return true;
- }
- }
- }
- return false;
- },
- /**
- * Gets a hash of only the fields that have been modified since this Model was created or committed.
- * @return {Object}
- */
- getChanges : function() {
- var modified = this.modified,
- changes = {},
- field;
- for (field in modified) {
- if (modified.hasOwnProperty(field)) {
- changes[field] = this.get(field);
- }
- }
- return changes;
- },
- /**
- * Returns `true` if the passed field name has been `{@link #modified}` since the load or last commit.
- * @param {String} fieldName {@link Ext.data.Field#name}
- * @return {Boolean}
- */
- isModified : function(fieldName) {
- return this.modified.hasOwnProperty(fieldName);
- },
- /**
- * Saves the model instance using the configured proxy.
- *
- * @param {Object/Function} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
- * If you pass a function, this will automatically become the callback method. For convenience the config
- * object may also contain `success` and `failure` methods in addition to `callback` - they will all be invoked
- * with the Model and Operation as arguments.
- * @param {Object} scope The scope to run your callback method in. This is only used if you passed a function
- * as the first argument.
- * @return {Ext.data.Model} The Model instance
- */
- save: function(options, scope) {
- var me = this,
- action = me.phantom ? 'create' : 'update',
- proxy = me.getProxy(),
- operation,
- callback;
- if (!proxy) {
- Ext.Logger.error('You are trying to save a model instance that doesn\'t have a Proxy specified');
- }
- options = options || {};
- scope = scope || me;
- if (Ext.isFunction(options)) {
- options = {
- callback: options,
- scope: scope
- };
- }
- Ext.applyIf(options, {
- records: [me],
- action : action,
- model: me.self
- });
- operation = Ext.create('Ext.data.Operation', options);
- callback = function(operation) {
- if (operation.wasSuccessful()) {
- Ext.callback(options.success, scope, [me, operation]);
- } else {
- Ext.callback(options.failure, scope, [me, operation]);
- }
- Ext.callback(options.callback, scope, [me, operation]);
- };
- proxy[action](operation, callback, me);
- return me;
- },
- /**
- * Destroys the record using the configured proxy. This will create a 'destroy' operation.
- * Note that this doesn't destroy this instance after the server comes back with a response.
- * It will however call `afterErase` on any Stores it is joined to. Stores by default will
- * automatically remove this instance from their data collection.
- *
- * @param {Object/Function} options Options to pass to the proxy. Config object for {@link Ext.data.Operation}.
- * If you pass a function, this will automatically become the callback method. For convenience the config
- * object may also contain `success` and `failure` methods in addition to `callback` - they will all be invoked
- * with the Model and Operation as arguments.
- * @param {Object} scope The scope to run your callback method in. This is only used if you passed a function
- * as the first argument.
- * @return {Ext.data.Model} The Model instance.
- */
- erase: function(options, scope) {
- var me = this,
- proxy = this.getProxy(),
- operation,
- callback;
- if (!proxy) {
- Ext.Logger.error('You are trying to erase a model instance that doesn\'t have a Proxy specified');
- }
- options = options || {};
- scope = scope || me;
- if (Ext.isFunction(options)) {
- options = {
- callback: options,
- scope: scope
- };
- }
- Ext.applyIf(options, {
- records: [me],
- action : 'destroy',
- model: this.self
- });
- operation = Ext.create('Ext.data.Operation', options);
- callback = function(operation) {
- if (operation.wasSuccessful()) {
- Ext.callback(options.success, scope, [me, operation]);
- } else {
- Ext.callback(options.failure, scope, [me, operation]);
- }
- Ext.callback(options.callback, scope, [me, operation]);
- };
- proxy.destroy(operation, callback, me);
- return me;
- },
- /**
- * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}. Rejects
- * all changes made to the model instance since either creation, or the last commit operation. Modified fields are
- * reverted to their original values.
- *
- * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of reject
- * operations.
- *
- * @param {Boolean} [silent=false] (optional) `true` to skip notification of the owning store of the change.
- */
- reject: function(silent) {
- var me = this,
- modified = me.modified,
- field;
- for (field in modified) {
- if (modified.hasOwnProperty(field)) {
- if (typeof modified[field] != "function") {
- me.data[field] = modified[field];
- }
- }
- }
- me.dirty = false;
- me.editing = false;
- me.modified = {};
- if (silent !== true) {
- me.afterReject();
- }
- },
- /**
- * Usually called by the {@link Ext.data.Store} which owns the model instance. Commits all changes made to the
- * instance since either creation or the last commit operation.
- *
- * Developers should subscribe to the {@link Ext.data.Store#update} event to have their code notified of commit
- * operations.
- *
- * @param {Boolean} [silent=false] (optional) `true` to skip notification of the owning store of the change.
- */
- commit: function(silent) {
- var me = this,
- modified = this.modified;
- me.phantom = me.dirty = me.editing = false;
- me.modified = {};
- if (silent !== true) {
- me.afterCommit(modified);
- }
- },
- /**
- * @private
- * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
- * `afterEdit` method is called.
- * @param {String[]} modifiedFieldNames Array of field names changed during edit.
- */
- afterEdit : function(modifiedFieldNames, modified) {
- this.notifyStores('afterEdit', modifiedFieldNames, modified);
- },
- /**
- * @private
- * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
- * `afterReject` method is called.
- */
- afterReject : function() {
- this.notifyStores("afterReject");
- },
- /**
- * @private
- * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
- * `afterCommit` method is called.
- */
- afterCommit: function(modified) {
- this.notifyStores('afterCommit', Ext.Object.getKeys(modified || {}), modified);
- },
- /**
- * @private
- * Helper function used by {@link #afterEdit}, {@link #afterReject}, and {@link #afterCommit}. Calls the given method on the
- * {@link Ext.data.Store store} that this instance has {@link #join join}ed, if any. The store function
- * will always be called with the model instance as its single argument.
- * @param {String} fn The function to call on the store.
- */
- notifyStores: function(fn) {
- var args = Ext.Array.clone(arguments),
- stores = this.stores,
- ln = stores.length,
- i, store;
- args[0] = this;
- for (i = 0; i < ln; ++i) {
- store = stores[i];
- if (store !== undefined && typeof store[fn] == "function") {
- store[fn].apply(store, args);
- }
- }
- },
- /**
- * Creates a copy (clone) of this Model instance.
- *
- * @param {String} id A new `id`. If you don't specify this a new `id` will be generated for you.
- * To generate a phantom instance with a new `id` use:
- *
- * var rec = record.copy(); // clone the record with a new id
- *
- * @return {Ext.data.Model}
- */
- copy: function(newId) {
- var me = this,
- idProperty = me.getIdProperty(),
- raw = Ext.apply({}, me.raw),
- data = Ext.apply({}, me.data);
- delete raw[idProperty];
- delete data[idProperty];
- return new me.self(null, newId, raw, data);
- },
- /**
- * Returns an object containing the data set on this record. This method also allows you to
- * retrieve all the associated data. Note that if you should always use this method if you
- * need all the associated data, since the data property on the record instance is not
- * ensured to be updated at all times.
- * @param {Boolean} includeAssociated `true` to include the associated data.
- * @return {Object} The data.
- */
- getData: function(includeAssociated) {
- var data = this.data;
- if (includeAssociated === true) {
- Ext.apply(data, this.getAssociatedData());
- }
- return data;
- },
- /**
- * Gets all of the data from this Models *loaded* associations. It does this recursively - for example if we have a
- * User which `hasMany` Orders, and each Order `hasMany` OrderItems, it will return an object like this:
- *
- * {
- * orders: [
- * {
- * id: 123,
- * status: 'shipped',
- * orderItems: [
- * // ...
- * ]
- * }
- * ]
- * }
- *
- * @return {Object} The nested data set for the Model's loaded associations.
- */
- getAssociatedData: function() {
- return this.prepareAssociatedData(this, [], null);
- },
- /**
- * @private
- * This complex-looking method takes a given Model instance and returns an object containing all data from
- * all of that Model's *loaded* associations. See {@link #getAssociatedData}
- * @param {Ext.data.Model} record The Model instance
- * @param {String[]} ids PRIVATE. The set of Model instance `internalIds` that have already been loaded
- * @param {String} associationType (optional) The name of the type of association to limit to.
- * @return {Object} The nested data set for the Model's loaded associations.
- */
- prepareAssociatedData: function(record, ids, associationType) {
- //we keep track of all of the internalIds of the models that we have loaded so far in here
- var associations = record.associations.items,
- associationCount = associations.length,
- associationData = {},
- associatedStore, associationName, associatedRecords, associatedRecord,
- associatedRecordCount, association, id, i, j, type, allow;
- for (i = 0; i < associationCount; i++) {
- association = associations[i];
- associationName = association.getName();
- type = association.getType();
- allow = true;
- if (associationType) {
- allow = type == associationType;
- }
- if (allow && type.toLowerCase() == 'hasmany') {
- //this is the hasMany store filled with the associated data
- associatedStore = record[association.getStoreName()];
- //we will use this to contain each associated record's data
- associationData[associationName] = [];
- //if it's loaded, put it into the association data
- if (associatedStore && associatedStore.getCount() > 0) {
- associatedRecords = associatedStore.data.items;
- associatedRecordCount = associatedRecords.length;
- //now we're finally iterating over the records in the association. We do this recursively
- for (j = 0; j < associatedRecordCount; j++) {
- associatedRecord = associatedRecords[j];
- // Use the id, since it is prefixed with the model name, guaranteed to be unique
- id = associatedRecord.id;
- //when we load the associations for a specific model instance we add it to the set of loaded ids so that
- //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
- if (Ext.Array.indexOf(ids, id) == -1) {
- ids.push(id);
- associationData[associationName][j] = associatedRecord.getData();
- Ext.apply(associationData[associationName][j], this.prepareAssociatedData(associatedRecord, ids, associationType));
- }
- }
- }
- } else if (allow && (type.toLowerCase() == 'belongsto' || type.toLowerCase() == 'hasone')) {
- associatedRecord = record[association.getInstanceName()];
- if (associatedRecord !== undefined) {
- id = associatedRecord.id;
- if (Ext.Array.indexOf(ids, id) === -1) {
- ids.push(id);
- associationData[associationName] = associatedRecord.getData();
- Ext.apply(associationData[associationName], this.prepareAssociatedData(associatedRecord, ids, associationType));
- }
- }
- }
- }
- return associationData;
- },
- /**
- * By joining this model to an instance of a class, this model will automatically try to
- * call certain template methods on that instance ({@link #afterEdit}, {@link #afterCommit}, {@link Ext.data.Store#afterErase}).
- * For example, a Store calls join and unjoin whenever you add or remove a record to it's data collection.
- * This way a Store can get notified of any changes made to this record.
- * This functionality is usually only required when creating custom components.
- * @param {Ext.data.Store} store The store to which this model has been added.
- */
- join: function(store) {
- Ext.Array.include(this.stores, store);
- },
- /**
- * This un-joins this record from an instance of a class. Look at the documentation for {@link #join}
- * for more information about joining records to class instances.
- * @param {Ext.data.Store} store The store from which this model has been removed.
- */
- unjoin: function(store) {
- Ext.Array.remove(this.stores, store);
- },
- /**
- * Marks this **Record** as `{@link #dirty}`. This method is used internally when adding `{@link #phantom}` records
- * to a {@link Ext.data.proxy.Server#writer writer enabled store}.
- *
- * Marking a record `{@link #dirty}` causes the phantom to be returned by {@link Ext.data.Store#getUpdatedRecords}
- * where it will have a create action composed for it during {@link Ext.data.Model#save model save} operations.
- */
- setDirty : function() {
- var me = this,
- name;
- me.dirty = true;
- me.fields.each(function(field) {
- if (field.getPersist()) {
- name = field.getName();
- me.modified[name] = me.get(name);
- }
- });
- },
- /**
- * Validates the current data against all of its configured {@link #cfg-validations}.
- * @return {Ext.data.Errors} The errors object.
- */
- validate: function() {
- var errors = Ext.create('Ext.data.Errors'),
- validations = this.getValidations().items,
- validators = Ext.data.Validations,
- length, validation, field, valid, type, i;
- if (validations) {
- length = validations.length;
- for (i = 0; i < length; i++) {
- validation = validations[i];
- field = validation.field || validation.name;
- type = validation.type;
- valid = validators[type](validation, this.get(field));
- if (!valid) {
- errors.add(Ext.create('Ext.data.Error', {
- field : field,
- message: validation.message || validators.getMessage(type)
- }));
- }
- }
- }
- return errors;
- },
- /**
- * Checks if the model is valid. See {@link #validate}.
- * @return {Boolean} `true` if the model is valid.
- */
- isValid: function(){
- return this.validate().isValid();
- },
- /**
- * Returns a url-suitable string for this model instance. By default this just returns the name of the Model class
- * followed by the instance ID - for example an instance of MyApp.model.User with ID 123 will return 'user/123'.
- * @return {String} The url string for this model instance.
- */
- toUrl: function() {
- var pieces = this.$className.split('.'),
- name = pieces[pieces.length - 1].toLowerCase();
- return name + '/' + this.getId();
- },
- /**
- * Destroys this model instance. Note that this doesn't do a 'destroy' operation. If you want to destroy
- * the record in your localStorage or on the server you should use the {@link #erase} method.
- */
- destroy: function() {
- var me = this;
- me.notifyStores('afterErase', me);
- if (me.getUseCache()) {
- delete Ext.data.Model.cache[Ext.data.Model.generateCacheId(me)];
- }
- me.raw = me.stores = me.modified = null;
- me.callParent(arguments);
- },
- //<debug>
- markDirty : function() {
- if (Ext.isDefined(Ext.Logger)) {
- Ext.Logger.deprecate('Ext.data.Model: markDirty has been deprecated. Use setDirty instead.');
- }
- return this.setDirty.apply(this, arguments);
- },
- //</debug>
- applyProxy: function(proxy, currentProxy) {
- return Ext.factory(proxy, Ext.data.Proxy, currentProxy, 'proxy');
- },
- updateProxy: function(proxy) {
- if (proxy) {
- proxy.setModel(this.self);
- }
- },
- applyAssociations: function(associations) {
- if (associations) {
- this.addAssociations(associations, 'hasMany');
- }
- },
- applyBelongsTo: function(belongsTo) {
- if (belongsTo) {
- this.addAssociations(belongsTo, 'belongsTo');
- }
- },
- applyHasMany: function(hasMany) {
- if (hasMany) {
- this.addAssociations(hasMany, 'hasMany');
- }
- },
- applyHasOne: function(hasOne) {
- if (hasOne) {
- this.addAssociations(hasOne, 'hasOne');
- }
- },
- addAssociations: function(associations, defaultType) {
- var ln, i, association,
- name = this.self.modelName,
- associationsCollection = this.self.associations,
- onCreatedFn;
- associations = Ext.Array.from(associations);
- for (i = 0, ln = associations.length; i < ln; i++) {
- association = associations[i];
- if (!Ext.isObject(association)) {
- association = {model: association};
- }
- Ext.applyIf(association, {
- type: defaultType,
- ownerModel: name,
- associatedModel: association.model
- });
- delete association.model;
- onCreatedFn = Ext.Function.bind(function(associationName) {
- associationsCollection.add(Ext.data.association.Association.create(this));
- }, association);
- Ext.ClassManager.onCreated(onCreatedFn, this, (typeof association.associatedModel === 'string') ? association.associatedModel : Ext.getClassName(association.associatedModel));
- }
- },
- applyValidations: function(validations) {
- if (validations) {
- if (!Ext.isArray(validations)) {
- validations = [validations];
- }
- this.addValidations(validations);
- }
- },
- addValidations: function(validations) {
- this.self.validations.addAll(validations);
- },
- /**
- * @method setFields
- * Updates the collection of Fields that all instances of this Model use. **Does not** update field values in a Model
- * instance (use {@link #set} for that), instead this updates which fields are available on the Model class. This
- * is normally used when creating or updating Model definitions dynamically, for example if you allow your users to
- * define their own Models and save the fields configuration to a database, this method allows you to change those
- * fields later.
- * @return {Array}
- */
- applyFields: function(fields) {
- var superFields = this.superclass.fields;
- if (superFields) {
- fields = superFields.items.concat(fields || []);
- }
- return fields || [];
- },
- updateFields: function(fields) {
- var ln = fields.length,
- me = this,
- prototype = me.self.prototype,
- idProperty = this.getIdProperty(),
- idField, fieldsCollection, field, i;
- /**
- * @property {Ext.util.MixedCollection} fields
- * The fields defined on this model.
- */
- fieldsCollection = me._fields = me.fields = new Ext.util.Collection(prototype.getFieldName);
- for (i = 0; i < ln; i++) {
- field = fields[i];
- if (!field.isField) {
- field = new Ext.data.Field(fields[i]);
- }
- fieldsCollection.add(field);
- }
- // We want every Model to have an id property field
- idField = fieldsCollection.get(idProperty);
- if (!idField) {
- fieldsCollection.add(new Ext.data.Field(idProperty));
- } else {
- idField.setType('auto');
- }
- fieldsCollection.addSorter(prototype.sortConvertFields);
- },
- applyIdentifier: function(identifier) {
- if (typeof identifier === 'string') {
- identifier = {
- type: identifier
- };
- }
- return Ext.factory(identifier, Ext.data.identifier.Simple, this.getIdentifier(), 'data.identifier');
- },
- /**
- * This method is used by the fields collection to retrieve the key for a field
- * based on it's name.
- * @param field
- * @return {String}
- * @private
- */
- getFieldName: function(field) {
- return field.getName();
- },
- /**
- * This method is being used to sort the fields based on their convert method. If
- * a field has a custom convert method, we ensure its more to the bottom of the collection.
- * @param field1
- * @param field2
- * @return {Number}
- * @private
- */
- sortConvertFields: function(field1, field2) {
- var f1SpecialConvert = field1.hasCustomConvert(),
- f2SpecialConvert = field2.hasCustomConvert();
- if (f1SpecialConvert && !f2SpecialConvert) {
- return 1;
- }
- if (!f1SpecialConvert && f2SpecialConvert) {
- return -1;
- }
- return 0;
- },
- /**
- * @private
- */
- onClassExtended: function(cls, data, hooks) {
- var onBeforeClassCreated = hooks.onBeforeCreated,
- Model = this,
- prototype = Model.prototype,
- configNameCache = Ext.Class.configNameCache,
- staticConfigs = prototype.staticConfigs.concat(data.staticConfigs || []),
- defaultConfig = prototype.config,
- config = data.config || {},
- key;
- // Convert old properties in data into a config object
- data.config = config;
- hooks.onBeforeCreated = function(cls, data) {
- var dependencies = [],
- prototype = cls.prototype,
- statics = {},
- config = prototype.config,
- staticConfigsLn = staticConfigs.length,
- copyMethods = ['set', 'get'],
- copyMethodsLn = copyMethods.length,
- associations = config.associations || [],
- name = Ext.getClassName(cls),
- key, methodName, i, j, ln;
- // Create static setters and getters for each config option
- for (i = 0; i < staticConfigsLn; i++) {
- key = staticConfigs[i];
- for (j = 0; j < copyMethodsLn; j++) {
- methodName = configNameCache[key][copyMethods[j]];
- if (methodName in prototype) {
- statics[methodName] = Model.generateProxyMethod(methodName);
- }
- }
- }
- cls.addStatics(statics);
- // Save modelName on class and its prototype
- cls.modelName = name;
- prototype.modelName = name;
- // Take out dependencies on other associations and the proxy type
- if (config.belongsTo) {
- dependencies.push('association.belongsto');
- }
- if (config.hasMany) {
- dependencies.push('association.hasmany');
- }
- if (config.hasOne) {
- dependencies.push('association.hasone');
- }
- for (i = 0,ln = associations.length; i < ln; ++i) {
- dependencies.push('association.' + associations[i].type.toLowerCase());
- }
- if (config.identifier) {
- if (typeof config.identifier === 'string') {
- dependencies.push('data.identifier.' + config.identifier);
- }
- else if (typeof config.identifier.type === 'string') {
- dependencies.push('data.identifier.' + config.identifier.type);
- }
- }
- if (config.proxy) {
- if (typeof config.proxy === 'string') {
- dependencies.push('proxy.' + config.proxy);
- }
- else if (typeof config.proxy.type === 'string') {
- dependencies.push('proxy.' + config.proxy.type);
- }
- }
- if (config.validations) {
- dependencies.push('Ext.data.Validations');
- }
- Ext.require(dependencies, function() {
- Ext.Function.interceptBefore(hooks, 'onCreated', function() {
- Ext.data.ModelManager.registerType(name, cls);
- var superCls = cls.prototype.superclass;
- /**
- * @property {Ext.util.Collection} associations
- * The associations defined on this model.
- */
- cls.prototype.associations = cls.associations = cls.prototype._associations = (superCls && superCls.associations)
- ? superCls.associations.clone()
- : new Ext.util.Collection(function(association) {
- return association.getName();
- });
- /**
- * @property {Ext.util.Collection} validations
- * The validations defined on this model.
- */
- cls.prototype.validations = cls.validations = cls.prototype._validations = (superCls && superCls.validations)
- ? superCls.validations.clone()
- : new Ext.util.Collection(function(validation) {
- return validation.field ? (validation.field + '-' + validation.type) : (validation.name + '-' + validation.type);
- });
- cls.prototype = Ext.Object.chain(cls.prototype);
- cls.prototype.initConfig.call(cls.prototype, config);
- delete cls.prototype.initConfig;
- });
- onBeforeClassCreated.call(Model, cls, data, hooks);
- });
- };
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.util.Grouper', {
- /* Begin Definitions */
- extend: 'Ext.util.Sorter',
- isGrouper: true,
-
- config: {
- /**
- * @cfg {Function} groupFn This function will be called for each item in the collection to
- * determine the group to which it belongs.
- * @cfg {Object} groupFn.item The current item from the collection
- * @cfg {String} groupFn.return The group identifier for the item
- */
- groupFn: null,
- /**
- * @cfg {String} sortProperty You can define this configuration if you want the groups to be sorted
- * on something other then the group string returned by the `groupFn`.
- */
- sortProperty: null,
- /**
- * @cfg {Function} sorterFn
- * Grouper has a custom sorterFn that cannot be overridden by the user. If a property has been defined
- * on this grouper, we use the default `sorterFn`, else we sort based on the returned group string.
- */
- sorterFn: function(item1, item2) {
- var property = this.getSortProperty(),
- groupFn, group1, group2, modifier;
- groupFn = this.getGroupFn();
- group1 = groupFn.call(this, item1);
- group2 = groupFn.call(this, item2);
- if (property) {
- if (group1 !== group2) {
- return this.defaultSortFn.call(this, item1, item2);
- } else {
- return 0;
- }
- }
- return (group1 > group2) ? 1 : ((group1 < group2) ? -1 : 0);
- }
- },
- /**
- * @private
- * Basic default sorter function that just compares the defined property of each object.
- */
- defaultSortFn: function(item1, item2) {
- var me = this,
- transform = me._transform,
- root = me._root,
- value1, value2,
- property = me._sortProperty;
- if (root !== null) {
- item1 = item1[root];
- item2 = item2[root];
- }
- value1 = item1[property];
- value2 = item2[property];
- if (transform) {
- value1 = transform(value1);
- value2 = transform(value2);
- }
- return value1 > value2 ? 1 : (value1 < value2 ? -1 : 0);
- },
- updateProperty: function(property) {
- this.setGroupFn(this.standardGroupFn);
- },
- standardGroupFn: function(item) {
- var root = this.getRoot(),
- property = this.getProperty(),
- data = item;
- if (root) {
- data = item[root];
- }
- return data[property];
- },
- getGroupString: function(item) {
- var group = this.getGroupFn().call(this, item);
- return typeof group != 'undefined' ? group.toString() : '';
- }
- });
- /**
- * @author Ed Spencer
- * @aside guide stores
- *
- * The Store class encapsulates a client side cache of {@link Ext.data.Model Model} objects. Stores load
- * data via a {@link Ext.data.proxy.Proxy Proxy}, and also provide functions for {@link #sort sorting},
- * {@link #filter filtering} and querying the {@link Ext.data.Model model} instances contained within it.
- *
- * Creating a Store is easy - we just tell it the Model and the Proxy to use to load and save its data:
- *
- * // Set up a {@link Ext.data.Model model} to use in our Store
- * Ext.define("User", {
- * extend: "Ext.data.Model",
- * config: {
- * fields: [
- * {name: "firstName", type: "string"},
- * {name: "lastName", type: "string"},
- * {name: "age", type: "int"},
- * {name: "eyeColor", type: "string"}
- * ]
- * }
- * });
- *
- * var myStore = Ext.create("Ext.data.Store", {
- * model: "User",
- * proxy: {
- * type: "ajax",
- * url : "/users.json",
- * reader: {
- * type: "json",
- * rootProperty: "users"
- * }
- * },
- * autoLoad: true
- * });
- *
- * Ext.create("Ext.List", {
- * fullscreen: true,
- * store: myStore,
- * itemTpl: "{lastName}, {firstName} ({age})"
- * });
- *
- * In the example above we configured an AJAX proxy to load data from the url '/users.json'. We told our Proxy
- * to use a {@link Ext.data.reader.Json JsonReader} to parse the response from the server into Model object -
- * {@link Ext.data.reader.Json see the docs on JsonReader} for details.
- *
- * The external data file, _/users.json_, is as follows:
- *
- * {
- * "success": true,
- * "users": [
- * {
- * "firstName": "Tommy",
- * "lastName": "Maintz",
- * "age": 24,
- * "eyeColor": "green"
- * },
- * {
- * "firstName": "Aaron",
- * "lastName": "Conran",
- * "age": 26,
- * "eyeColor": "blue"
- * },
- * {
- * "firstName": "Jamie",
- * "lastName": "Avins",
- * "age": 37,
- * "eyeColor": "brown"
- * }
- * ]
- * }
- *
- * ## Inline data
- *
- * Stores can also load data inline. Internally, Store converts each of the objects we pass in as {@link #cfg-data}
- * into Model instances:
- *
- * @example
- * // Set up a model to use in our Store
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * {name: 'firstName', type: 'string'},
- * {name: 'lastName', type: 'string'},
- * {name: 'age', type: 'int'},
- * {name: 'eyeColor', type: 'string'}
- * ]
- * }
- * });
- *
- * Ext.create("Ext.data.Store", {
- * storeId: "usersStore",
- * model: "User",
- * data : [
- * {firstName: "Ed", lastName: "Spencer"},
- * {firstName: "Tommy", lastName: "Maintz"},
- * {firstName: "Aaron", lastName: "Conran"},
- * {firstName: "Jamie", lastName: "Avins"}
- * ]
- * });
- *
- * Ext.create("Ext.List", {
- * fullscreen: true,
- * store: "usersStore",
- * itemTpl: "{lastName}, {firstName}"
- * });
- *
- * Loading inline data using the method above is great if the data is in the correct format already (e.g. it doesn't need
- * to be processed by a {@link Ext.data.reader.Reader reader}). If your inline data requires processing to decode the data structure,
- * use a {@link Ext.data.proxy.Memory MemoryProxy} instead (see the {@link Ext.data.proxy.Memory MemoryProxy} docs for an example).
- *
- * Additional data can also be loaded locally using {@link #method-add}.
- *
- * ## Loading Nested Data
- *
- * Applications often need to load sets of associated data - for example a CRM system might load a User and her Orders.
- * 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
- * and allow the Reader to automatically populate the associated models. Below is a brief example, see the {@link Ext.data.reader.Reader} intro
- * documentation for a full explanation:
- *
- * // Set up a model to use in our Store
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * {name: 'name', type: 'string'},
- * {name: 'id', type: 'int'}
- * ]
- * }
- * });
- *
- * var store = Ext.create('Ext.data.Store', {
- * autoLoad: true,
- * model: "User",
- * proxy: {
- * type: 'ajax',
- * url : 'users.json',
- * reader: {
- * type: 'json',
- * rootProperty: 'users'
- * }
- * }
- * });
- *
- * Ext.create("Ext.List", {
- * fullscreen: true,
- * store: store,
- * itemTpl: "{name} (id: {id})"
- * });
- *
- * Which would consume a response like this:
- *
- * {
- * "users": [
- * {
- * "id": 1,
- * "name": "Ed",
- * "orders": [
- * {
- * "id": 10,
- * "total": 10.76,
- * "status": "invoiced"
- * },
- * {
- * "id": 11,
- * "total": 13.45,
- * "status": "shipped"
- * }
- * ]
- * },
- * {
- * "id": 3,
- * "name": "Tommy",
- * "orders": [
- * ]
- * },
- * {
- * "id": 4,
- * "name": "Jamie",
- * "orders": [
- * {
- * "id": 12,
- * "total": 17.76,
- * "status": "shipped"
- * }
- * ]
- * }
- * ]
- * }
- *
- * See the {@link Ext.data.reader.Reader} intro docs for a full explanation.
- *
- * ## Filtering and Sorting
- *
- * Stores can be sorted and filtered - in both cases either remotely or locally. The {@link #sorters} and {@link #filters} are
- * held inside {@link Ext.util.MixedCollection MixedCollection} instances to make them easy to manage. Usually it is sufficient to
- * either just specify sorters and filters in the Store configuration or call {@link #sort} or {@link #filter}:
- *
- * // Set up a model to use in our Store
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * {name: 'firstName', type: 'string'},
- * {name: 'lastName', type: 'string'},
- * {name: 'age', type: 'int'}
- * ]
- * }
- * });
- *
- * var store = Ext.create("Ext.data.Store", {
- * autoLoad: true,
- * model: "User",
- * proxy: {
- * type: "ajax",
- * url : "users.json",
- * reader: {
- * type: "json",
- * rootProperty: "users"
- * }
- * },
- * sorters: [
- * {
- * property : "age",
- * direction: "DESC"
- * },
- * {
- * property : "firstName",
- * direction: "ASC"
- * }
- * ],
- * filters: [
- * {
- * property: "firstName",
- * value: /Jamie/
- * }
- * ]
- * });
- *
- * Ext.create("Ext.List", {
- * fullscreen: true,
- * store: store,
- * itemTpl: "{lastName}, {firstName} ({age})"
- * });
- *
- * And the data file, _users.json_, is as follows:
- *
- * {
- * "success": true,
- * "users": [
- * {
- * "firstName": "Tommy",
- * "lastName": "Maintz",
- * "age": 24
- * },
- * {
- * "firstName": "Aaron",
- * "lastName": "Conran",
- * "age": 26
- * },
- * {
- * "firstName": "Jamie",
- * "lastName": "Avins",
- * "age": 37
- * }
- * ]
- * }
- *
- * The new Store will keep the configured sorters and filters in the MixedCollection instances mentioned above. By default, sorting
- * and filtering are both performed locally by the Store - see {@link #remoteSort} and {@link #remoteFilter} to allow the server to
- * perform these operations instead.
- *
- * Filtering and sorting after the Store has been instantiated is also easy. Calling {@link #filter} adds another filter to the Store
- * and automatically filters the dataset (calling {@link #filter} with no arguments simply re-applies all existing filters). Note that by
- * default your sorters are automatically reapplied if using local sorting.
- *
- * store.filter('eyeColor', 'Brown');
- *
- * Change the sorting at any time by calling {@link #sort}:
- *
- * store.sort('height', 'ASC');
- *
- * Note that all existing sorters will be removed in favor of the new sorter data (if {@link #sort} is called with no arguments,
- * the existing sorters are just reapplied instead of being removed). To keep existing sorters and add new ones, just add them
- * to the MixedCollection:
- *
- * store.sorters.add(new Ext.util.Sorter({
- * property : 'shoeSize',
- * direction: 'ASC'
- * }));
- *
- * store.sort();
- *
- * ## Registering with StoreManager
- *
- * Any Store that is instantiated with a {@link #storeId} will automatically be registered with the {@link Ext.data.StoreManager StoreManager}.
- * This makes it easy to reuse the same store in multiple views:
- *
- * // this store can be used several times
- * Ext.create('Ext.data.Store', {
- * model: 'User',
- * storeId: 'usersStore'
- * });
- *
- * Ext.create('Ext.List', {
- * store: 'usersStore'
- * // other config goes here
- * });
- *
- * Ext.create('Ext.view.View', {
- * store: 'usersStore'
- * // other config goes here
- * });
- *
- * ## Further Reading
- *
- * Stores are backed up by an ecosystem of classes that enables their operation. To gain a full understanding of these
- * pieces and how they fit together, see:
- *
- * - {@link Ext.data.proxy.Proxy Proxy} - overview of what Proxies are and how they are used
- * - {@link Ext.data.Model Model} - the core class in the data package
- * - {@link Ext.data.reader.Reader Reader} - used by any subclass of {@link Ext.data.proxy.Server ServerProxy} to read a response
- */
- Ext.define('Ext.data.Store', {
- alias: 'store.store',
- extend: 'Ext.Evented',
- requires: [
- 'Ext.util.Collection',
- 'Ext.data.Operation',
- 'Ext.data.proxy.Memory',
- 'Ext.data.Model',
- 'Ext.data.StoreManager',
- 'Ext.util.Grouper'
- ],
- /**
- * @event addrecords
- * Fired when one or more new Model instances have been added to this Store. You should listen
- * for this event if you have to update a representation of the records in this store in your UI.
- * If you need the indices of the records that were added please use the store.indexOf(record) method.
- * @param {Ext.data.Store} store The store
- * @param {Ext.data.Model[]} records The Model instances that were added
- */
- /**
- * @event removerecords
- * Fired when one or more Model instances have been removed from this Store. You should listen
- * for this event if you have to update a representation of the records in this store in your UI.
- * @param {Ext.data.Store} store The Store object
- * @param {Ext.data.Model[]} records The Model instances that was removed
- * @param {Number[]} indices The indices of the records that were removed. These indices already
- * take into account any potential earlier records that you remove. This means that if you loop
- * over the records, you can get its current index in your data representation from this array.
- */
- /**
- * @event updaterecord
- * Fires when a Model instance has been updated
- * @param {Ext.data.Store} this
- * @param {Ext.data.Model} record The Model instance that was updated
- * @param {Number} newIndex If the update changed the index of the record (due to sorting for example), then
- * this gives you the new index in the store.
- * @param {Number} oldIndex If the update changed the index of the record (due to sorting for example), then
- * this gives you the old index in the store.
- * @param {Array} modifiedFieldNames An array containing the field names that have been modified since the
- * record was committed or created
- * @param {Object} modifiedValues An object where each key represents a field name that had it's value modified,
- * and where the value represents the old value for that field. To get the new value in a listener
- * you should use the {@link Ext.data.Model#get get} method.
- */
- /**
- * @event update
- * @inheritdoc Ext.data.Store#updaterecord
- * @removed 2.0 Listen to #updaterecord instead.
- */
- /**
- * @event refresh
- * Fires whenever the records in the Store have changed in a way that your representation of the records
- * need to be entirely refreshed.
- * @param {Ext.data.Store} this The data store
- * @param {Ext.util.Collection} data The data collection containing all the records
- */
- /**
- * @event beforeload
- * Fires before a request is made for a new data object. If the beforeload handler returns false the load
- * action will be canceled. Note that you should not listen for this event in order to refresh the
- * data view. Use the {@link #refresh} event for this instead.
- * @param {Ext.data.Store} store This Store
- * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to
- * load the Store
- */
- /**
- * @event load
- * Fires whenever records have been loaded into the store. Note that you should not listen
- * for this event in order to refresh the data view. Use the {@link #refresh} event for this instead.
- * @param {Ext.data.Store} this
- * @param {Ext.data.Model[]} records An array of records
- * @param {Boolean} successful `true` if the operation was successful.
- * @param {Ext.data.Operation} operation The associated operation.
- */
- /**
- * @event write
- * Fires whenever a successful write has been made via the configured {@link #proxy Proxy}
- * @param {Ext.data.Store} store This Store
- * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object that was used in
- * the write
- */
- /**
- * @event beforesync
- * Fired before a call to {@link #sync} is executed. Return `false` from any listener to cancel the sync
- * @param {Object} options Hash of all records to be synchronized, broken down into create, update and destroy
- */
- /**
- * @event clear
- * Fired after the {@link #removeAll} method is called. Note that you should not listen for this event in order
- * to refresh the data view. Use the {@link #refresh} event for this instead.
- * @param {Ext.data.Store} this
- * @return {Ext.data.Store}
- */
- statics: {
- create: function(store) {
- if (!store.isStore) {
- if (!store.type) {
- store.type = 'store';
- }
- store = Ext.createByAlias('store.' + store.type, store);
- }
- return store;
- }
- },
- isStore: true,
- config: {
- /**
- * @cfg {String} storeId
- * Unique identifier for this store. If present, this Store will be registered with the {@link Ext.data.StoreManager},
- * making it easy to reuse elsewhere.
- * @accessor
- */
- storeId: undefined,
- /**
- * @cfg {Object[]/Ext.data.Model[]} data
- * Array of Model instances or data objects to load locally. See "Inline data" above for details.
- * @accessor
- */
- data: null,
- /**
- * @cfg {Boolean/Object} [autoLoad=false]
- * If data is not specified, and if `autoLoad` is `true` or an Object, this store's load method is automatically called
- * after creation. If the value of `autoLoad` is an Object, this Object will be passed to the store's `load()` method.
- * @accessor
- */
- autoLoad: null,
- /**
- * @cfg {Boolean} autoSync
- * `true` to automatically sync the Store with its Proxy after every edit to one of its Records.
- * @accessor
- */
- autoSync: false,
- /**
- * @cfg {String} model
- * Name of the {@link Ext.data.Model Model} associated with this store.
- * The string is used as an argument for {@link Ext.ModelManager#getModel}.
- * @accessor
- */
- model: undefined,
- /**
- * @cfg {String/Ext.data.proxy.Proxy/Object} proxy The Proxy to use for this Store. This can be either a string, a config
- * object or a Proxy instance - see {@link #setProxy} for details.
- * @accessor
- */
- proxy: undefined,
- /**
- * @cfg {Object[]} fields
- * This may be used in place of specifying a {@link #model} configuration. The fields should be a
- * set of {@link Ext.data.Field} configuration objects. The store will automatically create a {@link Ext.data.Model}
- * with these fields. In general this configuration option should be avoided, it exists for the purposes of
- * backwards compatibility. For anything more complicated, such as specifying a particular id property or
- * associations, a {@link Ext.data.Model} should be defined and specified for the {@link #model}
- * config.
- * @accessor
- */
- fields: null,
- /**
- * @cfg {Boolean} remoteSort
- * `true` to defer any sorting operation to the server. If `false`, sorting is done locally on the client.
- *
- * 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
- * data from the server.
- * @accessor
- */
- remoteSort: false,
- /**
- * @cfg {Boolean} remoteFilter
- * `true` to defer any filtering operation to the server. If `false`, filtering is done locally on the client.
- *
- * 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
- * data from the server.
- * @accessor
- */
- remoteFilter: false,
- /**
- * @cfg {Boolean} remoteGroup
- * `true` to defer any grouping operation to the server. If `false`, grouping is done locally on the client.
- * @accessor
- */
- remoteGroup: false,
- /**
- * @cfg {Object[]} filters
- * Array of {@link Ext.util.Filter Filters} for this store. This configuration is handled by the
- * {@link Ext.mixin.Filterable Filterable} mixin of the {@link Ext.util.Collection data} collection.
- * @accessor
- */
- filters: null,
- /**
- * @cfg {Object[]} sorters
- * Array of {@link Ext.util.Sorter Sorters} for this store. This configuration is handled by the
- * {@link Ext.mixin.Sortable Sortable} mixin of the {@link Ext.util.Collection data} collection.
- * See also the {@link #sort} method.
- * @accessor
- */
- sorters: null,
- /**
- * @cfg {Object[]} grouper
- * A configuration object for this Store's {@link Ext.util.Grouper grouper}.
- *
- * For example, to group a store's items by the first letter of the last name:
- *
- * Ext.define('People', {
- * extend: 'Ext.data.Store',
- *
- * config: {
- * fields: ['first_name', 'last_name'],
- *
- * grouper: {
- * groupFn: function(record) {
- * return record.get('last_name').substr(0, 1);
- * },
- * sortProperty: 'last_name'
- * }
- * }
- * });
- *
- * @accessor
- */
- grouper: null,
- /**
- * @cfg {String} groupField
- * The (optional) field by which to group data in the store. Internally, grouping is very similar to sorting - the
- * groupField and {@link #groupDir} are injected as the first sorter (see {@link #sort}). Stores support a single
- * level of grouping, and groups can be fetched via the {@link #getGroups} method.
- * @accessor
- */
- groupField: null,
- /**
- * @cfg {String} groupDir
- * The direction in which sorting should be applied when grouping. If you specify a grouper by using the {@link #groupField}
- * configuration, this will automatically default to "ASC" - the other supported value is "DESC"
- * @accessor
- */
- groupDir: null,
- /**
- * @cfg {Function} getGroupString This function will be passed to the {@link #grouper} configuration as it's `groupFn`.
- * Note that this configuration is deprecated and grouper: `{groupFn: yourFunction}}` is preferred.
- * @deprecated
- * @accessor
- */
- getGroupString: null,
- /**
- * @cfg {Number} pageSize
- * The number of records considered to form a 'page'. This is used to power the built-in
- * paging using the nextPage and previousPage functions.
- * @accessor
- */
- pageSize: 25,
- /**
- * @cfg {Number} totalCount The total number of records in the full dataset, as indicated by a server. If the
- * server-side dataset contains 5000 records but only returns pages of 50 at a time, `totalCount` will be set to
- * 5000 and {@link #getCount} will return 50
- */
- totalCount: null,
- /**
- * @cfg {Boolean} clearOnPageLoad `true` to empty the store when loading another page via {@link #loadPage},
- * {@link #nextPage} or {@link #previousPage}. Setting to `false` keeps existing records, allowing
- * large data sets to be loaded one page at a time but rendered all together.
- * @accessor
- */
- clearOnPageLoad: true,
- modelDefaults: {},
- /**
- * @cfg {Boolean} autoDestroy This is a private configuration used in the framework whether this Store
- * can be destroyed.
- * @private
- */
- autoDestroy: false,
- /**
- * @cfg {Boolean} syncRemovedRecords This configuration allows you to disable the synchronization of
- * removed records on this Store. By default, when you call `removeAll()` or `remove()`, records will be added
- * to an internal removed array. When you then sync the Store, we send a destroy request for these records.
- * If you don't want this to happen, you can set this configuration to `false`.
- */
- syncRemovedRecords: true,
- /**
- * @cfg {Boolean} destroyRemovedRecords This configuration allows you to prevent destroying record
- * instances when they are removed from this store and are not in any other store.
- */
- destroyRemovedRecords: true
- },
- /**
- * @property {Number} currentPage
- * The page that the Store has most recently loaded (see {@link #loadPage})
- */
- currentPage: 1,
- constructor: function(config) {
- config = config || {};
- this.data = this._data = this.createDataCollection();
- this.data.setSortRoot('data');
- this.data.setFilterRoot('data');
- this.removed = [];
- if (config.id && !config.storeId) {
- config.storeId = config.id;
- delete config.id;
- }
- this.initConfig(config);
- this.callParent(arguments);
- },
- /**
- * @private
- * @return {Ext.util.Collection}
- */
- createDataCollection: function() {
- return new Ext.util.Collection(function(record) {
- return record.getId();
- });
- },
- applyStoreId: function(storeId) {
- if (storeId === undefined || storeId === null) {
- storeId = this.getUniqueId();
- }
- return storeId;
- },
- updateStoreId: function(storeId, oldStoreId) {
- if (oldStoreId) {
- Ext.data.StoreManager.unregister(this);
- }
- if (storeId) {
- Ext.data.StoreManager.register(this);
- }
- },
- applyModel: function(model) {
- if (typeof model == 'string') {
- var registeredModel = Ext.data.ModelManager.getModel(model);
- if (!registeredModel) {
- Ext.Logger.error('Model with name "' + model + '" does not exist.');
- }
- model = registeredModel;
- }
- if (model && !model.prototype.isModel && Ext.isObject(model)) {
- model = Ext.data.ModelManager.registerType(model.storeId || model.id || Ext.id(), model);
- }
- if (!model) {
- var fields = this.getFields(),
- data = this.config.data;
- if (!fields && data && data.length) {
- fields = Ext.Object.getKeys(data[0]);
- }
- if (fields) {
- model = Ext.define('Ext.data.Store.ImplicitModel-' + (this.getStoreId() || Ext.id()), {
- extend: 'Ext.data.Model',
- config: {
- fields: fields,
- proxy: this.getProxy()
- }
- });
- this.implicitModel = true;
- }
- }
- if (!model && this.getProxy()) {
- model = this.getProxy().getModel();
- }
- // <debug>
- if (!model) {
- 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');
- }
- // </debug>
- return model;
- },
- updateModel: function(model) {
- var proxy = this.getProxy();
- if (proxy && !proxy.getModel()) {
- proxy.setModel(model);
- }
- },
- applyProxy: function(proxy, currentProxy) {
- proxy = Ext.factory(proxy, Ext.data.Proxy, currentProxy, 'proxy');
- if (!proxy && this.getModel()) {
- proxy = this.getModel().getProxy();
- }
- if (!proxy) {
- proxy = new Ext.data.proxy.Memory({
- model: this.getModel()
- });
- }
- if (proxy.isMemoryProxy) {
- this.setSyncRemovedRecords(false);
- }
- return proxy;
- },
- updateProxy: function(proxy) {
- if (proxy) {
- if (!proxy.getModel()) {
- proxy.setModel(this.getModel());
- }
- proxy.on('metachange', this.onMetaChange, this);
- }
- },
- /**
- * We are using applyData so that we can return nothing and prevent the `this.data`
- * property to be overridden.
- * @param data
- */
- applyData: function(data) {
- var me = this,
- proxy;
- if (data) {
- proxy = me.getProxy();
- if (proxy instanceof Ext.data.proxy.Memory) {
- proxy.setData(data);
- me.load();
- return;
- } else {
- // We make it silent because we don't want to fire a refresh event
- me.removeAll(true);
- // This means we have to fire a clear event though
- me.fireEvent('clear', me);
- // We don't want to fire addrecords event since we will be firing
- // a refresh event later which will already take care of updating
- // any views bound to this store
- me.suspendEvents();
- me.add(data);
- me.resumeEvents();
- // We set this to true so isAutoLoading to try
- me.dataLoaded = true;
- }
- } else {
- me.removeAll(true);
- // This means we have to fire a clear event though
- me.fireEvent('clear', me);
- }
- me.fireEvent('refresh', me, me.data);
- },
- clearData: function() {
- this.setData(null);
- },
- addData: function(data) {
- var reader = this.getProxy().getReader(),
- resultSet = reader.read(data),
- records = resultSet.getRecords();
- this.add(records);
- },
- updateAutoLoad: function(autoLoad) {
- var proxy = this.getProxy();
- if (autoLoad && (proxy && !proxy.isMemoryProxy)) {
- this.load(Ext.isObject(autoLoad) ? autoLoad : null);
- }
- },
- /**
- * Returns `true` if the Store is set to {@link #autoLoad} or is a type which loads upon instantiation.
- * @return {Boolean}
- */
- isAutoLoading: function() {
- var proxy = this.getProxy();
- return (this.getAutoLoad() || (proxy && proxy.isMemoryProxy) || this.dataLoaded);
- },
- updateGroupField: function(groupField) {
- var grouper = this.getGrouper();
- if (groupField) {
- if (!grouper) {
- this.setGrouper({
- property: groupField,
- direction: this.getGroupDir() || 'ASC'
- });
- } else {
- grouper.setProperty(groupField);
- }
- } else if (grouper) {
- this.setGrouper(null);
- }
- },
- updateGroupDir: function(groupDir) {
- var grouper = this.getGrouper();
- if (grouper) {
- grouper.setDirection(groupDir);
- }
- },
- applyGetGroupString: function(getGroupStringFn) {
- var grouper = this.getGrouper();
- if (getGroupStringFn) {
- // <debug>
- Ext.Logger.warn('Specifying getGroupString on a store has been deprecated. Please use grouper: {groupFn: yourFunction}');
- // </debug>
- if (grouper) {
- grouper.setGroupFn(getGroupStringFn);
- } else {
- this.setGrouper({
- groupFn: getGroupStringFn
- });
- }
- } else if (grouper) {
- this.setGrouper(null);
- }
- },
- applyGrouper: function(grouper) {
- if (typeof grouper == 'string') {
- grouper = {
- property: grouper
- };
- }
- else if (typeof grouper == 'function') {
- grouper = {
- groupFn: grouper
- };
- }
- grouper = Ext.factory(grouper, Ext.util.Grouper, this.getGrouper());
- return grouper;
- },
- updateGrouper: function(grouper, oldGrouper) {
- var data = this.data;
- if (oldGrouper) {
- data.removeSorter(oldGrouper);
- if (!grouper) {
- data.getSorters().removeSorter('isGrouper');
- }
- }
- if (grouper) {
- data.insertSorter(0, grouper);
- if (!oldGrouper) {
- data.getSorters().addSorter({
- direction: 'DESC',
- property: 'isGrouper',
- transform: function(value) {
- return (value === true) ? 1 : -1;
- }
- });
- }
- }
- },
- /**
- * This method tells you if this store has a grouper defined on it.
- * @return {Boolean} `true` if this store has a grouper defined.
- */
- isGrouped: function() {
- return !!this.getGrouper();
- },
- updateSorters: function(sorters) {
- var grouper = this.getGrouper(),
- data = this.data,
- autoSort = data.getAutoSort();
- // While we remove/add sorters we don't want to automatically sort because we still need
- // to apply any field sortTypes as transforms on the Sorters after we have added them.
- data.setAutoSort(false);
- data.setSorters(sorters);
- if (grouper) {
- data.insertSorter(0, grouper);
- }
- this.updateSortTypes();
- // Now we put back autoSort on the Collection to the value it had before. If it was
- // auto sorted, setting this back will cause it to sort right away.
- data.setAutoSort(autoSort);
- },
- updateSortTypes: function() {
- var model = this.getModel(),
- fields = model && model.getFields(),
- data = this.data;
- // We loop over each sorter and set it's transform method to the every field's sortType.
- if (fields) {
- data.getSorters().each(function(sorter) {
- var property = sorter.getProperty(),
- field;
- if (!sorter.isGrouper && property && !sorter.getTransform()) {
- field = fields.get(property);
- if (field) {
- sorter.setTransform(field.getSortType());
- }
- }
- });
- }
- },
- updateFilters: function(filters) {
- this.data.setFilters(filters);
- },
- /**
- * Adds Model instance to the Store. This method accepts either:
- *
- * - An array of Model instances or Model configuration objects.
- * - Any number of Model instance or Model configuration object arguments.
- *
- * The new Model instances will be added at the end of the existing collection.
- *
- * Sample usage:
- *
- * myStore.add({some: 'data2'}, {some: 'other data2'});
- *
- * @param {Ext.data.Model[]/Ext.data.Model...} model An array of Model instances
- * or Model configuration objects, or variable number of Model instance or config arguments.
- * @return {Ext.data.Model[]} The model instances that were added.
- */
- add: function(records) {
- if (!Ext.isArray(records)) {
- records = Array.prototype.slice.call(arguments);
- }
- return this.insert(this.data.length, records);
- },
- /**
- * Inserts Model instances into the Store at the given index and fires the {@link #add} event.
- * See also `{@link #add}`.
- * @param {Number} index The start index at which to insert the passed Records.
- * @param {Ext.data.Model[]} records An Array of Ext.data.Model objects to add to the cache.
- * @return {Object}
- */
- insert: function(index, records) {
- if (!Ext.isArray(records)) {
- records = Array.prototype.slice.call(arguments, 1);
- }
- var me = this,
- sync = false,
- data = this.data,
- ln = records.length,
- Model = this.getModel(),
- modelDefaults = me.getModelDefaults(),
- added = false,
- i, record;
- records = records.slice();
- for (i = 0; i < ln; i++) {
- record = records[i];
- if (!record.isModel) {
- record = new Model(record);
- }
- // If we are adding a record that is already an instance which was still in the
- // removed array, then we remove it from the removed array
- else if (this.removed.indexOf(record) != -1) {
- Ext.Array.remove(this.removed, record);
- }
- record.set(modelDefaults);
- record.join(me);
- records[i] = record;
- // If this is a newly created record, then we might want to sync it later
- sync = sync || (record.phantom === true);
- }
- // Now we insert all these records in one go to the collection. Saves many function
- // calls to data.insert. Does however create two loops over the records we are adding.
- if (records.length === 1) {
- added = data.insert(index, records[0]);
- if (added) {
- added = [added];
- }
- } else {
- added = data.insertAll(index, records);
- }
- if (added) {
- me.fireEvent('addrecords', me, added);
- }
- if (me.getAutoSync() && sync) {
- me.sync();
- }
- return records;
- },
- /**
- * Removes the given record from the Store, firing the `removerecords` event passing all the instances that are removed.
- * @param {Ext.data.Model/Ext.data.Model[]} records Model instance or array of instances to remove.
- */
- remove: function (records) {
- if (records.isModel) {
- records = [records];
- }
- var me = this,
- sync = false,
- i = 0,
- autoSync = this.getAutoSync(),
- syncRemovedRecords = me.getSyncRemovedRecords(),
- destroyRemovedRecords = this.getDestroyRemovedRecords(),
- ln = records.length,
- indices = [],
- removed = [],
- isPhantom,
- items = me.data.items,
- record, index, j;
- for (; i < ln; i++) {
- record = records[i];
- if (me.data.contains(record)) {
- isPhantom = (record.phantom === true);
- index = items.indexOf(record);
- if (index !== -1) {
- removed.push(record);
- indices.push(index);
- }
- record.unjoin(me);
- me.data.remove(record);
- if (destroyRemovedRecords && !syncRemovedRecords && !record.stores.length) {
- record.destroy();
- }
- else if (!isPhantom && syncRemovedRecords) {
- // don't push phantom records onto removed
- me.removed.push(record);
- }
- sync = sync || !isPhantom;
- }
- }
- me.fireEvent('removerecords', me, removed, indices);
- if (autoSync && sync) {
- me.sync();
- }
- },
- /**
- * Removes the model instance at the given index.
- * @param {Number} index The record index.
- */
- removeAt: function(index) {
- var record = this.getAt(index);
- if (record) {
- this.remove(record);
- }
- },
- /**
- * Remove all items from the store.
- * @param {Boolean} silent Prevent the `clear` event from being fired.
- */
- removeAll: function(silent) {
- if (silent !== true && this.eventFiringSuspended !== true) {
- this.fireAction('clear', [this], 'doRemoveAll');
- } else {
- this.doRemoveAll.call(this, true);
- }
- },
- doRemoveAll: function (silent) {
- var me = this,
- destroyRemovedRecords = this.getDestroyRemovedRecords(),
- syncRemovedRecords = this.getSyncRemovedRecords(),
- records = me.data.all.slice(),
- ln = records.length,
- i, record;
- for (i = 0; i < ln; i++) {
- record = records[i];
- record.unjoin(me);
- if (destroyRemovedRecords && !syncRemovedRecords && !record.stores.length) {
- record.destroy();
- }
- else if (record.phantom !== true && syncRemovedRecords) {
- me.removed.push(record);
- }
- }
- me.data.clear();
- if (silent !== true) {
- me.fireEvent('refresh', me, me.data);
- }
- if (me.getAutoSync()) {
- this.sync();
- }
- },
- /**
- * Calls the specified function for each of the {@link Ext.data.Model Records} in the cache.
- *
- * // Set up a model to use in our Store
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * {name: 'firstName', type: 'string'},
- * {name: 'lastName', type: 'string'}
- * ]
- * }
- * });
- *
- * var store = Ext.create('Ext.data.Store', {
- * model: 'User',
- * data : [
- * {firstName: 'Ed', lastName: 'Spencer'},
- * {firstName: 'Tommy', lastName: 'Maintz'},
- * {firstName: 'Aaron', lastName: 'Conran'},
- * {firstName: 'Jamie', lastName: 'Avins'}
- * ]
- * });
- *
- * store.each(function (item, index, length) {
- * console.log(item.get('firstName'), index);
- * });
- *
- * @param {Function} fn The function to call. Returning `false` aborts and exits the iteration.
- * @param {Ext.data.Model} fn.item
- * @param {Number} fn.index
- * @param {Number} fn.length
- * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
- * Defaults to the current {@link Ext.data.Model Record} in the iteration.
- */
- each: function(fn, scope) {
- this.data.each(fn, scope);
- },
- /**
- * Gets the number of cached records. Note that filtered records are not included in this count.
- * If using paging, this may not be the total size of the dataset.
- * @return {Number} The number of Records in the Store's cache.
- */
- getCount: function() {
- return this.data.items.length || 0;
- },
- /**
- * Gets the number of all cached records including the ones currently filtered.
- * If using paging, this may not be the total size of the dataset.
- * @return {Number} The number of all Records in the Store's cache.
- */
- getAllCount: function () {
- return this.data.all.length || 0;
- },
- /**
- * Get the Record at the specified index.
- * @param {Number} index The index of the Record to find.
- * @return {Ext.data.Model/undefined} The Record at the passed index. Returns `undefined` if not found.
- */
- getAt: function(index) {
- return this.data.getAt(index);
- },
- /**
- * Returns a range of Records between specified indices.
- * @param {Number} [startIndex=0] (optional) The starting index.
- * @param {Number} [endIndex=-1] (optional) The ending index (defaults to the last Record in the Store).
- * @return {Ext.data.Model[]} An array of Records.
- */
- getRange: function(start, end) {
- return this.data.getRange(start, end);
- },
- /**
- * Get the Record with the specified id.
- * @param {String} id The id of the Record to find.
- * @return {Ext.data.Model/undefined} The Record with the passed id. Returns `undefined` if not found.
- */
- getById: function(id) {
- return this.data.findBy(function(record) {
- return record.getId() == id;
- });
- },
- /**
- * Get the index within the cache of the passed Record.
- * @param {Ext.data.Model} record The Ext.data.Model object to find.
- * @return {Number} The index of the passed Record. Returns -1 if not found.
- */
- indexOf: function(record) {
- return this.data.indexOf(record);
- },
- /**
- * Get the index within the cache of the Record with the passed id.
- * @param {String} id The id of the Record to find.
- * @return {Number} The index of the Record. Returns -1 if not found.
- */
- indexOfId: function(id) {
- return this.data.indexOfKey(id);
- },
- /**
- * @private
- * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
- * @param {Ext.data.Model} record The model instance that was edited.
- * @param {String[]} modifiedFieldNames Array of field names changed during edit.
- */
- afterEdit: function(record, modifiedFieldNames, modified) {
- var me = this,
- data = me.data,
- currentId = modified[record.getIdProperty()] || record.getId(),
- currentIndex = data.keys.indexOf(currentId),
- newIndex;
- if (currentIndex === -1 && data.map[currentId] === undefined) {
- return;
- }
- if (me.getAutoSync()) {
- me.sync();
- }
- if (currentId !== record.getId()) {
- data.replace(currentId, record);
- } else {
- data.replace(record);
- }
- newIndex = data.indexOf(record);
- if (currentIndex === -1 && newIndex !== -1) {
- me.fireEvent('addrecords', me, [record]);
- }
- else if (currentIndex !== -1 && newIndex === -1) {
- me.fireEvent('removerecords', me, [record], [currentIndex]);
- }
- else if (newIndex !== -1) {
- me.fireEvent('updaterecord', me, record, newIndex, currentIndex, modifiedFieldNames, modified);
- }
- },
- /**
- * @private
- * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
- * @param {Ext.data.Model} record The model instance that was edited.
- */
- afterReject: function(record) {
- var index = this.data.indexOf(record);
- this.fireEvent('updaterecord', this, record, index, index, [], {});
- },
- /**
- * @private
- * A model instance should call this method on the Store it has been {@link Ext.data.Model#join joined} to.
- * @param {Ext.data.Model} record The model instance that was edited.
- */
- afterCommit: function(record, modifiedFieldNames, modified) {
- var me = this,
- data = me.data,
- currentId = modified[record.getIdProperty()] || record.getId(),
- currentIndex = data.keys.indexOf(currentId),
- newIndex;
- if (currentIndex === -1 && data.map[currentId] === undefined) {
- return;
- }
- if (currentId !== record.getId()) {
- data.replace(currentId, record);
- } else {
- data.replace(record);
- }
- newIndex = data.indexOf(record);
- if (currentIndex === -1 && newIndex !== -1) {
- me.fireEvent('addrecords', me, [record]);
- }
- else if (currentIndex !== -1 && newIndex === -1) {
- me.fireEvent('removerecords', me, [record], [currentIndex]);
- }
- else if (newIndex !== -1) {
- me.fireEvent('updaterecord', me, record, newIndex, currentIndex, modifiedFieldNames, modified);
- }
- },
- /**
- * This gets called by a record after is gets erased from the server.
- * @param record
- * @private
- */
- afterErase: function(record) {
- var me = this,
- data = me.data,
- index = data.indexOf(record);
- if (index !== -1) {
- data.remove(record);
- me.fireEvent('removerecords', me, [record], [index]);
- }
- },
- updateRemoteFilter: function(remoteFilter) {
- this.data.setAutoFilter(!remoteFilter);
- },
- updateRemoteSort: function(remoteSort) {
- this.data.setAutoSort(!remoteSort);
- },
- /**
- * Sorts the data in the Store by one or more of its properties. Example usage:
- *
- * // sort by a single field
- * myStore.sort('myField', 'DESC');
- *
- * // sorting by multiple fields
- * myStore.sort([
- * {
- * property : 'age',
- * direction: 'ASC'
- * },
- * {
- * property : 'name',
- * direction: 'DESC'
- * }
- * ]);
- *
- * Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates
- * the actual sorting to its internal {@link Ext.util.Collection}.
- *
- * When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:
- *
- * store.sort('myField');
- * store.sort('myField');
- *
- * is equivalent to this code:
- *
- * store.sort('myField', 'ASC');
- * store.sort('myField', 'DESC');
- *
- * because Store handles the toggling automatically.
- *
- * If the {@link #remoteSort} configuration has been set to `true`, you will have to manually call the {@link #method-load}
- * method after you sort to retrieve the sorted data from the server.
- *
- * @param {String/Ext.util.Sorter[]} sorters Either a string name of one of the fields in this Store's configured
- * {@link Ext.data.Model Model}, or an array of sorter configurations.
- * @param {String} [defaultDirection=ASC] The default overall direction to sort the data by.
- * @param {String} where (Optional) This can be either `'prepend'` or `'append'`. If you leave this undefined
- * it will clear the current sorters.
- */
- sort: function(sorters, defaultDirection, where) {
- var data = this.data,
- grouper = this.getGrouper(),
- autoSort = data.getAutoSort();
- if (sorters) {
- // While we are adding sorters we don't want to sort right away
- // since we need to update sortTypes on the sorters.
- data.setAutoSort(false);
- if (typeof where === 'string') {
- if (where == 'prepend') {
- data.insertSorters(grouper ? 1 : 0, sorters, defaultDirection);
- } else {
- data.addSorters(sorters, defaultDirection);
- }
- } else {
- data.setSorters(null);
- if (grouper) {
- data.addSorters(grouper);
- }
- data.addSorters(sorters, defaultDirection);
- }
- this.updateSortTypes();
- // Setting back autoSort to true (if it was like that before) will
- // instantly sort the data again.
- data.setAutoSort(autoSort);
- }
- if (!this.getRemoteSort()) {
- // If we haven't added any new sorters we have to manually call sort
- if (!sorters) {
- this.data.sort();
- }
- this.fireEvent('sort', this, this.data, this.data.getSorters());
- if (data.length) {
- this.fireEvent('refresh', this, this.data);
- }
- }
- },
- /**
- * Filters the loaded set of records by a given set of filters.
- *
- * Filtering by single field:
- *
- * store.filter("email", /\.com$/);
- *
- * Using multiple filters:
- *
- * store.filter([
- * {property: "email", value: /\.com$/},
- * {filterFn: function(item) { return item.get("age") > 10; }}
- * ]);
- *
- * Using Ext.util.Filter instances instead of config objects
- * (note that we need to specify the {@link Ext.util.Filter#root root} config option in this case):
- *
- * store.filter([
- * Ext.create('Ext.util.Filter', {property: "email", value: /\.com$/, root: 'data'}),
- * Ext.create('Ext.util.Filter', {filterFn: function(item) { return item.get("age") > 10; }, root: 'data'})
- * ]);
- *
- * If the {@link #remoteFilter} configuration has been set to `true`, you will have to manually call the {@link #method-load}
- * method after you filter to retrieve the filtered data from the server.
- *
- * @param {Object[]/Ext.util.Filter[]/String} filters The set of filters to apply to the data.
- * These are stored internally on the store, but the filtering itself is done on the Store's
- * {@link Ext.util.MixedCollection MixedCollection}. See MixedCollection's
- * {@link Ext.util.MixedCollection#filter filter} method for filter syntax.
- * Alternatively, pass in a property string.
- * @param {String} [value] value to filter by (only if using a property string as the first argument).
- * @param {Boolean} [anyMatch=false] `true` to allow any match, false to anchor regex beginning with `^`.
- * @param {Boolean} [caseSensitive=false] `true` to make the filtering regex case sensitive.
- */
- filter: function(property, value, anyMatch, caseSensitive) {
- var data = this.data,
- filter = null;
- if (property) {
- if (Ext.isFunction(property)) {
- filter = {filterFn: property};
- }
- else if (Ext.isArray(property) || property.isFilter) {
- filter = property;
- }
- else {
- filter = {
- property : property,
- value : value,
- anyMatch : anyMatch,
- caseSensitive: caseSensitive,
- id : property
- }
- }
- }
- if (this.getRemoteFilter()) {
- data.addFilters(filter);
- } else {
- data.filter(filter);
- this.fireEvent('filter', this, data, data.getFilters());
- this.fireEvent('refresh', this, data);
- }
- },
- /**
- * Filter by a function. The specified function will be called for each
- * Record in this Store. If the function returns `true` the Record is included,
- * otherwise it is filtered out.
- * @param {Function} fn The function to be called. It will be passed the following parameters:
- * @param {Ext.data.Model} fn.record The {@link Ext.data.Model record}
- * to test for filtering. Access field values using {@link Ext.data.Model#get}.
- * @param {Object} fn.id The ID of the Record passed.
- * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to this Store.
- */
- filterBy: function(fn, scope) {
- var me = this,
- data = me.data,
- ln = data.length;
- data.filter({
- filterFn: function(record) {
- return fn.call(scope || me, record, record.getId())
- }
- });
- this.fireEvent('filter', this, data, data.getFilters());
- if (data.length !== ln) {
- this.fireEvent('refresh', this, data);
- }
- },
- /**
- * Query the cached records in this Store using a filtering function. The specified function
- * will be called with each record in this Store. If the function returns `true` the record is
- * included in the results.
- * @param {Function} fn The function to be called. It will be passed the following parameters:
- * @param {Ext.data.Model} fn.record The {@link Ext.data.Model record}
- * to test for filtering. Access field values using {@link Ext.data.Model#get}.
- * @param {Object} fn.id The ID of the Record passed.
- * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to this Store.
- * @return {Ext.util.MixedCollection} Returns an Ext.util.MixedCollection of the matched records.
- */
- queryBy: function(fn, scope) {
- return this.data.filterBy(fn, scope || this);
- },
- /**
- * Reverts to a view of the Record cache with no filtering applied.
- * @param {Boolean} [suppressEvent=false] `true` to clear silently without firing the `refresh` event.
- */
- clearFilter: function(suppressEvent) {
- var ln = this.data.length;
- if (suppressEvent) {
- this.suspendEvents();
- }
- this.data.setFilters(null);
- if (suppressEvent) {
- this.resumeEvents();
- } else if (ln !== this.data.length) {
- this.fireEvent('refresh', this, this.data);
- }
- },
- /**
- * Returns `true` if this store is currently filtered.
- * @return {Boolean}
- */
- isFiltered : function () {
- return this.data.filtered;
- },
- /**
- * Returns `true` if this store is currently sorted.
- * @return {Boolean}
- */
- isSorted : function () {
- return this.data.sorted;
- },
- getSorters: function() {
- var sorters = this.data.getSorters();
- return (sorters) ? sorters.items : [];
- },
- getFilters: function() {
- var filters = this.data.getFilters();
- return (filters) ? filters.items : [];
- },
- /**
- * Returns an array containing the result of applying the grouper to the records in this store. See {@link #groupField},
- * {@link #groupDir} and {@link #grouper}. Example for a store containing records with a color field:
- *
- * var myStore = Ext.create('Ext.data.Store', {
- * groupField: 'color',
- * groupDir : 'DESC'
- * });
- *
- * myStore.getGroups(); //returns:
- * [
- * {
- * name: 'yellow',
- * children: [
- * //all records where the color field is 'yellow'
- * ]
- * },
- * {
- * name: 'red',
- * children: [
- * //all records where the color field is 'red'
- * ]
- * }
- * ]
- *
- * @param {String} groupName (Optional) Pass in an optional `groupName` argument to access a specific group as defined by {@link #grouper}.
- * @return {Object/Object[]} The grouped data.
- */
- getGroups: function(requestGroupString) {
- var records = this.data.items,
- length = records.length,
- grouper = this.getGrouper(),
- groups = [],
- pointers = {},
- record,
- groupStr,
- group,
- i;
- // <debug>
- if (!grouper) {
- Ext.Logger.error('Trying to get groups for a store that has no grouper');
- }
- // </debug>
- for (i = 0; i < length; i++) {
- record = records[i];
- groupStr = grouper.getGroupString(record);
- group = pointers[groupStr];
- if (group === undefined) {
- group = {
- name: groupStr,
- children: []
- };
- groups.push(group);
- pointers[groupStr] = group;
- }
- group.children.push(record);
- }
- return requestGroupString ? pointers[requestGroupString] : groups;
- },
- /**
- * @param record
- * @return {null}
- */
- getGroupString: function(record) {
- var grouper = this.getGrouper();
- if (grouper) {
- return grouper.getGroupString(record);
- }
- return null;
- },
- /**
- * Finds the index of the first matching Record in this store by a specific field value.
- * @param {String} fieldName The name of the Record field to test.
- * @param {String/RegExp} value Either a string that the field value
- * should begin with, or a RegExp to test against the field.
- * @param {Number} startIndex (optional) The index to start searching at.
- * @param {Boolean} anyMatch (optional) `true` to match any part of the string, not just the beginning.
- * @param {Boolean} caseSensitive (optional) `true` for case sensitive comparison.
- * @param {Boolean} [exactMatch=false] (optional) `true` to force exact match (^ and $ characters added to the regex).
- * @return {Number} The matched index or -1
- */
- find: function(fieldName, value, startIndex, anyMatch, caseSensitive, exactMatch) {
- var filter = Ext.create('Ext.util.Filter', {
- property: fieldName,
- value: value,
- anyMatch: anyMatch,
- caseSensitive: caseSensitive,
- exactMatch: exactMatch,
- root: 'data'
- });
- return this.data.findIndexBy(filter.getFilterFn(), null, startIndex);
- },
- /**
- * Finds the first matching Record in this store by a specific field value.
- * @param {String} fieldName The name of the Record field to test.
- * @param {String/RegExp} value Either a string that the field value
- * should begin with, or a RegExp to test against the field.
- * @param {Number} startIndex (optional) The index to start searching at.
- * @param {Boolean} anyMatch (optional) `true` to match any part of the string, not just the beginning.
- * @param {Boolean} caseSensitive (optional) `true` for case sensitive comparison.
- * @param {Boolean} [exactMatch=false] (optional) `true` to force exact match (^ and $ characters added to the regex).
- * @return {Ext.data.Model} The matched record or `null`.
- */
- findRecord: function() {
- var me = this,
- index = me.find.apply(me, arguments);
- return index !== -1 ? me.getAt(index) : null;
- },
- /**
- * Finds the index of the first matching Record in this store by a specific field value.
- * @param {String} fieldName The name of the Record field to test.
- * @param {Object} value The value to match the field against.
- * @param {Number} startIndex (optional) The index to start searching at.
- * @return {Number} The matched index or -1.
- */
- findExact: function(fieldName, value, startIndex) {
- return this.data.findIndexBy(function(record) {
- return record.get(fieldName) === value;
- }, this, startIndex);
- },
- /**
- * Find the index of the first matching Record in this Store by a function.
- * If the function returns `true` it is considered a match.
- * @param {Function} fn The function to be called. It will be passed the following parameters:
- * @param {Ext.data.Model} fn.record The {@link Ext.data.Model record}
- * to test for filtering. Access field values using {@link Ext.data.Model#get}.
- * @param {Object} fn.id The ID of the Record passed.
- * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to this Store.
- * @param {Number} startIndex (optional) The index to start searching at.
- * @return {Number} The matched index or -1.
- */
- findBy: function(fn, scope, startIndex) {
- return this.data.findIndexBy(fn, scope, startIndex);
- },
- /**
- * Loads data into the Store via the configured {@link #proxy}. This uses the Proxy to make an
- * asynchronous call to whatever storage backend the Proxy uses, automatically adding the retrieved
- * instances into the Store and calling an optional callback if required. Example usage:
- *
- * store.load({
- * callback: function(records, operation, success) {
- * // the {@link Ext.data.Operation operation} object contains all of the details of the load operation
- * console.log(records);
- * },
- * scope: this
- * });
- *
- * If only the callback and scope options need to be specified, then one can call it simply like so:
- *
- * store.load(function(records, operation, success) {
- * console.log('loaded records');
- * }, this);
- *
- * @param {Object/Function} [options] config object, passed into the {@link Ext.data.Operation} object before loading.
- * @param {Object} [scope] Scope for the function.
- * @return {Object}
- */
- load: function(options, scope) {
- var me = this,
- operation,
- currentPage = me.currentPage,
- pageSize = me.getPageSize();
- options = options || {};
- if (Ext.isFunction(options)) {
- options = {
- callback: options,
- scope: scope || this
- };
- }
- if (me.getRemoteSort()) {
- options.sorters = options.sorters || this.getSorters();
- }
- if (me.getRemoteFilter()) {
- options.filters = options.filters || this.getFilters();
- }
- if (me.getRemoteGroup()) {
- options.grouper = options.grouper || this.getGrouper();
- }
- Ext.applyIf(options, {
- page: currentPage,
- start: (currentPage - 1) * pageSize,
- limit: pageSize,
- addRecords: false,
- action: 'read',
- model: this.getModel()
- });
- operation = Ext.create('Ext.data.Operation', options);
- if (me.fireEvent('beforeload', me, operation) !== false) {
- me.loading = true;
- me.getProxy().read(operation, me.onProxyLoad, me);
- }
- return me;
- },
- /**
- * Returns `true` if the Store is currently performing a load operation.
- * @return {Boolean} `true` if the Store is currently loading.
- */
- isLoading: function() {
- return Boolean(this.loading);
- },
- /**
- * Returns `true` if the Store has been loaded.
- * @return {Boolean} `true` if the Store has been loaded.
- */
- isLoaded: function() {
- return Boolean(this.loaded);
- },
- /**
- * Synchronizes the Store with its Proxy. This asks the Proxy to batch together any new, updated
- * and deleted records in the store, updating the Store's internal representation of the records
- * as each operation completes.
- * @return {Object}
- * @return {Object} return.added
- * @return {Object} return.updated
- * @return {Object} return.removed
- */
- sync: function() {
- var me = this,
- operations = {},
- toCreate = me.getNewRecords(),
- toUpdate = me.getUpdatedRecords(),
- toDestroy = me.getRemovedRecords(),
- needsSync = false;
- if (toCreate.length > 0) {
- operations.create = toCreate;
- needsSync = true;
- }
- if (toUpdate.length > 0) {
- operations.update = toUpdate;
- needsSync = true;
- }
- if (toDestroy.length > 0) {
- operations.destroy = toDestroy;
- needsSync = true;
- }
- if (needsSync && me.fireEvent('beforesync', this, operations) !== false) {
- me.getProxy().batch({
- operations: operations,
- listeners: me.getBatchListeners()
- });
- }
- return {
- added: toCreate,
- updated: toUpdate,
- removed: toDestroy
- };
- },
- /**
- * Convenience function for getting the first model instance in the store.
- * @return {Ext.data.Model/undefined} The first model instance in the store, or `undefined`.
- */
- first: function() {
- return this.data.first();
- },
- /**
- * Convenience function for getting the last model instance in the store.
- * @return {Ext.data.Model/undefined} The last model instance in the store, or `undefined`.
- */
- last: function() {
- return this.data.last();
- },
- /**
- * Sums the value of `property` for each {@link Ext.data.Model record} between `start`
- * and `end` and returns the result.
- * @param {String} field The field in each record.
- * @return {Number} The sum.
- */
- sum: function(field) {
- var total = 0,
- i = 0,
- records = this.data.items,
- len = records.length;
- for (; i < len; ++i) {
- total += records[i].get(field);
- }
- return total;
- },
- /**
- * Gets the minimum value in the store.
- * @param {String} field The field in each record.
- * @return {Object/undefined} The minimum value, if no items exist, `undefined`.
- */
- min: function(field) {
- var i = 1,
- records = this.data.items,
- len = records.length,
- value, min;
- if (len > 0) {
- min = records[0].get(field);
- }
- for (; i < len; ++i) {
- value = records[i].get(field);
- if (value < min) {
- min = value;
- }
- }
- return min;
- },
- /**
- * Gets the maximum value in the store.
- * @param {String} field The field in each record.
- * @return {Object/undefined} The maximum value, if no items exist, `undefined`.
- */
- max: function(field) {
- var i = 1,
- records = this.data.items,
- len = records.length,
- value,
- max;
- if (len > 0) {
- max = records[0].get(field);
- }
- for (; i < len; ++i) {
- value = records[i].get(field);
- if (value > max) {
- max = value;
- }
- }
- return max;
- },
- /**
- * Gets the average value in the store.
- * @param {String} field The field in each record you want to get the average for.
- * @return {Number} The average value, if no items exist, 0.
- */
- average: function(field) {
- var i = 0,
- records = this.data.items,
- len = records.length,
- sum = 0;
- if (records.length > 0) {
- for (; i < len; ++i) {
- sum += records[i].get(field);
- }
- return sum / len;
- }
- return 0;
- },
- /**
- * @private
- * Returns an object which is passed in as the listeners argument to `proxy.batch` inside `this.sync`.
- * This is broken out into a separate function to allow for customization of the listeners.
- * @return {Object} The listeners object.
- * @return {Object} return.scope
- * @return {Object} return.exception
- * @return {Object} return.complete
- */
- getBatchListeners: function() {
- return {
- scope: this,
- exception: this.onBatchException,
- complete: this.onBatchComplete
- };
- },
- /**
- * @private
- * Attached as the `complete` event listener to a proxy's Batch object. Iterates over the batch operations
- * and updates the Store's internal data MixedCollection.
- */
- onBatchComplete: function(batch) {
- var me = this,
- operations = batch.operations,
- length = operations.length,
- i;
- for (i = 0; i < length; i++) {
- me.onProxyWrite(operations[i]);
- }
- },
- onBatchException: function(batch, operation) {
- // //decide what to do... could continue with the next operation
- // batch.start();
- //
- // //or retry the last operation
- // batch.retry();
- },
- /**
- * @private
- * Called internally when a Proxy has completed a load request.
- */
- onProxyLoad: function(operation) {
- var me = this,
- records = operation.getRecords(),
- resultSet = operation.getResultSet(),
- successful = operation.wasSuccessful();
- if (resultSet) {
- me.setTotalCount(resultSet.getTotal());
- }
- if (successful) {
- this.fireAction('datarefresh', [this, this.data, operation], 'doDataRefresh');
- }
- me.loaded = true;
- me.loading = false;
- me.fireEvent('load', this, records, successful, operation);
- //this is a callback that would have been passed to the 'read' function and is optional
- Ext.callback(operation.getCallback(), operation.getScope() || me, [records, operation, successful]);
- },
- doDataRefresh: function(store, data, operation) {
- var records = operation.getRecords(),
- me = this,
- destroyRemovedRecords = me.getDestroyRemovedRecords(),
- currentRecords = data.all.slice(),
- ln = currentRecords.length,
- ln2 = records.length,
- ids = {},
- i, record;
- if (operation.getAddRecords() !== true) {
- for (i = 0; i < ln2; i++) {
- ids[records[i].id] = true;
- }
- for (i = 0; i < ln; i++) {
- record = currentRecords[i];
- record.unjoin(me);
- // If the record we are removing is not part of the records we are about to add to the store then handle
- // the destroying or removing of the record to avoid memory leaks.
- if (ids[record.id] !== true && destroyRemovedRecords && !record.stores.length) {
- record.destroy();
- }
- }
- data.clear();
- // This means we have to fire a clear event though
- me.fireEvent('clear', me);
- }
- if (records && records.length) {
- // Now lets add the records without firing an addrecords event
- me.suspendEvents();
- me.add(records);
- me.resumeEvents();
- }
- me.fireEvent('refresh', me, data);
- },
- /**
- * @private
- * Callback for any write Operation over the Proxy. Updates the Store's MixedCollection to reflect
- * the updates provided by the Proxy.
- */
- onProxyWrite: function(operation) {
- var me = this,
- success = operation.wasSuccessful(),
- records = operation.getRecords();
- switch (operation.getAction()) {
- case 'create':
- me.onCreateRecords(records, operation, success);
- break;
- case 'update':
- me.onUpdateRecords(records, operation, success);
- break;
- case 'destroy':
- me.onDestroyRecords(records, operation, success);
- break;
- }
- if (success) {
- me.fireEvent('write', me, operation);
- }
- //this is a callback that would have been passed to the 'create', 'update' or 'destroy' function and is optional
- Ext.callback(operation.getCallback(), operation.getScope() || me, [records, operation, success]);
- },
- // These methods are now just template methods since updating the records etc is all taken care of
- // by the operation itself.
- onCreateRecords: function(records, operation, success) {},
- onUpdateRecords: function(records, operation, success) {},
- onDestroyRecords: function(records, operation, success) {
- this.removed = [];
- },
- onMetaChange: function(data) {
- var model = this.getProxy().getModel();
- if (!this.getModel() && model) {
- this.setModel(model);
- }
- /**
- * @event metachange
- * Fires whenever the server has sent back new metadata to reconfigure the Reader.
- * @param {Ext.data.Store} this
- * @param {Object} data The metadata sent back from the server.
- */
- this.fireEvent('metachange', this, data);
- },
- /**
- * Returns all Model instances that are either currently a phantom (e.g. have no id), or have an ID but have not
- * yet been saved on this Store (this happens when adding a non-phantom record from another Store into this one).
- * @return {Ext.data.Model[]} The Model instances.
- */
- getNewRecords: function() {
- return this.data.filterBy(function(item) {
- // only want phantom records that are valid
- return item.phantom === true && item.isValid();
- }).items;
- },
- /**
- * Returns all Model instances that have been updated in the Store but not yet synchronized with the Proxy.
- * @return {Ext.data.Model[]} The updated Model instances.
- */
- getUpdatedRecords: function() {
- return this.data.filterBy(function(item) {
- // only want dirty records, not phantoms that are valid
- return item.dirty === true && item.phantom !== true && item.isValid();
- }).items;
- },
- /**
- * Returns any records that have been removed from the store but not yet destroyed on the proxy.
- * @return {Ext.data.Model[]} The removed Model instances.
- */
- getRemovedRecords: function() {
- return this.removed;
- },
- // PAGING METHODS
- /**
- * Loads a given 'page' of data by setting the start and limit values appropriately. Internally this just causes a normal
- * load operation, passing in calculated `start` and `limit` params.
- * @param {Number} page The number of the page to load.
- * @param {Object} options See options for {@link #method-load}.
- * @param scope
- */
- loadPage: function(page, options, scope) {
- if (typeof options === 'function') {
- options = {
- callback: options,
- scope: scope || this
- };
- }
- var me = this,
- pageSize = me.getPageSize(),
- clearOnPageLoad = me.getClearOnPageLoad();
- options = Ext.apply({}, options);
- me.currentPage = page;
- me.load(Ext.applyIf(options, {
- page: page,
- start: (page - 1) * pageSize,
- limit: pageSize,
- addRecords: !clearOnPageLoad
- }));
- },
- /**
- * Loads the next 'page' in the current data set.
- * @param {Object} options See options for {@link #method-load}.
- */
- nextPage: function(options) {
- this.loadPage(this.currentPage + 1, options);
- },
- /**
- * Loads the previous 'page' in the current data set.
- * @param {Object} options See options for {@link #method-load}.
- */
- previousPage: function(options) {
- this.loadPage(this.currentPage - 1, options);
- },
- destroy: function() {
- Ext.data.StoreManager.unregister(this);
- this.callParent(arguments);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.util.Offset', {
- /* Begin Definitions */
- statics: {
- fromObject: function(obj) {
- return new this(obj.x, obj.y);
- }
- },
- /* End Definitions */
- constructor: function(x, y) {
- this.x = (x != null && !isNaN(x)) ? x : 0;
- this.y = (y != null && !isNaN(y)) ? y : 0;
- return this;
- },
- copy: function() {
- return new Ext.util.Offset(this.x, this.y);
- },
- copyFrom: function(p) {
- this.x = p.x;
- this.y = p.y;
- },
- toString: function() {
- return "Offset[" + this.x + "," + this.y + "]";
- },
- equals: function(offset) {
- //<debug>
- if(!(offset instanceof this.statics())) {
- Ext.Error.raise('Offset must be an instance of Ext.util.Offset');
- }
- //</debug>
- return (this.x == offset.x && this.y == offset.y);
- },
- round: function(to) {
- if (!isNaN(to)) {
- var factor = Math.pow(10, to);
- this.x = Math.round(this.x * factor) / factor;
- this.y = Math.round(this.y * factor) / factor;
- } else {
- this.x = Math.round(this.x);
- this.y = Math.round(this.y);
- }
- },
- isZero: function() {
- return this.x == 0 && this.y == 0;
- }
- });
- /**
- * Represents a rectangular region and provides a number of utility methods
- * to compare regions.
- */
- Ext.define('Ext.util.Region', {
- requires: ['Ext.util.Offset'],
- statics: {
- /**
- * @static
- * Retrieves an Ext.util.Region for a particular element.
- * @param {String/HTMLElement/Ext.Element} el The element or its ID.
- * @return {Ext.util.Region} region
- */
- getRegion: function(el) {
- return Ext.fly(el).getPageBox(true);
- },
- /**
- * @static
- * Creates new Region from an object:
- *
- * Ext.util.Region.from({top: 0, right: 5, bottom: 3, left: -1});
- * // the above is equivalent to:
- * new Ext.util.Region(0, 5, 3, -1);
- *
- * @param {Object} o An object with `top`, `right`, `bottom`, and `left` properties.
- * @param {Number} o.top
- * @param {Number} o.right
- * @param {Number} o.bottom
- * @param {Number} o.left
- * @return {Ext.util.Region} The region constructed based on the passed object.
- */
- from: function(o) {
- return new this(o.top, o.right, o.bottom, o.left);
- }
- },
- /**
- * Creates new Region.
- * @param {Number} top Top
- * @param {Number} right Right
- * @param {Number} bottom Bottom
- * @param {Number} left Left
- */
- constructor: function(top, right, bottom, left) {
- var me = this;
- me.top = top;
- me[1] = top;
- me.right = right;
- me.bottom = bottom;
- me.left = left;
- me[0] = left;
- },
- /**
- * Checks if this region completely contains the region that is passed in.
- * @param {Ext.util.Region} region
- * @return {Boolean}
- */
- contains: function(region) {
- var me = this;
- return (region.left >= me.left &&
- region.right <= me.right &&
- region.top >= me.top &&
- region.bottom <= me.bottom);
- },
- /**
- * Checks if this region intersects the region passed in.
- * @param {Ext.util.Region} region
- * @return {Ext.util.Region/Boolean} Returns the intersected region or `false` if there is no intersection.
- */
- intersect: function(region) {
- var me = this,
- t = Math.max(me.top, region.top),
- r = Math.min(me.right, region.right),
- b = Math.min(me.bottom, region.bottom),
- l = Math.max(me.left, region.left);
- if (b > t && r > l) {
- return new Ext.util.Region(t, r, b, l);
- }
- else {
- return false;
- }
- },
- /**
- * Returns the smallest region that contains the current AND `targetRegion`.
- * @param {Ext.util.Region} region
- * @return {Ext.util.Region}
- */
- union: function(region) {
- var me = this,
- t = Math.min(me.top, region.top),
- r = Math.max(me.right, region.right),
- b = Math.max(me.bottom, region.bottom),
- l = Math.min(me.left, region.left);
- return new Ext.util.Region(t, r, b, l);
- },
- /**
- * Modifies the current region to be constrained to the `targetRegion`.
- * @param {Ext.util.Region} targetRegion
- * @return {Ext.util.Region} this
- */
- constrainTo: function(targetRegion) {
- var me = this,
- constrain = Ext.util.Numbers.constrain;
- me.top = constrain(me.top, targetRegion.top, targetRegion.bottom);
- me.bottom = constrain(me.bottom, targetRegion.top, targetRegion.bottom);
- me.left = constrain(me.left, targetRegion.left, targetRegion.right);
- me.right = constrain(me.right, targetRegion.left, targetRegion.right);
- return me;
- },
- /**
- * Modifies the current region to be adjusted by offsets.
- * @param {Number} top Top offset
- * @param {Number} right Right offset
- * @param {Number} bottom Bottom offset
- * @param {Number} left Left offset
- * @return {Ext.util.Region} this
- * @chainable
- */
- adjust: function(top, right, bottom, left) {
- var me = this;
- me.top += top;
- me.left += left;
- me.right += right;
- me.bottom += bottom;
- return me;
- },
- /**
- * Get the offset amount of a point outside the region.
- * @param {String/Object} axis optional.
- * @param {Ext.util.Point} p The point.
- * @return {Ext.util.Region}
- */
- getOutOfBoundOffset: function(axis, p) {
- if (!Ext.isObject(axis)) {
- if (axis == 'x') {
- return this.getOutOfBoundOffsetX(p);
- } else {
- return this.getOutOfBoundOffsetY(p);
- }
- } else {
- var d = new Ext.util.Offset();
- d.x = this.getOutOfBoundOffsetX(axis.x);
- d.y = this.getOutOfBoundOffsetY(axis.y);
- return d;
- }
- },
- /**
- * Get the offset amount on the x-axis.
- * @param {Number} p The offset.
- * @return {Number}
- */
- getOutOfBoundOffsetX: function(p) {
- if (p <= this.left) {
- return this.left - p;
- } else if (p >= this.right) {
- return this.right - p;
- }
- return 0;
- },
- /**
- * Get the offset amount on the y-axis.
- * @param {Number} p The offset.
- * @return {Number}
- */
- getOutOfBoundOffsetY: function(p) {
- if (p <= this.top) {
- return this.top - p;
- } else if (p >= this.bottom) {
- return this.bottom - p;
- }
- return 0;
- },
- /**
- * Check whether the point / offset is out of bounds.
- * @param {String} axis optional
- * @param {Ext.util.Point/Number} p The point / offset.
- * @return {Boolean}
- */
- isOutOfBound: function(axis, p) {
- if (!Ext.isObject(axis)) {
- if (axis == 'x') {
- return this.isOutOfBoundX(p);
- } else {
- return this.isOutOfBoundY(p);
- }
- } else {
- p = axis;
- return (this.isOutOfBoundX(p.x) || this.isOutOfBoundY(p.y));
- }
- },
- /**
- * Check whether the offset is out of bound in the x-axis.
- * @param {Number} p The offset.
- * @return {Boolean}
- */
- isOutOfBoundX: function(p) {
- return (p < this.left || p > this.right);
- },
- /**
- * Check whether the offset is out of bound in the y-axis.
- * @param {Number} p The offset.
- * @return {Boolean}
- */
- isOutOfBoundY: function(p) {
- return (p < this.top || p > this.bottom);
- },
- /*
- * Restrict a point within the region by a certain factor.
- * @param {String} axis Optional
- * @param {Ext.util.Point/Ext.util.Offset/Object} p
- * @param {Number} factor
- * @return {Ext.util.Point/Ext.util.Offset/Object/Number}
- */
- restrict: function(axis, p, factor) {
- if (Ext.isObject(axis)) {
- var newP;
- factor = p;
- p = axis;
- if (p.copy) {
- newP = p.copy();
- }
- else {
- newP = {
- x: p.x,
- y: p.y
- };
- }
- newP.x = this.restrictX(p.x, factor);
- newP.y = this.restrictY(p.y, factor);
- return newP;
- } else {
- if (axis == 'x') {
- return this.restrictX(p, factor);
- } else {
- return this.restrictY(p, factor);
- }
- }
- },
- /*
- * Restrict an offset within the region by a certain factor, on the x-axis.
- * @param {Number} p
- * @param {Number} [factor=1] (optional) The factor.
- * @return {Number}
- */
- restrictX: function(p, factor) {
- if (!factor) {
- factor = 1;
- }
- if (p <= this.left) {
- p -= (p - this.left) * factor;
- }
- else if (p >= this.right) {
- p -= (p - this.right) * factor;
- }
- return p;
- },
- /*
- * Restrict an offset within the region by a certain factor, on the y-axis.
- * @param {Number} p
- * @param {Number} [factor=1] (optional) The factor.
- * @return {Number}
- */
- restrictY: function(p, factor) {
- if (!factor) {
- factor = 1;
- }
- if (p <= this.top) {
- p -= (p - this.top) * factor;
- }
- else if (p >= this.bottom) {
- p -= (p - this.bottom) * factor;
- }
- return p;
- },
- /*
- * Get the width / height of this region.
- * @return {Object} An object with `width` and `height` properties.
- * @return {Number} return.width
- * @return {Number} return.height
- */
- getSize: function() {
- return {
- width: this.right - this.left,
- height: this.bottom - this.top
- };
- },
- /**
- * Copy a new instance.
- * @return {Ext.util.Region}
- */
- copy: function() {
- return new Ext.util.Region(this.top, this.right, this.bottom, this.left);
- },
- /**
- * Dump this to an eye-friendly string, great for debugging.
- * @return {String} For example `Region[0,1,3,2]`.
- */
- toString: function() {
- return "Region[" + this.top + "," + this.right + "," + this.bottom + "," + this.left + "]";
- },
- /**
- * Translate this region by the given offset amount.
- * @param {Object} offset
- * @return {Ext.util.Region} This Region.
- * @chainable
- */
- translateBy: function(offset) {
- this.left += offset.x;
- this.right += offset.x;
- this.top += offset.y;
- this.bottom += offset.y;
- return this;
- },
- /**
- * Round all the properties of this region.
- * @return {Ext.util.Region} This Region.
- * @chainable
- */
- round: function() {
- this.top = Math.round(this.top);
- this.right = Math.round(this.right);
- this.bottom = Math.round(this.bottom);
- this.left = Math.round(this.left);
- return this;
- },
- /**
- * Check whether this region is equivalent to the given region.
- * @param {Ext.util.Region} region The region to compare with.
- * @return {Boolean}
- */
- equals: function(region) {
- return (this.top == region.top && this.right == region.right && this.bottom == region.bottom && this.left == region.left)
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.event.publisher.Publisher', {
- targetType: '',
- idSelectorRegex: /^#([\w\-]+)$/i,
- constructor: function() {
- var handledEvents = this.handledEvents,
- handledEventsMap,
- i, ln, event;
- handledEventsMap = this.handledEventsMap = {};
- for (i = 0,ln = handledEvents.length; i < ln; i++) {
- event = handledEvents[i];
- handledEventsMap[event] = true;
- }
- this.subscribers = {};
- return this;
- },
- handles: function(eventName) {
- var map = this.handledEventsMap;
- return !!map[eventName] || !!map['*'] || eventName === '*';
- },
- getHandledEvents: function() {
- return this.handledEvents;
- },
- setDispatcher: function(dispatcher) {
- this.dispatcher = dispatcher;
- },
- subscribe: function() {
- return false;
- },
- unsubscribe: function() {
- return false;
- },
- unsubscribeAll: function() {
- delete this.subscribers;
- this.subscribers = {};
- return this;
- },
- notify: function() {
- return false;
- },
- getTargetType: function() {
- return this.targetType;
- },
- dispatch: function(target, eventName, args) {
- this.dispatcher.doDispatchEvent(this.targetType, target, eventName, args);
- }
- });
- /**
- * @author Ed Spencer
- * @class Ext.data.reader.Array
- *
- * Data reader class to create an Array of {@link Ext.data.Model} objects from an Array.
- * Each element of that Array represents a row of data fields. The
- * fields are pulled into a Record object using as a subscript, the `mapping` property
- * of the field definition if it exists, or the field's ordinal position in the definition.
- *
- * Example code:
- *
- * Employee = Ext.define('Employee', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [
- * 'id',
- * {name: 'name', mapping: 1}, // "mapping" only needed if an "id" field is present which
- * {name: 'occupation', mapping: 2} // precludes using the ordinal position as the index.
- * ]
- * }
- * });
- *
- * var myReader = new Ext.data.reader.Array({
- * model: 'Employee'
- * }, Employee);
- *
- * This would consume an Array like this:
- *
- * [ [1, 'Bill', 'Gardener'], [2, 'Ben', 'Horticulturalist'] ]
- *
- * @constructor
- * Create a new ArrayReader
- * @param {Object} meta Metadata configuration options.
- */
- Ext.define('Ext.data.reader.Array', {
- extend: 'Ext.data.reader.Json',
- alternateClassName: 'Ext.data.ArrayReader',
- alias : 'reader.array',
- // For Array Reader, methods in the base which use these properties must not see the defaults
- config: {
- totalProperty: undefined,
- successProperty: undefined
- },
- /**
- * @private
- * Returns an accessor expression for the passed Field from an Array using either the Field's mapping, or
- * its ordinal position in the fields collection as the index.
- * This is used by buildExtractors to create optimized on extractor function which converts raw data into model instances.
- */
- createFieldAccessExpression: function(field, fieldVarName, dataName) {
- var me = this,
- mapping = field.getMapping(),
- index = (mapping == null) ? me.getModel().getFields().indexOf(field) : mapping,
- result;
- if (typeof index === 'function') {
- result = fieldVarName + '.getMapping()(' + dataName + ', this)';
- } else {
- if (isNaN(index)) {
- index = '"' + index + '"';
- }
- result = dataName + "[" + index + "]";
- }
- return result;
- }
- });
- /**
- * @author Ed Spencer
- * @aside guide stores
- *
- * Small helper class to make creating {@link Ext.data.Store}s from Array data easier. An ArrayStore will be
- * automatically configured with a {@link Ext.data.reader.Array}.
- *
- * A store configuration would be something like:
- *
- * var store = Ext.create('Ext.data.ArrayStore', {
- * // store configs
- * autoDestroy: true,
- * storeId: 'myStore',
- * // reader configs
- * idIndex: 0,
- * fields: [
- * 'company',
- * {name: 'price', type: 'float'},
- * {name: 'change', type: 'float'},
- * {name: 'pctChange', type: 'float'},
- * {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
- * ]
- * });
- *
- * This store is configured to consume a returned object of the form:
- *
- * var myData = [
- * ['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
- * ['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
- * ['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
- * ['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
- * ['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
- * ];
- *
- * An object literal of this form could also be used as the {@link #data} config option.
- *
- * **Note:** Although not listed here, this class accepts all of the configuration options of
- * **{@link Ext.data.reader.Array ArrayReader}**.
- */
- Ext.define('Ext.data.ArrayStore', {
- extend: 'Ext.data.Store',
- alias: 'store.array',
- uses: ['Ext.data.reader.Array'],
- config: {
- proxy: {
- type: 'memory',
- reader: 'array'
- }
- },
- loadData: function(data, append) {
- // if (this.expandData === true) {
- // var r = [],
- // i = 0,
- // ln = data.length;
- //
- // for (; i < ln; i++) {
- // r[r.length] = [data[i]];
- // }
- //
- // data = r;
- // }
- this.callParent([data, append]);
- }
- }, function() {
- // backwards compat
- Ext.data.SimpleStore = Ext.data.ArrayStore;
- // Ext.reg('simplestore', Ext.data.SimpleStore);
- });
- /**
- * Ext.Direct aims to streamline communication between the client and server by providing a single interface that
- * reduces the amount of common code typically required to validate data and handle returned data packets (reading data,
- * error conditions, etc).
- *
- * The Ext.direct namespace includes several classes for a closer integration with the server-side. The Ext.data
- * namespace also includes classes for working with Ext.data.Stores which are backed by data from an Ext.Direct method.
- *
- * # Specification
- *
- * For additional information consult the [Ext.Direct Specification](http://sencha.com/products/extjs/extdirect).
- *
- * # Providers
- *
- * Ext.Direct uses a provider architecture, where one or more providers are used to transport data to and from the
- * server. There are several providers that exist in the core at the moment:
- *
- * - {@link Ext.direct.JsonProvider JsonProvider} for simple JSON operations
- * - {@link Ext.direct.PollingProvider PollingProvider} for repeated requests
- * - {@link Ext.direct.RemotingProvider RemotingProvider} exposes server side on the client.
- *
- * A provider does not need to be invoked directly, providers are added via {@link Ext.direct.Manager}.{@link #addProvider}.
- *
- * # Router
- *
- * Ext.Direct utilizes a "router" on the server to direct requests from the client to the appropriate server-side
- * method. Because the Ext.Direct API is completely platform-agnostic, you could completely swap out a Java based server
- * solution and replace it with one that uses C# without changing the client side JavaScript at all.
- *
- * # Server side events
- *
- * Custom events from the server may be handled by the client by adding listeners, for example:
- *
- * {"type":"event","name":"message","data":"Successfully polled at: 11:19:30 am"}
- *
- * // add a handler for a 'message' event sent by the server
- * Ext.direct.Manager.on('message', function(e){
- * out.append(String.format('<p><i>{0}</i></p>', e.data));
- * out.el.scrollTo('t', 100000, true);
- * });
- *
- * @singleton
- * @alternateClassName Ext.Direct
- */
- Ext.define('Ext.direct.Manager', {
- singleton: true,
- mixins: {
- observable: 'Ext.mixin.Observable'
- },
- requires: ['Ext.util.Collection'],
- alternateClassName: 'Ext.Direct',
- exceptions: {
- TRANSPORT: 'xhr',
- PARSE: 'parse',
- LOGIN: 'login',
- SERVER: 'exception'
- },
- /**
- * @event event
- * Fires after an event.
- * @param {Ext.direct.Event} e The Ext.direct.Event type that occurred.
- * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
- */
- /**
- * @event exception
- * Fires after an event exception.
- * @param {Ext.direct.Event} e The event type that occurred.
- */
- constructor: function() {
- var me = this;
- me.transactions = Ext.create('Ext.util.Collection', this.getKey);
- me.providers = Ext.create('Ext.util.Collection', this.getKey);
- },
- getKey: function(item) {
- return item.getId();
- },
- /**
- * Adds an Ext.Direct Provider and creates the proxy or stub methods to execute server-side methods. If the provider
- * is not already connected, it will auto-connect.
- *
- * Ext.direct.Manager.addProvider({
- * type: "remoting", // create a {@link Ext.direct.RemotingProvider}
- * url: "php/router.php", // url to connect to the Ext.Direct server-side router.
- * actions: { // each property within the actions object represents a Class
- * TestAction: [ // array of methods within each server side Class
- * {
- * name: "doEcho", // name of method
- * len: 1
- * },{
- * name: "multiply",
- * len: 1
- * },{
- * name: "doForm",
- * formHandler: true, // handle form on server with Ext.Direct.Transaction
- * len: 1
- * }]
- * },
- * namespace: "myApplication" // namespace to create the Remoting Provider in
- * });
- *
- * @param {Ext.direct.Provider/Object...} provider
- * Accepts any number of Provider descriptions (an instance or config object for
- * a Provider). Each Provider description instructs Ext.Direct how to create
- * client-side stub methods.
- * @return {Object}
- */
- addProvider : function(provider) {
- var me = this,
- args = Ext.toArray(arguments),
- i = 0, ln;
- if (args.length > 1) {
- for (ln = args.length; i < ln; ++i) {
- me.addProvider(args[i]);
- }
- return;
- }
- // if provider has not already been instantiated
- if (!provider.isProvider) {
- provider = Ext.create('direct.' + provider.type + 'provider', provider);
- }
- me.providers.add(provider);
- provider.on('data', me.onProviderData, me);
- if (!provider.isConnected()) {
- provider.connect();
- }
- return provider;
- },
- /**
- * Retrieves a {@link Ext.direct.Provider provider} by the **{@link Ext.direct.Provider#id id}** specified when the
- * provider is {@link #addProvider added}.
- * @param {String/Ext.direct.Provider} id The id of the provider, or the provider instance.
- * @return {Object}
- */
- getProvider : function(id){
- return id.isProvider ? id : this.providers.get(id);
- },
- /**
- * Removes the provider.
- * @param {String/Ext.direct.Provider} provider The provider instance or the id of the provider.
- * @return {Ext.direct.Provider/null} The provider, `null` if not found.
- */
- removeProvider : function(provider) {
- var me = this,
- providers = me.providers;
- provider = provider.isProvider ? provider : providers.get(provider);
- if (provider) {
- provider.un('data', me.onProviderData, me);
- providers.remove(provider);
- return provider;
- }
- return null;
- },
- /**
- * Adds a transaction to the manager.
- * @private
- * @param {Ext.direct.Transaction} transaction The transaction to add
- * @return {Ext.direct.Transaction} transaction
- */
- addTransaction: function(transaction) {
- this.transactions.add(transaction);
- return transaction;
- },
- /**
- * Removes a transaction from the manager.
- * @private
- * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to remove
- * @return {Ext.direct.Transaction} transaction
- */
- removeTransaction: function(transaction) {
- transaction = this.getTransaction(transaction);
- this.transactions.remove(transaction);
- return transaction;
- },
- /**
- * Gets a transaction
- * @private
- * @param {String/Ext.direct.Transaction} transaction The transaction/id of transaction to get
- * @return {Ext.direct.Transaction}
- */
- getTransaction: function(transaction) {
- return Ext.isObject(transaction) ? transaction : this.transactions.get(transaction);
- },
- onProviderData : function(provider, event) {
- var me = this,
- i = 0, ln,
- name;
- if (Ext.isArray(event)) {
- for (ln = event.length; i < ln; ++i) {
- me.onProviderData(provider, event[i]);
- }
- return;
- }
- name = event.getName();
- if (name && name != 'event' && name != 'exception') {
- me.fireEvent(name, event);
- } else if (event.getStatus() === false) {
- me.fireEvent('exception', event);
- }
- me.fireEvent('event', event, provider);
- },
- /**
- * Parses a direct function. It may be passed in a string format, for example:
- * "MyApp.Person.read".
- * @protected
- * @param {String/Function} fn The direct function
- * @return {Function} The function to use in the direct call. Null if not found
- */
- parseMethod: function(fn) {
- if (Ext.isString(fn)) {
- var parts = fn.split('.'),
- i = 0,
- ln = parts.length,
- current = window;
- while (current && i < ln) {
- current = current[parts[i]];
- ++i;
- }
- fn = Ext.isFunction(current) ? current : null;
- }
- return fn || null;
- }
- });
- /**
- * @aside guide proxies
- *
- * This class is used to send requests to the server using {@link Ext.direct.Manager Ext.Direct}. When a
- * request is made, the transport mechanism is handed off to the appropriate
- * {@link Ext.direct.RemotingProvider Provider} to complete the call.
- *
- * # Specifying the function
- *
- * This proxy expects a Direct remoting method to be passed in order to be able to complete requests.
- * This can be done by specifying the {@link #directFn} configuration. This will use the same direct
- * method for all requests. Alternatively, you can provide an {@link #api} configuration. This
- * allows you to specify a different remoting method for each CRUD action.
- *
- * # Parameters
- *
- * This proxy provides options to help configure which parameters will be sent to the server.
- * By specifying the {@link #paramsAsHash} option, it will send an object literal containing each
- * of the passed parameters. The {@link #paramOrder} option can be used to specify the order in which
- * the remoting method parameters are passed.
- *
- * # Example Usage
- *
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: ['firstName', 'lastName'],
- * proxy: {
- * type: 'direct',
- * directFn: MyApp.getUsers,
- * paramOrder: 'id' // Tells the proxy to pass the id as the first parameter to the remoting method.
- * }
- * }
- * });
- * User.load(1);
- */
- Ext.define('Ext.data.proxy.Direct', {
- extend: 'Ext.data.proxy.Server',
- alternateClassName: 'Ext.data.DirectProxy',
- alias: 'proxy.direct',
- requires: ['Ext.direct.Manager'],
- config: {
- /**
- * @cfg {String/String[]} paramOrder
- * Defaults to undefined. A list of params to be executed server side. Specify the params in the order in
- * which they must be executed on the server-side as either (1) an Array of String values, or (2) a String
- * of params delimited by either whitespace, comma, or pipe. For example, any of the following would be
- * acceptable:
- *
- * paramOrder: ['param1','param2','param3']
- * paramOrder: 'param1 param2 param3'
- * paramOrder: 'param1,param2,param3'
- * paramOrder: 'param1|param2|param'
- */
- paramOrder: undefined,
- /**
- * @cfg {Boolean} paramsAsHash
- * Send parameters as a collection of named arguments.
- * Providing a {@link #paramOrder} nullifies this configuration.
- */
- paramsAsHash: true,
- /**
- * @cfg {Function/String} directFn
- * Function to call when executing a request. directFn is a simple alternative to defining the api configuration-parameter
- * for Store's which will not implement a full CRUD api. The directFn may also be a string reference to the fully qualified
- * name of the function, for example: 'MyApp.company.GetProfile'. This can be useful when using dynamic loading. The string
- * will be looked up when the proxy is created.
- */
- directFn : undefined,
- /**
- * @cfg {Object} api
- * The same as {@link Ext.data.proxy.Server#api}, however instead of providing urls, you should provide a direct
- * function call. See {@link #directFn}.
- */
- api: null,
- /**
- * @cfg {Object} extraParams
- * Extra parameters that will be included on every read request. Individual requests with params
- * of the same name will override these params when they are in conflict.
- */
- extraParams: null
- },
- // @private
- paramOrderRe: /[\s,|]/,
- applyParamOrder: function(paramOrder) {
- if (Ext.isString(paramOrder)) {
- paramOrder = paramOrder.split(this.paramOrderRe);
- }
- return paramOrder;
- },
- applyDirectFn: function(directFn) {
- return Ext.direct.Manager.parseMethod(directFn);
- },
- applyApi: function(api) {
- var fn;
- if (api && Ext.isObject(api)) {
- for (fn in api) {
- if (api.hasOwnProperty(fn)) {
- api[fn] = Ext.direct.Manager.parseMethod(api[fn]);
- }
- }
- }
- return api;
- },
- doRequest: function(operation, callback, scope) {
- var me = this,
- writer = me.getWriter(),
- request = me.buildRequest(operation, callback, scope),
- api = me.getApi(),
- fn = api && api[request.getAction()] || me.getDirectFn(),
- params = request.getParams(),
- args = [],
- method;
- //<debug>
- if (!fn) {
- Ext.Logger.error('No direct function specified for this proxy');
- }
- //</debug>
- request = writer.write(request);
- if (operation.getAction() == 'read') {
- // We need to pass params
- method = fn.directCfg.method;
- args = method.getArgs(params, me.getParamOrder(), me.getParamsAsHash());
- } else {
- args.push(request.getJsonData());
- }
- args.push(me.createRequestCallback(request, operation, callback, scope), me);
- request.setConfig({
- args: args,
- directFn: fn
- });
- fn.apply(window, args);
- },
- /*
- * Inherit docs. We don't apply any encoding here because
- * all of the direct requests go out as jsonData
- */
- applyEncoding: function(value) {
- return value;
- },
- createRequestCallback: function(request, operation, callback, scope) {
- var me = this;
- return function(data, event) {
- me.processResponse(event.getStatus(), operation, request, event.getResult(), callback, scope);
- };
- },
- // @inheritdoc
- extractResponseData: function(response) {
- var result = response.getResult();
- return Ext.isDefined(result) ? result : response.getData();
- },
- // @inheritdoc
- setException: function(operation, response) {
- operation.setException(response.getMessage());
- },
- // @inheritdoc
- buildUrl: function() {
- return '';
- }
- });
- /**
- * @aside guide stores
- *
- * Small helper class to create an {@link Ext.data.Store} configured with an {@link Ext.data.proxy.Direct}
- * and {@link Ext.data.reader.Json} to make interacting with an {@link Ext.direct.Manager} server-side
- * {@link Ext.direct.Provider Provider} easier. To create a different proxy/reader combination create a basic
- * {@link Ext.data.Store} configured as needed.
- *
- * Since configurations are deeply merged with the standard configuration, you can override certain proxy and
- * reader configurations like this:
- *
- * Ext.create('Ext.data.DirectStore', {
- * proxy: {
- * paramsAsHash: true,
- * directFn: someDirectFn,
- * simpleSortMode: true,
- * reader: {
- * rootProperty: 'results',
- * idProperty: '_id'
- * }
- * }
- * });
- *
- */
- Ext.define('Ext.data.DirectStore', {
- extend: 'Ext.data.Store',
- alias: 'store.direct',
- requires: ['Ext.data.proxy.Direct'],
- config: {
- proxy: {
- type: 'direct',
- reader: {
- type: 'json'
- }
- }
- }
- });
- /**
- * @aside guide ajax
- * @singleton
- *
- * This class is used to create JsonP requests. JsonP is a mechanism that allows for making requests for data cross
- * domain. More information is available [here](http://en.wikipedia.org/wiki/JSONP).
- *
- * ## Example
- *
- * @example preview
- * Ext.Viewport.add({
- * xtype: 'button',
- * text: 'Make JsonP Request',
- * centered: true,
- * handler: function(button) {
- * // Mask the viewport
- * Ext.Viewport.mask();
- *
- * // Remove the button
- * button.destroy();
- *
- * // Make the JsonP request
- * Ext.data.JsonP.request({
- * url: 'http://free.worldweatheronline.com/feed/weather.ashx',
- * callbackKey: 'callback',
- * params: {
- * key: '23f6a0ab24185952101705',
- * q: '94301', // Palo Alto
- * format: 'json',
- * num_of_days: 5
- * },
- * success: function(result, request) {
- * // Unmask the viewport
- * Ext.Viewport.unmask();
- *
- * // Get the weather data from the json object result
- * var weather = result.data.weather;
- * if (weather) {
- * // Style the viewport html, and set the html of the max temperature
- * Ext.Viewport.setStyleHtmlContent(true);
- * Ext.Viewport.setHtml('The temperature in Palo Alto is <b>' + weather[0].tempMaxF + '° F</b>');
- * }
- * }
- * });
- * }
- * });
- *
- * See the {@link #request} method for more details on making a JsonP request.
- */
- Ext.define('Ext.data.JsonP', {
- alternateClassName: 'Ext.util.JSONP',
- /* Begin Definitions */
- singleton: true,
- /* End Definitions */
- /**
- * Number of requests done so far.
- * @private
- */
- requestCount: 0,
- /**
- * Hash of pending requests.
- * @private
- */
- requests: {},
- /**
- * @property {Number} [timeout=30000]
- * A default timeout (in milliseconds) for any JsonP requests. If the request has not completed in this time the failure callback will
- * be fired.
- */
- timeout: 30000,
- /**
- * @property {Boolean} disableCaching
- * `true` to add a unique cache-buster param to requests.
- */
- disableCaching: true,
- /**
- * @property {String} disableCachingParam
- * Change the parameter which is sent went disabling caching through a cache buster.
- */
- disableCachingParam: '_dc',
- /**
- * @property {String} callbackKey
- * Specifies the GET parameter that will be sent to the server containing the function name to be executed when the
- * request completes. Thus, a common request will be in the form of:
- * `url?callback=Ext.data.JsonP.callback1`
- */
- callbackKey: 'callback',
- /**
- * Makes a JSONP request.
- * @param {Object} options An object which may contain the following properties. Note that options will take
- * priority over any defaults that are specified in the class.
- *
- * @param {String} options.url The URL to request.
- * @param {Object} [options.params] An object containing a series of key value pairs that will be sent along with the request.
- * @param {Number} [options.timeout] See {@link #timeout}
- * @param {String} [options.callbackKey] See {@link #callbackKey}
- * @param {String} [options.callbackName] See {@link #callbackKey}
- * The function name to use for this request. By default this name will be auto-generated: Ext.data.JsonP.callback1,
- * Ext.data.JsonP.callback2, etc. Setting this option to "my_name" will force the function name to be
- * Ext.data.JsonP.my_name. Use this if you want deterministic behavior, but be careful - the callbackName should be
- * different in each JsonP request that you make.
- * @param {Boolean} [options.disableCaching] See {@link #disableCaching}
- * @param {String} [options.disableCachingParam] See {@link #disableCachingParam}
- * @param {Function} [options.success] A function to execute if the request succeeds.
- * @param {Function} [options.failure] A function to execute if the request fails.
- * @param {Function} [options.callback] A function to execute when the request completes, whether it is a success or failure.
- * @param {Object} [options.scope] The scope in which to execute the callbacks: The "this" object for the
- * callback function. Defaults to the browser window.
- *
- * @return {Object} request An object containing the request details.
- */
- request: function(options){
- options = Ext.apply({}, options);
- //<debug>
- if (!options.url) {
- Ext.Logger.error('A url must be specified for a JSONP request.');
- }
- //</debug>
- var me = this,
- disableCaching = Ext.isDefined(options.disableCaching) ? options.disableCaching : me.disableCaching,
- cacheParam = options.disableCachingParam || me.disableCachingParam,
- id = ++me.requestCount,
- callbackName = options.callbackName || 'callback' + id,
- callbackKey = options.callbackKey || me.callbackKey,
- timeout = Ext.isDefined(options.timeout) ? options.timeout : me.timeout,
- params = Ext.apply({}, options.params),
- url = options.url,
- name = Ext.isSandboxed ? Ext.getUniqueGlobalNamespace() : 'Ext',
- request,
- script;
- params[callbackKey] = name + '.data.JsonP.' + callbackName;
- if (disableCaching) {
- params[cacheParam] = new Date().getTime();
- }
- script = me.createScript(url, params, options);
- me.requests[id] = request = {
- url: url,
- params: params,
- script: script,
- id: id,
- scope: options.scope,
- success: options.success,
- failure: options.failure,
- callback: options.callback,
- callbackKey: callbackKey,
- callbackName: callbackName
- };
- if (timeout > 0) {
- request.timeout = setTimeout(Ext.bind(me.handleTimeout, me, [request]), timeout);
- }
- me.setupErrorHandling(request);
- me[callbackName] = Ext.bind(me.handleResponse, me, [request], true);
- me.loadScript(request);
- return request;
- },
- /**
- * Abort a request. If the request parameter is not specified all open requests will be aborted.
- * @param {Object/String} request The request to abort.
- */
- abort: function(request){
- var requests = this.requests,
- key;
- if (request) {
- if (!request.id) {
- request = requests[request];
- }
- this.handleAbort(request);
- } else {
- for (key in requests) {
- if (requests.hasOwnProperty(key)) {
- this.abort(requests[key]);
- }
- }
- }
- },
- /**
- * Sets up error handling for the script.
- * @private
- * @param {Object} request The request.
- */
- setupErrorHandling: function(request){
- request.script.onerror = Ext.bind(this.handleError, this, [request]);
- },
- /**
- * Handles any aborts when loading the script.
- * @private
- * @param {Object} request The request.
- */
- handleAbort: function(request){
- request.errorType = 'abort';
- this.handleResponse(null, request);
- },
- /**
- * Handles any script errors when loading the script.
- * @private
- * @param {Object} request The request.
- */
- handleError: function(request){
- request.errorType = 'error';
- this.handleResponse(null, request);
- },
- /**
- * Cleans up any script handling errors.
- * @private
- * @param {Object} request The request.
- */
- cleanupErrorHandling: function(request){
- request.script.onerror = null;
- },
- /**
- * Handle any script timeouts.
- * @private
- * @param {Object} request The request.
- */
- handleTimeout: function(request){
- request.errorType = 'timeout';
- this.handleResponse(null, request);
- },
- /**
- * Handle a successful response
- * @private
- * @param {Object} result The result from the request
- * @param {Object} request The request
- */
- handleResponse: function(result, request){
- var success = true;
- if (request.timeout) {
- clearTimeout(request.timeout);
- }
- delete this[request.callbackName];
- delete this.requests[request.id];
- this.cleanupErrorHandling(request);
- Ext.fly(request.script).destroy();
- if (request.errorType) {
- success = false;
- Ext.callback(request.failure, request.scope, [request.errorType, request]);
- } else {
- Ext.callback(request.success, request.scope, [result, request]);
- }
- Ext.callback(request.callback, request.scope, [success, result, request.errorType, request]);
- },
- /**
- * Create the script tag given the specified url, params and options. The options
- * parameter is passed to allow an override to access it.
- * @private
- * @param {String} url The url of the request
- * @param {Object} params Any extra params to be sent
- * @param {Object} options The object passed to {@link #request}.
- */
- createScript: function(url, params, options) {
- var script = document.createElement('script');
- script.setAttribute("src", Ext.urlAppend(url, Ext.Object.toQueryString(params)));
- script.setAttribute("async", true);
- script.setAttribute("type", "text/javascript");
- return script;
- },
- /**
- * Loads the script for the given request by appending it to the HEAD element. This is
- * its own method so that users can override it (as well as {@link #createScript}).
- * @private
- * @param request The request object.
- */
- loadScript: function (request) {
- Ext.getHead().appendChild(request.script);
- }
- });
- /**
- * @author Ed Spencer
- * @class Ext.data.JsonStore
- * @extends Ext.data.Store
- * @private
- *
- * Small helper class to make creating {@link Ext.data.Store}s from JSON data easier.
- * A JsonStore will be automatically configured with a {@link Ext.data.reader.Json}.
- *
- * A store configuration would be something like:
- *
- * var store = new Ext.data.JsonStore({
- * // store configs
- * autoDestroy: true,
- * storeId: 'myStore',
- *
- * proxy: {
- * type: 'ajax',
- * url: 'get-images.php',
- * reader: {
- * type: 'json',
- * root: 'images',
- * idProperty: 'name'
- * }
- * },
- *
- * // alternatively, a {@link Ext.data.Model} name can be given (see {@link Ext.data.Store} for an example)
- * fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
- * });
- *
- * This store is configured to consume a returned object of the form:
- *
- * {
- * images: [
- * {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
- * {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
- * ]
- * }
- *
- * An object literal of this form could also be used as the {@link #data} config option.
- *
- * @xtype jsonstore
- */
- Ext.define('Ext.data.JsonStore', {
- extend: 'Ext.data.Store',
- alias: 'store.json',
- config: {
- proxy: {
- type: 'ajax',
- reader: 'json',
- writer: 'json'
- }
- }
- });
- /**
- * @class Ext.data.NodeInterface
- * This class is meant to be used as a set of methods that are applied to the prototype of a
- * Record to decorate it with a Node API. This means that models used in conjunction with a tree
- * will have all of the tree related methods available on the model. In general this class will
- * not be used directly by the developer. This class also creates extra fields on the model if
- * they do not exist, to help maintain the tree state and UI. These fields are:
- *
- * - parentId
- * - index
- * - depth
- * - expanded
- * - expandable
- * - checked
- * - leaf
- * - cls
- * - iconCls
- * - root
- * - isLast
- * - isFirst
- * - allowDrop
- * - allowDrag
- * - loaded
- * - loading
- * - href
- * - hrefTarget
- * - qtip
- * - qtitle
- */
- Ext.define('Ext.data.NodeInterface', {
- requires: ['Ext.data.Field', 'Ext.data.ModelManager'],
- alternateClassName: 'Ext.data.Node',
- /**
- * @property nextSibling
- * A reference to this node's next sibling node. `null` if this node does not have a next sibling.
- */
- /**
- * @property previousSibling
- * A reference to this node's previous sibling node. `null` if this node does not have a previous sibling.
- */
- /**
- * @property parentNode
- * A reference to this node's parent node. `null` if this node is the root node.
- */
- /**
- * @property lastChild
- * A reference to this node's last child node. `null` if this node has no children.
- */
- /**
- * @property firstChild
- * A reference to this node's first child node. `null` if this node has no children.
- */
- /**
- * @property childNodes
- * An array of this nodes children. Array will be empty if this node has no children.
- */
- statics: {
- /**
- * This method allows you to decorate a Record's prototype to implement the NodeInterface.
- * This adds a set of methods, new events, new properties and new fields on every Record
- * with the same Model as the passed Record.
- * @param {Ext.data.Model} record The Record you want to decorate the prototype of.
- * @static
- */
- decorate: function(record) {
- if (!record.isNode) {
- // Apply the methods and fields to the prototype
- var mgr = Ext.data.ModelManager,
- modelName = record.modelName,
- modelClass = mgr.getModel(modelName),
- newFields = [],
- i, newField, len;
- // Start by adding the NodeInterface methods to the Model's prototype
- modelClass.override(this.getPrototypeBody());
- newFields = this.applyFields(modelClass, [
- {name: 'parentId', type: 'string', defaultValue: null},
- {name: 'index', type: 'int', defaultValue: 0},
- {name: 'depth', type: 'int', defaultValue: 0, persist: false},
- {name: 'expanded', type: 'bool', defaultValue: false, persist: false},
- {name: 'expandable', type: 'bool', defaultValue: true, persist: false},
- {name: 'checked', type: 'auto', defaultValue: null},
- {name: 'leaf', type: 'bool', defaultValue: false, persist: false},
- {name: 'cls', type: 'string', defaultValue: null, persist: false},
- {name: 'iconCls', type: 'string', defaultValue: null, persist: false},
- {name: 'root', type: 'boolean', defaultValue: false, persist: false},
- {name: 'isLast', type: 'boolean', defaultValue: false, persist: false},
- {name: 'isFirst', type: 'boolean', defaultValue: false, persist: false},
- {name: 'allowDrop', type: 'boolean', defaultValue: true, persist: false},
- {name: 'allowDrag', type: 'boolean', defaultValue: true, persist: false},
- {name: 'loaded', type: 'boolean', defaultValue: false, persist: false},
- {name: 'loading', type: 'boolean', defaultValue: false, persist: false},
- {name: 'href', type: 'string', defaultValue: null, persist: false},
- {name: 'hrefTarget', type: 'string', defaultValue: null, persist: false},
- {name: 'qtip', type: 'string', defaultValue: null, persist: false},
- {name: 'qtitle', type: 'string', defaultValue: null, persist: false}
- ]);
- len = newFields.length;
- // We set a dirty flag on the fields collection of the model. Any reader that
- // will read in data for this model will update their extractor functions.
- modelClass.getFields().isDirty = true;
- // Set default values
- for (i = 0; i < len; ++i) {
- newField = newFields[i];
- if (record.get(newField.getName()) === undefined) {
- record.data[newField.getName()] = newField.getDefaultValue();
- }
- }
- }
- if (!record.isDecorated) {
- record.isDecorated = true;
- Ext.applyIf(record, {
- firstChild: null,
- lastChild: null,
- parentNode: null,
- previousSibling: null,
- nextSibling: null,
- childNodes: []
- });
- record.enableBubble([
- /**
- * @event append
- * Fires when a new child node is appended.
- * @param {Ext.data.NodeInterface} this This node.
- * @param {Ext.data.NodeInterface} node The newly appended node.
- * @param {Number} index The index of the newly appended node.
- */
- "append",
- /**
- * @event remove
- * Fires when a child node is removed.
- * @param {Ext.data.NodeInterface} this This node.
- * @param {Ext.data.NodeInterface} node The removed node.
- */
- "remove",
- /**
- * @event move
- * Fires when this node is moved to a new location in the tree.
- * @param {Ext.data.NodeInterface} this This node.
- * @param {Ext.data.NodeInterface} oldParent The old parent of this node.
- * @param {Ext.data.NodeInterface} newParent The new parent of this node.
- * @param {Number} index The index it was moved to.
- */
- "move",
- /**
- * @event insert
- * Fires when a new child node is inserted.
- * @param {Ext.data.NodeInterface} this This node.
- * @param {Ext.data.NodeInterface} node The child node inserted.
- * @param {Ext.data.NodeInterface} refNode The child node the node was inserted before.
- */
- "insert",
- /**
- * @event beforeappend
- * Fires before a new child is appended, return `false` to cancel the append.
- * @param {Ext.data.NodeInterface} this This node.
- * @param {Ext.data.NodeInterface} node The child node to be appended.
- */
- "beforeappend",
- /**
- * @event beforeremove
- * Fires before a child is removed, return `false` to cancel the remove.
- * @param {Ext.data.NodeInterface} this This node.
- * @param {Ext.data.NodeInterface} node The child node to be removed.
- */
- "beforeremove",
- /**
- * @event beforemove
- * Fires before this node is moved to a new location in the tree. Return `false` to cancel the move.
- * @param {Ext.data.NodeInterface} this This node.
- * @param {Ext.data.NodeInterface} oldParent The parent of this node.
- * @param {Ext.data.NodeInterface} newParent The new parent this node is moving to.
- * @param {Number} index The index it is being moved to.
- */
- "beforemove",
- /**
- * @event beforeinsert
- * Fires before a new child is inserted, return false to cancel the insert.
- * @param {Ext.data.NodeInterface} this This node
- * @param {Ext.data.NodeInterface} node The child node to be inserted
- * @param {Ext.data.NodeInterface} refNode The child node the node is being inserted before
- */
- "beforeinsert",
- /**
- * @event expand
- * Fires when this node is expanded.
- * @param {Ext.data.NodeInterface} this The expanding node.
- */
- "expand",
- /**
- * @event collapse
- * Fires when this node is collapsed.
- * @param {Ext.data.NodeInterface} this The collapsing node.
- */
- "collapse",
- /**
- * @event beforeexpand
- * Fires before this node is expanded.
- * @param {Ext.data.NodeInterface} this The expanding node.
- */
- "beforeexpand",
- /**
- * @event beforecollapse
- * Fires before this node is collapsed.
- * @param {Ext.data.NodeInterface} this The collapsing node.
- */
- "beforecollapse",
- /**
- * @event sort
- * Fires when this node's childNodes are sorted.
- * @param {Ext.data.NodeInterface} this This node.
- * @param {Ext.data.NodeInterface[]} childNodes The childNodes of this node.
- */
- "sort",
- 'load'
- ]);
- }
- return record;
- },
- applyFields: function(modelClass, addFields) {
- var modelPrototype = modelClass.prototype,
- fields = modelPrototype.fields,
- keys = fields.keys,
- ln = addFields.length,
- addField, i,
- newFields = [];
- for (i = 0; i < ln; i++) {
- addField = addFields[i];
- if (!Ext.Array.contains(keys, addField.name)) {
- addField = Ext.create('Ext.data.Field', addField);
- newFields.push(addField);
- fields.add(addField);
- }
- }
- return newFields;
- },
- getPrototypeBody: function() {
- return {
- isNode: true,
- /**
- * Ensures that the passed object is an instance of a Record with the NodeInterface applied
- * @return {Boolean}
- * @private
- */
- createNode: function(node) {
- if (Ext.isObject(node) && !node.isModel) {
- node = Ext.data.ModelManager.create(node, this.modelName);
- }
- // Make sure the node implements the node interface
- return Ext.data.NodeInterface.decorate(node);
- },
- /**
- * Returns true if this node is a leaf
- * @return {Boolean}
- */
- isLeaf : function() {
- return this.get('leaf') === true;
- },
- /**
- * Sets the first child of this node
- * @private
- * @param {Ext.data.NodeInterface} node
- */
- setFirstChild : function(node) {
- this.firstChild = node;
- },
- /**
- * Sets the last child of this node
- * @private
- * @param {Ext.data.NodeInterface} node
- */
- setLastChild : function(node) {
- this.lastChild = node;
- },
- /**
- * Updates general data of this node like isFirst, isLast, depth. This
- * method is internally called after a node is moved. This shouldn't
- * have to be called by the developer unless they are creating custom
- * Tree plugins.
- * @return {Boolean}
- */
- updateInfo: function(silent) {
- var me = this,
- parentNode = me.parentNode,
- isFirst = (!parentNode ? true : parentNode.firstChild == me),
- isLast = (!parentNode ? true : parentNode.lastChild == me),
- depth = 0,
- parent = me,
- children = me.childNodes,
- ln = children.length,
- i;
- while (parent.parentNode) {
- ++depth;
- parent = parent.parentNode;
- }
- me.beginEdit();
- me.set({
- isFirst: isFirst,
- isLast: isLast,
- depth: depth,
- index: parentNode ? parentNode.indexOf(me) : 0,
- parentId: parentNode ? parentNode.getId() : null
- });
- me.endEdit(silent);
- if (silent) {
- me.commit(silent);
- }
- for (i = 0; i < ln; i++) {
- children[i].updateInfo(silent);
- }
- },
- /**
- * Returns `true` if this node is the last child of its parent.
- * @return {Boolean}
- */
- isLast : function() {
- return this.get('isLast');
- },
- /**
- * Returns `true` if this node is the first child of its parent.
- * @return {Boolean}
- */
- isFirst : function() {
- return this.get('isFirst');
- },
- /**
- * Returns `true` if this node has one or more child nodes, else `false`.
- * @return {Boolean}
- */
- hasChildNodes : function() {
- return !this.isLeaf() && this.childNodes.length > 0;
- },
- /**
- * Returns `true` if this node has one or more child nodes, or if the `expandable`
- * node attribute is explicitly specified as `true`, otherwise returns `false`.
- * @return {Boolean}
- */
- isExpandable : function() {
- var me = this;
- if (me.get('expandable')) {
- return !(me.isLeaf() || (me.isLoaded() && !me.hasChildNodes()));
- }
- return false;
- },
- /**
- * Insert node(s) as the last child node of this node.
- *
- * If the node was previously a child node of another parent node, it will be removed from that node first.
- *
- * @param {Ext.data.NodeInterface/Ext.data.NodeInterface[]} node The node or Array of nodes to append.
- * @return {Ext.data.NodeInterface} The appended node if single append, or `null` if an array was passed.
- */
- appendChild : function(node, suppressEvents, suppressNodeUpdate) {
- var me = this,
- i, ln,
- index,
- oldParent,
- ps;
- // if passed an array or multiple args do them one by one
- if (Ext.isArray(node)) {
- for (i = 0, ln = node.length; i < ln; i++) {
- me.appendChild(node[i]);
- }
- } else {
- // Make sure it is a record
- node = me.createNode(node);
- if (suppressEvents !== true && me.fireEvent("beforeappend", me, node) === false) {
- return false;
- }
- index = me.childNodes.length;
- oldParent = node.parentNode;
- // it's a move, make sure we move it cleanly
- if (oldParent) {
- if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index) === false) {
- return false;
- }
- oldParent.removeChild(node, null, false, true);
- }
- index = me.childNodes.length;
- if (index === 0) {
- me.setFirstChild(node);
- }
- me.childNodes.push(node);
- node.parentNode = me;
- node.nextSibling = null;
- me.setLastChild(node);
- ps = me.childNodes[index - 1];
- if (ps) {
- node.previousSibling = ps;
- ps.nextSibling = node;
- ps.updateInfo(suppressNodeUpdate);
- } else {
- node.previousSibling = null;
- }
- node.updateInfo(suppressNodeUpdate);
- // As soon as we append a child to this node, we are loaded
- if (!me.isLoaded()) {
- me.set('loaded', true);
- }
- // If this node didn't have any childnodes before, update myself
- else if (me.childNodes.length === 1) {
- me.set('loaded', me.isLoaded());
- }
- if (suppressEvents !== true) {
- me.fireEvent("append", me, node, index);
- if (oldParent) {
- node.fireEvent("move", node, oldParent, me, index);
- }
- }
- return node;
- }
- },
- /**
- * Returns the bubble target for this node.
- * @private
- * @return {Object} The bubble target.
- */
- getBubbleTarget: function() {
- return this.parentNode;
- },
- /**
- * Removes a child node from this node.
- * @param {Ext.data.NodeInterface} node The node to remove.
- * @param {Boolean} [destroy=false] `true` to destroy the node upon removal.
- * @return {Ext.data.NodeInterface} The removed node.
- */
- removeChild : function(node, destroy, suppressEvents, suppressNodeUpdate) {
- var me = this,
- index = me.indexOf(node);
- if (index == -1 || (suppressEvents !== true && me.fireEvent("beforeremove", me, node) === false)) {
- return false;
- }
- // remove it from childNodes collection
- Ext.Array.erase(me.childNodes, index, 1);
- // update child refs
- if (me.firstChild == node) {
- me.setFirstChild(node.nextSibling);
- }
- if (me.lastChild == node) {
- me.setLastChild(node.previousSibling);
- }
- if (suppressEvents !== true) {
- me.fireEvent("remove", me, node);
- }
- // update siblings
- if (node.previousSibling) {
- node.previousSibling.nextSibling = node.nextSibling;
- node.previousSibling.updateInfo(suppressNodeUpdate);
- }
- if (node.nextSibling) {
- node.nextSibling.previousSibling = node.previousSibling;
- node.nextSibling.updateInfo(suppressNodeUpdate);
- }
- // If this node suddenly doesn't have childnodes anymore, update myself
- if (!me.childNodes.length) {
- me.set('loaded', me.isLoaded());
- }
- if (destroy) {
- node.destroy(true);
- } else {
- node.clear();
- }
- return node;
- },
- /**
- * Creates a copy (clone) of this Node.
- * @param {String} id (optional) A new id, defaults to this Node's id.
- * @param {Boolean} deep (optional) If passed as `true`, all child Nodes are recursively copied into the new Node.
- * If omitted or `false`, the copy will have no child Nodes.
- * @return {Ext.data.NodeInterface} A copy of this Node.
- */
- copy: function(newId, deep) {
- var me = this,
- result = me.callOverridden(arguments),
- len = me.childNodes ? me.childNodes.length : 0,
- i;
- // Move child nodes across to the copy if required
- if (deep) {
- for (i = 0; i < len; i++) {
- result.appendChild(me.childNodes[i].copy(true));
- }
- }
- return result;
- },
- /**
- * Clear the node.
- * @private
- * @param {Boolean} destroy `true` to destroy the node.
- */
- clear : function(destroy) {
- var me = this;
- // clear any references from the node
- me.parentNode = me.previousSibling = me.nextSibling = null;
- if (destroy) {
- me.firstChild = me.lastChild = null;
- }
- },
- /**
- * Destroys the node.
- */
- destroy : function(silent) {
- /*
- * Silent is to be used in a number of cases
- * 1) When setRoot is called.
- * 2) When destroy on the tree is called
- * 3) For destroying child nodes on a node
- */
- var me = this,
- options = me.destroyOptions;
- if (silent === true) {
- me.clear(true);
- Ext.each(me.childNodes, function(n) {
- n.destroy(true);
- });
- me.childNodes = null;
- delete me.destroyOptions;
- me.callOverridden([options]);
- } else {
- me.destroyOptions = silent;
- // overridden method will be called, since remove will end up calling destroy(true);
- me.remove(true);
- }
- },
- /**
- * Inserts the first node before the second node in this nodes `childNodes` collection.
- * @param {Ext.data.NodeInterface} node The node to insert.
- * @param {Ext.data.NodeInterface} refNode The node to insert before (if `null` the node is appended).
- * @return {Ext.data.NodeInterface} The inserted node.
- */
- insertBefore : function(node, refNode, suppressEvents) {
- var me = this,
- index = me.indexOf(refNode),
- oldParent = node.parentNode,
- refIndex = index,
- ps;
- if (!refNode) { // like standard Dom, refNode can be null for append
- return me.appendChild(node);
- }
- // nothing to do
- if (node == refNode) {
- return false;
- }
- // Make sure it is a record with the NodeInterface
- node = me.createNode(node);
- if (suppressEvents !== true && me.fireEvent("beforeinsert", me, node, refNode) === false) {
- return false;
- }
- // when moving internally, indexes will change after remove
- if (oldParent == me && me.indexOf(node) < index) {
- refIndex--;
- }
- // it's a move, make sure we move it cleanly
- if (oldParent) {
- if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index, refNode) === false) {
- return false;
- }
- oldParent.removeChild(node);
- }
- if (refIndex === 0) {
- me.setFirstChild(node);
- }
- Ext.Array.splice(me.childNodes, refIndex, 0, node);
- node.parentNode = me;
- node.nextSibling = refNode;
- refNode.previousSibling = node;
- ps = me.childNodes[refIndex - 1];
- if (ps) {
- node.previousSibling = ps;
- ps.nextSibling = node;
- ps.updateInfo();
- } else {
- node.previousSibling = null;
- }
- node.updateInfo();
- if (!me.isLoaded()) {
- me.set('loaded', true);
- }
- // If this node didn't have any childnodes before, update myself
- else if (me.childNodes.length === 1) {
- me.set('loaded', me.isLoaded());
- }
- if (suppressEvents !== true) {
- me.fireEvent("insert", me, node, refNode);
- if (oldParent) {
- node.fireEvent("move", node, oldParent, me, refIndex, refNode);
- }
- }
- return node;
- },
- /**
- * Insert a node into this node.
- * @param {Number} index The zero-based index to insert the node at.
- * @param {Ext.data.Model} node The node to insert.
- * @return {Ext.data.Model} The record you just inserted.
- */
- insertChild: function(index, node) {
- var sibling = this.childNodes[index];
- if (sibling) {
- return this.insertBefore(node, sibling);
- }
- else {
- return this.appendChild(node);
- }
- },
- /**
- * Removes this node from its parent.
- * @param {Boolean} [destroy=false] `true` to destroy the node upon removal.
- * @return {Ext.data.NodeInterface} this
- */
- remove : function(destroy, suppressEvents) {
- var parentNode = this.parentNode;
- if (parentNode) {
- parentNode.removeChild(this, destroy, suppressEvents, true);
- }
- return this;
- },
- /**
- * Removes all child nodes from this node.
- * @param {Boolean} [destroy=false] `true` to destroy the node upon removal.
- * @return {Ext.data.NodeInterface} this
- */
- removeAll : function(destroy, suppressEvents) {
- var cn = this.childNodes,
- n;
- while ((n = cn[0])) {
- this.removeChild(n, destroy, suppressEvents);
- }
- return this;
- },
- /**
- * Returns the child node at the specified index.
- * @param {Number} index
- * @return {Ext.data.NodeInterface}
- */
- getChildAt : function(index) {
- return this.childNodes[index];
- },
- /**
- * Replaces one child node in this node with another.
- * @param {Ext.data.NodeInterface} newChild The replacement node.
- * @param {Ext.data.NodeInterface} oldChild The node to replace.
- * @return {Ext.data.NodeInterface} The replaced node.
- */
- replaceChild : function(newChild, oldChild, suppressEvents) {
- var s = oldChild ? oldChild.nextSibling : null;
- this.removeChild(oldChild, suppressEvents);
- this.insertBefore(newChild, s, suppressEvents);
- return oldChild;
- },
- /**
- * Returns the index of a child node.
- * @param {Ext.data.NodeInterface} node
- * @return {Number} The index of the node or -1 if it was not found.
- */
- indexOf : function(child) {
- return Ext.Array.indexOf(this.childNodes, child);
- },
- /**
- * Gets the hierarchical path from the root of the current node.
- * @param {String} field (optional) The field to construct the path from. Defaults to the model `idProperty`.
- * @param {String} [separator=/] (optional) A separator to use.
- * @return {String} The node path
- */
- getPath: function(field, separator) {
- field = field || this.idProperty;
- separator = separator || '/';
- var path = [this.get(field)],
- parent = this.parentNode;
- while (parent) {
- path.unshift(parent.get(field));
- parent = parent.parentNode;
- }
- return separator + path.join(separator);
- },
- /**
- * Returns depth of this node (the root node has a depth of 0).
- * @return {Number}
- */
- getDepth : function() {
- return this.get('depth');
- },
- /**
- * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
- * will be the args provided or the current node. If the function returns `false` at any point,
- * the bubble is stopped.
- * @param {Function} fn The function to call.
- * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to the current Node.
- * @param {Array} args (optional) The args to call the function with (default to passing the current Node).
- */
- bubble : function(fn, scope, args) {
- var p = this;
- while (p) {
- if (fn.apply(scope || p, args || [p]) === false) {
- break;
- }
- p = p.parentNode;
- }
- },
- /**
- * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
- * will be the args provided or the current node. If the function returns false at any point,
- * the cascade is stopped on that branch.
- * @param {Function} fn The function to call
- * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to the current Node.
- * @param {Array} args (optional) The args to call the function with (default to passing the current Node).
- */
- cascadeBy : function(fn, scope, args) {
- if (fn.apply(scope || this, args || [this]) !== false) {
- var childNodes = this.childNodes,
- length = childNodes.length,
- i;
- for (i = 0; i < length; i++) {
- childNodes[i].cascadeBy(fn, scope, args);
- }
- }
- },
- /**
- * Iterates the child nodes of this node, calling the specified function with each node. The arguments to the function
- * will be the args provided or the current node. If the function returns false at any point,
- * the iteration stops.
- * @param {Function} fn The function to call.
- * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to the current Node in the iteration.
- * @param {Array} args (optional) The args to call the function with (default to passing the current Node).
- */
- eachChild : function(fn, scope, args) {
- var childNodes = this.childNodes,
- length = childNodes.length,
- i;
- for (i = 0; i < length; i++) {
- if (fn.apply(scope || this, args || [childNodes[i]]) === false) {
- break;
- }
- }
- },
- /**
- * Finds the first child that has the attribute with the specified value.
- * @param {String} attribute The attribute name.
- * @param {Object} value The value to search for.
- * @param {Boolean} deep (Optional) `true` to search through nodes deeper than the immediate children.
- * @return {Ext.data.NodeInterface} The found child or `null` if none was found.
- */
- findChild : function(attribute, value, deep) {
- return this.findChildBy(function() {
- return this.get(attribute) == value;
- }, null, deep);
- },
- /**
- * Finds the first child by a custom function. The child matches if the function passed returns `true`.
- * @param {Function} fn A function which must return `true` if the passed Node is the required Node.
- * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. Defaults to the Node being tested.
- * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children.
- * @return {Ext.data.NodeInterface} The found child or null if `none` was found.
- */
- findChildBy : function(fn, scope, deep) {
- var cs = this.childNodes,
- len = cs.length,
- i = 0, n, res;
- for (; i < len; i++) {
- n = cs[i];
- if (fn.call(scope || n, n) === true) {
- return n;
- }
- else if (deep) {
- res = n.findChildBy(fn, scope, deep);
- if (res !== null) {
- return res;
- }
- }
- }
- return null;
- },
- /**
- * Returns `true` if this node is an ancestor (at any point) of the passed node.
- * @param {Ext.data.NodeInterface} node
- * @return {Boolean}
- */
- contains : function(node) {
- return node.isAncestor(this);
- },
- /**
- * Returns `true` if the passed node is an ancestor (at any point) of this node.
- * @param {Ext.data.NodeInterface} node
- * @return {Boolean}
- */
- isAncestor : function(node) {
- var p = this.parentNode;
- while (p) {
- if (p == node) {
- return true;
- }
- p = p.parentNode;
- }
- return false;
- },
- /**
- * Sorts this nodes children using the supplied sort function.
- * @param {Function} sortFn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
- * @param {Boolean} recursive Whether or not to apply this sort recursively.
- * @param {Boolean} suppressEvent Set to true to not fire a sort event.
- */
- sort: function(sortFn, recursive, suppressEvent) {
- var cs = this.childNodes,
- ln = cs.length,
- i, n;
- if (ln > 0) {
- Ext.Array.sort(cs, sortFn);
- for (i = 0; i < ln; i++) {
- n = cs[i];
- n.previousSibling = cs[i-1];
- n.nextSibling = cs[i+1];
- if (i === 0) {
- this.setFirstChild(n);
- }
- if (i == ln - 1) {
- this.setLastChild(n);
- }
- n.updateInfo(suppressEvent);
- if (recursive && !n.isLeaf()) {
- n.sort(sortFn, true, true);
- }
- }
- this.notifyStores('afterEdit', ['sorted'], {sorted: 'sorted'});
- if (suppressEvent !== true) {
- this.fireEvent('sort', this, cs);
- }
- }
- },
- /**
- * Returns `true` if this node is expanded.
- * @return {Boolean}
- */
- isExpanded: function() {
- return this.get('expanded');
- },
- /**
- * Returns `true` if this node is loaded.
- * @return {Boolean}
- */
- isLoaded: function() {
- return this.get('loaded');
- },
- /**
- * Returns `true` if this node is loading.
- * @return {Boolean}
- */
- isLoading: function() {
- return this.get('loading');
- },
- /**
- * Returns `true` if this node is the root node.
- * @return {Boolean}
- */
- isRoot: function() {
- return !this.parentNode;
- },
- /**
- * Returns `true` if this node is visible.
- * @return {Boolean}
- */
- isVisible: function() {
- var parent = this.parentNode;
- while (parent) {
- if (!parent.isExpanded()) {
- return false;
- }
- parent = parent.parentNode;
- }
- return true;
- },
- /**
- * Expand this node.
- * @param {Function} recursive (Optional) `true` to recursively expand all the children.
- * @param {Function} callback (Optional) The function to execute once the expand completes.
- * @param {Object} scope (Optional) The scope to run the callback in.
- */
- expand: function(recursive, callback, scope) {
- var me = this;
- if (!me.isLeaf()) {
- if (me.isLoading()) {
- me.on('expand', function() {
- me.expand(recursive, callback, scope);
- }, me, {single: true});
- }
- else {
- if (!me.isExpanded()) {
- // The TreeStore actually listens for the beforeexpand method and checks
- // whether we have to asynchronously load the children from the server
- // first. Thats why we pass a callback function to the event that the
- // store can call once it has loaded and parsed all the children.
- me.fireAction('expand', [this], function() {
- me.set('expanded', true);
- Ext.callback(callback, scope || me, [me.childNodes]);
- });
- }
- else {
- Ext.callback(callback, scope || me, [me.childNodes]);
- }
- }
- } else {
- Ext.callback(callback, scope || me);
- }
- },
- /**
- * Collapse this node.
- * @param {Function} recursive (Optional) `true` to recursively collapse all the children.
- * @param {Function} callback (Optional) The function to execute once the collapse completes.
- * @param {Object} scope (Optional) The scope to run the callback in.
- */
- collapse: function(recursive, callback, scope) {
- var me = this;
- // First we start by checking if this node is a parent
- if (!me.isLeaf() && me.isExpanded()) {
- this.fireAction('collapse', [me], function() {
- me.set('expanded', false);
- Ext.callback(callback, scope || me, [me.childNodes]);
- });
- } else {
- Ext.callback(callback, scope || me, [me.childNodes]);
- }
- }
- };
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.data.NodeStore', {
- extend: 'Ext.data.Store',
- alias: 'store.node',
- requires: ['Ext.data.NodeInterface'],
- config: {
- /**
- * @cfg {Ext.data.Model} node The Record you want to bind this Store to. Note that
- * this record will be decorated with the {@link Ext.data.NodeInterface} if this is not the
- * case yet.
- * @accessor
- */
- node: null,
- /**
- * @cfg {Boolean} recursive Set this to `true` if you want this NodeStore to represent
- * all the descendants of the node in its flat data collection. This is useful for
- * rendering a tree structure to a DataView and is being used internally by
- * the TreeView. Any records that are moved, removed, inserted or appended to the
- * node at any depth below the node this store is bound to will be automatically
- * updated in this Store's internal flat data structure.
- * @accessor
- */
- recursive: false,
- /**
- * @cfg {Boolean} rootVisible `false` to not include the root node in this Stores collection.
- * @accessor
- */
- rootVisible: false,
- sorters: undefined,
- filters: undefined,
- /**
- * @cfg {Boolean} folderSort
- * Set to `true` to automatically prepend a leaf sorter.
- */
- folderSort: false
- },
- afterEdit: function(record, modifiedFields) {
- if (modifiedFields) {
- if (modifiedFields.indexOf('loaded') !== -1) {
- return this.add(this.retrieveChildNodes(record));
- }
- if (modifiedFields.indexOf('expanded') !== -1) {
- return this.filter();
- }
- if (modifiedFields.indexOf('sorted') !== -1) {
- return this.sort();
- }
- }
- this.callParent(arguments);
- },
- onNodeAppend: function(parent, node) {
- this.add([node].concat(this.retrieveChildNodes(node)));
- },
- onNodeInsert: function(parent, node) {
- this.add([node].concat(this.retrieveChildNodes(node)));
- },
- onNodeRemove: function(parent, node) {
- this.remove([node].concat(this.retrieveChildNodes(node)));
- },
- onNodeSort: function() {
- this.sort();
- },
- updateFolderSort: function(folderSort) {
- if (folderSort) {
- this.setGrouper(function(node) {
- if (node.isLeaf()) {
- return 1;
- }
- return 0;
- });
- } else {
- this.setGrouper(null);
- }
- },
- createDataCollection: function() {
- var collection = this.callParent();
- collection.handleSort = Ext.Function.bind(this.handleTreeSort, this, [collection], true);
- collection.findInsertionIndex = Ext.Function.bind(this.handleTreeInsertionIndex, this, [collection, collection.findInsertionIndex], true);
- return collection;
- },
- handleTreeInsertionIndex: function(items, item, collection, originalFn) {
- return originalFn.call(collection, items, item, this.treeSortFn);
- },
- handleTreeSort: function(data) {
- Ext.Array.sort(data, this.treeSortFn);
- return data;
- },
- /**
- * This is a custom tree sorting algorithm. It uses the index property on each node to determine
- * how to sort siblings. It uses the depth property plus the index to create a weight for each node.
- * This weight algorithm has the limitation of not being able to go more then 80 levels in depth, or
- * more then 10k nodes per parent. The end result is a flat collection being correctly sorted based
- * on this one single sort function.
- * @param node1
- * @param node2
- * @return {Number}
- * @private
- */
- treeSortFn: function(node1, node2) {
- // A shortcut for siblings
- if (node1.parentNode === node2.parentNode) {
- return (node1.data.index < node2.data.index) ? -1 : 1;
- }
- // @NOTE: with the following algorithm we can only go 80 levels deep in the tree
- // and each node can contain 10000 direct children max
- var weight1 = 0,
- weight2 = 0,
- parent1 = node1,
- parent2 = node2;
- while (parent1) {
- weight1 += (Math.pow(10, (parent1.data.depth+1) * -4) * (parent1.data.index+1));
- parent1 = parent1.parentNode;
- }
- while (parent2) {
- weight2 += (Math.pow(10, (parent2.data.depth+1) * -4) * (parent2.data.index+1));
- parent2 = parent2.parentNode;
- }
- if (weight1 > weight2) {
- return 1;
- } else if (weight1 < weight2) {
- return -1;
- }
- return (node1.data.index > node2.data.index) ? 1 : -1;
- },
- applyFilters: function(filters) {
- var me = this;
- return function(item) {
- return me.isVisible(item);
- };
- },
- applyProxy: function(proxy) {
- //<debug>
- if (proxy) {
- Ext.Logger.warn("A NodeStore cannot be bound to a proxy. Instead bind it to a record " +
- "decorated with the NodeInterface by setting the node config.");
- }
- //</debug>
- },
- applyNode: function(node) {
- if (node) {
- node = Ext.data.NodeInterface.decorate(node);
- }
- return node;
- },
- updateNode: function(node, oldNode) {
- if (oldNode && !oldNode.isDestroyed) {
- oldNode.un({
- append : 'onNodeAppend',
- insert : 'onNodeInsert',
- remove : 'onNodeRemove',
- load : 'onNodeLoad',
- scope: this
- });
- oldNode.unjoin(this);
- }
- if (node) {
- node.on({
- scope : this,
- append : 'onNodeAppend',
- insert : 'onNodeInsert',
- remove : 'onNodeRemove',
- load : 'onNodeLoad'
- });
- node.join(this);
- var data = [];
- if (node.childNodes.length) {
- data = data.concat(this.retrieveChildNodes(node));
- }
- if (this.getRootVisible()) {
- data.push(node);
- } else if (node.isLoaded() || node.isLoading()) {
- node.set('expanded', true);
- }
- this.data.clear();
- this.fireEvent('clear', this);
- this.suspendEvents();
- this.add(data);
- this.resumeEvents();
- this.fireEvent('refresh', this, this.data);
- }
- },
- /**
- * Private method used to deeply retrieve the children of a record without recursion.
- * @private
- * @param root
- * @return {Array}
- */
- retrieveChildNodes: function(root) {
- var node = this.getNode(),
- recursive = this.getRecursive(),
- added = [],
- child = root;
- if (!root.childNodes.length || (!recursive && root !== node)) {
- return added;
- }
- if (!recursive) {
- return root.childNodes;
- }
- while (child) {
- if (child._added) {
- delete child._added;
- if (child === root) {
- break;
- } else {
- child = child.nextSibling || child.parentNode;
- }
- } else {
- if (child !== root) {
- added.push(child);
- }
- if (child.firstChild) {
- child._added = true;
- child = child.firstChild;
- } else {
- child = child.nextSibling || child.parentNode;
- }
- }
- }
- return added;
- },
- /**
- * @param {Object} node
- * @return {Boolean}
- */
- isVisible: function(node) {
- var parent = node.parentNode;
- if (!this.getRecursive() && parent !== this.getNode()) {
- return false;
- }
- while (parent) {
- if (!parent.isExpanded()) {
- return false;
- }
- //we need to check this because for a nodestore the node is not likely to be the root
- //so we stop going up the chain when we hit the original node as we don't care about any
- //ancestors above the configured node
- if (parent === this.getNode()) {
- break;
- }
- parent = parent.parentNode;
- }
- return true;
- }
- });
- /**
- * @aside guide stores
- *
- * The TreeStore is a store implementation that allows for nested data.
- *
- * It provides convenience methods for loading nodes, as well as the ability to use
- * the hierarchical tree structure combined with a store. This class also relays many events from
- * the Tree for convenience.
- *
- * # Using Models
- *
- * If no Model is specified, an implicit model will be created that implements {@link Ext.data.NodeInterface}.
- * The standard Tree fields will also be copied onto the Model for maintaining their state. These fields are listed
- * in the {@link Ext.data.NodeInterface} documentation.
- *
- * # Reading Nested Data
- *
- * For the tree to read nested data, the {@link Ext.data.reader.Reader} must be configured with a root property,
- * so the reader can find nested data for each node. If a root is not specified, it will default to
- * 'children'.
- */
- Ext.define('Ext.data.TreeStore', {
- extend: 'Ext.data.NodeStore',
- alias: 'store.tree',
- config: {
- /**
- * @cfg {Ext.data.Model/Ext.data.NodeInterface/Object} root
- * The root node for this store. For example:
- *
- * root: {
- * expanded: true,
- * text: "My Root",
- * children: [
- * { text: "Child 1", leaf: true },
- * { text: "Child 2", expanded: true, children: [
- * { text: "GrandChild", leaf: true }
- * ] }
- * ]
- * }
- *
- * Setting the `root` config option is the same as calling {@link #setRootNode}.
- * @accessor
- */
- root: undefined,
- /**
- * @cfg {Boolean} clearOnLoad
- * Remove previously existing child nodes before loading. Default to true.
- * @accessor
- */
- clearOnLoad : true,
- /**
- * @cfg {String} nodeParam
- * The name of the parameter sent to the server which contains the identifier of the node.
- * Defaults to 'node'.
- * @accessor
- */
- nodeParam: 'node',
- /**
- * @cfg {String} defaultRootId
- * The default root id. Defaults to 'root'
- * @accessor
- */
- defaultRootId: 'root',
- /**
- * @cfg {String} defaultRootProperty
- * The root property to specify on the reader if one is not explicitly defined.
- * @accessor
- */
- defaultRootProperty: 'children',
- /**
- * @cfg {Boolean} recursive
- * @private
- * @hide
- */
- recursive: true
- /**
- * @cfg {Object} node
- * @private
- * @hide
- */
- },
- applyProxy: function() {
- return Ext.data.Store.prototype.applyProxy.apply(this, arguments);
- },
- applyRoot: function(root) {
- var me = this;
- root = root || {};
- root = Ext.apply({}, root);
- if (!root.isModel) {
- Ext.applyIf(root, {
- id: me.getStoreId() + '-' + me.getDefaultRootId(),
- text: 'Root',
- allowDrag: false
- });
- root = Ext.data.ModelManager.create(root, me.getModel());
- }
- Ext.data.NodeInterface.decorate(root);
- root.set(root.raw);
- return root;
- },
- handleTreeInsertionIndex: function(items, item, collection, originalFn) {
- if (item.parentNode) {
- item.parentNode.sort(collection.getSortFn(), true, true);
- }
- return this.callParent(arguments);
- },
- handleTreeSort: function(data, collection) {
- if (this._sorting) {
- return data;
- }
- this._sorting = true;
- this.getNode().sort(collection.getSortFn(), true, true);
- delete this._sorting;
- return this.callParent(arguments);
- },
- updateRoot: function(root, oldRoot) {
- if (oldRoot) {
- oldRoot.unBefore({
- expand: 'onNodeBeforeExpand',
- scope: this
- });
- oldRoot.unjoin(this);
- }
- root.onBefore({
- expand: 'onNodeBeforeExpand',
- scope: this
- });
- this.onNodeAppend(null, root);
- this.setNode(root);
- if (!root.isLoaded() && !root.isLoading() && root.isExpanded()) {
- this.load({
- node: root
- });
- }
- /**
- * @event rootchange
- * Fires whenever the root node changes on this TreeStore.
- * @param {Ext.data.TreeStore} store This tree Store
- * @param {Ext.data.Model} newRoot The new root node
- * @param {Ext.data.Model} oldRoot The old root node
- */
- this.fireEvent('rootchange', this, root, oldRoot);
- },
- /**
- * Returns the record node by id
- * @return {Ext.data.NodeInterface}
- */
- getNodeById: function(id) {
- return this.data.getByKey(id);
- },
- onNodeBeforeExpand: function(node, options, e) {
- if (node.isLoading()) {
- e.pause();
- this.on('load', function() {
- e.resume();
- }, this, {single: true});
- }
- else if (!node.isLoaded()) {
- e.pause();
- this.load({
- node: node,
- callback: function() {
- e.resume();
- }
- });
- }
- },
- onNodeAppend: function(parent, node) {
- var proxy = this.getProxy(),
- reader = proxy.getReader(),
- Model = this.getModel(),
- data = node.raw,
- records = [],
- rootProperty = reader.getRootProperty(),
- dataRoot, processedData, i, ln, processedDataItem;
- if (!node.isLeaf()) {
- dataRoot = reader.getRoot(data);
- if (dataRoot) {
- processedData = reader.extractData(dataRoot);
- for (i = 0, ln = processedData.length; i < ln; i++) {
- processedDataItem = processedData[i];
- records.push(new Model(processedDataItem.data, processedDataItem.id, processedDataItem.node));
- }
- if (records.length) {
- this.fillNode(node, records);
- } else {
- node.set('loaded', true);
- }
- // If the child record is not a leaf, and it has a data root (e.g. items: [])
- // and there are items in this data root, then we call fillNode to automatically
- // add these items. fillNode sets the loaded property on the node, meaning that
- // the next time you expand that node, it's not going to the server to request the
- // children. If however you pass back an empty array as items, we have to set the
- // loaded property to true here as well to prevent the items from being be loaded
- // from the server the next time you expand it.
- // If you want to have the items loaded on the next expand, then the data for the
- // node should not contain the items: [] array.
- delete data[rootProperty];
- }
- }
- },
- updateAutoLoad: function(autoLoad) {
- if (autoLoad) {
- var root = this.getRoot();
- if (!root.isLoaded() && !root.isLoading()) {
- this.load({node: root});
- }
- }
- },
- /**
- * Loads the Store using its configured {@link #proxy}.
- * @param {Object} options (Optional) config object. This is passed into the {@link Ext.data.Operation Operation}
- * object that is created and then sent to the proxy's {@link Ext.data.proxy.Proxy#read} function.
- * The options can also contain a node, which indicates which node is to be loaded. If not specified, it will
- * default to the root node.
- * @return {Object}
- */
- load: function(options) {
- options = options || {};
- options.params = options.params || {};
- var me = this,
- node = options.node = options.node || me.getRoot();
- options.params[me.getNodeParam()] = node.getId();
- if (me.getClearOnLoad()) {
- node.removeAll(true);
- }
- node.set('loading', true);
- return me.callParent([options]);
- },
- updateProxy: function(proxy) {
- this.callParent(arguments);
- var reader = proxy.getReader();
- if (!reader.getRootProperty()) {
- reader.setRootProperty(this.getDefaultRootProperty());
- reader.buildExtractors();
- }
- },
- /**
- * @inheritdoc
- */
- removeAll: function() {
- this.getRoot().removeAll(true);
- this.callParent(arguments);
- },
- /**
- * @inheritdoc
- */
- onProxyLoad: function(operation) {
- var me = this,
- records = operation.getRecords(),
- successful = operation.wasSuccessful(),
- node = operation.getNode();
- node.beginEdit();
- node.set('loading', false);
- if (successful) {
- records = me.fillNode(node, records);
- }
- node.endEdit();
- me.loading = false;
- me.loaded = true;
- node.fireEvent('load', node, records, successful);
- me.fireEvent('load', this, records, successful, operation);
- //this is a callback that would have been passed to the 'read' function and is optional
- Ext.callback(operation.getCallback(), operation.getScope() || me, [records, operation, successful]);
- },
- /**
- * Fills a node with a series of child records.
- * @private
- * @param {Ext.data.NodeInterface} node The node to fill.
- * @param {Ext.data.Model[]} records The records to add.
- */
- fillNode: function(node, records) {
- var ln = records ? records.length : 0,
- i, child;
- for (i = 0; i < ln; i++) {
- // true/true to suppress any events fired by the node, or the new child node
- child = node.appendChild(records[i], true, true);
- this.onNodeAppend(node, child);
- }
- node.set('loaded', true);
- return records;
- }
- });
- /**
- * @author Ed Spencer
- * @aside guide models
- *
- * This singleton contains a set of validation functions that can be used to validate any type of data. They are most
- * often used in {@link Ext.data.Model Models}, where they are automatically set up and executed.
- */
- Ext.define('Ext.data.Validations', {
- alternateClassName: 'Ext.data.validations',
- singleton: true,
- config: {
- /**
- * @property {String} presenceMessage
- * The default error message used when a presence validation fails.
- */
- presenceMessage: 'must be present',
- /**
- * @property {String} lengthMessage
- * The default error message used when a length validation fails.
- */
- lengthMessage: 'is the wrong length',
- /**
- * @property {Boolean} formatMessage
- * The default error message used when a format validation fails.
- */
- formatMessage: 'is the wrong format',
- /**
- * @property {String} inclusionMessage
- * The default error message used when an inclusion validation fails.
- */
- inclusionMessage: 'is not included in the list of acceptable values',
- /**
- * @property {String} exclusionMessage
- * The default error message used when an exclusion validation fails.
- */
- exclusionMessage: 'is not an acceptable value',
- /**
- * @property {String} emailMessage
- * The default error message used when an email validation fails
- */
- emailMessage: 'is not a valid email address'
- },
- constructor: function(config) {
- this.initConfig(config);
- },
- /**
- * Returns the configured error message for any of the validation types.
- * @param {String} type The type of validation you want to get the error message for.
- * @return {Object}
- */
- getMessage: function(type) {
- var getterFn = this['get' + type[0].toUpperCase() + type.slice(1) + 'Message'];
- if (getterFn) {
- return getterFn.call(this);
- }
- return '';
- },
- /**
- * The regular expression used to validate email addresses
- * @property emailRe
- * @type RegExp
- */
- emailRe: /^\s*[\w\-\+_]+(\.[\w\-\+_]+)*\@[\w\-\+_]+\.[\w\-\+_]+(\.[\w\-\+_]+)*\s*$/,
- /**
- * Validates that the given value is present.
- * For example:
- *
- * validations: [{type: 'presence', field: 'age'}]
- *
- * @param {Object} config Config object.
- * @param {Object} value The value to validate.
- * @return {Boolean} `true` if validation passed.
- */
- presence: function(config, value) {
- if (arguments.length === 1) {
- value = config;
- }
- return !!value || value === 0;
- },
- /**
- * Returns `true` if the given value is between the configured min and max values.
- * For example:
- *
- * validations: [{type: 'length', field: 'name', min: 2}]
- *
- * @param {Object} config Config object.
- * @param {String} value The value to validate.
- * @return {Boolean} `true` if the value passes validation.
- */
- length: function(config, value) {
- if (value === undefined || value === null) {
- return false;
- }
- var length = value.length,
- min = config.min,
- max = config.max;
- if ((min && length < min) || (max && length > max)) {
- return false;
- } else {
- return true;
- }
- },
- /**
- * Validates that an email string is in the correct format.
- * @param {Object} config Config object.
- * @param {String} email The email address.
- * @return {Boolean} `true` if the value passes validation.
- */
- email: function(config, email) {
- return Ext.data.validations.emailRe.test(email);
- },
- /**
- * Returns `true` if the given value passes validation against the configured `matcher` regex.
- * For example:
- *
- * validations: [{type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}]
- *
- * @param {Object} config Config object.
- * @param {String} value The value to validate.
- * @return {Boolean} `true` if the value passes the format validation.
- */
- format: function(config, value) {
- if (value === undefined || value === null) {
- value = '';
- }
- return !!(config.matcher && config.matcher.test(value));
- },
- /**
- * Validates that the given value is present in the configured `list`.
- * For example:
- *
- * validations: [{type: 'inclusion', field: 'gender', list: ['Male', 'Female']}]
- *
- * @param {Object} config Config object.
- * @param {String} value The value to validate.
- * @return {Boolean} `true` if the value is present in the list.
- */
- inclusion: function(config, value) {
- return config.list && Ext.Array.indexOf(config.list,value) != -1;
- },
- /**
- * Validates that the given value is present in the configured `list`.
- * For example:
- *
- * validations: [{type: 'exclusion', field: 'username', list: ['Admin', 'Operator']}]
- *
- * @param {Object} config Config object.
- * @param {String} value The value to validate.
- * @return {Boolean} `true` if the value is not present in the list.
- */
- exclusion: function(config, value) {
- return config.list && Ext.Array.indexOf(config.list,value) == -1;
- }
- });
- /**
- * @author Tommy Maintz
- *
- * This class is a sequential id generator. A simple use of this class would be like so:
- *
- * Ext.define('MyApp.data.MyModel', {
- * extend: 'Ext.data.Model',
- * config: {
- * identifier: 'sequential'
- * }
- * });
- * // assign id's of 1, 2, 3, etc.
- *
- * An example of a configured generator would be:
- *
- * Ext.define('MyApp.data.MyModel', {
- * extend: 'Ext.data.Model',
- * config: {
- * identifier: {
- * type: 'sequential',
- * prefix: 'ID_',
- * seed: 1000
- * }
- * }
- * });
- * // assign id's of ID_1000, ID_1001, ID_1002, etc.
- *
- */
- Ext.define('Ext.data.identifier.Sequential', {
- extend: 'Ext.data.identifier.Simple',
- alias: 'data.identifier.sequential',
- config: {
- /**
- * @cfg {String} prefix
- * The string to place in front of the sequential number for each generated id. The
- * default is blank.
- */
- prefix: '',
- /**
- * @cfg {Number} seed
- * The number at which to start generating sequential id's. The default is 1.
- */
- seed: 1
- },
- constructor: function() {
- var me = this;
- me.callParent(arguments);
- me.parts = [me.getPrefix(), ''];
- },
- generate: function(record) {
- var me = this,
- parts = me.parts,
- seed = me.getSeed() + 1;
- me.setSeed(seed);
- parts[1] = seed;
- return parts.join('');
- }
- });
- /**
- * @author Tommy Maintz
- *
- * This class generates UUID's according to RFC 4122. This class has a default id property.
- * This means that a single instance is shared unless the id property is overridden. Thus,
- * two {@link Ext.data.Model} instances configured like the following share one generator:
- *
- * Ext.define('MyApp.data.MyModelX', {
- * extend: 'Ext.data.Model',
- * config: {
- * identifier: 'uuid'
- * }
- * });
- *
- * Ext.define('MyApp.data.MyModelY', {
- * extend: 'Ext.data.Model',
- * config: {
- * identifier: 'uuid'
- * }
- * });
- *
- * This allows all models using this class to share a commonly configured instance.
- *
- * # Using Version 1 ("Sequential") UUID's
- *
- * If a server can provide a proper timestamp and a "cryptographic quality random number"
- * (as described in RFC 4122), the shared instance can be configured as follows:
- *
- * Ext.data.identifier.Uuid.Global.reconfigure({
- * version: 1,
- * clockSeq: clock, // 14 random bits
- * salt: salt, // 48 secure random bits (the Node field)
- * timestamp: ts // timestamp per Section 4.1.4
- * });
- *
- * // or these values can be split into 32-bit chunks:
- *
- * Ext.data.identifier.Uuid.Global.reconfigure({
- * version: 1,
- * clockSeq: clock,
- * salt: { lo: saltLow32, hi: saltHigh32 },
- * timestamp: { lo: timestampLow32, hi: timestamptHigh32 }
- * });
- *
- * This approach improves the generator's uniqueness by providing a valid timestamp and
- * higher quality random data. Version 1 UUID's should not be used unless this information
- * can be provided by a server and care should be taken to avoid caching of this data.
- *
- * See [http://www.ietf.org/rfc/rfc4122.txt](http://www.ietf.org/rfc/rfc4122.txt) for details.
- */
- Ext.define('Ext.data.identifier.Uuid', {
- extend: 'Ext.data.identifier.Simple',
- alias: 'data.identifier.uuid',
- isUnique: true,
- config: {
- /**
- * The id for this generator instance. By default all model instances share the same
- * UUID generator instance. By specifying an id other then 'uuid', a unique generator instance
- * will be created for the Model.
- */
- id: undefined,
- /**
- * @property {Number/Object} salt
- * When created, this value is a 48-bit number. For computation, this value is split
- * into 32-bit parts and stored in an object with `hi` and `lo` properties.
- */
- salt: null,
- /**
- * @property {Number/Object} timestamp
- * When created, this value is a 60-bit number. For computation, this value is split
- * into 32-bit parts and stored in an object with `hi` and `lo` properties.
- */
- timestamp: null,
- /**
- * @cfg {Number} version
- * The Version of UUID. Supported values are:
- *
- * * 1 : Time-based, "sequential" UUID.
- * * 4 : Pseudo-random UUID.
- *
- * The default is 4.
- */
- version: 4
- },
- applyId: function(id) {
- if (id === undefined) {
- return Ext.data.identifier.Uuid.Global;
- }
- return id;
- },
- constructor: function() {
- var me = this;
- me.callParent(arguments);
- me.parts = [];
- me.init();
- },
- /**
- * Reconfigures this generator given new config properties.
- */
- reconfigure: function(config) {
- this.setConfig(config);
- this.init();
- },
- generate: function () {
- var me = this,
- parts = me.parts,
- version = me.getVersion(),
- salt = me.getSalt(),
- time = me.getTimestamp();
- /*
- The magic decoder ring (derived from RFC 4122 Section 4.2.2):
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | time_low |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | time_mid | ver | time_hi |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- |res| clock_hi | clock_low | salt 0 |M| salt 1 |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | salt (2-5) |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- time_mid clock_hi (low 6 bits)
- time_low | time_hi |clock_lo
- | | | || salt[0]
- | | | || | salt[1..5]
- v v v vv v v
- 0badf00d-aced-1def-b123-dfad0badbeef
- ^ ^ ^
- version | multicast (low bit)
- |
- reserved (upper 2 bits)
- */
- parts[0] = me.toHex(time.lo, 8);
- parts[1] = me.toHex(time.hi & 0xFFFF, 4);
- parts[2] = me.toHex(((time.hi >>> 16) & 0xFFF) | (version << 12), 4);
- parts[3] = me.toHex(0x80 | ((me.clockSeq >>> 8) & 0x3F), 2) +
- me.toHex(me.clockSeq & 0xFF, 2);
- parts[4] = me.toHex(salt.hi, 4) + me.toHex(salt.lo, 8);
- if (version == 4) {
- me.init(); // just regenerate all the random values...
- } else {
- // sequentially increment the timestamp...
- ++time.lo;
- if (time.lo >= me.twoPow32) { // if (overflow)
- time.lo = 0;
- ++time.hi;
- }
- }
- return parts.join('-').toLowerCase();
- },
- /**
- * @private
- */
- init: function () {
- var me = this,
- salt = me.getSalt(),
- time = me.getTimestamp();
- if (me.getVersion() == 4) {
- // See RFC 4122 (Secion 4.4)
- // o If the state was unavailable (e.g., non-existent or corrupted),
- // or the saved node ID is different than the current node ID,
- // generate a random clock sequence value.
- me.clockSeq = me.rand(0, me.twoPow14-1);
- if (!salt) {
- salt = {};
- me.setSalt(salt);
- }
- if (!time) {
- time = {};
- me.setTimestamp(time);
- }
- // See RFC 4122 (Secion 4.4)
- salt.lo = me.rand(0, me.twoPow32-1);
- salt.hi = me.rand(0, me.twoPow16-1);
- time.lo = me.rand(0, me.twoPow32-1);
- time.hi = me.rand(0, me.twoPow28-1);
- } else {
- // this is run only once per-instance
- me.setSalt(me.split(me.getSalt()));
- me.setTimestamp(me.split(me.getTimestamp()));
- // Set multicast bit: "the least significant bit of the first octet of the
- // node ID" (nodeId = salt for this implementation):
- me.getSalt().hi |= 0x100;
- }
- },
- /**
- * Some private values used in methods on this class.
- * @private
- */
- twoPow14: Math.pow(2, 14),
- twoPow16: Math.pow(2, 16),
- twoPow28: Math.pow(2, 28),
- twoPow32: Math.pow(2, 32),
- /**
- * Converts a value into a hexadecimal value. Also allows for a maximum length
- * of the returned value.
- * @param value
- * @param length
- * @private
- */
- toHex: function(value, length) {
- var ret = value.toString(16);
- if (ret.length > length) {
- ret = ret.substring(ret.length - length); // right-most digits
- } else if (ret.length < length) {
- ret = Ext.String.leftPad(ret, length, '0');
- }
- return ret;
- },
- /**
- * Generates a random value with between a low and high.
- * @param lo
- * @param hi
- * @private
- */
- rand: function(lo, hi) {
- var v = Math.random() * (hi - lo + 1);
- return Math.floor(v) + lo;
- },
- /**
- * Splits a number into a low and high value.
- * @param bignum
- * @private
- */
- split: function(bignum) {
- if (typeof(bignum) == 'number') {
- var hi = Math.floor(bignum / this.twoPow32);
- return {
- lo: Math.floor(bignum - hi * this.twoPow32),
- hi: hi
- };
- }
- return bignum;
- }
- }, function() {
- this.Global = new this({
- id: 'uuid'
- });
- });
- /**
- * @author Ed Spencer
- * @aside guide proxies
- *
- * The JsonP proxy is useful when you need to load data from a domain other than the one your application is running on. If
- * your application is running on http://domainA.com it cannot use {@link Ext.data.proxy.Ajax Ajax} to load its data
- * from http://domainB.com because cross-domain ajax requests are prohibited by the browser.
- *
- * We can get around this using a JsonP proxy. JsonP proxy injects a `<script>` tag into the DOM whenever an AJAX request
- * would usually be made. Let's say we want to load data from http://domainB.com/users - the script tag that would be
- * injected might look like this:
- *
- * <script src="http://domainB.com/users?callback=someCallback"></script>
- *
- * When we inject the tag above, the browser makes a request to that url and includes the response as if it was any
- * other type of JavaScript include. By passing a callback in the url above, we're telling domainB's server that we want
- * to be notified when the result comes in and that it should call our callback function with the data it sends back. So
- * long as the server formats the response to look like this, everything will work:
- *
- * someCallback({
- * users: [
- * {
- * id: 1,
- * name: "Ed Spencer",
- * email: "ed@sencha.com"
- * }
- * ]
- * });
- *
- * As soon as the script finishes loading, the 'someCallback' function that we passed in the url is called with the JSON
- * object that the server returned.
- *
- * JsonP proxy takes care of all of this automatically. It formats the url you pass, adding the callback parameter
- * automatically. It even creates a temporary callback function, waits for it to be called and then puts the data into
- * the Proxy making it look just like you loaded it through a normal {@link Ext.data.proxy.Ajax AjaxProxy}. Here's how
- * we might set that up:
- *
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: ['id', 'name', 'email']
- * }
- * });
- *
- * var store = Ext.create('Ext.data.Store', {
- * model: 'User',
- * proxy: {
- * type: 'jsonp',
- * url : 'http://domainB.com/users'
- * }
- * });
- *
- * store.load();
- *
- * 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
- * like this:
- *
- * <script src="http://domainB.com/users?callback=callback1"></script>
- *
- * # Customization
- *
- * This script tag can be customized using the {@link #callbackKey} configuration. For example:
- *
- * var store = Ext.create('Ext.data.Store', {
- * model: 'User',
- * proxy: {
- * type: 'jsonp',
- * url : 'http://domainB.com/users',
- * callbackKey: 'theCallbackFunction'
- * }
- * });
- *
- * store.load();
- *
- * Would inject a script tag like this:
- *
- * <script src="http://domainB.com/users?theCallbackFunction=callback1"></script>
- *
- * # Implementing on the server side
- *
- * The remote server side needs to be configured to return data in this format. Here are suggestions for how you might
- * achieve this using Java, PHP and ASP.net:
- *
- * Java:
- *
- * boolean jsonP = false;
- * String cb = request.getParameter("callback");
- * if (cb != null) {
- * jsonP = true;
- * response.setContentType("text/javascript");
- * } else {
- * response.setContentType("application/x-json");
- * }
- * Writer out = response.getWriter();
- * if (jsonP) {
- * out.write(cb + "(");
- * }
- * out.print(dataBlock.toJsonString());
- * if (jsonP) {
- * out.write(");");
- * }
- *
- * PHP:
- *
- * $callback = $_REQUEST['callback'];
- *
- * // Create the output object.
- * $output = array('a' => 'Apple', 'b' => 'Banana');
- *
- * //start output
- * if ($callback) {
- * header('Content-Type: text/javascript');
- * echo $callback . '(' . json_encode($output) . ');';
- * } else {
- * header('Content-Type: application/x-json');
- * echo json_encode($output);
- * }
- *
- * ASP.net:
- *
- * String jsonString = "{success: true}";
- * String cb = Request.Params.Get("callback");
- * String responseString = "";
- * if (!String.IsNullOrEmpty(cb)) {
- * responseString = cb + "(" + jsonString + ")";
- * } else {
- * responseString = jsonString;
- * }
- * Response.Write(responseString);
- */
- Ext.define('Ext.data.proxy.JsonP', {
- extend: 'Ext.data.proxy.Server',
- alternateClassName: 'Ext.data.ScriptTagProxy',
- alias: ['proxy.jsonp', 'proxy.scripttag'],
- requires: ['Ext.data.JsonP'],
- config: {
- defaultWriterType: 'base',
- /**
- * @cfg {String} callbackKey
- * See {@link Ext.data.JsonP#callbackKey}.
- * @accessor
- */
- callbackKey : 'callback',
- /**
- * @cfg {String} recordParam
- * The param name to use when passing records to the server (e.g. 'records=someEncodedRecordString').
- * @accessor
- */
- recordParam: 'records',
- /**
- * @cfg {Boolean} autoAppendParams
- * `true` to automatically append the request's params to the generated url.
- * @accessor
- */
- autoAppendParams: true
- },
- /**
- * Performs the read request to the remote domain. JsonP proxy does not actually create an Ajax request,
- * instead we write out a `<script>` tag based on the configuration of the internal Ext.data.Request object
- * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute.
- * @param {Function} callback A callback function to execute when the Operation has been completed.
- * @param {Object} scope The scope to execute the callback in.
- * @return {Object}
- * @protected
- */
- doRequest: function(operation, callback, scope) {
- // <debug>
- var action = operation.getAction();
- if (action !== 'read') {
- Ext.Logger.error('JsonP proxies can only be used to read data.');
- }
- // </debug>
- //generate the unique IDs for this request
- var me = this,
- request = me.buildRequest(operation),
- params = request.getParams();
- // apply JsonP proxy-specific attributes to the Request
- request.setConfig({
- callbackKey: me.getCallbackKey(),
- timeout: me.getTimeout(),
- scope: me,
- callback: me.createRequestCallback(request, operation, callback, scope)
- });
- // Prevent doubling up because the params are already added to the url in buildUrl
- if (me.getAutoAppendParams()) {
- request.setParams({});
- }
- request.setJsonP(Ext.data.JsonP.request(request.getCurrentConfig()));
- // Set the params back once we have made the request though
- request.setParams(params);
- operation.setStarted();
- me.lastRequest = request;
- return request;
- },
- /**
- * @private
- * Creates and returns the function that is called when the request has completed. The returned function
- * should accept a Response object, which contains the response to be read by the configured Reader.
- * The third argument is the callback that should be called after the request has been completed and the Reader has decoded
- * the response. This callback will typically be the callback passed by a store, e.g. in proxy.read(operation, theCallback, scope)
- * theCallback refers to the callback argument received by this function.
- * See {@link #doRequest} for details.
- * @param {Ext.data.Request} request The Request object.
- * @param {Ext.data.Operation} operation The Operation being executed.
- * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
- * passed to doRequest.
- * @param {Object} scope The scope in which to execute the callback function.
- * @return {Function} The callback function.
- */
- createRequestCallback: function(request, operation, callback, scope) {
- var me = this;
- return function(success, response, errorType) {
- delete me.lastRequest;
- me.processResponse(success, operation, request, response, callback, scope);
- };
- },
- // @inheritdoc
- setException: function(operation, response) {
- operation.setException(operation.getRequest().getJsonP().errorType);
- },
- /**
- * Generates a url based on a given Ext.data.Request object. Adds the params and callback function name to the url
- * @param {Ext.data.Request} request The request object.
- * @return {String} The url.
- */
- buildUrl: function(request) {
- var me = this,
- url = me.callParent(arguments),
- params = Ext.apply({}, request.getParams()),
- filters = params.filters,
- records,
- filter, i, value;
- delete params.filters;
- if (me.getAutoAppendParams()) {
- url = Ext.urlAppend(url, Ext.Object.toQueryString(params));
- }
- if (filters && filters.length) {
- for (i = 0; i < filters.length; i++) {
- filter = filters[i];
- value = filter.getValue();
- if (value) {
- url = Ext.urlAppend(url, filter.getProperty() + "=" + value);
- }
- }
- }
- return url;
- },
- /**
- * @inheritdoc
- */
- destroy: function() {
- this.abort();
- this.callParent(arguments);
- },
- /**
- * Aborts the current server request if one is currently running.
- */
- abort: function() {
- var lastRequest = this.lastRequest;
- if (lastRequest) {
- Ext.data.JsonP.abort(lastRequest.getJsonP());
- }
- }
- });
- /**
- * @author Ed Spencer
- *
- * WebStorageProxy is simply a superclass for the {@link Ext.data.proxy.LocalStorage LocalStorage} proxy. It uses the
- * new HTML5 key/value client-side storage objects to save {@link Ext.data.Model model instances} for offline use.
- * @private
- */
- Ext.define('Ext.data.proxy.WebStorage', {
- extend: 'Ext.data.proxy.Client',
- alternateClassName: 'Ext.data.WebStorageProxy',
- requires: 'Ext.Date',
- config: {
- /**
- * @cfg {String} id
- * The unique ID used as the key in which all record data are stored in the local storage object.
- */
- id: undefined,
- // WebStorage proxies dont use readers and writers
- /**
- * @cfg
- * @hide
- */
- reader: null,
- /**
- * @cfg
- * @hide
- */
- writer: null,
- /**
- * @cfg {Boolean} enablePagingParams This can be set to true if you want the webstorage proxy to comply
- * to the paging params set on the store.
- */
- enablePagingParams: false
- },
- /**
- * Creates the proxy, throws an error if local storage is not supported in the current browser.
- * @param {Object} config (optional) Config object.
- */
- constructor: function(config) {
- this.callParent(arguments);
- /**
- * @property {Object} cache
- * Cached map of records already retrieved by this Proxy. Ensures that the same instance is always retrieved.
- */
- this.cache = {};
- //<debug>
- if (this.getStorageObject() === undefined) {
- Ext.Logger.error("Local Storage is not supported in this browser, please use another type of data proxy");
- }
- //</debug>
- },
- updateModel: function(model) {
- if (!this.getId()) {
- this.setId(model.modelName);
- }
- },
- //inherit docs
- create: function(operation, callback, scope) {
- var records = operation.getRecords(),
- length = records.length,
- ids = this.getIds(),
- id, record, i;
- operation.setStarted();
- for (i = 0; i < length; i++) {
- record = records[i];
- // <debug>
- if (!this.getModel().getIdentifier().isUnique) {
- 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.');
- }
- // </debug>
- id = record.getId();
- this.setRecord(record);
- ids.push(id);
- }
- this.setIds(ids);
- operation.setCompleted();
- operation.setSuccessful();
- if (typeof callback == 'function') {
- callback.call(scope || this, operation);
- }
- },
- //inherit docs
- read: function(operation, callback, scope) {
- var records = [],
- ids = this.getIds(),
- model = this.getModel(),
- idProperty = model.getIdProperty(),
- params = operation.getParams() || {},
- sorters = operation.getSorters(),
- filters = operation.getFilters(),
- start = operation.getStart(),
- limit = operation.getLimit(),
- length = ids.length,
- i, record, collection;
- //read a single record
- if (params[idProperty] !== undefined) {
- record = this.getRecord(params[idProperty]);
- if (record) {
- records.push(record);
- operation.setSuccessful();
- }
- } else {
- for (i = 0; i < length; i++) {
- records.push(this.getRecord(ids[i]));
- }
- collection = Ext.create('Ext.util.Collection');
- // First we comply to filters
- if (filters && filters.length) {
- collection.setFilters(filters);
- }
- // Then we comply to sorters
- if (sorters && sorters.length) {
- collection.setSorters(sorters);
- }
- collection.addAll(records);
- if (this.getEnablePagingParams() && start !== undefined && limit !== undefined) {
- records = collection.items.slice(start, start + limit);
- } else {
- records = collection.items.slice();
- }
- operation.setSuccessful();
- }
- operation.setCompleted();
- operation.setResultSet(Ext.create('Ext.data.ResultSet', {
- records: records,
- total : records.length,
- loaded : true
- }));
- operation.setRecords(records);
- if (typeof callback == 'function') {
- callback.call(scope || this, operation);
- }
- },
- //inherit docs
- update: function(operation, callback, scope) {
- var records = operation.getRecords(),
- length = records.length,
- ids = this.getIds(),
- record, id, i;
- operation.setStarted();
- for (i = 0; i < length; i++) {
- record = records[i];
- this.setRecord(record);
- //we need to update the set of ids here because it's possible that a non-phantom record was added
- //to this proxy - in which case the record's id would never have been added via the normal 'create' call
- id = record.getId();
- if (id !== undefined && Ext.Array.indexOf(ids, id) == -1) {
- ids.push(id);
- }
- }
- this.setIds(ids);
- operation.setCompleted();
- operation.setSuccessful();
- if (typeof callback == 'function') {
- callback.call(scope || this, operation);
- }
- },
- //inherit
- destroy: function(operation, callback, scope) {
- var records = operation.getRecords(),
- length = records.length,
- ids = this.getIds(),
- //newIds is a copy of ids, from which we remove the destroyed records
- newIds = [].concat(ids),
- i;
- operation.setStarted();
- for (i = 0; i < length; i++) {
- Ext.Array.remove(newIds, records[i].getId());
- this.removeRecord(records[i], false);
- }
- this.setIds(newIds);
- operation.setCompleted();
- operation.setSuccessful();
- if (typeof callback == 'function') {
- callback.call(scope || this, operation);
- }
- },
- /**
- * @private
- * Fetches a model instance from the Proxy by ID. Runs each field's decode function (if present) to decode the data.
- * @param {String} id The record's unique ID
- * @return {Ext.data.Model} The model instance or undefined if the record did not exist in the storage.
- */
- getRecord: function(id) {
- if (this.cache[id] === undefined) {
- var recordKey = this.getRecordKey(id),
- item = this.getStorageObject().getItem(recordKey),
- data = {},
- Model = this.getModel(),
- fields = Model.getFields().items,
- length = fields.length,
- i, field, name, record, rawData, dateFormat;
- if (!item) {
- return undefined;
- }
- rawData = Ext.decode(item);
- for (i = 0; i < length; i++) {
- field = fields[i];
- name = field.getName();
- if (typeof field.getDecode() == 'function') {
- data[name] = field.getDecode()(rawData[name]);
- } else {
- if (field.getType().type == 'date') {
- dateFormat = field.getDateFormat();
- if (dateFormat) {
- data[name] = Ext.Date.parse(rawData[name], dateFormat);
- } else {
- data[name] = new Date(rawData[name]);
- }
- } else {
- data[name] = rawData[name];
- }
- }
- }
- record = new Model(data, id);
- this.cache[id] = record;
- }
- return this.cache[id];
- },
- /**
- * Saves the given record in the Proxy. Runs each field's encode function (if present) to encode the data.
- * @param {Ext.data.Model} record The model instance
- * @param {String} [id] The id to save the record under (defaults to the value of the record's getId() function)
- */
- setRecord: function(record, id) {
- if (id) {
- record.setId(id);
- } else {
- id = record.getId();
- }
- var me = this,
- rawData = record.getData(),
- data = {},
- Model = me.getModel(),
- fields = Model.getFields().items,
- length = fields.length,
- i = 0,
- field, name, obj, key, dateFormat;
- for (; i < length; i++) {
- field = fields[i];
- name = field.getName();
- if (field.getPersist() === false) {
- continue;
- }
- if (typeof field.getEncode() == 'function') {
- data[name] = field.getEncode()(rawData[name], record);
- } else {
- if (field.getType().type == 'date' && Ext.isDate(rawData[name])) {
- dateFormat = field.getDateFormat();
- if (dateFormat) {
- data[name] = Ext.Date.format(rawData[name], dateFormat);
- } else {
- data[name] = rawData[name].getTime();
- }
- } else {
- data[name] = rawData[name];
- }
- }
- }
- obj = me.getStorageObject();
- key = me.getRecordKey(id);
- //keep the cache up to date
- me.cache[id] = record;
- //iPad bug requires that we remove the item before setting it
- obj.removeItem(key);
- try {
- obj.setItem(key, Ext.encode(data));
- } catch(e){
- this.fireEvent('exception', this, e);
- }
- record.commit();
- },
- /**
- * @private
- * Physically removes a given record from the local storage. Used internally by {@link #destroy}, which you should
- * use instead because it updates the list of currently-stored record ids
- * @param {String/Number/Ext.data.Model} id The id of the record to remove, or an Ext.data.Model instance
- */
- removeRecord: function(id, updateIds) {
- var me = this,
- ids;
- if (id.isModel) {
- id = id.getId();
- }
- if (updateIds !== false) {
- ids = me.getIds();
- Ext.Array.remove(ids, id);
- me.setIds(ids);
- }
- me.getStorageObject().removeItem(me.getRecordKey(id));
- },
- /**
- * @private
- * Given the id of a record, returns a unique string based on that id and the id of this proxy. This is used when
- * storing data in the local storage object and should prevent naming collisions.
- * @param {String/Number/Ext.data.Model} id The record id, or a Model instance
- * @return {String} The unique key for this record
- */
- getRecordKey: function(id) {
- if (id.isModel) {
- id = id.getId();
- }
- return Ext.String.format("{0}-{1}", this.getId(), id);
- },
- /**
- * @private
- * Returns the array of record IDs stored in this Proxy
- * @return {Number[]} The record IDs. Each is cast as a Number
- */
- getIds: function() {
- var ids = (this.getStorageObject().getItem(this.getId()) || "").split(","),
- length = ids.length,
- i;
- if (length == 1 && ids[0] === "") {
- ids = [];
- }
- return ids;
- },
- /**
- * @private
- * Saves the array of ids representing the set of all records in the Proxy
- * @param {Number[]} ids The ids to set
- */
- setIds: function(ids) {
- var obj = this.getStorageObject(),
- str = ids.join(","),
- id = this.getId();
- obj.removeItem(id);
- if (!Ext.isEmpty(str)) {
- try {
- obj.setItem(id, str);
- } catch(e){
- this.fireEvent('exception', this, e);
- }
- }
- },
- /**
- * @private
- * Sets up the Proxy by claiming the key in the storage object that corresponds to the unique id of this Proxy. Called
- * automatically by the constructor, this should not need to be called again unless {@link #clear} has been called.
- */
- initialize: function() {
- this.callParent(arguments);
- var storageObject = this.getStorageObject();
- try {
- storageObject.setItem(this.getId(), storageObject.getItem(this.getId()) || "");
- } catch(e){
- this.fireEvent('exception', this, e);
- }
- },
- /**
- * Destroys all records stored in the proxy and removes all keys and values used to support the proxy from the
- * storage object.
- */
- clear: function() {
- var obj = this.getStorageObject(),
- ids = this.getIds(),
- len = ids.length,
- i;
- //remove all the records
- for (i = 0; i < len; i++) {
- this.removeRecord(ids[i], false);
- }
- //remove the supporting objects
- obj.removeItem(this.getId());
- },
- /**
- * @private
- * Abstract function which should return the storage object that data will be saved to. This must be implemented
- * in each subclass.
- * @return {Object} The storage object
- */
- getStorageObject: function() {
- //<debug>
- Ext.Logger.error("The getStorageObject function has not been defined in your Ext.data.proxy.WebStorage subclass");
- //</debug>
- }
- });
- /**
- * @author Ed Spencer
- * @aside guide proxies
- *
- * The LocalStorageProxy uses the new HTML5 localStorage API to save {@link Ext.data.Model Model} data locally on the
- * client browser. HTML5 localStorage is a key-value store (e.g. cannot save complex objects like JSON), so
- * LocalStorageProxy automatically serializes and deserializes data when saving and retrieving it.
- *
- * localStorage is extremely useful for saving user-specific information without needing to build server-side
- * infrastructure to support it. Let's imagine we're writing a Twitter search application and want to save the user's
- * searches locally so they can easily perform a saved search again later. We'd start by creating a Search model:
- *
- * Ext.define('Search', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: ['id', 'query'],
- * proxy: {
- * type: 'localstorage',
- * id : 'twitter-Searches'
- * }
- * }
- * });
- *
- * Our Search model contains just two fields - id and query - plus a Proxy definition. The only configuration we need to
- * pass to the LocalStorage proxy is an {@link #id}. This is important as it separates the Model data in this Proxy from
- * all others. The localStorage API puts all data into a single shared namespace, so by setting an id we enable
- * LocalStorageProxy to manage the saved Search data.
- *
- * Saving our data into localStorage is easy and would usually be done with a {@link Ext.data.Store Store}:
- *
- * //our Store automatically picks up the LocalStorageProxy defined on the Search model
- * var store = Ext.create('Ext.data.Store', {
- * model: "Search"
- * });
- *
- * //loads any existing Search data from localStorage
- * store.load();
- *
- * //now add some Searches
- * store.add({query: 'Sencha Touch'});
- * store.add({query: 'Ext JS'});
- *
- * //finally, save our Search data to localStorage
- * store.sync();
- *
- * The LocalStorageProxy automatically gives our new Searches an id when we call store.sync(). It encodes the Model data
- * and places it into localStorage. We can also save directly to localStorage, bypassing the Store altogether:
- *
- * var search = Ext.create('Search', {query: 'Sencha Animator'});
- *
- * //uses the configured LocalStorageProxy to save the new Search to localStorage
- * search.save();
- *
- * # Limitations
- *
- * If this proxy is used in a browser where local storage is not supported, the constructor will throw an error. A local
- * storage proxy requires a unique ID which is used as a key in which all record data are stored in the local storage
- * object.
- *
- * It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided but the
- * attached store has a storeId, the storeId will be used. If neither option is presented the proxy will throw an error.
- */
- Ext.define('Ext.data.proxy.LocalStorage', {
- extend: 'Ext.data.proxy.WebStorage',
- alias: 'proxy.localstorage',
- alternateClassName: 'Ext.data.LocalStorageProxy',
- //inherit docs
- getStorageObject: function() {
- return window.localStorage;
- }
- });
- /**
- * @author Ed Spencer
- * @aside guide proxies
- *
- * The Rest proxy is a specialization of the {@link Ext.data.proxy.Ajax AjaxProxy} which simply maps the four actions
- * (create, read, update and destroy) to RESTful HTTP verbs. For example, let's set up a {@link Ext.data.Model Model}
- * with an inline Rest proxy:
- *
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: ['id', 'name', 'email'],
- *
- * proxy: {
- * type: 'rest',
- * url : '/users'
- * }
- * }
- * });
- *
- * 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
- * request to '/users':
- *
- * var user = Ext.create('User', {name: 'Ed Spencer', email: 'ed@sencha.com'});
- *
- * user.save(); //POST /users
- *
- * Let's expand this a little and provide a callback for the {@link Ext.data.Model#save} call to update the Model once
- * it has been created. We'll assume the creation went successfully and that the server gave this user an ID of 123:
- *
- * user.save({
- * success: function(user) {
- * user.set('name', 'Khan Noonien Singh');
- *
- * user.save(); //PUT /users/123
- * }
- * });
- *
- * Now that we're no longer creating a new Model instance, the request method is changed to an HTTP PUT, targeting the
- * relevant url for that user. Now let's delete this user, which will use the DELETE method:
- *
- * user.erase(); //DELETE /users/123
- *
- * Finally, when we perform a load of a Model or Store, Rest proxy will use the GET method:
- *
- * //1. Load via Store
- *
- * //the Store automatically picks up the Proxy from the User model
- * var store = Ext.create('Ext.data.Store', {
- * model: 'User'
- * });
- *
- * store.load(); //GET /users
- *
- * //2. Load directly from the Model
- *
- * //GET /users/123
- * Ext.ModelManager.getModel('User').load(123, {
- * success: function(user) {
- * console.log(user.getId()); //outputs 123
- * }
- * });
- *
- * # Url generation
- *
- * The Rest proxy is able to automatically generate the urls above based on two configuration options - {@link #appendId} and
- * {@link #format}. If appendId is true (it is by default) then Rest proxy will automatically append the ID of the Model
- * instance in question to the configured url, resulting in the '/users/123' that we saw above.
- *
- * If the request is not for a specific Model instance (e.g. loading a Store), the url is not appended with an id.
- * The Rest proxy will automatically insert a '/' before the ID if one is not already present.
- *
- * new Ext.data.proxy.Rest({
- * url: '/users',
- * appendId: true //default
- * });
- *
- * // Collection url: /users
- * // Instance url : /users/123
- *
- * The Rest proxy can also optionally append a format string to the end of any generated url:
- *
- * new Ext.data.proxy.Rest({
- * url: '/users',
- * format: 'json'
- * });
- *
- * // Collection url: /users.json
- * // Instance url : /users/123.json
- *
- * If further customization is needed, simply implement the {@link #buildUrl} method and add your custom generated url
- * onto the {@link Ext.data.Request Request} object that is passed to buildUrl. See [Rest proxy's implementation][1] for
- * an example of how to achieve this.
- *
- * Note that Rest proxy inherits from {@link Ext.data.proxy.Ajax AjaxProxy}, which already injects all of the sorter,
- * filter, group and paging options into the generated url. See the {@link Ext.data.proxy.Ajax AjaxProxy docs} for more
- * details.
- *
- * [1]: source/Rest.html#Ext-data-proxy-Rest-method-buildUrl
- */
- Ext.define('Ext.data.proxy.Rest', {
- extend: 'Ext.data.proxy.Ajax',
- alternateClassName: 'Ext.data.RestProxy',
- alias : 'proxy.rest',
- config: {
- /**
- * @cfg {Boolean} appendId
- * `true` to automatically append the ID of a Model instance when performing a request based on that single instance.
- * See Rest proxy intro docs for more details.
- */
- appendId: true,
- /**
- * @cfg {String} format
- * Optional data format to send to the server when making any request (e.g. 'json'). See the Rest proxy intro docs
- * for full details.
- */
- format: null,
- /**
- * @cfg {Boolean} batchActions
- * `true` to batch actions of a particular type when synchronizing the store.
- */
- batchActions: false,
- actionMethods: {
- create : 'POST',
- read : 'GET',
- update : 'PUT',
- destroy: 'DELETE'
- }
- },
- /**
- * Specialized version of `buildUrl` that incorporates the {@link #appendId} and {@link #format} options into the
- * generated url. Override this to provide further customizations, but remember to call the superclass `buildUrl` so
- * that additional parameters like the cache buster string are appended.
- * @param {Object} request
- * @return {Object}
- */
- buildUrl: function(request) {
- var me = this,
- operation = request.getOperation(),
- records = operation.getRecords() || [],
- record = records[0],
- model = me.getModel(),
- idProperty= model.getIdProperty(),
- format = me.getFormat(),
- url = me.getUrl(request),
- params = request.getParams() || {},
- id = (record && !record.phantom) ? record.getId() : params[idProperty];
- if (me.getAppendId() && id) {
- if (!url.match(/\/$/)) {
- url += '/';
- }
- url += id;
- delete params[idProperty];
- }
- if (format) {
- if (!url.match(/\.$/)) {
- url += '.';
- }
- url += format;
- }
- request.setUrl(url);
- return me.callParent([request]);
- }
- });
- /**
- * SQL proxy.
- */
- Ext.define('Ext.data.proxy.SQL', {
- alias: 'proxy.sql',
- extend: 'Ext.data.proxy.Client',
- config: {
- /**
- * @cfg {Object} reader
- * @hide
- */
- reader: null,
- /**
- * @cfg {Object} writer
- * @hide
- */
- writer: null,
- table: null,
- database: 'Sencha',
- columns: '',
- uniqueIdStrategy: false,
- tableExists: false,
- defaultDateFormat: 'Y-m-d H:i:s.u'
- },
- updateModel: function(model) {
- if (model && !this.getTable()) {
- var modelName = model.modelName,
- defaultDateFormat = this.getDefaultDateFormat(),
- table = modelName.slice(modelName.lastIndexOf('.') + 1);
- model.getFields().each(function (field) {
- if (field.getType().type === 'date' && !field.getDateFormat()) {
- field.setDateFormat(defaultDateFormat);
- }
- });
- this.setUniqueIdStrategy(model.getIdentifier().isUnique);
- this.setTable(table);
- this.setColumns(this.getPersistedModelColumns(model));
- }
- this.callParent(arguments);
- },
- create: function (operation, callback, scope) {
- var me = this,
- db = me.getDatabaseObject(),
- records = operation.getRecords(),
- tableExists = me.getTableExists();
- operation.setStarted();
- db.transaction(function(transaction) {
- if (!tableExists) {
- me.createTable(transaction);
- }
- me.insertRecords(records, transaction, function(resultSet, errors) {
- if (operation.process(operation.getAction(), resultSet) === false) {
- me.fireEvent('exception', this, operation);
- }
- if (typeof callback == 'function') {
- callback.call(scope || this, operation);
- }
- }, this);
- });
- },
- read: function(operation, callback, scope) {
- var me = this,
- db = me.getDatabaseObject(),
- model = me.getModel(),
- idProperty = model.getIdProperty(),
- tableExists = me.getTableExists(),
- params = operation.getParams() || {},
- id = params[idProperty],
- sorters = operation.getSorters(),
- filters = operation.getFilters(),
- page = operation.getPage(),
- start = operation.getStart(),
- limit = operation.getLimit(),
- filtered, i, ln;
- params = Ext.apply(params, {
- page: page,
- start: start,
- limit: limit,
- sorters: sorters,
- filters: filters
- });
- operation.setStarted();
- db.transaction(function(transaction) {
- if (!tableExists) {
- me.createTable(transaction);
- }
- me.selectRecords(transaction, id !== undefined ? id : params, function (resultSet, errors) {
- if (operation.process(operation.getAction(), resultSet) === false) {
- me.fireEvent('exception', me, operation);
- }
- if (filters.length) {
- filtered = Ext.create('Ext.util.Collection', function(record) {
- return record.getId();
- });
- filtered.setFilterRoot('data');
- for (i = 0, ln = filters.length; i < ln; i++) {
- if (filters[i].getProperty() === null) {
- filtered.addFilter(filters[i]);
- }
- }
- filtered.addAll(operation.getRecords());
- operation.setRecords(filtered.items.slice());
- resultSet.setRecords(operation.getRecords());
- resultSet.setCount(filtered.items.length);
- resultSet.setTotal(filtered.items.length);
- }
- if (typeof callback == 'function') {
- callback.call(scope || me, operation);
- }
- });
- });
- },
- update: function(operation, callback, scope) {
- var me = this,
- records = operation.getRecords(),
- db = me.getDatabaseObject(),
- tableExists = me.getTableExists();
- operation.setStarted();
- db.transaction(function (transaction) {
- if (!tableExists) {
- me.createTable(transaction);
- }
- me.updateRecords(transaction, records, function(resultSet, errors) {
- if (operation.process(operation.getAction(), resultSet) === false) {
- me.fireEvent('exception', me, operation);
- }
- if (typeof callback == 'function') {
- callback.call(scope || me, operation);
- }
- });
- });
- },
- destroy: function(operation, callback, scope) {
- var me = this,
- records = operation.getRecords(),
- db = me.getDatabaseObject(),
- tableExists = me.getTableExists();
- operation.setStarted();
- db.transaction(function(transaction) {
- if (!tableExists) {
- me.createTable(transaction);
- }
- me.destroyRecords(transaction, records, function(resultSet, errors) {
- if (operation.process(operation.getAction(), resultSet) === false) {
- me.fireEvent('exception', me, operation);
- }
- if (typeof callback == 'function') {
- callback.call(scope || me, operation);
- }
- });
- });
- },
- createTable: function (transaction) {
- transaction.executeSql('CREATE TABLE IF NOT EXISTS ' + this.getTable() + ' (' + this.getSchemaString() + ')');
- this.setTableExists(true);
- },
- insertRecords: function(records, transaction, callback, scope) {
- var me = this,
- table = me.getTable(),
- columns = me.getColumns(),
- totalRecords = records.length,
- executed = 0,
- tmp = [],
- insertedRecords = [],
- errors = [],
- uniqueIdStrategy = me.getUniqueIdStrategy(),
- i, ln, placeholders, result;
- result = new Ext.data.ResultSet({
- records: insertedRecords,
- success: true
- });
- for (i = 0, ln = columns.length; i < ln; i++) {
- tmp.push('?');
- }
- placeholders = tmp.join(', ');
- Ext.each(records, function (record) {
- var id = record.getId(),
- data = me.getRecordData(record),
- values = me.getColumnValues(columns, data);
- transaction.executeSql(
- 'INSERT INTO ' + table + ' (' + columns.join(', ') + ') VALUES (' + placeholders + ')', values,
- function (transaction, resultSet) {
- executed++;
- insertedRecords.push({
- clientId: id,
- id: uniqueIdStrategy ? id : resultSet.insertId,
- data: data,
- node: data
- });
- if (executed === totalRecords && typeof callback == 'function') {
- callback.call(scope || me, result, errors);
- }
- },
- function (transaction, error) {
- executed++;
- errors.push({
- clientId: id,
- error: error
- });
- if (executed === totalRecords && typeof callback == 'function') {
- callback.call(scope || me, result, errors);
- }
- }
- );
- });
- },
- selectRecords: function(transaction, params, callback, scope) {
- var me = this,
- table = me.getTable(),
- idProperty = me.getModel().getIdProperty(),
- sql = 'SELECT * FROM ' + table,
- records = [],
- filterStatement = ' WHERE ',
- sortStatement = ' ORDER BY ',
- i, ln, data, result, count, rows, filter, sorter, property, value;
- result = new Ext.data.ResultSet({
- records: records,
- success: true
- });
- if (!Ext.isObject(params)) {
- sql += filterStatement + idProperty + ' = ' + params;
- } else {
- ln = params.filters && params.filters.length;
- if (ln) {
- for (i = 0; i < ln; i++) {
- filter = params.filters[i];
- property = filter.getProperty();
- value = filter.getValue();
- if (property !== null) {
- sql += filterStatement + property + ' ' + (filter.getAnyMatch() ? ('LIKE \'%' + value + '%\'') : ('= \'' + value + '\''));
- filterStatement = ' AND ';
- }
- }
- }
- ln = params.sorters.length;
- if (ln) {
- for (i = 0; i < ln; i++) {
- sorter = params.sorters[i];
- property = sorter.getProperty();
- if (property !== null) {
- sql += sortStatement + property + ' ' + sorter.getDirection();
- sortStatement = ', ';
- }
- }
- }
- // handle start, limit, sort, filter and group params
- if (params.page !== undefined) {
- sql += ' LIMIT ' + parseInt(params.start, 10) + ', ' + parseInt(params.limit, 10);
- }
- }
- transaction.executeSql(sql, null,
- function(transaction, resultSet) {
- rows = resultSet.rows;
- count = rows.length;
- for (i = 0, ln = count; i < ln; i++) {
- data = rows.item(i);
- records.push({
- clientId: null,
- id: data[idProperty],
- data: data,
- node: data
- });
- }
- result.setSuccess(true);
- result.setTotal(count);
- result.setCount(count);
- if (typeof callback == 'function') {
- callback.call(scope || me, result)
- }
- },
- function(transaction, errors) {
- result.setSuccess(false);
- result.setTotal(0);
- result.setCount(0);
- if (typeof callback == 'function') {
- callback.call(scope || me, result)
- }
- }
- );
- },
- updateRecords: function (transaction, records, callback, scope) {
- var me = this,
- table = me.getTable(),
- columns = me.getColumns(),
- totalRecords = records.length,
- idProperty = me.getModel().getIdProperty(),
- executed = 0,
- updatedRecords = [],
- errors = [],
- i, ln, result;
- result = new Ext.data.ResultSet({
- records: updatedRecords,
- success: true
- });
- Ext.each(records, function (record) {
- var id = record.getId(),
- data = me.getRecordData(record),
- values = me.getColumnValues(columns, data),
- updates = [];
- for (i = 0, ln = columns.length; i < ln; i++) {
- updates.push(columns[i] + ' = ?');
- }
- transaction.executeSql(
- 'UPDATE ' + table + ' SET ' + updates.join(', ') + ' WHERE ' + idProperty + ' = ?', values.concat(id),
- function (transaction, resultSet) {
- executed++;
- updatedRecords.push({
- clientId: id,
- id: id,
- data: data,
- node: data
- });
- if (executed === totalRecords && typeof callback == 'function') {
- callback.call(scope || me, result, errors);
- }
- },
- function (transaction, error) {
- executed++;
- errors.push({
- clientId: id,
- error: error
- });
- if (executed === totalRecords && typeof callback == 'function') {
- callback.call(scope || me, result, errors);
- }
- }
- );
- });
- },
- destroyRecords: function (transaction, records, callback, scope) {
- var me = this,
- table = me.getTable(),
- idProperty = me.getModel().getIdProperty(),
- ids = [],
- values = [],
- destroyedRecords = [],
- i, ln, result, record;
- for (i = 0, ln = records.length; i < ln; i++) {
- ids.push(idProperty + ' = ?');
- values.push(records[i].getId());
- }
- result = new Ext.data.ResultSet({
- records: destroyedRecords,
- success: true
- });
- transaction.executeSql(
- 'DELETE FROM ' + table + ' WHERE ' + ids.join(' OR '), values,
- function (transaction, resultSet) {
- for (i = 0, ln = records.length; i < ln; i++) {
- record = records[i];
- destroyedRecords.push({
- id: record.getId()
- });
- }
- if (typeof callback == 'function') {
- callback.call(scope || me, result);
- }
- },
- function (transaction, error) {
- if (typeof callback == 'function') {
- callback.call(scope || me, result);
- }
- }
- );
- },
- /**
- * Formats the data for each record before sending it to the server. This
- * method should be overridden to format the data in a way that differs from the default.
- * @param {Object} record The record that we are writing to the server.
- * @return {Object} An object literal of name/value keys to be written to the server.
- * By default this method returns the data property on the record.
- */
- getRecordData: function (record) {
- var me = this,
- fields = record.getFields(),
- idProperty = record.getIdProperty(),
- uniqueIdStrategy = me.getUniqueIdStrategy(),
- data = {},
- name, value;
- fields.each(function (field) {
- if (field.getPersist()) {
- name = field.getName();
- if (name === idProperty && !uniqueIdStrategy) {
- return;
- }
- value = record.get(name);
- if (field.getType().type == 'date') {
- value = me.writeDate(field, value);
- }
- data[name] = value;
- }
- }, this);
- return data;
- },
- getColumnValues: function(columns, data) {
- var ln = columns.length,
- values = [],
- i, column, value;
- for (i = 0; i < ln; i++) {
- column = columns[i];
- value = data[column];
- if (value !== undefined) {
- values.push(value);
- }
- }
- return values;
- },
- getSchemaString: function() {
- var me = this,
- schema = [],
- model = me.getModel(),
- idProperty = model.getIdProperty(),
- fields = model.getFields().items,
- uniqueIdStrategy = me.getUniqueIdStrategy(),
- ln = fields.length,
- i, field, type, name;
- for (i = 0; i < ln; i++) {
- field = fields[i];
- type = field.getType().type;
- name = field.getName();
- if (name === idProperty) {
- if (uniqueIdStrategy) {
- type = me.convertToSqlType(type);
- schema.unshift(idProperty + ' ' + type);
- } else {
- schema.unshift(idProperty + ' INTEGER PRIMARY KEY AUTOINCREMENT');
- }
- } else {
- type = me.convertToSqlType(type);
- schema.push(name + ' ' + type);
- }
- }
- return schema.join(', ');
- },
- getPersistedModelColumns: function(model) {
- var fields = model.getFields().items,
- uniqueIdStrategy = this.getUniqueIdStrategy(),
- idProperty = model.getIdProperty(),
- columns = [],
- ln = fields.length,
- i, field, name;
- for (i = 0; i < ln; i++) {
- field = fields[i];
- name = field.getName();
- if (name === idProperty && !uniqueIdStrategy) {
- continue;
- }
- if (field.getPersist()) {
- columns.push(field.getName());
- }
- }
- return columns;
- },
- convertToSqlType: function(type) {
- switch (type.toLowerCase()) {
- case 'date':
- case 'string':
- case 'auto':
- return 'TEXT';
- case 'int':
- return 'INTEGER';
- case 'float':
- return 'REAL';
- case 'bool':
- return 'NUMERIC'
- }
- },
- writeDate: function (field, date) {
- var dateFormat = field.getDateFormat() || this.getDefaultDateFormat();
- switch (dateFormat) {
- case 'timestamp':
- return date.getTime() / 1000;
- case 'time':
- return date.getTime();
- default:
- return Ext.Date.format(date, dateFormat);
- }
- },
- dropTable: function() {
- var me = this,
- table = me.getTable(),
- db = me.getDatabaseObject();
- db.transaction(function(transaction) {
- transaction.executeSql('DROP TABLE ' + table);
- });
- me.setTableExists(false);
- },
- getDatabaseObject: function() {
- return openDatabase(this.getDatabase(), '1.0', 'Sencha Database', 5 * 1024 * 1024);
- }
- });
- /**
- * @author Ed Spencer
- * @aside guide proxies
- *
- * Proxy which uses HTML5 session storage as its data storage/retrieval mechanism. If this proxy is used in a browser
- * where session storage is not supported, the constructor will throw an error. A session storage proxy requires a
- * unique ID which is used as a key in which all record data are stored in the session storage object.
- *
- * It's important to supply this unique ID as it cannot be reliably determined otherwise. If no id is provided but the
- * attached store has a storeId, the storeId will be used. If neither option is presented the proxy will throw an error.
- *
- * Proxies are almost always used with a {@link Ext.data.Store store}:
- *
- * new Ext.data.Store({
- * proxy: {
- * type: 'sessionstorage',
- * id : 'myProxyKey'
- * }
- * });
- *
- * Alternatively you can instantiate the Proxy directly:
- *
- * new Ext.data.proxy.SessionStorage({
- * id : 'myOtherProxyKey'
- * });
- *
- * Note that session storage is different to local storage (see {@link Ext.data.proxy.LocalStorage}) - if a browser
- * session is ended (e.g. by closing the browser) then all data in a SessionStorageProxy are lost. Browser restarts
- * don't affect the {@link Ext.data.proxy.LocalStorage} - the data are preserved.
- */
- Ext.define('Ext.data.proxy.SessionStorage', {
- extend: 'Ext.data.proxy.WebStorage',
- alias: 'proxy.sessionstorage',
- alternateClassName: 'Ext.data.SessionStorageProxy',
- //inherit docs
- getStorageObject: function() {
- return window.sessionStorage;
- }
- });
- /**
- * @author Ed Spencer
- * @class Ext.data.reader.Xml
- * @extends Ext.data.reader.Reader
- *
- * The XML Reader is used by a Proxy to read a server response that is sent back in XML format. This usually
- * happens as a result of loading a Store - for example we might create something like this:
- *
- * Ext.define('User', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: ['id', 'name', 'email']
- * }
- * });
- *
- * var store = Ext.create('Ext.data.Store', {
- * model: 'User',
- * proxy: {
- * type: 'ajax',
- * url : 'users.xml',
- * reader: {
- * type: 'xml',
- * record: 'user'
- * }
- * }
- * });
- *
- * The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
- * not already familiar with them.
- *
- * We created the simplest type of XML Reader possible by simply telling our {@link Ext.data.Store Store}'s
- * {@link Ext.data.proxy.Proxy Proxy} that we want a XML Reader. The Store automatically passes the configured model to the
- * Store, so it is as if we passed this instead:
- *
- * reader: {
- * type : 'xml',
- * model: 'User',
- * record: 'user'
- * }
- *
- * The reader we set up is ready to read data from our server - at the moment it will accept a response like this:
- *
- * <?xml version="1.0" encoding="UTF-8"?>
- * <user>
- * <id>1</id>
- * <name>Ed Spencer</name>
- * <email>ed@sencha.com</email>
- * </user>
- * <user>
- * <id>2</id>
- * <name>Abe Elias</name>
- * <email>abe@sencha.com</email>
- * </user>
- *
- * The XML Reader uses the configured {@link #record} option to pull out the data for each record - in this case we
- * set record to 'user', so each `<user>` above will be converted into a User model.
- *
- * ## Reading other XML formats
- *
- * If you already have your XML format defined and it doesn't look quite like what we have above, you can usually
- * pass XmlReader a couple of configuration options to make it parse your format. For example, we can use the
- * {@link #rootProperty} configuration to parse data that comes back like this:
- *
- * <?xml version="1.0" encoding="UTF-8"?>
- * <users>
- * <user>
- * <id>1</id>
- * <name>Ed Spencer</name>
- * <email>ed@sencha.com</email>
- * </user>
- * <user>
- * <id>2</id>
- * <name>Abe Elias</name>
- * <email>abe@sencha.com</email>
- * </user>
- * </users>
- *
- * To parse this we just pass in a {@link #rootProperty} configuration that matches the 'users' above:
- *
- * reader: {
- * type: 'xml',
- * record: 'user',
- * rootProperty: 'users'
- * }
- *
- * Note that XmlReader doesn't care whether your {@link #rootProperty} and {@link #record} elements are nested deep
- * inside a larger structure, so a response like this will still work:
- *
- * <?xml version="1.0" encoding="UTF-8"?>
- * <deeply>
- * <nested>
- * <xml>
- * <users>
- * <user>
- * <id>1</id>
- * <name>Ed Spencer</name>
- * <email>ed@sencha.com</email>
- * </user>
- * <user>
- * <id>2</id>
- * <name>Abe Elias</name>
- * <email>abe@sencha.com</email>
- * </user>
- * </users>
- * </xml>
- * </nested>
- * </deeply>
- *
- * ## Response metadata
- *
- * The server can return additional data in its response, such as the {@link #totalProperty total number of records}
- * and the {@link #successProperty success status of the response}. These are typically included in the XML response
- * like this:
- *
- * <?xml version="1.0" encoding="UTF-8"?>
- * <total>100</total>
- * <success>true</success>
- * <users>
- * <user>
- * <id>1</id>
- * <name>Ed Spencer</name>
- * <email>ed@sencha.com</email>
- * </user>
- * <user>
- * <id>2</id>
- * <name>Abe Elias</name>
- * <email>abe@sencha.com</email>
- * </user>
- * </users>
- *
- * If these properties are present in the XML response they can be parsed out by the XmlReader and used by the
- * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration
- * options:
- *
- * reader: {
- * type: 'xml',
- * rootProperty: 'users',
- * totalProperty : 'total',
- * successProperty: 'success'
- * }
- *
- * These final options are not necessary to make the Reader work, but can be useful when the server needs to report
- * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
- * returned.
- *
- * ## Response format
- *
- * __Note:__ In order for the browser to parse a returned XML document, the Content-Type header in the HTTP
- * response must be set to "text/xml" or "application/xml". This is very important - the XmlReader will not
- * work correctly otherwise.
- */
- Ext.define('Ext.data.reader.Xml', {
- extend: 'Ext.data.reader.Reader',
- alternateClassName: 'Ext.data.XmlReader',
- alias : 'reader.xml',
- config: {
- /**
- * @cfg {String} record The DomQuery path to the repeated element which contains record information.
- */
- record: null
- },
- /**
- * @private
- * Creates a function to return some particular key of data from a response. The {@link #totalProperty} and
- * {@link #successProperty} are treated as special cases for type casting, everything else is just a simple selector.
- * @param {String} expr
- * @return {Function}
- */
- createAccessor: function(expr) {
- var me = this;
- if (Ext.isEmpty(expr)) {
- return Ext.emptyFn;
- }
- if (Ext.isFunction(expr)) {
- return expr;
- }
- return function(root) {
- return me.getNodeValue(Ext.DomQuery.selectNode(expr, root));
- };
- },
- getNodeValue: function(node) {
- if (node && node.firstChild) {
- return node.firstChild.nodeValue;
- }
- return undefined;
- },
- //inherit docs
- getResponseData: function(response) {
- // Check to see if the response is already an xml node.
- if (response.nodeType === 1 || response.nodeType === 9) {
- return response;
- }
- var xml = response.responseXML;
- //<debug>
- if (!xml) {
- /**
- * @event exception Fires whenever the reader is unable to parse a response.
- * @param {Ext.data.reader.Xml} reader A reference to this reader.
- * @param {XMLHttpRequest} response The XMLHttpRequest response object.
- * @param {String} error The error message.
- */
- this.fireEvent('exception', this, response, 'XML data not found in the response');
- Ext.Logger.warn('XML data not found in the response');
- }
- //</debug>
- return xml;
- },
- /**
- * Normalizes the data object.
- * @param {Object} data The raw data object.
- * @return {Object} Returns the `documentElement` property of the data object if present, or the same object if not.
- */
- getData: function(data) {
- return data.documentElement || data;
- },
- /**
- * @private
- * Given an XML object, returns the Element that represents the root as configured by the Reader's meta data.
- * @param {Object} data The XML data object.
- * @return {XMLElement} The root node element.
- */
- getRoot: function(data) {
- var nodeName = data.nodeName,
- root = this.getRootProperty();
- if (!root || (nodeName && nodeName == root)) {
- return data;
- } else if (Ext.DomQuery.isXml(data)) {
- // This fix ensures we have XML data
- // Related to TreeStore calling getRoot with the root node, which isn't XML
- // Probably should be resolved in TreeStore at some point
- return Ext.DomQuery.selectNode(root, data);
- }
- },
- /**
- * @private
- * We're just preparing the data for the superclass by pulling out the record nodes we want.
- * @param {XMLElement} root The XML root node.
- * @return {Ext.data.Model[]} The records.
- */
- extractData: function(root) {
- var recordName = this.getRecord();
- //<debug>
- if (!recordName) {
- Ext.Logger.error('Record is a required parameter');
- }
- //</debug>
- if (recordName != root.nodeName && recordName !== root.localName) {
- root = Ext.DomQuery.select(recordName, root);
- } else {
- root = [root];
- }
- return this.callParent([root]);
- },
- /**
- * @private
- * See {@link Ext.data.reader.Reader#getAssociatedDataRoot} docs.
- * @param {Object} data The raw data object.
- * @param {String} associationName The name of the association to get data for (uses {@link Ext.data.association.Association#associationKey} if present).
- * @return {XMLElement} The root.
- */
- getAssociatedDataRoot: function(data, associationName) {
- return Ext.DomQuery.select(associationName, data)[0];
- },
- /**
- * Parses an XML document and returns a ResultSet containing the model instances.
- * @param {Object} doc Parsed XML document.
- * @return {Ext.data.ResultSet} The parsed result set.
- */
- readRecords: function(doc) {
- //it's possible that we get passed an array here by associations. Make sure we strip that out (see Ext.data.reader.Reader#readAssociated)
- if (Ext.isArray(doc)) {
- doc = doc[0];
- }
- return this.callParent([doc]);
- },
- /**
- * @private
- * Returns an accessor expression for the passed Field from an XML element using either the Field's mapping, or
- * its ordinal position in the fields collection as the index.
- *
- * This is used by `buildExtractors` to create optimized on extractor function which converts raw data into model instances.
- */
- createFieldAccessExpression: function(field, fieldVarName, dataName) {
- var selector = field.getMapping() || field.getName(),
- result;
- if (typeof selector === 'function') {
- result = fieldVarName + '.getMapping()(' + dataName + ', this)';
- } else {
- selector = selector.split('@');
- if (selector.length === 2 && selector[0]) {
- result = 'me.getNodeValue(Ext.DomQuery.selectNode("@' + selector[1] + '", Ext.DomQuery.selectNode("' + selector[0] + '", ' + dataName + ')))';
- } else if (selector.length === 2) {
- result = 'me.getNodeValue(Ext.DomQuery.selectNode("@' + selector[1] + '", ' + dataName + '))';
- } else if (selector.length === 1) {
- result = 'me.getNodeValue(Ext.DomQuery.selectNode("' + selector[0] + '", ' + dataName + '))';
- } else {
- throw "Unsupported query - too many queries for attributes in " + selector.join('@');
- }
- }
- return result;
- }
- });
- /**
- * @author Ed Spencer
- * @class Ext.data.writer.Xml
- *
- * This class is used to write {@link Ext.data.Model} data to the server in an XML format.
- * The {@link #documentRoot} property is used to specify the root element in the XML document.
- * The {@link #record} option is used to specify the element name for each record that will make
- * up the XML document.
- */
- Ext.define('Ext.data.writer.Xml', {
-
- /* Begin Definitions */
-
- extend: 'Ext.data.writer.Writer',
- alternateClassName: 'Ext.data.XmlWriter',
-
- alias: 'writer.xml',
-
- /* End Definitions */
- config: {
- /**
- * @cfg {String} documentRoot The name of the root element of the document.
- * If there is more than 1 record and the root is not specified, the default document root will still be used
- * to ensure a valid XML document is created.
- */
- documentRoot: 'xmlData',
- /**
- * @cfg {String} defaultDocumentRoot The root to be used if {@link #documentRoot} is empty and a root is required
- * to form a valid XML document.
- */
- defaultDocumentRoot: 'xmlData',
- /**
- * @cfg {String} header A header to use in the XML document (such as setting the encoding or version).
- */
- header: '',
- /**
- * @cfg {String} record The name of the node to use for each record.
- */
- record: 'record'
- },
- /**
- * @param request
- * @param data
- * @return {Object}
- */
- writeRecords: function(request, data) {
- var me = this,
- xml = [],
- i = 0,
- len = data.length,
- root = me.getDocumentRoot(),
- record = me.getRecord(),
- needsRoot = data.length !== 1,
- item,
- key;
-
- // may not exist
- xml.push(me.getHeader() || '');
-
- if (!root && needsRoot) {
- root = me.getDefaultDocumentRoot();
- }
-
- if (root) {
- xml.push('<', root, '>');
- }
-
- for (; i < len; ++i) {
- item = data[i];
- xml.push('<', record, '>');
- for (key in item) {
- if (item.hasOwnProperty(key)) {
- xml.push('<', key, '>', item[key], '</', key, '>');
- }
- }
- xml.push('</', record, '>');
- }
-
- if (root) {
- xml.push('</', root, '>');
- }
-
- request.setXmlData(xml.join(''));
- return request;
- }
- });
- /**
- * @aside video list
- * @aside guide list
- *
- * IndexBar is a component used to display a list of data (primarily an alphabet) which can then be used to quickly
- * navigate through a list (see {@link Ext.List}) of data. When a user taps on an item in the {@link Ext.IndexBar},
- * it will fire the {@link #index} event.
- *
- * Here is an example of the usage in a {@link Ext.List}:
- *
- * @example phone portrait preview
- * Ext.define('Contact', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: ['firstName', 'lastName']
- * }
- * });
- *
- * var store = new Ext.data.JsonStore({
- * model: 'Contact',
- * sorters: 'lastName',
- *
- * grouper: {
- * groupFn: function(record) {
- * return record.get('lastName')[0];
- * }
- * },
- *
- * data: [
- * {firstName: 'Tommy', lastName: 'Maintz'},
- * {firstName: 'Rob', lastName: 'Dougan'},
- * {firstName: 'Ed', lastName: 'Spencer'},
- * {firstName: 'Jamie', lastName: 'Avins'},
- * {firstName: 'Aaron', lastName: 'Conran'},
- * {firstName: 'Dave', lastName: 'Kaneda'},
- * {firstName: 'Jacky', lastName: 'Nguyen'},
- * {firstName: 'Abraham', lastName: 'Elias'},
- * {firstName: 'Jay', lastName: 'Robinson'},
- * {firstName: 'Nigel', lastName: 'White'},
- * {firstName: 'Don', lastName: 'Griffin'},
- * {firstName: 'Nico', lastName: 'Ferrero'},
- * {firstName: 'Jason', lastName: 'Johnston'}
- * ]
- * });
- *
- * var list = new Ext.List({
- * fullscreen: true,
- * itemTpl: '<div class="contact">{firstName} <strong>{lastName}</strong></div>',
- *
- * grouped : true,
- * indexBar : true,
- * store: store,
- * hideOnMaskTap: false
- * });
- *
- */
- Ext.define('Ext.dataview.IndexBar', {
- extend: 'Ext.Component',
- alternateClassName: 'Ext.IndexBar',
- /**
- * @event index
- * Fires when an item in the index bar display has been tapped.
- * @param {Ext.dataview.IndexBar} this The IndexBar instance
- * @param {String} html The HTML inside the tapped node.
- * @param {Ext.dom.Element} target The node on the indexbar that has been tapped.
- */
- config: {
- baseCls: Ext.baseCSSPrefix + 'indexbar',
- /**
- * @cfg {String} direction
- * Layout direction, can be either 'vertical' or 'horizontal'
- * @accessor
- */
- direction: 'vertical',
- /**
- * @cfg {Array} letters
- * The letters to show on the index bar.
- * @accessor
- */
- 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'],
- ui: 'alphabet',
- /**
- * @cfg {String} listPrefix
- * The prefix string to be used at the beginning of the list.
- * E.g: useful to add a "#" prefix before numbers.
- * @accessor
- */
- listPrefix: null
- },
- // @private
- itemCls: Ext.baseCSSPrefix + '',
- updateDirection: function(newDirection, oldDirection) {
- var baseCls = this.getBaseCls();
- this.element.replaceCls(baseCls + '-' + oldDirection, baseCls + '-' + newDirection);
- },
- getElementConfig: function() {
- return {
- reference: 'wrapper',
- classList: ['x-centered', 'x-indexbar-wrapper'],
- children: [this.callParent()]
- };
- },
- updateLetters: function(letters) {
- this.innerElement.setHtml('');
- if (letters) {
- var ln = letters.length,
- i;
- for (i = 0; i < ln; i++) {
- this.innerElement.createChild({
- html: letters[i]
- });
- }
- }
- },
- updateListPrefix: function(listPrefix) {
- if (listPrefix && listPrefix.length) {
- this.innerElement.createChild({
- html: listPrefix
- }, 0);
- }
- },
- // @private
- initialize: function() {
- this.callParent();
- this.innerElement.on({
- touchstart: this.onTouchStart,
- touchend: this.onTouchEnd,
- touchmove: this.onTouchMove,
- scope: this
- });
- },
- // @private
- onTouchStart: function(e, t) {
- e.stopPropagation();
- this.innerElement.addCls(this.getBaseCls() + '-pressed');
- this.pageBox = this.innerElement.getPageBox();
- this.onTouchMove(e);
- },
- // @private
- onTouchEnd: function(e, t) {
- this.innerElement.removeCls(this.getBaseCls() + '-pressed');
- },
- // @private
- onTouchMove: function(e) {
- var point = Ext.util.Point.fromEvent(e),
- target,
- pageBox = this.pageBox;
- if (!pageBox) {
- pageBox = this.pageBox = this.el.getPageBox();
- }
- if (this.getDirection() === 'vertical') {
- if (point.y > pageBox.bottom || point.y < pageBox.top) {
- return;
- }
- target = Ext.Element.fromPoint(pageBox.left + (pageBox.width / 2), point.y);
- }
- else {
- if (point.x > pageBox.right || point.x < pageBox.left) {
- return;
- }
- target = Ext.Element.fromPoint(point.x, pageBox.top + (pageBox.height / 2));
- }
- if (target) {
- this.fireEvent('index', this, target.dom.innerHTML, target);
- }
- },
- destroy: function() {
- var me = this,
- elements = Array.prototype.slice.call(me.innerElement.dom.childNodes),
- ln = elements.length,
- i = 0;
- for (; i < ln; i++) {
- Ext.removeNode(elements[i]);
- }
- this.callParent();
- }
- }, function() {
- });
- /**
- * @private - To be made a sample
- */
- Ext.define('Ext.dataview.ListItemHeader', {
- extend: 'Ext.Component',
- xtype : 'listitemheader',
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'list-header',
- docked: 'top'
- }
- });
- /**
- * A DataItem is a container for {@link Ext.dataview.DataView} with useComponents: true. It ties together
- * {@link Ext.data.Model records} to its contained Components via a {@link #dataMap dataMap} configuration.
- *
- * For example, lets say you have a `text` configuration which, when applied, gets turned into an instance of an
- * Ext.Component. We want to update the {@link #html} of a sub-component when the 'text' field of the record gets
- * changed.
- *
- * As you can see below, it is simply a matter of setting the key of the object to be the getter of the config
- * (`getText`), and then give that property a value of an object, which then has 'setHtml' (the `html` setter) as the key,
- * and 'text' (the field name) as the value. You can continue this for a as many sub-components as you wish.
- *
- * dataMap: {
- * // When the record is updated, get the text configuration, and
- * // call {@link #setHtml} with the 'text' field of the record.
- * getText: {
- * setHtml: 'text'
- * },
- *
- * // When the record is updated, get the userName configuration, and
- * // call {@link #setHtml} with the 'from_user' field of the record.
- * getUserName: {
- * setHtml: 'from_user'
- * },
- *
- * // When the record is updated, get the avatar configuration, and
- * // call `setSrc` with the 'profile_image_url' field of the record.
- * getAvatar: {
- * setSrc: 'profile_image_url'
- * }
- * }
- */
- Ext.define('Ext.dataview.component.ListItem', {
- extend: 'Ext.dataview.component.DataItem',
- xtype : 'listitem',
- config: {
- baseCls: Ext.baseCSSPrefix + 'list-item',
- dataMap: null,
- body: {
- xtype: 'component',
- cls: 'x-list-item-body'
- },
- disclosure: {
- xtype: 'component',
- cls: 'x-list-disclosure',
- hidden: true,
- docked: 'right'
- },
- header: {
- xtype: 'component',
- cls: 'x-list-header',
- html: ' ',
- hidden: true
- },
- tpl: null,
- items: null
- },
- applyBody: function(body) {
- if (body && !body.isComponent) {
- body = Ext.factory(body, Ext.Component, this.getBody());
- }
- return body;
- },
- updateBody: function(body, oldBody) {
- if (body) {
- this.add(body);
- } else if (oldBody) {
- oldBody.destroy();
- }
- },
- applyHeader: function(header) {
- if (header && !header.isComponent) {
- header = Ext.factory(header, Ext.Component, this.getHeader());
- }
- return header;
- },
- updateHeader: function(header, oldHeader) {
- if (header) {
- this.element.getFirstChild().insertFirst(header.element);
- } else if (oldHeader) {
- oldHeader.destroy();
- }
- },
- applyDisclosure: function(disclosure) {
- if (disclosure && !disclosure.isComponent) {
- disclosure = Ext.factory(disclosure, Ext.Component, this.getDisclosure());
- }
- return disclosure;
- },
- updateDisclosure: function(disclosure, oldDisclosure) {
- if (disclosure) {
- this.add(disclosure);
- } else if (oldDisclosure) {
- oldDisclosure.destroy();
- }
- },
- updateTpl: function(tpl) {
- this.getBody().setTpl(tpl);
- },
- updateRecord: function(record) {
- var me = this,
- dataview = me.dataview || this.getDataview(),
- data = record && dataview.prepareData(record.getData(true), dataview.getStore().indexOf(record), record),
- dataMap = me.getDataMap(),
- body = this.getBody(),
- disclosure = this.getDisclosure();
- me._record = record;
- if (dataMap) {
- me.doMapData(dataMap, data, body);
- } else if (body) {
- body.updateData(data || null);
- }
- if (disclosure && record && dataview.getOnItemDisclosure()) {
- var disclosureProperty = dataview.getDisclosureProperty();
- disclosure[(data.hasOwnProperty(disclosureProperty) && data[disclosureProperty] === false) ? 'hide' : 'show']();
- }
- /**
- * @event updatedata
- * Fires whenever the data of the DataItem is updated.
- * @param {Ext.dataview.component.DataItem} this The DataItem instance.
- * @param {Object} newData The new data.
- */
- me.fireEvent('updatedata', me, data);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.util.TranslatableList', {
- extend: 'Ext.util.translatable.Abstract',
- config: {
- items: []
- },
- applyItems: function(items) {
- return Ext.Array.from(items);
- },
- doTranslate: function(x, y) {
- var items = this.getItems(),
- offset = 0,
- i, ln, item, translateY;
- for (i = 0, ln = items.length; i < ln; i++) {
- item = items[i];
- if (item && !item._list_hidden) {
- translateY = y + offset;
- offset += item.$height;
- item.translate(0, translateY);
- }
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.util.PositionMap', {
- config: {
- minimumHeight: 50
- },
- constructor: function(config) {
- this.map = [];
- this.adjustments = {};
- this.offset = 0;
- this.initConfig(config);
- },
- populate: function(count, offset) {
- var map = this.map = this.map || [],
- minimumHeight = this.getMinimumHeight(),
- i, previousIndex, ln;
- // We add 1 item to the count so that we can get the height of the bottom item
- count++;
- map.length = count;
- map[0] = 0;
- for (i = offset + 1, ln = count - 1; i <= ln; i++) {
- previousIndex = i - 1;
- map[i] = map[previousIndex] + minimumHeight;
- }
- this.adjustments = {
- indices: [],
- heights: {}
- };
- this.offset = 0;
- for (i = 1, ln = count - 1; i <= ln; i++) {
- previousIndex = i - 1;
- this.offset += map[i] - map[previousIndex] - minimumHeight;
- }
- },
- setItemHeight: function(index, height) {
- height = Math.max(height, this.getMinimumHeight());
- if (height !== this.getItemHeight(index)) {
- var adjustments = this.adjustments;
- adjustments.indices.push(parseInt(index, 10));
- adjustments.heights[index] = height;
- }
- },
- update: function() {
- var adjustments = this.adjustments,
- indices = adjustments.indices,
- heights = adjustments.heights,
- map = this.map,
- ln = indices.length,
- minimumHeight = this.getMinimumHeight(),
- difference = 0,
- i, j, height, index, nextIndex, currentHeight;
- if (!adjustments.indices.length) {
- return false;
- }
- Ext.Array.sort(indices, function(a, b) {
- return a - b;
- });
- for (i = 0; i < ln; i++) {
- index = indices[i];
- nextIndex = indices[i + 1] || map.length - 1;
- currentHeight = (map[index + 1] !== undefined) ? (map[index + 1] - map[index] + difference) : minimumHeight;
- height = heights[index];
- difference += height - currentHeight;
- for (j = index + 1; j <= nextIndex; j++) {
- map[j] += difference;
- }
- }
- this.offset += difference;
- this.adjustments = {
- indices: [],
- heights: {}
- };
- return true;
- },
- getItemHeight: function(index) {
- return this.map[index + 1] - this.map[index];
- },
- getTotalHeight: function() {
- return ((this.map.length - 1) * this.getMinimumHeight()) + this.offset;
- },
- findIndex: function(pos) {
- return this.map.length ? this.binarySearch(this.map, pos) : 0;
- },
- binarySearch: function(sorted, value) {
- var start = 0,
- end = sorted.length;
- if (value < sorted[0]) {
- return 0;
- }
- if (value > sorted[end - 1]) {
- return end - 1;
- }
- while (start + 1 < end) {
- var mid = (start + end) >> 1,
- val = sorted[mid];
- if (val == value) {
- return mid;
- } else if (val < value) {
- start = mid;
- } else {
- end = mid;
- }
- }
- return start;
- }
- });
- /**
- * @aside guide list
- * @aside video list
- *
- * List is a custom styled DataView which allows Grouping, Indexing, Icons, and a Disclosure. See the
- * [Guide](#!/guide/list) and [Video](#!/video/list) for more.
- *
- * @example miniphone preview
- * Ext.create('Ext.List', {
- * fullscreen: true,
- * itemTpl: '{title}',
- * data: [
- * { title: 'Item 1' },
- * { title: 'Item 2' },
- * { title: 'Item 3' },
- * { title: 'Item 4' }
- * ]
- * });
- *
- * A more advanced example showing a list of people groped by last name:
- *
- * @example miniphone preview
- * Ext.define('Contact', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: ['firstName', 'lastName']
- * }
- * });
- *
- * var store = Ext.create('Ext.data.Store', {
- * model: 'Contact',
- * sorters: 'lastName',
- *
- * grouper: {
- * groupFn: function(record) {
- * return record.get('lastName')[0];
- * }
- * },
- *
- * data: [
- * { firstName: 'Tommy', lastName: 'Maintz' },
- * { firstName: 'Rob', lastName: 'Dougan' },
- * { firstName: 'Ed', lastName: 'Spencer' },
- * { firstName: 'Jamie', lastName: 'Avins' },
- * { firstName: 'Aaron', lastName: 'Conran' },
- * { firstName: 'Dave', lastName: 'Kaneda' },
- * { firstName: 'Jacky', lastName: 'Nguyen' },
- * { firstName: 'Abraham', lastName: 'Elias' },
- * { firstName: 'Jay', lastName: 'Robinson'},
- * { firstName: 'Nigel', lastName: 'White' },
- * { firstName: 'Don', lastName: 'Griffin' },
- * { firstName: 'Nico', lastName: 'Ferrero' },
- * { firstName: 'Jason', lastName: 'Johnston'}
- * ]
- * });
- *
- * Ext.create('Ext.List', {
- * fullscreen: true,
- * itemTpl: '<div class="contact">{firstName} <strong>{lastName}</strong></div>',
- * store: store,
- * grouped: true
- * });
- */
- Ext.define('Ext.dataview.List', {
- alternateClassName: 'Ext.List',
- extend: 'Ext.dataview.DataView',
- xtype: 'list',
- mixins: ['Ext.mixin.Bindable'],
- requires: [
- 'Ext.dataview.IndexBar',
- 'Ext.dataview.ListItemHeader',
- 'Ext.dataview.component.ListItem',
- 'Ext.util.TranslatableList',
- 'Ext.util.PositionMap'
- ],
- /**
- * @event disclose
- * @preventable doDisclose
- * Fires whenever a disclosure is handled
- * @param {Ext.dataview.List} this The List instance
- * @param {Ext.data.Model} record The record associated to the item
- * @param {HTMLElement} target The element disclosed
- * @param {Number} index The index of the item disclosed
- * @param {Ext.EventObject} e The event object
- */
- config: {
- /**
- * @cfg layout
- * Hide layout config in DataView. It only causes confusion.
- * @accessor
- * @private
- */
- layout: 'fit',
- /**
- * @cfg {Boolean/Object} indexBar
- * `true` to render an alphabet IndexBar docked on the right.
- * This can also be a config object that will be passed to {@link Ext.IndexBar}.
- * @accessor
- */
- indexBar: false,
- icon: null,
- /**
- * @cfg {Boolean} clearSelectionOnDeactivate
- * `true` to clear any selections on the list when the list is deactivated.
- * @removed 2.0.0
- */
- /**
- * @cfg {Boolean} preventSelectionOnDisclose `true` to prevent the item selection when the user
- * taps a disclose icon.
- * @accessor
- */
- preventSelectionOnDisclose: true,
- /**
- * @cfg baseCls
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'list',
- /**
- * @cfg {Boolean} pinHeaders
- * Whether or not to pin headers on top of item groups while scrolling for an iPhone native list experience.
- * @accessor
- */
- pinHeaders: true,
- /**
- * @cfg {Boolean} grouped
- * Whether or not to group items in the provided Store with a header for each item.
- * @accessor
- */
- grouped: false,
- /**
- * @cfg {Boolean/Function/Object} onItemDisclosure
- * `true` to display a disclosure icon on each list item.
- * The list will still fire the disclose event, and the event can be stopped before itemtap.
- * By setting this config to a function, the function passed will be called when the disclosure
- * is tapped.
- * Finally you can specify an object with a 'scope' and 'handler'
- * property defined. This will also be bound to the tap event listener
- * and is useful when you want to change the scope of the handler.
- * @accessor
- */
- onItemDisclosure: null,
- /**
- * @cfg {String} disclosureProperty
- * A property to check on each record to display the disclosure on a per record basis. This
- * property must be false to prevent the disclosure from being displayed on the item.
- * @accessor
- */
- disclosureProperty: 'disclosure',
- /**
- * @cfg {String} ui
- * The style of this list. Available options are `normal` and `round`.
- */
- ui: 'normal',
- /**
- * @cfg {Boolean} useComponents
- * Flag the use a component based DataView implementation. This allows the full use of components in the
- * DataView at the cost of some performance.
- *
- * Checkout the [DataView Guide](#!/guide/dataview) for more information on using this configuration.
- * @accessor
- * @private
- */
- /**
- * @cfg {Object} itemConfig
- * A configuration object that is passed to every item created by a component based DataView. Because each
- * item that a DataView renders is a Component, we can pass configuration options to each component to
- * easily customize how each child component behaves.
- * Note this is only used when useComponents is true.
- * @accessor
- * @private
- */
- /**
- * @cfg {Number} maxItemCache
- * Maintains a cache of reusable components when using a component based DataView. Improving performance at
- * the cost of memory.
- * Note this is currently only used when useComponents is true.
- * @accessor
- * @private
- */
- /**
- * @cfg {String} defaultType
- * The xtype used for the component based DataView. Defaults to dataitem.
- * Note this is only used when useComponents is true.
- * @accessor
- */
- defaultType: 'listitem',
- /**
- * @cfg {Object} itemMap
- * @private
- */
- itemMap: {
- minimumHeight: 47
- },
- /**
- * @cfg {Boolean} variableHeights
- * Whether or not this list contains items with variable heights. If you want to force the
- * items in the list to have a fixed height, set the {@link #itemHeight} configuration.
- * If you also variableHeights to false, the scrolling performance of the list will be
- * improved.
- */
- variableHeights: true,
- /**
- * @cfg {Number} itemHeight
- * This allows you to set the default item height and is used to roughly calculate the amount
- * of items needed to fill the list. By default items are around 50px high. If you set this
- * configuration in combination with setting the {@link #variableHeights} to false you
- * can improve the scrolling speed
- */
- itemHeight: 47,
- /**
- * @cfg {Boolean} refreshHeightOnUpdate
- * Set this to false if you make many updates to your list (like in an interval), but updates
- * won't affect the item's height. Doing this will increase the performance of these updates.
- * Note that if you have {@link #variableHeights} set to false, this configuration option has
- * no effect.
- */
- refreshHeightOnUpdate: true,
- scrollable: false
- },
- constructor: function(config) {
- var me = this,
- layout;
- me.callParent(arguments);
- if (Ext.os.is.Android4 && !Ext.browser.is.ChromeMobile) {
- me.headerTranslateFn = Ext.Function.createThrottled(me.headerTranslateFn, 50, me);
- }
- //<debug>
- layout = this.getLayout();
- if (layout && !layout.isFit) {
- Ext.Logger.error('The base layout for a DataView must always be a Fit Layout');
- }
- //</debug>
- },
- topItemIndex: 0,
- topItemPosition: 0,
- updateItemHeight: function(itemHeight) {
- this.getItemMap().setMinimumHeight(itemHeight);
- },
- applyItemMap: function(itemMap) {
- return Ext.factory(itemMap, Ext.util.PositionMap, this.getItemMap());
- },
- // apply to the selection model to maintain visual UI cues
- // onItemTrigger: function(me, index, target, record, e) {
- // if (!(this.getPreventSelectionOnDisclose() && Ext.fly(e.target).hasCls(this.getBaseCls() + '-disclosure'))) {
- // this.callParent(arguments);
- // }
- // },
- beforeInitialize: function() {
- var me = this,
- container;
- me.listItems = [];
- me.scrollDockItems = {
- top: [],
- bottom: []
- };
- container = me.container = me.add(new Ext.Container({
- scrollable: {
- scroller: {
- autoRefresh: false,
- direction: 'vertical',
- translatable: {
- xclass: 'Ext.util.TranslatableList'
- }
- }
- }
- }));
- container.getScrollable().getScroller().getTranslatable().setItems(me.listItems);
- // Tie List's scroller to its container's scroller
- me.setScrollable(container.getScrollable());
- me.scrollableBehavior = container.getScrollableBehavior();
- },
- initialize: function() {
- var me = this,
- container = me.container,
- i, ln;
- me.updatedItems = [];
- me.headerMap = [];
- me.on(me.getTriggerCtEvent(), me.onContainerTrigger, me);
- me.on(me.getTriggerEvent(), me.onItemTrigger, me);
- me.header = Ext.factory({
- xclass: 'Ext.dataview.ListItemHeader',
- html: ' ',
- translatable: true,
- role: 'globallistheader',
- cls: ['x-list-header', 'x-list-header-swap']
- });
- me.container.innerElement.insertFirst(me.header.element);
- me.headerTranslate = me.header.getTranslatable();
- me.headerTranslate.translate(0, -10000);
- if (!me.getGrouped()) {
- me.updatePinHeaders(null);
- }
- container.element.on({
- delegate: '.' + me.getBaseCls() + '-disclosure',
- tap: 'handleItemDisclosure',
- scope: me
- });
- container.element.on({
- resize: 'onResize',
- scope: me
- });
- // Android 2.x not a direct child
- container.innerElement.on({
- touchstart: 'onItemTouchStart',
- touchend: 'onItemTouchEnd',
- tap: 'onItemTap',
- taphold: 'onItemTapHold',
- singletap: 'onItemSingleTap',
- doubletap: 'onItemDoubleTap',
- swipe: 'onItemSwipe',
- delegate: '.' + Ext.baseCSSPrefix + 'list-item-body',
- scope: me
- });
- for (i = 0, ln = me.scrollDockItems.top.length; i < ln; i++) {
- container.add(me.scrollDockItems.top[i]);
- }
- for (i = 0, ln = me.scrollDockItems.bottom.length; i < ln; i++) {
- container.add(me.scrollDockItems.bottom[i]);
- }
- if (me.getStore()) {
- me.refresh();
- }
- },
- updateInline: function(newInline) {
- var me = this;
- me.callParent(arguments);
- if (newInline) {
- me.setOnItemDisclosure(false);
- me.setIndexBar(false);
- me.setGrouped(false);
- }
- },
- applyIndexBar: function(indexBar) {
- return Ext.factory(indexBar, Ext.dataview.IndexBar, this.getIndexBar());
- },
- updateIndexBar: function(indexBar) {
- var me = this;
- if (indexBar && me.getScrollable()) {
- me.indexBarElement = me.getScrollableBehavior().getScrollView().getElement().appendChild(indexBar.renderElement);
- indexBar.on({
- index: 'onIndex',
- scope: me
- });
- me.element.addCls(me.getBaseCls() + '-indexed');
- }
- },
- updateGrouped: function(grouped) {
- var me = this,
- baseCls = this.getBaseCls(),
- cls = baseCls + '-grouped',
- unCls = baseCls + '-ungrouped';
- if (grouped) {
- me.addCls(cls);
- me.removeCls(unCls);
- me.updatePinHeaders(me.getPinHeaders());
- }
- else {
- me.addCls(unCls);
- me.removeCls(cls);
- me.updatePinHeaders(null);
- }
- if (me.isPainted() && me.listItems.length) {
- me.setItemsCount(me.listItems.length);
- }
- },
- updatePinHeaders: function(pinnedHeaders) {
- if (this.headerTranslate) {
- this.headerTranslate.translate(0, -10000);
- }
- },
- updateScrollerSize: function() {
- var me = this,
- totalHeight = me.getItemMap().getTotalHeight(),
- scroller = me.container.getScrollable().getScroller();
- if (totalHeight > 0) {
- scroller.givenSize = totalHeight;
- scroller.refresh();
- }
- },
- onResize: function() {
- var me = this,
- container = me.container,
- element = container.element,
- minimumHeight = me.getItemMap().getMinimumHeight(),
- containerSize;
- if (!me.listItems.length) {
- me.bind(container.getScrollable().getScroller().getTranslatable(), 'doTranslate', 'onTranslate');
- }
- me.containerSize = containerSize = element.getHeight();
- me.setItemsCount(Math.ceil(containerSize / minimumHeight) + 1);
- },
- scrollDockHeightRefresh: function() {
- var items = this.listItems,
- scrollDockItems = this.scrollDockItems,
- ln = items.length,
- i, item;
- for (i = 0; i < ln; i++) {
- item = items[i];
- if ((item.isFirst && scrollDockItems.top.length) || (item.isLast && scrollDockItems.bottom.length)) {
- this.updatedItems.push(item);
- }
- }
- this.refreshScroller();
- },
- headerTranslateFn: function(record, transY, headerTranslate) {
- var headerString = this.getStore().getGroupString(record);
- if (this.currentHeader !== headerString) {
- this.currentHeader = headerString;
- this.header.setHtml(headerString);
- }
- headerTranslate.translate(0, transY);
- },
- onTranslate: function(x, y, args) {
- var me = this,
- listItems = me.listItems,
- itemsCount = listItems.length,
- currentTopIndex = me.topItemIndex,
- itemMap = me.getItemMap(),
- store = me.getStore(),
- storeCount = store.getCount(),
- info = me.getListItemInfo(),
- grouped = me.getGrouped(),
- storeGroups = me.groups,
- headerMap = me.headerMap,
- headerTranslate = me.headerTranslate,
- pinHeaders = me.getPinHeaders(),
- maxIndex = storeCount - itemsCount + 1,
- topIndex, changedCount, i, index, item,
- closestHeader, record, pushedHeader, transY, element;
- if (me.updatedItems.length) {
- me.updateItemHeights();
- }
- me.topItemPosition = itemMap.findIndex(-y) || 0;
- me.indexOffset = me.topItemIndex = topIndex = Math.max(0, Math.min(me.topItemPosition, maxIndex));
- if (grouped && headerTranslate && storeGroups.length && pinHeaders) {
- closestHeader = itemMap.binarySearch(headerMap, -y);
- record = storeGroups[closestHeader].children[0];
- if (record) {
- pushedHeader = y + headerMap[closestHeader + 1] - me.headerHeight;
- // Top of the list or above (hide the floating header offscreen)
- if (y >= 0) {
- transY = -10000;
- }
- // Scroll the floating header a bit
- else if (pushedHeader < 0) {
- transY = pushedHeader;
- }
- // Stick to the top of the screen
- else {
- transY = Math.max(0, y);
- }
- this.headerTranslateFn(record, transY, headerTranslate);
- }
- }
- args[1] = (itemMap.map[topIndex] || 0) + y;
- if (currentTopIndex !== topIndex && topIndex <= maxIndex) {
- // Scroll up
- if (currentTopIndex > topIndex) {
- changedCount = Math.min(itemsCount, currentTopIndex - topIndex);
- for (i = changedCount - 1; i >= 0; i--) {
- item = listItems.pop();
- listItems.unshift(item);
- me.updateListItem(item, i + topIndex, info);
- }
- }
- else {
- // Scroll down
- changedCount = Math.min(itemsCount, topIndex - currentTopIndex);
- for (i = 0; i < changedCount; i++) {
- item = listItems.shift();
- listItems.push(item);
- index = i + topIndex + itemsCount - changedCount;
- me.updateListItem(item, index, info);
- }
- }
- }
- if (listItems.length && grouped && pinHeaders) {
- if (me.headerIndices[topIndex]) {
- element = listItems[0].getHeader().element;
- if (y < itemMap.map[topIndex]) {
- element.setVisibility(false);
- }
- else {
- element.setVisibility(true);
- }
- }
- for (i = 1; i <= changedCount; i++) {
- if (listItems[i]) {
- listItems[i].getHeader().element.setVisibility(true);
- }
- }
- }
- },
- setItemsCount: function(itemsCount) {
- var me = this,
- listItems = me.listItems,
- minimumHeight = me.getItemMap().getMinimumHeight(),
- config = {
- xtype: me.getDefaultType(),
- itemConfig: me.getItemConfig(),
- tpl: me.getItemTpl(),
- minHeight: minimumHeight,
- cls: me.getItemCls()
- },
- info = me.getListItemInfo(),
- i, item;
- for (i = 0; i < itemsCount; i++) {
- // We begin by checking if we already have an item for this length
- item = listItems[i];
- // If we don't have an item yet at this index then create one
- if (!item) {
- item = Ext.factory(config);
- item.dataview = me;
- item.$height = minimumHeight;
- me.container.doAdd(item);
- listItems.push(item);
- }
- item.dataIndex = null;
- if (info.store) {
- me.updateListItem(item, i + me.topItemIndex, info);
- }
- }
- me.updateScrollerSize();
- },
- getListItemInfo: function() {
- var me = this,
- baseCls = me.getBaseCls();
- return {
- store: me.getStore(),
- grouped: me.getGrouped(),
- baseCls: baseCls,
- selectedCls: me.getSelectedCls(),
- headerCls: baseCls + '-header-wrap',
- footerCls: baseCls + '-footer-wrap',
- firstCls: baseCls + '-item-first',
- lastCls: baseCls + '-item-last',
- itemMap: me.getItemMap(),
- variableHeights: me.getVariableHeights(),
- defaultItemHeight: me.getItemHeight()
- };
- },
- updateListItem: function(item, index, info) {
- var record = info.store.getAt(index);
- if (this.isSelected(record)) {
- item.addCls(info.selectedCls);
- }
- else {
- item.removeCls(info.selectedCls);
- }
- item.removeCls([info.headerCls, info.footerCls, info.firstCls, info.lastCls]);
- this.replaceItemContent(item, index, info)
- },
- taskRunner: function() {
- delete this.intervalId;
- if (this.scheduledTasks && this.scheduledTasks.length > 0) {
- var task = this.scheduledTasks.shift();
- this.doUpdateListItem(task.item, task.index, task.info);
- if (this.scheduledTasks.length === 0 && this.getVariableHeights() && !this.container.getScrollable().getScroller().getTranslatable().isAnimating) {
- this.refreshScroller();
- } else if (this.scheduledTasks.length > 0) {
- this.intervalId = requestAnimationFrame(Ext.Function.bind(this.taskRunner, this));
- }
- }
- },
- scheduledTasks: null,
- replaceItemContent: function(item, index, info) {
- var translatable = this.container.getScrollable().getScroller().getTranslatable();
- // This falls apart when scrolling up. Turning off for now.
- if (Ext.os.is.Android4
- && !Ext.browser.is.Chrome
- && !info.variableHeights
- && !info.grouped
- && translatable.isAnimating
- && translatable.activeEasingY
- && Math.abs(translatable.activeEasingY._startVelocity) > .75) {
- if (!this.scheduledTasks) {
- this.scheduledTasks = [];
- }
- for (var i = 0; i < this.scheduledTasks.length; i++) {
- if (this.scheduledTasks[i].item === item) {
- Ext.Array.remove(this.scheduledTasks, this.scheduledTasks[i]);
- break;
- }
- }
- this.scheduledTasks.push({
- item: item,
- index: index,
- info: info
- });
- if (!this.intervalId) {
- this.intervalId = requestAnimationFrame(Ext.Function.bind(this.taskRunner, this));
- }
- } else {
- this.doUpdateListItem(item, index, info);
- }
- },
- doUpdateListItem: function(item, index, info) {
- var record = info.store.getAt(index),
- headerIndices = this.headerIndices,
- footerIndices = this.footerIndices,
- headerItem = item.getHeader(),
- scrollDockItems = this.scrollDockItems,
- updatedItems = this.updatedItems,
- itemHeight = info.itemMap.getItemHeight(index),
- ln, i, scrollDockItem;
- if (!record) {
- item.setRecord(null);
- item.translate(0, -10000);
- item._list_hidden = true;
- return;
- }
- item._list_hidden = false;
- if (item.isFirst && scrollDockItems.top.length) {
- for (i = 0, ln = scrollDockItems.top.length; i < ln; i++) {
- scrollDockItem = scrollDockItems.top[i];
- scrollDockItem.addCls(Ext.baseCSSPrefix + 'list-scrolldock-hidden');
- item.remove(scrollDockItem, false);
- }
- item.isFirst = false;
- }
- if (item.isLast && scrollDockItems.bottom.length) {
- for (i = 0, ln = scrollDockItems.bottom.length; i < ln; i++) {
- scrollDockItem = scrollDockItems.bottom[i];
- scrollDockItem.addCls(Ext.baseCSSPrefix + 'list-scrolldock-hidden');
- item.remove(scrollDockItem, false);
- }
- item.isLast = false;
- }
- if (item.getRecord) {
- if (item.dataIndex !== index) {
- item.dataIndex = index;
- this.fireEvent('itemindexchange', this, record, index, item);
- }
- if (item.getRecord() === record) {
- item.updateRecord(record);
- } else {
- item.setRecord(record);
- }
- }
- if (this.isSelected(record)) {
- item.addCls(info.selectedCls);
- }
- else {
- item.removeCls(info.selectedCls);
- }
- item.removeCls([info.headerCls, info.footerCls, info.firstCls, info.lastCls]);
- if (info.grouped) {
- if (headerIndices[index]) {
- item.addCls(info.headerCls);
- headerItem.setHtml(info.store.getGroupString(record));
- headerItem.show();
- headerItem.element.setVisibility(true);
- // If this record is a group header, and the items height is still the default height
- // we need to read the actual size of the item (including the header)
- if (!info.variableHeights && itemHeight === info.defaultItemHeight) {
- Ext.Array.include(updatedItems, item);
- }
- }
- else {
- headerItem.hide();
- // If this record is not a header (anymore) and its height is unequal to the default item height
- // it means the item must have gotten a different height because being a header before and now needs
- // to become the default height again
- if (!info.variableHeights && !footerIndices[index] && itemHeight !== info.defaultItemHeight) {
- info.itemMap.setItemHeight(index, info.defaultItemHeight);
- info.itemMap.update();
- }
- }
- if (footerIndices[index]) {
- item.addCls(info.footerCls);
- // If this record is a footer and its height is still the same as the default item height, we have
- // to make sure to read this items height to see if adding the foot cls effects its height
- if (!info.variableHeights && itemHeight === info.defaultItemHeight) {
- Ext.Array.include(updatedItems, item);
- }
- }
- } else if (!info.variableHeights && itemHeight !== info.defaultItemHeight) {
- // If this list is not grouped, the only thing that can change the height of an item
- // can be scroll dock items. If an items height is not equal to the default item height
- // it means it must have had scroll dock items. In this case we set the items height
- // to become the default height again.
- info.itemMap.setItemHeight(index, info.defaultItemHeight);
- info.itemMap.update();
- }
- if (index === 0) {
- item.isFirst = true;
- item.addCls(info.firstCls);
- if (!info.grouped) {
- item.addCls(info.headerCls);
- }
- for (i = 0, ln = scrollDockItems.top.length; i < ln; i++) {
- scrollDockItem = scrollDockItems.top[i];
- item.insert(0, scrollDockItem);
- scrollDockItem.removeCls(Ext.baseCSSPrefix + 'list-scrolldock-hidden');
- }
- // If an item gets scrolldock items inside of it, we need to always read the height
- // in the next frame so we add it to the updatedItems array
- if (ln && !info.variableHeights) {
- Ext.Array.include(updatedItems, item);
- }
- }
- if (index === info.store.getCount() - 1) {
- item.isLast = true;
- item.addCls(info.lastCls);
- if (!info.grouped) {
- item.addCls(info.footerCls);
- }
- for (i = 0, ln = scrollDockItems.bottom.length; i < ln; i++) {
- scrollDockItem = scrollDockItems.bottom[i];
- item.insert(0, scrollDockItem);
- scrollDockItem.removeCls(Ext.baseCSSPrefix + 'list-scrolldock-hidden');
- }
- // If an item gets scrolldock items inside of it, we need to always read the height
- // in the next frame so we add it to the updatedItems array
- if (ln && !info.variableHeights) {
- Ext.Array.include(updatedItems, item);
- }
- }
- item.$height = info.itemMap.getItemHeight(index);
- if (info.variableHeights) {
- updatedItems.push(item);
- }
- },
- updateItemHeights: function() {
- if (!this.isPainted()) {
- this.pendingHeightUpdate = true;
- if (!this.pendingHeightUpdate) {
- this.on('painted', this.updateItemHeights, this, {single: true});
- }
- return;
- }
- var updatedItems = this.updatedItems,
- ln = updatedItems.length,
- itemMap = this.getItemMap(),
- scroller = this.container.getScrollable().getScroller(),
- minimumHeight = itemMap.getMinimumHeight(),
- headerIndices = this.headerIndices,
- headerMap = this.headerMap,
- translatable = scroller.getTranslatable(),
- itemIndex, i, item, height;
- this.pendingHeightUpdate = false;
- // First we do all the reads
- for (i = 0; i < ln; i++) {
- item = updatedItems[i];
- itemIndex = item.dataIndex;
- // itemIndex may not be set yet if the store is still being loaded
- if (itemIndex !== null) {
- height = item.element.getFirstChild().getHeight();
- height = Math.max(height, minimumHeight);
- if (headerIndices && !this.headerHeight && headerIndices[itemIndex]) {
- this.headerHeight = parseInt(item.getHeader().element.getHeight(), 10);
- }
- itemMap.setItemHeight(itemIndex, height);
- }
- }
- itemMap.update();
- height = itemMap.getTotalHeight();
- headerMap.length = 0;
- for (i in headerIndices) {
- headerMap.push(itemMap.map[i]);
- }
- // Now do the dom writes
- for (i = 0; i < ln; i++) {
- item = updatedItems[i];
- itemIndex = item.dataIndex;
- item.$height = itemMap.getItemHeight(itemIndex);
- }
- if (height != scroller.givenSize) {
- scroller.setSize(height);
- scroller.refreshMaxPosition();
- if (translatable.isAnimating) {
- translatable.activeEasingY.setMinMomentumValue(-scroller.getMaxPosition().y);
- }
- }
- this.updatedItems.length = 0;
- },
- /**
- * Returns an item at the specified index.
- * @param {Number} index Index of the item.
- * @return {Ext.dom.Element/Ext.dataview.component.DataItem} item Item at the specified index.
- */
- getItemAt: function(index) {
- var listItems = this.listItems,
- ln = listItems.length,
- i, listItem;
- for (i = 0; i < ln; i++) {
- listItem = listItems[i];
- if (listItem.dataIndex === index) {
- return listItem;
- }
- }
- },
- /**
- * Returns an index for the specified item.
- * @param {Number} item The item to locate.
- * @return {Number} Index for the specified item.
- */
- getItemIndex: function(item) {
- var index = item.dataIndex;
- return (index === -1) ? index : this.indexOffset + index;
- },
- /**
- * Returns an array of the current items in the DataView.
- * @return {Ext.dom.Element[]/Ext.dataview.component.DataItem[]} Array of Items.
- */
- getViewItems: function() {
- return this.listItems;
- },
- doRefresh: function(list) {
- if (this.intervalId) {
- cancelAnimationFrame(this.intervalId);
- delete this.intervalId;
- }
- if (this.scheduledTasks) {
- this.scheduledTasks.length = 0;
- }
- var me = this,
- store = me.getStore(),
- scrollable = me.container.getScrollable(),
- scroller = scrollable && scrollable.getScroller(),
- painted = me.isPainted(),
- storeCount = store.getCount();
- me.getItemMap().populate(storeCount, this.topItemPosition);
- if (me.getGrouped()) {
- me.findGroupHeaderIndices();
- }
- // This will refresh the items on the screen with the new data
- if (me.listItems.length) {
- me.setItemsCount(me.listItems.length);
- if (painted) {
- me.refreshScroller(scroller);
- }
- }
- if (painted && this.getScrollToTopOnRefresh() && scroller && list) {
- scroller.scrollToTop();
- }
- // No items, hide all the items from the collection.
- if (storeCount < 1) {
- me.onStoreClear();
- return;
- } else {
- me.hideEmptyText();
- }
- },
- findGroupHeaderIndices: function() {
- var me = this,
- store = me.getStore(),
- storeLn = store.getCount(),
- groups = store.getGroups(),
- groupLn = groups.length,
- headerIndices = me.headerIndices = {},
- footerIndices = me.footerIndices = {},
- i, previousIndex, firstGroupedRecord, storeIndex;
- me.groups = groups;
- for (i = 0; i < groupLn; i++) {
- firstGroupedRecord = groups[i].children[0];
- storeIndex = store.indexOf(firstGroupedRecord);
- headerIndices[storeIndex] = true;
- previousIndex = storeIndex - 1;
- if (previousIndex) {
- footerIndices[previousIndex] = true;
- }
- }
- footerIndices[storeLn - 1] = true;
- return headerIndices;
- },
- // Handling adds and removes like this is fine for now. It should not perform much slower then a dedicated solution
- onStoreAdd: function() {
- this.doRefresh();
- },
- onStoreRemove: function() {
- this.doRefresh();
- },
- onStoreUpdate: function(store, record, newIndex, oldIndex) {
- var me = this,
- scroller = me.container.getScrollable().getScroller(),
- item;
- oldIndex = (typeof oldIndex === 'undefined') ? newIndex : oldIndex;
- if (oldIndex !== newIndex) {
- // Just refreshing the list here saves a lot of code and shouldnt be much slower
- me.doRefresh();
- }
- else {
- if (newIndex >= me.topItemIndex && newIndex < me.topItemIndex + me.listItems.length) {
- item = me.getItemAt(newIndex);
- me.doUpdateListItem(item, newIndex, me.getListItemInfo());
- // Bypassing setter because sometimes we pass the same record (different data)
- //me.updateListItem(me.getItemAt(newIndex), newIndex, me.getListItemInfo());
- if (me.getVariableHeights() && me.getRefreshHeightOnUpdate()) {
- me.updatedItems.push(item);
- me.updateItemHeights();
- me.refreshScroller(scroller);
- }
- }
- }
- },
- /*
- * @private
- * This is to fix the variable heights again since the item height might have changed after the update
- */
- refreshScroller: function(scroller) {
- if (!scroller) {
- scroller = this.container.getScrollable().getScroller()
- }
- scroller.scrollTo(0, scroller.position.y + 1);
- scroller.scrollTo(0, scroller.position.y - 1);
- },
- onStoreClear: function() {
- if (this.headerTranslate) {
- this.headerTranslate.translate(0, -10000);
- }
- this.showEmptyText();
- },
- onIndex: function(indexBar, index) {
- var me = this,
- key = index.toLowerCase(),
- store = me.getStore(),
- groups = store.getGroups(),
- ln = groups.length,
- scrollable = me.container.getScrollable(),
- scroller, group, i, closest, id;
- if (scrollable) {
- scroller = scrollable.getScroller();
- }
- else {
- return;
- }
- for (i = 0; i < ln; i++) {
- group = groups[i];
- id = group.name.toLowerCase();
- if (id == key || id > key) {
- closest = group;
- break;
- }
- else {
- closest = group;
- }
- }
- if (scrollable && closest) {
- index = store.indexOf(closest.children[0]);
- //stop the scroller from scrolling
- scroller.stopAnimation();
- //make sure the new offsetTop is not out of bounds for the scroller
- var containerSize = scroller.getContainerSize().y,
- size = scroller.getSize().y,
- maxOffset = size - containerSize,
- offsetTop = me.getItemMap().map[index],
- offset = (offsetTop > maxOffset) ? maxOffset : offsetTop;
- // This is kind of hacky, but since there might be variable heights we have to render the frame
- // twice. First to update all the content, then to read the heights and translate items accordingly
- scroller.scrollTo(0, offset);
- if (this.updatedItems.length > 0 && (!this.scheduledTasks || this.scheduledTasks.length === 0)) {
- this.refreshScroller();
- }
- //scroller.scrollTo(0, offset);
- }
- },
- applyOnItemDisclosure: function(config) {
- if (Ext.isFunction(config)) {
- return {
- scope: this,
- handler: config
- };
- }
- return config;
- },
- handleItemDisclosure: function(e) {
- var me = this,
- item = Ext.getCmp(Ext.get(e.getTarget()).up('.x-list-item').id),
- index = item.dataIndex,
- record = me.getStore().getAt(index);
- me.fireAction('disclose', [me, record, item, index, e], 'doDisclose');
- },
- doDisclose: function(me, record, item, index, e) {
- var onItemDisclosure = me.getOnItemDisclosure();
- if (onItemDisclosure && onItemDisclosure.handler) {
- onItemDisclosure.handler.call(onItemDisclosure.scope || me, record, item, index, e);
- }
- },
- updateItemCls: function(newCls, oldCls) {
- var items = this.listItems,
- ln = items.length,
- i, item;
- for (i = 0; i < ln; i++) {
- item = items[i];
- item.removeCls(oldCls);
- item.addCls(newCls);
- }
- },
- onItemTouchStart: function(e) {
- this.container.innerElement.on({
- touchmove: 'onItemTouchMove',
- delegate: '.' + Ext.baseCSSPrefix + 'list-item-body',
- single: true,
- scope: this
- });
- this.callParent(this.parseEvent(e));
- },
- onItemTouchMove: function(e) {
- this.callParent(this.parseEvent(e));
- },
- onItemTouchEnd: function(e) {
- this.container.innerElement.un({
- touchmove: 'onItemTouchMove',
- delegate: '.' + Ext.baseCSSPrefix + 'list-item-body',
- scope: this
- });
- this.callParent(this.parseEvent(e));
- },
- onItemTap: function(e) {
- this.callParent(this.parseEvent(e));
- },
- onItemTapHold: function(e) {
- this.callParent(this.parseEvent(e));
- },
- onItemSingleTap: function(e) {
- this.callParent(this.parseEvent(e));
- },
- onItemDoubleTap: function(e) {
- this.callParent(this.parseEvent(e));
- },
- onItemSwipe: function(e) {
- this.callParent(this.parseEvent(e));
- },
- parseEvent: function(e) {
- var me = this,
- target = Ext.fly(e.getTarget()).findParent('.' + Ext.baseCSSPrefix + 'list-item', 8),
- item = Ext.getCmp(target.id);
- return [me, item, item.dataIndex, e];
- },
- onItemAdd: function(item) {
- var me = this,
- config = item.config;
- if (config.scrollDock) {
- if (config.scrollDock == 'bottom') {
- me.scrollDockItems.bottom.push(item);
- } else {
- me.scrollDockItems.top.push(item);
- }
- item.addCls(Ext.baseCSSPrefix + 'list-scrolldock-hidden');
- if (me.container) {
- me.container.add(item);
- }
- } else {
- me.callParent(arguments);
- }
- },
- destroy: function() {
- Ext.destroy(this.getIndexBar(), this.indexBarElement, this.header);
- if (this.intervalId) {
- cancelAnimationFrame(this.intervalId);
- delete this.intervalId;
- }
- this.callParent();
- }
- });
- /**
- * NestedList provides a miller column interface to navigate between nested sets
- * and provide a clean interface with limited screen real-estate.
- *
- * @example miniphone preview
- * var data = {
- * text: 'Groceries',
- * items: [{
- * text: 'Drinks',
- * items: [{
- * text: 'Water',
- * items: [{
- * text: 'Sparkling',
- * leaf: true
- * }, {
- * text: 'Still',
- * leaf: true
- * }]
- * }, {
- * text: 'Coffee',
- * leaf: true
- * }, {
- * text: 'Espresso',
- * leaf: true
- * }, {
- * text: 'Redbull',
- * leaf: true
- * }, {
- * text: 'Coke',
- * leaf: true
- * }, {
- * text: 'Diet Coke',
- * leaf: true
- * }]
- * }, {
- * text: 'Fruit',
- * items: [{
- * text: 'Bananas',
- * leaf: true
- * }, {
- * text: 'Lemon',
- * leaf: true
- * }]
- * }, {
- * text: 'Snacks',
- * items: [{
- * text: 'Nuts',
- * leaf: true
- * }, {
- * text: 'Pretzels',
- * leaf: true
- * }, {
- * text: 'Wasabi Peas',
- * leaf: true
- * }]
- * }]
- * };
- *
- * Ext.define('ListItem', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: [{
- * name: 'text',
- * type: 'string'
- * }]
- * }
- * });
- *
- * var store = Ext.create('Ext.data.TreeStore', {
- * model: 'ListItem',
- * defaultRootProperty: 'items',
- * root: data
- * });
- *
- * var nestedList = Ext.create('Ext.NestedList', {
- * fullscreen: true,
- * title: 'Groceries',
- * displayField: 'text',
- * store: store
- * });
- *
- * @aside guide nested_list
- * @aside example nested-list
- * @aside example navigation-view
- */
- Ext.define('Ext.dataview.NestedList', {
- alternateClassName: 'Ext.NestedList',
- extend: 'Ext.Container',
- xtype: 'nestedlist',
- requires: [
- 'Ext.List',
- 'Ext.TitleBar',
- 'Ext.Button',
- 'Ext.XTemplate',
- 'Ext.data.StoreManager',
- 'Ext.data.NodeStore',
- 'Ext.data.TreeStore'
- ],
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- cls: Ext.baseCSSPrefix + 'nested-list',
- /**
- * @cfg {String/Object/Boolean} cardSwitchAnimation
- * Animation to be used during transitions of cards.
- * @removed 2.0.0 please use {@link Ext.layout.Card#animation}
- */
- /**
- * @cfg {String} backText
- * The label to display for the back button.
- * @accessor
- */
- backText: 'Back',
- /**
- * @cfg {Boolean} useTitleAsBackText
- * `true` to use title as a label for back button.
- * @accessor
- */
- useTitleAsBackText: true,
- /**
- * @cfg {Boolean} updateTitleText
- * Update the title with the currently selected category.
- * @accessor
- */
- updateTitleText: true,
- /**
- * @cfg {String} displayField
- * Display field to use when setting item text and title.
- * This configuration is ignored when overriding {@link #getItemTextTpl} or
- * {@link #getTitleTextTpl} for the item text or title.
- * @accessor
- */
- displayField: 'text',
- /**
- * @cfg {String} loadingText
- * Loading text to display when a subtree is loading.
- * @accessor
- */
- loadingText: 'Loading...',
- /**
- * @cfg {String} emptyText
- * Empty text to display when a subtree is empty.
- * @accessor
- */
- emptyText: 'No items available.',
- /**
- * @cfg {Boolean/Function} onItemDisclosure
- * Maps to the {@link Ext.List#onItemDisclosure} configuration for individual lists.
- * @accessor
- */
- onItemDisclosure: false,
- /**
- * @cfg {Boolean} allowDeselect
- * Set to `true` to allow the user to deselect leaf items via interaction.
- * @accessor
- */
- allowDeselect: false,
- /**
- * @deprecated 2.0.0 Please set the {@link #toolbar} configuration to `false` instead
- * @cfg {Boolean} useToolbar `true` to show the header toolbar.
- * @accessor
- */
- useToolbar: null,
- /**
- * @cfg {Ext.Toolbar/Object/Boolean} toolbar
- * The configuration to be used for the toolbar displayed in this nested list.
- * @accessor
- */
- toolbar: {
- docked: 'top',
- xtype: 'titlebar',
- ui: 'light',
- inline: true
- },
- /**
- * @cfg {String} title The title of the toolbar
- * @accessor
- */
- title: '',
- /**
- * @cfg {String} layout
- * @hide
- * @accessor
- */
- layout: {
- type: 'card',
- animation: {
- type: 'slide',
- duration: 250,
- direction: 'left'
- }
- },
- /**
- * @cfg {Ext.data.TreeStore/String} store The tree store to be used for this nested list.
- */
- store: null,
- /**
- * @cfg {Ext.Container} detailContainer The container of the `detailCard`.
- * @accessor
- */
- detailContainer: undefined,
- /**
- * @cfg {Ext.Component} detailCard to provide a final card for leaf nodes.
- * @accessor
- */
- detailCard: null,
- /**
- * @cfg {Object} backButton The configuration for the back button used in the nested list.
- */
- backButton: {
- ui: 'back',
- hidden: true
- },
- /**
- * @cfg {Object} listConfig An optional config object which is merged with the default
- * configuration used to create each nested list.
- */
- listConfig: null,
- // @private
- lastNode: null,
- // @private
- lastActiveList: null
- },
- /**
- * @event itemtap
- * Fires when a node is tapped on.
- * @param {Ext.dataview.NestedList} this
- * @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.
- * @param {Number} index The index of the item tapped.
- * @param {Ext.dom.Element} target The element tapped.
- * @param {Ext.data.Record} record The record tapped.
- * @param {Ext.event.Event} e The event object.
- */
- /**
- * @event itemdoubletap
- * Fires when a node is double tapped on.
- * @param {Ext.dataview.NestedList} this
- * @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.
- * @param {Number} index The index of the item that was tapped.
- * @param {Ext.dom.Element} target The element tapped.
- * @param {Ext.data.Record} record The record tapped.
- * @param {Ext.event.Event} e The event object.
- */
- /**
- * @event containertap
- * Fires when a tap occurs and it is not on a template node.
- * @param {Ext.dataview.NestedList} this
- * @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.
- * @param {Ext.event.Event} e The raw event object.
- */
- /**
- * @event selectionchange
- * Fires when the selected nodes change.
- * @param {Ext.dataview.NestedList} this
- * @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.
- * @param {Array} selections Array of the selected nodes.
- */
- /**
- * @event beforeselectionchange
- * Fires before a selection is made.
- * @param {Ext.dataview.NestedList} this
- * @param {Ext.dataview.List} list The Ext.dataview.List that is currently active.
- * @param {HTMLElement} node The node to be selected.
- * @param {Array} selections Array of currently selected nodes.
- * @deprecated 2.0.0 Please listen to the {@link #selectionchange} event with an order of `before` instead.
- */
- /**
- * @event listchange
- * Fires when the user taps a list item.
- * @param {Ext.dataview.NestedList} this
- * @param {Object} listitem The new active list.
- */
- /**
- * @event leafitemtap
- * Fires when the user taps a leaf list item.
- * @param {Ext.dataview.NestedList} this
- * @param {Ext.List} list The subList the item is on.
- * @param {Number} index The index of the item tapped.
- * @param {Ext.dom.Element} target The element tapped.
- * @param {Ext.data.Record} record The record tapped.
- * @param {Ext.event.Event} e The event.
- */
- /**
- * @event back
- * @preventable doBack
- * Fires when the user taps Back.
- * @param {Ext.dataview.NestedList} this
- * @param {HTMLElement} node The node to be selected.
- * @param {Ext.dataview.List} lastActiveList The Ext.dataview.List that was last active.
- * @param {Boolean} detailCardActive Flag set if the detail card is currently active.
- */
- /**
- * @event beforeload
- * Fires before a request is made for a new data object.
- * @param {Ext.dataview.NestedList} this
- * @param {Ext.data.Store} store The store instance.
- * @param {Ext.data.Operation} operation The Ext.data.Operation object that will be passed to the Proxy to
- * load the Store.
- */
- /**
- * @event load
- * Fires whenever records have been loaded into the store.
- * @param {Ext.dataview.NestedList} this
- * @param {Ext.data.Store} store The store instance.
- * @param {Ext.util.Grouper[]} records An array of records.
- * @param {Boolean} successful `true` if the operation was successful.
- * @param {Ext.data.Operation} operation The associated operation.
- */
- constructor: function (config) {
- if (Ext.isObject(config)) {
- if (config.getTitleTextTpl) {
- this.getTitleTextTpl = config.getTitleTextTpl;
- }
- if (config.getItemTextTpl) {
- this.getItemTextTpl = config.getItemTextTpl;
- }
- }
- this.callParent(arguments);
- },
- onItemInteraction: function () {
- if (this.isGoingTo) {
- return false;
- }
- },
- applyDetailContainer: function (config) {
- if (!config) {
- config = this;
- }
- return config;
- },
- updateDetailContainer: function (newContainer, oldContainer) {
- if (newContainer) {
- newContainer.onBefore('activeitemchange', 'onBeforeDetailContainerChange', this);
- newContainer.onAfter('activeitemchange', 'onDetailContainerChange', this);
- }
- },
- onBeforeDetailContainerChange: function () {
- this.isGoingTo = true;
- },
- onDetailContainerChange: function () {
- this.isGoingTo = false;
- },
- /**
- * Called when an list item has been tapped.
- * @param {Ext.List} list The subList the item is on.
- * @param {Number} index The id of the item tapped.
- * @param {Ext.Element} target The list item tapped.
- * @param {Ext.data.Record} record The record which as tapped.
- * @param {Ext.event.Event} e The event.
- */
- onItemTap: function (list, index, target, record, e) {
- var me = this,
- store = list.getStore(),
- node = store.getAt(index);
- me.fireEvent('itemtap', this, list, index, target, record, e);
- if (node.isLeaf()) {
- me.fireEvent('leafitemtap', this, list, index, target, record, e);
- me.goToLeaf(node);
- }
- else {
- this.goToNode(node);
- }
- },
- onBeforeSelect: function () {
- this.fireEvent.apply(this, [].concat('beforeselect', this, Array.prototype.slice.call(arguments)));
- },
- onContainerTap: function () {
- this.fireEvent.apply(this, [].concat('containertap', this, Array.prototype.slice.call(arguments)));
- },
- onSelectionChange: function () {
- this.fireEvent.apply(this, [].concat('selectionchange', this, Array.prototype.slice.call(arguments)));
- },
- onItemDoubleTap: function () {
- this.fireEvent.apply(this, [].concat('itemdoubletap', this, Array.prototype.slice.call(arguments)));
- },
- onStoreBeforeLoad: function () {
- var loadingText = this.getLoadingText(),
- scrollable = this.getScrollable();
- if (loadingText) {
- this.setMasked({
- xtype: 'loadmask',
- message: loadingText
- });
- //disable scrolling while it is masked
- if (scrollable) {
- scrollable.getScroller().setDisabled(true);
- }
- }
- this.fireEvent.apply(this, [].concat('beforeload', this, Array.prototype.slice.call(arguments)));
- },
- onStoreLoad: function (store, records, successful, operation) {
- this.setMasked(false);
- this.fireEvent.apply(this, [].concat('load', this, Array.prototype.slice.call(arguments)));
- if (store.indexOf(this.getLastNode()) === -1) {
- this.goToNode(store.getRoot());
- }
- },
- /**
- * Called when the backButton has been tapped.
- */
- onBackTap: function () {
- var me = this,
- node = me.getLastNode(),
- detailCard = me.getDetailCard(),
- detailCardActive = detailCard && me.getActiveItem() == detailCard,
- lastActiveList = me.getLastActiveList();
- this.fireAction('back', [this, node, lastActiveList, detailCardActive], 'doBack');
- },
- doBack: function (me, node, lastActiveList, detailCardActive) {
- var layout = me.getLayout(),
- animation = (layout) ? layout.getAnimation() : null;
- if (detailCardActive && lastActiveList) {
- if (animation) {
- animation.setReverse(true);
- }
- me.setActiveItem(lastActiveList);
- me.setLastNode(node.parentNode);
- me.syncToolbar();
- }
- else {
- this.goToNode(node.parentNode);
- }
- },
- updateData: function (data) {
- if (!this.getStore()) {
- this.setStore(new Ext.data.TreeStore({
- root: data
- }));
- }
- },
- applyStore: function (store) {
- if (store) {
- if (Ext.isString(store)) {
- // store id
- store = Ext.data.StoreManager.get(store);
- } else {
- // store instance or store config
- if (!(store instanceof Ext.data.TreeStore)) {
- store = Ext.factory(store, Ext.data.TreeStore, null);
- }
- }
- // <debug>
- if (!store) {
- Ext.Logger.warn("The specified Store cannot be found", this);
- }
- //</debug>
- }
- return store;
- },
- storeListeners: {
- rootchange: 'onStoreRootChange',
- load: 'onStoreLoad',
- beforeload: 'onStoreBeforeLoad'
- },
- updateStore: function (newStore, oldStore) {
- var me = this,
- listeners = this.storeListeners;
- listeners.scope = me;
- if (oldStore && Ext.isObject(oldStore) && oldStore.isStore) {
- if (oldStore.autoDestroy) {
- oldStore.destroy();
- }
- oldStore.un(listeners);
- }
- if (newStore) {
- me.goToNode(newStore.getRoot());
- newStore.on(listeners);
- }
- },
- onStoreRootChange: function (store, node) {
- this.goToNode(node);
- },
- applyBackButton: function (config) {
- return Ext.factory(config, Ext.Button, this.getBackButton());
- },
- applyDetailCard: function (config, oldDetailCard) {
- if (config === null) {
- return Ext.factory(config, Ext.Component, oldDetailCard);
- } else {
- return Ext.factory(config, Ext.Component);
- }
- },
- updateBackButton: function (newButton, oldButton) {
- if (newButton) {
- var me = this;
- newButton.on('tap', me.onBackTap, me);
- newButton.setText(me.getBackText());
- me.getToolbar().insert(0, newButton);
- }
- else if (oldButton) {
- oldButton.destroy();
- }
- },
- applyToolbar: function (config) {
- return Ext.factory(config, Ext.TitleBar, this.getToolbar());
- },
- updateToolbar: function (newToolbar, oldToolbar) {
- var me = this;
- if (newToolbar) {
- newToolbar.setTitle(me.getTitle());
- if (!newToolbar.getParent()) {
- me.add(newToolbar);
- }
- }
- else if (oldToolbar) {
- oldToolbar.destroy();
- }
- },
- updateUseToolbar: function (newUseToolbar, oldUseToolbar) {
- if (!newUseToolbar) {
- this.setToolbar(false);
- }
- },
- updateTitle: function (newTitle) {
- var me = this,
- toolbar = me.getToolbar();
- if (toolbar) {
- if (me.getUpdateTitleText()) {
- toolbar.setTitle(newTitle);
- }
- }
- },
- /**
- * Override this method to provide custom template rendering of individual
- * nodes. The template will receive all data within the Record and will also
- * receive whether or not it is a leaf node.
- * @param {Ext.data.Record} node
- * @return {String}
- */
- getItemTextTpl: function (node) {
- return '{' + this.getDisplayField() + '}';
- },
- /**
- * Override this method to provide custom template rendering of titles/back
- * buttons when {@link #useTitleAsBackText} is enabled.
- * @param {Ext.data.Record} node
- * @return {String}
- */
- getTitleTextTpl: function (node) {
- return '{' + this.getDisplayField() + '}';
- },
- /**
- * @private
- */
- renderTitleText: function (node, forBackButton) {
- if (!node.titleTpl) {
- node.titleTpl = Ext.create('Ext.XTemplate', this.getTitleTextTpl(node));
- }
- if (node.isRoot()) {
- var initialTitle = this.getInitialConfig('title');
- return (forBackButton && initialTitle === '') ? this.getInitialConfig('backText') : initialTitle;
- }
- return node.titleTpl.applyTemplate(node.data);
- },
- /**
- * Method to handle going to a specific node within this nested list. Node must be part of the
- * internal {@link #store}.
- * @param {Ext.data.NodeInterface} node The specified node to navigate to.
- */
- goToNode: function (node) {
- if (!node) {
- return;
- }
- var me = this,
- activeItem = me.getActiveItem(),
- detailCard = me.getDetailCard(),
- detailCardActive = detailCard && me.getActiveItem() == detailCard,
- reverse = me.goToNodeReverseAnimation(node),
- firstList = me.firstList,
- secondList = me.secondList,
- layout = me.getLayout(),
- animation = (layout) ? layout.getAnimation() : null,
- list;
- //if the node is a leaf, throw an error
- if (node.isLeaf()) {
- throw new Error('goToNode: passed a node which is a leaf.');
- }
- //if we are currently at the passed node, do nothing.
- if (node == me.getLastNode() && !detailCardActive) {
- return;
- }
- if (detailCardActive) {
- if (animation) {
- animation.setReverse(true);
- }
- list = me.getLastActiveList();
- list.getStore().setNode(node);
- node.expand();
- me.setActiveItem(list);
- }
- else {
- if (firstList && secondList) {
- //firstList and secondList have both been created
- activeItem = me.getActiveItem();
- me.setLastActiveList(activeItem);
- list = (activeItem == firstList) ? secondList : firstList;
- list.getStore().setNode(node);
- node.expand();
- if (animation) {
- animation.setReverse(reverse);
- }
- me.setActiveItem(list);
- list.deselectAll();
- }
- else if (firstList) {
- //only firstList has been created
- me.setLastActiveList(me.getActiveItem());
- me.setActiveItem(me.getList(node));
- me.secondList = me.getActiveItem();
- }
- else {
- //no lists have been created
- me.setActiveItem(me.getList(node));
- me.firstList = me.getActiveItem();
- }
- }
- me.fireEvent('listchange', this, me.getActiveItem());
- me.setLastNode(node);
- me.syncToolbar();
- },
- /**
- * The leaf you want to navigate to. You should pass a node instance.
- * @param {Ext.data.NodeInterface} node The specified node to navigate to.
- */
- goToLeaf: function (node) {
- if (!node.isLeaf()) {
- throw new Error('goToLeaf: passed a node which is not a leaf.');
- }
- var me = this,
- card = me.getDetailCard(node),
- container = me.getDetailContainer(),
- sharedContainer = container == this,
- layout = me.getLayout(),
- animation = (layout) ? layout.getAnimation() : false;
- if (card) {
- if (container.getItems().indexOf(card) === -1) {
- container.add(card);
- }
- if (sharedContainer) {
- if (me.getActiveItem() instanceof Ext.dataview.List) {
- me.setLastActiveList(me.getActiveItem());
- }
- me.setLastNode(node);
- }
- if (animation) {
- animation.setReverse(false);
- }
- container.setActiveItem(card);
- me.syncToolbar();
- }
- },
- /**
- * @private
- * Method which updates the {@link #backButton} and {@link #toolbar} with the latest information from
- * the current node.
- */
- syncToolbar: function (forceDetail) {
- var me = this,
- detailCard = me.getDetailCard(),
- node = me.getLastNode(),
- detailActive = forceDetail || (detailCard && (me.getActiveItem() == detailCard)),
- parentNode = (detailActive) ? node : node.parentNode,
- backButton = me.getBackButton();
- //show/hide the backButton, and update the backButton text, if one exists
- if (backButton) {
- backButton[parentNode ? 'show' : 'hide']();
- if (parentNode && me.getUseTitleAsBackText()) {
- backButton.setText(me.renderTitleText(node.parentNode, true));
- }
- }
- if (node) {
- me.setTitle(me.renderTitleText(node));
- }
- },
- updateBackText: function (newText) {
- this.getBackButton().setText(newText);
- },
- /**
- * @private
- * Returns `true` if the passed node should have a reverse animation from the previous current node.
- * @param {Ext.data.NodeInterface} node
- */
- goToNodeReverseAnimation: function (node) {
- var me = this,
- lastNode = me.getLastNode();
- if (!lastNode) {
- return false;
- }
- return (!lastNode.contains(node) && lastNode.isAncestor(node)) ? true : false;
- },
- /**
- * @private
- * Returns the list config for a specified node.
- * @param {HTMLElement} node The node for the list config.
- */
- getList: function (node) {
- var me = this,
- nodeStore = Ext.create('Ext.data.NodeStore', {
- recursive: false,
- node: node,
- rootVisible: false,
- model: me.getStore().getModel()
- });
- node.expand();
- return Ext.Object.merge({
- xtype: 'list',
- pressedDelay: 250,
- autoDestroy: true,
- store: nodeStore,
- onItemDisclosure: me.getOnItemDisclosure(),
- allowDeselect: me.getAllowDeselect(),
- variableHeights: false,
- listeners: [
- { event: 'itemdoubletap', fn: 'onItemDoubleTap', scope: me },
- { event: 'itemtap', fn: 'onItemInteraction', scope: me, order: 'before'},
- { event: 'itemtouchstart', fn: 'onItemInteraction', scope: me, order: 'before'},
- { event: 'itemtap', fn: 'onItemTap', scope: me },
- { event: 'beforeselectionchange', fn: 'onBeforeSelect', scope: me },
- { event: 'containertap', fn: 'onContainerTap', scope: me },
- { event: 'selectionchange', fn: 'onSelectionChange', order: 'before', scope: me }
- ],
- itemTpl: '<span<tpl if="leaf == true"> class="x-list-item-leaf"</tpl>>' + me.getItemTextTpl(node) + '</span>'
- }, this.getListConfig());
- }
- }, function () {
- });
- /**
- * @private
- */
- Ext.define('Ext.dataview.element.List', {
- extend: 'Ext.dataview.element.Container',
- updateBaseCls: function(newBaseCls) {
- var me = this;
- me.itemClsShortCache = newBaseCls + '-item';
- me.headerClsShortCache = newBaseCls + '-header';
- me.headerClsCache = '.' + me.headerClsShortCache;
- me.headerItemClsShortCache = newBaseCls + '-header-item';
- me.footerClsShortCache = newBaseCls + '-footer-item';
- me.footerClsCache = '.' + me.footerClsShortCache;
- me.labelClsShortCache = newBaseCls + '-item-label';
- me.labelClsCache = '.' + me.labelClsShortCache;
- me.disclosureClsShortCache = newBaseCls + '-disclosure';
- me.disclosureClsCache = '.' + me.disclosureClsShortCache;
- me.iconClsShortCache = newBaseCls + '-icon';
- me.iconClsCache = '.' + me.iconClsShortCache;
- this.callParent(arguments);
- },
- hiddenDisplayCache: Ext.baseCSSPrefix + 'hidden-display',
- getItemElementConfig: function(index, data) {
- var me = this,
- dataview = me.dataview,
- itemCls = dataview.getItemCls(),
- cls = me.itemClsShortCache,
- config, iconSrc;
- if (itemCls) {
- cls += ' ' + itemCls;
- }
- config = {
- cls: cls,
- children: [{
- cls: me.labelClsShortCache,
- html: dataview.getItemTpl().apply(data)
- }]
- };
- if (dataview.getIcon()) {
- iconSrc = data.iconSrc;
- config.children.push({
- cls: me.iconClsShortCache,
- style: 'background-image: ' + iconSrc ? 'url("' + newSrc + '")' : ''
- });
- }
- if (dataview.getOnItemDisclosure()) {
- config.children.push({
- cls: me.disclosureClsShortCache + ' ' + ((data[dataview.getDisclosureProperty()] === false) ? me.hiddenDisplayCache : '')
- });
- }
- return config;
- },
- updateListItem: function(record, item) {
- var me = this,
- dataview = me.dataview,
- extItem = Ext.fly(item),
- innerItem = extItem.down(me.labelClsCache, true),
- data = dataview.prepareData(record.getData(true), dataview.getStore().indexOf(record), record),
- disclosureProperty = dataview.getDisclosureProperty(),
- hasDisclosureProperty = data && data.hasOwnProperty(disclosureProperty),
- iconSrc = data && data.hasOwnProperty('iconSrc'),
- disclosureEl, iconEl;
- innerItem.innerHTML = dataview.getItemTpl().apply(data);
- if (hasDisclosureProperty) {
- disclosureEl = extItem.down(me.disclosureClsCache);
- disclosureEl[data[disclosureProperty] === false ? 'addCls' : 'removeCls'](me.hiddenDisplayCache);
- }
- if (dataview.getIcon()) {
- iconEl = extItem.down(me.iconClsCache, true);
- iconEl.style.backgroundImage = iconSrc ? 'url("' + iconSrc + '")' : '';
- }
- },
- doRemoveHeaders: function() {
- var me = this,
- headerClsShortCache = me.headerItemClsShortCache,
- existingHeaders = me.element.query(me.headerClsCache),
- existingHeadersLn = existingHeaders.length,
- i = 0,
- item;
- for (; i < existingHeadersLn; i++) {
- item = existingHeaders[i];
- Ext.fly(item.parentNode).removeCls(headerClsShortCache);
- Ext.get(item).destroy();
- }
- },
- doRemoveFooterCls: function() {
- var me = this,
- footerClsShortCache = me.footerClsShortCache,
- existingFooters = me.element.query(me.footerClsCache),
- existingFootersLn = existingFooters.length,
- i = 0;
- for (; i < existingFootersLn; i++) {
- Ext.fly(existingFooters[i]).removeCls(footerClsShortCache);
- }
- },
- doAddHeader: function(item, html) {
- item = Ext.fly(item);
- if (html) {
- item.insertFirst(Ext.Element.create({
- cls: this.headerClsShortCache,
- html: html
- }));
- }
- item.addCls(this.headerItemClsShortCache);
- },
- destroy: function() {
- this.doRemoveHeaders();
- this.callParent();
- }
- });
- /**
- * @private
- *
- * This object handles communication between the WebView and Sencha's native shell.
- * Currently it has two primary responsibilities:
- *
- * 1. Maintaining unique string ids for callback functions, together with their scope objects
- * 2. Serializing given object data into HTTP GET request parameters
- *
- * As an example, to capture a photo from the device's camera, we use `Ext.device.Camera.capture()` like:
- *
- * Ext.device.Camera.capture(
- * function(dataUri){
- * // Do something with the base64-encoded `dataUri` string
- * },
- * function(errorMessage) {
- *
- * },
- * callbackScope,
- * {
- * quality: 75,
- * width: 500,
- * height: 500
- * }
- * );
- *
- * Internally, `Ext.device.Communicator.send()` will then be invoked with the following argument:
- *
- * Ext.device.Communicator.send({
- * command: 'Camera#capture',
- * callbacks: {
- * onSuccess: function() {
- * // ...
- * },
- * onError: function() {
- * // ...
- * }
- * },
- * scope: callbackScope,
- * quality: 75,
- * width: 500,
- * height: 500
- * });
- *
- * Which will then be transformed into a HTTP GET request, sent to native shell's local
- * HTTP server with the following parameters:
- *
- * ?quality=75&width=500&height=500&command=Camera%23capture&onSuccess=3&onError=5
- *
- * Notice that `onSuccess` and `onError` have been converted into string ids (`3` and `5`
- * respectively) and maintained by `Ext.device.Communicator`.
- *
- * Whenever the requested operation finishes, `Ext.device.Communicator.invoke()` simply needs
- * to be executed from the native shell with the corresponding ids given before. For example:
- *
- * Ext.device.Communicator.invoke('3', ['DATA_URI_OF_THE_CAPTURED_IMAGE_HERE']);
- *
- * will invoke the original `onSuccess` callback under the given scope. (`callbackScope`), with
- * the first argument of 'DATA_URI_OF_THE_CAPTURED_IMAGE_HERE'
- *
- * Note that `Ext.device.Communicator` maintains the uniqueness of each function callback and
- * its scope object. If subsequent calls to `Ext.device.Communicator.send()` have the same
- * callback references, the same old ids will simply be reused, which guarantee the best possible
- * performance for a large amount of repetitive calls.
- */
- Ext.define('Ext.device.communicator.Default', {
- SERVER_URL: 'http://localhost:3000', // Change this to the correct server URL
- callbackDataMap: {},
- callbackIdMap: {},
- idSeed: 0,
- globalScopeId: '0',
- generateId: function() {
- return String(++this.idSeed);
- },
- getId: function(object) {
- var id = object.$callbackId;
- if (!id) {
- object.$callbackId = id = this.generateId();
- }
- return id;
- },
- getCallbackId: function(callback, scope) {
- var idMap = this.callbackIdMap,
- dataMap = this.callbackDataMap,
- id, scopeId, callbackId, data;
- if (!scope) {
- scopeId = this.globalScopeId;
- }
- else if (scope.isIdentifiable) {
- scopeId = scope.getId();
- }
- else {
- scopeId = this.getId(scope);
- }
- callbackId = this.getId(callback);
- if (!idMap[scopeId]) {
- idMap[scopeId] = {};
- }
- if (!idMap[scopeId][callbackId]) {
- id = this.generateId();
- data = {
- callback: callback,
- scope: scope
- };
- idMap[scopeId][callbackId] = id;
- dataMap[id] = data;
- }
- return idMap[scopeId][callbackId];
- },
- getCallbackData: function(id) {
- return this.callbackDataMap[id];
- },
- invoke: function(id, args) {
- var data = this.getCallbackData(id);
- data.callback.apply(data.scope, args);
- },
- send: function(args) {
- var callbacks, scope, name, callback;
- if (!args) {
- args = {};
- }
- else if (args.callbacks) {
- callbacks = args.callbacks;
- scope = args.scope;
- delete args.callbacks;
- delete args.scope;
- for (name in callbacks) {
- if (callbacks.hasOwnProperty(name)) {
- callback = callbacks[name];
- if (typeof callback == 'function') {
- args[name] = this.getCallbackId(callback, scope);
- }
- }
- }
- }
- this.doSend(args);
- },
- doSend: function(args) {
- var xhr = new XMLHttpRequest();
- xhr.open('GET', this.SERVER_URL + '?' + Ext.Object.toQueryString(args) + '&_dc=' + new Date().getTime(), false);
- // wrap the request in a try/catch block so we can check if any errors are thrown and attempt to call any
- // failure/callback functions if defined
- try {
- xhr.send(null);
- } catch(e) {
- if (args.failure) {
- this.invoke(args.failure);
- } else if (args.callback) {
- this.invoke(args.callback);
- }
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.communicator.Android', {
- extend: 'Ext.device.communicator.Default',
- doSend: function(args) {
- window.Sencha.action(JSON.stringify(args));
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.Communicator', {
- requires: [
- 'Ext.device.communicator.Default',
- 'Ext.device.communicator.Android'
- ],
- singleton: true,
- constructor: function() {
- if (Ext.os.is.Android) {
- return new Ext.device.communicator.Android();
- }
- return new Ext.device.communicator.Default();
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.camera.Abstract', {
- source: {
- library: 0,
- camera: 1,
- album: 2
- },
- destination: {
- data: 0, // Returns base64-encoded string
- file: 1 // Returns file's URI
- },
- encoding: {
- jpeg: 0,
- jpg: 0,
- png: 1
- },
- /**
- * Allows you to capture a photo.
- *
- * @param {Object} options
- * The options to use when taking a photo.
- *
- * @param {Function} options.success
- * The success callback which is called when the photo has been taken.
- *
- * @param {String} options.success.image
- * The image which was just taken, either a base64 encoded string or a URI depending on which
- * option you chose (destination).
- *
- * @param {Function} options.failure
- * The function which is called when something goes wrong.
- *
- * @param {Object} scope
- * The scope in which to call the `success` and `failure` functions, if specified.
- *
- * @param {Number} options.quality
- * The quality of the image which is returned in the callback. This should be a percentage.
- *
- * @param {String} options.source
- * The source of where the image should be taken. Available options are:
- *
- * - **album** - prompts the user to choose an image from an album
- * - **camera** - prompts the user to take a new photo
- * - **library** - prompts the user to choose an image from the library
- *
- * @param {String} destination
- * The destination of the image which is returned. Available options are:
- *
- * - **data** - returns a base64 encoded string
- * - **file** - returns the file's URI
- *
- * @param {String} encoding
- * The encoding of the returned image. Available options are:
- *
- * - **jpg**
- * - **png**
- *
- * @param {Number} width
- * The width of the image to return
- *
- * @param {Number} height
- * The height of the image to return
- */
- capture: Ext.emptyFn
- });
- /**
- * @private
- */
- Ext.define('Ext.device.camera.PhoneGap', {
- extend: 'Ext.device.camera.Abstract',
- capture: function(args) {
- var onSuccess = args.success,
- onError = args.failure,
- scope = args.scope,
- sources = this.source,
- destinations = this.destination,
- encodings = this.encoding,
- source = args.source,
- destination = args.destination,
- encoding = args.encoding,
- options = {};
- if (scope) {
- onSuccess = Ext.Function.bind(onSuccess, scope);
- onError = Ext.Function.bind(onError, scope);
- }
- if (source !== undefined) {
- options.sourceType = sources.hasOwnProperty(source) ? sources[source] : source;
- }
- if (destination !== undefined) {
- options.destinationType = destinations.hasOwnProperty(destination) ? destinations[destination] : destination;
- }
- if (encoding !== undefined) {
- options.encodingType = encodings.hasOwnProperty(encoding) ? encodings[encoding] : encoding;
- }
- if ('quality' in args) {
- options.quality = args.quality;
- }
- if ('width' in args) {
- options.targetWidth = args.width;
- }
- if ('height' in args) {
- options.targetHeight = args.height;
- }
- try {
- navigator.camera.getPicture(onSuccess, onError, options);
- }
- catch (e) {
- alert(e);
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.camera.Sencha', {
- extend: 'Ext.device.camera.Abstract',
- requires: [
- 'Ext.device.Communicator'
- ],
- capture: function(options) {
- var sources = this.source,
- destinations = this.destination,
- encodings = this.encoding,
- source = options.source,
- destination = options.destination,
- encoding = options.encoding;
- if (sources.hasOwnProperty(source)) {
- source = sources[source];
- }
- if (destinations.hasOwnProperty(destination)) {
- destination = destinations[destination];
- }
- if (encodings.hasOwnProperty(encoding)) {
- encoding = encodings[encoding];
- }
- Ext.device.Communicator.send({
- command: 'Camera#capture',
- callbacks: {
- success: options.success,
- failure: options.failure
- },
- scope: options.scope,
- quality: options.quality,
- width: options.width,
- height: options.height,
- source: source,
- destination: destination,
- encoding: encoding
- });
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.camera.Simulator', {
- extend: 'Ext.device.camera.Abstract',
- config: {
- samples: [
- {
- success: 'http://www.sencha.com/img/sencha-large.png'
- }
- ]
- },
- constructor: function(config) {
- this.initConfig(config);
- },
- updateSamples: function(samples) {
- this.sampleIndex = 0;
- },
- capture: function(options) {
- var index = this.sampleIndex,
- samples = this.getSamples(),
- samplesCount = samples.length,
- sample = samples[index],
- scope = options.scope,
- success = options.success,
- failure = options.failure;
- if ('success' in sample) {
- if (success) {
- success.call(scope, sample.success);
- }
- }
- else {
- if (failure) {
- failure.call(scope, sample.failure);
- }
- }
- if (++index > samplesCount - 1) {
- index = 0;
- }
- this.sampleIndex = index;
- }
- });
- /**
- * This class allows you to use native APIs to take photos using the device camera.
- *
- * When this singleton is instantiated, it will automatically select the correct implementation depending on the
- * current device:
- *
- * - Sencha Packager
- * - PhoneGap
- * - Simulator
- *
- * Both the Sencha Packager and PhoneGap implementations will use the native camera functionality to take or select
- * a photo. The Simulator implementation will simply return fake images.
- *
- * ## Example
- *
- * You can use the {@link Ext.device.Camera#capture} function to take a photo:
- *
- * Ext.device.Camera.capture({
- * success: function(image) {
- * imageView.setSrc(image);
- * },
- * quality: 75,
- * width: 200,
- * height: 200,
- * destination: 'data'
- * });
- *
- * See the documentation for {@link Ext.device.Camera#capture} all available configurations.
- *
- * @mixins Ext.device.camera.Abstract
- *
- * @aside guide native_apis
- */
- Ext.define('Ext.device.Camera', {
- singleton: true,
- requires: [
- 'Ext.device.Communicator',
- 'Ext.device.camera.PhoneGap',
- 'Ext.device.camera.Sencha',
- 'Ext.device.camera.Simulator'
- ],
- constructor: function() {
- var browserEnv = Ext.browser.is;
- if (browserEnv.WebView) {
- if (browserEnv.PhoneGap) {
- return Ext.create('Ext.device.camera.PhoneGap');
- }
- else {
- return Ext.create('Ext.device.camera.Sencha');
- }
- }
- else {
- return Ext.create('Ext.device.camera.Simulator');
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.connection.Abstract', {
- extend: 'Ext.Evented',
- config: {
- online: false,
- type: null
- },
- /**
- * @property {String} UNKNOWN
- * Text label for a connection type.
- */
- UNKNOWN: 'Unknown connection',
- /**
- * @property {String} ETHERNET
- * Text label for a connection type.
- */
- ETHERNET: 'Ethernet connection',
- /**
- * @property {String} WIFI
- * Text label for a connection type.
- */
- WIFI: 'WiFi connection',
- /**
- * @property {String} CELL_2G
- * Text label for a connection type.
- */
- CELL_2G: 'Cell 2G connection',
- /**
- * @property {String} CELL_3G
- * Text label for a connection type.
- */
- CELL_3G: 'Cell 3G connection',
- /**
- * @property {String} CELL_4G
- * Text label for a connection type.
- */
- CELL_4G: 'Cell 4G connection',
- /**
- * @property {String} NONE
- * Text label for a connection type.
- */
- NONE: 'No network connection',
- /**
- * True if the device is currently online
- * @return {Boolean} online
- */
- isOnline: function() {
- return this.getOnline();
- }
- /**
- * @method getType
- * Returns the current connection type.
- * @return {String} type
- */
- });
- /**
- * @private
- */
- Ext.define('Ext.device.connection.Sencha', {
- extend: 'Ext.device.connection.Abstract',
- /**
- * @event onlinechange
- * Fires when the connection status changes.
- * @param {Boolean} online True if you are {@link Ext.device.Connection#isOnline online}
- * @param {String} type The new online {@link Ext.device.Connection#getType type}
- */
- initialize: function() {
- Ext.device.Communicator.send({
- command: 'Connection#watch',
- callbacks: {
- callback: this.onConnectionChange
- },
- scope: this
- });
- },
- onConnectionChange: function(e) {
- this.setOnline(Boolean(e.online));
- this.setType(this[e.type]);
- this.fireEvent('onlinechange', this.getOnline(), this.getType());
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.connection.PhoneGap', {
- extend: 'Ext.device.connection.Abstract',
- syncOnline: function() {
- var type = navigator.network.connection.type;
- this._type = type;
- this._online = type != Connection.NONE;
- },
- getOnline: function() {
- this.syncOnline();
- return this._online;
- },
- getType: function() {
- this.syncOnline();
- return this._type;
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.connection.Simulator', {
- extend: 'Ext.device.connection.Abstract',
- getOnline: function() {
- this._online = navigator.onLine;
- this._type = Ext.device.Connection.UNKNOWN;
- return this._online;
- }
- });
- /**
- * This class is used to check if the current device is currently online or not. It has three different implementations:
- *
- * - Sencha Packager
- * - PhoneGap
- * - Simulator
- *
- * Both the Sencha Packager and PhoneGap implementations will use the native functionality to determine if the current
- * device is online. The Simulator version will simply use `navigator.onLine`.
- *
- * When this singleton ({@link Ext.device.Connection}) is instantiated, it will automatically decide which version to
- * use based on the current platform.
- *
- * ## Examples
- *
- * Determining if the current device is online:
- *
- * alert(Ext.device.Connection.isOnline());
- *
- * Checking the type of connection the device has:
- *
- * alert('Your connection type is: ' + Ext.device.Connection.getType());
- *
- * The available connection types are:
- *
- * - {@link Ext.device.Connection#UNKNOWN UNKNOWN} - Unknown connection
- * - {@link Ext.device.Connection#ETHERNET ETHERNET} - Ethernet connection
- * - {@link Ext.device.Connection#WIFI WIFI} - WiFi connection
- * - {@link Ext.device.Connection#CELL_2G CELL_2G} - Cell 2G connection
- * - {@link Ext.device.Connection#CELL_3G CELL_3G} - Cell 3G connection
- * - {@link Ext.device.Connection#CELL_4G CELL_4G} - Cell 4G connection
- * - {@link Ext.device.Connection#NONE NONE} - No network connection
- *
- * @mixins Ext.device.connection.Abstract
- *
- * @aside guide native_apis
- */
- Ext.define('Ext.device.Connection', {
- singleton: true,
- requires: [
- 'Ext.device.Communicator',
- 'Ext.device.connection.Sencha',
- 'Ext.device.connection.PhoneGap',
- 'Ext.device.connection.Simulator'
- ],
-
- /**
- * @event onlinechange
- * @inheritdoc Ext.device.connection.Sencha#onlinechange
- */
- constructor: function() {
- var browserEnv = Ext.browser.is;
- if (browserEnv.WebView) {
- if (browserEnv.PhoneGap) {
- return Ext.create('Ext.device.connection.PhoneGap');
- }
- else {
- return Ext.create('Ext.device.connection.Sencha');
- }
- }
- else {
- return Ext.create('Ext.device.connection.Simulator');
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.contacts.Abstract', {
- extend: 'Ext.Evented',
- config: {
- /**
- * @cfg {Boolean} includeImages
- * True to include images when you get the contacts store. Please beware that this can be very slow.
- */
- includeImages: false
- },
- /**
- * Returns an Array of contact objects.
- * @return {Object[]} An array of contact objects.
- */
- getContacts: function(config) {
- if (!this._store) {
- this._store = [
- {
- first: 'Robert',
- last: 'Dougan',
- emails: {
- work: 'rob@sencha.com'
- }
- },
- {
- first: 'Jamie',
- last: 'Avins',
- emails: {
- work: 'jamie@sencha.com'
- }
- }
- ];
- }
- config.success.call(config.scope || this, this._store);
- },
- /**
- * Returns base64 encoded image thumbnail for a contact specified in config.id
- * @return {String} base64 string
- */
- getThumbnail: function(config) {
- config.callback.call(config.scope || this, "");
- },
- /**
- * Returns localized, user readable label for a contact field (i.e. "Mobile", "Home")
- * @return {String} user readable string
- */
- getLocalizedLabel: function(config) {
- config.callback.call(config.scope || this, config.label.toUpperCase(), config.label);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.contacts.Sencha', {
- extend: 'Ext.device.contacts.Abstract',
- getContacts: function(config) {
- var includeImages = this.getIncludeImages();
- if (typeof config.includeImages != "undefined") {
- includeImages = config.includeImages;
- }
- if (!config) {
- Ext.Logger.warn('Ext.device.Contacts#getContacts: You must specify a `config` object.');
- return false;
- }
- if (!config.success) {
- Ext.Logger.warn('Ext.device.Contacts#getContacts: You must specify a `success` method.');
- return false;
- }
- Ext.device.Communicator.send({
- command: 'Contacts#all',
- callbacks: {
- success: function(contacts) {
- config.success.call(config.scope || this, contacts);
- },
- failure: function() {
- if (config.failure) {
- config.failure.call(config.scope || this);
- }
- }
- },
- includeImages: includeImages,
- scope: this
- });
- },
- getThumbnail: function(config) {
- if (!config || typeof config.id == "undefined") {
- Ext.Logger.warn('Ext.device.Contacts#getThumbnail: You must specify an `id` of the contact.');
- return false;
- }
- if (!config || !config.callback) {
- Ext.Logger.warn('Ext.device.Contacts#getThumbnail: You must specify a `callback`.');
- return false;
- }
- Ext.device.Communicator.send({
- command: 'Contacts#getThumbnail',
- callbacks: {
- success: function(src) {
- this.set('thumbnail', src);
- if (config.callback) {
- config.callback.call(config.scope || this, this);
- }
- }
- },
- id: id,
- scope: this
- });
- },
- getLocalizedLabel: function(config) {
- if (!config || typeof config.label == "undefined") {
- Ext.Logger.warn('Ext.device.Contacts#getLocalizedLabel: You must specify an `label` to be localized.');
- return false;
- }
- if (!config || !config.callback) {
- Ext.Logger.warn('Ext.device.Contacts#getLocalizedLabel: You must specify a `callback`.');
- return false;
- }
- Ext.device.Communicator.send({
- command: 'Contacts#getLocalizedLabel',
- callbacks: {
- callback: function(label) {
- config.callback.call(config.scope || this, label, config.label);
- }
- },
- label: config.label,
- scope: this
- });
- }
- });
- /**
- * This device API allows you to access a users contacts using a {@link Ext.data.Store}. This allows you to search, filter
- * and sort through all the contacts using its methods.
- *
- * To use this API, all you need to do is require this class (`Ext.device.Contacts`) and then use `Ext.device.Contacts.getContacts()`
- * to retrieve an array of contacts.
- *
- * **Please note that this will *only* work using the Sencha Native Packager.**
- *
- * # Example
- *
- * Ext.application({
- * name: 'Sencha',
- * requires: 'Ext.device.Contacts',
- *
- * launch: function() {
- * Ext.Viewport.add({
- * xtype: 'list',
- * itemTpl: '{First} {Last}',
- * store: {
- * fields: ['First', 'Last'],
- * data: Ext.device.Contacts.getContacts()
- * }
- * });
- * }
- * });
- *
- * @mixins Ext.device.contacts.Abstract
- * @mixins Ext.device.contacts.Sencha
- *
- * @aside guide native_apis
- */
- Ext.define('Ext.device.Contacts', {
- singleton: true,
- requires: [
- 'Ext.device.Communicator',
- 'Ext.device.contacts.Sencha'
- ],
- constructor: function() {
- var browserEnv = Ext.browser.is;
- if (browserEnv.WebView && !browserEnv.PhoneGap) {
- return Ext.create('Ext.device.contacts.Sencha');
- } else {
- return Ext.create('Ext.device.contacts.Abstract');
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.device.Abstract', {
- extend: 'Ext.EventedBase',
- /**
- * @event schemeupdate
- * Event which is fired when your Sencha Native packaged application is opened from another application using a custom URL scheme.
- *
- * This event will only fire if the application was already open (in other words; `onReady` was already fired). This means you should check
- * if {@link Ext.device.Device#scheme} is set in your Application `launch`/`onReady` method, and perform any needed changes for that URL (if defined).
- * Then listen to this event for future changed.
- *
- * ## Example
- *
- * Ext.application({
- * name: 'Sencha',
- * requires: ['Ext.device.Device'],
- * launch: function() {
- * if (Ext.device.Device.scheme) {
- * // the application was opened via another application. Do something:
- * console.log('Applicaton opened via another application: ' + Ext.device.Device.scheme.url);
- * }
- *
- * // Listen for future changes
- * Ext.device.Device.on('schemeupdate', function(device, scheme) {
- * // the application was launched, closed, and then launched another from another application
- * // this means onReady wont be called again ('cause the application is already running in the
- * // background) - but this event will be fired
- * console.log('Applicated reopened via another application: ' + scheme.url);
- * }, this);
- * }
- * });
- *
- * __Note:__ This currently only works with the Sencha Native Packager. If you attempt to listen to this event when packaged with
- * PhoneGap or simply in the browser, it will never fire.**
- *
- * @param {Ext.device.Device} this The instance of Ext.device.Device
- * @param {Object/Boolean} scheme The scheme information, if opened via another application
- * @param {String} scheme.url The URL that was opened, if this application was opened via another application. Example: `sencha:`
- * @param {String} scheme.sourceApplication The source application that opened this application. Example: `com.apple.safari`.
- */
-
- /**
- * @property {String} name
- * Returns the name of the current device. If the current device does not have a name (for example, in a browser), it will
- * default to `not available`.
- *
- * alert('Device name: ' + Ext.device.Device.name);
- */
- name: 'not available',
- /**
- * @property {String} uuid
- * Returns a unique identifier for the current device. If the current device does not have a unique identifier (for example,
- * in a browser), it will default to `anonymous`.
- *
- * alert('Device UUID: ' + Ext.device.Device.uuid);
- */
- uuid: 'anonymous',
- /**
- * @property {String} platform
- * The current platform the device is running on.
- *
- * alert('Device platform: ' + Ext.device.Device.platform);
- */
- platform: Ext.os.name,
- /**
- * @property {Object/Boolean} scheme
- *
- */
- scheme: false,
-
- /**
- * Opens a specified URL. The URL can contain a custom URL Scheme for another app or service:
- *
- * // Safari
- * Ext.device.Device.openURL('http://sencha.com');
- *
- * // Telephone
- * Ext.device.Device.openURL('tel:6501231234');
- *
- * // SMS with a default number
- * Ext.device.Device.openURL('sms:+12345678901');
- *
- * // Email client
- * Ext.device.Device.openURL('mailto:rob@sencha.com');
- *
- * 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).
- *
- * __Note:__ This currently only works on iOS using the Sencha Native Packager. Attempting to use this on PhoneGap, iOS Simulator
- * or the browser will simply result in the current window location changing.**
- *
- * If successful, this will close the application (as another one opens).
- *
- * @param {String} url The URL to open
- */
- openURL: function(url) {
- window.location = url;
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.device.PhoneGap', {
- extend: 'Ext.device.device.Abstract',
- constructor: function() {
- // We can't get the device details until the device is ready, so lets wait.
- if (Ext.Viewport.isReady) {
- this.onReady();
- } else {
- Ext.Viewport.on('ready', this.onReady, this, {single: true});
- }
- },
- onReady: function() {
- this.name = device.name;
- this.uuid = device.uuid;
- this.platform = device.platformName || Ext.os.name;
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.device.Sencha', {
- extend: 'Ext.device.device.Abstract',
- constructor: function() {
- this.name = device.name;
- this.uuid = device.uuid;
- this.platform = device.platformName || Ext.os.name;
- this.initURL();
- },
- openURL: function(url) {
- Ext.device.Communicator.send({
- command: 'OpenURL#open',
- url: url
- });
- },
- /**
- * @private
- */
- initURL: function() {
- Ext.device.Communicator.send({
- command: "OpenURL#watch",
- callbacks: {
- callback: this.updateURL
- },
- scope: this
- });
- },
- /**
- * @private
- */
- updateURL: function() {
- this.scheme = device.scheme || false;
- this.fireEvent('schemeupdate', this, this.scheme);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.device.Simulator', {
- extend: 'Ext.device.device.Abstract'
- });
- /**
- * Provides a cross device way to get information about the device your application is running on. There are 3 different implementations:
- *
- * - Sencha Packager
- * - [PhoneGap](http://docs.phonegap.com/en/1.4.1/phonegap_device_device.md.html)
- * - Simulator
- *
- * ## Examples
- *
- * #### Device Information
- *
- * Getting the device information:
- *
- * Ext.application({
- * name: 'Sencha',
- *
- * // Remember that the Ext.device.Device class *must* be required
- * requires: ['Ext.device.Device'],
- *
- * launch: function() {
- * alert([
- * 'Device name: ' + Ext.device.Device.name,
- * 'Device platform: ' + Ext.device.Device.platform,
- * 'Device UUID: ' + Ext.device.Device.uuid
- * ].join('\n'));
- * }
- * });
- *
- * ### Custom Scheme URLs
- *
- * Using custom scheme URLs to application your application from other applications:
- *
- * Ext.application({
- * name: 'Sencha',
- * requires: ['Ext.device.Device'],
- * launch: function() {
- * if (Ext.device.Device.scheme) {
- * // the application was opened via another application. Do something:
- * alert('Applicaton pened via another application: ' + Ext.device.Device.scheme.url);
- * }
- *
- * // Listen for future changes
- * Ext.device.Device.on('schemeupdate', function(device, scheme) {
- * // the application was launched, closed, and then launched another from another application
- * // this means onReady wont be called again ('cause the application is already running in the
- * // background) - but this event will be fired
- * alert('Applicated reopened via another application: ' + scheme.url);
- * }, this);
- * }
- * });
- *
- * Of course, you must add add the custom URLs you would like to use when packaging your application. You can do this by adding
- * the following code into the `rawConfig` property inside your `package.json` file (Sencha Native Packager configuration file):
- *
- * {
- * ...
- * "rawConfig": "<key>CFBundleURLTypes</key><array><dict><key>CFBundleURLSchemes</key><array><string>sencha</string></array><key>CFBundleURLName</key><string>com.sencha.example</string></dict></array>"
- * ...
- * }
- *
- * You can change the available URL schemes and the application identifier above.
- *
- * You can then test it by packaging and installing the application onto a device/iOS Simulator, opening Safari and typing: `sencha:testing`.
- * The application will launch and it will `alert` the URL you specified.
- *
- * **PLEASE NOTE: This currently only works with the Sencha Native Packager. If you attempt to listen to this event when packaged with
- * PhoneGap or simply in the browser, it will not function.**
- *
- * @mixins Ext.device.device.Abstract
- *
- * @aside guide native_apis
- */
- Ext.define('Ext.device.Device', {
- singleton: true,
- requires: [
- 'Ext.device.Communicator',
- 'Ext.device.device.PhoneGap',
- 'Ext.device.device.Sencha',
- 'Ext.device.device.Simulator'
- ],
- constructor: function() {
- var browserEnv = Ext.browser.is;
- if (browserEnv.WebView) {
- if (browserEnv.PhoneGap) {
- return Ext.create('Ext.device.device.PhoneGap');
- }
- else {
- return Ext.create('Ext.device.device.Sencha');
- }
- }
- else {
- return Ext.create('Ext.device.device.Simulator');
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.geolocation.Abstract', {
- config: {
- /**
- * @cfg {Number} maximumAge
- * This option indicates that the application is willing to accept cached location information whose age
- * is no greater than the specified time in milliseconds. If maximumAge is set to 0, an attempt to retrieve
- * new location information is made immediately.
- */
- maximumAge: 0,
- /**
- * @cfg {Number} frequency The default frequency to get the current position when using {@link Ext.device.Geolocation#watchPosition}.
- */
- frequency: 10000,
- /**
- * @cfg {Boolean} allowHighAccuracy True to allow high accuracy when getting the current position.
- */
- allowHighAccuracy: false,
- /**
- * @cfg {Number} timeout
- * The maximum number of milliseconds allowed to elapse between a location update operation.
- */
- timeout: Infinity
- },
- /**
- * Attempts to get the current position of this device.
- *
- * Ext.device.Geolocation.getCurrentPosition({
- * success: function(position) {
- * console.log(position);
- * },
- * failure: function() {
- * Ext.Msg.alert('Geolocation', 'Something went wrong!');
- * }
- * });
- *
- * *Note:* If you want to watch the current position, you could use {@link Ext.device.Geolocation#watchPosition} instead.
- *
- * @param {Object} config An object which contains the following config options:
- *
- * @param {Function} config.success
- * The function to call when the location of the current device has been received.
- *
- * @param {Object} config.success.position
- *
- * @param {Function} config.failure
- * The function that is called when something goes wrong.
- *
- * @param {Object} config.scope
- * The scope of the `success` and `failure` functions.
- *
- * @param {Number} config.maximumAge
- * The maximum age of a cached location. If you do not enter a value for this, the value of {@link #maximumAge}
- * will be used.
- *
- * @param {Number} config.timeout
- * The timeout for this request. If you do not specify a value, it will default to {@link #timeout}.
- *
- * @param {Boolean} config.allowHighAccuracy
- * True to enable allow accuracy detection of the location of the current device. If you do not specify a value, it will
- * default to {@link #allowHighAccuracy}.
- */
- getCurrentPosition: function(config) {
- var defaultConfig = Ext.device.geolocation.Abstract.prototype.config;
- config = Ext.applyIf(config, {
- maximumAge: defaultConfig.maximumAge,
- frequency: defaultConfig.frequency,
- allowHighAccuracy: defaultConfig.allowHighAccuracy,
- timeout: defaultConfig.timeout
- });
- // <debug>
- if (!config.success) {
- Ext.Logger.warn('You need to specify a `success` function for #getCurrentPosition');
- }
- // </debug>
- return config;
- },
- /**
- * Watches for the current position and calls the callback when successful depending on the specified {@link #frequency}.
- *
- * Ext.device.Geolocation.watchPosition({
- * callback: function(position) {
- * console.log(position);
- * },
- * failure: function() {
- * Ext.Msg.alert('Geolocation', 'Something went wrong!');
- * }
- * });
- *
- * @param {Object} config An object which contains the following config options:
- *
- * @param {Function} config.callback
- * The function to be called when the position has been updated.
- *
- * @param {Function} config.failure
- * The function that is called when something goes wrong.
- *
- * @param {Object} config.scope
- * The scope of the `success` and `failure` functions.
- *
- * @param {Boolean} config.frequency
- * The frequency in which to call the supplied callback. Defaults to {@link #frequency} if you do not specify a value.
- *
- * @param {Boolean} config.allowHighAccuracy
- * True to enable allow accuracy detection of the location of the current device. If you do not specify a value, it will
- * default to {@link #allowHighAccuracy}.
- */
- watchPosition: function(config) {
- var defaultConfig = Ext.device.geolocation.Abstract.prototype.config;
- config = Ext.applyIf(config, {
- maximumAge: defaultConfig.maximumAge,
- frequency: defaultConfig.frequency,
- allowHighAccuracy: defaultConfig.allowHighAccuracy,
- timeout: defaultConfig.timeout
- });
- // <debug>
- if (!config.callback) {
- Ext.Logger.warn('You need to specify a `callback` function for #watchPosition');
- }
- // </debug>
- return config;
- },
- /**
- * If you are currently watching for the current position, this will stop that task.
- */
- clearWatch: function() {}
- });
- /**
- * @private
- */
- Ext.define('Ext.device.geolocation.Sencha', {
- extend: 'Ext.device.geolocation.Abstract',
- getCurrentPosition: function(config) {
- config = this.callParent([config]);
- Ext.apply(config, {
- command: 'Geolocation#getCurrentPosition',
- callbacks: {
- success: config.success,
- failure: config.failure
- }
- });
- Ext.applyIf(config, {
- scope: this
- });
- delete config.success;
- delete config.failure;
- Ext.device.Communicator.send(config);
- return config;
- },
- watchPosition: function(config) {
- config = this.callParent([config]);
- Ext.apply(config, {
- command: 'Geolocation#watchPosition',
- callbacks: {
- success: config.callback,
- failure: config.failure
- }
- });
- Ext.applyIf(config, {
- scope: this
- });
- delete config.callback;
- delete config.failure;
- Ext.device.Communicator.send(config);
- return config;
- },
- clearWatch: function() {
- Ext.device.Communicator.send({
- command: 'Geolocation#clearWatch'
- });
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.geolocation.Simulator', {
- extend: 'Ext.device.geolocation.Abstract',
- requires: ['Ext.util.Geolocation'],
- getCurrentPosition: function(config) {
- config = this.callParent([config]);
- Ext.apply(config, {
- autoUpdate: false,
- listeners: {
- scope: this,
- locationupdate: function(geolocation) {
- if (config.success) {
- config.success.call(config.scope || this, geolocation.position);
- }
- },
- locationerror: function() {
- if (config.failure) {
- config.failure.call(config.scope || this);
- }
- }
- }
- });
- this.geolocation = Ext.create('Ext.util.Geolocation', config);
- this.geolocation.updateLocation();
- return config;
- },
- watchPosition: function(config) {
- config = this.callParent([config]);
- Ext.apply(config, {
- listeners: {
- scope: this,
- locationupdate: function(geolocation) {
- if (config.callback) {
- config.callback.call(config.scope || this, geolocation.position);
- }
- },
- locationerror: function() {
- if (config.failure) {
- config.failure.call(config.scope || this);
- }
- }
- }
- });
- this.geolocation = Ext.create('Ext.util.Geolocation', config);
- return config;
- },
- clearWatch: function() {
- if (this.geolocation) {
- this.geolocation.destroy();
- }
- this.geolocation = null;
- }
- });
- /**
- * Provides access to the native Geolocation API when running on a device. There are three implementations of this API:
- *
- * - Sencha Packager
- * - [PhoneGap](http://docs.phonegap.com/en/1.4.1/phonegap_device_device.md.html)
- * - Browser
- *
- * This class will automatically select the correct implementation depending on the device your application is running on.
- *
- * ## Examples
- *
- * Getting the current location:
- *
- * Ext.device.Geolocation.getCurrentPosition({
- * success: function(position) {
- * console.log(position.coords);
- * },
- * failure: function() {
- * console.log('something went wrong!');
- * }
- * });
- *
- * Watching the current location:
- *
- * Ext.device.Geolocation.watchPosition({
- * frequency: 3000, // Update every 3 seconds
- * callback: function(position) {
- * console.log('Position updated!', position.coords);
- * },
- * failure: function() {
- * console.log('something went wrong!');
- * }
- * });
- *
- * @mixins Ext.device.geolocation.Abstract
- *
- * @aside guide native_apis
- */
- Ext.define('Ext.device.Geolocation', {
- singleton: true,
- requires: [
- 'Ext.device.Communicator',
- 'Ext.device.geolocation.Sencha',
- 'Ext.device.geolocation.Simulator'
- ],
- constructor: function() {
- var browserEnv = Ext.browser.is;
- if (browserEnv.WebView && browserEnv.Sencha) {
- return Ext.create('Ext.device.geolocation.Sencha');
- }
- else {
- return Ext.create('Ext.device.geolocation.Simulator');
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.notification.Abstract', {
- /**
- * A simple way to show a notification.
- *
- * Ext.device.Notification.show({
- * title: 'Verification',
- * message: 'Is your email address is: test@sencha.com',
- * buttons: Ext.MessageBox.OKCANCEL,
- * callback: function(button) {
- * if (button == "ok") {
- * console.log('Verified');
- * } else {
- * console.log('Nope.');
- * }
- * }
- * });
- *
- * @param {Object} config An object which contains the following config options:
- *
- * @param {String} config.title The title of the notification
- *
- * @param {String} config.message The message to be displayed on the notification
- *
- * @param {String/String[]} [config.buttons="OK"]
- * 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.
- * Please not that you should not use more than 2 buttons, as they may not be displayed correct on all devices.
- *
- * @param {Function} config.callback
- * A callback function which is called when the notification is dismissed by clicking on the configured buttons.
- * @param {String} config.callback.buttonId The id of the button pressed, one of: 'ok', 'yes', 'no', 'cancel'.
- *
- * @param {Object} config.scope The scope of the callback function
- */
- show: function(config) {
- if (!config.message) {
- throw('[Ext.device.Notification#show] You passed no message');
- }
- if (!config.buttons) {
- config.buttons = "OK";
- }
- if (!Ext.isArray(config.buttons)) {
- config.buttons = [config.buttons];
- }
- if (!config.scope) {
- config.scope = this;
- }
- return config;
- },
- /**
- * Vibrates the device.
- */
- vibrate: Ext.emptyFn
- });
- /**
- * @private
- */
- Ext.define('Ext.device.notification.PhoneGap', {
- extend: 'Ext.device.notification.Abstract',
- requires: ['Ext.device.Communicator'],
- show: function() {
- var config = this.callParent(arguments),
- buttons = (config.buttons) ? config.buttons.join(',') : null,
- onShowCallback = function(index) {
- if (config.callback) {
- config.callback.apply(config.scope, (config.buttons) ? [config.buttons[index - 1]].toLowerCase() : []);
- }
- };
- // change Ext.MessageBox buttons into normal arrays
- var ln = butons.length;
- if (ln && typeof buttons[0] != "string") {
- var newButtons = [],
- i;
- for (i = 0; i < ln; i++) {
- newButtons.push(buttons[i].text);
- }
- buttons = newButtons;
- }
- navigator.notification.confirm(
- config.message, // message
- onShowCallback, // callback
- config.title, // title
- buttons // array of button names
- );
- },
- vibrate: function() {
- navigator.notification.vibrate(2000);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.notification.Sencha', {
- extend: 'Ext.device.notification.Abstract',
- requires: ['Ext.device.Communicator'],
- show: function() {
- var config = this.callParent(arguments);
- Ext.device.Communicator.send({
- command: 'Notification#show',
- callbacks: {
- callback: config.callback
- },
- scope : config.scope,
- title : config.title,
- message: config.message,
- buttons: config.buttons.join(',') //@todo fix this
- });
- },
- vibrate: function() {
- Ext.device.Communicator.send({
- command: 'Notification#vibrate'
- });
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.notification.Simulator', {
- extend: 'Ext.device.notification.Abstract',
- requires: ['Ext.MessageBox'],
- // @private
- msg: null,
- show: function() {
- var config = this.callParent(arguments),
- buttons = [],
- ln = config.buttons.length,
- button, i, callback, msg;
- //buttons
- for (i = 0; i < ln; i++) {
- button = config.buttons[i];
- if (Ext.isString(button)) {
- button = {
- text: config.buttons[i],
- itemId: config.buttons[i].toLowerCase()
- };
- }
- buttons.push(button);
- }
- this.msg = Ext.create('Ext.MessageBox');
- msg = this.msg;
- callback = function(itemId) {
- if (config.callback) {
- config.callback.apply(config.scope, [itemId]);
- }
- };
- this.msg.show({
- title : config.title,
- message: config.message,
- scope : this.msg,
- buttons: buttons,
- fn : callback
- });
- },
- vibrate: function() {
- //nice animation to fake vibration
- var animation = [
- "@-webkit-keyframes vibrate{",
- " from {",
- " -webkit-transform: rotate(-2deg);",
- " }",
- " to{",
- " -webkit-transform: rotate(2deg);",
- " }",
- "}",
- "body {",
- " -webkit-animation: vibrate 50ms linear 10 alternate;",
- "}"
- ];
- var head = document.getElementsByTagName("head")[0];
- var cssNode = document.createElement('style');
- cssNode.innerHTML = animation.join('\n');
- head.appendChild(cssNode);
- setTimeout(function() {
- head.removeChild(cssNode);
- }, 400);
- }
- });
- /**
- * Provides a cross device way to show notifications. There are three different implementations:
- *
- * - Sencha Packager
- * - PhoneGap
- * - Simulator
- *
- * When this singleton is instantiated, it will automatically use the correct implementation depending on the current device.
- *
- * Both the Sencha Packager and PhoneGap versions will use the native implementations to display the notification. The
- * Simulator implementation will use {@link Ext.MessageBox} for {@link #show} and a simply animation when you call {@link #vibrate}.
- *
- * ## Examples
- *
- * To show a simple notification:
- *
- * Ext.device.Notification.show({
- * title: 'Verification',
- * message: 'Is your email address: test@sencha.com',
- * buttons: Ext.MessageBox.OKCANCEL,
- * callback: function(button) {
- * if (button === "ok") {
- * console.log('Verified');
- * } else {
- * console.log('Nope');
- * }
- * }
- * });
- *
- * To make the device vibrate:
- *
- * Ext.device.Notification.vibrate();
- *
- * @mixins Ext.device.notification.Abstract
- *
- * @aside guide native_apis
- */
- Ext.define('Ext.device.Notification', {
- singleton: true,
- requires: [
- 'Ext.device.Communicator',
- 'Ext.device.notification.PhoneGap',
- 'Ext.device.notification.Sencha',
- 'Ext.device.notification.Simulator'
- ],
- constructor: function() {
- var browserEnv = Ext.browser.is;
- if (browserEnv.WebView) {
- if (browserEnv.PhoneGap) {
- return Ext.create('Ext.device.notification.PhoneGap');
- }
- else {
- return Ext.create('Ext.device.notification.Sencha');
- }
- }
- else {
- return Ext.create('Ext.device.notification.Simulator');
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.orientation.Abstract', {
- extend: 'Ext.EventedBase',
- /**
- * @event orientationchange
- * Fires when the orientation has been changed on this device.
- *
- * Ext.device.Orientation.on({
- * scope: this,
- * orientationchange: function(e) {
- * console.log('Alpha: ', e.alpha);
- * console.log('Beta: ', e.beta);
- * console.log('Gamma: ', e.gamma);
- * }
- * });
- *
- * @param {Object} event The event object
- * @param {Object} event.alpha The alpha value of the orientation event
- * @param {Object} event.beta The beta value of the orientation event
- * @param {Object} event.gamma The gamma value of the orientation event
- */
- onDeviceOrientation: function(e) {
- this.doFireEvent('orientationchange', [e]);
- }
- });
- /**
- * Provides the HTML5 implementation for the orientation API.
- * @private
- */
- Ext.define('Ext.device.orientation.HTML5', {
- extend: 'Ext.device.orientation.Abstract',
- initialize: function() {
- this.onDeviceOrientation = Ext.Function.bind(this.onDeviceOrientation, this);
- window.addEventListener('deviceorientation', this.onDeviceOrientation, true);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.orientation.Sencha', {
- extend: 'Ext.device.orientation.Abstract',
- requires: [
- 'Ext.device.Communicator'
- ],
- /**
- * From the native shell, the callback needs to be invoked infinitely using a timer, ideally 50 times per second.
- * The callback expects one event object argument, the format of which should looks like this:
- *
- * {
- * alpha: 0,
- * beta: 0,
- * gamma: 0
- * }
- *
- * Refer to [Safari DeviceOrientationEvent Class Reference][1] for more details.
- *
- * [1]: http://developer.apple.com/library/safari/#documentation/SafariDOMAdditions/Reference/DeviceOrientationEventClassRef/DeviceOrientationEvent/DeviceOrientationEvent.html
- */
- initialize: function() {
- Ext.device.Communicator.send({
- command: 'Orientation#watch',
- callbacks: {
- callback: this.onDeviceOrientation
- },
- scope: this
- });
- }
- });
- /**
- * This class provides you with a cross platform way of listening to when the the orientation changes on the
- * device your application is running on.
- *
- * The {@link Ext.device.Orientation#orientationchange orientationchange} event gets passes the `alpha`, `beta` and
- * `gamma` values.
- *
- * 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).
- *
- * ## Example
- *
- * To listen to the device orientation, you can do the following:
- *
- * Ext.device.Orientation.on({
- * scope: this,
- * orientationchange: function(e) {
- * console.log('Alpha: ', e.alpha);
- * console.log('Beta: ', e.beta);
- * console.log('Gamma: ', e.gamma);
- * }
- * });
- *
- * @mixins Ext.device.orientation.Abstract
- *
- * @aside guide native_apis
- */
- Ext.define('Ext.device.Orientation', {
- singleton: true,
- requires: [
- 'Ext.device.Communicator',
- 'Ext.device.orientation.HTML5',
- 'Ext.device.orientation.Sencha'
- ],
- constructor: function() {
- var browserEnv = Ext.browser.is;
- if (browserEnv.Sencha) {
- return Ext.create('Ext.device.orientation.Sencha');
- }
- else {
- return Ext.create('Ext.device.orientation.HTML5');
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.purchases.Sencha', {
- /**
- * Checks if the current user is able to make payments.
- *
- * ## Example
- *
- * Ext.device.Purchases.canMakePayments({
- * success: function() {
- * console.log('Yup! :)');
- * },
- * failure: function() {
- * console.log('Nope! :(');
- * }
- * });
- *
- * @param {Object} config
- * @param {Function} config.success
- * @param {Function} config.failure
- * @param {Object} config.scope
- */
- canMakePayments: function(config) {
- if (!config.success) {
- Ext.Logger.error('You must specify a `success` callback for `#canMakePayments` to work.');
- return false;
- }
- if (!config.failure) {
- Ext.Logger.error('You must specify a `failure` callback for `#canMakePayments` to work.');
- return false;
- }
- Ext.device.Communicator.send({
- command: 'Purchase#canMakePayments',
- callbacks: {
- success: config.success,
- failure: config.failure
- },
- scope: config.scope || this
- });
- },
- /**
- * Returns a {@link Ext.data.Store} instance of all the available products.
- *
- * ## Example
- *
- * Ext.device.Purchases.getProducts({
- * success: function(store) {
- * console.log('Got the store! You have ' + store.getCount() + ' products.');
- * },
- * failure: function() {
- * console.log('Oops. Looks like something went wrong.');
- * }
- * });
- *
- * @param {Object} config
- * @param {Function} config.success
- * @param {Ext.data.Store} config.success.store A store of products available to purchase.
- * @param {Function} config.failure
- * @param {Object} config.scope
- */
- getProducts: function(config) {
- if (!config.success) {
- Ext.Logger.error('You must specify a `success` callback for `#getProducts` to work.');
- return false;
- }
- if (!config.failure) {
- Ext.Logger.error('You must specify a `failure` callback for `#getProducts` to work.');
- return false;
- }
- Ext.device.Communicator.send({
- command: 'Purchase#getProducts',
- callbacks: {
- success: function(products) {
- var store = Ext.create('Ext.data.Store', {
- model: 'Ext.device.Purchases.Product',
- data: products
- });
- config.success.call(config.scope || this, store);
- },
- failure: config.failure
- },
- scope: config.scope || this
- });
- },
- /**
- * Returns all purchases ever made by this user.
- * @param {Object} config
- * @param {Function} config.success
- * @param {Array[]} config.success.purchases
- * @param {Function} config.failure
- * @param {Object} config.scope
- */
- getPurchases: function(config) {
- if (!config.success) {
- Ext.Logger.error('You must specify a `success` callback for `#getPurchases` to work.');
- return false;
- }
- if (!config.failure) {
- Ext.Logger.error('You must specify a `failure` callback for `#getPurchases` to work.');
- return false;
- }
- Ext.device.Communicator.send({
- command: 'Purchase#getPurchases',
- callbacks: {
- success: function(purchases) {
- var array = [],
- ln = purchases.length,
- i;
- for (i = 0; i < ln; i++) {
- array.push({
- productIdentifier: purchases[i]
- });
- }
- var store = Ext.create('Ext.data.Store', {
- model: 'Ext.device.Purchases.Purchase',
- data: array
- });
- config.success.call(config.scope || this, store);
- },
- failure: function() {
- config.failure.call(config.scope || this);
- }
- },
- scope: config.scope || this
- });
- },
- /**
- * Returns all purchases that are currently pending.
- * @param {Object} config
- * @param {Function} config.success
- * @param {Ext.data.Store} config.success.purchases
- * @param {Function} config.failure
- * @param {Object} config.scope
- */
- getPendingPurchases: function(config) {
- if (!config.success) {
- Ext.Logger.error('You must specify a `success` callback for `#getPendingPurchases` to work.');
- return false;
- }
- if (!config.failure) {
- Ext.Logger.error('You must specify a `failure` callback for `#getPendingPurchases` to work.');
- return false;
- }
- Ext.device.Communicator.send({
- command: 'Purchase#getPendingPurchases',
- callbacks: {
- success: function(purchases) {
- var array = [],
- ln = purchases.length,
- i;
- for (i = 0; i < ln; i++) {
- array.push({
- productIdentifier: purchases[i],
- state: 'pending'
- });
- }
- var store = Ext.create('Ext.data.Store', {
- model: 'Ext.device.Purchases.Purchase',
- data: array
- });
- config.success.call(config.scope || this, store);
- },
- failure: function() {
- config.failure.call(config.scope || this);
- }
- },
- scope: config.scope || this
- });
- }
- }, function() {
- /**
- * The product model class which is uses when fetching available products using {@link Ext.device.Purchases#getProducts}.
- */
- Ext.define('Ext.device.Purchases.Product', {
- extend: 'Ext.data.Model',
- config: {
- fields: [
- 'localizeTitle',
- 'price',
- 'priceLocale',
- 'localizedDescription',
- 'productIdentifier'
- ]
- },
- /**
- * Will attempt to purchase this product.
- *
- * ## Example
- *
- * product.purchase({
- * success: function() {
- * console.log(product.get('title') + ' purchased!');
- * },
- * failure: function() {
- * console.log('Something went wrong while trying to purchase ' + product.get('title'));
- * }
- * });
- *
- * @param {Object} config
- * @param {Ext.data.Model/String} config.product
- * @param {Function} config.success
- * @param {Function} config.failure
- */
- purchase: function(config) {
- if (!config.success) {
- Ext.Logger.error('You must specify a `success` callback for `#product` to work.');
- return false;
- }
- if (!config.failure) {
- Ext.Logger.error('You must specify a `failure` callback for `#product` to work.');
- return false;
- }
- Ext.device.Communicator.send({
- command: 'Purchase#purchase',
- callbacks: {
- success: config.success,
- failure: config.failure
- },
- identifier: this.get('productIdentifier'),
- scope: config.scope || this
- });
- }
- });
- /**
- *
- */
- Ext.define('Ext.device.Purchases.Purchase', {
- extend: 'Ext.data.Model',
- config: {
- fields: [
- 'productIdentifier',
- 'state'
- ]
- },
- /**
- * Attempts to mark this purchase as complete
- * @param {Object} config
- * @param {Function} config.success
- * @param {Function} config.failure
- * @param {Object} config.scope
- */
- complete: function(config) {
- var me = this;
- if (!config.success) {
- Ext.Logger.error('You must specify a `success` callback for `#complete` to work.');
- return false;
- }
- if (!config.failure) {
- Ext.Logger.error('You must specify a `failure` callback for `#complete` to work.');
- return false;
- }
- if (this.get('state') != "pending") {
- config.failure.call(config.scope || this, "purchase is not pending");
- }
- Ext.device.Communicator.send({
- command: 'Purchase#completePurchase',
- identifier: me.get('productIdentifier'),
- callbacks: {
- success: function() {
- me.set('state', 'complete');
- config.success.call(config.scope || this);
- },
- failure: function() {
- me.set('state', 'pending');
- config.failure.call(config.scope || this);
- }
- },
- scope: config.scope || this
- });
- }
- });
- });
- /**
- *
- *
- * @mixins Ext.device.purchases.Sencha
- *
- * @aside guide native_apis
- */
- Ext.define('Ext.device.Purchases', {
- singleton: true,
- requires: [
- 'Ext.device.purchases.Sencha'
- ],
- constructor: function() {
- return Ext.create('Ext.device.purchases.Sencha');
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.push.Abstract', {
- /**
- * @property
- * Notification type: alert.
- */
- ALERT: 1,
- /**
- * @property
- * Notification type: badge.
- */
- BADGE: 2,
- /**
- * @property
- * Notification type: sound.
- */
- SOUND: 4,
- /**
- * @method getInitialConfig
- * @hide
- */
- /**
- * Registers a push notification.
- *
- * Ext.device.Push.register({
- * type: Ext.device.Push.ALERT|Ext.device.Push.BADGE|Ext.device.Push.SOUND,
- * success: function(token) {
- * console.log('# Push notification registration successful:');
- * console.log(' token: ' + token);
- * },
- * failure: function(error) {
- * console.log('# Push notification registration unsuccessful:');
- * console.log(' error: ' + error);
- * },
- * received: function(notifications) {
- * console.log('# Push notification received:');
- * console.log(' ' + JSON.stringify(notifications));
- * }
- * });
- *
- * @param {Object} config
- * The configuration for to pass when registering this push notification service.
- *
- * @param {Number} config.type
- * The type(s) of notifications to enable. Available options are:
- *
- * - {@link Ext.device.Push#ALERT}
- * - {@link Ext.device.Push#BADGE}
- * - {@link Ext.device.Push#SOUND}
- *
- * **Usage**
- *
- * Enable alerts and badges:
- *
- * Ext.device.Push.register({
- * type: Ext.device.Push.ALERT|Ext.device.Push.BADGE
- * // ...
- * });
- *
- * Enable alerts, badges and sounds:
- *
- * Ext.device.Push.register({
- * type: Ext.device.Push.ALERT|Ext.device.Push.BADGE|Ext.device.Push.SOUND
- * // ...
- * });
- *
- * Enable only sounds:
- *
- * Ext.device.Push.register({
- * type: Ext.device.Push.SOUND
- * // ...
- * });
- *
- * @param {Function} config.success
- * The callback to be called when registration is complete.
- *
- * @param {String} config.success.token
- * A unique token for this push notification service.
- *
- * @param {Function} config.failure
- * The callback to be called when registration fails.
- *
- * @param {String} config.failure.error
- * The error message.
- *
- * @param {Function} config.received
- * The callback to be called when a push notification is received on this device.
- *
- * @param {Object} config.received.notifications
- * The notifications that have been received.
- */
- register: function(config) {
- var me = this;
- if (!config.received) {
- Ext.Logger.error('Failed to pass a received callback. This is required.');
- }
- if (!config.type) {
- Ext.Logger.error('Failed to pass a type. This is required.');
- }
- return {
- success: function(token) {
- me.onSuccess(token, config.success, config.scope || me);
- },
- failure: function(error) {
- me.onFailure(error, config.failure, config.scope || me);
- },
- received: function(notifications) {
- me.onReceived(notifications, config.received, config.scope || me);
- },
- type: config.type
- };
- },
- onSuccess: function(token, callback, scope) {
- if (callback) {
- callback.call(scope, token);
- }
- },
- onFailure: function(error, callback, scope) {
- if (callback) {
- callback.call(scope, error);
- }
- },
- onReceived: function(notifications, callback, scope) {
- if (callback) {
- callback.call(scope, notifications);
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.device.push.Sencha', {
- extend: 'Ext.device.push.Abstract',
- register: function() {
- var config = this.callParent(arguments);
- Ext.apply(config, {
- command: 'PushNotification#Register',
- callbacks: {
- success: config.success,
- failure: config.failure,
- received: config.received
- },
- type: config.type
- });
- Ext.device.Communicator.send(config);
- }
- });
- /**
- * Provides a way to send push notifications to a device. Currently only available on iOS.
- *
- * # Example
- *
- * Ext.device.Push.register({
- * type: Ext.device.Push.ALERT|Ext.device.Push.BADGE|Ext.device.Push.SOUND,
- * success: function(token) {
- * console.log('# Push notification registration successful:');
- * console.log(' token: ' + token);
- * },
- * failure: function(error) {
- * console.log('# Push notification registration unsuccessful:');
- * console.log(' error: ' + error);
- * },
- * received: function(notifications) {
- * console.log('# Push notification received:');
- * console.log(' ' + JSON.stringify(notifications));
- * }
- * });
- *
- * @mixins Ext.device.push.Abstract
- *
- * @aside guide native_apis
- */
- Ext.define('Ext.device.Push', {
- singleton: true,
- requires: [
- 'Ext.device.Communicator',
- 'Ext.device.push.Sencha'
- ],
- constructor: function() {
- var browserEnv = Ext.browser.is;
- if (browserEnv.WebView) {
- if (!browserEnv.PhoneGap) {
- return Ext.create('Ext.device.push.Sencha');
- }
- else {
- return Ext.create('Ext.device.push.Abstract');
- }
- }
- else {
- return Ext.create('Ext.device.push.Abstract');
- }
- }
- });
- /**
- * @class Ext.direct.Event
- * A base class for all Ext.direct events. An event is
- * created after some kind of interaction with the server.
- * The event class is essentially just a data structure
- * to hold a Direct response.
- */
- Ext.define('Ext.direct.Event', {
- alias: 'direct.event',
- requires: ['Ext.direct.Manager'],
- config: {
- status: true,
- /**
- * @cfg {Object} data The raw data for this event.
- * @accessor
- */
- data: null,
- /**
- * @cfg {String} name The name of this Event.
- * @accessor
- */
- name: 'event',
- xhr: null,
- code: null,
- message: '',
- result: null,
- transaction: null
- },
- constructor: function(config) {
- this.initConfig(config)
- }
- });
- /**
- * @class Ext.direct.RemotingEvent
- * An event that is fired when data is received from a
- * {@link Ext.direct.RemotingProvider}. Contains a method to the
- * related transaction for the direct request, see {@link #getTransaction}
- */
- Ext.define('Ext.direct.RemotingEvent', {
- extend: 'Ext.direct.Event',
- alias: 'direct.rpc',
- config: {
- name: 'remoting',
- tid: null,
- transaction: null
- },
- /**
- * Get the transaction associated with this event.
- * @return {Ext.direct.Transaction} The transaction
- */
- getTransaction: function() {
- return this._transaction || Ext.direct.Manager.getTransaction(this.getTid());
- }
- });
- /**
- * @class Ext.direct.ExceptionEvent
- * An event that is fired when an exception is received from a {@link Ext.direct.RemotingProvider}
- */
- Ext.define('Ext.direct.ExceptionEvent', {
- extend: 'Ext.direct.RemotingEvent',
- alias: 'direct.exception',
- config: {
- status: false,
- name: 'exception',
- error: null
- }
- });
- /**
- * Ext.direct.Provider is an abstract class meant to be extended.
- *
- * For example Ext JS implements the following subclasses:
- *
- * Provider
- * |
- * +---{@link Ext.direct.JsonProvider JsonProvider}
- * |
- * +---{@link Ext.direct.PollingProvider PollingProvider}
- * |
- * +---{@link Ext.direct.RemotingProvider RemotingProvider}
- *
- * @abstract
- */
- Ext.define('Ext.direct.Provider', {
- alias: 'direct.provider',
- mixins: {
- observable: 'Ext.mixin.Observable'
- },
- config: {
- /**
- * @cfg {String} id
- * The unique id of the provider (defaults to an auto-assigned id).
- * You should assign an id if you need to be able to access the provider later and you do
- * not have an object reference available, for example:
- *
- * Ext.direct.Manager.addProvider({
- * type: 'polling',
- * url: 'php/poll.php',
- * id: 'poll-provider'
- * });
- * var p = {@link Ext.direct.Manager}.{@link Ext.direct.Manager#getProvider getProvider}('poll-provider');
- * p.disconnect();
- *
- */
- id: undefined
- },
- /**
- * @event connect
- * Fires when the Provider connects to the server-side
- * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
- */
- /**
- * @event disconnect
- * Fires when the Provider disconnects from the server-side
- * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
- */
- /**
- * @event data
- * Fires when the Provider receives data from the server-side
- * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
- * @param {Ext.direct.Event} e The Ext.direct.Event type that occurred.
- */
- /**
- * @event exception
- * Fires when the Provider receives an exception from the server-side
- */
- constructor : function(config){
- this.initConfig(config);
- },
- applyId: function(id) {
- if (id === undefined) {
- id = this.getUniqueId();
- }
- return id;
- },
- /**
- * Returns whether or not the server-side is currently connected.
- * Abstract method for subclasses to implement.
- * @return {Boolean}
- */
- isConnected: function() {
- return false;
- },
- /**
- * Abstract methods for subclasses to implement.
- * @method
- */
- connect: Ext.emptyFn,
- /**
- * Abstract methods for subclasses to implement.
- * @method
- */
- disconnect: Ext.emptyFn
- });
- /**
- * @class Ext.direct.JsonProvider
- *
- * A base provider for communicating using JSON. This is an abstract class
- * and should not be instanced directly.
- * @abstract
- */
- Ext.define('Ext.direct.JsonProvider', {
- extend: 'Ext.direct.Provider',
- alias: 'direct.jsonprovider',
- uses: ['Ext.direct.ExceptionEvent'],
- /**
- * Parse the JSON response.
- * @private
- * @param {Object} response The XHR response object.
- * @return {Object} The data in the response.
- */
- parseResponse: function(response) {
- if (!Ext.isEmpty(response.responseText)) {
- if (Ext.isObject(response.responseText)) {
- return response.responseText;
- }
- return Ext.decode(response.responseText);
- }
- return null;
- },
- /**
- * Creates a set of events based on the XHR response.
- * @private
- * @param {Object} response The XHR response.
- * @return {Ext.direct.Event[]} An array of {@link Ext.direct.Event} objects.
- */
- createEvents: function(response) {
- var data = null,
- events = [],
- i = 0,
- ln, event;
- try {
- data = this.parseResponse(response);
- } catch(e) {
- event = Ext.create('Ext.direct.ExceptionEvent', {
- data: e,
- xhr: response,
- code: Ext.direct.Manager.exceptions.PARSE,
- message: 'Error parsing json response: \n\n ' + data
- });
- return [event];
- }
- if (Ext.isArray(data)) {
- for (ln = data.length; i < ln; ++i) {
- events.push(this.createEvent(data[i]));
- }
- } else {
- events.push(this.createEvent(data));
- }
- return events;
- },
- /**
- * Create an event from a response object.
- * @param {Object} response The XHR response object.
- * @return {Ext.direct.Event} The event.
- */
- createEvent: function(response) {
- return Ext.create('direct.' + response.type, response);
- }
- });
- /**
- * The DelayedTask class provides a convenient way to "buffer" the execution of a method,
- * performing `setTimeout` where a new timeout cancels the old timeout. When called, the
- * task will wait the specified time period before executing. If during that time period,
- * the task is called again, the original call will be canceled. This continues so that
- * the function is only called a single time for each iteration.
- *
- * This method is especially useful for things like detecting whether a user has finished
- * typing in a text field. An example would be performing validation on a keypress. You can
- * use this class to buffer the keypress events for a certain number of milliseconds, and
- * perform only if they stop for that amount of time.
- *
- * Using {@link Ext.util.DelayedTask} is very simple:
- *
- * //create the delayed task instance with our callback
- * var task = Ext.create('Ext.util.DelayedTask', function() {
- * console.log('callback!');
- * });
- *
- * task.delay(1500); //the callback function will now be called after 1500ms
- *
- * task.cancel(); //the callback function will never be called now, unless we call delay() again
- *
- * ## Example
- *
- * @example
- * //create a textfield where we can listen to text
- * var field = Ext.create('Ext.field.Text', {
- * xtype: 'textfield',
- * label: 'Length: 0'
- * });
- *
- * //add the textfield into a fieldset
- * Ext.Viewport.add({
- * xtype: 'formpanel',
- * items: [{
- * xtype: 'fieldset',
- * items: [field],
- * instructions: 'Type into the field and watch the count go up after 500ms.'
- * }]
- * });
- *
- * //create our delayed task with a function that returns the fields length as the fields label
- * var task = Ext.create('Ext.util.DelayedTask', function() {
- * field.setLabel('Length: ' + field.getValue().length);
- * });
- *
- * // Wait 500ms before calling our function. If the user presses another key
- * // during that 500ms, it will be canceled and we'll wait another 500ms.
- * field.on('keyup', function() {
- * task.delay(500);
- * });
- *
- * @constructor
- * The parameters to this constructor serve as defaults and are not required.
- * @param {Function} fn The default function to call.
- * @param {Object} scope The default scope (The `this` reference) in which the function is called. If
- * not specified, `this` will refer to the browser window.
- * @param {Array} args The default Array of arguments.
- */
- Ext.define('Ext.util.DelayedTask', {
- config: {
- interval: null,
- delay: null,
- fn: null,
- scope: null,
- args: null
- },
- constructor: function(fn, scope, args) {
- var config = {
- fn: fn,
- scope: scope,
- args: args
- };
- this.initConfig(config);
- },
- /**
- * Cancels any pending timeout and queues a new one.
- * @param {Number} delay The milliseconds to delay
- * @param {Function} newFn Overrides the original function passed when instantiated.
- * @param {Object} newScope Overrides the original `scope` passed when instantiated. Remember that if no scope
- * is specified, `this` will refer to the browser window.
- * @param {Array} newArgs Overrides the original `args` passed when instantiated.
- */
- delay: function(delay, newFn, newScope, newArgs) {
- var me = this;
- //cancel any existing queued functions
- me.cancel();
-
- //set all the new configurations
- me.setConfig({
- delay: delay,
- fn: newFn,
- scope: newScope,
- args: newArgs
- });
- //create the callback method for this delayed task
- var call = function() {
- me.getFn().apply(me.getScope(), me.getArgs() || []);
- me.cancel();
- };
- me.setInterval(setInterval(call, me.getDelay()));
- },
- /**
- * Cancel the last queued timeout
- */
- cancel: function() {
- this.setInterval(null);
- },
- /**
- * @private
- * Clears the old interval
- */
- updateInterval: function(newInterval, oldInterval) {
- if (oldInterval) {
- clearInterval(oldInterval);
- }
- },
- /**
- * @private
- * Changes the value into an array if it isn't one.
- */
- applyArgs: function(config) {
- if (!Ext.isArray(config)) {
- config = [config];
- }
- return config;
- }
- });
- /**
- * @class Ext.direct.PollingProvider
- *
- * Provides for repetitive polling of the server at distinct {@link #interval intervals}.
- * The initial request for data originates from the client, and then is responded to by the
- * server.
- *
- * All configurations for the PollingProvider should be generated by the server-side
- * API portion of the Ext.Direct stack.
- *
- * An instance of PollingProvider may be created directly via the new keyword or by simply
- * specifying `type = 'polling'`. For example:
- *
- * var pollA = Ext.create('Ext.direct.PollingProvider', {
- * type:'polling',
- * url: 'php/pollA.php'
- * });
- *
- * Ext.direct.Manager.addProvider(pollA);
- * pollA.disconnect();
- *
- * Ext.direct.Manager.addProvider({
- * type:'polling',
- * url: 'php/pollB.php',
- * id: 'pollB-provider'
- * });
- *
- * var pollB = Ext.direct.Manager.getProvider('pollB-provider');
- */
- Ext.define('Ext.direct.PollingProvider', {
- extend: 'Ext.direct.JsonProvider',
- alias: 'direct.pollingprovider',
- uses: ['Ext.direct.ExceptionEvent'],
- requires: ['Ext.Ajax', 'Ext.util.DelayedTask'],
- config: {
- /**
- * @cfg {Number} interval
- * How often to poll the server-side, in milliseconds.
- */
- interval: 3000,
- /**
- * @cfg {Object} baseParams
- * An object containing properties which are to be sent as parameters on every polling request.
- */
- baseParams: null,
- /**
- * @cfg {String/Function} url
- * The url which the PollingProvider should contact with each request. This can also be
- * an imported {@link Ext.Direct} method which will accept the `{@link #baseParams}` as its only argument.
- */
- url: null
- },
- /**
- * @event beforepoll
- * Fired immediately before a poll takes place, an event handler can return `false`
- * in order to cancel the poll.
- * @param {Ext.direct.PollingProvider} this
- */
- /**
- * @event poll
- * This event has not yet been implemented.
- * @param {Ext.direct.PollingProvider} this
- */
- /**
- * @inheritdoc
- */
- isConnected: function() {
- return !!this.pollTask;
- },
- /**
- * Connect to the server-side and begin the polling process. To handle each
- * response subscribe to the `data` event.
- */
- connect: function() {
- var me = this,
- url = me.getUrl(),
- baseParams = me.getBaseParams();
- if (url && !me.pollTask) {
- me.pollTask = setInterval(function() {
- if (me.fireEvent('beforepoll', me) !== false) {
- if (Ext.isFunction(url)) {
- url(baseParams);
- } else {
- Ext.Ajax.request({
- url: url,
- callback: me.onData,
- scope: me,
- params: baseParams
- });
- }
- }
- }, me.getInterval());
- me.fireEvent('connect', me);
- } else if (!url) {
- //<debug>
- Ext.Error.raise('Error initializing PollingProvider, no url configured.');
- //</debug>
- }
- },
- /**
- * Disconnect from the server-side and stop the polling process. The `disconnect`
- * event will be fired on a successful disconnect.
- */
- disconnect: function() {
- var me = this;
- if (me.pollTask) {
- clearInterval(me.pollTask);
- delete me.pollTask;
- me.fireEvent('disconnect', me);
- }
- },
- // @private
- onData: function(opt, success, response) {
- var me = this,
- i = 0,
- len,
- events;
- if (success) {
- events = me.createEvents(response);
- for (len = events.length; i < len; ++i) {
- me.fireEvent('data', me, events[i]);
- }
- } else {
- me.fireEvent('data', me, Ext.create('Ext.direct.ExceptionEvent', {
- data: null,
- code: Ext.direct.Manager.exceptions.TRANSPORT,
- message: 'Unable to connect to the server.',
- xhr: response
- }));
- }
- }
- });
- /**
- * Small utility class used internally to represent a Direct method.
- * @class Ext.direct.RemotingMethod
- * @private
- */
- Ext.define('Ext.direct.RemotingMethod', {
- config: {
- name: null,
- params: null,
- formHandler: null,
- len: null,
- ordered: true
- },
- constructor: function(config) {
- this.initConfig(config);
- },
- applyParams: function(params) {
- if (Ext.isNumber(params)) {
- this.setLen(params);
- } else if (Ext.isArray(params)) {
- this.setOrdered(false);
- var ln = params.length,
- ret = [],
- i, param, name;
- for (i = 0; i < ln; i++) {
- param = params[i];
- name = Ext.isObject(param) ? param.name : param;
- ret.push(name);
- }
- return ret;
- }
- },
- getArgs: function(params, paramOrder, paramsAsHash) {
- var args = [],
- i, ln;
- if (this.getOrdered()) {
- if (this.getLen() > 0) {
- // If a paramOrder was specified, add the params into the argument list in that order.
- if (paramOrder) {
- for (i = 0, ln = paramOrder.length; i < ln; i++) {
- args.push(params[paramOrder[i]]);
- }
- } else if (paramsAsHash) {
- // If paramsAsHash was specified, add all the params as a single object argument.
- args.push(params);
- }
- }
- } else {
- args.push(params);
- }
- return args;
- },
- /**
- * Takes the arguments for the Direct function and splits the arguments
- * from the scope and the callback.
- * @param {Array} args The arguments passed to the direct call
- * @return {Object} An object with 3 properties, args, callback & scope.
- */
- getCallData: function(args) {
- var me = this,
- data = null,
- len = me.getLen(),
- params = me.getParams(),
- callback, scope, name;
- if (me.getOrdered()) {
- callback = args[len];
- scope = args[len + 1];
- if (len !== 0) {
- data = args.slice(0, len);
- }
- } else {
- data = Ext.apply({}, args[0]);
- callback = args[1];
- scope = args[2];
- for (name in data) {
- if (data.hasOwnProperty(name)) {
- if (!Ext.Array.contains(params, name)) {
- delete data[name];
- }
- }
- }
- }
- return {
- data: data,
- callback: callback,
- scope: scope
- };
- }
- });
- /**
- * Supporting Class for Ext.Direct (not intended to be used directly).
- */
- Ext.define('Ext.direct.Transaction', {
- alias: 'direct.transaction',
- alternateClassName: 'Ext.Direct.Transaction',
- statics: {
- TRANSACTION_ID: 0
- },
- config: {
- id: undefined,
- provider: null,
- retryCount: 0,
- args: null,
- action: null,
- method: null,
- data: null,
- callback: null,
- form: null
- },
- constructor: function(config) {
- this.initConfig(config);
- },
- applyId: function(id) {
- if (id === undefined) {
- id = ++this.self.TRANSACTION_ID;
- }
- return id;
- },
- updateId: function(id) {
- this.id = this.tid = id;
- },
- getTid: function() {
- return this.tid;
- },
- send: function(){
- this.getProvider().queueTransaction(this);
- },
- retry: function(){
- this.setRetryCount(this.getRetryCount() + 1);
- this.send();
- }
- });
- /**
- * @class Ext.direct.RemotingProvider
- *
- * The {@link Ext.direct.RemotingProvider RemotingProvider} exposes access to
- * server side methods on the client (a remote procedure call (RPC) type of
- * connection where the client can initiate a procedure on the server).
- *
- * This allows for code to be organized in a fashion that is maintainable,
- * while providing a clear path between client and server, something that is
- * not always apparent when using URLs.
- *
- * To accomplish this the server-side needs to describe what classes and methods
- * are available on the client-side. This configuration will typically be
- * outputted by the server-side Ext.Direct stack when the API description is built.
- */
- Ext.define('Ext.direct.RemotingProvider', {
- alias: 'direct.remotingprovider',
- extend: 'Ext.direct.JsonProvider',
- requires: [
- 'Ext.util.MixedCollection',
- 'Ext.util.DelayedTask',
- 'Ext.direct.Transaction',
- 'Ext.direct.RemotingMethod'
- ],
- config: {
- /**
- * @cfg {String/Object} namespace
- * Namespace for the Remoting Provider (defaults to the browser global scope of _window_).
- * Explicitly specify the namespace Object, or specify a String to have a
- * {@link Ext#namespace namespace created} implicitly.
- */
- namespace: undefined,
- /**
- * @cfg {String} url (required) The url to connect to the {@link Ext.direct.Manager} server-side router.
- */
- url: null,
- /**
- * @cfg {String} enableUrlEncode
- * Specify which param will hold the arguments for the method.
- */
- enableUrlEncode: null,
- /**
- * @cfg {Number/Boolean} enableBuffer
- *
- * `true` or `false` to enable or disable combining of method
- * calls. If a number is specified this is the amount of time in milliseconds
- * to wait before sending a batched request.
- *
- * Calls which are received within the specified timeframe will be
- * concatenated together and sent in a single request, optimizing the
- * application by reducing the amount of round trips that have to be made
- * to the server.
- */
- enableBuffer: 10,
- /**
- * @cfg {Number} maxRetries
- * Number of times to re-attempt delivery on failure of a call.
- */
- maxRetries: 1,
- /**
- * @cfg {Number} timeout
- * The timeout to use for each request.
- */
- timeout: undefined,
- /**
- * @cfg {Object} actions
- * Object literal defining the server side actions and methods. For example, if
- * the Provider is configured with:
- *
- * actions: { // each property within the 'actions' object represents a server side Class
- * // array of methods within each server side Class to be stubbed out on client
- * TestAction: [{
- * name: "doEcho",
- * len: 1
- * }, {
- * "name": "multiply", // name of method
- * "len": 2 // The number of parameters that will be used to create an
- * // array of data to send to the server side function.
- * // Ensure the server sends back a Number, not a String.
- * }, {
- * name: "doForm",
- * formHandler: true, // direct the client to use specialized form handling method
- * len: 1
- * }]
- * }
- *
- * __Note:__ A Store is not required, a server method can be called at any time.
- * In the following example a **client side** handler is used to call the
- * server side method "multiply" in the server-side "TestAction" Class:
- *
- * TestAction.multiply(
- * 2, 4, // pass two arguments to server, so specify len=2
- * // callback function after the server is called
- * // result: the result returned by the server
- * // e: Ext.direct.RemotingEvent object
- * function(result, e) {
- * var t = e.getTransaction();
- * var action = t.action; // server side Class called
- * var method = t.method; // server side method called
- * if (e.getStatus()) {
- * var answer = Ext.encode(result); // 8
- * } else {
- * var msg = e.getMessage(); // failure message
- * }
- * }
- * );
- *
- * In the example above, the server side "multiply" function will be passed two
- * arguments (2 and 4). The "multiply" method should return the value 8 which will be
- * available as the `result` in the example above.
- */
- actions: {}
- },
- /**
- * @event beforecall
- * Fires immediately before the client-side sends off the RPC call.
- * By returning `false` from an event handler you can prevent the call from
- * executing.
- * @param {Ext.direct.RemotingProvider} provider
- * @param {Ext.direct.Transaction} transaction
- * @param {Object} meta The meta data.
- */
- /**
- * @event call
- * Fires immediately after the request to the server-side is sent. This does
- * NOT fire after the response has come back from the call.
- * @param {Ext.direct.RemotingProvider} provider
- * @param {Ext.direct.Transaction} transaction
- * @param {Object} meta The meta data.
- */
- constructor : function(config) {
- var me = this;
- me.callParent(arguments);
- me.transactions = Ext.create('Ext.util.Collection', function(item) {
- return item.getId();
- });
- me.callBuffer = [];
- },
- applyNamespace: function(namespace) {
- if (Ext.isString(namespace)) {
- return Ext.ns(namespace);
- }
- return namespace || window;
- },
- /**
- * Initialize the API
- * @private
- */
- initAPI : function() {
- var actions = this.getActions(),
- namespace = this.getNamespace(),
- action, cls, methods,
- i, ln, method;
- for (action in actions) {
- if (actions.hasOwnProperty(action)) {
- cls = namespace[action];
- if (!cls) {
- cls = namespace[action] = {};
- }
- methods = actions[action];
- for (i = 0, ln = methods.length; i < ln; ++i) {
- method = Ext.create('Ext.direct.RemotingMethod', methods[i]);
- cls[method.getName()] = this.createHandler(action, method);
- }
- }
- }
- },
- /**
- * Create a handler function for a direct call.
- * @private
- * @param {String} action The action the call is for.
- * @param {Object} method The details of the method.
- * @return {Function} A JavaScript function that will kick off the call.
- */
- createHandler : function(action, method) {
- var me = this,
- handler;
- if (!method.getFormHandler()) {
- handler = function() {
- me.configureRequest(action, method, Array.prototype.slice.call(arguments, 0));
- };
- } else {
- handler = function(form, callback, scope) {
- me.configureFormRequest(action, method, form, callback, scope);
- };
- }
- handler.directCfg = {
- action: action,
- method: method
- };
- return handler;
- },
- // @inheritdoc
- isConnected: function() {
- return !!this.connected;
- },
- // @inheritdoc
- connect: function() {
- var me = this;
- if (me.getUrl()) {
- me.initAPI();
- me.connected = true;
- me.fireEvent('connect', me);
- } else {
- //<debug>
- Ext.Error.raise('Error initializing RemotingProvider, no url configured.');
- //</debug>
- }
- },
- // @inheritdoc
- disconnect: function() {
- var me = this;
- if (me.connected) {
- me.connected = false;
- me.fireEvent('disconnect', me);
- }
- },
- /**
- * Run any callbacks related to the transaction.
- * @private
- * @param {Ext.direct.Transaction} transaction The transaction
- * @param {Ext.direct.Event} event The event
- */
- runCallback: function(transaction, event) {
- var success = !!event.getStatus(),
- functionName = success ? 'success' : 'failure',
- callback = transaction && transaction.getCallback(),
- result;
- if (callback) {
- // this doesnt make any sense. why do we have both result and data?
- // result = Ext.isDefined(event.getResult()) ? event.result : event.data;
- result = event.getResult();
- if (Ext.isFunction(callback)) {
- callback(result, event, success);
- } else {
- Ext.callback(callback[functionName], callback.scope, [result, event, success]);
- Ext.callback(callback.callback, callback.scope, [result, event, success]);
- }
- }
- },
- /**
- * React to the AJAX request being completed.
- * @private
- */
- onData: function(options, success, response) {
- var me = this,
- i = 0,
- ln, events, event,
- transaction, transactions;
- if (success) {
- events = me.createEvents(response);
- for (ln = events.length; i < ln; ++i) {
- event = events[i];
- transaction = me.getTransaction(event);
- me.fireEvent('data', me, event);
- if (transaction) {
- me.runCallback(transaction, event, true);
- Ext.direct.Manager.removeTransaction(transaction);
- }
- }
- } else {
- transactions = [].concat(options.transaction);
- for (ln = transactions.length; i < ln; ++i) {
- transaction = me.getTransaction(transactions[i]);
- if (transaction && transaction.getRetryCount() < me.getMaxRetries()) {
- transaction.retry();
- } else {
- event = Ext.create('Ext.direct.ExceptionEvent', {
- data: null,
- transaction: transaction,
- code: Ext.direct.Manager.exceptions.TRANSPORT,
- message: 'Unable to connect to the server.',
- xhr: response
- });
- me.fireEvent('data', me, event);
- if (transaction) {
- me.runCallback(transaction, event, false);
- Ext.direct.Manager.removeTransaction(transaction);
- }
- }
- }
- }
- },
- /**
- * Get transaction from XHR options.
- * @private
- * @param {Object} options The options sent to the AJAX request.
- * @return {Ext.direct.Transaction/null} The transaction, `null` if not found.
- */
- getTransaction: function(options) {
- return options && options.getTid ? Ext.direct.Manager.getTransaction(options.getTid()) : null;
- },
- /**
- * Configure a direct request.
- * @private
- * @param {String} action The action being executed.
- * @param {Object} method The being executed.
- */
- configureRequest: function(action, method, args) {
- var me = this,
- callData = method.getCallData(args),
- data = callData.data,
- callback = callData.callback,
- scope = callData.scope,
- transaction;
- transaction = Ext.create('Ext.direct.Transaction', {
- provider: me,
- args: args,
- action: action,
- method: method.getName(),
- data: data,
- callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback
- });
- if (me.fireEvent('beforecall', me, transaction, method) !== false) {
- Ext.direct.Manager.addTransaction(transaction);
- me.queueTransaction(transaction);
- me.fireEvent('call', me, transaction, method);
- }
- },
- /**
- * Gets the AJAX call info for a transaction.
- * @private
- * @param {Ext.direct.Transaction} transaction The transaction.
- * @return {Object} The call params.
- */
- getCallData: function(transaction) {
- return {
- action: transaction.getAction(),
- method: transaction.getMethod(),
- data: transaction.getData(),
- type: 'rpc',
- tid: transaction.getId()
- };
- },
- /**
- * Sends a request to the server.
- * @private
- * @param {Object/Array} data The data to send.
- */
- sendRequest : function(data) {
- var me = this,
- request = {
- url: me.getUrl(),
- callback: me.onData,
- scope: me,
- transaction: data,
- timeout: me.getTimeout()
- }, callData,
- enableUrlEncode = me.getEnableUrlEncode(),
- i = 0,
- ln, params;
- if (Ext.isArray(data)) {
- callData = [];
- for (ln = data.length; i < ln; ++i) {
- callData.push(me.getCallData(data[i]));
- }
- } else {
- callData = me.getCallData(data);
- }
- if (enableUrlEncode) {
- params = {};
- params[Ext.isString(enableUrlEncode) ? enableUrlEncode : 'data'] = Ext.encode(callData);
- request.params = params;
- } else {
- request.jsonData = callData;
- }
- Ext.Ajax.request(request);
- },
- /**
- * Add a new transaction to the queue.
- * @private
- * @param {Ext.direct.Transaction} transaction The transaction.
- */
- queueTransaction: function(transaction) {
- var me = this,
- enableBuffer = me.getEnableBuffer();
- if (transaction.getForm()) {
- me.sendFormRequest(transaction);
- return;
- }
- me.callBuffer.push(transaction);
- if (enableBuffer) {
- if (!me.callTask) {
- me.callTask = Ext.create('Ext.util.DelayedTask', me.combineAndSend, me);
- }
- me.callTask.delay(Ext.isNumber(enableBuffer) ? enableBuffer : 10);
- } else {
- me.combineAndSend();
- }
- },
- /**
- * Combine any buffered requests and send them off.
- * @private
- */
- combineAndSend : function() {
- var buffer = this.callBuffer,
- ln = buffer.length;
- if (ln > 0) {
- this.sendRequest(ln == 1 ? buffer[0] : buffer);
- this.callBuffer = [];
- }
- }
- // /**
- // * Configure a form submission request.
- // * @private
- // * @param {String} action The action being executed.
- // * @param {Object} method The method being executed.
- // * @param {HTMLElement} form The form being submitted.
- // * @param {Function} callback (optional) A callback to run after the form submits.
- // * @param {Object} scope (optional) A scope to execute the callback in.
- // */
- // configureFormRequest : function(action, method, form, callback, scope){
- // var me = this,
- // transaction = new Ext.direct.Transaction({
- // provider: me,
- // action: action,
- // method: method.name,
- // args: [form, callback, scope],
- // callback: scope && Ext.isFunction(callback) ? Ext.Function.bind(callback, scope) : callback,
- // isForm: true
- // }),
- // isUpload,
- // params;
- //
- // if (me.fireEvent('beforecall', me, transaction, method) !== false) {
- // Ext.direct.Manager.addTransaction(transaction);
- // isUpload = String(form.getAttribute("enctype")).toLowerCase() == 'multipart/form-data';
- //
- // params = {
- // extTID: transaction.id,
- // extAction: action,
- // extMethod: method.name,
- // extType: 'rpc',
- // extUpload: String(isUpload)
- // };
- //
- // // change made from typeof callback check to callback.params
- // // to support addl param passing in DirectSubmit EAC 6/2
- // Ext.apply(transaction, {
- // form: Ext.getDom(form),
- // isUpload: isUpload,
- // params: callback && Ext.isObject(callback.params) ? Ext.apply(params, callback.params) : params
- // });
- // me.fireEvent('call', me, transaction, method);
- // me.sendFormRequest(transaction);
- // }
- // },
- //
- // /**
- // * Sends a form request
- // * @private
- // * @param {Ext.direct.Transaction} transaction The transaction to send
- // */
- // sendFormRequest: function(transaction){
- // Ext.Ajax.request({
- // url: this.url,
- // params: transaction.params,
- // callback: this.onData,
- // scope: this,
- // form: transaction.form,
- // isUpload: transaction.isUpload,
- // transaction: transaction
- // });
- // }
- });
- /**
- * This class encapsulates a _collection_ of DOM elements, providing methods to filter members, or to perform collective
- * actions upon the whole set.
- *
- * Although they are not listed, this class supports all of the methods of {@link Ext.dom.Element}. The methods from
- * these classes will be performed on all the elements in this collection.
- *
- * All methods return _this_ and can be chained.
- *
- * Usage:
- *
- * var els = Ext.select("#some-el div.some-class", true);
- * // or select directly from an existing element
- * var el = Ext.get('some-el');
- * el.select('div.some-class', true);
- *
- * els.setWidth(100); // all elements become 100 width
- * els.hide(true); // all elements fade out and hide
- * // or
- * els.setWidth(100).hide(true);
- */
- Ext.define('Ext.dom.CompositeElement', {
- alternateClassName: 'Ext.CompositeElement',
- extend: 'Ext.dom.CompositeElementLite',
- // @private
- getElement: function(el) {
- // In this case just return it, since we already have a reference to it
- return el;
- },
- // @private
- transformElement: function(el) {
- return Ext.get(el);
- }
- }, function() {
- Ext.dom.Element.select = function(selector, unique, root) {
- var elements;
- if (typeof selector == "string") {
- elements = Ext.dom.Element.selectorFunction(selector, root);
- }
- else if (selector.length !== undefined) {
- elements = selector;
- }
- else {
- //<debug>
- throw new Error("[Ext.select] Invalid selector specified: " + selector);
- //</debug>
- }
- return (unique === true) ? new Ext.CompositeElement(elements) : new Ext.CompositeElementLite(elements);
- };
- });
- // Using @mixins to include all members of Ext.event.Touch
- // into here to keep documentation simpler
- /**
- * @mixins Ext.event.Touch
- *
- * Just as {@link Ext.dom.Element} wraps around a native DOM node, {@link Ext.event.Event} wraps the browser's native
- * event-object normalizing cross-browser differences such as mechanisms to stop event-propagation along with a method
- * to prevent default actions from taking place.
- *
- * Here is a simple example of how you use it:
- *
- * @example preview
- * Ext.Viewport.add({
- * layout: 'fit',
- * items: [
- * {
- * docked: 'top',
- * xtype: 'toolbar',
- * title: 'Ext.event.Event example!'
- * },
- * {
- * id: 'logger',
- * styleHtmlContent: true,
- * html: 'Tap somewhere!',
- * padding: 5
- * }
- * ]
- * });
- *
- * Ext.Viewport.element.on({
- * tap: function(e, node) {
- * var string = '';
- *
- * string += 'You tapped at: <strong>{ x: ' + e.pageX + ', y: ' + e.pageY + ' }</strong> <i>(e.pageX & e.pageY)</i>';
- * string += '<hr />';
- * string += 'The HTMLElement you tapped has the className of: <strong>' + e.target.className + '</strong> <i>(e.target)</i>';
- * string += '<hr />';
- * string += 'The HTMLElement which has the listener has a className of: <strong>' + e.getTarget().className + '</strong> <i>(e.getTarget())</i>';
- *
- * Ext.getCmp('logger').setHtml(string);
- * }
- * });
- *
- * ## Recognizers
- *
- * Sencha Touch includes a bunch of default event recognizers to know when a user taps, swipes, etc.
- *
- * For a full list of default recognizers, and more information, please view the {@link Ext.event.recognizer.Recognizer} documentation.
- */
- Ext.define('Ext.event.Event', {
- alternateClassName: 'Ext.EventObject',
- isStopped: false,
- set: function(name, value) {
- if (arguments.length === 1 && typeof name != 'string') {
- var info = name;
- for (name in info) {
- if (info.hasOwnProperty(name)) {
- this[name] = info[name];
- }
- }
- }
- else {
- this[name] = info[name];
- }
- },
- /**
- * Stop the event (`preventDefault` and `{@link #stopPropagation}`).
- * @chainable
- */
- stopEvent: function() {
- return this.stopPropagation();
- },
- /**
- * Cancels bubbling of the event.
- * @chainable
- */
- stopPropagation: function() {
- this.isStopped = true;
- return this;
- }
- });
- /**
- * @private
- * @extends Object
- * DOM event. This class really extends {@link Ext.event.Event}, but for documentation
- * purposes it's members are listed inside {@link Ext.event.Event}.
- */
- Ext.define('Ext.event.Dom', {
- extend: 'Ext.event.Event',
- constructor: function(event) {
- var target = event.target,
- touches;
- if (target && target.nodeType !== 1) {
- target = target.parentNode;
- }
- touches = event.changedTouches;
- if (touches) {
- touches = touches[0];
- this.pageX = touches.pageX;
- this.pageY = touches.pageY;
- }
- else {
- this.pageX = event.pageX;
- this.pageY = event.pageY;
- }
- this.browserEvent = this.event = event;
- this.target = this.delegatedTarget = target;
- this.type = event.type;
- this.timeStamp = this.time = event.timeStamp;
- if (typeof this.time != 'number') {
- this.time = new Date(this.time).getTime();
- }
- return this;
- },
- /**
- * @property {Number} distance
- * The distance of the event.
- *
- * **This is only available when the event type is `swipe` and `pinch`.**
- */
- /**
- * @property {HTMLElement} target
- * The target HTMLElement for this event. For example; if you are listening to a tap event and you tap on a `<div>` element,
- * this will return that `<div>` element.
- */
- /**
- * @property {Number} pageX The browsers x coordinate of the event.
- */
- /**
- * @property {Number} pageY The browsers y coordinate of the event.
- */
- stopEvent: function() {
- this.preventDefault();
- return this.callParent();
- },
- /**
- * Prevents the browsers default handling of the event.
- */
- preventDefault: function() {
- this.browserEvent.preventDefault();
- },
- /**
- * Gets the x coordinate of the event.
- * @deprecated 2.0 Please use {@link #pageX} property directly.
- * @return {Number}
- */
- getPageX: function() {
- return this.browserEvent.pageX;
- },
- /**
- * Gets the y coordinate of the event.
- * @deprecated 2.0 Please use {@link #pageX} property directly.
- * @return {Number}
- */
- getPageY: function() {
- return this.browserEvent.pageY;
- },
- /**
- * Gets the X and Y coordinates of the event.
- * @deprecated 2.0 Please use the {@link #pageX} and {@link #pageY} properties directly.
- * @return {Array}
- */
- getXY: function() {
- if (!this.xy) {
- this.xy = [this.getPageX(), this.getPageY()];
- }
- return this.xy;
- },
- /**
- * Gets the target for the event. Unlike {@link #target}, this returns the main element for your event. So if you are
- * listening to a tap event on Ext.Viewport.element, and you tap on an inner element of Ext.Viewport.element, this will
- * return Ext.Viewport.element.
- *
- * If you want the element you tapped on, then use {@link #target}.
- *
- * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
- * @param {Number/Mixed} [maxDepth=10||document.body] (optional) The max depth to
- * search as a number or element (defaults to 10 || document.body)
- * @param {Boolean} returnEl (optional) `true` to return a Ext.Element object instead of DOM node.
- * @return {HTMLElement}
- */
- getTarget: function(selector, maxDepth, returnEl) {
- if (arguments.length === 0) {
- return this.delegatedTarget;
- }
- return selector ? Ext.fly(this.target).findParent(selector, maxDepth, returnEl) : (returnEl ? Ext.get(this.target) : this.target);
- },
- /**
- * Returns the time of the event.
- * @return {Date}
- */
- getTime: function() {
- return this.time;
- },
- setDelegatedTarget: function(target) {
- this.delegatedTarget = target;
- },
- makeUnpreventable: function() {
- this.browserEvent.preventDefault = Ext.emptyFn;
- }
- });
- /**
- * @private
- * Touch event.
- */
- Ext.define('Ext.event.Touch', {
- extend: 'Ext.event.Dom',
- requires: [
- 'Ext.util.Point'
- ],
- constructor: function(event, info) {
- if (info) {
- this.set(info);
- }
- this.touchesMap = {};
- this.changedTouches = this.cloneTouches(event.changedTouches);
- this.touches = this.cloneTouches(event.touches);
- this.targetTouches = this.cloneTouches(event.targetTouches);
- return this.callParent([event]);
- },
- clone: function() {
- return new this.self(this);
- },
- setTargets: function(targetsMap) {
- this.doSetTargets(this.changedTouches, targetsMap);
- this.doSetTargets(this.touches, targetsMap);
- this.doSetTargets(this.targetTouches, targetsMap);
- },
- doSetTargets: function(touches, targetsMap) {
- var i, ln, touch, identifier, targets;
- for (i = 0,ln = touches.length; i < ln; i++) {
- touch = touches[i];
- identifier = touch.identifier;
- targets = targetsMap[identifier];
- if (targets) {
- touch.targets = targets;
- }
- }
- },
- cloneTouches: function(touches) {
- var map = this.touchesMap,
- clone = [],
- lastIdentifier = null,
- i, ln, touch, identifier;
- for (i = 0,ln = touches.length; i < ln; i++) {
- touch = touches[i];
- identifier = touch.identifier;
- // A quick fix for a bug found in Bada 1.0 where all touches have
- // idenfitier of 0
- if (lastIdentifier !== null && identifier === lastIdentifier) {
- identifier++;
- }
- lastIdentifier = identifier;
- if (!map[identifier]) {
- map[identifier] = {
- pageX: touch.pageX,
- pageY: touch.pageY,
- identifier: identifier,
- target: touch.target,
- timeStamp: touch.timeStamp,
- point: Ext.util.Point.fromTouch(touch),
- targets: touch.targets
- };
- }
- clone[i] = map[identifier];
- }
- return clone;
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.event.publisher.ComponentDelegation', {
- extend: 'Ext.event.publisher.Publisher',
- requires: [
- 'Ext.Component',
- 'Ext.ComponentQuery'
- ],
- targetType: 'component',
- optimizedSelectorRegex: /^#([\w\-]+)((?:[\s]*)>(?:[\s]*)|(?:\s*))([\w\-]+)$/i,
- handledEvents: ['*'],
- getSubscribers: function(eventName, createIfNotExist) {
- var subscribers = this.subscribers,
- eventSubscribers = subscribers[eventName];
- if (!eventSubscribers && createIfNotExist) {
- eventSubscribers = subscribers[eventName] = {
- type: {
- $length: 0
- },
- selector: [],
- $length: 0
- }
- }
- return eventSubscribers;
- },
- subscribe: function(target, eventName) {
- // Ignore id-only selectors since they are already handled
- if (this.idSelectorRegex.test(target)) {
- return false;
- }
- var optimizedSelector = target.match(this.optimizedSelectorRegex),
- subscribers = this.getSubscribers(eventName, true),
- typeSubscribers = subscribers.type,
- selectorSubscribers = subscribers.selector,
- id, isDescendant, type, map, subMap;
- if (optimizedSelector !== null) {
- id = optimizedSelector[1];
- isDescendant = optimizedSelector[2].indexOf('>') === -1;
- type = optimizedSelector[3];
- map = typeSubscribers[type];
- if (!map) {
- typeSubscribers[type] = map = {
- descendents: {
- $length: 0
- },
- children: {
- $length: 0
- },
- $length: 0
- }
- }
- subMap = isDescendant ? map.descendents : map.children;
- if (subMap.hasOwnProperty(id)) {
- subMap[id]++;
- return true;
- }
- subMap[id] = 1;
- subMap.$length++;
- map.$length++;
- typeSubscribers.$length++;
- }
- else {
- if (selectorSubscribers.hasOwnProperty(target)) {
- selectorSubscribers[target]++;
- return true;
- }
- selectorSubscribers[target] = 1;
- selectorSubscribers.push(target);
- }
- subscribers.$length++;
- return true;
- },
- unsubscribe: function(target, eventName, all) {
- var subscribers = this.getSubscribers(eventName);
- if (!subscribers) {
- return false;
- }
- var match = target.match(this.optimizedSelectorRegex),
- typeSubscribers = subscribers.type,
- selectorSubscribers = subscribers.selector,
- id, isDescendant, type, map, subMap;
- all = Boolean(all);
- if (match !== null) {
- id = match[1];
- isDescendant = match[2].indexOf('>') === -1;
- type = match[3];
- map = typeSubscribers[type];
- if (!map) {
- return true;
- }
- subMap = isDescendant ? map.descendents : map.children;
- if (!subMap.hasOwnProperty(id) || (!all && --subMap[id] > 0)) {
- return true;
- }
- delete subMap[id];
- subMap.$length--;
- map.$length--;
- typeSubscribers.$length--;
- }
- else {
- if (!selectorSubscribers.hasOwnProperty(target) || (!all && --selectorSubscribers[target] > 0)) {
- return true;
- }
- delete selectorSubscribers[target];
- Ext.Array.remove(selectorSubscribers, target);
- }
- if (--subscribers.$length === 0) {
- delete this.subscribers[eventName];
- }
- return true;
- },
- notify: function(target, eventName) {
- var subscribers = this.getSubscribers(eventName),
- id, component;
- if (!subscribers || subscribers.$length === 0) {
- return false;
- }
- id = target.substr(1);
- component = Ext.ComponentManager.get(id);
- if (component) {
- this.dispatcher.doAddListener(this.targetType, target, eventName, 'publish', this, {
- args: [eventName, component]
- }, 'before');
- }
- },
- matchesSelector: function(component, selector) {
- return Ext.ComponentQuery.is(component, selector);
- },
- dispatch: function(target, eventName, args, connectedController) {
- this.dispatcher.doDispatchEvent(this.targetType, target, eventName, args, null, connectedController);
- },
- publish: function(eventName, component) {
- var subscribers = this.getSubscribers(eventName);
- if (!subscribers) {
- return;
- }
- var eventController = arguments[arguments.length - 1],
- typeSubscribers = subscribers.type,
- selectorSubscribers = subscribers.selector,
- args = Array.prototype.slice.call(arguments, 2, -2),
- types = component.xtypesChain,
- descendentsSubscribers, childrenSubscribers,
- parentId, ancestorIds, ancestorId, parentComponent,
- selector,
- i, ln, type, j, subLn;
- for (i = 0, ln = types.length; i < ln; i++) {
- type = types[i];
- subscribers = typeSubscribers[type];
- if (subscribers && subscribers.$length > 0) {
- descendentsSubscribers = subscribers.descendents;
- if (descendentsSubscribers.$length > 0) {
- if (!ancestorIds) {
- ancestorIds = component.getAncestorIds();
- }
- for (j = 0, subLn = ancestorIds.length; j < subLn; j++) {
- ancestorId = ancestorIds[j];
- if (descendentsSubscribers.hasOwnProperty(ancestorId)) {
- this.dispatch('#' + ancestorId + ' ' + type, eventName, args, eventController);
- }
- }
- }
- childrenSubscribers = subscribers.children;
- if (childrenSubscribers.$length > 0) {
- if (!parentId) {
- if (ancestorIds) {
- parentId = ancestorIds[0];
- }
- else {
- parentComponent = component.getParent();
- if (parentComponent) {
- parentId = parentComponent.getId();
- }
- }
- }
- if (parentId) {
- if (childrenSubscribers.hasOwnProperty(parentId)) {
- this.dispatch('#' + parentId + ' > ' + type, eventName, args, eventController);
- }
- }
- }
- }
- }
- ln = selectorSubscribers.length;
- if (ln > 0) {
- for (i = 0; i < ln; i++) {
- selector = selectorSubscribers[i];
- if (this.matchesSelector(component, selector)) {
- this.dispatch(selector, eventName, args, eventController);
- }
- }
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.event.publisher.ComponentPaint', {
- extend: 'Ext.event.publisher.Publisher',
- targetType: 'component',
- handledEvents: ['erased'],
- eventNames: {
- painted: 'painted',
- erased: 'erased'
- },
- constructor: function() {
- this.callParent(arguments);
- this.hiddenQueue = {};
- this.renderedQueue = {};
- },
- getSubscribers: function(eventName, createIfNotExist) {
- var subscribers = this.subscribers;
- if (!subscribers.hasOwnProperty(eventName)) {
- if (!createIfNotExist) {
- return null;
- }
- subscribers[eventName] = {
- $length: 0
- };
- }
- return subscribers[eventName];
- },
- setDispatcher: function(dispatcher) {
- var targetType = this.targetType;
- dispatcher.doAddListener(targetType, '*', 'renderedchange', 'onBeforeComponentRenderedChange', this, null, 'before');
- dispatcher.doAddListener(targetType, '*', 'hiddenchange', 'onBeforeComponentHiddenChange', this, null, 'before');
- dispatcher.doAddListener(targetType, '*', 'renderedchange', 'onComponentRenderedChange', this, null, 'after');
- dispatcher.doAddListener(targetType, '*', 'hiddenchange', 'onComponentHiddenChange', this, null, 'after');
- return this.callParent(arguments);
- },
- subscribe: function(target, eventName) {
- var match = target.match(this.idSelectorRegex),
- subscribers,
- id;
- if (!match) {
- return false;
- }
- id = match[1];
- subscribers = this.getSubscribers(eventName, true);
- if (subscribers.hasOwnProperty(id)) {
- subscribers[id]++;
- return true;
- }
- subscribers[id] = 1;
- subscribers.$length++;
- return true;
- },
- unsubscribe: function(target, eventName, all) {
- var match = target.match(this.idSelectorRegex),
- subscribers,
- id;
- if (!match || !(subscribers = this.getSubscribers(eventName))) {
- return false;
- }
- id = match[1];
- if (!subscribers.hasOwnProperty(id) || (!all && --subscribers[id] > 0)) {
- return true;
- }
- delete subscribers[id];
- if (--subscribers.$length === 0) {
- delete this.subscribers[eventName];
- }
- return true;
- },
- onBeforeComponentRenderedChange: function(container, component, rendered) {
- var eventNames = this.eventNames,
- eventName = rendered ? eventNames.painted : eventNames.erased,
- subscribers = this.getSubscribers(eventName),
- queue;
- if (subscribers && subscribers.$length > 0) {
- this.renderedQueue[component.getId()] = queue = [];
- this.publish(subscribers, component, eventName, queue);
- }
- },
- onBeforeComponentHiddenChange: function(component, hidden) {
- var eventNames = this.eventNames,
- eventName = hidden ? eventNames.erased : eventNames.painted,
- subscribers = this.getSubscribers(eventName),
- queue;
- if (subscribers && subscribers.$length > 0) {
- this.hiddenQueue[component.getId()] = queue = [];
- this.publish(subscribers, component, eventName, queue);
- }
- },
- onComponentRenderedChange: function(container, component) {
- var renderedQueue = this.renderedQueue,
- id = component.getId(),
- queue;
- if (!renderedQueue.hasOwnProperty(id)) {
- return;
- }
- queue = renderedQueue[id];
- delete renderedQueue[id];
- if (queue.length > 0) {
- this.dispatchQueue(queue);
- }
- },
- onComponentHiddenChange: function(component) {
- var hiddenQueue = this.hiddenQueue,
- id = component.getId(),
- queue;
- if (!hiddenQueue.hasOwnProperty(id)) {
- return;
- }
- queue = hiddenQueue[id];
- delete hiddenQueue[id];
- if (queue.length > 0) {
- this.dispatchQueue(queue);
- }
- },
- dispatchQueue: function(dispatchingQueue) {
- var dispatcher = this.dispatcher,
- targetType = this.targetType,
- eventNames = this.eventNames,
- queue = dispatchingQueue.slice(),
- ln = queue.length,
- i, item, component, eventName, isPainted;
- dispatchingQueue.length = 0;
- if (ln > 0) {
- for (i = 0; i < ln; i++) {
- item = queue[i];
- component = item.component;
- eventName = item.eventName;
- isPainted = component.isPainted();
- if ((eventName === eventNames.painted && isPainted) || eventName === eventNames.erased && !isPainted) {
- dispatcher.doDispatchEvent(targetType, '#' + item.id, eventName, [component]);
- }
- }
- queue.length = 0;
- }
- },
- publish: function(subscribers, component, eventName, dispatchingQueue) {
- var id = component.getId(),
- needsDispatching = false,
- eventNames, items, i, ln, isPainted;
- if (subscribers[id]) {
- eventNames = this.eventNames;
- isPainted = component.isPainted();
- if ((eventName === eventNames.painted && !isPainted) || eventName === eventNames.erased && isPainted) {
- needsDispatching = true;
- }
- else {
- return this;
- }
- }
- if (component.isContainer) {
- items = component.getItems().items;
- for (i = 0,ln = items.length; i < ln; i++) {
- this.publish(subscribers, items[i], eventName, dispatchingQueue);
- }
- }
- else if (component.isDecorator) {
- this.publish(subscribers, component.getComponent(), eventName, dispatchingQueue);
- }
- if (needsDispatching) {
- dispatchingQueue.push({
- id: id,
- eventName: eventName,
- component: component
- });
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.event.publisher.ComponentSize', {
- extend: 'Ext.event.publisher.Publisher',
- requires: [
- 'Ext.ComponentManager'
- ],
- targetType: 'component',
- handledEvents: ['resize', 'innerresize'],
- constructor: function() {
- this.callParent(arguments);
- this.sizeMonitors = {};
- },
- getSubscribers: function(target, createIfNotExist) {
- var subscribers = this.subscribers;
- if (!subscribers.hasOwnProperty(target)) {
- if (!createIfNotExist) {
- return null;
- }
- subscribers[target] = {
- $length: 0
- };
- }
- return subscribers[target];
- },
- subscribe: function(target, eventName) {
- var match = target.match(this.idSelectorRegex),
- sizeMonitors = this.sizeMonitors,
- dispatcher = this.dispatcher,
- targetType = this.targetType,
- subscribers, component, id;
- if (!match) {
- return false;
- }
- id = match[1];
- subscribers = this.getSubscribers(target, true);
- subscribers.$length++;
- if (subscribers.hasOwnProperty(eventName)) {
- subscribers[eventName]++;
- return true;
- }
- if (subscribers.$length === 1) {
- dispatcher.addListener(targetType, target, 'painted', 'onComponentPainted', this, null, 'before');
- }
- component = Ext.ComponentManager.get(id);
- //<debug error>
- if (!component) {
- Ext.Logger.error("Adding a listener to the 'resize' event of a non-existing component");
- }
- //</debug>
- if (!sizeMonitors[target]) {
- sizeMonitors[target] = {};
- }
- sizeMonitors[target][eventName] = new Ext.util.SizeMonitor({
- element: eventName === 'resize' ? component.element : component.innerElement,
- callback: this.onComponentSizeChange,
- scope: this,
- args: [this, target, eventName]
- });
- subscribers[eventName] = 1;
- return true;
- },
- unsubscribe: function(target, eventName, all) {
- var match = target.match(this.idSelectorRegex),
- dispatcher = this.dispatcher,
- targetType = this.targetType,
- sizeMonitors = this.sizeMonitors,
- subscribers,
- id;
- if (!match || !(subscribers = this.getSubscribers(target))) {
- return false;
- }
- id = match[1];
- if (!subscribers.hasOwnProperty(eventName) || (!all && --subscribers[eventName] > 0)) {
- return true;
- }
- delete subscribers[eventName];
- sizeMonitors[target][eventName].destroy();
- delete sizeMonitors[target][eventName];
- if (--subscribers.$length === 0) {
- delete sizeMonitors[target];
- delete this.subscribers[target];
- dispatcher.removeListener(targetType, target, 'painted', 'onComponentPainted', this, 'before');
- }
- return true;
- },
- onComponentPainted: function(component) {
- var target = component.getObservableId(),
- sizeMonitors = this.sizeMonitors[target];
- if (sizeMonitors.resize) {
- sizeMonitors.resize.refresh();
- }
- if (sizeMonitors.innerresize) {
- sizeMonitors.innerresize.refresh();
- }
- },
- onComponentSizeChange: function(component, observableId, eventName) {
- this.dispatcher.doDispatchEvent(this.targetType, observableId, eventName, [component]);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.event.publisher.Dom', {
- extend: 'Ext.event.publisher.Publisher',
- requires: [
- 'Ext.env.Browser',
- 'Ext.Element',
- 'Ext.event.Dom'
- ],
- targetType: 'element',
- idOrClassSelectorRegex: /^([#|\.])([\w\-]+)$/,
- handledEvents: ['click', 'focus', 'blur', 'paste', 'input',
- 'mousemove', 'mousedown', 'mouseup', 'mouseover', 'mouseout',
- 'keyup', 'keydown', 'keypress', 'submit',
- 'transitionend', 'animationstart', 'animationend'],
- classNameSplitRegex: /\s+/,
- SELECTOR_ALL: '*',
- constructor: function() {
- var eventNames = this.getHandledEvents(),
- eventNameMap = {},
- i, ln, eventName, vendorEventName;
- this.doBubbleEventsMap = {
- 'click': true,
- 'submit': true,
- 'mousedown': true,
- 'mousemove': true,
- 'mouseup': true,
- 'mouseover': true,
- 'mouseout': true,
- 'transitionend': true
- };
- this.onEvent = Ext.Function.bind(this.onEvent, this);
- for (i = 0,ln = eventNames.length; i < ln; i++) {
- eventName = eventNames[i];
- vendorEventName = this.getVendorEventName(eventName);
- eventNameMap[vendorEventName] = eventName;
- this.attachListener(vendorEventName);
- }
- this.eventNameMap = eventNameMap;
- return this.callParent();
- },
- getSubscribers: function(eventName) {
- var subscribers = this.subscribers,
- eventSubscribers = subscribers[eventName];
- if (!eventSubscribers) {
- eventSubscribers = subscribers[eventName] = {
- id: {
- $length: 0
- },
- className: {
- $length: 0
- },
- selector: [],
- all: 0,
- $length: 0
- }
- }
- return eventSubscribers;
- },
- getVendorEventName: function(eventName) {
- if (eventName === 'transitionend') {
- eventName = Ext.browser.getVendorProperyName('transitionEnd');
- }
- else if (eventName === 'animationstart') {
- eventName = Ext.browser.getVendorProperyName('animationStart');
- }
- else if (eventName === 'animationend') {
- eventName = Ext.browser.getVendorProperyName('animationEnd');
- }
- return eventName;
- },
- attachListener: function(eventName) {
- document.addEventListener(eventName, this.onEvent, !this.doesEventBubble(eventName));
- return this;
- },
- removeListener: function(eventName) {
- document.removeEventListener(eventName, this.onEvent, !this.doesEventBubble(eventName));
- return this;
- },
- doesEventBubble: function(eventName) {
- return !!this.doBubbleEventsMap[eventName];
- },
- subscribe: function(target, eventName) {
- if (!this.handles(eventName)) {
- return false;
- }
- var idOrClassSelectorMatch = target.match(this.idOrClassSelectorRegex),
- subscribers = this.getSubscribers(eventName),
- idSubscribers = subscribers.id,
- classNameSubscribers = subscribers.className,
- selectorSubscribers = subscribers.selector,
- type, value;
- if (idOrClassSelectorMatch !== null) {
- type = idOrClassSelectorMatch[1];
- value = idOrClassSelectorMatch[2];
- if (type === '#') {
- if (idSubscribers.hasOwnProperty(value)) {
- idSubscribers[value]++;
- return true;
- }
- idSubscribers[value] = 1;
- idSubscribers.$length++;
- }
- else {
- if (classNameSubscribers.hasOwnProperty(value)) {
- classNameSubscribers[value]++;
- return true;
- }
- classNameSubscribers[value] = 1;
- classNameSubscribers.$length++;
- }
- }
- else {
- if (target === this.SELECTOR_ALL) {
- subscribers.all++;
- }
- else {
- if (selectorSubscribers.hasOwnProperty(target)) {
- selectorSubscribers[target]++;
- return true;
- }
- selectorSubscribers[target] = 1;
- selectorSubscribers.push(target);
- }
- }
- subscribers.$length++;
- return true;
- },
- unsubscribe: function(target, eventName, all) {
- if (!this.handles(eventName)) {
- return false;
- }
- var idOrClassSelectorMatch = target.match(this.idOrClassSelectorRegex),
- subscribers = this.getSubscribers(eventName),
- idSubscribers = subscribers.id,
- classNameSubscribers = subscribers.className,
- selectorSubscribers = subscribers.selector,
- type, value;
- all = Boolean(all);
- if (idOrClassSelectorMatch !== null) {
- type = idOrClassSelectorMatch[1];
- value = idOrClassSelectorMatch[2];
- if (type === '#') {
- if (!idSubscribers.hasOwnProperty(value) || (!all && --idSubscribers[value] > 0)) {
- return true;
- }
- delete idSubscribers[value];
- idSubscribers.$length--;
- }
- else {
- if (!classNameSubscribers.hasOwnProperty(value) || (!all && --classNameSubscribers[value] > 0)) {
- return true;
- }
- delete classNameSubscribers[value];
- classNameSubscribers.$length--;
- }
- }
- else {
- if (target === this.SELECTOR_ALL) {
- if (all) {
- subscribers.all = 0;
- }
- else {
- subscribers.all--;
- }
- }
- else {
- if (!selectorSubscribers.hasOwnProperty(target) || (!all && --selectorSubscribers[target] > 0)) {
- return true;
- }
- delete selectorSubscribers[target];
- Ext.Array.remove(selectorSubscribers, target);
- }
- }
- subscribers.$length--;
- return true;
- },
- getElementTarget: function(target) {
- if (target.nodeType !== 1) {
- target = target.parentNode;
- if (!target || target.nodeType !== 1) {
- return null;
- }
- }
- return target;
- },
- getBubblingTargets: function(target) {
- var targets = [];
- if (!target) {
- return targets;
- }
- do {
- targets[targets.length] = target;
- target = target.parentNode;
- } while (target && target.nodeType === 1);
- return targets;
- },
- dispatch: function(target, eventName, args) {
- args.push(args[0].target);
- this.callParent(arguments);
- },
- publish: function(eventName, targets, event) {
- var subscribers = this.getSubscribers(eventName),
- wildcardSubscribers;
- if (subscribers.$length === 0 || !this.doPublish(subscribers, eventName, targets, event)) {
- wildcardSubscribers = this.getSubscribers('*');
- if (wildcardSubscribers.$length > 0) {
- this.doPublish(wildcardSubscribers, eventName, targets, event);
- }
- }
- return this;
- },
- doPublish: function(subscribers, eventName, targets, event) {
- var idSubscribers = subscribers.id,
- classNameSubscribers = subscribers.className,
- selectorSubscribers = subscribers.selector,
- hasIdSubscribers = idSubscribers.$length > 0,
- hasClassNameSubscribers = classNameSubscribers.$length > 0,
- hasSelectorSubscribers = selectorSubscribers.length > 0,
- hasAllSubscribers = subscribers.all > 0,
- isClassNameHandled = {},
- args = [event],
- hasDispatched = false,
- classNameSplitRegex = this.classNameSplitRegex,
- i, ln, j, subLn, target, id, className, classNames, selector;
- for (i = 0,ln = targets.length; i < ln; i++) {
- target = targets[i];
- event.setDelegatedTarget(target);
- if (hasIdSubscribers) {
- id = target.id;
- if (id) {
- if (idSubscribers.hasOwnProperty(id)) {
- hasDispatched = true;
- this.dispatch('#' + id, eventName, args);
- }
- }
- }
- if (hasClassNameSubscribers) {
- className = target.className;
- if (className) {
- classNames = className.split(classNameSplitRegex);
- for (j = 0,subLn = classNames.length; j < subLn; j++) {
- className = classNames[j];
- if (!isClassNameHandled[className]) {
- isClassNameHandled[className] = true;
- if (classNameSubscribers.hasOwnProperty(className)) {
- hasDispatched = true;
- this.dispatch('.' + className, eventName, args);
- }
- }
- }
- }
- }
- // Stop propagation
- if (event.isStopped) {
- return hasDispatched;
- }
- }
- if (hasAllSubscribers && !hasDispatched) {
- event.setDelegatedTarget(event.browserEvent.target);
- hasDispatched = true;
- this.dispatch(this.SELECTOR_ALL, eventName, args);
- if (event.isStopped) {
- return hasDispatched;
- }
- }
- if (hasSelectorSubscribers) {
- for (j = 0,subLn = targets.length; j < subLn; j++) {
- target = targets[j];
- for (i = 0,ln = selectorSubscribers.length; i < ln; i++) {
- selector = selectorSubscribers[i];
- if (this.matchesSelector(target, selector)) {
- event.setDelegatedTarget(target);
- hasDispatched = true;
- this.dispatch(selector, eventName, args);
- }
- if (event.isStopped) {
- return hasDispatched;
- }
- }
- }
- }
- return hasDispatched;
- },
- matchesSelector: function(element, selector) {
- if ('webkitMatchesSelector' in element) {
- return element.webkitMatchesSelector(selector);
- }
- return Ext.DomQuery.is(element, selector);
- },
- onEvent: function(e) {
- var eventName = this.eventNameMap[e.type];
- // Set the current frame start time to be the timestamp of the event.
- Ext.frameStartTime = e.timeStamp;
- if (!eventName || this.getSubscribersCount(eventName) === 0) {
- return;
- }
- var target = this.getElementTarget(e.target),
- targets;
- if (!target) {
- return;
- }
- if (this.doesEventBubble(eventName)) {
- targets = this.getBubblingTargets(target);
- }
- else {
- targets = [target];
- }
- this.publish(eventName, targets, new Ext.event.Dom(e));
- },
- //<debug>
- hasSubscriber: function(target, eventName) {
- if (!this.handles(eventName)) {
- return false;
- }
- var match = target.match(this.idOrClassSelectorRegex),
- subscribers = this.getSubscribers(eventName),
- type, value;
- if (match !== null) {
- type = match[1];
- value = match[2];
- if (type === '#') {
- return subscribers.id.hasOwnProperty(value);
- }
- else {
- return subscribers.className.hasOwnProperty(value);
- }
- }
- else {
- return (subscribers.selector.hasOwnProperty(target) && Ext.Array.indexOf(subscribers.selector, target) !== -1);
- }
- return false;
- },
- //</debug>
- getSubscribersCount: function(eventName) {
- if (!this.handles(eventName)) {
- return 0;
- }
- return this.getSubscribers(eventName).$length + this.getSubscribers('*').$length;
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.util.paintmonitor.Abstract', {
- config: {
- element: null,
- callback: Ext.emptyFn,
- scope: null,
- args: []
- },
- eventName: '',
- monitorClass: '',
- constructor: function(config) {
- this.onElementPainted = Ext.Function.bind(this.onElementPainted, this);
- this.initConfig(config);
- },
- bindListeners: function(bind) {
- this.monitorElement[bind ? 'addEventListener' : 'removeEventListener'](this.eventName, this.onElementPainted, true);
- },
- applyElement: function(element) {
- if (element) {
- return Ext.get(element);
- }
- },
- updateElement: function(element) {
- this.monitorElement = Ext.Element.create({
- classList: ['x-paint-monitor', this.monitorClass]
- }, true);
- element.appendChild(this.monitorElement);
- element.addCls('x-paint-monitored');
- this.bindListeners(true);
- },
- onElementPainted: function() {},
- destroy: function() {
- var monitorElement = this.monitorElement,
- parentNode = monitorElement.parentNode,
- element = this.getElement();
- this.bindListeners(false);
- delete this.monitorElement;
- if (element && !element.isDestroyed) {
- element.removeCls('x-paint-monitored');
- delete this._element;
- }
- if (parentNode) {
- parentNode.removeChild(monitorElement);
- }
- this.callSuper();
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.util.paintmonitor.CssAnimation', {
- extend: 'Ext.util.paintmonitor.Abstract',
- eventName: 'webkitAnimationEnd',
- monitorClass: 'cssanimation',
- onElementPainted: function(e) {
- if (e.animationName === 'x-paint-monitor-helper') {
- this.getCallback().apply(this.getScope(), this.getArgs());
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.util.paintmonitor.OverflowChange', {
- extend: 'Ext.util.paintmonitor.Abstract',
- eventName: 'overflowchanged',
- monitorClass: 'overflowchange',
- onElementPainted: function(e) {
- this.getCallback().apply(this.getScope(), this.getArgs());
- }
- });
- /**
- *
- */
- Ext.define('Ext.util.PaintMonitor', {
- requires: [
- 'Ext.util.paintmonitor.CssAnimation',
- 'Ext.util.paintmonitor.OverflowChange'
- ],
- constructor: function(config) {
- if (Ext.browser.engineVersion.gtEq('536')) {
- return new Ext.util.paintmonitor.OverflowChange(config);
- }
- else {
- return new Ext.util.paintmonitor.CssAnimation(config);
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.event.publisher.ElementPaint', {
- extend: 'Ext.event.publisher.Publisher',
- requires: [
- 'Ext.util.PaintMonitor',
- 'Ext.TaskQueue'
- ],
- targetType: 'element',
- handledEvents: ['painted'],
- constructor: function() {
- this.monitors = {};
- this.callSuper(arguments);
- },
- subscribe: function(target) {
- var match = target.match(this.idSelectorRegex),
- subscribers = this.subscribers,
- id, element;
- if (!match) {
- return false;
- }
- id = match[1];
- if (subscribers.hasOwnProperty(id)) {
- subscribers[id]++;
- return true;
- }
- subscribers[id] = 1;
- element = Ext.get(id);
- this.monitors[id] = new Ext.util.PaintMonitor({
- element: element,
- callback: this.onElementPainted,
- scope: this,
- args: [target, element]
- });
- return true;
- },
- unsubscribe: function(target, eventName, all) {
- var match = target.match(this.idSelectorRegex),
- subscribers = this.subscribers,
- id;
- if (!match) {
- return false;
- }
- id = match[1];
- if (!subscribers.hasOwnProperty(id) || (!all && --subscribers[id] > 0)) {
- return true;
- }
- delete subscribers[id];
- this.monitors[id].destroy();
- delete this.monitors[id];
- return true;
- },
- onElementPainted: function(target, element) {
- Ext.TaskQueue.requestRead('dispatch', this, [target, 'painted', [element]]);
- }
- });
- /**
- *
- */
- Ext.define('Ext.mixin.Templatable', {
- extend: 'Ext.mixin.Mixin',
- mixinConfig: {
- id: 'templatable'
- },
- referenceAttributeName: 'reference',
- referenceSelector: '[reference]',
- getElementConfig: function() {
- return {
- reference: 'element'
- };
- },
- getElementTemplate: function() {
- var elementTemplate = document.createDocumentFragment();
- elementTemplate.appendChild(Ext.Element.create(this.getElementConfig(), true));
- return elementTemplate;
- },
- initElement: function() {
- var prototype = this.self.prototype;
- prototype.elementTemplate = this.getElementTemplate();
- prototype.initElement = prototype.doInitElement;
- this.initElement.apply(this, arguments);
- },
- linkElement: function(reference, node) {
- this.link(reference, node);
- },
- doInitElement: function() {
- var referenceAttributeName = this.referenceAttributeName,
- renderElement, referenceNodes, i, ln, referenceNode, reference;
- renderElement = this.elementTemplate.cloneNode(true);
- referenceNodes = renderElement.querySelectorAll(this.referenceSelector);
- for (i = 0,ln = referenceNodes.length; i < ln; i++) {
- referenceNode = referenceNodes[i];
- reference = referenceNode.getAttribute(referenceAttributeName);
- referenceNode.removeAttribute(referenceAttributeName);
- this.linkElement(reference, referenceNode);
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.util.sizemonitor.Abstract', {
- mixins: ['Ext.mixin.Templatable'],
- requires: [
- 'Ext.TaskQueue'
- ],
- config: {
- element: null,
- callback: Ext.emptyFn,
- scope: null,
- args: []
- },
- width: 0,
- height: 0,
- contentWidth: 0,
- contentHeight: 0,
- constructor: function(config) {
- this.refresh = Ext.Function.bind(this.refresh, this);
- this.info = {
- width: 0,
- height: 0,
- contentWidth: 0,
- contentHeight: 0,
- flag: 0
- };
- this.initElement();
- this.initConfig(config);
- this.bindListeners(true);
- },
- bindListeners: Ext.emptyFn,
- applyElement: function(element) {
- if (element) {
- return Ext.get(element);
- }
- },
- updateElement: function(element) {
- element.append(this.detectorsContainer);
- element.addCls('x-size-monitored');
- },
- applyArgs: function(args) {
- return args.concat([this.info]);
- },
- refreshMonitors: Ext.emptyFn,
- forceRefresh: function() {
- Ext.TaskQueue.requestRead('refresh', this);
- },
- refreshSize: function() {
- var element = this.getElement();
- if (!element || element.isDestroyed) {
- return false;
- }
- var width = element.getWidth(),
- height = element.getHeight(),
- contentElement = this.detectorsContainer,
- contentWidth = contentElement.offsetWidth,
- contentHeight = contentElement.offsetHeight,
- currentContentWidth = this.contentWidth,
- currentContentHeight = this.contentHeight,
- info = this.info,
- resized = false,
- flag;
- this.width = width;
- this.height = height;
- this.contentWidth = contentWidth;
- this.contentHeight = contentHeight;
- flag = ((currentContentWidth !== contentWidth ? 1 : 0) + (currentContentHeight !== contentHeight ? 2 : 0));
- if (flag > 0) {
- info.width = width;
- info.height = height;
- info.contentWidth = contentWidth;
- info.contentHeight = contentHeight;
- info.flag = flag;
- resized = true;
- this.getCallback().apply(this.getScope(), this.getArgs());
- }
- return resized;
- },
- refresh: function(force) {
- if (this.refreshSize() || force) {
- Ext.TaskQueue.requestWrite('refreshMonitors', this);
- }
- },
- destroy: function() {
- var element = this.getElement();
- this.bindListeners(false);
- if (element && !element.isDestroyed) {
- element.removeCls('x-size-monitored');
- }
- delete this._element;
- this.callSuper();
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.util.sizemonitor.Scroll', {
- extend: 'Ext.util.sizemonitor.Abstract',
- getElementConfig: function() {
- return {
- reference: 'detectorsContainer',
- classList: ['x-size-monitors', 'scroll'],
- children: [
- {
- reference: 'expandMonitor',
- className: 'expand'
- },
- {
- reference: 'shrinkMonitor',
- className: 'shrink'
- }
- ]
- }
- },
- constructor: function(config) {
- this.onScroll = Ext.Function.bind(this.onScroll, this);
- this.callSuper(arguments);
- },
- bindListeners: function(bind) {
- var method = bind ? 'addEventListener' : 'removeEventListener';
- this.expandMonitor[method]('scroll', this.onScroll, true);
- this.shrinkMonitor[method]('scroll', this.onScroll, true);
- },
- forceRefresh: function() {
- Ext.TaskQueue.requestRead('refresh', this, [true]);
- },
- onScroll: function() {
- Ext.TaskQueue.requestRead('refresh', this);
- },
- refreshMonitors: function() {
- var expandMonitor = this.expandMonitor,
- shrinkMonitor = this.shrinkMonitor,
- end = 1000000;
- if (expandMonitor && !expandMonitor.isDestroyed) {
- expandMonitor.scrollLeft = end;
- expandMonitor.scrollTop = end;
- }
- if (shrinkMonitor && !shrinkMonitor.isDestroyed) {
- shrinkMonitor.scrollLeft = end;
- shrinkMonitor.scrollTop = end;
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.util.sizemonitor.OverflowChange', {
- extend: 'Ext.util.sizemonitor.Abstract',
- constructor: function(config) {
- this.onExpand = Ext.Function.bind(this.onExpand, this);
- this.onShrink = Ext.Function.bind(this.onShrink, this);
- this.callSuper(arguments);
- },
- getElementConfig: function() {
- return {
- reference: 'detectorsContainer',
- classList: ['x-size-monitors', 'overflowchanged'],
- children: [
- {
- reference: 'expandMonitor',
- className: 'expand',
- children: [{
- reference: 'expandHelper'
- }]
- },
- {
- reference: 'shrinkMonitor',
- className: 'shrink',
- children: [{
- reference: 'shrinkHelper'
- }]
- }
- ]
- }
- },
- bindListeners: function(bind) {
- var method = bind ? 'addEventListener' : 'removeEventListener';
- this.expandMonitor[method]('overflowchanged', this.onExpand, true);
- this.shrinkMonitor[method]('overflowchanged', this.onShrink, true);
- },
- onExpand: function(e) {
- if (e.horizontalOverflow && e.verticalOverflow) {
- return;
- }
- Ext.TaskQueue.requestRead('refresh', this);
- },
- onShrink: function(e) {
- if (!e.horizontalOverflow && !e.verticalOverflow) {
- return;
- }
- Ext.TaskQueue.requestRead('refresh', this);
- },
- refreshMonitors: function() {
- var expandHelper = this.expandHelper,
- shrinkHelper = this.shrinkHelper,
- width = this.contentWidth,
- height = this.contentHeight;
- if (expandHelper && !expandHelper.isDestroyed) {
- expandHelper.style.width = (width + 1) + 'px';
- expandHelper.style.height = (height + 1) + 'px';
- }
- if (shrinkHelper && !shrinkHelper.isDestroyed) {
- shrinkHelper.style.width = width + 'px';
- shrinkHelper.style.height = height + 'px';
- }
- Ext.TaskQueue.requestRead('refresh', this);
- }
- });
- /**
- *
- */
- Ext.define('Ext.util.SizeMonitor', {
- requires: [
- 'Ext.util.sizemonitor.Scroll',
- 'Ext.util.sizemonitor.OverflowChange'
- ],
- constructor: function(config) {
- if (Ext.browser.engineVersion.gtEq('535')) {
- return new Ext.util.sizemonitor.OverflowChange(config);
- }
- else {
- return new Ext.util.sizemonitor.Scroll(config);
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.event.publisher.ElementSize', {
- extend: 'Ext.event.publisher.Publisher',
- requires: [
- 'Ext.util.SizeMonitor'
- ],
- targetType: 'element',
- handledEvents: ['resize'],
- constructor: function() {
- this.monitors = {};
- this.callSuper(arguments);
- },
- subscribe: function(target) {
- var match = target.match(this.idSelectorRegex),
- subscribers = this.subscribers,
- id, element, sizeMonitor;
- if (!match) {
- return false;
- }
- id = match[1];
- if (subscribers.hasOwnProperty(id)) {
- subscribers[id]++;
- return true;
- }
- subscribers[id] = 1;
- element = Ext.get(id);
- this.monitors[id] = sizeMonitor = new Ext.util.SizeMonitor({
- element: element,
- callback: this.onElementResize,
- scope: this,
- args: [target, element]
- });
- this.dispatcher.addListener('element', target, 'painted', 'forceRefresh', sizeMonitor);
- return true;
- },
- unsubscribe: function(target, eventName, all) {
- var match = target.match(this.idSelectorRegex),
- subscribers = this.subscribers,
- monitors = this.monitors,
- id, sizeMonitor;
- if (!match) {
- return false;
- }
- id = match[1];
- if (!subscribers.hasOwnProperty(id) || (!all && --subscribers[id] > 0)) {
- return true;
- }
- delete subscribers[id];
- sizeMonitor = monitors[id];
- this.dispatcher.removeListener('element', target, 'painted', 'forceRefresh', sizeMonitor);
- sizeMonitor.destroy();
- delete monitors[id];
- return true;
- },
- onElementResize: function(target, element, info) {
- Ext.TaskQueue.requestRead('dispatch', this, [target, 'resize', [element, info]]);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.event.publisher.TouchGesture', {
- extend: 'Ext.event.publisher.Dom',
- requires: [
- 'Ext.util.Point',
- 'Ext.event.Touch'
- ],
- handledEvents: ['touchstart', 'touchmove', 'touchend', 'touchcancel'],
- moveEventName: 'touchmove',
- config: {
- moveThrottle: 1,
- buffering: {
- enabled: false,
- interval: 10
- },
- recognizers: {}
- },
- currentTouchesCount: 0,
- constructor: function(config) {
- this.processEvents = Ext.Function.bind(this.processEvents, this);
- this.eventProcessors = {
- touchstart: this.onTouchStart,
- touchmove: this.onTouchMove,
- touchend: this.onTouchEnd,
- touchcancel: this.onTouchEnd
- };
- this.eventToRecognizerMap = {};
- this.activeRecognizers = [];
- this.currentRecognizers = [];
- this.currentTargets = {};
- this.currentTouches = {};
- this.buffer = [];
- this.initConfig(config);
- return this.callParent();
- },
- applyBuffering: function(buffering) {
- if (buffering.enabled === true) {
- this.bufferTimer = setInterval(this.processEvents, buffering.interval);
- }
- else {
- clearInterval(this.bufferTimer);
- }
- return buffering;
- },
- applyRecognizers: function(recognizers) {
- var i, recognizer;
- for (i in recognizers) {
- if (recognizers.hasOwnProperty(i)) {
- recognizer = recognizers[i];
- if (recognizer) {
- this.registerRecognizer(recognizer);
- }
- }
- }
- return recognizers;
- },
- handles: function(eventName) {
- return this.callParent(arguments) || this.eventToRecognizerMap.hasOwnProperty(eventName);
- },
- doesEventBubble: function() {
- // All touch events bubble
- return true;
- },
- eventLogs: [],
- onEvent: function(e) {
- var buffering = this.getBuffering();
- e = new Ext.event.Touch(e);
- if (buffering.enabled) {
- this.buffer.push(e);
- }
- else {
- this.processEvent(e);
- }
- },
- processEvents: function() {
- var buffer = this.buffer,
- ln = buffer.length,
- moveEvents = [],
- events, event, i;
- if (ln > 0) {
- events = buffer.slice(0);
- buffer.length = 0;
- for (i = 0; i < ln; i++) {
- event = events[i];
- if (event.type === this.moveEventName) {
- moveEvents.push(event);
- }
- else {
- if (moveEvents.length > 0) {
- this.processEvent(this.mergeEvents(moveEvents));
- moveEvents.length = 0;
- }
- this.processEvent(event);
- }
- }
- if (moveEvents.length > 0) {
- this.processEvent(this.mergeEvents(moveEvents));
- moveEvents.length = 0;
- }
- }
- },
- mergeEvents: function(events) {
- var changedTouchesLists = [],
- ln = events.length,
- i, event, targetEvent;
- targetEvent = events[ln - 1];
- if (ln === 1) {
- return targetEvent;
- }
- for (i = 0; i < ln; i++) {
- event = events[i];
- changedTouchesLists.push(event.changedTouches);
- }
- targetEvent.changedTouches = this.mergeTouchLists(changedTouchesLists);
- return targetEvent;
- },
- mergeTouchLists: function(touchLists) {
- var touches = {},
- list = [],
- i, ln, touchList, j, subLn, touch, identifier;
- for (i = 0,ln = touchLists.length; i < ln; i++) {
- touchList = touchLists[i];
- for (j = 0,subLn = touchList.length; j < subLn; j++) {
- touch = touchList[j];
- identifier = touch.identifier;
- touches[identifier] = touch;
- }
- }
- for (identifier in touches) {
- if (touches.hasOwnProperty(identifier)) {
- list.push(touches[identifier]);
- }
- }
- return list;
- },
- registerRecognizer: function(recognizer) {
- var map = this.eventToRecognizerMap,
- activeRecognizers = this.activeRecognizers,
- handledEvents = recognizer.getHandledEvents(),
- i, ln, eventName;
- recognizer.setOnRecognized(this.onRecognized);
- recognizer.setCallbackScope(this);
- for (i = 0,ln = handledEvents.length; i < ln; i++) {
- eventName = handledEvents[i];
- map[eventName] = recognizer;
- }
- activeRecognizers.push(recognizer);
- return this;
- },
- onRecognized: function(eventName, e, touches, info) {
- var targetGroups = [],
- ln = touches.length,
- targets, i, touch;
- if (ln === 1) {
- return this.publish(eventName, touches[0].targets, e, info);
- }
- for (i = 0; i < ln; i++) {
- touch = touches[i];
- targetGroups.push(touch.targets);
- }
- targets = this.getCommonTargets(targetGroups);
- this.publish(eventName, targets, e, info);
- },
- publish: function(eventName, targets, event, info) {
- event.set(info);
- return this.callParent([eventName, targets, event]);
- },
- getCommonTargets: function(targetGroups) {
- var firstTargetGroup = targetGroups[0],
- ln = targetGroups.length;
- if (ln === 1) {
- return firstTargetGroup;
- }
- var commonTargets = [],
- i = 1,
- target, targets, j;
- while (true) {
- target = firstTargetGroup[firstTargetGroup.length - i];
- if (!target) {
- return commonTargets;
- }
- for (j = 1; j < ln; j++) {
- targets = targetGroups[j];
- if (targets[targets.length - i] !== target) {
- return commonTargets;
- }
- }
- commonTargets.unshift(target);
- i++;
- }
- return commonTargets;
- },
- invokeRecognizers: function(methodName, e) {
- var recognizers = this.activeRecognizers,
- ln = recognizers.length,
- i, recognizer;
- if (methodName === 'onStart') {
- for (i = 0; i < ln; i++) {
- recognizers[i].isActive = true;
- }
- }
- for (i = 0; i < ln; i++) {
- recognizer = recognizers[i];
- if (recognizer.isActive && recognizer[methodName].call(recognizer, e) === false) {
- recognizer.isActive = false;
- }
- }
- },
- getActiveRecognizers: function() {
- return this.activeRecognizers;
- },
- processEvent: function(e) {
- this.eventProcessors[e.type].call(this, e);
- },
- onTouchStart: function(e) {
- var currentTargets = this.currentTargets,
- currentTouches = this.currentTouches,
- currentTouchesCount = this.currentTouchesCount,
- currentIdentifiers = {},
- changedTouches = e.changedTouches,
- changedTouchedLn = changedTouches.length,
- touches = e.touches,
- touchesLn = touches.length,
- i, touch, identifier, fakeEndEvent;
- currentTouchesCount += changedTouchedLn;
- if (currentTouchesCount > touchesLn) {
- for (i = 0; i < touchesLn; i++) {
- touch = touches[i];
- identifier = touch.identifier;
- currentIdentifiers[identifier] = true;
- }
- if (!Ext.os.is.Android3 && !Ext.os.is.Android4) {
- for (identifier in currentTouches) {
- if (currentTouches.hasOwnProperty(identifier)) {
- if (!currentIdentifiers[identifier]) {
- currentTouchesCount--;
- fakeEndEvent = e.clone();
- touch = currentTouches[identifier];
- touch.targets = this.getBubblingTargets(this.getElementTarget(touch.target));
- fakeEndEvent.changedTouches = [touch];
- this.onTouchEnd(fakeEndEvent);
- }
- }
- }
- }
- // Fix for a bug found in Motorola Droid X (Gingerbread) and similar
- // where there are 2 touchstarts but just one touchend
- if (Ext.os.is.Android2 && currentTouchesCount > touchesLn) {
- return;
- }
- }
- for (i = 0; i < changedTouchedLn; i++) {
- touch = changedTouches[i];
- identifier = touch.identifier;
- if (!currentTouches.hasOwnProperty(identifier)) {
- this.currentTouchesCount++;
- }
- currentTouches[identifier] = touch;
- currentTargets[identifier] = this.getBubblingTargets(this.getElementTarget(touch.target));
- }
- e.setTargets(currentTargets);
- for (i = 0; i < changedTouchedLn; i++) {
- touch = changedTouches[i];
- this.publish('touchstart', touch.targets, e, {touch: touch});
- }
- if (!this.isStarted) {
- this.isStarted = true;
- this.invokeRecognizers('onStart', e);
- }
- this.invokeRecognizers('onTouchStart', e);
- },
- onTouchMove: function(e) {
- if (!this.isStarted) {
- return;
- }
- var currentTargets = this.currentTargets,
- currentTouches = this.currentTouches,
- moveThrottle = this.getMoveThrottle(),
- changedTouches = e.changedTouches,
- stillTouchesCount = 0,
- i, ln, touch, point, oldPoint, identifier;
- e.setTargets(currentTargets);
- for (i = 0,ln = changedTouches.length; i < ln; i++) {
- touch = changedTouches[i];
- identifier = touch.identifier;
- point = touch.point;
- oldPoint = currentTouches[identifier].point;
- if (moveThrottle && point.isCloseTo(oldPoint, moveThrottle)) {
- stillTouchesCount++;
- continue;
- }
- currentTouches[identifier] = touch;
- this.publish('touchmove', touch.targets, e, {touch: touch});
- }
- if (stillTouchesCount < ln) {
- this.invokeRecognizers('onTouchMove', e);
- }
- },
- onTouchEnd: function(e) {
- if (!this.isStarted) {
- return;
- }
- var currentTargets = this.currentTargets,
- currentTouches = this.currentTouches,
- changedTouches = e.changedTouches,
- ln = changedTouches.length,
- isEnded, identifier, i, touch;
- e.setTargets(currentTargets);
- this.currentTouchesCount -= ln;
- isEnded = (this.currentTouchesCount === 0);
- if (isEnded) {
- this.isStarted = false;
- }
- for (i = 0; i < ln; i++) {
- touch = changedTouches[i];
- identifier = touch.identifier;
- delete currentTouches[identifier];
- delete currentTargets[identifier];
- this.publish('touchend', touch.targets, e, {touch: touch});
- }
- this.invokeRecognizers('onTouchEnd', e);
- if (isEnded) {
- this.invokeRecognizers('onEnd', e);
- }
- }
- }, function() {
- if (!Ext.feature.has.Touch) {
- this.override({
- moveEventName: 'mousemove',
- map: {
- mouseToTouch: {
- mousedown: 'touchstart',
- mousemove: 'touchmove',
- mouseup: 'touchend'
- },
- touchToMouse: {
- touchstart: 'mousedown',
- touchmove: 'mousemove',
- touchend: 'mouseup'
- }
- },
- attachListener: function(eventName) {
- eventName = this.map.touchToMouse[eventName];
- if (!eventName) {
- return;
- }
- return this.callOverridden([eventName]);
- },
- lastEventType: null,
- onEvent: function(e) {
- if ('button' in e && e.button !== 0) {
- return;
- }
- var type = e.type,
- touchList = [e];
- // Temporary fix for a recent Chrome bugs where events don't seem to bubble up to document
- // when the element is being animated
- // with webkit-transition (2 mousedowns without any mouseup)
- if (type === 'mousedown' && this.lastEventType && this.lastEventType !== 'mouseup') {
- var fixedEvent = document.createEvent("MouseEvent");
- fixedEvent.initMouseEvent('mouseup', e.bubbles, e.cancelable,
- document.defaultView, e.detail, e.screenX, e.screenY, e.clientX,
- e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.metaKey,
- e.button, e.relatedTarget);
- this.onEvent(fixedEvent);
- }
- if (type !== 'mousemove') {
- this.lastEventType = type;
- }
- e.identifier = 1;
- e.touches = (type !== 'mouseup') ? touchList : [];
- e.targetTouches = (type !== 'mouseup') ? touchList : [];
- e.changedTouches = touchList;
- return this.callOverridden([e]);
- },
- processEvent: function(e) {
- this.eventProcessors[this.map.mouseToTouch[e.type]].call(this, e);
- }
- });
- }
- });
- /**
- * A base class for all event recognizers in Sencha Touch.
- *
- * Sencha Touch, by default, includes various different {@link Ext.event.recognizer.Recognizer} subclasses to recognize
- * events happening in your application.
- *
- * ## Default recognizers
- *
- * * {@link Ext.event.recognizer.Tap}
- * * {@link Ext.event.recognizer.DoubleTap}
- * * {@link Ext.event.recognizer.LongPress}
- * * {@link Ext.event.recognizer.Drag}
- * * {@link Ext.event.recognizer.HorizontalSwipe}
- * * {@link Ext.event.recognizer.Pinch}
- * * {@link Ext.event.recognizer.Rotate}
- *
- * ## Additional recognizers
- *
- * * {@link Ext.event.recognizer.VerticalSwipe}
- *
- * If you want to create custom recognizers, or disable recognizers in your Sencha Touch application, please refer to the
- * documentation in {@link Ext#setup}.
- *
- * @private
- */
- Ext.define('Ext.event.recognizer.Recognizer', {
- mixins: ['Ext.mixin.Identifiable'],
- handledEvents: [],
- config: {
- onRecognized: Ext.emptyFn,
- onFailed: Ext.emptyFn,
- callbackScope: null
- },
- constructor: function(config) {
- this.initConfig(config);
- return this;
- },
- getHandledEvents: function() {
- return this.handledEvents;
- },
- onStart: Ext.emptyFn,
- onEnd: Ext.emptyFn,
- fail: function() {
- this.getOnFailed().apply(this.getCallbackScope(), arguments);
- return false;
- },
- fire: function() {
- this.getOnRecognized().apply(this.getCallbackScope(), arguments);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.event.recognizer.Touch', {
- extend: 'Ext.event.recognizer.Recognizer',
- onTouchStart: Ext.emptyFn,
- onTouchMove: Ext.emptyFn,
- onTouchEnd: Ext.emptyFn
- });
- /**
- * @private
- */
- Ext.define('Ext.event.recognizer.SingleTouch', {
- extend: 'Ext.event.recognizer.Touch',
- inheritableStatics: {
- NOT_SINGLE_TOUCH: 0x01,
- TOUCH_MOVED: 0x02
- },
- onTouchStart: function(e) {
- if (e.touches.length > 1) {
- return this.fail(this.self.NOT_SINGLE_TOUCH);
- }
- }
- });
- /**
- * A simple event recognizer which knows when you double tap.
- *
- * @private
- */
- Ext.define('Ext.event.recognizer.DoubleTap', {
- extend: 'Ext.event.recognizer.SingleTouch',
- inheritableStatics: {
- DIFFERENT_TARGET: 0x03
- },
- config: {
- maxDuration: 300
- },
- handledEvents: ['singletap', 'doubletap'],
- /**
- * @member Ext.dom.Element
- * @event singletap
- * Fires when there is a single tap.
- * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
- * @param {HTMLElement} node The target of the event.
- * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
- */
- /**
- * @member Ext.dom.Element
- * @event doubletap
- * Fires when there is a double tap.
- * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
- * @param {HTMLElement} node The target of the event.
- * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
- */
- singleTapTimer: null,
- startTime: 0,
- lastTapTime: 0,
- onTouchStart: function(e) {
- if (this.callParent(arguments) === false) {
- return false;
- }
- this.startTime = e.time;
- clearTimeout(this.singleTapTimer);
- },
- onTouchMove: function() {
- return this.fail(this.self.TOUCH_MOVED);
- },
- onEnd: function(e) {
- var me = this,
- maxDuration = this.getMaxDuration(),
- touch = e.changedTouches[0],
- time = e.time,
- target = e.target,
- lastTapTime = this.lastTapTime,
- lastTarget = this.lastTarget,
- duration;
- this.lastTapTime = time;
- this.lastTarget = target;
- if (lastTapTime) {
- duration = time - lastTapTime;
- if (duration <= maxDuration) {
- if (target !== lastTarget) {
- return this.fail(this.self.DIFFERENT_TARGET);
- }
- this.lastTarget = null;
- this.lastTapTime = 0;
- this.fire('doubletap', e, [touch], {
- touch: touch,
- duration: duration
- });
- return;
- }
- }
- if (time - this.startTime > maxDuration) {
- this.fireSingleTap(e, touch);
- }
- else {
- this.singleTapTimer = setTimeout(function() {
- me.fireSingleTap(e, touch);
- }, maxDuration);
- }
- },
- fireSingleTap: function(e, touch) {
- this.fire('singletap', e, [touch], {
- touch: touch
- });
- }
- });
- /**
- * A simple event recognizer which knows when you drag.
- *
- * @private
- */
- Ext.define('Ext.event.recognizer.Drag', {
- extend: 'Ext.event.recognizer.SingleTouch',
- isStarted: false,
- startPoint: null,
- previousPoint: null,
- lastPoint: null,
- handledEvents: ['dragstart', 'drag', 'dragend'],
- /**
- * @member Ext.dom.Element
- * @event dragstart
- * Fired once when a drag has started.
- * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
- * @param {HTMLElement} node The target of the event.
- * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
- */
- /**
- * @member Ext.dom.Element
- * @event drag
- * Fires continuously when there is dragging (the touch must move for this to be fired).
- * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
- * @param {HTMLElement} node The target of the event.
- * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
- */
- /**
- * @member Ext.dom.Element
- * @event dragend
- * Fires when a drag has ended.
- * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
- * @param {HTMLElement} node The target of the event.
- * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
- */
- onTouchStart: function(e) {
- var startTouches,
- startTouch;
- if (this.callParent(arguments) === false) {
- if (this.isStarted && this.lastMoveEvent !== null) {
- this.onTouchEnd(this.lastMoveEvent);
- }
- return false;
- }
- this.startTouches = startTouches = e.changedTouches;
- this.startTouch = startTouch = startTouches[0];
- this.startPoint = startTouch.point;
- },
- onTouchMove: function(e) {
- var touches = e.changedTouches,
- touch = touches[0],
- point = touch.point,
- time = e.time;
- if (this.lastPoint) {
- this.previousPoint = this.lastPoint;
- }
- if (this.lastTime) {
- this.previousTime = this.lastTime;
- }
- this.lastTime = time;
- this.lastPoint = point;
- this.lastMoveEvent = e;
- if (!this.isStarted) {
- this.isStarted = true;
- this.startTime = time;
- this.previousTime = time;
- this.previousPoint = this.startPoint;
- this.fire('dragstart', e, this.startTouches, this.getInfo(e, this.startTouch));
- }
- else {
- this.fire('drag', e, touches, this.getInfo(e, touch));
- }
- },
- onTouchEnd: function(e) {
- if (this.isStarted) {
- var touches = e.changedTouches,
- touch = touches[0],
- point = touch.point;
- this.isStarted = false;
- this.lastPoint = point;
- this.fire('dragend', e, touches, this.getInfo(e, touch));
- this.startTime = 0;
- this.previousTime = 0;
- this.lastTime = 0;
- this.startPoint = null;
- this.previousPoint = null;
- this.lastPoint = null;
- this.lastMoveEvent = null;
- }
- },
- getInfo: function(e, touch) {
- var time = e.time,
- startPoint = this.startPoint,
- previousPoint = this.previousPoint,
- startTime = this.startTime,
- previousTime = this.previousTime,
- point = this.lastPoint,
- deltaX = point.x - startPoint.x,
- deltaY = point.y - startPoint.y,
- info = {
- touch: touch,
- startX: startPoint.x,
- startY: startPoint.y,
- previousX: previousPoint.x,
- previousY: previousPoint.y,
- pageX: point.x,
- pageY: point.y,
- deltaX: deltaX,
- deltaY: deltaY,
- absDeltaX: Math.abs(deltaX),
- absDeltaY: Math.abs(deltaY),
- previousDeltaX: point.x - previousPoint.x,
- previousDeltaY: point.y - previousPoint.y,
- time: time,
- startTime: startTime,
- previousTime: previousTime,
- deltaTime: time - startTime,
- previousDeltaTime: time - previousTime
- };
- return info;
- }
- });
- /**
- * A base class used for both {@link Ext.event.recognizer.VerticalSwipe} and {@link Ext.event.recognizer.HorizontalSwipe}
- * event recognizers.
- *
- * @private
- */
- Ext.define('Ext.event.recognizer.Swipe', {
- extend: 'Ext.event.recognizer.SingleTouch',
- handledEvents: ['swipe'],
- /**
- * @member Ext.dom.Element
- * @event swipe
- * Fires when there is a swipe
- * When listening to this, ensure you know about the {@link Ext.event.Event#direction} property in the `event` object.
- * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
- * @param {HTMLElement} node The target of the event.
- * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
- */
- /**
- * @property {Number} direction
- * The direction of the swipe. Available options are:
- *
- * - up
- * - down
- * - left
- * - right
- *
- * __Note:__ In order to recognize swiping up and down, you must enable the vertical swipe recognizer.
- *
- * **This is only available when the event type is `swipe`**
- * @member Ext.event.Event
- */
- /**
- * @property {Number} duration
- * The duration of the swipe.
- *
- * **This is only available when the event type is `swipe`**
- * @member Ext.event.Event
- */
- inheritableStatics: {
- MAX_OFFSET_EXCEEDED: 0x10,
- MAX_DURATION_EXCEEDED: 0x11,
- DISTANCE_NOT_ENOUGH: 0x12
- },
- config: {
- minDistance: 80,
- maxOffset: 35,
- maxDuration: 1000
- },
- onTouchStart: function(e) {
- if (this.callParent(arguments) === false) {
- return false;
- }
- var touch = e.changedTouches[0];
- this.startTime = e.time;
- this.isHorizontal = true;
- this.isVertical = true;
- this.startX = touch.pageX;
- this.startY = touch.pageY;
- },
- onTouchMove: function(e) {
- var touch = e.changedTouches[0],
- x = touch.pageX,
- y = touch.pageY,
- absDeltaX = Math.abs(x - this.startX),
- absDeltaY = Math.abs(y - this.startY),
- time = e.time;
- if (time - this.startTime > this.getMaxDuration()) {
- return this.fail(this.self.MAX_DURATION_EXCEEDED);
- }
- if (this.isVertical && absDeltaX > this.getMaxOffset()) {
- this.isVertical = false;
- }
- if (this.isHorizontal && absDeltaY > this.getMaxOffset()) {
- this.isHorizontal = false;
- }
- if (!this.isHorizontal && !this.isVertical) {
- return this.fail(this.self.MAX_OFFSET_EXCEEDED);
- }
- },
- onTouchEnd: function(e) {
- if (this.onTouchMove(e) === false) {
- return false;
- }
- var touch = e.changedTouches[0],
- x = touch.pageX,
- y = touch.pageY,
- deltaX = x - this.startX,
- deltaY = y - this.startY,
- absDeltaX = Math.abs(deltaX),
- absDeltaY = Math.abs(deltaY),
- minDistance = this.getMinDistance(),
- duration = e.time - this.startTime,
- direction, distance;
- if (this.isVertical && absDeltaY < minDistance) {
- this.isVertical = false;
- }
- if (this.isHorizontal && absDeltaX < minDistance) {
- this.isHorizontal = false;
- }
- if (this.isHorizontal) {
- direction = (deltaX < 0) ? 'left' : 'right';
- distance = absDeltaX;
- }
- else if (this.isVertical) {
- direction = (deltaY < 0) ? 'up' : 'down';
- distance = absDeltaY;
- }
- else {
- return this.fail(this.self.DISTANCE_NOT_ENOUGH);
- }
- this.fire('swipe', e, [touch], {
- touch: touch,
- direction: direction,
- distance: distance,
- duration: duration
- });
- }
- });
- /**
- * A event recognizer created to recognize horizontal swipe movements.
- *
- * @private
- */
- Ext.define('Ext.event.recognizer.HorizontalSwipe', {
- extend: 'Ext.event.recognizer.Swipe',
- handledEvents: ['swipe'],
- onTouchStart: function(e) {
- if (this.callParent(arguments) === false) {
- return false;
- }
- var touch = e.changedTouches[0];
- this.startTime = e.time;
- this.startX = touch.pageX;
- this.startY = touch.pageY;
- },
- onTouchMove: function(e) {
- var touch = e.changedTouches[0],
- y = touch.pageY,
- absDeltaY = Math.abs(y - this.startY),
- time = e.time,
- maxDuration = this.getMaxDuration(),
- maxOffset = this.getMaxOffset();
- if (time - this.startTime > maxDuration) {
- return this.fail(this.self.MAX_DURATION_EXCEEDED);
- }
- if (absDeltaY > maxOffset) {
- return this.fail(this.self.MAX_OFFSET_EXCEEDED);
- }
- },
- onTouchEnd: function(e) {
- if (this.onTouchMove(e) !== false) {
- var touch = e.changedTouches[0],
- x = touch.pageX,
- deltaX = x - this.startX,
- distance = Math.abs(deltaX),
- duration = e.time - this.startTime,
- minDistance = this.getMinDistance(),
- direction;
- if (distance < minDistance) {
- return this.fail(this.self.DISTANCE_NOT_ENOUGH);
- }
- direction = (deltaX < 0) ? 'left' : 'right';
- this.fire('swipe', e, [touch], {
- touch: touch,
- direction: direction,
- distance: distance,
- duration: duration
- });
- }
- }
- });
- /**
- * A event recognizer which knows when you tap and hold for more than 1 second.
- *
- * @private
- */
- Ext.define('Ext.event.recognizer.LongPress', {
- extend: 'Ext.event.recognizer.SingleTouch',
- inheritableStatics: {
- DURATION_NOT_ENOUGH: 0x20
- },
- config: {
- minDuration: 1000
- },
- handledEvents: ['longpress'],
- /**
- * @member Ext.dom.Element
- * @event longpress
- * Fires when you touch and hold still for more than 1 second.
- * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
- * @param {HTMLElement} node The target of the event.
- * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
- */
- /**
- * @member Ext.dom.Element
- * @event taphold
- * @inheritdoc Ext.dom.Element#longpress
- */
- fireLongPress: function(e) {
- var touch = e.changedTouches[0];
- this.fire('longpress', e, [touch], {
- touch: touch,
- duration: this.getMinDuration()
- });
- this.isLongPress = true;
- },
- onTouchStart: function(e) {
- var me = this;
- if (this.callParent(arguments) === false) {
- return false;
- }
- this.isLongPress = false;
- this.timer = setTimeout(function() {
- me.fireLongPress(e);
- }, this.getMinDuration());
- },
- onTouchMove: function() {
- return this.fail(this.self.TOUCH_MOVED);
- },
- onTouchEnd: function() {
- if (!this.isLongPress) {
- return this.fail(this.self.DURATION_NOT_ENOUGH);
- }
- },
- fail: function() {
- clearTimeout(this.timer);
- return this.callParent(arguments);
- }
- }, function() {
- this.override({
- handledEvents: ['longpress', 'taphold'],
- fire: function(eventName) {
- if (eventName === 'longpress') {
- var args = Array.prototype.slice.call(arguments);
- args[0] = 'taphold';
- this.fire.apply(this, args);
- }
- return this.callOverridden(arguments);
- }
- });
- });
- /**
- * @private
- */
- Ext.define('Ext.event.recognizer.MultiTouch', {
- extend: 'Ext.event.recognizer.Touch',
- requiredTouchesCount: 2,
- isTracking: false,
- isStarted: false,
- onTouchStart: function(e) {
- var requiredTouchesCount = this.requiredTouchesCount,
- touches = e.touches,
- touchesCount = touches.length;
- if (touchesCount === requiredTouchesCount) {
- this.start(e);
- }
- else if (touchesCount > requiredTouchesCount) {
- this.end(e);
- }
- },
- onTouchEnd: function(e) {
- this.end(e);
- },
- start: function() {
- if (!this.isTracking) {
- this.isTracking = true;
- this.isStarted = false;
- }
- },
- end: function(e) {
- if (this.isTracking) {
- this.isTracking = false;
- if (this.isStarted) {
- this.isStarted = false;
- this.fireEnd(e);
- }
- }
- }
- });
- /**
- * A event recognizer which knows when you pinch.
- *
- * @private
- */
- Ext.define('Ext.event.recognizer.Pinch', {
- extend: 'Ext.event.recognizer.MultiTouch',
- requiredTouchesCount: 2,
- handledEvents: ['pinchstart', 'pinch', 'pinchend'],
- /**
- * @member Ext.dom.Element
- * @event pinchstart
- * Fired once when a pinch has started.
- * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
- * @param {HTMLElement} node The target of the event.
- * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
- */
- /**
- * @member Ext.dom.Element
- * @event pinch
- * Fires continuously when there is pinching (the touch must move for this to be fired).
- * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
- * @param {HTMLElement} node The target of the event.
- * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
- */
- /**
- * @member Ext.dom.Element
- * @event pinchend
- * Fires when a pinch has ended.
- * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
- * @param {HTMLElement} node The target of the event.
- * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
- */
- /**
- * @property {Number} scale
- * The scape of a pinch event.
- *
- * **This is only available when the event type is `pinch`**
- * @member Ext.event.Event
- */
- startDistance: 0,
- lastTouches: null,
- onTouchMove: function(e) {
- if (!this.isTracking) {
- return;
- }
- var touches = Array.prototype.slice.call(e.touches),
- firstPoint, secondPoint, distance;
- firstPoint = touches[0].point;
- secondPoint = touches[1].point;
- distance = firstPoint.getDistanceTo(secondPoint);
- if (distance === 0) {
- return;
- }
- if (!this.isStarted) {
- this.isStarted = true;
- this.startDistance = distance;
- this.fire('pinchstart', e, touches, {
- touches: touches,
- distance: distance,
- scale: 1
- });
- }
- else {
- this.fire('pinch', e, touches, {
- touches: touches,
- distance: distance,
- scale: distance / this.startDistance
- });
- }
- this.lastTouches = touches;
- },
- fireEnd: function(e) {
- this.fire('pinchend', e, this.lastTouches);
- },
- fail: function() {
- return this.callParent(arguments);
- }
- });
- /**
- * A simple event recognizer which knows when you rotate.
- *
- * @private
- */
- Ext.define('Ext.event.recognizer.Rotate', {
- extend: 'Ext.event.recognizer.MultiTouch',
- requiredTouchesCount: 2,
- handledEvents: ['rotatestart', 'rotate', 'rotateend'],
- /**
- * @member Ext.dom.Element
- * @event rotatestart
- * Fired once when a rotation has started.
- * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
- * @param {HTMLElement} node The target of the event.
- * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
- */
- /**
- * @member Ext.dom.Element
- * @event rotate
- * Fires continuously when there is rotation (the touch must move for this to be fired).
- * When listening to this, ensure you know about the {@link Ext.event.Event#angle} and {@link Ext.event.Event#rotation}
- * properties in the `event` object.
- * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
- * @param {HTMLElement} node The target of the event.
- * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
- */
- /**
- * @member Ext.dom.Element
- * @event rotateend
- * Fires when a rotation event has ended.
- * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
- * @param {HTMLElement} node The target of the event.
- * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
- */
- /**
- * @property {Number} angle
- * The angle of the rotation.
- *
- * **This is only available when the event type is `rotate`**
- * @member Ext.event.Event
- */
- /**
- * @property {Number} rotation
- * A amount of rotation, since the start of the event.
- *
- * **This is only available when the event type is `rotate`**
- * @member Ext.event.Event
- */
- startAngle: 0,
- lastTouches: null,
- lastAngle: null,
- onTouchMove: function(e) {
- if (!this.isTracking) {
- return;
- }
- var touches = Array.prototype.slice.call(e.touches),
- lastAngle = this.lastAngle,
- firstPoint, secondPoint, angle, nextAngle, previousAngle, diff;
- firstPoint = touches[0].point;
- secondPoint = touches[1].point;
- angle = firstPoint.getAngleTo(secondPoint);
- if (lastAngle !== null) {
- diff = Math.abs(lastAngle - angle);
- nextAngle = angle + 360;
- previousAngle = angle - 360;
- if (Math.abs(nextAngle - lastAngle) < diff) {
- angle = nextAngle;
- }
- else if (Math.abs(previousAngle - lastAngle) < diff) {
- angle = previousAngle;
- }
- }
- this.lastAngle = angle;
- if (!this.isStarted) {
- this.isStarted = true;
- this.startAngle = angle;
- this.fire('rotatestart', e, touches, {
- touches: touches,
- angle: angle,
- rotation: 0
- });
- }
- else {
- this.fire('rotate', e, touches, {
- touches: touches,
- angle: angle,
- rotation: angle - this.startAngle
- });
- }
- this.lastTouches = touches;
- },
- fireEnd: function(e) {
- this.lastAngle = null;
- this.fire('rotateend', e, this.lastTouches);
- }
- });
- /**
- * A simple event recognizer which knows when you tap.
- *
- * @private
- */
- Ext.define('Ext.event.recognizer.Tap', {
- handledEvents: ['tap'],
- /**
- * @member Ext.dom.Element
- * @event tap
- * Fires when you tap
- * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
- * @param {HTMLElement} node The target of the event.
- * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
- */
- /**
- * @member Ext.dom.Element
- * @event touchstart
- * Fires when touch starts.
- * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
- * @param {HTMLElement} node The target of the event.
- * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
- */
- /**
- * @member Ext.dom.Element
- * @event tapstart
- * @inheritdoc Ext.dom.Element#touchstart
- * @deprecated 2.0.0 Please add listener to 'touchstart' event instead
- */
- /**
- * @member Ext.dom.Element
- * @event touchmove
- * Fires when movement while touching.
- * @param {Ext.event.Event} event The {@link Ext.event.Event} event encapsulating the DOM event.
- * @param {HTMLElement} node The target of the event.
- * @param {Object} options The options object passed to Ext.mixin.Observable.addListener.
- */
- /**
- * @member Ext.dom.Element
- * @event tapcancel
- * @inheritdoc Ext.dom.Element#touchmove
- * @deprecated 2.0.0 Please add listener to 'touchmove' event instead
- */
- extend: 'Ext.event.recognizer.SingleTouch',
- onTouchMove: function() {
- return this.fail(this.self.TOUCH_MOVED);
- },
- onTouchEnd: function(e) {
- var touch = e.changedTouches[0];
- this.fire('tap', e, [touch]);
- }
- }, function() {
- });
- /**
- * A event recognizer created to recognize vertical swipe movements.
- *
- * This is disabled by default in Sencha Touch as it has a performance impact when your application
- * has vertical scrollers, plus, in most cases it is not very useful.
- *
- * If you wish to recognize vertical swipe movements in your application, please refer to the documentation of
- * {@link Ext.event.recognizer.Recognizer} and {@link Ext#setup}.
- *
- * @private
- */
- Ext.define('Ext.event.recognizer.VerticalSwipe', {
- extend: 'Ext.event.recognizer.Swipe',
- onTouchStart: function(e) {
- if (this.callParent(arguments) === false) {
- return false;
- }
- var touch = e.changedTouches[0];
- this.startTime = e.time;
- this.startX = touch.pageX;
- this.startY = touch.pageY;
- },
- onTouchMove: function(e) {
- var touch = e.changedTouches[0],
- x = touch.pageX,
- absDeltaX = Math.abs(x - this.startX),
- maxDuration = this.getMaxDuration(),
- maxOffset = this.getMaxOffset(),
- time = e.time;
- if (time - this.startTime > maxDuration) {
- return this.fail(this.self.MAX_DURATION_EXCEEDED);
- }
- if (absDeltaX > maxOffset) {
- return this.fail(this.self.MAX_OFFSET_EXCEEDED);
- }
- },
- onTouchEnd: function(e) {
- if (this.onTouchMove(e) !== false) {
- var touch = e.changedTouches[0],
- y = touch.pageY,
- deltaY = y - this.startY,
- distance = Math.abs(deltaY),
- duration = e.time - this.startTime,
- minDistance = this.getMinDistance(),
- direction;
- if (distance < minDistance) {
- return this.fail(this.self.DISTANCE_NOT_ENOUGH);
- }
- direction = (deltaY < 0) ? 'up' : 'down';
- this.fire('swipe', e, [touch], {
- touch: touch,
- distance: distance,
- duration: duration,
- duration: duration
- });
- }
- }
- });
- /**
- * @aside guide forms
- *
- * The checkbox field is an enhanced version of the native browser checkbox and is great for enabling your user to
- * choose one or more items from a set (for example choosing toppings for a pizza order). It works like any other
- * {@link Ext.field.Field field} and is usually found in the context of a form:
- *
- * ## Example
- *
- * @example miniphone preview
- * var form = Ext.create('Ext.form.Panel', {
- * fullscreen: true,
- * items: [
- * {
- * xtype: 'checkboxfield',
- * name : 'tomato',
- * label: 'Tomato',
- * value: 'tomato',
- * checked: true
- * },
- * {
- * xtype: 'checkboxfield',
- * name : 'salami',
- * label: 'Salami'
- * },
- * {
- * xtype: 'toolbar',
- * docked: 'bottom',
- * items: [
- * { xtype: 'spacer' },
- * {
- * text: 'getValues',
- * handler: function() {
- * var form = Ext.ComponentQuery.query('formpanel')[0],
- * values = form.getValues();
- *
- * Ext.Msg.alert(null,
- * "Tomato: " + ((values.tomato) ? "yes" : "no") +
- * "<br />Salami: " + ((values.salami) ? "yes" : "no")
- * );
- * }
- * },
- * { xtype: 'spacer' }
- * ]
- * }
- * ]
- * });
- *
- *
- * The form above contains two check boxes - one for Tomato, one for Salami. We configured the Tomato checkbox to be
- * checked immediately on load, and the Salami checkbox to be unchecked. We also specified an optional text
- * {@link #value} that will be sent when we submit the form. We can get this value using the Form's
- * {@link Ext.form.Panel#getValues getValues} function, or have it sent as part of the data that is sent when the
- * form is submitted:
- *
- * form.getValues(); //contains a key called 'tomato' if the Tomato field is still checked
- * form.submit(); //will send 'tomato' in the form submission data
- *
- */
- Ext.define('Ext.field.Checkbox', {
- extend: 'Ext.field.Field',
- alternateClassName: 'Ext.form.Checkbox',
- xtype: 'checkboxfield',
- qsaLeftRe: /[\[]/g,
- qsaRightRe: /[\]]/g,
- isCheckbox: true,
- /**
- * @event change
- * Fires just before the field blurs if the field value has changed.
- * @param {Ext.field.Checkbox} this This field.
- * @param {Boolean} newValue The new value.
- * @param {Boolean} oldValue The original value.
- */
- /**
- * @event check
- * Fires when the checkbox is checked.
- * @param {Ext.field.Checkbox} this This checkbox.
- * @param {Ext.EventObject} e This event object.
- */
- /**
- * @event uncheck
- * Fires when the checkbox is unchecked.
- * @param {Ext.field.Checkbox} this This checkbox.
- * @param {Ext.EventObject} e This event object.
- */
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- ui: 'checkbox',
- /**
- * @cfg {String} value The string value to submit if the item is in a checked state.
- * @accessor
- */
- value: '',
- /**
- * @cfg {Boolean} checked `true` if the checkbox should render initially checked.
- * @accessor
- */
- checked: false,
- /**
- * @cfg {Number} tabIndex
- * @hide
- */
- tabIndex: -1,
- /**
- * @cfg
- * @inheritdoc
- */
- component: {
- xtype : 'input',
- type : 'checkbox',
- useMask : true,
- cls : Ext.baseCSSPrefix + 'input-checkbox'
- }
- },
- // @private
- initialize: function() {
- var me = this;
- me.callParent();
- me.getComponent().on({
- scope: me,
- order: 'before',
- masktap: 'onMaskTap'
- });
- },
- // @private
- doInitValue: function() {
- var me = this,
- initialConfig = me.getInitialConfig();
- // you can have a value or checked config, but checked get priority
- if (initialConfig.hasOwnProperty('value')) {
- me.originalState = initialConfig.value;
- }
- if (initialConfig.hasOwnProperty('checked')) {
- me.originalState = initialConfig.checked;
- }
- me.callParent(arguments);
- },
- // @private
- updateInputType: function(newInputType) {
- var component = this.getComponent();
- if (component) {
- component.setType(newInputType);
- }
- },
- // @private
- updateName: function(newName) {
- var component = this.getComponent();
- if (component) {
- component.setName(newName);
- }
- },
- /**
- * Returns the field checked value.
- * @return {Mixed} The field value.
- */
- getChecked: function() {
- // we need to get the latest value from the {@link #input} and then update the value
- this._checked = this.getComponent().getChecked();
- return this._checked;
- },
- /**
- * Returns the submit value for the checkbox which can be used when submitting forms.
- * @return {Boolean/String} value The value of {@link #value} or `true`, if {@link #checked}.
- */
- getSubmitValue: function() {
- return (this.getChecked()) ? this._value || true : null;
- },
- setChecked: function(newChecked) {
- this.updateChecked(newChecked);
- this._checked = newChecked;
- },
- updateChecked: function(newChecked) {
- this.getComponent().setChecked(newChecked);
- // only call onChange (which fires events) if the component has been initialized
- if (this.initialized) {
- this.onChange();
- }
- },
- // @private
- onMaskTap: function(component, e) {
- var me = this,
- dom = component.input.dom;
- if (me.getDisabled()) {
- return false;
- }
- //we must manually update the input dom with the new checked value
- dom.checked = !dom.checked;
- me.onChange(e);
- //return false so the mask does not disappear
- return false;
- },
- /**
- * Fires the `check` or `uncheck` event when the checked value of this component changes.
- * @private
- */
- onChange: function(e) {
- var me = this,
- oldChecked = me._checked,
- newChecked = me.getChecked();
- // only fire the event when the value changes
- if (oldChecked != newChecked) {
- if (newChecked) {
- me.fireEvent('check', me, e);
- } else {
- me.fireEvent('uncheck', me, e);
- }
- me.fireEvent('change', me, newChecked, oldChecked);
- }
- },
- /**
- * @method
- * Method called when this {@link Ext.field.Checkbox} has been checked.
- */
- doChecked: Ext.emptyFn,
- /**
- * @method
- * Method called when this {@link Ext.field.Checkbox} has been unchecked.
- */
- doUnChecked: Ext.emptyFn,
- /**
- * Returns the checked state of the checkbox.
- * @return {Boolean} `true` if checked, `false` otherwise.
- */
- isChecked: function() {
- return this.getChecked();
- },
- /**
- * Set the checked state of the checkbox to `true`.
- * @return {Ext.field.Checkbox} This checkbox.
- */
- check: function() {
- return this.setChecked(true);
- },
- /**
- * Set the checked state of the checkbox to `false`.
- * @return {Ext.field.Checkbox} This checkbox.
- */
- uncheck: function() {
- return this.setChecked(false);
- },
- getSameGroupFields: function() {
- var component = this.up('formpanel') || this.up('fieldset'),
- name = this.getName(),
- replaceLeft = this.qsaLeftRe,
- replaceRight = this.qsaRightRe,
- components = [],
- elements, element, i, ln;
- if (!component) {
- // <debug>
- Ext.Logger.warn('Ext.field.Radio components must always be descendants of an Ext.form.Panel or Ext.form.FieldSet.');
- // </debug>
- component = Ext.Viewport;
- }
- // This is to handle ComponentQuery's lack of handling [name=foo[bar]] properly
- name = name.replace(replaceLeft, '\\[');
- name = name.replace(replaceRight, '\\]');
- elements = Ext.query('[name=' + name + ']', component.element.dom);
- ln = elements.length;
- for (i = 0; i < ln; i++) {
- element = elements[i];
- element = Ext.fly(element).up('.x-field-' + element.getAttribute('type'));
- if (element && element.id) {
- components.push(Ext.getCmp(element.id));
- }
- }
- return components;
- },
- /**
- * Returns an array of values from the checkboxes in the group that are checked.
- * @return {Array}
- */
- getGroupValues: function() {
- var values = [];
- this.getSameGroupFields().forEach(function(field) {
- if (field.getChecked()) {
- values.push(field.getValue());
- }
- });
- return values;
- },
- /**
- * Set the status of all matched checkboxes in the same group to checked.
- * @param {Array} values An array of values.
- * @return {Ext.field.Checkbox} This checkbox.
- */
- setGroupValues: function(values) {
- this.getSameGroupFields().forEach(function(field) {
- field.setChecked((values.indexOf(field.getValue()) !== -1));
- });
- return this;
- },
- /**
- * Resets the status of all matched checkboxes in the same group to checked.
- * @return {Ext.field.Checkbox} This checkbox.
- */
- resetGroupValues: function() {
- this.getSameGroupFields().forEach(function(field) {
- field.setChecked(field.originalState);
- });
- return this;
- },
- reset: function() {
- this.setChecked(this.originalState);
- return this;
- }
- });
- /**
- * @private
- *
- * A general {@link Ext.picker.Picker} slot class. Slots are used to organize multiple scrollable slots into
- * a single {@link Ext.picker.Picker}.
- *
- * {
- * name : 'limit_speed',
- * title: 'Speed Limit',
- * data : [
- * {text: '50 KB/s', value: 50},
- * {text: '100 KB/s', value: 100},
- * {text: '200 KB/s', value: 200},
- * {text: '300 KB/s', value: 300}
- * ]
- * }
- *
- * See the {@link Ext.picker.Picker} documentation on how to use slots.
- */
- Ext.define('Ext.picker.Slot', {
- extend: 'Ext.dataview.DataView',
- xtype : 'pickerslot',
- alternateClassName: 'Ext.Picker.Slot',
- requires: [
- 'Ext.XTemplate',
- 'Ext.data.Store',
- 'Ext.Component',
- 'Ext.data.StoreManager'
- ],
- /**
- * @event slotpick
- * Fires whenever an slot is picked
- * @param {Ext.picker.Slot} this
- * @param {Mixed} value The value of the pick
- * @param {HTMLElement} node The node element of the pick
- */
- isSlot: true,
- config: {
- /**
- * @cfg {String} title The title to use for this slot, or `null` for no title.
- * @accessor
- */
- title: null,
- /**
- * @private
- * @cfg {Boolean} showTitle
- * @accessor
- */
- showTitle: true,
- /**
- * @private
- * @cfg {String} cls The main component class
- * @accessor
- */
- cls: Ext.baseCSSPrefix + 'picker-slot',
- /**
- * @cfg {String} name (required) The name of this slot.
- * @accessor
- */
- name: null,
- /**
- * @cfg {Number} value The value of this slot
- * @accessor
- */
- value: null,
- /**
- * @cfg {Number} flex
- * @accessor
- * @private
- */
- flex: 1,
- /**
- * @cfg {String} align The horizontal alignment of the slot's contents.
- *
- * Valid values are: "left", "center", and "right".
- * @accessor
- */
- align: 'left',
- /**
- * @cfg {String} displayField The display field in the store.
- * @accessor
- */
- displayField: 'text',
- /**
- * @cfg {String} valueField The value field in the store.
- * @accessor
- */
- valueField: 'value',
- /**
- * @cfg {Object} scrollable
- * @accessor
- * @hide
- */
- scrollable: {
- direction: 'vertical',
- indicators: false,
- momentumEasing: {
- minVelocity: 2
- },
- slotSnapEasing: {
- duration: 100
- }
- }
- },
- constructor: function() {
- /**
- * @property selectedIndex
- * @type Number
- * The current `selectedIndex` of the picker slot.
- * @private
- */
- this.selectedIndex = 0;
- /**
- * @property picker
- * @type Ext.picker.Picker
- * A reference to the owner Picker.
- * @private
- */
- this.callParent(arguments);
- },
- /**
- * Sets the title for this dataview by creating element.
- * @param {String} title
- * @return {String}
- */
- applyTitle: function(title) {
- //check if the title isnt defined
- if (title) {
- //create a new title element
- title = Ext.create('Ext.Component', {
- cls: Ext.baseCSSPrefix + 'picker-slot-title',
- docked : 'top',
- html : title
- });
- }
- return title;
- },
- updateTitle: function(newTitle, oldTitle) {
- if (newTitle) {
- this.add(newTitle);
- this.setupBar();
- }
- if (oldTitle) {
- this.remove(oldTitle);
- }
- },
- updateShowTitle: function(showTitle) {
- var title = this.getTitle();
- if (title) {
- title[showTitle ? 'show' : 'hide']();
- this.setupBar();
- }
- },
- updateDisplayField: function(newDisplayField) {
- this.setItemTpl('<div class="' + Ext.baseCSSPrefix + 'picker-item {cls} <tpl if="extra">' + Ext.baseCSSPrefix + 'picker-invalid</tpl>">{' + newDisplayField + '}</div>');
- },
- /**
- * Updates the {@link #align} configuration
- */
- updateAlign: function(newAlign, oldAlign) {
- var element = this.element;
- element.addCls(Ext.baseCSSPrefix + 'picker-' + newAlign);
- element.removeCls(Ext.baseCSSPrefix + 'picker-' + oldAlign);
- },
- /**
- * Looks at the {@link #data} configuration and turns it into {@link #store}.
- * @param {Object} data
- * @return {Object}
- */
- applyData: function(data) {
- var parsedData = [],
- ln = data && data.length,
- i, item, obj;
- if (data && Ext.isArray(data) && ln) {
- for (i = 0; i < ln; i++) {
- item = data[i];
- obj = {};
- if (Ext.isArray(item)) {
- obj[this.valueField] = item[0];
- obj[this.displayField] = item[1];
- }
- else if (Ext.isString(item)) {
- obj[this.valueField] = item;
- obj[this.displayField] = item;
- }
- else if (Ext.isObject(item)) {
- obj = item;
- }
- parsedData.push(obj);
- }
- }
- return data;
- },
- updateData: function(data) {
- this.setStore(Ext.create('Ext.data.Store', {
- fields: ['text', 'value'],
- data : data
- }));
- },
- // @private
- initialize: function() {
- this.callParent();
- var scroller = this.getScrollable().getScroller();
- this.on({
- scope: this,
- painted: 'onPainted',
- itemtap: 'doItemTap'
- });
- scroller.on({
- scope: this,
- scrollend: 'onScrollEnd'
- });
- },
- // @private
- onPainted: function() {
- this.setupBar();
- },
- /**
- * Returns an instance of the owner picker.
- * @return {Object}
- * @private
- */
- getPicker: function() {
- if (!this.picker) {
- this.picker = this.getParent();
- }
- return this.picker;
- },
- // @private
- setupBar: function() {
- if (!this.rendered) {
- //if the component isnt rendered yet, there is no point in calculating the padding just eyt
- return;
- }
- var element = this.element,
- innerElement = this.innerElement,
- picker = this.getPicker(),
- bar = picker.bar,
- value = this.getValue(),
- showTitle = this.getShowTitle(),
- title = this.getTitle(),
- scrollable = this.getScrollable(),
- scroller = scrollable.getScroller(),
- titleHeight = 0,
- barHeight, padding;
- barHeight = bar.getHeight();
- if (showTitle && title) {
- titleHeight = title.element.getHeight();
- }
- padding = Math.ceil((element.getHeight() - titleHeight - barHeight) / 2);
- innerElement.setStyle({
- padding: padding + 'px 0 ' + (padding) + 'px'
- });
- scroller.refresh();
- scroller.setSlotSnapSize(barHeight);
- this.setValue(value);
- },
- // @private
- doItemTap: function(list, index, item, e) {
- var me = this;
- me.selectedIndex = index;
- me.selectedNode = item;
- me.scrollToItem(item, true);
- },
- // @private
- scrollToItem: function(item, animated) {
- var y = item.getY(),
- parentEl = item.parent(),
- parentY = parentEl.getY(),
- scrollView = this.getScrollable(),
- scroller = scrollView.getScroller(),
- difference;
- difference = y - parentY;
- scroller.scrollTo(0, difference, animated);
- },
- // @private
- onScrollEnd: function(scroller, x, y) {
- var me = this,
- index = Math.round(y / me.picker.bar.getHeight()),
- viewItems = me.getViewItems(),
- item = viewItems[index];
- if (item) {
- me.selectedIndex = index;
- me.selectedNode = item;
- me.fireEvent('slotpick', me, me.getValue(), me.selectedNode);
- }
- },
- /**
- * Returns the value of this slot
- * @private
- */
- getValue: function(useDom) {
- var store = this.getStore(),
- record, value;
- if (!store) {
- return;
- }
- if (!this.rendered || !useDom) {
- return this._value;
- }
- //if the value is ever false, that means we do not want to return anything
- if (this._value === false) {
- return null;
- }
- record = store.getAt(this.selectedIndex);
- value = record ? record.get(this.getValueField()) : null;
- // this._value = value;
- return value;
- },
- /**
- * Sets the value of this slot
- * @private
- */
- setValue: function(value) {
- if (!Ext.isDefined(value)) {
- return;
- }
- if (!this.rendered) {
- //we don't want to call this until the slot has been rendered
- this._value = value;
- return;
- }
- var store = this.getStore(),
- viewItems = this.getViewItems(),
- valueField = this.getValueField(),
- index, item;
- index = store.findExact(valueField, value);
- if (index != -1) {
- item = Ext.get(viewItems[index]);
- this.selectedIndex = index;
- if (item) {
- this.scrollToItem(item);
- }
- this._value = value;
- }
- },
- /**
- * Sets the value of this slot
- * @private
- */
- setValueAnimated: function(value) {
- if (!this.rendered) {
- //we don't want to call this until the slot has been rendered
- this._value = value;
- return;
- }
- var store = this.getStore(),
- viewItems = this.getViewItems(),
- valueField = this.getValueField(),
- index, item;
- index = store.find(valueField, value);
- if (index != -1) {
- item = Ext.get(viewItems[index]);
- this.selectedIndex = index;
- if (item) {
- this.scrollToItem(item, {
- duration: 100
- });
- }
- this._value = value;
- }
- }
- });
- /**
- * @aside example pickers
- * A general picker class. {@link Ext.picker.Slot}s are used to organize multiple scrollable slots into a single picker. {@link #slots} is
- * the only necessary configuration.
- *
- * The {@link #slots} configuration with a few key values:
- *
- * - `name`: The name of the slot (will be the key when using {@link #getValues} in this {@link Ext.picker.Picker}).
- * - `title`: The title of this slot (if {@link #useTitles} is set to `true`).
- * - `data`/`store`: The data or store to use for this slot.
- *
- * Remember, {@link Ext.picker.Slot} class extends from {@link Ext.dataview.DataView}.
- *
- * ## Examples
- *
- * @example miniphone preview
- * var picker = Ext.create('Ext.Picker', {
- * slots: [
- * {
- * name : 'limit_speed',
- * title: 'Speed',
- * data : [
- * {text: '50 KB/s', value: 50},
- * {text: '100 KB/s', value: 100},
- * {text: '200 KB/s', value: 200},
- * {text: '300 KB/s', value: 300}
- * ]
- * }
- * ]
- * });
- * Ext.Viewport.add(picker);
- * picker.show();
- *
- * You can also customize the top toolbar on the {@link Ext.picker.Picker} by changing the {@link #doneButton} and {@link #cancelButton} configurations:
- *
- * @example miniphone preview
- * var picker = Ext.create('Ext.Picker', {
- * doneButton: 'I\'m done!',
- * cancelButton: false,
- * slots: [
- * {
- * name : 'limit_speed',
- * title: 'Speed',
- * data : [
- * {text: '50 KB/s', value: 50},
- * {text: '100 KB/s', value: 100},
- * {text: '200 KB/s', value: 200},
- * {text: '300 KB/s', value: 300}
- * ]
- * }
- * ]
- * });
- * Ext.Viewport.add(picker);
- * picker.show();
- *
- * Or by passing a custom {@link #toolbar} configuration:
- *
- * @example miniphone preview
- * var picker = Ext.create('Ext.Picker', {
- * doneButton: false,
- * cancelButton: false,
- * toolbar: {
- * ui: 'light',
- * title: 'My Picker!'
- * },
- * slots: [
- * {
- * name : 'limit_speed',
- * title: 'Speed',
- * data : [
- * {text: '50 KB/s', value: 50},
- * {text: '100 KB/s', value: 100},
- * {text: '200 KB/s', value: 200},
- * {text: '300 KB/s', value: 300}
- * ]
- * }
- * ]
- * });
- * Ext.Viewport.add(picker);
- * picker.show();
- */
- Ext.define('Ext.picker.Picker', {
- extend: 'Ext.Sheet',
- alias : 'widget.picker',
- alternateClassName: 'Ext.Picker',
- requires: ['Ext.picker.Slot', 'Ext.TitleBar', 'Ext.data.Model'],
- isPicker: true,
- /**
- * @event pick
- * Fired when a slot has been picked
- * @param {Ext.Picker} this This Picker.
- * @param {Object} The values of this picker's slots, in `{name:'value'}` format.
- * @param {Ext.Picker.Slot} slot An instance of Ext.Picker.Slot that has been picked.
- */
- /**
- * @event change
- * Fired when the value of this picker has changed the Done button has been pressed.
- * @param {Ext.picker.Picker} this This Picker.
- * @param {Object} value The values of this picker's slots, in `{name:'value'}` format.
- */
- /**
- * @event cancel
- * Fired when the cancel button is tapped and the values are reverted back to
- * what they were.
- * @param {Ext.Picker} this This Picker.
- */
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- cls: Ext.baseCSSPrefix + 'picker',
- /**
- * @cfg {String/Mixed} doneButton
- * Can be either:
- *
- * - A {String} text to be used on the Done button.
- * - An {Object} as config for {@link Ext.Button}.
- * - `false` or `null` to hide it.
- * @accessor
- */
- doneButton: true,
- /**
- * @cfg {String/Mixed} cancelButton
- * Can be either:
- *
- * - A {String} text to be used on the Cancel button.
- * - An {Object} as config for {@link Ext.Button}.
- * - `false` or `null` to hide it.
- * @accessor
- */
- cancelButton: true,
- /**
- * @cfg {Boolean} useTitles
- * Generate a title header for each individual slot and use
- * the title configuration of the slot.
- * @accessor
- */
- useTitles: false,
- /**
- * @cfg {Array} slots
- * An array of slot configurations.
- *
- * - `name` {String} - Name of the slot
- * - `data` {Array} - An array of text/value pairs in the format `{text: 'myKey', value: 'myValue'}`
- * - `title` {String} - Title of the slot. This is used in conjunction with `useTitles: true`.
- *
- * @accessor
- */
- slots: null,
- /**
- * @cfg {String/Number} value The value to initialize the picker with.
- * @accessor
- */
- value: null,
- /**
- * @cfg {Number} height
- * The height of the picker.
- * @accessor
- */
- height: 220,
- /**
- * @cfg
- * @inheritdoc
- */
- layout: {
- type : 'hbox',
- align: 'stretch'
- },
- /**
- * @cfg
- * @hide
- */
- centered: false,
- /**
- * @cfg
- * @inheritdoc
- */
- left : 0,
- /**
- * @cfg
- * @inheritdoc
- */
- right: 0,
- /**
- * @cfg
- * @inheritdoc
- */
- bottom: 0,
- // @private
- defaultType: 'pickerslot',
- /**
- * @cfg {Ext.TitleBar/Ext.Toolbar/Object} toolbar
- * The toolbar which contains the {@link #doneButton} and {@link #cancelButton} buttons.
- * You can override this if you wish, and add your own configurations. Just ensure that you take into account
- * the {@link #doneButton} and {@link #cancelButton} configurations.
- *
- * The default xtype is a {@link Ext.TitleBar}:
- *
- * toolbar: {
- * items: [
- * {
- * xtype: 'button',
- * text: 'Left',
- * align: 'left'
- * },
- * {
- * xtype: 'button',
- * text: 'Right',
- * align: 'left'
- * }
- * ]
- * }
- *
- * Or to use a {@link Ext.Toolbar instead}:
- *
- * toolbar: {
- * xtype: 'toolbar',
- * items: [
- * {
- * xtype: 'button',
- * text: 'Left'
- * },
- * {
- * xtype: 'button',
- * text: 'Left Two'
- * }
- * ]
- * }
- *
- * @accessor
- */
- toolbar: true
- },
- initElement: function() {
- this.callParent(arguments);
- var me = this,
- clsPrefix = Ext.baseCSSPrefix,
- innerElement = this.innerElement;
- //insert the mask, and the picker bar
- this.mask = innerElement.createChild({
- cls: clsPrefix + 'picker-mask'
- });
- this.bar = this.mask.createChild({
- cls: clsPrefix + 'picker-bar'
- });
- me.on({
- scope : this,
- delegate: 'pickerslot',
- slotpick: 'onSlotPick'
- });
- me.on({
- scope: this,
- show: 'onShow'
- });
- },
- /**
- * @private
- */
- applyToolbar: function(config) {
- if (config === true) {
- config = {};
- }
- Ext.applyIf(config, {
- docked: 'top'
- });
- return Ext.factory(config, 'Ext.TitleBar', this.getToolbar());
- },
- /**
- * @private
- */
- updateToolbar: function(newToolbar, oldToolbar) {
- if (newToolbar) {
- this.add(newToolbar);
- }
- if (oldToolbar) {
- this.remove(oldToolbar);
- }
- },
- /**
- * Updates the {@link #doneButton} configuration. Will change it into a button when appropriate, or just update the text if needed.
- * @param {Object} config
- * @return {Object}
- */
- applyDoneButton: function(config) {
- if (config) {
- if (Ext.isBoolean(config)) {
- config = {};
- }
- if (typeof config == "string") {
- config = {
- text: config
- };
- }
- Ext.applyIf(config, {
- ui: 'action',
- align: 'right',
- text: 'Done'
- });
- }
- return Ext.factory(config, 'Ext.Button', this.getDoneButton());
- },
- updateDoneButton: function(newDoneButton, oldDoneButton) {
- var toolbar = this.getToolbar();
- if (newDoneButton) {
- toolbar.add(newDoneButton);
- newDoneButton.on('tap', this.onDoneButtonTap, this);
- } else if (oldDoneButton) {
- toolbar.remove(oldDoneButton);
- }
- },
- /**
- * Updates the {@link #cancelButton} configuration. Will change it into a button when appropriate, or just update the text if needed.
- * @param {Object} config
- * @return {Object}
- */
- applyCancelButton: function(config) {
- if (config) {
- if (Ext.isBoolean(config)) {
- config = {};
- }
- if (typeof config == "string") {
- config = {
- text: config
- };
- }
- Ext.applyIf(config, {
- align: 'left',
- text: 'Cancel'
- });
- }
- return Ext.factory(config, 'Ext.Button', this.getCancelButton());
- },
- updateCancelButton: function(newCancelButton, oldCancelButton) {
- var toolbar = this.getToolbar();
- if (newCancelButton) {
- toolbar.add(newCancelButton);
- newCancelButton.on('tap', this.onCancelButtonTap, this);
- } else if (oldCancelButton) {
- toolbar.remove(oldCancelButton);
- }
- },
- /**
- * @private
- */
- updateUseTitles: function(useTitles) {
- var innerItems = this.getInnerItems(),
- ln = innerItems.length,
- cls = Ext.baseCSSPrefix + 'use-titles',
- i, innerItem;
- //add a cls onto the picker
- if (useTitles) {
- this.addCls(cls);
- } else {
- this.removeCls(cls);
- }
- //show the time on each of the slots
- for (i = 0; i < ln; i++) {
- innerItem = innerItems[i];
- if (innerItem.isSlot) {
- innerItem.setShowTitle(useTitles);
- }
- }
- },
- applySlots: function(slots) {
- //loop through each of the slots and add a reference to this picker
- if (slots) {
- var ln = slots.length,
- i;
- for (i = 0; i < ln; i++) {
- slots[i].picker = this;
- }
- }
- return slots;
- },
- /**
- * Adds any new {@link #slots} to this picker, and removes existing {@link #slots}
- * @private
- */
- updateSlots: function(newSlots) {
- var bcss = Ext.baseCSSPrefix,
- innerItems;
- this.removeAll();
- if (newSlots) {
- this.add(newSlots);
- }
- innerItems = this.getInnerItems();
- if (innerItems.length > 0) {
- innerItems[0].addCls(bcss + 'first');
- innerItems[innerItems.length - 1].addCls(bcss + 'last');
- }
- this.updateUseTitles(this.getUseTitles());
- },
- /**
- * @private
- * Called when the done button has been tapped.
- */
- onDoneButtonTap: function() {
- var oldValue = this._value,
- newValue = this.getValue(true);
- if (newValue != oldValue) {
- this.fireEvent('change', this, newValue);
- }
- this.hide();
- },
- /**
- * @private
- * Called when the cancel button has been tapped.
- */
- onCancelButtonTap: function() {
- this.fireEvent('cancel', this);
- this.hide();
- },
- /**
- * @private
- * Called when a slot has been picked.
- */
- onSlotPick: function(slot) {
- this.fireEvent('pick', this, this.getValue(true), slot);
- },
- onShow: function() {
- if (!this.isHidden()) {
- this.setValue(this._value);
- }
- },
- /**
- * Sets the values of the pickers slots.
- * @param {Object} values The values in a {name:'value'} format.
- * @param {Boolean} animated `true` to animate setting the values.
- * @return {Ext.Picker} this This picker.
- */
- setValue: function(values, animated) {
- var me = this,
- slots = me.getInnerItems(),
- ln = slots.length,
- key, slot, loopSlot, i, value;
- if (!values) {
- values = {};
- for (i = 0; i < ln; i++) {
- //set the value to false so the slot will return null when getValue is called
- values[slots[i].config.name] = null;
- }
- }
- for (key in values) {
- slot = null;
- value = values[key];
- for (i = 0; i < slots.length; i++) {
- loopSlot = slots[i];
- if (loopSlot.config.name == key) {
- slot = loopSlot;
- break;
- }
- }
- if (slot) {
- if (animated) {
- slot.setValueAnimated(value);
- } else {
- slot.setValue(value);
- }
- }
- }
- me._values = me._value = values;
- return me;
- },
- setValueAnimated: function(values) {
- this.setValue(values, true);
- },
- /**
- * Returns the values of each of the pickers slots
- * @return {Object} The values of the pickers slots
- */
- getValue: function(useDom) {
- var values = {},
- items = this.getItems().items,
- ln = items.length,
- item, i;
- if (useDom) {
- for (i = 0; i < ln; i++) {
- item = items[i];
- if (item && item.isSlot) {
- values[item.getName()] = item.getValue(useDom);
- }
- }
- this._values = values;
- }
- return this._values;
- },
- /**
- * Returns the values of each of the pickers slots.
- * @return {Object} The values of the pickers slots.
- */
- getValues: function() {
- return this.getValue();
- },
- destroy: function() {
- this.callParent();
- Ext.destroy(this.mask, this.bar);
- }
- }, function() {
- });
- /**
- * A date picker component which shows a Date Picker on the screen. This class extends from {@link Ext.picker.Picker}
- * and {@link Ext.Sheet} so it is a popup.
- *
- * This component has no required configurations.
- *
- * ## Examples
- *
- * @example miniphone preview
- * var datePicker = Ext.create('Ext.picker.Date');
- * Ext.Viewport.add(datePicker);
- * datePicker.show();
- *
- * You may want to adjust the {@link #yearFrom} and {@link #yearTo} properties:
- *
- * @example miniphone preview
- * var datePicker = Ext.create('Ext.picker.Date', {
- * yearFrom: 2000,
- * yearTo : 2015
- * });
- * Ext.Viewport.add(datePicker);
- * datePicker.show();
- *
- * You can set the value of the {@link Ext.picker.Date} to the current date using `new Date()`:
- *
- * @example miniphone preview
- * var datePicker = Ext.create('Ext.picker.Date', {
- * value: new Date()
- * });
- * Ext.Viewport.add(datePicker);
- * datePicker.show();
- *
- * And you can hide the titles from each of the slots by using the {@link #useTitles} configuration:
- *
- * @example miniphone preview
- * var datePicker = Ext.create('Ext.picker.Date', {
- * useTitles: false
- * });
- * Ext.Viewport.add(datePicker);
- * datePicker.show();
- */
- Ext.define('Ext.picker.Date', {
- extend: 'Ext.picker.Picker',
- xtype: 'datepicker',
- alternateClassName: 'Ext.DatePicker',
- requires: ['Ext.DateExtras'],
- /**
- * @event change
- * Fired when the value of this picker has changed and the done button is pressed.
- * @param {Ext.picker.Date} this This Picker
- * @param {Date} value The date value
- */
- config: {
- /**
- * @cfg {Number} yearFrom
- * The start year for the date picker. If {@link #yearFrom} is greater than
- * {@link #yearTo} then the order of years will be reversed.
- * @accessor
- */
- yearFrom: 1980,
- /**
- * @cfg {Number} [yearTo=new Date().getFullYear()]
- * The last year for the date picker. If {@link #yearFrom} is greater than
- * {@link #yearTo} then the order of years will be reversed.
- * @accessor
- */
- yearTo: new Date().getFullYear(),
- /**
- * @cfg {String} monthText
- * The label to show for the month column.
- * @accessor
- */
- monthText: 'Month',
- /**
- * @cfg {String} dayText
- * The label to show for the day column.
- * @accessor
- */
- dayText: 'Day',
- /**
- * @cfg {String} yearText
- * The label to show for the year column.
- * @accessor
- */
- yearText: 'Year',
- /**
- * @cfg {Array} slotOrder
- * An array of strings that specifies the order of the slots.
- * @accessor
- */
- slotOrder: ['month', 'day', 'year']
- /**
- * @cfg {Object/Date} value
- * Default value for the field and the internal {@link Ext.picker.Date} component. Accepts an object of 'year',
- * 'month' and 'day' values, all of which should be numbers, or a {@link Date}.
- *
- * Examples:
- *
- * - `{year: 1989, day: 1, month: 5}` = 1st May 1989
- * - `new Date()` = current date
- * @accessor
- */
- /**
- * @cfg {Array} slots
- * @hide
- * @accessor
- */
- },
- initialize: function() {
- this.callParent();
- this.on({
- scope: this,
- delegate: '> slot',
- slotpick: this.onSlotPick
- });
- this.on({
- scope: this,
- show: this.onSlotPick
- });
- },
- setValue: function(value, animated) {
- if (Ext.isDate(value)) {
- value = {
- day : value.getDate(),
- month: value.getMonth() + 1,
- year : value.getFullYear()
- };
- }
- this.callParent([value, animated]);
- },
- getValue: function(useDom) {
- var values = {},
- items = this.getItems().items,
- ln = items.length,
- daysInMonth, day, month, year, item, i;
- for (i = 0; i < ln; i++) {
- item = items[i];
- if (item instanceof Ext.picker.Slot) {
- values[item.getName()] = item.getValue(useDom);
- }
- }
- //if all the slots return null, we should not return a date
- if (values.year === null && values.month === null && values.day === null) {
- return null;
- }
- year = Ext.isNumber(values.year) ? values.year : 1;
- month = Ext.isNumber(values.month) ? values.month : 1;
- day = Ext.isNumber(values.day) ? values.day : 1;
- if (month && year && month && day) {
- daysInMonth = this.getDaysInMonth(month, year);
- }
- day = (daysInMonth) ? Math.min(day, daysInMonth): day;
- return new Date(year, month - 1, day);
- },
- /**
- * Updates the yearFrom configuration
- */
- updateYearFrom: function() {
- if (this.initialized) {
- this.createSlots();
- }
- },
- /**
- * Updates the yearTo configuration
- */
- updateYearTo: function() {
- if (this.initialized) {
- this.createSlots();
- }
- },
- /**
- * Updates the monthText configuration
- */
- updateMonthText: function(newMonthText, oldMonthText) {
- var innerItems = this.getInnerItems,
- ln = innerItems.length,
- item, i;
- //loop through each of the current items and set the title on the correct slice
- if (this.initialized) {
- for (i = 0; i < ln; i++) {
- item = innerItems[i];
- if ((typeof item.title == "string" && item.title == oldMonthText) || (item.title.html == oldMonthText)) {
- item.setTitle(newMonthText);
- }
- }
- }
- },
- /**
- * Updates the {@link #dayText} configuration.
- */
- updateDayText: function(newDayText, oldDayText) {
- var innerItems = this.getInnerItems,
- ln = innerItems.length,
- item, i;
- //loop through each of the current items and set the title on the correct slice
- if (this.initialized) {
- for (i = 0; i < ln; i++) {
- item = innerItems[i];
- if ((typeof item.title == "string" && item.title == oldDayText) || (item.title.html == oldDayText)) {
- item.setTitle(newDayText);
- }
- }
- }
- },
- /**
- * Updates the yearText configuration
- */
- updateYearText: function(yearText) {
- var innerItems = this.getInnerItems,
- ln = innerItems.length,
- item, i;
- //loop through each of the current items and set the title on the correct slice
- if (this.initialized) {
- for (i = 0; i < ln; i++) {
- item = innerItems[i];
- if (item.title == this.yearText) {
- item.setTitle(yearText);
- }
- }
- }
- },
- // @private
- constructor: function() {
- this.callParent(arguments);
- this.createSlots();
- },
- /**
- * Generates all slots for all years specified by this component, and then sets them on the component
- * @private
- */
- createSlots: function() {
- var me = this,
- slotOrder = me.getSlotOrder(),
- yearsFrom = me.getYearFrom(),
- yearsTo = me.getYearTo(),
- years = [],
- days = [],
- months = [],
- reverse = yearsFrom > yearsTo,
- ln, i, daysInMonth;
- while (yearsFrom) {
- years.push({
- text : yearsFrom,
- value : yearsFrom
- });
- if (yearsFrom === yearsTo) {
- break;
- }
- if (reverse) {
- yearsFrom--;
- } else {
- yearsFrom++;
- }
- }
- daysInMonth = me.getDaysInMonth(1, new Date().getFullYear());
- for (i = 0; i < daysInMonth; i++) {
- days.push({
- text : i + 1,
- value : i + 1
- });
- }
- for (i = 0, ln = Ext.Date.monthNames.length; i < ln; i++) {
- months.push({
- text : Ext.Date.monthNames[i],
- value : i + 1
- });
- }
- var slots = [];
- slotOrder.forEach(function (item) {
- slots.push(me.createSlot(item, days, months, years));
- });
- me.setSlots(slots);
- },
- /**
- * Returns a slot config for a specified date.
- * @private
- */
- createSlot: function(name, days, months, years) {
- switch (name) {
- case 'year':
- return {
- name: 'year',
- align: 'center',
- data: years,
- title: this.getYearText(),
- flex: 3
- };
- case 'month':
- return {
- name: name,
- align: 'right',
- data: months,
- title: this.getMonthText(),
- flex: 4
- };
- case 'day':
- return {
- name: 'day',
- align: 'center',
- data: days,
- title: this.getDayText(),
- flex: 2
- };
- }
- },
- onSlotPick: function() {
- var value = this.getValue(true),
- slot = this.getDaySlot(),
- year = value.getFullYear(),
- month = value.getMonth(),
- days = [],
- daysInMonth, i;
- if (!value || !Ext.isDate(value) || !slot) {
- return;
- }
- this.callParent();
- //get the new days of the month for this new date
- daysInMonth = this.getDaysInMonth(month + 1, year);
- for (i = 0; i < daysInMonth; i++) {
- days.push({
- text: i + 1,
- value: i + 1
- });
- }
- // We don't need to update the slot days unless it has changed
- if (slot.getData().length == days.length) {
- return;
- }
- slot.setData(days);
- // Now we have the correct amount of days for the day slot, lets update it
- var store = slot.getStore(),
- viewItems = slot.getViewItems(),
- valueField = slot.getValueField(),
- index, item;
- index = store.find(valueField, value.getDate());
- if (index == -1) {
- return;
- }
- item = Ext.get(viewItems[index]);
- slot.selectedIndex = index;
- slot.scrollToItem(item);
- // slot._value = value;
- },
- getDaySlot: function() {
- var innerItems = this.getInnerItems(),
- ln = innerItems.length,
- i, slot;
- if (this.daySlot) {
- return this.daySlot;
- }
- for (i = 0; i < ln; i++) {
- slot = innerItems[i];
- if (slot.isSlot && slot.getName() == "day") {
- this.daySlot = slot;
- return slot;
- }
- }
- return null;
- },
- // @private
- getDaysInMonth: function(month, year) {
- var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
- return month == 2 && this.isLeapYear(year) ? 29 : daysInMonth[month-1];
- },
- // @private
- isLeapYear: function(year) {
- return !!((year & 3) === 0 && (year % 100 || (year % 400 === 0 && year)));
- },
- onDoneButtonTap: function() {
- var oldValue = this._value,
- newValue = this.getValue(true),
- testValue = newValue;
- if (Ext.isDate(newValue)) {
- testValue = newValue.toDateString();
- }
- if (Ext.isDate(oldValue)) {
- oldValue = oldValue.toDateString();
- }
- if (testValue != oldValue) {
- this.fireEvent('change', this, newValue);
- }
- this.hide();
- }
- });
- /**
- * @aside guide forms
- *
- * This is a specialized field which shows a {@link Ext.picker.Date} when tapped. If it has a predefined value,
- * or a value is selected in the {@link Ext.picker.Date}, it will be displayed like a normal {@link Ext.field.Text}
- * (but not selectable/changable).
- *
- * Ext.create('Ext.field.DatePicker', {
- * label: 'Birthday',
- * value: new Date()
- * });
- *
- * {@link Ext.field.DatePicker} fields are very simple to implement, and have no required configurations.
- *
- * ## Examples
- *
- * It can be very useful to set a default {@link #value} configuration on {@link Ext.field.DatePicker} fields. In
- * this example, we set the {@link #value} to be the current date. You can also use the {@link #setValue} method to
- * update the value at any time.
- *
- * @example miniphone preview
- * Ext.create('Ext.form.Panel', {
- * fullscreen: true,
- * items: [
- * {
- * xtype: 'fieldset',
- * items: [
- * {
- * xtype: 'datepickerfield',
- * label: 'Birthday',
- * name: 'birthday',
- * value: new Date()
- * }
- * ]
- * },
- * {
- * xtype: 'toolbar',
- * docked: 'bottom',
- * items: [
- * { xtype: 'spacer' },
- * {
- * text: 'setValue',
- * handler: function() {
- * var datePickerField = Ext.ComponentQuery.query('datepickerfield')[0];
- *
- * var randomNumber = function(from, to) {
- * return Math.floor(Math.random() * (to - from + 1) + from);
- * };
- *
- * datePickerField.setValue({
- * month: randomNumber(0, 11),
- * day : randomNumber(0, 28),
- * year : randomNumber(1980, 2011)
- * });
- * }
- * },
- * { xtype: 'spacer' }
- * ]
- * }
- * ]
- * });
- *
- * When you need to retrieve the date from the {@link Ext.field.DatePicker}, you can either use the {@link #getValue} or
- * {@link #getFormattedValue} methods:
- *
- * @example preview
- * Ext.create('Ext.form.Panel', {
- * fullscreen: true,
- * items: [
- * {
- * xtype: 'fieldset',
- * items: [
- * {
- * xtype: 'datepickerfield',
- * label: 'Birthday',
- * name: 'birthday',
- * value: new Date()
- * }
- * ]
- * },
- * {
- * xtype: 'toolbar',
- * docked: 'bottom',
- * items: [
- * {
- * text: 'getValue',
- * handler: function() {
- * var datePickerField = Ext.ComponentQuery.query('datepickerfield')[0];
- * Ext.Msg.alert(null, datePickerField.getValue());
- * }
- * },
- * { xtype: 'spacer' },
- * {
- * text: 'getFormattedValue',
- * handler: function() {
- * var datePickerField = Ext.ComponentQuery.query('datepickerfield')[0];
- * Ext.Msg.alert(null, datePickerField.getFormattedValue());
- * }
- * }
- * ]
- * }
- * ]
- * });
- *
- *
- */
- Ext.define('Ext.field.DatePicker', {
- extend: 'Ext.field.Text',
- alternateClassName: 'Ext.form.DatePicker',
- xtype: 'datepickerfield',
- requires: [
- 'Ext.picker.Date',
- 'Ext.DateExtras'
- ],
- /**
- * @event change
- * Fires when a date is selected
- * @param {Ext.field.DatePicker} this
- * @param {Date} newDate The new date
- * @param {Date} oldDate The old date
- */
- config: {
- ui: 'select',
- /**
- * @cfg {Object/Ext.picker.Date} picker
- * An object that is used when creating the internal {@link Ext.picker.Date} component or a direct instance of {@link Ext.picker.Date}.
- * @accessor
- */
- picker: true,
- /**
- * @cfg {Boolean}
- * @hide
- * @accessor
- */
- clearIcon: false,
- /**
- * @cfg {Object/Date} value
- * Default value for the field and the internal {@link Ext.picker.Date} component. Accepts an object of 'year',
- * 'month' and 'day' values, all of which should be numbers, or a {@link Date}.
- *
- * Example: {year: 1989, day: 1, month: 5} = 1st May 1989 or new Date()
- * @accessor
- */
- /**
- * @cfg {Boolean} destroyPickerOnHide
- * Whether or not to destroy the picker widget on hide. This save memory if it's not used frequently,
- * but increase delay time on the next show due to re-instantiation.
- * @accessor
- */
- destroyPickerOnHide: false,
- /**
- * @cfg {String} [dateFormat=Ext.util.Format.defaultDateFormat] The format to be used when displaying the date in this field.
- * Accepts any valid date format. You can view formats over in the {@link Ext.Date} documentation.
- */
- dateFormat: null,
- /**
- * @cfg {Object}
- * @hide
- */
- component: {
- useMask: true
- }
- },
- initialize: function() {
- var me = this,
- component = me.getComponent();
- me.callParent();
- component.on({
- scope: me,
- masktap: 'onMaskTap'
- });
- if (Ext.os.is.Android2) {
- component.input.dom.disabled = true;
- }
- },
- syncEmptyCls: Ext.emptyFn,
- applyValue: function(value) {
- if (!Ext.isDate(value) && !Ext.isObject(value)) {
- return null;
- }
- if (Ext.isObject(value)) {
- return new Date(value.year, value.month - 1, value.day);
- }
- return value;
- },
- updateValue: function(newValue, oldValue) {
- var me = this,
- picker = me._picker;
- if (picker && picker.isPicker) {
- picker.setValue(newValue);
- }
- // Ext.Date.format expects a Date
- if (newValue !== null) {
- me.getComponent().setValue(Ext.Date.format(newValue, me.getDateFormat() || Ext.util.Format.defaultDateFormat));
- } else {
- me.getComponent().setValue('');
- }
- if (newValue !== oldValue) {
- me.fireEvent('change', me, newValue, oldValue);
- }
- },
- /**
- * Updates the date format in the field.
- * @private
- */
- updateDateFormat: function(newDateFormat, oldDateFormat) {
- var value = this.getValue();
- if (newDateFormat != oldDateFormat && Ext.isDate(value)) {
- this.getComponent().setValue(Ext.Date.format(value, newDateFormat || Ext.util.Format.defaultDateFormat));
- }
- },
- /**
- * Returns the {@link Date} value of this field.
- * If you wanted a formated date
- * @return {Date} The date selected
- */
- getValue: function() {
- if (this._picker && this._picker instanceof Ext.picker.Date) {
- return this._picker.getValue();
- }
- return this._value;
- },
- /**
- * Returns the value of the field formatted using the specified format. If it is not specified, it will default to
- * {@link #dateFormat} and then {@link Ext.util.Format#defaultDateFormat}.
- * @param {String} format The format to be returned.
- * @return {String} The formatted date.
- */
- getFormattedValue: function(format) {
- var value = this.getValue();
- return (Ext.isDate(value)) ? Ext.Date.format(value, format || this.getDateFormat() || Ext.util.Format.defaultDateFormat) : value;
- },
- applyPicker: function(picker, pickerInstance) {
- if (pickerInstance && pickerInstance.isPicker) {
- picker = pickerInstance.setConfig(picker);
- }
- return picker;
- },
- getPicker: function() {
- var picker = this._picker,
- value = this.getValue();
- if (picker && !picker.isPicker) {
- picker = Ext.factory(picker, Ext.picker.Date);
- if (value != null) {
- picker.setValue(value);
- }
- }
- picker.on({
- scope: this,
- change: 'onPickerChange',
- hide : 'onPickerHide'
- });
- Ext.Viewport.add(picker);
- this._picker = picker;
- return picker;
- },
- /**
- * @private
- * Listener to the tap event of the mask element. Shows the internal DatePicker component when the button has been tapped.
- */
- onMaskTap: function() {
- if (this.getDisabled()) {
- return false;
- }
- this.onFocus();
- return false;
- },
- /**
- * Called when the picker changes its value.
- * @param {Ext.picker.Date} picker The date picker.
- * @param {Object} value The new value from the date picker.
- * @private
- */
- onPickerChange: function(picker, value) {
- var me = this,
- oldValue = me.getValue();
- me.setValue(value);
- me.fireEvent('select', me, value);
- me.onChange(me, value, oldValue);
- },
- /**
- * Override this or change event will be fired twice. change event is fired in updateValue
- * for this field. TOUCH-2861
- */
- onChange: Ext.emptyFn,
- /**
- * Destroys the picker when it is hidden, if
- * {@link Ext.field.DatePicker#destroyPickerOnHide destroyPickerOnHide} is set to `true`.
- * @private
- */
- onPickerHide: function() {
- var me = this,
- picker = me.getPicker();
- if (me.getDestroyPickerOnHide() && picker) {
- picker.destroy();
- me._picker = me.getInitialConfig().picker || true;
- }
- },
- reset: function() {
- this.setValue(this.originalValue);
- },
- onFocus: function(e) {
- var component = this.getComponent();
- this.fireEvent('focus', this, e);
- if (Ext.os.is.Android4) {
- component.input.dom.focus();
- }
- component.input.dom.blur();
- if (this.getReadOnly()) {
- return false;
- }
- this.isFocused = true;
- this.getPicker().show();
- },
- // @private
- destroy: function() {
- var picker = this._picker;
- if (picker && picker.isPicker) {
- picker.destroy();
- }
- this.callParent(arguments);
- }
- });
- /**
- * @aside guide forms
- *
- * The Email field creates an HTML5 email input and is usually created inside a form. Because it creates an HTML email
- * input field, most browsers will show a specialized virtual keyboard for email address input. Aside from that, the
- * email field is just a normal text field. Here's an example of how to use it in a form:
- *
- * @example
- * Ext.create('Ext.form.Panel', {
- * fullscreen: true,
- * items: [
- * {
- * xtype: 'fieldset',
- * title: 'Register',
- * items: [
- * {
- * xtype: 'emailfield',
- * label: 'Email',
- * name: 'email'
- * },
- * {
- * xtype: 'passwordfield',
- * label: 'Password',
- * name: 'password'
- * }
- * ]
- * }
- * ]
- * });
- *
- * Or on its own, outside of a form:
- *
- * Ext.create('Ext.field.Email', {
- * label: 'Email address',
- * value: 'prefilled@email.com'
- * });
- *
- * Because email field inherits from {@link Ext.field.Text textfield} it gains all of the functionality that text fields
- * provide, including getting and setting the value at runtime, validations and various events that are fired as the
- * user interacts with the component. Check out the {@link Ext.field.Text} docs to see the additional functionality
- * available.
- */
- Ext.define('Ext.field.Email', {
- extend: 'Ext.field.Text',
- alternateClassName: 'Ext.form.Email',
- xtype: 'emailfield',
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- component: {
- type: 'email'
- },
- /**
- * @cfg
- * @inheritdoc
- */
- autoCapitalize: false
- }
- });
- /**
- * @aside guide forms
- *
- * Hidden fields allow you to easily inject additional data into a {@link Ext.form.Panel form} without displaying
- * additional fields on the screen. This is often useful for sending dynamic or previously collected data back to the
- * server in the same request as the normal form submission. For example, here is how we might set up a form to send
- * back a hidden userId field:
- *
- * @example
- * var form = Ext.create('Ext.form.Panel', {
- * fullscreen: true,
- * items: [
- * {
- * xtype: 'fieldset',
- * title: 'Enter your name',
- * items: [
- * {
- * xtype: 'hiddenfield',
- * name: 'userId',
- * value: 123
- * },
- * {
- * xtype: 'checkboxfield',
- * label: 'Enable notifications',
- * name: 'notifications'
- * }
- * ]
- * }
- * ]
- * });
- *
- * In the form above we created two fields - a hidden field and a {@link Ext.field.Checkbox check box field}. Only the
- * check box will be visible, but both fields will be submitted. Hidden fields cannot be tabbed to - they are removed
- * from the tab index so when your user taps the next/previous field buttons the hidden field is skipped over.
- *
- * It's easy to read and update the value of a hidden field within a form. Using the example above, we can get a
- * reference to the hidden field and then set it to a new value in 2 lines of code:
- *
- * var userId = form.down('hiddenfield')[0];
- * userId.setValue(1234);
- */
- Ext.define('Ext.field.Hidden', {
- extend: 'Ext.field.Text',
- alternateClassName: 'Ext.form.Hidden',
- xtype: 'hiddenfield',
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- component: {
- xtype: 'input',
- type : 'hidden'
- },
- /**
- * @cfg
- * @inheritdoc
- */
- ui: 'hidden',
- /**
- * @cfg hidden
- * @hide
- */
- hidden: true,
- /**
- * @cfg {Number} tabIndex
- * @hide
- */
- tabIndex: -1
- }
- });
- /**
- * @aside guide forms
- *
- * The Number field creates an HTML5 number input and is usually created inside a form. Because it creates an HTML
- * number input field, most browsers will show a specialized virtual keyboard for entering numbers. The Number field
- * only accepts numerical input and also provides additional spinner UI that increases or decreases the current value
- * by a configured {@link #stepValue step value}. Here's how we might use one in a form:
- *
- * @example
- * Ext.create('Ext.form.Panel', {
- * fullscreen: true,
- * items: [
- * {
- * xtype: 'fieldset',
- * title: 'How old are you?',
- * items: [
- * {
- * xtype: 'numberfield',
- * label: 'Age',
- * minValue: 18,
- * maxValue: 150,
- * name: 'age'
- * }
- * ]
- * }
- * ]
- * });
- *
- * Or on its own, outside of a form:
- *
- * Ext.create('Ext.field.Number', {
- * label: 'Age',
- * value: '26'
- * });
- *
- * ## minValue, maxValue and stepValue
- *
- * The {@link #minValue} and {@link #maxValue} configurations are self-explanatory and simply constrain the value
- * entered to the range specified by the configured min and max values. The other option exposed by this component
- * is {@link #stepValue}, which enables you to set how much the value changes every time the up and down spinners
- * are tapped on. For example, to create a salary field that ticks up and down by $1,000 each tap we can do this:
- *
- * @example
- * Ext.create('Ext.form.Panel', {
- * fullscreen: true,
- * items: [
- * {
- * xtype: 'fieldset',
- * title: 'Are you rich yet?',
- * items: [
- * {
- * xtype: 'numberfield',
- * label: 'Salary',
- * value: 30000,
- * minValue: 25000,
- * maxValue: 50000,
- * stepValue: 1000
- * }
- * ]
- * }
- * ]
- * });
- *
- * This creates a field that starts with a value of $30,000, steps up and down in $1,000 increments and will not go
- * beneath $25,000 or above $50,000.
- *
- * Because number field inherits from {@link Ext.field.Text textfield} it gains all of the functionality that text
- * fields provide, including getting and setting the value at runtime, validations and various events that are fired as
- * the user interacts with the component. Check out the {@link Ext.field.Text} docs to see the additional functionality
- * available.
- */
- Ext.define('Ext.field.Number', {
- extend: 'Ext.field.Text',
- xtype: 'numberfield',
- alternateClassName: 'Ext.form.Number',
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- component: {
- type: 'number'
- },
- /**
- * @cfg
- * @inheritdoc
- */
- ui: 'number'
- },
- proxyConfig: {
- /**
- * @cfg {Number} minValue The minimum value that this Number field can accept
- * @accessor
- */
- minValue: null,
- /**
- * @cfg {Number} maxValue The maximum value that this Number field can accept
- * @accessor
- */
- maxValue: null,
- /**
- * @cfg {Number} stepValue The amount by which the field is incremented or decremented each time the spinner is tapped.
- * Defaults to undefined, which means that the field goes up or down by 1 each time the spinner is tapped
- * @accessor
- */
- stepValue: null
- },
- applyValue: function(value) {
- var minValue = this.getMinValue(),
- maxValue = this.getMaxValue();
- if (Ext.isNumber(minValue)) {
- value = Math.max(value, minValue);
- }
- if (Ext.isNumber(maxValue)) {
- value = Math.min(value, maxValue);
- }
- value = parseFloat(value);
- return (isNaN(value)) ? '' : value;
- },
- getValue: function() {
- var value = parseFloat(this.callParent(), 10);
- return (isNaN(value)) ? null : value;
- },
- doClearIconTap: function(me, e) {
- me.getComponent().setValue('');
- me.getValue();
- me.hideClearIcon();
- }
- });
- /**
- * @aside guide forms
- *
- * The Password field creates a password input and is usually created inside a form. Because it creates a password
- * field, when the user enters text it will show up as stars. Aside from that, the password field is just a normal text
- * field. Here's an example of how to use it in a form:
- *
- * @example
- * Ext.create('Ext.form.Panel', {
- * fullscreen: true,
- * items: [
- * {
- * xtype: 'fieldset',
- * title: 'Register',
- * items: [
- * {
- * xtype: 'emailfield',
- * label: 'Email',
- * name: 'email'
- * },
- * {
- * xtype: 'passwordfield',
- * label: 'Password',
- * name: 'password'
- * }
- * ]
- * }
- * ]
- * });
- *
- * Or on its own, outside of a form:
- *
- * Ext.create('Ext.field.Password', {
- * label: 'Password',
- * value: 'existingPassword'
- * });
- *
- * Because the password field inherits from {@link Ext.field.Text textfield} it gains all of the functionality that text
- * fields provide, including getting and setting the value at runtime, validations and various events that are fired as
- * the user interacts with the component. Check out the {@link Ext.field.Text} docs to see the additional functionality
- * available.
- */
- Ext.define('Ext.field.Password', {
- extend: 'Ext.field.Text',
- xtype: 'passwordfield',
- alternateClassName: 'Ext.form.Password',
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- autoCapitalize: false,
- /**
- * @cfg
- * @inheritdoc
- */
- component: {
- type: 'password'
- }
- }
- });
- /**
- * @aside guide forms
- *
- * The radio field is an enhanced version of the native browser radio controls and is a good way of allowing your user
- * to choose one option out of a selection of several (for example, choosing a favorite color):
- *
- * @example
- * var form = Ext.create('Ext.form.Panel', {
- * fullscreen: true,
- * items: [
- * {
- * xtype: 'radiofield',
- * name : 'color',
- * value: 'red',
- * label: 'Red',
- * checked: true
- * },
- * {
- * xtype: 'radiofield',
- * name : 'color',
- * value: 'green',
- * label: 'Green'
- * },
- * {
- * xtype: 'radiofield',
- * name : 'color',
- * value: 'blue',
- * label: 'Blue'
- * }
- * ]
- * });
- *
- * Above we created a simple form which allows the user to pick a color from the options red, green and blue. Because
- * we gave each of the fields above the same {@link #name}, the radio field ensures that only one of them can be
- * 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
- * will be sent for each group of radio fields with the same name:
- *
- * form.getValues(); //looks like {color: 'red'}
- * form.submit(); //sends a single field back to the server (in this case color: red)
- *
- */
- Ext.define('Ext.field.Radio', {
- extend: 'Ext.field.Checkbox',
- xtype: 'radiofield',
- alternateClassName: 'Ext.form.Radio',
- isRadio: true,
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- ui: 'radio',
- /**
- * @cfg
- * @inheritdoc
- */
- component: {
- type: 'radio',
- cls: Ext.baseCSSPrefix + 'input-radio'
- }
- },
- getValue: function() {
- return (this._value) ? this._value : null;
- },
- setValue: function(value) {
- this._value = value;
- return this;
- },
- getSubmitValue: function() {
- var value = this._value;
- if (typeof value == "undefined" || value == null) {
- value = true;
- }
- return (this.getChecked()) ? value : null;
- },
- updateChecked: function(newChecked) {
- this.getComponent().setChecked(newChecked);
- if (this.initialized) {
- this.refreshGroupValues();
- }
- },
- // @private
- onMaskTap: function(component, e) {
- var me = this,
- dom = component.input.dom;
- if (me.getDisabled()) {
- return false;
- }
- if (!me.getChecked()) {
- dom.checked = true;
- }
- me.refreshGroupValues();
- //return false so the mask does not disappear
- return false;
- },
- /**
- * Returns the selected value if this radio is part of a group (other radio fields with the same name, in the same FormPanel),
- * @return {String}
- */
- getGroupValue: function() {
- var fields = this.getSameGroupFields(),
- ln = fields.length,
- i = 0,
- field;
- for (; i < ln; i++) {
- field = fields[i];
- if (field.getChecked()) {
- return field.getValue();
- }
- }
- return null;
- },
- /**
- * Set the matched radio field's status (that has the same value as the given string) to checked.
- * @param {String} value The value of the radio field to check.
- * @return {Ext.field.Radio} The field that is checked.
- */
- setGroupValue: function(value) {
- var fields = this.getSameGroupFields(),
- ln = fields.length,
- i = 0,
- field;
- for (; i < ln; i++) {
- field = fields[i];
- if (field.getValue() === value) {
- field.setChecked(true);
- return field;
- }
- }
- },
- /**
- * Loops through each of the fields this radiofield is linked to (has the same name) and
- * calls `onChange` on those fields so the appropriate event is fired.
- * @private
- */
- refreshGroupValues: function() {
- var fields = this.getSameGroupFields(),
- ln = fields.length,
- i = 0,
- field;
- for (; i < ln; i++) {
- field = fields[i];
- field.onChange();
- }
- }
- });
- /**
- * @aside guide forms
- *
- * The Search field creates an HTML5 search input and is usually created inside a form. Because it creates an HTML
- * search input type, the visual styling of this input is slightly different to normal text input controls (the corners
- * are rounded), though the virtual keyboard displayed by the operating system is the standard keyboard control.
- *
- * As with all other form fields in Sencha Touch, the search field gains a "clear" button that appears whenever there
- * is text entered into the form, and which removes that text when tapped.
- *
- * @example
- * Ext.create('Ext.form.Panel', {
- * fullscreen: true,
- * items: [
- * {
- * xtype: 'fieldset',
- * title: 'Search',
- * items: [
- * {
- * xtype: 'searchfield',
- * label: 'Query',
- * name: 'query'
- * }
- * ]
- * }
- * ]
- * });
- *
- * Or on its own, outside of a form:
- *
- * Ext.create('Ext.field.Search', {
- * label: 'Search:',
- * value: 'query'
- * });
- *
- * Because search field inherits from {@link Ext.field.Text textfield} it gains all of the functionality that text
- * fields provide, including getting and setting the value at runtime, validations and various events that are fired
- * as the user interacts with the component. Check out the {@link Ext.field.Text} docs to see the additional
- * functionality available.
- */
- Ext.define('Ext.field.Search', {
- extend: 'Ext.field.Text',
- xtype: 'searchfield',
- alternateClassName: 'Ext.form.Search',
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- component: {
- type: 'search'
- },
- /**
- * @cfg
- * @inheritdoc
- */
- ui: 'search'
- }
- });
- /**
- * @aside guide forms
- *
- * Simple Select field wrapper. Example usage:
- *
- * @example
- * Ext.create('Ext.form.Panel', {
- * fullscreen: true,
- * items: [
- * {
- * xtype: 'fieldset',
- * title: 'Select',
- * items: [
- * {
- * xtype: 'selectfield',
- * label: 'Choose one',
- * options: [
- * {text: 'First Option', value: 'first'},
- * {text: 'Second Option', value: 'second'},
- * {text: 'Third Option', value: 'third'}
- * ]
- * }
- * ]
- * }
- * ]
- * });
- */
- Ext.define('Ext.field.Select', {
- extend: 'Ext.field.Text',
- xtype: 'selectfield',
- alternateClassName: 'Ext.form.Select',
- requires: [
- 'Ext.Panel',
- 'Ext.picker.Picker',
- 'Ext.data.Store',
- 'Ext.data.StoreManager',
- 'Ext.dataview.List'
- ],
- /**
- * @event change
- * Fires when an option selection has changed
- * @param {Ext.field.Select} this
- * @param {Mixed} newValue The new value
- * @param {Mixed} oldValue The old value
- */
- /**
- * @event focus
- * 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
- * 'next' or 'tab' on a keyboard.
- *
- * Please note that this event is not very reliable on Android. For example, if your Select field is second in your form panel,
- * you cannot use the Next button to get to this select field. This functionality works as expected on iOS.
- * @param {Ext.field.Select} this This field
- * @param {Ext.event.Event} e
- */
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- ui: 'select',
- /**
- * @cfg {Boolean} useClearIcon
- * @hide
- */
- /**
- * @cfg {String/Number} valueField The underlying {@link Ext.data.Field#name data value name} (or numeric Array index) to bind to this
- * Select control.
- * @accessor
- */
- valueField: 'value',
- /**
- * @cfg {String/Number} displayField The underlying {@link Ext.data.Field#name data value name} (or numeric Array index) to bind to this
- * Select control. This resolved value is the visibly rendered value of the available selection options.
- * @accessor
- */
- displayField: 'text',
- /**
- * @cfg {Ext.data.Store/Object/String} store The store to provide selection options data.
- * Either a Store instance, configuration object or store ID.
- * @accessor
- */
- store: null,
- /**
- * @cfg {Array} options An array of select options.
- *
- * [
- * {text: 'First Option', value: 'first'},
- * {text: 'Second Option', value: 'second'},
- * {text: 'Third Option', value: 'third'}
- * ]
- *
- * __Note:__ Option object member names should correspond with defined {@link #valueField valueField} and {@link #displayField displayField} values.
- * This config will be ignored if a {@link #store store} instance is provided.
- * @accessor
- */
- options: null,
- /**
- * @cfg {String} hiddenName Specify a `hiddenName` if you're using the {@link Ext.form.Panel#standardSubmit standardSubmit} option.
- * This name will be used to post the underlying value of the select to the server.
- * @accessor
- */
- hiddenName: null,
- /**
- * @cfg {Object} component
- * @accessor
- * @hide
- */
- component: {
- useMask: true
- },
- /**
- * @cfg {Boolean} clearIcon
- * @hide
- * @accessor
- */
- clearIcon: false,
- /**
- * @cfg {String/Boolean} usePicker
- * `true` if you want this component to always use a {@link Ext.picker.Picker}.
- * `false` if you want it to use a popup overlay {@link Ext.List}.
- * `auto` if you want to show a {@link Ext.picker.Picker} only on phones.
- */
- usePicker: 'auto',
- /**
- * @cfg {Boolean} autoSelect
- * `true` to auto select the first value in the {@link #store} or {@link #options} when they are changed. Only happens when
- * the {@link #value} is set to `null`.
- */
- autoSelect: true,
- /**
- * @cfg {Object} defaultPhonePickerConfig
- * The default configuration for the picker component when you are on a phone.
- */
- defaultPhonePickerConfig: null,
- /**
- * @cfg {Object} defaultTabletPickerConfig
- * The default configuration for the picker component when you are on a tablet.
- */
- defaultTabletPickerConfig: null,
- /**
- * @cfg
- * @inheritdoc
- */
- name: 'picker'
- },
- // @private
- initialize: function() {
- var me = this,
- component = me.getComponent();
- me.callParent();
- component.on({
- scope: me,
- masktap: 'onMaskTap'
- });
- if (Ext.os.is.Android2) {
- component.input.dom.disabled = true;
- }
- },
- /**
- * @private
- */
- updateDefaultPhonePickerConfig: function(newConfig) {
- var picker = this.picker;
- if (picker) {
- picker.setConfig(newConfig);
- }
- },
- /**
- * @private
- */
- updateDefaultTabletPickerConfig: function(newConfig) {
- var listPanel = this.listPanel;
- if (listPanel) {
- listPanel.setConfig(newConfig);
- }
- },
- /**
- * @private
- * Checks if the value is `auto`. If it is, it only uses the picker if the current device type
- * is a phone.
- */
- applyUsePicker: function(usePicker) {
- if (usePicker == "auto") {
- usePicker = (Ext.os.deviceType == 'Phone');
- }
- return Boolean(usePicker);
- },
- syncEmptyCls: Ext.emptyFn,
- /**
- * @private
- */
- applyValue: function(value) {
- var record = value,
- index, store;
- //we call this so that the options configruation gets intiailized, so that a store exists, and we can
- //find the correct value
- this.getOptions();
- store = this.getStore();
- if ((value != undefined && !value.isModel) && store) {
- index = store.find(this.getValueField(), value, null, null, null, true);
- if (index == -1) {
- index = store.find(this.getDisplayField(), value, null, null, null, true);
- }
- record = store.getAt(index);
- }
- return record;
- },
- updateValue: function(newValue, oldValue) {
- this.record = newValue;
- this.callParent([(newValue && newValue.isModel) ? newValue.get(this.getDisplayField()) : '']);
- },
- getValue: function() {
- var record = this.record;
- return (record && record.isModel) ? record.get(this.getValueField()) : null;
- },
- /**
- * Returns the current selected {@link Ext.data.Model record} instance selected in this field.
- * @return {Ext.data.Model} the record.
- */
- getRecord: function() {
- return this.record;
- },
- // @private
- getPhonePicker: function() {
- var config = this.getDefaultPhonePickerConfig();
- if (!this.picker) {
- this.picker = Ext.create('Ext.picker.Picker', Ext.apply({
- slots: [{
- align : 'center',
- name : this.getName(),
- valueField : this.getValueField(),
- displayField: this.getDisplayField(),
- value : this.getValue(),
- store : this.getStore()
- }],
- listeners: {
- change: this.onPickerChange,
- scope: this
- }
- }, config));
- }
- return this.picker;
- },
- // @private
- getTabletPicker: function() {
- var config = this.getDefaultTabletPickerConfig();
- if (!this.listPanel) {
- this.listPanel = Ext.create('Ext.Panel', Ext.apply({
- left: 0,
- top: 0,
- modal: true,
- cls: Ext.baseCSSPrefix + 'select-overlay',
- layout: 'fit',
- hideOnMaskTap: true,
- width: Ext.os.is.Phone ? '14em' : '18em',
- height: Ext.os.is.Phone ? '12.5em' : '22em',
- items: {
- xtype: 'list',
- store: this.getStore(),
- itemTpl: '<span class="x-list-label">{' + this.getDisplayField() + ':htmlEncode}</span>',
- listeners: {
- select : this.onListSelect,
- itemtap: this.onListTap,
- scope : this
- }
- }
- }, config));
- }
- return this.listPanel;
- },
- // @private
- onMaskTap: function() {
- if (this.getDisabled()) {
- return false;
- }
- this.onFocus();
- return false;
- },
- /**
- * Shows the picker for the select field, whether that is a {@link Ext.picker.Picker} or a simple
- * {@link Ext.List list}.
- */
- showPicker: function() {
- var store = this.getStore();
- //check if the store is empty, if it is, return
- if (!store || store.getCount() === 0) {
- return;
- }
- if (this.getReadOnly()) {
- return;
- }
- this.isFocused = true;
- if (this.getUsePicker()) {
- var picker = this.getPhonePicker(),
- name = this.getName(),
- value = {};
- value[name] = this.getValue();
- picker.setValue(value);
- if (!picker.getParent()) {
- Ext.Viewport.add(picker);
- }
- picker.show();
- } else {
- var listPanel = this.getTabletPicker(),
- list = listPanel.down('list'),
- index, record;
- store = list.getStore();
- index = store.find(this.getValueField(), this.getValue(), null, null, null, true);
- record = store.getAt((index == -1) ? 0 : index);
- if (!listPanel.getParent()) {
- Ext.Viewport.add(listPanel);
- }
- listPanel.showBy(this.getComponent());
- list.select(record, null, true);
- }
- },
- // @private
- onListSelect: function(item, record) {
- var me = this;
- if (record) {
- me.setValue(record);
- }
- },
- onListTap: function() {
- this.listPanel.hide({
- type : 'fade',
- out : true,
- scope: this
- });
- },
- // @private
- onPickerChange: function(picker, value) {
- var me = this,
- newValue = value[me.getName()],
- store = me.getStore(),
- index = store.find(me.getValueField(), newValue, null, null, null, true),
- record = store.getAt(index);
- me.setValue(record);
- },
- onChange: function(component, newValue, oldValue) {
- var me = this,
- store = me.getStore(),
- index = (store) ? store.find(me.getDisplayField(), oldValue) : -1,
- valueField = me.getValueField(),
- record = (store) ? store.getAt(index) : null;
- oldValue = (record) ? record.get(valueField) : null;
- me.fireEvent('change', me, me.getValue(), oldValue);
- },
- /**
- * Updates the underlying `<options>` list with new values.
- * @param {Array} options An array of options configurations to insert or append.
- *
- * selectBox.setOptions([
- * {text: 'First Option', value: 'first'},
- * {text: 'Second Option', value: 'second'},
- * {text: 'Third Option', value: 'third'}
- * ]).setValue('third');
- *
- * __Note:__ option object member names should correspond with defined {@link #valueField valueField} and
- * {@link #displayField displayField} values.
- * @return {Ext.field.Select} this
- */
- updateOptions: function(newOptions) {
- var store = this.getStore();
- if (!store) {
- this.setStore(true);
- store = this._store;
- }
- if (!newOptions) {
- store.clearData();
- }
- else {
- store.setData(newOptions);
- this.onStoreDataChanged(store);
- }
- return this;
- },
- applyStore: function(store) {
- if (store === true) {
- store = Ext.create('Ext.data.Store', {
- fields: [this.getValueField(), this.getDisplayField()],
- autoDestroy: true
- });
- }
- if (store) {
- store = Ext.data.StoreManager.lookup(store);
- store.on({
- scope: this,
- addrecords: 'onStoreDataChanged',
- removerecords: 'onStoreDataChanged',
- updaterecord: 'onStoreDataChanged',
- refresh: 'onStoreDataChanged'
- });
- }
- return store;
- },
- updateStore: function(newStore) {
- if (newStore) {
- this.onStoreDataChanged(newStore);
- }
- if (this.getUsePicker() && this.picker) {
- this.picker.down('pickerslot').setStore(newStore);
- } else if (this.listPanel) {
- this.listPanel.down('dataview').setStore(newStore);
- }
- },
- /**
- * Called when the internal {@link #store}'s data has changed.
- */
- onStoreDataChanged: function(store) {
- var initialConfig = this.getInitialConfig(),
- value = this.getValue();
- if (value || value == 0) {
- this.updateValue(this.applyValue(value));
- }
- if (this.getValue() === null) {
- if (initialConfig.hasOwnProperty('value')) {
- this.setValue(initialConfig.value);
- }
- if (this.getValue() === null && this.getAutoSelect()) {
- if (store.getCount() > 0) {
- this.setValue(store.getAt(0));
- }
- }
- }
- },
- /**
- * @private
- */
- doSetDisabled: function(disabled) {
- Ext.Component.prototype.doSetDisabled.apply(this, arguments);
- },
- /**
- * @private
- */
- setDisabled: function() {
- Ext.Component.prototype.setDisabled.apply(this, arguments);
- },
- /**
- * Resets the Select field to the value of the first record in the store.
- * @return {Ext.field.Select} this
- * @chainable
- */
- reset: function() {
- var store = this.getStore(),
- record = (this.originalValue) ? this.originalValue : store.getAt(0);
- if (store && record) {
- this.setValue(record);
- }
- return this;
- },
- onFocus: function(e) {
- var component = this.getComponent();
- this.fireEvent('focus', this, e);
- if (Ext.os.is.Android4) {
- component.input.dom.focus();
- }
- component.input.dom.blur();
- this.isFocused = true;
- this.showPicker();
- },
- destroy: function () {
- this.callParent(arguments);
- var store = this.getStore();
- if (store && store.getAutoDestroy()) {
- Ext.destroy(store);
- }
- }
- });
- /**
- * @private
- * Utility class used by Ext.slider.Slider - should never need to be used directly.
- */
- Ext.define('Ext.slider.Thumb', {
- extend: 'Ext.Component',
- xtype : 'thumb',
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'thumb',
- /**
- * @cfg
- * @inheritdoc
- */
- draggable: {
- direction: 'horizontal'
- }
- },
- elementWidth: 0,
- initialize: function() {
- this.callParent();
- this.getDraggable().onBefore({
- dragstart: 'onDragStart',
- drag: 'onDrag',
- dragend: 'onDragEnd',
- scope: this
- });
- this.element.on('resize', 'onElementResize', this);
- },
- onDragStart: function() {
- if (this.isDisabled()) {
- return false;
- }
- this.relayEvent(arguments);
- },
- onDrag: function() {
- if (this.isDisabled()) {
- return false;
- }
- this.relayEvent(arguments);
- },
- onDragEnd: function() {
- if (this.isDisabled()) {
- return false;
- }
- this.relayEvent(arguments);
- },
- onElementResize: function(element, info) {
- this.elementWidth = info.width;
- },
- getElementWidth: function() {
- return this.elementWidth;
- }
- });
- /**
- * Utility class used by Ext.field.Slider.
- * @private
- */
- Ext.define('Ext.slider.Slider', {
- extend: 'Ext.Container',
- xtype: 'slider',
- requires: [
- 'Ext.slider.Thumb',
- 'Ext.fx.easing.EaseOut'
- ],
- /**
- * @event change
- * Fires when the value changes
- * @param {Ext.slider.Slider} this
- * @param {Ext.slider.Thumb} thumb The thumb being changed
- * @param {Number} newValue The new value
- * @param {Number} oldValue The old value
- */
- /**
- * @event dragstart
- * Fires when the slider thumb starts a drag
- * @param {Ext.slider.Slider} this
- * @param {Ext.slider.Thumb} thumb The thumb being dragged
- * @param {Array} value The start value
- * @param {Ext.EventObject} e
- */
- /**
- * @event drag
- * Fires when the slider thumb starts a drag
- * @param {Ext.slider.Slider} this
- * @param {Ext.slider.Thumb} thumb The thumb being dragged
- * @param {Ext.EventObject} e
- */
- /**
- * @event dragend
- * Fires when the slider thumb starts a drag
- * @param {Ext.slider.Slider} this
- * @param {Ext.slider.Thumb} thumb The thumb being dragged
- * @param {Array} value The end value
- * @param {Ext.EventObject} e
- */
- config: {
- baseCls: 'x-slider',
- /**
- * @cfg {Object} thumbConfig The config object to factory {@link Ext.slider.Thumb} instances
- * @accessor
- */
- thumbConfig: {
- draggable: {
- translatable: {
- easingX: {
- duration: 300,
- type: 'ease-out'
- }
- }
- }
- },
- /**
- * @cfg {Number} increment The increment by which to snap each thumb when its value changes. Any thumb movement
- * will be snapped to the nearest value that is a multiple of the increment (e.g. if increment is 10 and the user
- * tries to move the thumb to 67, it will be snapped to 70 instead)
- * @accessor
- */
- increment : 1,
- /**
- * @cfg {Number/Number[]} value The value(s) of this slider's thumbs. If you pass
- * a number, it will assume you have just 1 thumb.
- * @accessor
- */
- value: 0,
- /**
- * @cfg {Number} minValue The lowest value any thumb on this slider can be set to.
- * @accessor
- */
- minValue: 0,
- /**
- * @cfg {Number} maxValue The highest value any thumb on this slider can be set to.
- * @accessor
- */
- maxValue: 100,
- /**
- * @cfg {Boolean} allowThumbsOverlapping Whether or not to allow multiple thumbs to overlap each other.
- * Setting this to true guarantees the ability to select every possible value in between {@link #minValue}
- * and {@link #maxValue} that satisfies {@link #increment}
- * @accessor
- */
- allowThumbsOverlapping: false,
- /**
- * @cfg {Boolean/Object} animation
- * The animation to use when moving the slider. Possible properties are:
- *
- * - duration
- * - easingX
- * - easingY
- *
- * @accessor
- */
- animation: true,
- /**
- * Will make this field read only, meaning it cannot be changed with used interaction.
- * @cfg {Boolean} readOnly
- * @accessor
- */
- readOnly: false
- },
- /**
- * @cfg {Number/Number[]} values Alias to {@link #value}
- */
- elementWidth: 0,
- offsetValueRatio: 0,
- activeThumb: null,
- constructor: function(config) {
- config = config || {};
- if (config.hasOwnProperty('values')) {
- config.value = config.values;
- }
- this.callParent([config]);
- },
- // @private
- initialize: function() {
- var element = this.element;
- this.callParent();
- element.on({
- scope: this,
- tap: 'onTap',
- resize: 'onResize'
- });
- this.on({
- scope: this,
- delegate: '> thumb',
- dragstart: 'onThumbDragStart',
- drag: 'onThumbDrag',
- dragend: 'onThumbDragEnd'
- });
- },
- /**
- * @private
- */
- factoryThumb: function() {
- return Ext.factory(this.getThumbConfig(), Ext.slider.Thumb);
- },
- /**
- * Returns the Thumb instances bound to this Slider
- * @return {Ext.slider.Thumb[]} The thumb instances
- */
- getThumbs: function() {
- return this.innerItems;
- },
- /**
- * Returns the Thumb instance bound to this Slider
- * @param {Number} [index=0] The index of Thumb to return.
- * @return {Ext.slider.Thumb} The thumb instance
- */
- getThumb: function(index) {
- if (typeof index != 'number') {
- index = 0;
- }
- return this.innerItems[index];
- },
- refreshOffsetValueRatio: function() {
- var valueRange = this.getMaxValue() - this.getMinValue(),
- trackWidth = this.elementWidth - this.thumbWidth;
- this.offsetValueRatio = trackWidth / valueRange;
- },
- onResize: function(element, info) {
- var thumb = this.getThumb(0);
- if (thumb) {
- this.thumbWidth = thumb.getElementWidth();
- }
- this.elementWidth = info.width;
- this.refresh();
- },
- refresh: function() {
- this.refreshValue();
- },
- setActiveThumb: function(thumb) {
- var oldActiveThumb = this.activeThumb;
- if (oldActiveThumb && oldActiveThumb !== thumb) {
- oldActiveThumb.setZIndex(null);
- }
- this.activeThumb = thumb;
- thumb.setZIndex(2);
- return this;
- },
- onThumbDragStart: function(thumb, e) {
- if (e.absDeltaX <= e.absDeltaY || this.getReadOnly()) {
- return false;
- }
- else {
- e.stopPropagation();
- }
- if (this.getAllowThumbsOverlapping()) {
- this.setActiveThumb(thumb);
- }
- this.dragStartValue = this.getValue()[this.getThumbIndex(thumb)];
- this.fireEvent('dragstart', this, thumb, this.dragStartValue, e);
- },
- onThumbDrag: function(thumb, e, offsetX) {
- var index = this.getThumbIndex(thumb),
- offsetValueRatio = this.offsetValueRatio,
- constrainedValue = this.constrainValue(this.getMinValue() + offsetX / offsetValueRatio);
- e.stopPropagation();
- this.setIndexValue(index, constrainedValue);
- this.fireEvent('drag', this, thumb, this.getValue(), e);
- return false;
- },
- setIndexValue: function(index, value, animation) {
- var thumb = this.getThumb(index),
- values = this.getValue(),
- offsetValueRatio = this.offsetValueRatio,
- draggable = thumb.getDraggable();
- draggable.setOffset((value - this.getMinValue()) * offsetValueRatio, null, animation);
- values[index] = value;
- },
- onThumbDragEnd: function(thumb, e) {
- this.refreshThumbConstraints(thumb);
- var index = this.getThumbIndex(thumb),
- newValue = this.getValue()[index],
- oldValue = this.dragStartValue;
- this.fireEvent('dragend', this, thumb, this.getValue(), e);
- if (oldValue !== newValue) {
- this.fireEvent('change', this, thumb, newValue, oldValue);
- }
- },
- getThumbIndex: function(thumb) {
- return this.getThumbs().indexOf(thumb);
- },
- refreshThumbConstraints: function(thumb) {
- var allowThumbsOverlapping = this.getAllowThumbsOverlapping(),
- offsetX = thumb.getDraggable().getOffset().x,
- thumbs = this.getThumbs(),
- index = this.getThumbIndex(thumb),
- previousThumb = thumbs[index - 1],
- nextThumb = thumbs[index + 1],
- thumbWidth = this.thumbWidth;
- if (previousThumb) {
- previousThumb.getDraggable().addExtraConstraint({
- max: {
- x: offsetX - ((allowThumbsOverlapping) ? 0 : thumbWidth)
- }
- });
- }
- if (nextThumb) {
- nextThumb.getDraggable().addExtraConstraint({
- min: {
- x: offsetX + ((allowThumbsOverlapping) ? 0 : thumbWidth)
- }
- });
- }
- },
- // @private
- onTap: function(e) {
- if (this.isDisabled()) {
- return;
- }
- var targetElement = Ext.get(e.target);
- if (!targetElement || targetElement.hasCls('x-thumb')) {
- return;
- }
- var touchPointX = e.touch.point.x,
- element = this.element,
- elementX = element.getX(),
- offset = touchPointX - elementX - (this.thumbWidth / 2),
- value = this.constrainValue(this.getMinValue() + offset / this.offsetValueRatio),
- values = this.getValue(),
- minDistance = Infinity,
- ln = values.length,
- i, absDistance, testValue, closestIndex, oldValue, thumb;
- if (ln === 1) {
- closestIndex = 0;
- }
- else {
- for (i = 0; i < ln; i++) {
- testValue = values[i];
- absDistance = Math.abs(testValue - value);
- if (absDistance < minDistance) {
- minDistance = absDistance;
- closestIndex = i;
- }
- }
- }
- oldValue = values[closestIndex];
- thumb = this.getThumb(closestIndex);
- this.setIndexValue(closestIndex, value, this.getAnimation());
- this.refreshThumbConstraints(thumb);
- if (oldValue !== value) {
- this.fireEvent('change', this, thumb, value, oldValue);
- }
- },
- // @private
- updateThumbs: function(newThumbs) {
- this.add(newThumbs);
- },
- applyValue: function(value) {
- var values = Ext.Array.from(value || 0),
- filteredValues = [],
- previousFilteredValue = this.getMinValue(),
- filteredValue, i, ln;
- for (i = 0,ln = values.length; i < ln; i++) {
- filteredValue = this.constrainValue(values[i]);
- if (filteredValue < previousFilteredValue) {
- //<debug warn>
- Ext.Logger.warn("Invalid values of '"+Ext.encode(values)+"', values at smaller indexes must " +
- "be smaller than or equal to values at greater indexes");
- //</debug>
- filteredValue = previousFilteredValue;
- }
- filteredValues.push(filteredValue);
- previousFilteredValue = filteredValue;
- }
- return filteredValues;
- },
- /**
- * Updates the sliders thumbs with their new value(s)
- */
- updateValue: function(newValue, oldValue) {
- var thumbs = this.getThumbs(),
- ln = newValue.length,
- minValue = this.getMinValue(),
- offset = this.offsetValueRatio,
- i;
- this.setThumbsCount(ln);
- for (i = 0; i < ln; i++) {
- thumbs[i].getDraggable().setExtraConstraint(null).setOffset((newValue[i] - minValue) * offset);
- }
- for (i = 0; i < ln; i++) {
- this.refreshThumbConstraints(thumbs[i]);
- }
- },
- /**
- * @private
- */
- refreshValue: function() {
- this.refreshOffsetValueRatio();
- this.setValue(this.getValue());
- },
- /**
- * @private
- * Takes a desired value of a thumb and returns the nearest snap value. e.g if minValue = 0, maxValue = 100, increment = 10 and we
- * pass a value of 67 here, the returned value will be 70. The returned number is constrained within {@link #minValue} and {@link #maxValue},
- * so in the above example 68 would be returned if {@link #maxValue} was set to 68.
- * @param {Number} value The value to snap
- * @return {Number} The snapped value
- */
- constrainValue: function(value) {
- var me = this,
- minValue = me.getMinValue(),
- maxValue = me.getMaxValue(),
- increment = me.getIncrement(),
- remainder;
- value = parseFloat(value);
- if (isNaN(value)) {
- value = minValue;
- }
- remainder = (value - minValue) % increment;
- value -= remainder;
- if (Math.abs(remainder) >= (increment / 2)) {
- value += (remainder > 0) ? increment : -increment;
- }
- value = Math.max(minValue, value);
- value = Math.min(maxValue, value);
- return value;
- },
- setThumbsCount: function(count) {
- var thumbs = this.getThumbs(),
- thumbsCount = thumbs.length,
- i, ln, thumb;
- if (thumbsCount > count) {
- for (i = 0,ln = thumbsCount - count; i < ln; i++) {
- thumb = thumbs[thumbs.length - 1];
- thumb.destroy();
- }
- }
- else if (thumbsCount < count) {
- for (i = 0,ln = count - thumbsCount; i < ln; i++) {
- this.add(this.factoryThumb());
- }
- }
- return this;
- },
- /**
- * Convenience method. Calls {@link #setValue}.
- */
- setValues: function(value) {
- this.setValue(value);
- },
- /**
- * Convenience method. Calls {@link #getValue}.
- * @return {Object}
- */
- getValues: function() {
- return this.getValue();
- },
- /**
- * Sets the {@link #increment} configuration.
- * @param {Number} increment
- * @return {Number}
- */
- applyIncrement: function(increment) {
- if (increment === 0) {
- increment = 1;
- }
- return Math.abs(increment);
- },
- // @private
- updateAllowThumbsOverlapping: function(newValue, oldValue) {
- if (typeof oldValue != 'undefined') {
- this.refreshValue();
- }
- },
- // @private
- updateMinValue: function(newValue, oldValue) {
- if (typeof oldValue != 'undefined') {
- this.refreshValue();
- }
- },
- // @private
- updateMaxValue: function(newValue, oldValue) {
- if (typeof oldValue != 'undefined') {
- this.refreshValue();
- }
- },
- // @private
- updateIncrement: function(newValue, oldValue) {
- if (typeof oldValue != 'undefined') {
- this.refreshValue();
- }
- },
- doSetDisabled: function(disabled) {
- this.callParent(arguments);
- var items = this.getItems().items,
- ln = items.length,
- i;
- for (i = 0; i < ln; i++) {
- items[i].setDisabled(disabled);
- }
- }
- }, function() {
- });
- /**
- * @aside guide forms
- *
- * The slider is a way to allow the user to select a value from a given numerical range. You might use it for choosing
- * a percentage, combine two of them to get min and max values, or use three of them to specify the hex values for a
- * color. Each slider contains a single 'thumb' that can be dragged along the slider's length to change the value.
- * Sliders are equally useful inside {@link Ext.form.Panel forms} and standalone. Here's how to quickly create a
- * slider in form, in this case enabling a user to choose a percentage:
- *
- * @example
- * Ext.create('Ext.form.Panel', {
- * fullscreen: true,
- * items: [
- * {
- * xtype: 'sliderfield',
- * label: 'Percentage',
- * value: 50,
- * minValue: 0,
- * maxValue: 100
- * }
- * ]
- * });
- *
- * In this case we set a starting value of 50%, and defined the min and max values to be 0 and 100 respectively, giving
- * us a percentage slider. Because this is such a common use case, the defaults for {@link #minValue} and
- * {@link #maxValue} are already set to 0 and 100 so in the example above they could be removed.
- *
- * It's often useful to render sliders outside the context of a form panel too. In this example we create a slider that
- * allows a user to choose the waist measurement of a pair of jeans. Let's say the online store we're making this for
- * sells jeans with waist sizes from 24 inches to 60 inches in 2 inch increments - here's how we might achieve that:
- *
- * @example
- * Ext.create('Ext.form.Panel', {
- * fullscreen: true,
- * items: [
- * {
- * xtype: 'sliderfield',
- * label: 'Waist Measurement',
- * minValue: 24,
- * maxValue: 60,
- * increment: 2,
- * value: 32
- * }
- * ]
- * });
- *
- * Now that we've got our slider, we can ask it what value it currently has and listen to events that it fires. For
- * example, if we wanted our app to show different images for different sizes, we can listen to the {@link #change}
- * event to be informed whenever the slider is moved:
- *
- * slider.on('change', function(field, newValue) {
- * if (newValue[0] > 40) {
- * imgComponent.setSrc('large.png');
- * } else {
- * imgComponent.setSrc('small.png');
- * }
- * }, this);
- *
- * Here we listened to the {@link #change} event on the slider and updated the background image of an
- * {@link Ext.Img image component} based on what size the user selected. Of course, you can use any logic inside your
- * event listener.
- */
- Ext.define('Ext.field.Slider', {
- extend : 'Ext.field.Field',
- xtype : 'sliderfield',
- requires: ['Ext.slider.Slider'],
- alternateClassName: 'Ext.form.Slider',
- /**
- * @event change
- * Fires when an option selection has changed.
- * @param {Ext.field.Slider} me
- * @param {Ext.slider.Slider} sl Slider Component.
- * @param {Ext.slider.Thumb} thumb
- * @param {Number} newValue The new value of this thumb.
- * @param {Number} oldValue The old value of this thumb.
- */
- /**
- * @event dragstart
- * Fires when the slider thumb starts a drag operation.
- * @param {Ext.field.Slider} this
- * @param {Ext.slider.Slider} sl Slider Component.
- * @param {Ext.slider.Thumb} thumb The thumb being dragged.
- * @param {Array} value The start value.
- * @param {Ext.EventObject} e
- */
- /**
- * @event drag
- * Fires when the slider thumb starts a drag operation.
- * @param {Ext.field.Slider} this
- * @param {Ext.slider.Slider} sl Slider Component.
- * @param {Ext.slider.Thumb} thumb The thumb being dragged.
- * @param {Ext.EventObject} e
- */
- /**
- * @event dragend
- * Fires when the slider thumb ends a drag operation.
- * @param {Ext.field.Slider} this
- * @param {Ext.slider.Slider} sl Slider Component.
- * @param {Ext.slider.Thumb} thumb The thumb being dragged.
- * @param {Array} value The end value.
- * @param {Ext.EventObject} e
- */
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- cls: Ext.baseCSSPrefix + 'slider-field',
- /**
- * @cfg
- * @inheritdoc
- */
- tabIndex: -1,
- /**
- * Will make this field read only, meaning it cannot be changed with used interaction.
- * @cfg {Boolean} readOnly
- * @accessor
- */
- readOnly: false
- },
- proxyConfig: {
- /**
- * @inheritdoc Ext.slider.Slider#increment
- * @cfg {Number} increment
- * @accessor
- */
- increment : 1,
- /**
- * @inheritdoc Ext.slider.Slider#value
- * @cfg {Number/Number[]} value
- * @accessor
- */
- value: 0,
- /**
- * @inheritdoc Ext.slider.Slider#minValue
- * @cfg {Number} minValue
- * @accessor
- */
- minValue: 0,
- /**
- * @inheritdoc Ext.slider.Slider#maxValue
- * @cfg {Number} maxValue
- * @accessor
- */
- maxValue: 100
- },
- /**
- * @inheritdoc Ext.slider.Slider#values
- * @cfg {Number/Number[]} values
- */
- constructor: function(config) {
- config = config || {};
- if (config.hasOwnProperty('values')) {
- config.value = config.values;
- }
- this.callParent([config]);
- },
- // @private
- initialize: function() {
- this.callParent();
- this.getComponent().on({
- scope: this,
- change: 'onSliderChange',
- dragstart: 'onSliderDragStart',
- drag: 'onSliderDrag',
- dragend: 'onSliderDragEnd'
- });
- },
- // @private
- applyComponent: function(config) {
- return Ext.factory(config, Ext.slider.Slider);
- },
- onSliderChange: function() {
- this.fireEvent.apply(this, [].concat('change', this, Array.prototype.slice.call(arguments)));
- },
- onSliderDragStart: function() {
- this.fireEvent.apply(this, [].concat('dragstart', this, Array.prototype.slice.call(arguments)));
- },
- onSliderDrag: function() {
- this.fireEvent.apply(this, [].concat('drag', this, Array.prototype.slice.call(arguments)));
- },
- onSliderDragEnd: function() {
- this.fireEvent.apply(this, [].concat('dragend', this, Array.prototype.slice.call(arguments)));
- },
- /**
- * Convenience method. Calls {@link #setValue}.
- * @param {Object} value
- */
- setValues: function(value) {
- this.setValue(value);
- },
- /**
- * Convenience method. Calls {@link #getValue}
- * @return {Object}
- */
- getValues: function() {
- return this.getValue();
- },
- reset: function() {
- var config = this.config,
- initialValue = (this.config.hasOwnProperty('values')) ? config.values : config.value;
- this.setValue(initialValue);
- },
- doSetDisabled: function(disabled) {
- this.callParent(arguments);
- this.getComponent().setDisabled(disabled);
- },
- updateReadOnly: function(newValue) {
- this.getComponent().setReadOnly(newValue);
- },
- isDirty : function () {
- if (this.getDisabled()) {
- return false;
- }
- return this.getValue() !== this.originalValue;
- }
- });
- /**
- * A wrapper class which can be applied to any element. Fires a "tap" event while
- * touching the device. The interval between firings may be specified in the config but
- * defaults to 20 milliseconds.
- */
- Ext.define('Ext.util.TapRepeater', {
- requires: ['Ext.DateExtras'],
- mixins: {
- observable: 'Ext.mixin.Observable'
- },
- /**
- * @event touchstart
- * Fires when the touch is started.
- * @param {Ext.util.TapRepeater} this
- * @param {Ext.event.Event} e
- */
- /**
- * @event tap
- * Fires on a specified interval during the time the element is pressed.
- * @param {Ext.util.TapRepeater} this
- * @param {Ext.event.Event} e
- */
- /**
- * @event touchend
- * Fires when the touch is ended.
- * @param {Ext.util.TapRepeater} this
- * @param {Ext.event.Event} e
- */
- config: {
- el: null,
- accelerate: true,
- interval: 10,
- delay: 250,
- preventDefault: true,
- stopDefault: false,
- timer: 0,
- pressCls: null
- },
- /**
- * Creates new TapRepeater.
- * @param {Mixed} el The element to listen on
- * @param {Object} config
- */
- constructor: function(config) {
- var me = this;
- //<debug warn>
- for (var configName in config) {
- if (me.self.prototype.config && !(configName in me.self.prototype.config)) {
- me[configName] = config[configName];
- Ext.Logger.warn('Applied config as instance property: "' + configName + '"', me);
- }
- }
- //</debug>
- me.initConfig(config);
- },
- updateEl: function(newEl, oldEl) {
- var eventCfg = {
- touchstart: 'onTouchStart',
- touchend: 'onTouchEnd',
- tap: 'eventOptions',
- scope: this
- };
- if (oldEl) {
- oldEl.un(eventCfg)
- }
- newEl.on(eventCfg);
- },
- // @private
- eventOptions: function(e) {
- if (this.getPreventDefault()) {
- e.preventDefault();
- }
- if (this.getStopDefault()) {
- e.stopEvent();
- }
- },
- // @private
- destroy: function() {
- this.clearListeners();
- Ext.destroy(this.el);
- },
- // @private
- onTouchStart: function(e) {
- var me = this,
- pressCls = me.getPressCls();
- clearTimeout(me.getTimer());
- if (pressCls) {
- me.getEl().addCls(pressCls);
- }
- me.tapStartTime = new Date();
- me.fireEvent('touchstart', me, e);
- me.fireEvent('tap', me, e);
- // Do not honor delay or interval if acceleration wanted.
- if (me.getAccelerate()) {
- me.delay = 400;
- }
- me.setTimer(Ext.defer(me.tap, me.getDelay() || me.getInterval(), me, [e]));
- },
- // @private
- tap: function(e) {
- var me = this;
- me.fireEvent('tap', me, e);
- me.setTimer(Ext.defer(me.tap, me.getAccelerate() ? me.easeOutExpo(Ext.Date.getElapsed(me.tapStartTime),
- 400,
- -390,
- 12000) : me.getInterval(), me, [e]));
- },
- // Easing calculation
- // @private
- easeOutExpo: function(t, b, c, d) {
- return (t == d) ? b + c : c * ( - Math.pow(2, -10 * t / d) + 1) + b;
- },
- // @private
- onTouchEnd: function(e) {
- var me = this;
- clearTimeout(me.getTimer());
- me.getEl().removeCls(me.getPressCls());
- me.fireEvent('touchend', me, e);
- }
- });
- /**
- * @aside guide forms
- *
- * Wraps an HTML5 number field. Example usage:
- *
- * @example miniphone
- * var spinner = Ext.create('Ext.field.Spinner', {
- * label: 'Spinner Field',
- * minValue: 0,
- * maxValue: 100,
- * increment: 2,
- * cycle: true
- * });
- * Ext.Viewport.add({ xtype: 'container', items: [spinner] });
- *
- */
- Ext.define('Ext.field.Spinner', {
- extend: 'Ext.field.Number',
- xtype: 'spinnerfield',
- alternateClassName: 'Ext.form.Spinner',
- requires: ['Ext.util.TapRepeater'],
- /**
- * @event spin
- * Fires when the value is changed via either spinner buttons.
- * @param {Ext.field.Spinner} this
- * @param {Number} value
- * @param {String} direction 'up' or 'down'.
- */
- /**
- * @event spindown
- * Fires when the value is changed via the spinner down button.
- * @param {Ext.field.Spinner} this
- * @param {Number} value
- */
- /**
- * @event spinup
- * Fires when the value is changed via the spinner up button.
- * @param {Ext.field.Spinner} this
- * @param {Number} value
- */
- /**
- * @event change
- * Fires just before the field blurs if the field value has changed.
- * @param {Ext.field.Text} this This field.
- * @param {Number} newValue The new value.
- * @param {Number} oldValue The original value.
- */
- /**
- * @event updatedata
- * @hide
- */
- /**
- * @event action
- * @hide
- */
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- cls: Ext.baseCSSPrefix + 'spinner',
- /**
- * @cfg {Number} [minValue=-infinity] The minimum allowed value.
- * @accessor
- */
- minValue: Number.NEGATIVE_INFINITY,
- /**
- * @cfg {Number} [maxValue=infinity] The maximum allowed value.
- * @accessor
- */
- maxValue: Number.MAX_VALUE,
- /**
- * @cfg {Number} stepValue Value that is added or subtracted from the current value when a spinner is used.
- * @accessor
- */
- stepValue: 0.1,
- /**
- * @cfg {Boolean} accelerateOnTapHold True if autorepeating should start slowly and accelerate.
- * @accessor
- */
- accelerateOnTapHold: true,
- /**
- * @cfg {Boolean} cycle When set to `true`, it will loop the values of a minimum or maximum is reached.
- * If the maximum value is reached, the value will be set to the minimum.
- * @accessor
- */
- cycle: false,
- /**
- * @cfg {Boolean} clearIcon
- * @hide
- * @accessor
- */
- clearIcon: false,
- /**
- * @cfg {Number} defaultValue The default value for this field when no value has been set.
- * It is also used when the value is set to `NaN`.
- */
- defaultValue: 0,
- /**
- * @cfg {Number} tabIndex
- * @hide
- */
- tabIndex: -1,
- /**
- * @cfg {Boolean} groupButtons
- * `true` if you want to group the buttons to the right of the fields. `false` if you want the buttons
- * to be at either side of the field.
- */
- groupButtons: true,
- /**
- * @cfg
- * @inheritdoc
- */
- component: {
- disabled: true
- }
- },
- constructor: function() {
- var me = this;
- me.callParent(arguments);
- if (!me.getValue()) {
- me.suspendEvents();
- me.setValue(me.getDefaultValue());
- me.resumeEvents();
- }
- },
- syncEmptyCls: Ext.emptyFn,
- /**
- * Updates the {@link #component} configuration
- */
- updateComponent: function(newComponent) {
- this.callParent(arguments);
- var innerElement = this.innerElement,
- cls = this.getCls();
- if (newComponent) {
- this.spinDownButton = Ext.Element.create({
- cls : cls + '-button ' + cls + '-button-down',
- html: '-'
- });
- this.spinUpButton = Ext.Element.create({
- cls : cls + '-button ' + cls + '-button-up',
- html: '+'
- });
- this.downRepeater = this.createRepeater(this.spinDownButton, this.onSpinDown);
- this.upRepeater = this.createRepeater(this.spinUpButton, this.onSpinUp);
- }
- },
- updateGroupButtons: function(newGroupButtons, oldGroupButtons) {
- var me = this,
- innerElement = me.innerElement,
- cls = me.getBaseCls() + '-grouped-buttons';
- me.getComponent();
- if (newGroupButtons != oldGroupButtons) {
- if (newGroupButtons) {
- this.addCls(cls);
- innerElement.appendChild(me.spinDownButton);
- innerElement.appendChild(me.spinUpButton);
- } else {
- this.removeCls(cls);
- innerElement.insertFirst(me.spinDownButton);
- innerElement.appendChild(me.spinUpButton);
- }
- }
- },
- applyValue: function(value) {
- value = parseFloat(value);
- if (isNaN(value) || value === null) {
- value = this.getDefaultValue();
- }
- //round the value to 1 decimal
- value = Math.round(value * 10) / 10;
- return this.callParent([value]);
- },
- // @private
- createRepeater: function(el, fn) {
- var me = this,
- repeater = Ext.create('Ext.util.TapRepeater', {
- el: el,
- accelerate: me.getAccelerateOnTapHold()
- });
- repeater.on({
- tap: fn,
- touchstart: 'onTouchStart',
- touchend: 'onTouchEnd',
- scope: me
- });
- return repeater;
- },
- // @private
- onSpinDown: function() {
- if (!this.getDisabled() && !this.getReadOnly()) {
- this.spin(true);
- }
- },
- // @private
- onSpinUp: function() {
- if (!this.getDisabled() && !this.getReadOnly()) {
- this.spin(false);
- }
- },
- // @private
- onTouchStart: function(repeater) {
- if (!this.getDisabled() && !this.getReadOnly()) {
- repeater.getEl().addCls(Ext.baseCSSPrefix + 'button-pressed');
- }
- },
- // @private
- onTouchEnd: function(repeater) {
- repeater.getEl().removeCls(Ext.baseCSSPrefix + 'button-pressed');
- },
- // @private
- spin: function(down) {
- var me = this,
- originalValue = me.getValue(),
- stepValue = me.getStepValue(),
- direction = down ? 'down' : 'up',
- minValue = me.getMinValue(),
- maxValue = me.getMaxValue(),
- value;
- if (down) {
- value = originalValue - stepValue;
- }
- else {
- value = originalValue + stepValue;
- }
- //if cycle is true, then we need to check fi the value hasn't changed and we cycle the value
- if (me.getCycle()) {
- if (originalValue == minValue && value < minValue) {
- value = maxValue;
- }
- if (originalValue == maxValue && value > maxValue) {
- value = minValue;
- }
- }
- me.setValue(value);
- value = me.getValue();
- me.fireEvent('spin', me, value, direction);
- me.fireEvent('spin' + direction, me, value);
- },
- /**
- * @private
- */
- doSetDisabled: function(disabled) {
- Ext.Component.prototype.doSetDisabled.apply(this, arguments);
- },
- /**
- * @private
- */
- setDisabled: function() {
- Ext.Component.prototype.setDisabled.apply(this, arguments);
- },
- reset: function() {
- this.setValue(this.getDefaultValue());
- },
- // @private
- destroy: function() {
- var me = this;
- Ext.destroy(me.downRepeater, me.upRepeater, me.spinDownButton, me.spinUpButton);
- me.callParent(arguments);
- }
- }, function() {
- });
- /**
- * @private
- */
- Ext.define('Ext.slider.Toggle', {
- extend: 'Ext.slider.Slider',
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: 'x-toggle',
- /**
- * @cfg {String} minValueCls CSS class added to the field when toggled to its minValue
- * @accessor
- */
- minValueCls: 'x-toggle-off',
- /**
- * @cfg {String} maxValueCls CSS class added to the field when toggled to its maxValue
- * @accessor
- */
- maxValueCls: 'x-toggle-on'
- },
- initialize: function() {
- this.callParent();
- this.on({
- change: 'onChange'
- });
- },
- applyMinValue: function() {
- return 0;
- },
- applyMaxValue: function() {
- return 1;
- },
- applyIncrement: function() {
- return 1;
- },
- updateMinValueCls: function(newCls, oldCls) {
- var element = this.element;
- if (oldCls && element.hasCls(oldCls)) {
- element.replaceCls(oldCls, newCls);
- }
- },
- updateMaxValueCls: function(newCls, oldCls) {
- var element = this.element;
- if (oldCls && element.hasCls(oldCls)) {
- element.replaceCls(oldCls, newCls);
- }
- },
- setValue: function(newValue, oldValue) {
- this.callParent(arguments);
- this.onChange(this, this.getThumbs()[0], newValue, oldValue);
- },
- onChange: function(me, thumb, newValue, oldValue) {
- var isOn = newValue > 0,
- onCls = me.getMaxValueCls(),
- offCls = me.getMinValueCls();
- this.element.addCls(isOn ? onCls : offCls);
- this.element.removeCls(isOn ? offCls : onCls);
- },
- toggle: function() {
- var value = this.getValue();
- this.setValue((value == 1) ? 0 : 1);
- return this;
- },
- onTap: function() {
- if (this.isDisabled() || this.getReadOnly()) {
- return;
- }
- var oldValue = this.getValue(),
- newValue = (oldValue == 1) ? 0 : 1,
- thumb = this.getThumb(0);
- this.setIndexValue(0, newValue, this.getAnimation());
- this.refreshThumbConstraints(thumb);
- this.fireEvent('change', this, thumb, newValue, oldValue);
- }
- });
- /**
- * @aside guide forms
- *
- * Specialized {@link Ext.field.Slider} with a single thumb which only supports two {@link #value values}.
- *
- * ## Examples
- *
- * @example miniphone preview
- * Ext.Viewport.add({
- * xtype: 'togglefield',
- * name: 'awesome',
- * label: 'Are you awesome?',
- * labelWidth: '40%'
- * });
- *
- * Having a default value of 'toggled':
- *
- * @example miniphone preview
- * Ext.Viewport.add({
- * xtype: 'togglefield',
- * name: 'awesome',
- * value: 1,
- * label: 'Are you awesome?',
- * labelWidth: '40%'
- * });
- *
- * And using the {@link #value} {@link #toggle} method:
- *
- * @example miniphone preview
- * Ext.Viewport.add([
- * {
- * xtype: 'togglefield',
- * name: 'awesome',
- * value: 1,
- * label: 'Are you awesome?',
- * labelWidth: '40%'
- * },
- * {
- * xtype: 'toolbar',
- * docked: 'top',
- * items: [
- * {
- * xtype: 'button',
- * text: 'Toggle',
- * flex: 1,
- * handler: function() {
- * Ext.ComponentQuery.query('togglefield')[0].toggle();
- * }
- * }
- * ]
- * }
- * ]);
- */
- Ext.define('Ext.field.Toggle', {
- extend: 'Ext.field.Slider',
- xtype : 'togglefield',
- alternateClassName: 'Ext.form.Toggle',
- requires: ['Ext.slider.Toggle'],
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- cls: 'x-toggle-field'
- },
- /**
- * @event change
- * Fires when an option selection has changed.
- *
- * Ext.Viewport.add({
- * xtype: 'togglefield',
- * label: 'Event Example',
- * listeners: {
- * change: function(field, newValue) {
- * console.log('Value of this toggle has changed:', (newValue) ? 'ON' : 'OFF');
- * }
- * }
- * });
- *
- * @param {Ext.field.Toggle} me
- * @param {Number} newValue the new value of this thumb
- * @param {Number} oldValue the old value of this thumb
- */
- /**
- * @event dragstart
- * @hide
- */
- /**
- * @event drag
- * @hide
- */
- /**
- * @event dragend
- * @hide
- */
- proxyConfig: {
- /**
- * @cfg {String} minValueCls See {@link Ext.slider.Toggle#minValueCls}
- * @accessor
- */
- minValueCls: 'x-toggle-off',
- /**
- * @cfg {String} maxValueCls See {@link Ext.slider.Toggle#maxValueCls}
- * @accessor
- */
- maxValueCls: 'x-toggle-on'
- },
- // @private
- applyComponent: function(config) {
- return Ext.factory(config, Ext.slider.Toggle);
- },
- /**
- * Sets the value of the toggle.
- * @param {Number} value **1** for toggled, **0** for untoggled.
- * @return {Object} this
- */
- setValue: function(newValue) {
- if (newValue === true) {
- newValue = 1;
- }
- var oldValue = this.getValue();
- if (oldValue != newValue) {
- this.getComponent().setValue(newValue);
- this.fireEvent('change', this, newValue, oldValue);
- }
- return this;
- },
- getValue: function() {
- return (this.getComponent().getValue() == 1) ? 1 : 0;
- },
- /**
- * Toggles the value of this toggle field.
- * @return {Object} this
- */
- toggle: function() {
- // We call setValue directly so the change event can be fired
- var value = this.getValue();
- this.setValue((value == 1) ? 0 : 1);
- return this;
- }
- });
- /**
- * @aside guide forms
- *
- * The Url field creates an HTML5 url input and is usually created inside a form. Because it creates an HTML url input
- * field, most browsers will show a specialized virtual keyboard for web address input. Aside from that, the url field
- * is just a normal text field. Here's an example of how to use it in a form:
- *
- * @example
- * Ext.create('Ext.form.Panel', {
- * fullscreen: true,
- * items: [
- * {
- * xtype: 'fieldset',
- * title: 'Add Bookmark',
- * items: [
- * {
- * xtype: 'urlfield',
- * label: 'Url',
- * name: 'url'
- * }
- * ]
- * }
- * ]
- * });
- *
- * Or on its own, outside of a form:
- *
- * Ext.create('Ext.field.Url', {
- * label: 'Web address',
- * value: 'http://sencha.com'
- * });
- *
- * Because url field inherits from {@link Ext.field.Text textfield} it gains all of the functionality that text fields
- * provide, including getting and setting the value at runtime, validations and various events that are fired as the
- * user interacts with the component. Check out the {@link Ext.field.Text} docs to see the additional functionality
- * available.
- */
- Ext.define('Ext.field.Url', {
- extend: 'Ext.field.Text',
- xtype: 'urlfield',
- alternateClassName: 'Ext.form.Url',
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- autoCapitalize: false,
- /**
- * @cfg
- * @inheritdoc
- */
- component: {
- type: 'url'
- }
- }
- });
- /**
- * @aside guide forms
- * @aside example forms
- * @aside example forms-toolbars
- *
- * A FieldSet is a great way to visually separate elements of a form. It's normally used when you have a form with
- * fields that can be divided into groups - for example a customer's billing details in one fieldset and their shipping
- * address in another. A fieldset can be used inside a form or on its own elsewhere in your app. Fieldsets can
- * optionally have a title at the top and instructions at the bottom. Here's how we might create a FieldSet inside a
- * form:
- *
- * @example
- * Ext.create('Ext.form.Panel', {
- * fullscreen: true,
- * items: [
- * {
- * xtype: 'fieldset',
- * title: 'About You',
- * instructions: 'Tell us all about yourself',
- * items: [
- * {
- * xtype: 'textfield',
- * name : 'firstName',
- * label: 'First Name'
- * },
- * {
- * xtype: 'textfield',
- * name : 'lastName',
- * label: 'Last Name'
- * }
- * ]
- * }
- * ]
- * });
- *
- * Above we created a {@link Ext.form.Panel form} with a fieldset that contains two text fields. In this case, all
- * of the form fields are in the same fieldset, but for longer forms we may choose to use multiple fieldsets. We also
- * configured a {@link #title} and {@link #instructions} to give the user more information on filling out the form if
- * required.
- */
- Ext.define('Ext.form.FieldSet', {
- extend : 'Ext.Container',
- alias : 'widget.fieldset',
- requires: ['Ext.Title'],
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'form-fieldset',
- /**
- * @cfg {String} title
- * Optional fieldset title, rendered just above the grouped fields.
- *
- * ## Example
- *
- * Ext.create('Ext.form.Fieldset', {
- * fullscreen: true,
- *
- * title: 'Login',
- *
- * items: [{
- * xtype: 'textfield',
- * label: 'Email'
- * }]
- * });
- *
- * @accessor
- */
- title: null,
- /**
- * @cfg {String} instructions
- * Optional fieldset instructions, rendered just below the grouped fields.
- *
- * ## Example
- *
- * Ext.create('Ext.form.Fieldset', {
- * fullscreen: true,
- *
- * instructions: 'Please enter your email address.',
- *
- * items: [{
- * xtype: 'textfield',
- * label: 'Email'
- * }]
- * });
- *
- * @accessor
- */
- instructions: null
- },
- // @private
- applyTitle: function(title) {
- if (typeof title == 'string') {
- title = {title: title};
- }
- Ext.applyIf(title, {
- docked : 'top',
- baseCls: this.getBaseCls() + '-title'
- });
- return Ext.factory(title, Ext.Title, this._title);
- },
- // @private
- updateTitle: function(newTitle, oldTitle) {
- if (newTitle) {
- this.add(newTitle);
- }
- if (oldTitle) {
- this.remove(oldTitle);
- }
- },
- // @private
- getTitle: function() {
- var title = this._title;
- if (title && title instanceof Ext.Title) {
- return title.getTitle();
- }
- return title;
- },
- // @private
- applyInstructions: function(instructions) {
- if (typeof instructions == 'string') {
- instructions = {title: instructions};
- }
- Ext.applyIf(instructions, {
- docked : 'bottom',
- baseCls: this.getBaseCls() + '-instructions'
- });
- return Ext.factory(instructions, Ext.Title, this._instructions);
- },
- // @private
- updateInstructions: function(newInstructions, oldInstructions) {
- if (newInstructions) {
- this.add(newInstructions);
- }
- if (oldInstructions) {
- this.remove(oldInstructions);
- }
- },
- // @private
- getInstructions: function() {
- var instructions = this._instructions;
- if (instructions && instructions instanceof Ext.Title) {
- return instructions.getTitle();
- }
- return instructions;
- },
- /**
- * A convenient method to disable all fields in this FieldSet
- * @return {Ext.form.FieldSet} This FieldSet
- */
-
- doSetDisabled: function(newDisabled) {
- this.getFieldsAsArray().forEach(function(field) {
- field.setDisabled(newDisabled);
- });
- return this;
- },
- /**
- * @private
- */
- getFieldsAsArray: function() {
- var fields = [],
- getFieldsFrom = function(item) {
- if (item.isField) {
- fields.push(item);
- }
- if (item.isContainer) {
- item.getItems().each(getFieldsFrom);
- }
- };
- this.getItems().each(getFieldsFrom);
- return fields;
- }
- });
- /**
- * The Form panel presents a set of form fields and provides convenient ways to load and save data. Usually a form
- * panel just contains the set of fields you want to display, ordered inside the items configuration like this:
- *
- * @example
- * var form = Ext.create('Ext.form.Panel', {
- * fullscreen: true,
- * items: [
- * {
- * xtype: 'textfield',
- * name: 'name',
- * label: 'Name'
- * },
- * {
- * xtype: 'emailfield',
- * name: 'email',
- * label: 'Email'
- * },
- * {
- * xtype: 'passwordfield',
- * name: 'password',
- * label: 'Password'
- * }
- * ]
- * });
- *
- * Here we just created a simple form panel which could be used as a registration form to sign up to your service. We
- * added a plain {@link Ext.field.Text text field} for the user's Name, an {@link Ext.field.Email email field} and
- * finally a {@link Ext.field.Password password field}. In each case we provided a {@link Ext.field.Field#name name}
- * config on the field so that we can identify it later on when we load and save data on the form.
- *
- * ##Loading data
- *
- * Using the form we created above, we can load data into it in a few different ways, the easiest is to use
- * {@link #setValues}:
- *
- * form.setValues({
- * name: 'Ed',
- * email: 'ed@sencha.com',
- * password: 'secret'
- * });
- *
- * 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
- * to load a particular instance into our form:
- *
- * Ext.define('MyApp.model.User', {
- * extend: 'Ext.data.Model',
- * config: {
- * fields: ['name', 'email', 'password']
- * }
- * });
- *
- * var ed = Ext.create('MyApp.model.User', {
- * name: 'Ed',
- * email: 'ed@sencha.com',
- * password: 'secret'
- * });
- *
- * form.setRecord(ed);
- *
- * ##Retrieving form data
- *
- * Getting data out of the form panel is simple and is usually achieve via the {@link #getValues} method:
- *
- * var values = form.getValues();
- *
- * //values now looks like this:
- * {
- * name: 'Ed',
- * email: 'ed@sencha.com',
- * password: 'secret'
- * }
- *
- * It's also possible to listen to the change events on individual fields to get more timely notification of changes
- * that the user is making. Here we expand on the example above with the User model, updating the model as soon as
- * any of the fields are changed:
- *
- * var form = Ext.create('Ext.form.Panel', {
- * listeners: {
- * '> field': {
- * change: function(field, newValue, oldValue) {
- * ed.set(field.getName(), newValue);
- * }
- * }
- * },
- * items: [
- * {
- * xtype: 'textfield',
- * name: 'name',
- * label: 'Name'
- * },
- * {
- * xtype: 'emailfield',
- * name: 'email',
- * label: 'Email'
- * },
- * {
- * xtype: 'passwordfield',
- * name: 'password',
- * label: 'Password'
- * }
- * ]
- * });
- *
- * The above used a new capability of Sencha Touch 2.0, which enables you to specify listeners on child components of any
- * container. In this case, we attached a listener to the {@link Ext.field.Text#change change} event of each form
- * field that is a direct child of the form panel. Our listener gets the name of the field that fired the change event,
- * and updates our {@link Ext.data.Model Model} instance with the new value. For example, changing the email field
- * in the form will update the Model's email field.
- *
- * ##Submitting forms
- *
- * There are a few ways to submit form data. In our example above we have a Model instance that we have updated, giving
- * us the option to use the Model's {@link Ext.data.Model#save save} method to persist the changes back to our server,
- * without using a traditional form submission. Alternatively, we can send a normal browser form submit using the
- * {@link #method} method:
- *
- * form.submit({
- * url: 'url/to/submit/to',
- * method: 'POST',
- * success: function() {
- * alert('form submitted successfully!');
- * }
- * });
- *
- * In this case we provided the `url` to submit the form to inside the submit call - alternatively you can just set the
- * {@link #url} configuration when you create the form. We can specify other parameters (see {@link #method} for a
- * full list), including callback functions for success and failure, which are called depending on whether or not the
- * form submission was successful. These functions are usually used to take some action in your app after your data
- * has been saved to the server side.
- *
- * @aside guide forms
- * @aside example forms
- * @aside example forms-toolbars
- */
- Ext.define('Ext.form.Panel', {
- alternateClassName: 'Ext.form.FormPanel',
- extend : 'Ext.Panel',
- xtype : 'formpanel',
- requires: ['Ext.XTemplate', 'Ext.field.Checkbox', 'Ext.Ajax'],
- /**
- * @event submit
- * @preventable doSubmit
- * Fires upon successful (Ajax-based) form submission.
- * @param {Ext.form.Panel} this This FormPanel.
- * @param {Object} result The result object as returned by the server.
- * @param {Ext.EventObject} e The event object.
- */
- /**
- * @event beforesubmit
- * @preventable doBeforeSubmit
- * Fires immediately preceding any Form submit action.
- * Implementations may adjust submitted form values or options prior to execution.
- * A return value of `false` from this listener will abort the submission
- * attempt (regardless of `standardSubmit` configuration).
- * @param {Ext.form.Panel} this This FormPanel.
- * @param {Object} values A hash collection of the qualified form values about to be submitted.
- * @param {Object} options Submission options hash (only available when `standardSubmit` is `false`).
- */
- /**
- * @event exception
- * Fires when either the Ajax HTTP request reports a failure OR the server returns a `success:false`
- * response in the result payload.
- * @param {Ext.form.Panel} this This FormPanel.
- * @param {Object} result Either a failed Ext.data.Connection request object or a failed (logical) server.
- * response payload.
- */
- config: {
- /**
- * @cfg {String} baseCls
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'form',
- /**
- * @cfg {Boolean} standardSubmit
- * Whether or not we want to perform a standard form submit.
- * @accessor
- */
- standardSubmit: false,
- /**
- * @cfg {String} url
- * The default url for submit actions.
- * @accessor
- */
- url: null,
- /**
- * @cfg {Object} baseParams
- * Optional hash of params to be sent (when `standardSubmit` configuration is `false`) on every submit.
- * @accessor
- */
- baseParams : null,
- /**
- * @cfg {Object} submitOnAction
- * When this is set to `true`, the form will automatically submit itself whenever the `action`
- * event fires on a field in this form. The action event usually fires whenever you press
- * go or enter inside a textfield.
- * @accessor
- */
- submitOnAction: false,
- /**
- * @cfg {Ext.data.Model} record The model instance of this form. Can by dynamically set at any time.
- * @accessor
- */
- record: null,
- /**
- * @cfg {String} method
- * The method which this form will be submitted. `post` or `get`.
- */
- method: 'post',
- /**
- * @cfg {Object} scrollable
- * @inheritdoc
- */
- scrollable: {
- translatable: {
- translationMethod: 'scrollposition'
- }
- }
- },
- getElementConfig: function() {
- var config = this.callParent();
- config.tag = "form";
- return config;
- },
- // @private
- initialize: function() {
- var me = this;
- me.callParent();
- me.element.on({
- submit: 'onSubmit',
- scope : me
- });
- },
- updateRecord: function(newRecord) {
- var fields, values, name;
- if (newRecord && (fields = newRecord.fields)) {
- values = this.getValues();
- for (name in values) {
- if (values.hasOwnProperty(name) && fields.containsKey(name)) {
- newRecord.set(name, values[name]);
- }
- }
- }
- return this;
- },
- /**
- * Loads matching fields from a model instance into this form.
- * @param {Ext.data.Model} instance The model instance.
- * @return {Ext.form.Panel} This form.
- */
- setRecord: function(record) {
- var me = this;
- if (record && record.data) {
- me.setValues(record.data);
- }
- me._record = record;
- return this;
- },
- // @private
- onSubmit: function(e) {
- var me = this;
- if (e && !me.getStandardSubmit()) {
- e.stopEvent();
- } else {
- this.submit();
- }
- },
- updateSubmitOnAction: function(newSubmitOnAction) {
- if (newSubmitOnAction) {
- this.on({
- action: 'onFieldAction',
- scope: this
- });
- } else {
- this.un({
- action: 'onFieldAction',
- scope: this
- });
- }
- },
- // @private
- onFieldAction: function(field) {
- if (this.getSubmitOnAction()) {
- field.blur();
- this.submit();
- }
- },
- /**
- * Performs a Ajax-based submission of form values (if `standardSubmit` is `false`) or otherwise
- * executes a standard HTML Form submit action.
- *
- * @param {Object} options
- * The configuration when submitting this form.
- *
- * @param {String} options.url
- * The url for the action (defaults to the form's {@link #url}).
- *
- * @param {String} options.method
- * The form method to use (defaults to the form's {@link #method}, or POST if not defined).
- *
- * @param {String/Object} params
- * The params to pass when submitting this form (defaults to this forms {@link #baseParams}).
- * Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.
- *
- * @param {Object} headers
- * Request headers to set for the action.
- *
- * @param {Boolean} [autoAbort=false]
- * `true` to abort any pending Ajax request prior to submission.
- * __Note:__ Has no effect when `{@link #standardSubmit}` is enabled.
- *
- * @param {Boolean} [options.submitDisabled=false]
- * `true` to submit all fields regardless of disabled state.
- * __Note:__ Has no effect when `{@link #standardSubmit}` is enabled.
- *
- * @param {String/Object} [waitMsg]
- * If specified, the value which is passed to the loading {@link #masked mask}. See {@link #masked} for
- * more information.
- *
- * @param {Function} options.success
- * The callback that will be invoked after a successful response. A response is successful if
- * a response is received from the server and is a JSON object where the `success` property is set
- * to `true`, `{"success": true}`.
- *
- * The function is passed the following parameters:
- *
- * @param {Ext.form.Panel} options.success.form
- * The form that requested the action.
- *
- * @param {Ext.form.Panel} options.success.result
- * The result object returned by the server as a result of the submit request.
- *
- * @param {Function} options.failure
- * The callback that will be invoked after a failed transaction attempt.
- *
- * The function is passed the following parameters:
- *
- * @param {Ext.form.Panel} options.failure.form
- * The {@link Ext.form.Panel} that requested the submit.
- *
- * @param {Ext.form.Panel} options.failure.result
- * The failed response or result object returned by the server which performed the operation.
- *
- * @param {Object} options.scope
- * The scope in which to call the callback functions (The `this` reference for the callback functions).
- *
- * @return {Ext.data.Connection} The request object.
- */
- submit: function(options) {
- var me = this,
- form = me.element.dom || {},
- formValues;
- options = Ext.apply({
- url : me.getUrl() || form.action,
- submit: false,
- method : me.getMethod() || form.method || 'post',
- autoAbort : false,
- params : null,
- waitMsg : null,
- headers : null,
- success : null,
- failure : null
- }, options || {});
- formValues = me.getValues(me.getStandardSubmit() || !options.submitDisabled);
- return me.fireAction('beforesubmit', [me, formValues, options], 'doBeforeSubmit');
- },
- doBeforeSubmit: function(me, formValues, options) {
- var form = me.element.dom || {};
- if (me.getStandardSubmit()) {
- if (options.url && Ext.isEmpty(form.action)) {
- form.action = options.url;
- }
- // Spinner fields must have their components enabled *before* submitting or else the value
- // will not be posted.
- var fields = this.query('spinnerfield'),
- ln = fields.length,
- i, field;
- for (i = 0; i < ln; i++) {
- field = fields[i];
- if (!field.getDisabled()) {
- field.getComponent().setDisabled(false);
- }
- }
- form.method = (options.method || form.method).toLowerCase();
- form.submit();
- }
- else {
- if (options.waitMsg) {
- me.setMasked(options.waitMsg);
- }
- return Ext.Ajax.request({
- url: options.url,
- method: options.method,
- rawData: Ext.urlEncode(Ext.apply(
- Ext.apply({}, me.getBaseParams() || {}),
- options.params || {},
- formValues
- )),
- autoAbort: options.autoAbort,
- headers: Ext.apply(
- {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
- options.headers || {}
- ),
- scope: me,
- callback: function(callbackOptions, success, response) {
- var me = this,
- responseText = response.responseText,
- failureFn;
- me.setMasked(false);
- failureFn = function() {
- if (Ext.isFunction(options.failure)) {
- options.failure.call(options.scope || me, me, response, responseText);
- }
- me.fireEvent('exception', me, response);
- };
- if (success) {
- response = Ext.decode(responseText);
- success = !!response.success;
- if (success) {
- if (Ext.isFunction(options.success)) {
- options.success.call(options.scope || me, me, response, responseText);
- }
- me.fireEvent('submit', me, response);
- } else {
- failureFn();
- }
- }
- else {
- failureFn();
- }
- }
- });
- }
- },
- /**
- * Sets the values of form fields in bulk. Example usage:
- *
- * myForm.setValues({
- * name: 'Ed',
- * crazy: true,
- * username: 'edspencer'
- * });
- *
- * If there groups of checkbox fields with the same name, pass their values in an array. For example:
- *
- * myForm.setValues({
- * name: 'Jacky',
- * crazy: false,
- * hobbies: [
- * 'reading',
- * 'cooking',
- * 'gaming'
- * ]
- * });
- *
- * @param {Object} values field name => value mapping object.
- * @return {Ext.form.Panel} This form.
- */
- setValues: function(values) {
- var fields = this.getFields(),
- name, field, value, ln, i, f;
- values = values || {};
- for (name in values) {
- if (values.hasOwnProperty(name)) {
- field = fields[name];
- value = values[name];
- if (field) {
- // If there are multiple fields with the same name. Checkboxes, radio fields and maybe event just normal fields..
- if (Ext.isArray(field)) {
- ln = field.length;
- // Loop through each of the fields
- for (i = 0; i < ln; i++) {
- f = field[i];
- if (f.isRadio) {
- // If it is a radio field just use setGroupValue which will handle all of the radio fields
- f.setGroupValue(value);
- break;
- } else if (f.isCheckbox) {
- if (Ext.isArray(value)) {
- f.setChecked((value.indexOf(f._value) != -1));
- } else {
- f.setChecked((value == f._value));
- }
- } else {
- // If it is a bunch of fields with the same name, check if the value is also an array, so we can map it
- // to each field
- if (Ext.isArray(value)) {
- f.setValue(value[i]);
- }
- }
- }
- } else {
- if (field.isRadio || field.isCheckbox) {
- // If the field is a radio or a checkbox
- field.setChecked(value);
- } else {
- // If just a normal field
- field.setValue(value);
- }
- }
- }
- }
- }
- return this;
- },
- /**
- * Returns an object containing the value of each field in the form, keyed to the field's name.
- * For groups of checkbox fields with the same name, it will be arrays of values. For example:
- *
- * {
- * name: "Jacky Nguyen", // From a TextField
- * favorites: [
- * 'pizza',
- * 'noodle',
- * 'cake'
- * ]
- * }
- *
- * @param {Boolean} enabled `true` to return only enabled fields.
- * @param {Boolean} all `true` to return all fields even if they don't have a
- * {@link Ext.field.Field#name name} configured.
- * @return {Object} Object mapping field name to its value.
- */
- getValues: function(enabled, all) {
- var fields = this.getFields(),
- values = {},
- isArray = Ext.isArray,
- field, value, addValue, bucket, name, ln, i;
- // Function which you give a field and a name, and it will add it into the values
- // object accordingly
- addValue = function(field, name) {
- if (!all && (!name || name === 'null')) {
- return;
- }
- if (field.isCheckbox) {
- value = field.getSubmitValue();
- } else {
- value = field.getValue();
- }
- if (!(enabled && field.getDisabled())) {
- // RadioField is a special case where the value returned is the fields valUE
- // ONLY if it is checked
- if (field.isRadio) {
- if (field.isChecked()) {
- values[name] = value;
- }
- } else {
- // Check if the value already exists
- bucket = values[name];
- if (bucket) {
- // if it does and it isn't an array, we need to make it into an array
- // so we can push more
- if (!isArray(bucket)) {
- bucket = values[name] = [bucket];
- }
- // Check if it is an array
- if (isArray(value)) {
- // Concat it into the other values
- bucket = values[name] = bucket.concat(value);
- } else {
- // If it isn't an array, just pushed more values
- bucket.push(value);
- }
- } else {
- values[name] = value;
- }
- }
- }
- };
- // Loop through each of the fields, and add the values for those fields.
- for (name in fields) {
- if (fields.hasOwnProperty(name)) {
- field = fields[name];
- if (isArray(field)) {
- ln = field.length;
- for (i = 0; i < ln; i++) {
- addValue(field[i], name);
- }
- } else {
- addValue(field, name);
- }
- }
- }
- return values;
- },
- /**
- * Resets all fields in the form back to their original values.
- * @return {Ext.form.Panel} This form.
- */
- reset: function() {
- this.getFieldsAsArray().forEach(function(field) {
- field.reset();
- });
- return this;
- },
- /**
- * A convenient method to disable all fields in this form.
- * @return {Ext.form.Panel} This form.
- */
- doSetDisabled: function(newDisabled) {
- this.getFieldsAsArray().forEach(function(field) {
- field.setDisabled(newDisabled);
- });
- return this;
- },
- /**
- * @private
- */
- getFieldsAsArray: function() {
- var fields = [],
- getFieldsFrom = function(item) {
- if (item.isField) {
- fields.push(item);
- }
- if (item.isContainer) {
- item.getItems().each(getFieldsFrom);
- }
- };
- this.getItems().each(getFieldsFrom);
- return fields;
- },
- /**
- * @private
- * Returns all {@link Ext.field.Field field} instances inside this form.
- * @param byName return only fields that match the given name, otherwise return all fields.
- * @return {Object/Array} All field instances, mapped by field name; or an array if `byName` is passed.
- */
- getFields: function(byName) {
- var fields = {},
- itemName;
- var getFieldsFrom = function(item) {
- if (item.isField) {
- itemName = item.getName();
- if ((byName && itemName == byName) || typeof byName == 'undefined') {
- if (fields.hasOwnProperty(itemName)) {
- if (!Ext.isArray(fields[itemName])) {
- fields[itemName] = [fields[itemName]];
- }
- fields[itemName].push(item);
- } else {
- fields[itemName] = item;
- }
- }
- }
- if (item.isContainer) {
- item.items.each(getFieldsFrom);
- }
- };
- this.getItems().each(getFieldsFrom);
- return (byName) ? (fields[byName] || []) : fields;
- },
- /**
- * Returns an array of fields in this formpanel.
- * @return {Ext.field.Field[]} An array of fields in this form panel.
- * @private
- */
- getFieldsArray: function() {
- var fields = [];
- var getFieldsFrom = function(item) {
- if (item.isField) {
- fields.push(item);
- }
- if (item.isContainer) {
- item.items.each(getFieldsFrom);
- }
- };
- this.items.each(getFieldsFrom);
- return fields;
- },
- getFieldsFromItem: Ext.emptyFn,
- /**
- * Shows a generic/custom mask over a designated Element.
- * @param {String/Object} cfg Either a string message or a configuration object supporting
- * the following options:
- *
- * {
- * message : 'Please Wait',
- * cls : 'form-mask'
- * }
- *
- * @param {Object} target
- * @return {Ext.form.Panel} This form
- * @deprecated 2.0.0 Please use {@link #setMasked} instead.
- */
- showMask: function(cfg, target) {
- //<debug>
- Ext.Logger.warn('showMask is now deprecated. Please use Ext.form.Panel#setMasked instead');
- //</debug>
- cfg = Ext.isObject(cfg) ? cfg.message : cfg;
- if (cfg) {
- this.setMasked({
- xtype: 'loadmask',
- message: cfg
- });
- } else {
- this.setMasked(true);
- }
- return this;
- },
- /**
- * Hides a previously shown wait mask (See {@link #showMask}).
- * @return {Ext.form.Panel} this
- * @deprecated 2.0.0 Please use {@link #unmask} or {@link #setMasked} instead.
- */
- hideMask: function() {
- this.setMasked(false);
- return this;
- },
- /**
- * Returns the currently focused field
- * @return {Ext.field.Field} The currently focused field, if one is focused or `null`.
- * @private
- */
- getFocusedField: function() {
- var fields = this.getFieldsArray(),
- ln = fields.length,
- field, i;
- for (i = 0; i < ln; i++) {
- field = fields[i];
- if (field.isFocused) {
- return field;
- }
- }
- return null;
- },
- /**
- * @private
- * @return {Boolean/Ext.field.Field} The next field if one exists, or `false`.
- * @private
- */
- getNextField: function() {
- var fields = this.getFieldsArray(),
- focusedField = this.getFocusedField(),
- index;
- if (focusedField) {
- index = fields.indexOf(focusedField);
- if (index !== fields.length - 1) {
- index++;
- return fields[index];
- }
- }
- return false;
- },
- /**
- * Tries to focus the next field in the form, if there is currently a focused field.
- * @return {Boolean/Ext.field.Field} The next field that was focused, or `false`.
- * @private
- */
- focusNextField: function() {
- var field = this.getNextField();
- if (field) {
- field.focus();
- return field;
- }
- return false;
- },
- /**
- * @private
- * @return {Boolean/Ext.field.Field} The next field if one exists, or `false`.
- */
- getPreviousField: function() {
- var fields = this.getFieldsArray(),
- focusedField = this.getFocusedField(),
- index;
- if (focusedField) {
- index = fields.indexOf(focusedField);
- if (index !== 0) {
- index--;
- return fields[index];
- }
- }
- return false;
- },
- /**
- * Tries to focus the previous field in the form, if there is currently a focused field.
- * @return {Boolean/Ext.field.Field} The previous field that was focused, or `false`.
- * @private
- */
- focusPreviousField: function() {
- var field = this.getPreviousField();
- if (field) {
- field.focus();
- return field;
- }
- return false;
- }
- }, function() {
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.Easing', {
- requires: ['Ext.fx.easing.Linear'],
- constructor: function(easing) {
- return Ext.factory(easing, Ext.fx.easing.Linear, null, 'easing');
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.runner.Css', {
- extend: 'Ext.Evented',
- requires: [
- 'Ext.fx.Animation'
- ],
- prefixedProperties: {
- 'transform' : true,
- 'transform-origin' : true,
- 'perspective' : true,
- 'transform-style' : true,
- 'transition' : true,
- 'transition-property' : true,
- 'transition-duration' : true,
- 'transition-timing-function': true,
- 'transition-delay' : true,
- 'animation' : true,
- 'animation-name' : true,
- 'animation-duration' : true,
- 'animation-iteration-count' : true,
- 'animation-direction' : true,
- 'animation-timing-function' : true,
- 'animation-delay' : true
- },
- lengthProperties: {
- 'top' : true,
- 'right' : true,
- 'bottom' : true,
- 'left' : true,
- 'width' : true,
- 'height' : true,
- 'max-height' : true,
- 'max-width' : true,
- 'min-height' : true,
- 'min-width' : true,
- 'margin-bottom' : true,
- 'margin-left' : true,
- 'margin-right' : true,
- 'margin-top' : true,
- 'padding-bottom' : true,
- 'padding-left' : true,
- 'padding-right' : true,
- 'padding-top' : true,
- 'border-bottom-width': true,
- 'border-left-width' : true,
- 'border-right-width' : true,
- 'border-spacing' : true,
- 'border-top-width' : true,
- 'border-width' : true,
- 'outline-width' : true,
- 'letter-spacing' : true,
- 'line-height' : true,
- 'text-indent' : true,
- 'word-spacing' : true,
- 'font-size' : true,
- 'translate' : true,
- 'translateX' : true,
- 'translateY' : true,
- 'translateZ' : true,
- 'translate3d' : true
- },
- durationProperties: {
- 'transition-duration' : true,
- 'transition-delay' : true,
- 'animation-duration' : true,
- 'animation-delay' : true
- },
- angleProperties: {
- rotate : true,
- rotateX : true,
- rotateY : true,
- rotateZ : true,
- skew : true,
- skewX : true,
- skewY : true
- },
- lengthUnitRegex: /([a-z%]*)$/,
- DEFAULT_UNIT_LENGTH: 'px',
- DEFAULT_UNIT_ANGLE: 'deg',
- DEFAULT_UNIT_DURATION: 'ms',
- formattedNameCache: {},
- constructor: function() {
- var supports3dTransform = Ext.feature.has.Css3dTransforms;
- if (supports3dTransform) {
- this.transformMethods = ['translateX', 'translateY', 'translateZ', 'rotate', 'rotateX', 'rotateY', 'rotateZ', 'skewX', 'skewY', 'scaleX', 'scaleY', 'scaleZ'];
- }
- else {
- this.transformMethods = ['translateX', 'translateY', 'rotate', 'skewX', 'skewY', 'scaleX', 'scaleY'];
- }
- this.vendorPrefix = Ext.browser.getStyleDashPrefix();
- this.ruleStylesCache = {};
- return this;
- },
- getStyleSheet: function() {
- var styleSheet = this.styleSheet,
- styleElement, styleSheets;
- if (!styleSheet) {
- styleElement = document.createElement('style');
- styleElement.type = 'text/css';
- (document.head || document.getElementsByTagName('head')[0]).appendChild(styleElement);
- styleSheets = document.styleSheets;
- this.styleSheet = styleSheet = styleSheets[styleSheets.length - 1];
- }
- return styleSheet;
- },
- applyRules: function(selectors) {
- var styleSheet = this.getStyleSheet(),
- ruleStylesCache = this.ruleStylesCache,
- rules = styleSheet.cssRules,
- selector, properties, ruleStyle,
- ruleStyleCache, rulesLength, name, value;
- for (selector in selectors) {
- properties = selectors[selector];
- ruleStyle = ruleStylesCache[selector];
- if (ruleStyle === undefined) {
- rulesLength = rules.length;
- styleSheet.insertRule(selector + '{}', rulesLength);
- ruleStyle = ruleStylesCache[selector] = rules.item(rulesLength).style;
- }
- ruleStyleCache = ruleStyle.$cache;
- if (!ruleStyleCache) {
- ruleStyleCache = ruleStyle.$cache = {};
- }
- for (name in properties) {
- value = this.formatValue(properties[name], name);
- name = this.formatName(name);
- if (ruleStyleCache[name] !== value) {
- ruleStyleCache[name] = value;
- // console.log(name + " " + value);
- if (value === null) {
- ruleStyle.removeProperty(name);
- }
- else {
- ruleStyle.setProperty(name, value, 'important');
- }
- }
- }
- }
- return this;
- },
- applyStyles: function(styles) {
- var id, element, elementStyle, properties, name, value;
- for (id in styles) {
- // console.log("-> ["+id+"]", "APPLY======================");
- element = document.getElementById(id);
- if (!element) {
- return this;
- }
- elementStyle = element.style;
- properties = styles[id];
- for (name in properties) {
- value = this.formatValue(properties[name], name);
- name = this.formatName(name);
- // console.log("->-> ["+id+"]", name, value);
- if (value === null) {
- elementStyle.removeProperty(name);
- }
- else {
- elementStyle.setProperty(name, value, 'important');
- }
- }
- }
- return this;
- },
- formatName: function(name) {
- var cache = this.formattedNameCache,
- formattedName = cache[name];
- if (!formattedName) {
- if (this.prefixedProperties[name]) {
- formattedName = this.vendorPrefix + name;
- }
- else {
- formattedName = name;
- }
- cache[name] = formattedName;
- }
- return formattedName;
- },
- formatValue: function(value, name) {
- var type = typeof value,
- lengthUnit = this.DEFAULT_UNIT_LENGTH,
- transformMethods,
- method, i, ln,
- transformValues, values, unit;
- if (type == 'string') {
- if (this.lengthProperties[name]) {
- unit = value.match(this.lengthUnitRegex)[1];
- if (unit.length > 0) {
- //<debug error>
- if (unit !== lengthUnit) {
- Ext.Logger.error("Length unit: '" + unit + "' in value: '" + value + "' of property: '" + name + "' is not " +
- "valid for animation. Only 'px' is allowed");
- }
- //</debug>
- }
- else {
- return value + lengthUnit;
- }
- }
- return value;
- }
- else if (type == 'number') {
- if (value == 0) {
- return '0';
- }
- if (this.lengthProperties[name]) {
- return value + lengthUnit;
- }
- if (this.angleProperties[name]) {
- return value + this.DEFAULT_UNIT_ANGLE;
- }
- if (this.durationProperties[name]) {
- return value + this.DEFAULT_UNIT_DURATION;
- }
- }
- else if (name === 'transform') {
- transformMethods = this.transformMethods;
- transformValues = [];
- for (i = 0,ln = transformMethods.length; i < ln; i++) {
- method = transformMethods[i];
- transformValues.push(method + '(' + this.formatValue(value[method], method) + ')');
- }
- return transformValues.join(' ');
- }
- else if (Ext.isArray(value)) {
- values = [];
- for (i = 0,ln = value.length; i < ln; i++) {
- values.push(this.formatValue(value[i], name));
- }
- return (values.length > 0) ? values.join(', ') : 'none';
- }
- return value;
- }
- });
- /**
- * @author Jacky Nguyen <jacky@sencha.com>
- * @private
- */
- Ext.define('Ext.fx.runner.CssTransition', {
- extend: 'Ext.fx.runner.Css',
- listenersAttached: false,
- constructor: function() {
- this.runningAnimationsData = {};
- return this.callParent(arguments);
- },
- attachListeners: function() {
- this.listenersAttached = true;
- this.getEventDispatcher().addListener('element', '*', 'transitionend', 'onTransitionEnd', this);
- },
- onTransitionEnd: function(e) {
- var target = e.target,
- id = target.id;
- if (id && this.runningAnimationsData.hasOwnProperty(id)) {
- this.refreshRunningAnimationsData(Ext.get(target), [e.browserEvent.propertyName]);
- }
- },
- onAnimationEnd: function(element, data, animation, isInterrupted, isReplaced) {
- var id = element.getId(),
- runningData = this.runningAnimationsData[id],
- endRules = {},
- endData = {},
- runningNameMap, toPropertyNames, i, ln, name;
- animation.un('stop', 'onAnimationStop', this);
- if (runningData) {
- runningNameMap = runningData.nameMap;
- }
- endRules[id] = endData;
- if (data.onBeforeEnd) {
- data.onBeforeEnd.call(data.scope || this, element, isInterrupted);
- }
- animation.fireEvent('animationbeforeend', animation, element, isInterrupted);
- this.fireEvent('animationbeforeend', this, animation, element, isInterrupted);
- if (isReplaced || (!isInterrupted && !data.preserveEndState)) {
- toPropertyNames = data.toPropertyNames;
- for (i = 0,ln = toPropertyNames.length; i < ln; i++) {
- name = toPropertyNames[i];
- if (runningNameMap && !runningNameMap.hasOwnProperty(name)) {
- endData[name] = null;
- }
- }
- }
- if (data.after) {
- Ext.merge(endData, data.after);
- }
- this.applyStyles(endRules);
- if (data.onEnd) {
- data.onEnd.call(data.scope || this, element, isInterrupted);
- }
- animation.fireEvent('animationend', animation, element, isInterrupted);
- this.fireEvent('animationend', this, animation, element, isInterrupted);
- },
- onAllAnimationsEnd: function(element) {
- var id = element.getId(),
- endRules = {};
- delete this.runningAnimationsData[id];
- endRules[id] = {
- 'transition-property': null,
- 'transition-duration': null,
- 'transition-timing-function': null,
- 'transition-delay': null
- };
- this.applyStyles(endRules);
- this.fireEvent('animationallend', this, element);
- },
- hasRunningAnimations: function(element) {
- var id = element.getId(),
- runningAnimationsData = this.runningAnimationsData;
- return runningAnimationsData.hasOwnProperty(id) && runningAnimationsData[id].sessions.length > 0;
- },
- refreshRunningAnimationsData: function(element, propertyNames, interrupt, replace) {
- var id = element.getId(),
- runningAnimationsData = this.runningAnimationsData,
- runningData = runningAnimationsData[id];
- if (!runningData) {
- return;
- }
- var nameMap = runningData.nameMap,
- nameList = runningData.nameList,
- sessions = runningData.sessions,
- ln, j, subLn, name,
- i, session, map, list,
- hasCompletedSession = false;
- interrupt = Boolean(interrupt);
- replace = Boolean(replace);
- if (!sessions) {
- return this;
- }
- ln = sessions.length;
- if (ln === 0) {
- return this;
- }
- if (replace) {
- runningData.nameMap = {};
- nameList.length = 0;
- for (i = 0; i < ln; i++) {
- session = sessions[i];
- this.onAnimationEnd(element, session.data, session.animation, interrupt, replace);
- }
- sessions.length = 0;
- }
- else {
- for (i = 0; i < ln; i++) {
- session = sessions[i];
- map = session.map;
- list = session.list;
- for (j = 0,subLn = propertyNames.length; j < subLn; j++) {
- name = propertyNames[j];
- if (map[name]) {
- delete map[name];
- Ext.Array.remove(list, name);
- session.length--;
- if (--nameMap[name] == 0) {
- delete nameMap[name];
- Ext.Array.remove(nameList, name);
- }
- }
- }
- if (session.length == 0) {
- sessions.splice(i, 1);
- i--;
- ln--;
- hasCompletedSession = true;
- this.onAnimationEnd(element, session.data, session.animation, interrupt);
- }
- }
- }
- if (!replace && !interrupt && sessions.length == 0 && hasCompletedSession) {
- this.onAllAnimationsEnd(element);
- }
- },
- getRunningData: function(id) {
- var runningAnimationsData = this.runningAnimationsData;
- if (!runningAnimationsData.hasOwnProperty(id)) {
- runningAnimationsData[id] = {
- nameMap: {},
- nameList: [],
- sessions: []
- };
- }
- return runningAnimationsData[id];
- },
- getTestElement: function() {
- var testElement = this.testElement,
- iframe, iframeDocument, iframeStyle;
- if (!testElement) {
- iframe = document.createElement('iframe');
- iframeStyle = iframe.style;
- iframeStyle.setProperty('visibility', 'hidden', 'important');
- iframeStyle.setProperty('width', '0px', 'important');
- iframeStyle.setProperty('height', '0px', 'important');
- iframeStyle.setProperty('position', 'absolute', 'important');
- iframeStyle.setProperty('border', '0px', 'important');
- iframeStyle.setProperty('zIndex', '-1000', 'important');
- document.body.appendChild(iframe);
- iframeDocument = iframe.contentDocument;
- iframeDocument.open();
- iframeDocument.writeln('</body>');
- iframeDocument.close();
- this.testElement = testElement = iframeDocument.createElement('div');
- testElement.style.setProperty('position', 'absolute', '!important');
- iframeDocument.body.appendChild(testElement);
- this.testElementComputedStyle = window.getComputedStyle(testElement);
- }
- return testElement;
- },
- getCssStyleValue: function(name, value) {
- var testElement = this.getTestElement(),
- computedStyle = this.testElementComputedStyle,
- style = testElement.style;
- style.setProperty(name, value);
- value = computedStyle.getPropertyValue(name);
- style.removeProperty(name);
- return value;
- },
- run: function(animations) {
- var me = this,
- isLengthPropertyMap = this.lengthProperties,
- fromData = {},
- toData = {},
- data = {},
- element, elementId, from, to, before,
- fromPropertyNames, toPropertyNames,
- doApplyTo, message,
- runningData,
- i, j, ln, animation, propertiesLength, sessionNameMap,
- computedStyle, formattedName, name, toFormattedValue,
- computedValue, fromFormattedValue, isLengthProperty,
- runningNameMap, runningNameList, runningSessions, runningSession;
- if (!this.listenersAttached) {
- this.attachListeners();
- }
- animations = Ext.Array.from(animations);
- for (i = 0,ln = animations.length; i < ln; i++) {
- animation = animations[i];
- animation = Ext.factory(animation, Ext.fx.Animation);
- element = animation.getElement();
- computedStyle = window.getComputedStyle(element.dom);
- elementId = element.getId();
- data = Ext.merge({}, animation.getData());
- if (animation.onBeforeStart) {
- animation.onBeforeStart.call(animation.scope || this, element);
- }
- animation.fireEvent('animationstart', animation);
- this.fireEvent('animationstart', this, animation);
- data[elementId] = data;
- before = data.before;
- from = data.from;
- to = data.to;
- data.fromPropertyNames = fromPropertyNames = [];
- data.toPropertyNames = toPropertyNames = [];
- for (name in to) {
- if (to.hasOwnProperty(name)) {
- to[name] = toFormattedValue = this.formatValue(to[name], name);
- formattedName = this.formatName(name);
- isLengthProperty = isLengthPropertyMap.hasOwnProperty(name);
- if (!isLengthProperty) {
- toFormattedValue = this.getCssStyleValue(formattedName, toFormattedValue);
- }
- if (from.hasOwnProperty(name)) {
- from[name] = fromFormattedValue = this.formatValue(from[name], name);
- if (!isLengthProperty) {
- fromFormattedValue = this.getCssStyleValue(formattedName, fromFormattedValue);
- }
- if (toFormattedValue !== fromFormattedValue) {
- fromPropertyNames.push(formattedName);
- toPropertyNames.push(formattedName);
- }
- }
- else {
- computedValue = computedStyle.getPropertyValue(formattedName);
- if (toFormattedValue !== computedValue) {
- toPropertyNames.push(formattedName);
- }
- }
- }
- }
- propertiesLength = toPropertyNames.length;
- if (propertiesLength === 0) {
- this.onAnimationEnd(element, data, animation);
- continue;
- }
- runningData = this.getRunningData(elementId);
- runningSessions = runningData.sessions;
- if (runningSessions.length > 0) {
- this.refreshRunningAnimationsData(
- element, Ext.Array.merge(fromPropertyNames, toPropertyNames), true, data.replacePrevious
- );
- }
- runningNameMap = runningData.nameMap;
- runningNameList = runningData.nameList;
- sessionNameMap = {};
- for (j = 0; j < propertiesLength; j++) {
- name = toPropertyNames[j];
- sessionNameMap[name] = true;
- if (!runningNameMap.hasOwnProperty(name)) {
- runningNameMap[name] = 1;
- runningNameList.push(name);
- }
- else {
- runningNameMap[name]++;
- }
- }
- runningSession = {
- element: element,
- map: sessionNameMap,
- list: toPropertyNames.slice(),
- length: propertiesLength,
- data: data,
- animation: animation
- };
- runningSessions.push(runningSession);
- animation.on('stop', 'onAnimationStop', this);
- fromData[elementId] = from = Ext.apply(Ext.Object.chain(before), from);
- if (runningNameList.length > 0) {
- fromPropertyNames = Ext.Array.difference(runningNameList, fromPropertyNames);
- toPropertyNames = Ext.Array.merge(fromPropertyNames, toPropertyNames);
- from['transition-property'] = fromPropertyNames;
- }
- toData[elementId] = to = Ext.Object.chain(to);
- to['transition-property'] = toPropertyNames;
- to['transition-duration'] = data.duration;
- to['transition-timing-function'] = data.easing;
- to['transition-delay'] = data.delay;
- animation.startTime = Date.now();
- }
- message = this.$className;
- this.applyStyles(fromData);
- doApplyTo = function(e) {
- if (e.data === message && e.source === window) {
- window.removeEventListener('message', doApplyTo, false);
- me.applyStyles(toData);
- }
- };
- window.addEventListener('message', doApplyTo, false);
- window.postMessage(message, '*');
- },
- onAnimationStop: function(animation) {
- var runningAnimationsData = this.runningAnimationsData,
- id, runningData, sessions, i, ln, session;
- for (id in runningAnimationsData) {
- if (runningAnimationsData.hasOwnProperty(id)) {
- runningData = runningAnimationsData[id];
- sessions = runningData.sessions;
- for (i = 0,ln = sessions.length; i < ln; i++) {
- session = sessions[i];
- if (session.animation === animation) {
- this.refreshRunningAnimationsData(session.element, session.list.slice(), false);
- }
- }
- }
- }
- }
- });
- /**
- * @class Ext.fx.Runner
- * @private
- */
- Ext.define('Ext.fx.Runner', {
- requires: [
- 'Ext.fx.runner.CssTransition'
- // 'Ext.fx.runner.CssAnimation'
- ],
- constructor: function() {
- return new Ext.fx.runner.CssTransition();
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.animation.Cube', {
- extend: 'Ext.fx.animation.Abstract',
- alias: 'animation.cube',
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- before: {
- // 'transform-style': 'preserve-3d'
- },
- after: {},
- /**
- * @cfg {String} direction The direction of which the slide animates
- * @accessor
- */
- direction: 'right',
- out: false
- },
- // getData: function() {
- // var to = this.getTo(),
- // from = this.getFrom(),
- // out = this.getOut(),
- // direction = this.getDirection(),
- // el = this.getElement(),
- // elW = el.getWidth(),
- // elH = el.getHeight(),
- // halfWidth = (elW / 2),
- // halfHeight = (elH / 2),
- // fromTransform = {},
- // toTransform = {},
- // originalFromTransform = {
- // rotateY: 0,
- // translateX: 0,
- // translateZ: 0
- // },
- // originalToTransform = {
- // rotateY: 90,
- // translateX: halfWidth,
- // translateZ: halfWidth
- // },
- // originalVerticalFromTransform = {
- // rotateX: 0,
- // translateY: 0,
- // translateZ: 0
- // },
- // originalVerticalToTransform = {
- // rotateX: 90,
- // translateY: halfHeight,
- // translateZ: halfHeight
- // },
- // tempTransform;
- //
- // if (direction == "left" || direction == "right") {
- // if (out) {
- // toTransform = originalToTransform;
- // fromTransform = originalFromTransform;
- // } else {
- // toTransform = originalFromTransform;
- // fromTransform = originalToTransform;
- // fromTransform.rotateY *= -1;
- // fromTransform.translateX *= -1;
- // }
- //
- // if (direction === 'right') {
- // tempTransform = fromTransform;
- // fromTransform = toTransform;
- // toTransform = tempTransform;
- // }
- // }
- //
- // if (direction == "up" || direction == "down") {
- // if (out) {
- // toTransform = originalVerticalFromTransform;
- // fromTransform = {
- // rotateX: -90,
- // translateY: halfHeight,
- // translateZ: halfHeight
- // };
- // } else {
- // fromTransform = originalVerticalFromTransform;
- // toTransform = {
- // rotateX: 90,
- // translateY: -halfHeight,
- // translateZ: halfHeight
- // };
- // }
- //
- // if (direction == "up") {
- // tempTransform = fromTransform;
- // fromTransform = toTransform;
- // toTransform = tempTransform;
- // }
- // }
- //
- // from.set('transform', fromTransform);
- // to.set('transform', toTransform);
- //
- // return this.callParent(arguments);
- // },
- getData: function() {
- var to = this.getTo(),
- from = this.getFrom(),
- before = this.getBefore(),
- after = this.getAfter(),
- out = this.getOut(),
- direction = this.getDirection(),
- el = this.getElement(),
- elW = el.getWidth(),
- elH = el.getHeight(),
- origin = out ? '100% 100%' : '0% 0%',
- fromOpacity = 1,
- toOpacity = 1,
- transformFrom = {
- rotateY: 0,
- translateZ: 0
- },
- transformTo = {
- rotateY: 0,
- translateZ: 0
- };
- if (direction == "left" || direction == "right") {
- if (out) {
- toOpacity = 0.5;
- transformTo.translateZ = elW;
- transformTo.rotateY = -90;
- } else {
- fromOpacity = 0.5;
- transformFrom.translateZ = elW;
- transformFrom.rotateY = 90;
- }
- }
- before['transform-origin'] = origin;
- after['transform-origin'] = null;
- to.set('transform', transformTo);
- from.set('transform', transformFrom);
- from.set('opacity', fromOpacity);
- to.set('opacity', toOpacity);
- return this.callParent(arguments);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.animation.Wipe', {
- extend: 'Ext.fx.Animation',
- alternateClassName: 'Ext.fx.animation.WipeIn',
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- easing: 'ease-out',
- /**
- * @cfg {String} direction The direction of which the slide animates
- * @accessor
- */
- direction: 'right',
- /**
- * @cfg {Boolean} out True if you want to make this animation wipe out, instead of slide in.
- * @accessor
- */
- out: false
- },
- refresh: function() {
- var me = this,
- el = me.getElement(),
- elBox = el.dom.getBoundingClientRect(),
- elWidth = elBox.width,
- elHeight = elBox.height,
- from = me.getFrom(),
- to = me.getTo(),
- out = me.getOut(),
- direction = me.getDirection(),
- maskFromX = 0,
- maskFromY = 0,
- maskToX = 0,
- maskToY = 0,
- mask, tmp;
- switch (direction) {
- case 'up':
- if (out) {
- mask = '-webkit-gradient(linear, left top, left bottom, from(#000), to(transparent), color-stop(33%, #000), color-stop(66%, transparent))';
- maskFromY = elHeight * 3 + 'px';
- maskToY = elHeight + 'px';
- } else {
- mask = '-webkit-gradient(linear, left top, left bottom, from(transparent), to(#000), color-stop(66%, #000), color-stop(33%, transparent))';
- maskFromY = -elHeight * 2 + 'px';
- maskToY = 0;
- }
- break;
- case 'down':
- if (out) {
- mask = '-webkit-gradient(linear, left top, left bottom, from(transparent), to(#000), color-stop(66%, #000), color-stop(33%, transparent))';
- maskFromY = -elHeight * 2 + 'px';
- maskToY = 0;
- } else {
- mask = '-webkit-gradient(linear, left top, left bottom, from(#000), to(transparent), color-stop(33%, #000), color-stop(66%, transparent))';
- maskFromY = elHeight * 3 + 'px';
- maskToY = elHeight + 'px';
- }
- break;
- case 'right':
- if (out) {
- mask = '-webkit-gradient(linear, right top, left top, from(#000), to(transparent), color-stop(33%, #000), color-stop(66%, transparent))';
- maskFromX = -elWidth * 2 + 'px';
- maskToX = 0;
- } else {
- mask = '-webkit-gradient(linear, right top, left top, from(transparent), to(#000), color-stop(66%, #000), color-stop(33%, transparent))';
- maskToX = -elWidth * 2 + 'px';
- }
- break;
- case 'left':
- if (out) {
- mask = '-webkit-gradient(linear, right top, left top, from(transparent), to(#000), color-stop(66%, #000), color-stop(33%, transparent))';
- maskToX = -elWidth * 2 + 'px';
- } else {
- mask = '-webkit-gradient(linear, right top, left top, from(#000), to(transparent), color-stop(33%, #000), color-stop(66%, transparent))';
- maskFromX = -elWidth * 2 + 'px';
- maskToX = 0;
- }
- break;
- }
- if (!out) {
- tmp = maskFromY;
- maskFromY = maskToY;
- maskToY = tmp;
- tmp = maskFromX;
- maskFromX = maskToX;
- maskToX = tmp;
- }
- from.set('mask-image', mask);
- from.set('mask-size', elWidth * 3 + 'px ' + elHeight * 3 + 'px');
- from.set('mask-position-x', maskFromX);
- from.set('mask-position-y', maskFromY);
- to.set('mask-position-x', maskToX);
- to.set('mask-position-y', maskToY);
- // me.setEasing(out ? 'ease-in' : 'ease-out');
- },
- getData: function() {
- this.refresh();
- return this.callParent(arguments);
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.animation.WipeOut', {
- extend: 'Ext.fx.animation.Wipe',
- config: {
- // @hide
- out: true
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.easing.EaseIn', {
- extend: 'Ext.fx.easing.Linear',
- alias: 'easing.ease-in',
- config: {
- exponent: 4,
- duration: 1500
- },
- getValue: function() {
- var deltaTime = Ext.Date.now() - this.getStartTime(),
- duration = this.getDuration(),
- startValue = this.getStartValue(),
- endValue = this.getEndValue(),
- distance = this.distance,
- theta = deltaTime / duration,
- thetaEnd = Math.pow(theta, this.getExponent()),
- currentValue = startValue + (thetaEnd * distance);
- if (deltaTime >= duration) {
- this.isEnded = true;
- return endValue;
- }
- return currentValue;
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.layout.card.Cube', {
- extend: 'Ext.fx.layout.card.Style',
- alias: 'fx.layout.card.cube',
- config: {
- reverse: null,
- inAnimation: {
- type: 'cube'
- },
- outAnimation: {
- type: 'cube',
- out: true
- }
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.layout.card.ScrollCover', {
- extend: 'Ext.fx.layout.card.Scroll',
- alias: 'fx.layout.card.scrollcover',
- onActiveItemChange: function(cardLayout, inItem, outItem, options, controller) {
- var containerElement, containerSize, xy, animConfig,
- inTranslate, outTranslate;
- this.lastController = controller;
- this.inItem = inItem;
- if (inItem && outItem) {
- containerElement = this.getLayout().container.innerElement;
- containerSize = containerElement.getSize();
- xy = this.calculateXY(containerSize);
- animConfig = {
- easing: this.getEasing(),
- duration: this.getDuration()
- };
- inItem.renderElement.dom.style.setProperty('visibility', 'hidden', '!important');
- inTranslate = inItem.setTranslatable(true).getTranslatable();
- outTranslate = outItem.setTranslatable(true).getTranslatable();
- outTranslate.translate({ x: 0, y: 0});
- // outItem.setTranslate(null);
- inTranslate.translate({ x: xy.left, y: xy.top});
- inTranslate.getWrapper().dom.style.setProperty('z-index', '100', '!important');
- inItem.show();
- inTranslate.on({
- animationstart: 'onInAnimationStart',
- animationend: 'onInAnimationEnd',
- scope: this
- });
- inTranslate.translateAnimated({ x: 0, y: 0}, animConfig);
- controller.pause();
- }
- },
- onInAnimationStart: function() {
- this.inItem.renderElement.dom.style.removeProperty('visibility');
- },
- onInAnimationEnd: function() {
- this.inItem.getTranslatable().getWrapper().dom.style.removeProperty('z-index'); // Remove this when we can remove translatable
- // this.inItem.setTranslatable(null);
- this.lastController.resume();
- }
- });
- /**
- * @private
- */
- Ext.define('Ext.fx.layout.card.ScrollReveal', {
- extend: 'Ext.fx.layout.card.Scroll',
- alias: 'fx.layout.card.scrollreveal',
- onActiveItemChange: function(cardLayout, inItem, outItem, options, controller) {
- var containerElement, containerSize, xy, animConfig,
- outTranslate, inTranslate;
- this.lastController = controller;
- this.outItem = outItem;
- this.inItem = inItem;
- if (inItem && outItem) {
- containerElement = this.getLayout().container.innerElement;
- containerSize = containerElement.getSize();
- xy = this.calculateXY(containerSize);
- animConfig = {
- easing: this.getEasing(),
- duration: this.getDuration()
- };
- outTranslate = outItem.setTranslatable(true).getTranslatable();
- inTranslate = inItem.setTranslatable(true).getTranslatable();
- outTranslate.getWrapper().dom.style.setProperty('z-index', '100', '!important');
- outTranslate.translate({ x: 0, y: 0});
- inTranslate.translate({ x: 0, y: 0});
- inItem.show();
- outTranslate.on({
- animationend: 'onOutAnimationEnd',
- scope: this
- });
- outTranslate.translateAnimated({ x: xy.x, y: xy.y}, animConfig);
- controller.pause();
- }
- },
- onOutAnimationEnd: function() {
- this.outItem.getTranslatable().getWrapper().dom.style.removeProperty('z-index'); // Remove this when we can remove translatable
- // this.outItem.setTranslatable(null);
- this.lastController.resume();
- }
- });
- /**
- * @author Jacky Nguyen <jacky@sencha.com>
- * @private
- */
- Ext.define('Ext.fx.runner.CssAnimation', {
- extend: 'Ext.fx.runner.Css',
- constructor: function() {
- this.runningAnimationsMap = {};
- this.elementEndStates = {};
- this.animationElementMap = {};
- this.keyframesRulesCache = {};
- this.uniqueId = 0;
- return this.callParent(arguments);
- },
- attachListeners: function() {
- var eventDispatcher = this.getEventDispatcher();
- this.listenersAttached = true;
- eventDispatcher.addListener('element', '*', 'animationstart', 'onAnimationStart', this);
- eventDispatcher.addListener('element', '*', 'animationend', 'onAnimationEnd', this);
- },
- onAnimationStart: function(e) {
- var name = e.browserEvent.animationName,
- elementId = this.animationElementMap[name],
- animation = this.runningAnimationsMap[elementId][name],
- elementEndStates = this.elementEndStates,
- elementEndState = elementEndStates[elementId],
- data = {};
- console.log("START============= " + name);
- if (elementEndState) {
- delete elementEndStates[elementId];
- data[elementId] = elementEndState;
- this.applyStyles(data);
- }
- if (animation.before) {
- data[elementId] = animation.before;
- this.applyStyles(data);
- }
- },
- onAnimationEnd: function(e) {
- var element = e.target,
- name = e.browserEvent.animationName,
- animationElementMap = this.animationElementMap,
- elementId = animationElementMap[name],
- runningAnimationsMap = this.runningAnimationsMap,
- runningAnimations = runningAnimationsMap[elementId],
- animation = runningAnimations[name];
- console.log("END============= " + name);
- if (animation.onBeforeEnd) {
- animation.onBeforeEnd.call(animation.scope || this, element);
- }
- if (animation.onEnd) {
- animation.onEnd.call(animation.scope || this, element);
- }
- delete animationElementMap[name];
- delete runningAnimations[name];
- this.removeKeyframesRule(name);
- },
- generateAnimationId: function() {
- return 'animation-' + (++this.uniqueId);
- },
- run: function(animations) {
- var data = {},
- elementEndStates = this.elementEndStates,
- animationElementMap = this.animationElementMap,
- runningAnimationsMap = this.runningAnimationsMap,
- runningAnimations, states,
- elementId, animationId, i, ln, animation,
- name, runningAnimation,
- names, durations, easings, delays, directions, iterations;
- if (!this.listenersAttached) {
- this.attachListeners();
- }
- animations = Ext.Array.from(animations);
- for (i = 0,ln = animations.length; i < ln; i++) {
- animation = animations[i];
- animation = Ext.factory(animation, Ext.fx.Animation);
- elementId = animation.getElement().getId();
- animationId = animation.getName() || this.generateAnimationId();
- animationElementMap[animationId] = elementId;
- animation = animation.getData();
- states = animation.states;
- this.addKeyframesRule(animationId, states);
- runningAnimations = runningAnimationsMap[elementId];
- if (!runningAnimations) {
- runningAnimations = runningAnimationsMap[elementId] = {};
- }
- runningAnimations[animationId] = animation;
- names = [];
- durations = [];
- easings = [];
- delays = [];
- directions = [];
- iterations = [];
- for (name in runningAnimations) {
- if (runningAnimations.hasOwnProperty(name)) {
- runningAnimation = runningAnimations[name];
- names.push(name);
- durations.push(runningAnimation.duration);
- easings.push(runningAnimation.easing);
- delays.push(runningAnimation.delay);
- directions.push(runningAnimation.direction);
- iterations.push(runningAnimation.iteration);
- }
- }
- data[elementId] = {
- 'animation-name' : names,
- 'animation-duration' : durations,
- 'animation-timing-function' : easings,
- 'animation-delay' : delays,
- 'animation-direction' : directions,
- 'animation-iteration-count' : iterations
- };
- // Ext.apply(data[elementId], animation.origin);
- if (animation.preserveEndState) {
- elementEndStates[elementId] = states['100%'];
- }
- }
- this.applyStyles(data);
- },
- addKeyframesRule: function(name, keyframes) {
- var percentage, properties,
- keyframesRule,
- styleSheet, rules, styles, rulesLength, key, value;
- styleSheet = this.getStyleSheet();
- rules = styleSheet.cssRules;
- rulesLength = rules.length;
- styleSheet.insertRule('@' + this.vendorPrefix + 'keyframes ' + name + '{}', rulesLength);
- keyframesRule = rules[rulesLength];
- for (percentage in keyframes) {
- properties = keyframes[percentage];
- rules = keyframesRule.cssRules;
- rulesLength = rules.length;
- styles = [];
- for (key in properties) {
- value = this.formatValue(properties[key], key);
- key = this.formatName(key);
- styles.push(key + ':' + value);
- }
- keyframesRule.insertRule(percentage + '{' + styles.join(';') + '}', rulesLength);
- }
- return this;
- },
- removeKeyframesRule: function(name) {
- var styleSheet = this.getStyleSheet(),
- rules = styleSheet.cssRules,
- i, ln, rule;
- for (i = 0,ln = rules.length; i < ln; i++) {
- rule = rules[i];
- if (rule.name === name) {
- styleSheet.removeRule(i);
- break;
- }
- }
- return this;
- }
- });
- //<feature logger>
- Ext.define('Ext.log.Base', {
- config: {},
- constructor: function(config) {
- this.initConfig(config);
- return this;
- }
- });
- //</feature>
- //<feature logger>
- /**
- * @class Ext.Logger
- * Logs messages to help with debugging.
- *
- * ## Example
- *
- * Ext.Logger.deprecate('This method is no longer supported.');
- *
- * @singleton
- */
- (function() {
- var Logger = Ext.define('Ext.log.Logger', {
- extend: 'Ext.log.Base',
- statics: {
- defaultPriority: 'info',
- priorities: {
- /**
- * @method verbose
- * Convenience method for {@link #log} with priority 'verbose'.
- */
- verbose: 0,
- /**
- * @method info
- * Convenience method for {@link #log} with priority 'info'.
- */
- info: 1,
- /**
- * @method deprecate
- * Convenience method for {@link #log} with priority 'deprecate'.
- */
- deprecate: 2,
- /**
- * @method warn
- * Convenience method for {@link #log} with priority 'warn'.
- */
- warn: 3,
- /**
- * @method error
- * Convenience method for {@link #log} with priority 'error'.
- */
- error: 4
- }
- },
- config: {
- enabled: true,
- minPriority: 'deprecate',
- writers: {}
- },
- /**
- * Logs a message to help with debugging.
- * @param {String} message Message to log.
- * @param {Number} priority Priority of the log message.
- */
- log: function(message, priority, callerId) {
- if (!this.getEnabled()) {
- return this;
- }
- var statics = Logger,
- priorities = statics.priorities,
- priorityValue = priorities[priority],
- caller = this.log.caller,
- callerDisplayName = '',
- writers = this.getWriters(),
- event, i, originalCaller;
- if (!priority) {
- priority = 'info';
- }
- if (priorities[this.getMinPriority()] > priorityValue) {
- return this;
- }
- if (!callerId) {
- callerId = 1;
- }
- if (Ext.isArray(message)) {
- message = message.join(" ");
- }
- else {
- message = String(message);
- }
- if (typeof callerId == 'number') {
- i = callerId;
- do {
- i--;
- caller = caller.caller;
- if (!caller) {
- break;
- }
- if (!originalCaller) {
- originalCaller = caller.caller;
- }
- if (i <= 0 && caller.displayName) {
- break;
- }
- }
- while (caller !== originalCaller);
- callerDisplayName = Ext.getDisplayName(caller);
- }
- else {
- caller = caller.caller;
- callerDisplayName = Ext.getDisplayName(callerId) + '#' + caller.$name;
- }
- event = {
- time: Ext.Date.now(),
- priority: priorityValue,
- priorityName: priority,
- message: message,
- caller: caller,
- callerDisplayName: callerDisplayName
- };
- for (i in writers) {
- if (writers.hasOwnProperty(i)) {
- writers[i].write(Ext.merge({}, event));
- }
- }
- return this;
- }
- }, function() {
- Ext.Object.each(this.priorities, function(priority) {
- this.override(priority, function(message, callerId) {
- if (!callerId) {
- callerId = 1;
- }
- if (typeof callerId == 'number') {
- callerId += 1;
- }
- this.log(message, priority, callerId);
- });
- }, this);
- });
- })();
- //</feature>
- //<feature logger>
- Ext.define('Ext.log.filter.Filter', {
- extend: 'Ext.log.Base',
- accept: function(event) {
- return true;
- }
- });
- //</feature>
- //<feature logger>
- Ext.define('Ext.log.filter.Priority', {
- extend: 'Ext.log.filter.Filter',
- config: {
- minPriority: 1
- },
- accept: function(event) {
- return event.priority >= this.getMinPriority();
- }
- });
- //</feature>
- //<feature logger>
- Ext.define('Ext.log.formatter.Formatter', {
- extend: 'Ext.log.Base',
- config: {
- messageFormat: "{message}"
- },
- format: function(event) {
- return this.substitute(this.getMessageFormat(), event);
- },
- substitute: function(template, data) {
- var name, value;
- for (name in data) {
- if (data.hasOwnProperty(name)) {
- value = data[name];
- template = template.replace(new RegExp("\\{" + name + "\\}", "g"), value);
- }
- }
- return template;
- }
- });
- //</feature>
- //<feature logger>
- Ext.define('Ext.log.formatter.Default', {
- extend: 'Ext.log.formatter.Formatter',
- config: {
- messageFormat: "[{priorityName}][{callerDisplayName}] {message}"
- },
- format: function(event) {
- var event = Ext.merge({}, event, {
- priorityName: event.priorityName.toUpperCase()
- });
- return this.callParent([event]);
- }
- });
- //</feature>
- //<feature logger>
- Ext.define('Ext.log.formatter.Identity', {
- extend: 'Ext.log.formatter.Default',
- config: {
- messageFormat: "[{osIdentity}][{browserIdentity}][{timestamp}][{priorityName}][{callerDisplayName}] {message}"
- },
- format: function(event) {
- event.timestamp = Ext.Date.toString();
- event.browserIdentity = Ext.browser.name + ' ' + Ext.browser.version;
- event.osIdentity = Ext.os.name + ' ' + Ext.os.version;
- return this.callParent(arguments);
- }
- });
- //</feature>
- //<feature logger>
- Ext.define('Ext.log.writer.Writer', {
- extend: 'Ext.log.Base',
- requires: ['Ext.log.formatter.Formatter'],
- config: {
- formatter: null,
- filters: {}
- },
- constructor: function() {
- this.activeFilters = [];
- return this.callParent(arguments);
- },
- updateFilters: function(filters) {
- var activeFilters = this.activeFilters,
- i, filter;
- activeFilters.length = 0;
- for (i in filters) {
- if (filters.hasOwnProperty(i)) {
- filter = filters[i];
- activeFilters.push(filter);
- }
- }
- },
- write: function(event) {
- var filters = this.activeFilters,
- formatter = this.getFormatter(),
- i, ln, filter;
- for (i = 0,ln = filters.length; i < ln; i++) {
- filter = filters[i];
- if (!filters[i].accept(event)) {
- return this;
- }
- }
- if (formatter) {
- event.message = formatter.format(event);
- }
- this.doWrite(event);
- return this;
- },
- // @private
- doWrite: Ext.emptyFn
- });
- //</feature>
- //<feature logger>
- Ext.define('Ext.log.writer.Console', {
- extend: 'Ext.log.writer.Writer',
- config: {
- throwOnErrors: true,
- throwOnWarnings: false
- },
- doWrite: function(event) {
- var message = event.message,
- priority = event.priorityName,
- consoleMethod;
- if (priority === 'error' && this.getThrowOnErrors()) {
- throw new Error(message);
- }
- if (typeof console !== 'undefined') {
- consoleMethod = priority;
- if (consoleMethod === 'deprecate') {
- consoleMethod = 'warn';
- }
- if (consoleMethod === 'warn' && this.getThrowOnWarnings()) {
- throw new Error(message);
- }
- if (!(consoleMethod in console)) {
- consoleMethod = 'log';
- }
- console[consoleMethod](message);
- }
- }
- });
- //</feature>
- //<feature logger>
- Ext.define('Ext.log.writer.DocumentTitle', {
- extend: 'Ext.log.writer.Writer',
- doWrite: function(event) {
- var message = event.message;
- document.title = message;
- }
- });
- //</feature>
- //<feature logger>
- Ext.define('Ext.log.writer.Remote', {
- extend: 'Ext.log.writer.Writer',
- requires: [
- 'Ext.Ajax'
- ],
- config: {
- batchSendDelay: 100,
- onFailureRetryDelay: 500,
- url: ''
- },
- isSending: false,
- sendingTimer: null,
- constructor: function() {
- this.queue = [];
- this.send = Ext.Function.bind(this.send, this);
- return this.callParent(arguments);
- },
- doWrite: function(event) {
- var queue = this.queue;
- queue.push(event.message);
- if (!this.isSending && this.sendingTimer === null) {
- this.sendingTimer = setTimeout(this.send, this.getBatchSendDelay());
- }
- },
- send: function() {
- var queue = this.queue,
- messages = queue.slice();
- queue.length = 0;
- this.sendingTimer = null;
- if (messages.length > 0) {
- this.doSend(messages);
- }
- },
- doSend: function(messages) {
- var me = this;
- me.isSending = true;
- Ext.Ajax.request({
- url: me.getUrl(),
- method: 'POST',
- params: {
- messages: messages.join("\n")
- },
- success: function(){
- me.isSending = false;
- me.send();
- },
- failure: function() {
- setTimeout(function() {
- me.doSend(messages);
- }, me.getOnFailureRetryDelay());
- },
- scope: me
- });
- }
- });
- //</feature>
- /**
- * This component is used in {@link Ext.navigation.View} to control animations in the toolbar. You should never need to
- * interact with the component directly, unless you are subclassing it.
- * @private
- * @author Robert Dougan <rob@sencha.com>
- */
- Ext.define('Ext.navigation.Bar', {
- extend: 'Ext.TitleBar',
- requires: [
- 'Ext.Button',
- 'Ext.Spacer'
- ],
- // @private
- isToolbar: true,
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'toolbar',
- /**
- * @cfg
- * @inheritdoc
- */
- cls: Ext.baseCSSPrefix + 'navigation-bar',
- /**
- * @cfg {String} ui
- * Style options for Toolbar. Either 'light' or 'dark'.
- * @accessor
- */
- ui: 'dark',
- /**
- * @cfg {String} title
- * The title of the toolbar. You should NEVER set this, it is used internally. You set the title of the
- * navigation bar by giving a navigation views children a title configuration.
- * @private
- * @accessor
- */
- title: null,
- /**
- * @cfg
- * @hide
- * @accessor
- */
- defaultType: 'button',
- /**
- * @cfg
- * @ignore
- * @accessor
- */
- layout: {
- type: 'hbox'
- },
- /**
- * @cfg {Array/Object} items The child items to add to this NavigationBar. The {@link #cfg-defaultType} of
- * a NavigationBar is {@link Ext.Button}, so you do not need to specify an `xtype` if you are adding
- * buttons.
- *
- * You can also give items a `align` configuration which will align the item to the `left` or `right` of
- * the NavigationBar.
- * @hide
- * @accessor
- */
- /**
- * @cfg {String} defaultBackButtonText
- * The text to be displayed on the back button if:
- * a) The previous view does not have a title
- * b) The {@link #useTitleForBackButtonText} configuration is true.
- * @private
- * @accessor
- */
- defaultBackButtonText: 'Back',
- /**
- * @cfg {Object} animation
- * @private
- * @accessor
- */
- animation: {
- duration: 300
- },
- /**
- * @cfg {Boolean} useTitleForBackButtonText
- * Set to false if you always want to display the {@link #defaultBackButtonText} as the text
- * on the back button. True if you want to use the previous views title.
- * @private
- * @accessor
- */
- useTitleForBackButtonText: null,
- /**
- * @cfg {Ext.navigation.View} view A reference to the navigation view this bar is linked to.
- * @private
- * @accessor
- */
- view: null,
- /**
- * @cfg {Boolean} androidAnimation Optionally enable CSS transforms on Android 2
- * for NavigationBar animations. Note that this may cause flickering if the
- * NavigationBar is hidden.
- * @accessor
- */
- android2Transforms: false,
- /**
- * @cfg {Ext.Button/Object} backButton The configuration for the back button
- * @private
- * @accessor
- */
- backButton: {
- align: 'left',
- ui: 'back',
- hidden: true
- }
- },
- /**
- * @event back
- * Fires when the back button was tapped.
- * @param {Ext.navigation.Bar} this This bar
- */
- constructor: function(config) {
- config = config || {};
- if (!config.items) {
- config.items = [];
- }
- this.backButtonStack = [];
- this.activeAnimations = [];
- this.callParent([config]);
- },
- /**
- * @private
- */
- applyBackButton: function(config) {
- return Ext.factory(config, Ext.Button, this.getBackButton());
- },
- /**
- * @private
- */
- updateBackButton: function(newBackButton, oldBackButton) {
- if (oldBackButton) {
- this.remove(oldBackButton);
- }
- if (newBackButton) {
- this.add(newBackButton);
- newBackButton.on({
- scope: this,
- tap: this.onBackButtonTap
- });
- }
- },
- onBackButtonTap: function() {
- this.fireEvent('back', this);
- },
- /**
- * @private
- */
- updateView: function(newView) {
- var me = this,
- backButton = me.getBackButton(),
- innerItems, i, backButtonText, item, title;
- me.getItems();
- if (newView) {
- //update the back button stack with the current inner items of the view
- innerItems = newView.getInnerItems();
- for (i = 0; i < innerItems.length; i++) {
- item = innerItems[i];
- title = (item.getTitle) ? item.getTitle() : item.config.title;
- me.backButtonStack.push(title || ' ');
- }
- me.setTitle(me.getTitleText());
- backButtonText = me.getBackButtonText();
- if (backButtonText) {
- backButton.setText(backButtonText);
- backButton.show();
- }
- }
- },
- /**
- * @private
- */
- onViewAdd: function(view, item) {
- var me = this,
- backButtonStack = me.backButtonStack,
- hasPrevious, title;
- me.endAnimation();
- title = (item.getTitle) ? item.getTitle() : item.config.title;
- backButtonStack.push(title || ' ');
- hasPrevious = backButtonStack.length > 1;
- me.doChangeView(view, hasPrevious, false);
- },
- /**
- * @private
- */
- onViewRemove: function(view) {
- var me = this,
- backButtonStack = me.backButtonStack,
- hasPrevious;
- me.endAnimation();
- backButtonStack.pop();
- hasPrevious = backButtonStack.length > 1;
- me.doChangeView(view, hasPrevious, true);
- },
- /**
- * @private
- */
- doChangeView: function(view, hasPrevious, reverse) {
- var me = this,
- leftBox = me.leftBox,
- leftBoxElement = leftBox.element,
- titleComponent = me.titleComponent,
- titleElement = titleComponent.element,
- backButton = me.getBackButton(),
- titleText = me.getTitleText(),
- backButtonText = me.getBackButtonText(),
- animation = me.getAnimation() && view.getLayout().getAnimation(),
- animated = animation && animation.isAnimation && view.isPainted(),
- properties, leftGhost, titleGhost, leftProps, titleProps;
- if (animated) {
- leftGhost = me.createProxy(leftBox.element);
- leftBoxElement.setStyle('opacity', '0');
- backButton.setText(backButtonText);
- backButton[hasPrevious ? 'show' : 'hide']();
- titleGhost = me.createProxy(titleComponent.element.getParent());
- titleElement.setStyle('opacity', '0');
- me.setTitle(titleText);
- me.refreshTitlePosition();
- properties = me.measureView(leftGhost, titleGhost, reverse);
- leftProps = properties.left;
- titleProps = properties.title;
- me.isAnimating = true;
- me.animate(leftBoxElement, leftProps.element);
- me.animate(titleElement, titleProps.element, function() {
- titleElement.setLeft(properties.titleLeft);
- me.isAnimating = false;
- });
- if (Ext.os.is.Android2 && !this.getAndroid2Transforms()) {
- leftGhost.ghost.destroy();
- titleGhost.ghost.destroy();
- }
- else {
- me.animate(leftGhost.ghost, leftProps.ghost);
- me.animate(titleGhost.ghost, titleProps.ghost, function() {
- leftGhost.ghost.destroy();
- titleGhost.ghost.destroy();
- });
- }
- }
- else {
- if (hasPrevious) {
- backButton.setText(backButtonText);
- backButton.show();
- }
- else {
- backButton.hide();
- }
- me.setTitle(titleText);
- }
- },
- /**
- * Calculates and returns the position values needed for the back button when you are pushing a title.
- * @private
- */
- measureView: function(oldLeft, oldTitle, reverse) {
- var me = this,
- barElement = me.element,
- newLeftElement = me.leftBox.element,
- titleElement = me.titleComponent.element,
- minOffset = Math.min(barElement.getWidth() / 3, 200),
- newLeftWidth = newLeftElement.getWidth(),
- barX = barElement.getX(),
- barWidth = barElement.getWidth(),
- titleX = titleElement.getX(),
- titleLeft = titleElement.getLeft(),
- titleWidth = titleElement.getWidth(),
- oldLeftX = oldLeft.x,
- oldLeftWidth = oldLeft.width,
- oldLeftLeft = oldLeft.left,
- useLeft = Ext.os.is.Android2 && !this.getAndroid2Transforms(),
- newOffset, oldOffset, leftAnims, titleAnims, omega, theta;
- theta = barX - oldLeftX - oldLeftWidth;
- if (reverse) {
- newOffset = theta;
- oldOffset = Math.min(titleX - oldLeftWidth, minOffset);
- }
- else {
- oldOffset = theta;
- newOffset = Math.min(titleX - barX, minOffset);
- }
- if (useLeft) {
- leftAnims = {
- element: {
- from: {
- left: newOffset,
- opacity: 1
- },
- to: {
- left: 0,
- opacity: 1
- }
- }
- };
- }
- else {
- leftAnims = {
- element: {
- from: {
- transform: {
- translateX: newOffset
- },
- opacity: 0
- },
- to: {
- transform: {
- translateX: 0
- },
- opacity: 1
- }
- },
- ghost: {
- to: {
- transform: {
- translateX: oldOffset
- },
- opacity: 0
- }
- }
- };
- }
- theta = barX - titleX + newLeftWidth;
- if ((oldLeftLeft + titleWidth) > titleX) {
- omega = barX - titleX - titleWidth;
- }
- if (reverse) {
- titleElement.setLeft(0);
- oldOffset = barX + barWidth;
- if (omega !== undefined) {
- newOffset = omega;
- }
- else {
- newOffset = theta;
- }
- }
- else {
- newOffset = barWidth - titleX;
- if (omega !== undefined) {
- oldOffset = omega;
- }
- else {
- oldOffset = theta;
- }
- }
- if (useLeft) {
- titleAnims = {
- element: {
- from: {
- left: newOffset,
- opacity: 1
- },
- to: {
- left: titleLeft,
- opacity: 1
- }
- }
- };
- }
- else {
- titleAnims = {
- element: {
- from: {
- transform: {
- translateX: newOffset
- },
- opacity: 0
- },
- to: {
- transform: {
- translateX: titleLeft
- },
- opacity: 1
- }
- },
- ghost: {
- to: {
- transform: {
- translateX: oldOffset
- },
- opacity: 0
- }
- }
- };
- }
- return {
- left: leftAnims,
- title: titleAnims,
- titleLeft: titleLeft
- };
- },
- /**
- * Helper method used to animate elements.
- * You pass it an element, objects for the from and to positions an option onEnd callback called when the animation is over.
- * Normally this method is passed configurations returned from the methods such as #measureTitle(true) etc.
- * It is called from the #pushLeftBoxAnimated, #pushTitleAnimated, #popBackButtonAnimated and #popTitleAnimated
- * methods.
- *
- * If the current device is Android, it will use top/left to animate.
- * If it is anything else, it will use transform.
- * @private
- */
- animate: function(element, config, callback) {
- var me = this,
- animation;
- //reset the left of the element
- element.setLeft(0);
- config = Ext.apply(config, {
- element: element,
- easing: 'ease-in-out',
- duration: me.getAnimation().duration || 250,
- preserveEndState: true
- });
- animation = new Ext.fx.Animation(config);
- animation.on('animationend', function() {
- if (callback) {
- callback.call(me);
- }
- }, me);
- Ext.Animator.run(animation);
- me.activeAnimations.push(animation);
- },
- endAnimation: function() {
- var activeAnimations = this.activeAnimations,
- animation, i, ln;
- if (activeAnimations) {
- ln = activeAnimations.length;
- for (i = 0; i < ln; i++) {
- animation = activeAnimations[i];
- if (animation.isAnimating) {
- animation.stopAnimation();
- }
- else {
- animation.destroy();
- }
- }
- this.activeAnimations = [];
- }
- },
- refreshTitlePosition: function() {
- if (!this.isAnimating) {
- this.callParent();
- }
- },
- /**
- * Returns the text needed for the current back button at anytime.
- * @private
- */
- getBackButtonText: function() {
- var text = this.backButtonStack[this.backButtonStack.length - 2],
- useTitleForBackButtonText = this.getUseTitleForBackButtonText();
- if (!useTitleForBackButtonText) {
- if (text) {
- text = this.getDefaultBackButtonText();
- }
- }
- return text;
- },
- /**
- * Returns the text needed for the current title at anytime.
- * @private
- */
- getTitleText: function() {
- return this.backButtonStack[this.backButtonStack.length - 1];
- },
- /**
- * Handles removing back button stacks from this bar
- * @private
- */
- beforePop: function(count) {
- count--;
- for (var i = 0; i < count; i++) {
- this.backButtonStack.pop();
- }
- },
- /**
- * 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
- * the screen, much like the navigation bar proxy. This means that all animations, pushing, popping etc. all still work when if you hide/show
- * this bar at any time.
- * @private
- */
- doSetHidden: function(hidden) {
- if (!hidden) {
- this.element.setStyle({
- position: 'relative',
- top: 'auto',
- left: 'auto',
- width: 'auto'
- });
- } else {
- this.element.setStyle({
- position: 'absolute',
- top: '-1000px',
- left: '-1000px',
- width: this.element.getWidth() + 'px'
- });
- }
- },
- /**
- * Creates a proxy element of the passed element, and positions it in the same position, using absolute positioning.
- * The createNavigationBarProxy method uses this to create proxies of the backButton and the title elements.
- * @private
- */
- createProxy: function(element) {
- var ghost, x, y, left, width;
- ghost = element.dom.cloneNode(true);
- ghost.id = element.id + '-proxy';
- //insert it into the toolbar
- element.getParent().dom.appendChild(ghost);
- //set the x/y
- ghost = Ext.get(ghost);
- x = element.getX();
- y = element.getY();
- left = element.getLeft();
- width = element.getWidth();
- ghost.setStyle('position', 'absolute');
- ghost.setX(x);
- ghost.setY(y);
- ghost.setHeight(element.getHeight());
- ghost.setWidth(width);
- return {
- x: x,
- y: y,
- left: left,
- width: width,
- ghost: ghost
- };
- }
- });
- /**
- * @author Robert Dougan <rob@sencha.com>
- *
- * NavigationView is basically a {@link Ext.Container} with a {@link Ext.layout.Card card} layout, so only one view
- * can be visible at a time. However, NavigationView also adds extra functionality on top of this to allow
- * you to `push` and `pop` views at any time. When you do this, your NavigationView will automatically animate
- * between your current active view, and the new view you want to `push`, or the previous view you want to `pop`.
- *
- * Using the NavigationView is very simple. Here is a basic example of it in action:
- *
- * @example
- * var view = Ext.create('Ext.NavigationView', {
- * fullscreen: true,
- *
- * items: [{
- * title: 'First',
- * items: [{
- * xtype: 'button',
- * text: 'Push a new view!',
- * handler: function() {
- * // use the push() method to push another view. It works much like
- * // add() or setActiveItem(). it accepts a view instance, or you can give it
- * // a view config.
- * view.push({
- * title: 'Second',
- * html: 'Second view!'
- * });
- * }
- * }]
- * }]
- * });
- *
- * Now, here comes the fun part: you can push any view/item into the NavigationView, at any time, and it will
- * automatically handle the animations between the two views, including adding a back button (if necessary)
- * and showing the new title.
- *
- * view.push({
- * title: 'A new view',
- * html: 'Some new content'
- * });
- *
- * As you can see, it is as simple as calling the {@link #method-push} method, with a new view (instance or object). Done.
- *
- * You can also `pop` a view at any time. This will remove the top-most view from the NavigationView, and animate back
- * to the previous view. You can do this using the {@link #method-pop} method (which requires no arguments).
- *
- * view.pop();
- *
- * @aside guide navigation_view
- */
- Ext.define('Ext.navigation.View', {
- extend: 'Ext.Container',
- alternateClassName: 'Ext.NavigationView',
- xtype: 'navigationview',
- requires: ['Ext.navigation.Bar'],
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'navigationview',
- /**
- * @cfg {Boolean/Object} navigationBar
- * The NavigationBar used in this navigation view. It defaults to be docked to the top.
- *
- * You can just pass in a normal object if you want to customize the NavigationBar. For example:
- *
- * navigationBar: {
- * ui: 'dark',
- * docked: 'bottom'
- * }
- *
- * You **cannot** specify a *title* property in this configuration. The title of the navigationBar is taken
- * from the configuration of this views children:
- *
- * view.push({
- * title: 'This views title which will be shown in the navigation bar',
- * html: 'Some HTML'
- * });
- *
- * @accessor
- */
- navigationBar: {
- docked: 'top'
- },
- /**
- * @cfg {String} defaultBackButtonText
- * The text to be displayed on the back button if:
- *
- * - The previous view does not have a title.
- * - The {@link #useTitleForBackButtonText} configuration is `true`.
- * @accessor
- */
- defaultBackButtonText: 'Back',
- /**
- * @cfg {Boolean} useTitleForBackButtonText
- * Set to `false` if you always want to display the {@link #defaultBackButtonText} as the text
- * on the back button. `true` if you want to use the previous views title.
- * @accessor
- */
- useTitleForBackButtonText: false,
- /**
- * @cfg {Array/Object} items The child items to add to this NavigationView. This is usually an array of Component
- * configurations or instances, for example:
- *
- * Ext.create('Ext.Container', {
- * items: [
- * {
- * xtype: 'panel',
- * title: 'My title',
- * html: 'This is an item'
- * }
- * ]
- * });
- *
- * If you want a title to be displayed in the {@link #navigationBar}, you must specify a `title` configuration in your
- * view, like above.
- *
- * __Note:__ Only one view will be visible at a time. If you want to change to another view, use the {@link #method-push} or
- * {@link #setActiveItem} methods.
- * @accessor
- */
- /**
- * @cfg
- * @hide
- */
- layout: {
- type: 'card',
- animation: {
- duration: 300,
- easing: 'ease-out',
- type: 'slide',
- direction: 'left'
- }
- }
- // See https://sencha.jira.com/browse/TOUCH-1568
- // If you do, add to #navigationBar config docs:
- //
- // If you want to add a button on the right of the NavigationBar,
- // use the {@link #rightButton} configuration.
- },
- /**
- * @event push
- * Fires when a view is pushed into this navigation view
- * @param {Ext.navigation.View} this The component instance
- * @param {Mixed} view The view that has been pushed
- */
- /**
- * @event pop
- * Fires when a view is popped from this navigation view
- * @param {Ext.navigation.View} this The component instance
- * @param {Mixed} view The view that has been popped
- */
- /**
- * @event back
- * Fires when the back button in the navigation view was tapped.
- * @param {Ext.navigation.View} this The component instance\
- */
- // @private
- initialize: function() {
- var me = this,
- navBar = me.getNavigationBar();
- //add a listener onto the back button in the navigationbar
- navBar.on({
- back: me.onBackButtonTap,
- scope: me
- });
- me.relayEvents(navBar, 'rightbuttontap');
- me.relayEvents(me, {
- add: 'push',
- remove: 'pop'
- });
- //<debug>
- var layout = me.getLayout();
- if (layout && !layout.isCard) {
- Ext.Logger.error('The base layout for a NavigationView must always be a Card Layout');
- }
- //</debug>
- },
- /**
- * @private
- */
- applyLayout: function(config) {
- config = config || {};
- return config;
- },
- /**
- * @private
- * Called when the user taps on the back button
- */
- onBackButtonTap: function() {
- this.pop();
- this.fireEvent('back', this);
- },
- /**
- * Pushes a new view into this navigation view using the default animation that this view has.
- * @param {Object} view The view to push.
- * @return {Ext.Component} The new item you just pushed.
- */
- push: function(view) {
- return this.add(view);
- },
- /**
- * Removes the current active view from the stack and sets the previous view using the default animation
- * of this view. You can also pass a {@link Ext.ComponentQuery} selector to target what inner item to pop to.
- * @param {Number} count The number of views you want to pop.
- * @return {Ext.Component} The new active item.
- */
- pop: function(count) {
- if (this.beforePop(count)) {
- return this.doPop();
- }
- },
- /**
- * @private
- * Calculates whether it needs to remove any items from the stack when you are popping more than 1
- * item. If it does, it removes those views from the stack and returns `true`.
- * @return {Boolean} `true` if it has removed views.
- */
- beforePop: function(count) {
- var me = this,
- innerItems = me.getInnerItems();
- if (Ext.isString(count) || Ext.isObject(count)) {
- var last = innerItems.length - 1,
- i;
- for (i = last; i >= 0; i--) {
- if ((Ext.isString(count) && Ext.ComponentQuery.is(innerItems[i], count)) || (Ext.isObject(count) && count == innerItems[i])) {
- count = last - i;
- break;
- }
- }
- if (!Ext.isNumber(count)) {
- return false;
- }
- }
- var ln = innerItems.length,
- toRemove;
- //default to 1 pop
- if (!Ext.isNumber(count) || count < 1) {
- count = 1;
- }
- //check if we are trying to remove more items than we have
- count = Math.min(count, ln - 1);
- if (count) {
- //we need to reset the backButtonStack in the navigation bar
- me.getNavigationBar().beforePop(count);
- //get the items we need to remove from the view and remove theme
- toRemove = innerItems.splice(-count, count - 1);
- for (i = 0; i < toRemove.length; i++) {
- this.remove(toRemove[i]);
- }
- return true;
- }
- return false;
- },
- /**
- * @private
- */
- doPop: function() {
- var me = this,
- innerItems = this.getInnerItems();
- //set the new active item to be the new last item of the stack
- me.remove(innerItems[innerItems.length - 1]);
- return this.getActiveItem();
- },
- /**
- * Returns the previous item, if one exists.
- * @return {Mixed} The previous view
- */
- getPreviousItem: function() {
- var innerItems = this.getInnerItems();
- return innerItems[innerItems.length - 2];
- },
- /**
- * Updates the backbutton text accordingly in the {@link #navigationBar}
- * @private
- */
- updateUseTitleForBackButtonText: function(useTitleForBackButtonText) {
- var navigationBar = this.getNavigationBar();
- if (navigationBar) {
- navigationBar.setUseTitleForBackButtonText(useTitleForBackButtonText);
- }
- },
- /**
- * Updates the backbutton text accordingly in the {@link #navigationBar}
- * @private
- */
- updateDefaultBackButtonText: function(defaultBackButtonText) {
- var navigationBar = this.getNavigationBar();
- if (navigationBar) {
- navigationBar.setDefaultBackButtonText(defaultBackButtonText);
- }
- },
- // @private
- applyNavigationBar: function(config) {
- if (!config) {
- config = {
- hidden: true,
- docked: 'top'
- };
- }
- if (config.title) {
- delete config.title;
- //<debug>
- Ext.Logger.warn("Ext.navigation.View: The 'navigationBar' configuration does not accept a 'title' property. You " +
- "set the title of the navigationBar by giving this navigation view's children a 'title' property.");
- //</debug>
- }
- config.view = this;
- config.useTitleForBackButtonText = this.getUseTitleForBackButtonText();
- return Ext.factory(config, Ext.navigation.Bar, this.getNavigationBar());
- },
- // @private
- updateNavigationBar: function(newNavigationBar, oldNavigationBar) {
- if (oldNavigationBar) {
- this.remove(oldNavigationBar, true);
- }
- if (newNavigationBar) {
- this.add(newNavigationBar);
- }
- },
- /**
- * @private
- */
- applyActiveItem: function(activeItem, currentActiveItem) {
- var me = this,
- innerItems = me.getInnerItems();
- // Make sure the items are already initialized
- me.getItems();
- // If we are not initialzed yet, we should set the active item to the last item in the stack
- if (!me.initialized) {
- activeItem = innerItems.length - 1;
- }
- return this.callParent([activeItem, currentActiveItem]);
- },
- doResetActiveItem: function(innerIndex) {
- var me = this,
- innerItems = me.getInnerItems(),
- animation = me.getLayout().getAnimation();
- if (innerIndex > 0) {
- if (animation && animation.isAnimation) {
- animation.setReverse(true);
- }
- me.setActiveItem(innerIndex - 1);
- me.getNavigationBar().onViewRemove(me, innerItems[innerIndex], innerIndex);
- }
- },
- /**
- * @private
- */
- doRemove: function() {
- var animation = this.getLayout().getAnimation();
- if (animation && animation.isAnimation) {
- animation.setReverse(false);
- }
- this.callParent(arguments);
- },
- /**
- * @private
- */
- onItemAdd: function(item, index) {
- this.doItemLayoutAdd(item, index);
- if (!this.isItemsInitializing && item.isInnerItem()) {
- this.setActiveItem(item);
- this.getNavigationBar().onViewAdd(this, item, index);
- }
- if (this.initialized) {
- this.fireEvent('add', this, item, index);
- }
- },
- /**
- * Resets the view by removing all items between the first and last item.
- * @return {Ext.Component} The view that is now active
- */
- reset: function() {
- return this.pop(this.getInnerItems().length);
- }
- });
- /**
- * Adds a Load More button at the bottom of the list. When the user presses this button,
- * the next page of data will be loaded into the store and appended to the List.
- *
- * By specifying `{@link #autoPaging}: true`, an 'infinite scroll' effect can be achieved,
- * i.e., the next page of content will load automatically when the user scrolls to the
- * bottom of the list.
- *
- * ## Example
- *
- * Ext.create('Ext.dataview.List', {
- *
- * store: Ext.create('TweetStore'),
- *
- * plugins: [
- * {
- * xclass: 'Ext.plugin.ListPaging',
- * autoPaging: true
- * }
- * ],
- *
- * itemTpl: [
- * '<img src="{profile_image_url}" />',
- * '<div class="tweet">{text}</div>'
- * ]
- * });
- */
- Ext.define('Ext.plugin.ListPaging', {
- extend: 'Ext.Component',
- alias: 'plugin.listpaging',
- config: {
- /**
- * @cfg {Boolean} autoPaging
- * True to automatically load the next page when you scroll to the bottom of the list.
- */
- autoPaging: false,
- /**
- * @cfg {String} loadMoreText The text used as the label of the Load More button.
- */
- loadMoreText: 'Load More...',
- /**
- * @cfg {String} noMoreRecordsText The text used as the label of the Load More button when the Store's
- * {@link Ext.data.Store#totalCount totalCount} indicates that all of the records available on the server are
- * already loaded
- */
- noMoreRecordsText: 'No More Records',
- /**
- * @private
- * @cfg {String} loadTpl The template used to render the load more text
- */
- loadTpl: [
- '<div class="{cssPrefix}loading-spinner" style="font-size: 180%; margin: 10px auto;">',
- '<span class="{cssPrefix}loading-top"></span>',
- '<span class="{cssPrefix}loading-right"></span>',
- '<span class="{cssPrefix}loading-bottom"></span>',
- '<span class="{cssPrefix}loading-left"></span>',
- '</div>',
- '<div class="{cssPrefix}list-paging-msg">{message}</div>'
- ].join(''),
- /**
- * @cfg {Object} loadMoreCmp
- * @private
- */
- loadMoreCmp: {
- xtype: 'component',
- baseCls: Ext.baseCSSPrefix + 'list-paging',
- scrollDock: 'bottom',
- docked: 'bottom',
- hidden: true
- },
- /**
- * @private
- * @cfg {Boolean} loadMoreCmpAdded Indicates whether or not the load more component has been added to the List
- * yet.
- */
- loadMoreCmpAdded: false,
- /**
- * @private
- * @cfg {String} loadingCls The CSS class that is added to the {@link #loadMoreCmp} while the Store is loading
- */
- loadingCls: Ext.baseCSSPrefix + 'loading',
- /**
- * @private
- * @cfg {Ext.List} list Local reference to the List this plugin is bound to
- */
- list: null,
- /**
- * @private
- * @cfg {Ext.scroll.Scroller} scroller Local reference to the List's Scroller
- */
- scroller: null,
- /**
- * @private
- * @cfg {Boolean} loading True if the plugin has initiated a Store load that has not yet completed
- */
- loading: false
- },
- /**
- * @private
- * Sets up all of the references the plugin needs
- */
- init: function(list) {
- var scroller = list.getScrollable().getScroller(),
- store = list.getStore();
- this.setList(list);
- this.setScroller(scroller);
- this.bindStore(list.getStore());
- list.setScrollToTopOnRefresh(false);
- this.addLoadMoreCmp();
- // We provide our own load mask so if the Store is autoLoading already disable the List's mask straight away,
- // otherwise if the Store loads later allow the mask to show once then remove it thereafter
- if (store) {
- this.disableDataViewMask(store);
- }
- // The List's Store could change at any time so make sure we are informed when that happens
- list.updateStore = Ext.Function.createInterceptor(list.updateStore, this.bindStore, this);
- if (this.getAutoPaging()) {
- scroller.on({
- scrollend: this.onScrollEnd,
- scope: this
- });
- }
- },
- /**
- * @private
- */
- bindStore: function(newStore, oldStore) {
- if (oldStore) {
- oldStore.un({
- beforeload: this.onStoreBeforeLoad,
- load: this.onStoreLoad,
- scope: this
- });
- }
- if (newStore) {
- newStore.on({
- beforeload: this.onStoreBeforeLoad,
- load: this.onStoreLoad,
- scope: this
- });
- }
- },
- /**
- * @private
- * Removes the List/DataView's loading mask because we show our own in the plugin. The logic here disables the
- * loading mask immediately if the store is autoloading. If it's not autoloading, allow the mask to show the first
- * time the Store loads, then disable it and use the plugin's loading spinner.
- * @param {Ext.data.Store} store The store that is bound to the DataView
- */
- disableDataViewMask: function(store) {
- var list = this.getList();
- if (store.isAutoLoading()) {
- list.setLoadingText(null);
- } else {
- store.on({
- load: {
- single: true,
- fn: function() {
- list.setLoadingText(null);
- }
- }
- });
- }
- },
- /**
- * @private
- */
- applyLoadTpl: function(config) {
- return (Ext.isObject(config) && config.isTemplate) ? config : new Ext.XTemplate(config);
- },
- /**
- * @private
- */
- applyLoadMoreCmp: function(config) {
- config = Ext.merge(config, {
- html: this.getLoadTpl().apply({
- cssPrefix: Ext.baseCSSPrefix,
- message: this.getLoadMoreText()
- }),
- listeners: {
- tap: {
- fn: this.loadNextPage,
- scope: this,
- element: 'element'
- }
- }
- });
- return Ext.factory(config, Ext.Component, this.getLoadMoreCmp());
- },
- /**
- * @private
- * If we're using autoPaging and detect that the user has scrolled to the bottom, kick off loading of the next page
- */
- onScrollEnd: function(scroller, x, y) {
- if (!this.getLoading() && y >= scroller.maxPosition.y) {
- this.loadNextPage();
- }
- },
- /**
- * @private
- * Makes sure we add/remove the loading CSS class while the Store is loading
- */
- updateLoading: function(isLoading) {
- var loadMoreCmp = this.getLoadMoreCmp(),
- loadMoreCls = this.getLoadingCls();
- if (isLoading) {
- loadMoreCmp.addCls(loadMoreCls);
- } else {
- loadMoreCmp.removeCls(loadMoreCls);
- }
- },
- /**
- * @private
- * If the Store is just about to load but it's currently empty, we hide the load more button because this is
- * usually an outcome of setting a new Store on the List so we don't want the load more button to flash while
- * the new Store loads
- */
- onStoreBeforeLoad: function(store) {
- if (store.getCount() === 0) {
- this.getLoadMoreCmp().hide();
- }
- },
- /**
- * @private
- */
- onStoreLoad: function(store) {
- var loadCmp = this.getLoadMoreCmp(),
- template = this.getLoadTpl(),
- message = this.storeFullyLoaded() ? this.getNoMoreRecordsText() : this.getLoadMoreText();
- if (store.getCount()) {
- loadCmp.show();
- this.getList().scrollDockHeightRefresh();
- }
- this.setLoading(false);
- //if we've reached the end of the data set, switch to the noMoreRecordsText
- loadCmp.setHtml(template.apply({
- cssPrefix: Ext.baseCSSPrefix,
- message: message
- }));
- },
- /**
- * @private
- * Because the attached List's inner list element is rendered after our init function is called,
- * we need to dynamically add the loadMoreCmp later. This does this once and caches the result.
- */
- addLoadMoreCmp: function() {
- var list = this.getList(),
- cmp = this.getLoadMoreCmp();
- if (!this.getLoadMoreCmpAdded()) {
- list.add(cmp);
- /**
- * @event loadmorecmpadded Fired when the Load More component is added to the list. Fires on the List.
- * @param {Ext.plugin.ListPaging} this The list paging plugin
- * @param {Ext.List} list The list
- */
- list.fireEvent('loadmorecmpadded', this, list);
- this.setLoadMoreCmpAdded(true);
- }
- return cmp;
- },
- /**
- * @private
- * Returns true if the Store is detected as being fully loaded, or the server did not return a total count, which
- * means we're in 'infinite' mode
- * @return {Boolean}
- */
- storeFullyLoaded: function() {
- var store = this.getList().getStore(),
- total = store.getTotalCount();
- return total !== null ? store.getTotalCount() <= (store.currentPage * store.getPageSize()) : false;
- },
- /**
- * @private
- */
- loadNextPage: function() {
- var me = this;
- if (!me.storeFullyLoaded()) {
- me.setLoading(true);
- me.getList().getStore().nextPage({ addRecords: true });
- }
- }
- });
- /**
- * This plugin adds pull to refresh functionality to the List.
- *
- * ## Example
- *
- * @example
- * var store = Ext.create('Ext.data.Store', {
- * fields: ['name', 'img', 'text'],
- * data: [
- * {
- * name: 'rdougan',
- * img: 'http://a0.twimg.com/profile_images/1261180556/171265_10150129602722922_727937921_7778997_8387690_o_reasonably_small.jpg',
- * text: 'JavaScript development'
- * }
- * ]
- * });
- *
- * Ext.create('Ext.dataview.List', {
- * fullscreen: true,
- *
- * store: store,
- *
- * plugins: [
- * {
- * xclass: 'Ext.plugin.PullRefresh',
- * pullRefreshText: 'Pull down for more new Tweets!'
- * }
- * ],
- *
- * itemTpl: [
- * '<img src="{img}" alt="{name} photo" />',
- * '<div class="tweet"><b>{name}:</b> {text}</div>'
- * ]
- * });
- */
- Ext.define('Ext.plugin.PullRefresh', {
- extend: 'Ext.Component',
- alias: 'plugin.pullrefresh',
- requires: ['Ext.DateExtras'],
- config: {
- /**
- * @cfg {Ext.dataview.List} list
- * The list to which this PullRefresh plugin is connected.
- * This will usually by set automatically when configuring the list with this plugin.
- * @accessor
- */
- list: null,
- /**
- * @cfg {String} pullRefreshText The text that will be shown while you are pulling down.
- * @accessor
- */
- pullRefreshText: 'Pull down to refresh...',
- /**
- * @cfg {String} releaseRefreshText The text that will be shown after you have pulled down enough to show the release message.
- * @accessor
- */
- releaseRefreshText: 'Release to refresh...',
- /**
- * @cfg {String} lastUpdatedText The text to be shown in front of the last updated time.
- * @accessor
- */
- lastUpdatedText: 'Last Updated:',
- /**
- * @cfg {String} loadingText The text that will be shown while the list is refreshing.
- * @accessor
- */
- loadingText: 'Loading...',
- /**
- * @cfg {Number} snappingAnimationDuration The duration for snapping back animation after the data has been refreshed
- * @accessor
- */
- snappingAnimationDuration: 150,
- /**
- * @cfg {Function} refreshFn The function that will be called to refresh the list.
- * If this is not defined, the store's load function will be called.
- * The refresh function gets called with a reference to this plugin instance.
- * @accessor
- */
- refreshFn: null,
- /**
- * @cfg {Ext.XTemplate/String/Array} pullTpl The template being used for the pull to refresh markup.
- * @accessor
- */
- pullTpl: [
- '<div class="x-list-pullrefresh">',
- '<div class="x-list-pullrefresh-arrow"></div>',
- '<div class="x-loading-spinner">',
- '<span class="x-loading-top"></span>',
- '<span class="x-loading-right"></span>',
- '<span class="x-loading-bottom"></span>',
- '<span class="x-loading-left"></span>',
- '</div>',
- '<div class="x-list-pullrefresh-wrap">',
- '<h3 class="x-list-pullrefresh-message">{message}</h3>',
- '<div class="x-list-pullrefresh-updated">{lastUpdatedText} {lastUpdated:date("m/d/Y h:iA")}</div>',
- '</div>',
- '</div>'
- ].join(''),
- translatable: true
- },
- isRefreshing: false,
- currentViewState: '',
- initialize: function() {
- this.callParent();
- this.on({
- painted: 'onPainted',
- scope: this
- });
- },
- init: function(list) {
- var me = this;
- me.setList(list);
- me.initScrollable();
- },
- initScrollable: function() {
- var me = this,
- list = me.getList(),
- store = list.getStore(),
- pullTpl = me.getPullTpl(),
- element = me.element,
- scrollable = list.getScrollable(),
- scroller;
- if (!scrollable) {
- return;
- }
- scroller = scrollable.getScroller();
- me.lastUpdated = new Date();
- list.container.insert(0, me);
- // We provide our own load mask so if the Store is autoLoading already disable the List's mask straight away,
- // otherwise if the Store loads later allow the mask to show once then remove it thereafter
- if (store) {
- if (store.isAutoLoading()) {
- list.setLoadingText(null);
- } else {
- store.on({
- load: {
- single: true,
- fn: function() {
- list.setLoadingText(null);
- }
- }
- });
- }
- }
- pullTpl.overwrite(element, {
- message: me.getPullRefreshText(),
- lastUpdatedText: me.getLastUpdatedText(),
- lastUpdated: me.lastUpdated
- }, true);
- me.loadingElement = element.getFirstChild();
- me.messageEl = element.down('.x-list-pullrefresh-message');
- me.updatedEl = element.down('.x-list-pullrefresh-updated');
- me.maxScroller = scroller.getMaxPosition();
- scroller.on({
- maxpositionchange: me.setMaxScroller,
- scroll: me.onScrollChange,
- scope: me
- });
- },
- onScrollableChange: function() {
- this.initScrollable();
- },
- updateList: function(newList, oldList) {
- var me = this;
- if (newList && newList != oldList) {
- newList.on({
- order: 'after',
- scrollablechange: me.onScrollableChange,
- scope: me
- });
- } else if (oldList) {
- oldList.un({
- order: 'after',
- scrollablechange: me.onScrollableChange,
- scope: me
- });
- }
- },
- /**
- * @private
- * Attempts to load the newest posts via the attached List's Store's Proxy
- */
- fetchLatest: function() {
- var store = this.getList().getStore(),
- proxy = store.getProxy(),
- operation;
- operation = Ext.create('Ext.data.Operation', {
- page: 1,
- start: 0,
- model: store.getModel(),
- limit: store.getPageSize(),
- action: 'read',
- filters: store.getRemoteFilter() ? store.getFilters() : []
- });
- proxy.read(operation, this.onLatestFetched, this);
- },
- /**
- * @private
- * Called after fetchLatest has finished grabbing data. Matches any returned records against what is already in the
- * Store. If there is an overlap, updates the existing records with the new data and inserts the new items at the
- * front of the Store. If there is no overlap, insert the new records anyway and record that there's a break in the
- * timeline between the new and the old records.
- */
- onLatestFetched: function(operation) {
- var store = this.getList().getStore(),
- oldRecords = store.getData(),
- newRecords = operation.getRecords(),
- length = newRecords.length,
- toInsert = [],
- newRecord, oldRecord, i;
- for (i = 0; i < length; i++) {
- newRecord = newRecords[i];
- oldRecord = oldRecords.getByKey(newRecord.getId());
- if (oldRecord) {
- oldRecord.set(newRecord.getData());
- } else {
- toInsert.push(newRecord);
- }
- oldRecord = undefined;
- }
- store.insert(0, toInsert);
- },
- onPainted: function() {
- this.pullHeight = this.loadingElement.getHeight();
- },
- setMaxScroller: function(scroller, position) {
- this.maxScroller = position;
- },
- onScrollChange: function(scroller, x, y) {
- if (y < 0) {
- this.onBounceTop(y);
- }
- if (y > this.maxScroller.y) {
- this.onBounceBottom(y);
- }
- },
- /**
- * @private
- */
- applyPullTpl: function(config) {
- return (Ext.isObject(config) && config.isTemplate) ? config : new Ext.XTemplate(config);
- },
- onBounceTop: function(y) {
- var me = this,
- pullHeight = me.pullHeight,
- list = me.getList(),
- scroller = list.getScrollable().getScroller();
- if (!me.isReleased) {
- if (!pullHeight) {
- me.onPainted();
- pullHeight = me.pullHeight;
- }
- if (!me.isRefreshing && -y >= pullHeight + 10) {
- me.isRefreshing = true;
- me.setViewState('release');
- scroller.getContainer().onBefore({
- dragend: 'onScrollerDragEnd',
- single: true,
- scope: me
- });
- }
- else if (me.isRefreshing && -y < pullHeight + 10) {
- me.isRefreshing = false;
- me.setViewState('pull');
- }
- }
- me.getTranslatable().translate(0, -y);
- },
- onScrollerDragEnd: function() {
- var me = this;
- if (me.isRefreshing) {
- var list = me.getList(),
- scroller = list.getScrollable().getScroller();
- scroller.minPosition.y = -me.pullHeight;
- scroller.on({
- scrollend: 'loadStore',
- single: true,
- scope: me
- });
- me.isReleased = true;
- }
- },
- loadStore: function() {
- var me = this,
- list = me.getList(),
- scroller = list.getScrollable().getScroller();
- me.setViewState('loading');
- me.isReleased = false;
- Ext.defer(function() {
- scroller.on({
- scrollend: function() {
- if (me.getRefreshFn()) {
- me.getRefreshFn().call(me, me);
- } else {
- me.fetchLatest();
- }
- me.resetRefreshState();
- },
- delay: 100,
- single: true,
- scope: me
- });
- scroller.minPosition.y = 0;
- scroller.scrollTo(null, 0, true);
- }, 500, me);
- },
- onBounceBottom: Ext.emptyFn,
- setViewState: function(state) {
- var me = this,
- prefix = Ext.baseCSSPrefix,
- messageEl = me.messageEl,
- loadingElement = me.loadingElement;
- if (state === me.currentViewState) {
- return me;
- }
- me.currentViewState = state;
- if (messageEl && loadingElement) {
- switch (state) {
- case 'pull':
- messageEl.setHtml(me.getPullRefreshText());
- loadingElement.removeCls([prefix + 'list-pullrefresh-release', prefix + 'list-pullrefresh-loading']);
- break;
- case 'release':
- messageEl.setHtml(me.getReleaseRefreshText());
- loadingElement.addCls(prefix + 'list-pullrefresh-release');
- break;
- case 'loading':
- messageEl.setHtml(me.getLoadingText());
- loadingElement.addCls(prefix + 'list-pullrefresh-loading');
- break;
- }
- }
- return me;
- },
- resetRefreshState: function() {
- var me = this;
- me.isRefreshing = false;
- me.lastUpdated = new Date();
- me.setViewState('pull');
- me.updatedEl.setHtml(this.getLastUpdatedText() + ' ' + Ext.util.Format.date(me.lastUpdated, "m/d/Y h:iA"));
- }
- });
- /**
- * Used in the {@link Ext.tab.Bar} component. This shouldn't be used directly, instead use
- * {@link Ext.tab.Bar} or {@link Ext.tab.Panel}.
- * @private
- */
- Ext.define('Ext.tab.Tab', {
- extend: 'Ext.Button',
- xtype: 'tab',
- alternateClassName: 'Ext.Tab',
- // @private
- isTab: true,
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'tab',
- /**
- * @cfg {String} pressedCls
- * The CSS class to be applied to a Tab when it is pressed.
- * Providing your own CSS for this class enables you to customize the pressed state.
- * @accessor
- */
- pressedCls: Ext.baseCSSPrefix + 'tab-pressed',
- /**
- * @cfg {String} activeCls
- * The CSS class to be applied to a Tab when it is active.
- * Providing your own CSS for this class enables you to customize the active state.
- * @accessor
- */
- activeCls: Ext.baseCSSPrefix + 'tab-active',
- /**
- * @cfg {Boolean} active
- * Set this to `true` to have the tab be active by default.
- * @accessor
- */
- active: false,
- /**
- * @cfg {String} title
- * The title of the card that this tab is bound to.
- * @accessor
- */
- title: ' '
- },
- // We need to override this so the `iconElement` is properly hidden using visibility
- // when we render it.
- template: [
- {
- tag: 'span',
- reference: 'badgeElement',
- hidden: true
- },
- {
- tag: 'span',
- className: Ext.baseCSSPrefix + 'button-icon',
- reference: 'iconElement',
- style: 'visibility: hidden !important'
- },
- {
- tag: 'span',
- reference: 'textElement',
- hidden: true
- }
- ],
- updateIconCls : function(newCls, oldCls) {
- this.callParent([newCls, oldCls]);
- if (oldCls) {
- this.removeCls('x-tab-icon');
- }
- if (newCls) {
- this.addCls('x-tab-icon');
- }
- },
- /**
- * @event activate
- * Fires when a tab is activated
- * @param {Ext.tab.Tab} this
- */
- /**
- * @event deactivate
- * Fires when a tab is deactivated
- * @param {Ext.tab.Tab} this
- */
- updateTitle: function(title) {
- this.setText(title);
- },
- hideIconElement: function() {
- this.iconElement.dom.style.setProperty('visibility', 'hidden', '!important');
- },
- showIconElement: function() {
- this.iconElement.dom.style.setProperty('visibility', 'visible', '!important');
- },
- updateActive: function(active, oldActive) {
- var activeCls = this.getActiveCls();
- if (active && !oldActive) {
- this.element.addCls(activeCls);
- this.fireEvent('activate', this);
- } else if (oldActive) {
- this.element.removeCls(activeCls);
- this.fireEvent('deactivate', this);
- }
- }
- }, function() {
- this.override({
- activate: function() {
- this.setActive(true);
- },
- deactivate: function() {
- this.setActive(false);
- }
- });
- });
- /**
- * 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
- * panel. It's unusual to use it directly, instead see the {@link Ext.tab.Panel tab panel docs} for usage instructions.
- *
- * Used in the {@link Ext.tab.Panel} component to display {@link Ext.tab.Tab} components.
- *
- * @private
- */
- Ext.define('Ext.tab.Bar', {
- extend: 'Ext.Toolbar',
- alternateClassName: 'Ext.TabBar',
- xtype : 'tabbar',
- requires: ['Ext.tab.Tab'],
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'tabbar',
- // @private
- defaultType: 'tab',
- // @private
- layout: {
- type: 'hbox',
- align: 'middle'
- }
- },
- eventedConfig: {
- /**
- * @cfg {Number/String/Ext.Component} activeTab
- * The initially activated tab. Can be specified as numeric index,
- * component ID or as the component instance itself.
- * @accessor
- * @evented
- */
- activeTab: null
- },
- /**
- * @event tabchange
- * Fired when active tab changes.
- * @param {Ext.tab.Bar} this
- * @param {Ext.tab.Tab} newTab The new Tab
- * @param {Ext.tab.Tab} oldTab The old Tab
- */
- initialize: function() {
- var me = this;
- me.callParent();
- me.on({
- tap: 'onTabTap',
- delegate: '> tab',
- scope : me
- });
- },
- // @private
- onTabTap: function(tab) {
- this.setActiveTab(tab);
- },
- /**
- * @private
- */
- applyActiveTab: function(newActiveTab, oldActiveTab) {
- if (!newActiveTab && newActiveTab !== 0) {
- return;
- }
- var newTabInstance = this.parseActiveTab(newActiveTab);
- if (!newTabInstance) {
- // <debug warn>
- if (oldActiveTab) {
- Ext.Logger.warn('Trying to set a non-existent activeTab');
- }
- // </debug>
- return;
- }
- return newTabInstance;
- },
- /**
- * @private
- * Default pack to center when docked to the bottom, otherwise default pack to left
- */
- doSetDocked: function(newDocked) {
- var layout = this.getLayout(),
- initialConfig = this.getInitialConfig(),
- pack;
- if (!initialConfig.layout || !initialConfig.layout.pack) {
- pack = (newDocked == 'bottom') ? 'center' : 'left';
- //layout isn't guaranteed to be instantiated so must test
- if (layout.isLayout) {
- layout.setPack(pack);
- } else {
- layout.pack = (layout && layout.pack) ? layout.pack : pack;
- }
- }
- },
- /**
- * @private
- * Sets the active tab
- */
- doSetActiveTab: function(newTab, oldTab) {
- if (newTab) {
- newTab.setActive(true);
- }
- //Check if the parent is present, if not it is destroyed
- if (oldTab && oldTab.parent) {
- oldTab.setActive(false);
- }
- },
- /**
- * @private
- * Parses the active tab, which can be a number or string
- */
- parseActiveTab: function(tab) {
- //we need to call getItems to initialize the items, otherwise they will not exist yet.
- if (typeof tab == 'number') {
- return this.getInnerItems()[tab];
- }
- else if (typeof tab == 'string') {
- tab = Ext.getCmp(tab);
- }
- return tab;
- }
- });
- /**
- * @aside guide tabs
- * @aside video tabs-toolbars
- * @aside example tabs
- * @aside example tabs-bottom
- *
- * Tab Panels are a great way to allow the user to switch between several pages that are all full screen. Each
- * Component in the Tab Panel gets its own Tab, which shows the Component when tapped on. Tabs can be positioned at
- * the top or the bottom of the Tab Panel, and can optionally accept title and icon configurations.
- *
- * 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
- * to toggle between code mode and live preview mode (you can also edit the code and see your changes in the live
- * preview):
- *
- * @example miniphone preview
- * Ext.create('Ext.TabPanel', {
- * fullscreen: true,
- * tabBarPosition: 'bottom',
- *
- * defaults: {
- * styleHtmlContent: true
- * },
- *
- * items: [
- * {
- * title: 'Home',
- * iconCls: 'home',
- * html: 'Home Screen'
- * },
- * {
- * title: 'Contact',
- * iconCls: 'user',
- * html: 'Contact Screen'
- * }
- * ]
- * });
- * One tab was created for each of the {@link Ext.Panel panels} defined in the items array. Each tab automatically uses
- * the title and icon defined on the item configuration, and switches to that item when tapped on. We can also position
- * the tab bar at the top, which makes our Tab Panel look like this:
- *
- * @example miniphone preview
- * Ext.create('Ext.TabPanel', {
- * fullscreen: true,
- *
- * defaults: {
- * styleHtmlContent: true
- * },
- *
- * items: [
- * {
- * title: 'Home',
- * html: 'Home Screen'
- * },
- * {
- * title: 'Contact',
- * html: 'Contact Screen'
- * }
- * ]
- * });
- *
- */
- Ext.define('Ext.tab.Panel', {
- extend: 'Ext.Container',
- xtype : 'tabpanel',
- alternateClassName: 'Ext.TabPanel',
- requires: ['Ext.tab.Bar'],
- config: {
- /**
- * @cfg {String} ui
- * Sets the UI of this component.
- * Available values are: `light` and `dark`.
- * @accessor
- */
- ui: 'dark',
- /**
- * @cfg {Object} tabBar
- * An Ext.tab.Bar configuration.
- * @accessor
- */
- tabBar: true,
- /**
- * @cfg {String} tabBarPosition
- * The docked position for the {@link #tabBar} instance.
- * Possible values are 'top' and 'bottom'.
- * @accessor
- */
- tabBarPosition: 'top',
- /**
- * @cfg layout
- * @inheritdoc
- */
- layout: {
- type: 'card',
- animation: {
- type: 'slide',
- direction: 'left'
- }
- },
- /**
- * @cfg cls
- * @inheritdoc
- */
- cls: Ext.baseCSSPrefix + 'tabpanel'
- /**
- * @cfg {Boolean/String/Object} scrollable
- * @accessor
- * @hide
- */
- /**
- * @cfg {Boolean/String/Object} scroll
- * @hide
- */
- },
- delegateListeners: {
- delegate: '> component',
- centeredchange: 'onItemCenteredChange',
- dockedchange: 'onItemDockedChange',
- floatingchange: 'onItemFloatingChange',
- disabledchange: 'onItemDisabledChange'
- },
- initialize: function() {
- this.callParent();
- this.on({
- order: 'before',
- activetabchange: 'doTabChange',
- delegate: '> tabbar',
- scope : this
- });
- },
- /**
- * Tab panels should not be scrollable. Instead, you should add scrollable to any item that
- * you want to scroll.
- * @private
- */
- applyScrollable: function() {
- return false;
- },
- /**
- * Updates the Ui for this component and the {@link #tabBar}.
- */
- updateUi: function(newUi, oldUi) {
- this.callParent(arguments);
- if (this.initialized) {
- this.getTabBar().setUi(newUi);
- }
- },
- /**
- * @private
- */
- doSetActiveItem: function(newActiveItem, oldActiveItem) {
- if (newActiveItem) {
- var items = this.getInnerItems(),
- oldIndex = items.indexOf(oldActiveItem),
- newIndex = items.indexOf(newActiveItem),
- reverse = oldIndex > newIndex,
- animation = this.getLayout().getAnimation(),
- tabBar = this.getTabBar(),
- oldTab = tabBar.parseActiveTab(oldIndex),
- newTab = tabBar.parseActiveTab(newIndex);
- if (animation && animation.setReverse) {
- animation.setReverse(reverse);
- }
- this.callParent(arguments);
- if (newIndex != -1) {
- this.forcedChange = true;
- tabBar.setActiveTab(newIndex);
- this.forcedChange = false;
- if (oldTab) {
- oldTab.setActive(false);
- }
- if (newTab) {
- newTab.setActive(true);
- }
- }
- }
- },
- /**
- * Updates this container with the new active item.
- * @param {Object} tabBar
- * @param {Object} newTab
- * @return {Boolean}
- */
- doTabChange: function(tabBar, newTab) {
- var oldActiveItem = this.getActiveItem(),
- newActiveItem;
- this.setActiveItem(tabBar.indexOf(newTab));
- newActiveItem = this.getActiveItem();
- return this.forcedChange || oldActiveItem !== newActiveItem;
- },
- /**
- * Creates a new {@link Ext.tab.Bar} instance using {@link Ext#factory}.
- * @param {Object} config
- * @return {Object}
- * @private
- */
- applyTabBar: function(config) {
- if (config === true) {
- config = {};
- }
- if (config) {
- Ext.applyIf(config, {
- ui: this.getUi(),
- docked: this.getTabBarPosition()
- });
- }
- return Ext.factory(config, Ext.tab.Bar, this.getTabBar());
- },
- /**
- * Adds the new {@link Ext.tab.Bar} instance into this container.
- * @private
- */
- updateTabBar: function(newTabBar) {
- if (newTabBar) {
- this.add(newTabBar);
- this.setTabBarPosition(newTabBar.getDocked());
- }
- },
- /**
- * Updates the docked position of the {@link #tabBar}.
- * @private
- */
- updateTabBarPosition: function(position) {
- var tabBar = this.getTabBar();
- if (tabBar) {
- tabBar.setDocked(position);
- }
- },
- onItemAdd: function(card) {
- var me = this;
- if (!card.isInnerItem()) {
- return me.callParent(arguments);
- }
- var tabBar = me.getTabBar(),
- initialConfig = card.getInitialConfig(),
- tabConfig = initialConfig.tab || {},
- tabTitle = (card.getTitle) ? card.getTitle() : initialConfig.title,
- tabIconCls = (card.getIconCls) ? card.getIconCls() : initialConfig.iconCls,
- tabHidden = (card.getHidden) ? card.getHidden() : initialConfig.hidden,
- tabDisabled = (card.getDisabled) ? card.getDisabled() : initialConfig.disabled,
- tabBadgeText = (card.getBadgeText) ? card.getBadgeText() : initialConfig.badgeText,
- innerItems = me.getInnerItems(),
- index = innerItems.indexOf(card),
- tabs = tabBar.getItems(),
- activeTab = tabBar.getActiveTab(),
- currentTabInstance = (tabs.length >= innerItems.length) && tabs.getAt(index),
- tabInstance;
- if (tabTitle && !tabConfig.title) {
- tabConfig.title = tabTitle;
- }
- if (tabIconCls && !tabConfig.iconCls) {
- tabConfig.iconCls = tabIconCls;
- }
- if (tabHidden && !tabConfig.hidden) {
- tabConfig.hidden = tabHidden;
- }
- if (tabDisabled && !tabConfig.disabled) {
- tabConfig.disabled = tabDisabled;
- }
- if (tabBadgeText && !tabConfig.badgeText) {
- tabConfig.badgeText = tabBadgeText;
- }
- //<debug warn>
- if (!currentTabInstance && !tabConfig.title && !tabConfig.iconCls) {
- if (!tabConfig.title && !tabConfig.iconCls) {
- Ext.Logger.error('Adding a card to a tab container without specifying any tab configuration');
- }
- }
- //</debug>
- tabInstance = Ext.factory(tabConfig, Ext.tab.Tab, currentTabInstance);
- if (!currentTabInstance) {
- tabBar.insert(index, tabInstance);
- }
- card.tab = tabInstance;
- me.callParent(arguments);
- if (!activeTab && activeTab !== 0) {
- tabBar.setActiveTab(tabBar.getActiveItem());
- }
- },
- /**
- * If an item gets enabled/disabled and it has an tab, we should also enable/disable that tab
- * @private
- */
- onItemDisabledChange: function(item, newDisabled) {
- if (item && item.tab) {
- item.tab.setDisabled(newDisabled);
- }
- },
- // @private
- onItemRemove: function(item, index) {
- this.getTabBar().remove(item.tab, this.getAutoDestroy());
- this.callParent(arguments);
- }
- }, function() {
- });
- Ext.define('Ext.table.Cell', {
- extend: 'Ext.Container',
- xtype: 'tablecell',
- config: {
- baseCls: 'x-table-cell'
- },
- getElementConfig: function() {
- var config = this.callParent();
- config.children.length = 0;
- return config;
- }
- });
- Ext.define('Ext.table.Row', {
- extend: 'Ext.table.Cell',
- xtype: 'tablerow',
- config: {
- baseCls: 'x-table-row',
- defaultType: 'tablecell'
- }
- });
- Ext.define('Ext.table.Table', {
- extend: 'Ext.Container',
- requires: ['Ext.table.Row'],
- xtype: 'table',
- config: {
- baseCls: 'x-table',
- defaultType: 'tablerow'
- },
- cachedConfig: {
- fixedLayout: false
- },
- fixedLayoutCls: 'x-table-fixed',
- updateFixedLayout: function(fixedLayout) {
- this.innerElement[fixedLayout ? 'addCls' : 'removeCls'](this.fixedLayoutCls);
- }
- });
- /**
- *
- */
- Ext.define('Ext.util.Droppable', {
- mixins: {
- observable: 'Ext.mixin.Observable'
- },
- config: {
- /**
- * @cfg
- * @inheritdoc
- */
- baseCls: Ext.baseCSSPrefix + 'droppable'
- },
- /**
- * @cfg {String} activeCls
- * The CSS added to a Droppable when a Draggable in the same group is being
- * dragged.
- */
- activeCls: Ext.baseCSSPrefix + 'drop-active',
- /**
- * @cfg {String} invalidCls
- * The CSS class to add to the droppable when dragging a draggable that is
- * not in the same group.
- */
- invalidCls: Ext.baseCSSPrefix + 'drop-invalid',
- /**
- * @cfg {String} hoverCls
- * The CSS class to add to the droppable when hovering over a valid drop.
- */
- hoverCls: Ext.baseCSSPrefix + 'drop-hover',
- /**
- * @cfg {String} validDropMode
- * Determines when a drop is considered 'valid' whether it simply need to
- * intersect the region or if it needs to be contained within the region.
- * Valid values are: 'intersects' or 'contains'
- */
- validDropMode: 'intersect',
- /**
- * @cfg {Boolean} disabled
- */
- disabled: false,
- /**
- * @cfg {String} group
- * Draggable and Droppable objects can participate in a group which are
- * capable of interacting.
- */
- group: 'base',
- // not yet implemented
- tolerance: null,
- // @private
- monitoring: false,
- /**
- * Creates new Droppable.
- * @param {Mixed} el String, HtmlElement or Ext.Element representing an
- * element on the page.
- * @param {Object} config Configuration options for this class.
- */
- constructor: function(el, config) {
- var me = this;
- config = config || {};
- Ext.apply(me, config);
- /**
- * @event dropactivate
- * @param {Ext.util.Droppable} this
- * @param {Ext.util.Draggable} draggable
- * @param {Ext.event.Event} e
- */
- /**
- * @event dropdeactivate
- * @param {Ext.util.Droppable} this
- * @param {Ext.util.Draggable} draggable
- * @param {Ext.event.Event} e
- */
- /**
- * @event dropenter
- * @param {Ext.util.Droppable} this
- * @param {Ext.util.Draggable} draggable
- * @param {Ext.event.Event} e
- */
- /**
- * @event dropleave
- * @param {Ext.util.Droppable} this
- * @param {Ext.util.Draggable} draggable
- * @param {Ext.event.Event} e
- */
- /**
- * @event drop
- * @param {Ext.util.Droppable} this
- * @param {Ext.util.Draggable} draggable
- * @param {Ext.event.Event} e
- */
- me.el = Ext.get(el);
- me.callParent();
- me.mixins.observable.constructor.call(me);
- if (!me.disabled) {
- me.enable();
- }
- me.el.addCls(me.baseCls);
- },
- // @private
- onDragStart: function(draggable, e) {
- if (draggable.group === this.group) {
- this.monitoring = true;
- this.el.addCls(this.activeCls);
- this.region = this.el.getPageBox(true);
- draggable.on({
- drag: this.onDrag,
- beforedragend: this.onBeforeDragEnd,
- dragend: this.onDragEnd,
- scope: this
- });
- if (this.isDragOver(draggable)) {
- this.setCanDrop(true, draggable, e);
- }
- this.fireEvent('dropactivate', this, draggable, e);
- }
- else {
- draggable.on({
- dragend: function() {
- this.el.removeCls(this.invalidCls);
- },
- scope: this,
- single: true
- });
- this.el.addCls(this.invalidCls);
- }
- },
- // @private
- isDragOver: function(draggable, region) {
- return this.region[this.validDropMode](draggable.region);
- },
- // @private
- onDrag: function(draggable, e) {
- this.setCanDrop(this.isDragOver(draggable), draggable, e);
- },
- // @private
- setCanDrop: function(canDrop, draggable, e) {
- if (canDrop && !this.canDrop) {
- this.canDrop = true;
- this.el.addCls(this.hoverCls);
- this.fireEvent('dropenter', this, draggable, e);
- }
- else if (!canDrop && this.canDrop) {
- this.canDrop = false;
- this.el.removeCls(this.hoverCls);
- this.fireEvent('dropleave', this, draggable, e);
- }
- },
- // @private
- onBeforeDragEnd: function(draggable, e) {
- draggable.cancelRevert = this.canDrop;
- },
- // @private
- onDragEnd: function(draggable, e) {
- this.monitoring = false;
- this.el.removeCls(this.activeCls);
- draggable.un({
- drag: this.onDrag,
- beforedragend: this.onBeforeDragEnd,
- dragend: this.onDragEnd,
- scope: this
- });
- if (this.canDrop) {
- this.canDrop = false;
- this.el.removeCls(this.hoverCls);
- this.fireEvent('drop', this, draggable, e);
- }
- this.fireEvent('dropdeactivate', this, draggable, e);
- },
- /**
- * Enable the Droppable target.
- * This is invoked immediately after constructing a Droppable if the
- * disabled parameter is NOT set to true.
- */
- enable: function() {
- if (!this.mgr) {
- this.mgr = Ext.util.Observable.observe(Ext.util.Draggable);
- }
- this.mgr.on({
- dragstart: this.onDragStart,
- scope: this
- });
- this.disabled = false;
- },
- /**
- * Disable the Droppable target.
- */
- disable: function() {
- this.mgr.un({
- dragstart: this.onDragStart,
- scope: this
- });
- this.disabled = true;
- },
- /**
- * Method to determine whether this Component is currently disabled.
- * @return {Boolean} the disabled state of this Component.
- */
- isDisabled: function() {
- return this.disabled;
- },
- /**
- * Method to determine whether this Droppable is currently monitoring drag operations of Draggables.
- * @return {Boolean} the monitoring state of this Droppable
- */
- isMonitoring: function() {
- return this.monitoring;
- }
- });
- /*
- http://www.JSON.org/json2.js
- 2010-03-20
- Public Domain.
- NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
- See http://www.JSON.org/js.html
- This code should be minified before deployment.
- See http://javascript.crockford.com/jsmin.html
- USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
- NOT CONTROL.
- This file creates a global JSON object containing two methods: stringify
- and parse.
- JSON.stringify(value, replacer, space)
- value any JavaScript value, usually an object or array.
- replacer an optional parameter that determines how object
- values are stringified for objects. It can be a
- function or an array of strings.
- space an optional parameter that specifies the indentation
- of nested structures. If it is omitted, the text will
- be packed without extra whitespace. If it is a number,
- it will specify the number of spaces to indent at each
- level. If it is a string (such as '\t' or ' '),
- it contains the characters used to indent at each level.
- This method produces a JSON text from a JavaScript value.
- When an object value is found, if the object contains a toJSON
- method, its toJSON method will be called and the result will be
- stringified. A toJSON method does not serialize: it returns the
- value represented by the name/value pair that should be serialized,
- or undefined if nothing should be serialized. The toJSON method
- will be passed the key associated with the value, and this will be
- bound to the value
- For example, this would serialize Dates as ISO strings.
- Date.prototype.toJSON = function (key) {
- function f(n) {
- // Format integers to have at least two digits.
- return n < 10 ? '0' + n : n;
- }
- return this.getUTCFullYear() + '-' +
- f(this.getUTCMonth() + 1) + '-' +
- f(this.getUTCDate()) + 'T' +
- f(this.getUTCHours()) + ':' +
- f(this.getUTCMinutes()) + ':' +
- f(this.getUTCSeconds()) + 'Z';
- };
- You can provide an optional replacer method. It will be passed the
- key and value of each member, with this bound to the containing
- object. The value that is returned from your method will be
- serialized. If your method returns undefined, then the member will
- be excluded from the serialization.
- If the replacer parameter is an array of strings, then it will be
- used to select the members to be serialized. It filters the results
- such that only members with keys listed in the replacer array are
- stringified.
- Values that do not have JSON representations, such as undefined or
- functions, will not be serialized. Such values in objects will be
- dropped; in arrays they will be replaced with null. You can use
- a replacer function to replace those with JSON values.
- JSON.stringify(undefined) returns undefined.
- The optional space parameter produces a stringification of the
- value that is filled with line breaks and indentation to make it
- easier to read.
- If the space parameter is a non-empty string, then that string will
- be used for indentation. If the space parameter is a number, then
- the indentation will be that many spaces.
- Example:
- text = JSON.stringify(['e', {pluribus: 'unum'}]);
- // text is '["e",{"pluribus":"unum"}]'
- text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
- // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
- text = JSON.stringify([new Date()], function (key, value) {
- return this[key] instanceof Date ?
- 'Date(' + this[key] + ')' : value;
- });
- // text is '["Date(---current time---)"]'
- JSON.parse(text, reviver)
- This method parses a JSON text to produce an object or array.
- It can throw a SyntaxError exception.
- The optional reviver parameter is a function that can filter and
- transform the results. It receives each of the keys and values,
- and its return value is used instead of the original value.
- If it returns what it received, then the structure is not modified.
- If it returns undefined then the member is deleted.
- Example:
- // Parse the text. Values that look like ISO date strings will
- // be converted to Date objects.
- myData = JSON.parse(text, function (key, value) {
- var a;
- if (typeof value === 'string') {
- a =
- /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
- if (a) {
- return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
- +a[5], +a[6]));
- }
- }
- return value;
- });
- myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
- var d;
- if (typeof value === 'string' &&
- value.slice(0, 5) === 'Date(' &&
- value.slice(-1) === ')') {
- d = new Date(value.slice(5, -1));
- if (d) {
- return d;
- }
- }
- return value;
- });
- This is a reference implementation. You are free to copy, modify, or
- redistribute.
- */
- /*jslint evil: true, strict: false */
- /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
- call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
- getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
- lastIndex, length, parse, prototype, push, replace, slice, stringify,
- test, toJSON, toString, valueOf
- */
- // Create a JSON object only if one does not already exist. We create the
- // methods in a closure to avoid creating global variables.
- if (!this.JSON) {
- this.JSON = {};
- }
- (function () {
- function f(n) {
- // Format integers to have at least two digits.
- return n < 10 ? '0' + n : n;
- }
- if (typeof Date.prototype.toJSON !== 'function') {
- Date.prototype.toJSON = function (key) {
- return isFinite(this.valueOf()) ?
- this.getUTCFullYear() + '-' +
- f(this.getUTCMonth() + 1) + '-' +
- f(this.getUTCDate()) + 'T' +
- f(this.getUTCHours()) + ':' +
- f(this.getUTCMinutes()) + ':' +
- f(this.getUTCSeconds()) + 'Z' : null;
- };
- String.prototype.toJSON =
- Number.prototype.toJSON =
- Boolean.prototype.toJSON = function (key) {
- return this.valueOf();
- };
- }
- var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
- escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
- gap,
- indent,
- meta = { // table of character substitutions
- '\b': '\\b',
- '\t': '\\t',
- '\n': '\\n',
- '\f': '\\f',
- '\r': '\\r',
- '"' : '\\"',
- '\\': '\\\\'
- },
- rep;
- function quote(string) {
- // If the string contains no control characters, no quote characters, and no
- // backslash characters, then we can safely slap some quotes around it.
- // Otherwise we must also replace the offending characters with safe escape
- // sequences.
- escapable.lastIndex = 0;
- return escapable.test(string) ?
- '"' + string.replace(escapable, function (a) {
- var c = meta[a];
- return typeof c === 'string' ? c :
- '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
- }) + '"' :
- '"' + string + '"';
- }
- function str(key, holder) {
- // Produce a string from holder[key].
- var i, // The loop counter.
- k, // The member key.
- v, // The member value.
- length,
- mind = gap,
- partial,
- value = holder[key];
- // If the value has a toJSON method, call it to obtain a replacement value.
- if (value && typeof value === 'object' &&
- typeof value.toJSON === 'function') {
- value = value.toJSON(key);
- }
- // If we were called with a replacer function, then call the replacer to
- // obtain a replacement value.
- if (typeof rep === 'function') {
- value = rep.call(holder, key, value);
- }
- // What happens next depends on the value's type.
- switch (typeof value) {
- case 'string':
- return quote(value);
- case 'number':
- // JSON numbers must be finite. Encode non-finite numbers as null.
- return isFinite(value) ? String(value) : 'null';
- case 'boolean':
- case 'null':
- // If the value is a boolean or null, convert it to a string. Note:
- // typeof null does not produce 'null'. The case is included here in
- // the remote chance that this gets fixed someday.
- return String(value);
- // If the type is 'object', we might be dealing with an object or an array or
- // null.
- case 'object':
- // Due to a specification blunder in ECMAScript, typeof null is 'object',
- // so watch out for that case.
- if (!value) {
- return 'null';
- }
- // Make an array to hold the partial results of stringifying this object value.
- gap += indent;
- partial = [];
- // Is the value an array?
- if (Object.prototype.toString.apply(value) === '[object Array]') {
- // The value is an array. Stringify every element. Use null as a placeholder
- // for non-JSON values.
- length = value.length;
- for (i = 0; i < length; i += 1) {
- partial[i] = str(i, value) || 'null';
- }
- // Join all of the elements together, separated with commas, and wrap them in
- // brackets.
- v = partial.length === 0 ? '[]' :
- gap ? '[\n' + gap +
- partial.join(',\n' + gap) + '\n' +
- mind + ']' :
- '[' + partial.join(',') + ']';
- gap = mind;
- return v;
- }
- // If the replacer is an array, use it to select the members to be stringified.
- if (rep && typeof rep === 'object') {
- length = rep.length;
- for (i = 0; i < length; i += 1) {
- k = rep[i];
- if (typeof k === 'string') {
- v = str(k, value);
- if (v) {
- partial.push(quote(k) + (gap ? ': ' : ':') + v);
- }
- }
- }
- } else {
- // Otherwise, iterate through all of the keys in the object.
- for (k in value) {
- if (Object.hasOwnProperty.call(value, k)) {
- v = str(k, value);
- if (v) {
- partial.push(quote(k) + (gap ? ': ' : ':') + v);
- }
- }
- }
- }
- // Join all of the member texts together, separated with commas,
- // and wrap them in braces.
- v = partial.length === 0 ? '{}' :
- gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
- mind + '}' : '{' + partial.join(',') + '}';
- gap = mind;
- return v;
- }
- return v;
- }
- // If the JSON object does not yet have a stringify method, give it one.
- if (typeof JSON.stringify !== 'function') {
- JSON.stringify = function (value, replacer, space) {
- // The stringify method takes a value and an optional replacer, and an optional
- // space parameter, and returns a JSON text. The replacer can be a function
- // that can replace values, or an array of strings that will select the keys.
- // A default replacer method can be provided. Use of the space parameter can
- // produce text that is more easily readable.
- var i;
- gap = '';
- indent = '';
- // If the space parameter is a number, make an indent string containing that
- // many spaces.
- if (typeof space === 'number') {
- for (i = 0; i < space; i += 1) {
- indent += ' ';
- }
- // If the space parameter is a string, it will be used as the indent string.
- } else if (typeof space === 'string') {
- indent = space;
- }
- // If there is a replacer, it must be a function or an array.
- // Otherwise, throw an error.
- rep = replacer;
- if (replacer && typeof replacer !== 'function' &&
- (typeof replacer !== 'object' ||
- typeof replacer.length !== 'number')) {
- throw new Error('JSON.stringify');
- }
- // Make a fake root object containing our value under the key of ''.
- // Return the result of stringifying the value.
- return str('', {'': value});
- };
- }
- // If the JSON object does not yet have a parse method, give it one.
- if (typeof JSON.parse !== 'function') {
- JSON.parse = function (text, reviver) {
- // The parse method takes a text and an optional reviver function, and returns
- // a JavaScript value if the text is a valid JSON text.
- var j;
- function walk(holder, key) {
- // The walk method is used to recursively walk the resulting structure so
- // that modifications can be made.
- var k, v, value = holder[key];
- if (value && typeof value === 'object') {
- for (k in value) {
- if (Object.hasOwnProperty.call(value, k)) {
- v = walk(value, k);
- if (v !== undefined) {
- value[k] = v;
- } else {
- delete value[k];
- }
- }
- }
- }
- return reviver.call(holder, key, value);
- }
- // Parsing happens in four stages. In the first stage, we replace certain
- // Unicode characters with escape sequences. JavaScript handles many characters
- // incorrectly, either silently deleting them, or treating them as line endings.
- text = String(text);
- cx.lastIndex = 0;
- if (cx.test(text)) {
- text = text.replace(cx, function (a) {
- return '\\u' +
- ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
- });
- }
- // In the second stage, we run the text against regular expressions that look
- // for non-JSON patterns. We are especially concerned with '()' and 'new'
- // because they can cause invocation, and '=' because it can cause mutation.
- // But just to be safe, we want to reject all unexpected forms.
- // We split the second stage into 4 regexp operations in order to work around
- // crippling inefficiencies in IE's and Safari's regexp engines. First we
- // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
- // replace all simple value tokens with ']' characters. Third, we delete all
- // open brackets that follow a colon or comma or that begin the text. Finally,
- // we look to see that the remaining characters are only whitespace or ']' or
- // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
- if (/^[\],:{}\s]*$/.
- test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
- replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
- replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
- // In the third stage we use the eval function to compile the text into a
- // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
- // in JavaScript: it can begin a block or an object literal. We wrap the text
- // in parens to eliminate the ambiguity.
- j = eval('(' + text + ')');
- // In the optional fourth stage, we recursively walk the new structure, passing
- // each name/value pair to a reviver function for possible transformation.
- return typeof reviver === 'function' ?
- walk({'': j}, '') : j;
- }
- // If the text is not JSON parseable, then a SyntaxError is thrown.
- throw new SyntaxError('JSON.parse');
- };
- }
- }());
- /**
- * @class Ext.util.JSON
- * Modified version of Douglas Crockford"s json.js that doesn"t
- * mess with the Object prototype
- * http://www.json.org/js.html
- * @singleton
- * @ignore
- */
- Ext.util.JSON = {
- encode: function(o) {
- return JSON.stringify(o);
- },
- decode: function(s) {
- return JSON.parse(s);
- }
- };
- /**
- * Shorthand for {@link Ext.util.JSON#encode}
- * @param {Mixed} o The variable to encode
- * @return {String} The JSON string
- * @member Ext
- * @method encode
- * @ignore
- */
- Ext.encode = Ext.util.JSON.encode;
- /**
- * Shorthand for {@link Ext.util.JSON#decode}
- * @param {String} json The JSON string
- * @param {Boolean} safe (optional) Whether to return null or throw an exception if the JSON is invalid.
- * @return {Object} The resulting object
- * @member Ext
- * @method decode
- * @ignore
- */
- Ext.decode = Ext.util.JSON.decode;
- /**
- * @class Ext.util.translatable.CssPosition
- * @private
- */
- Ext.define('Ext.util.translatable.CssPosition', {
- extend: 'Ext.util.translatable.Dom',
- doTranslate: function(x, y) {
- var domStyle = this.getElement().dom.style;
- if (typeof x == 'number') {
- domStyle.left = x + 'px';
- }
- if (typeof y == 'number') {
- domStyle.top = y + 'px';
- }
- },
- destroy: function() {
- var domStyle = this.getElement().dom.style;
- domStyle.left = null;
- domStyle.top = null;
- this.callParent(arguments);
- }
- });
- Ext.define('Ext.ux.Faker', {
- config: {
- names: ['Ed Spencer', 'Tommy Maintz', 'Rob Dougan', 'Jamie Avins', 'Jacky Nguyen'],
- emails: ['ed@sencha.com', 'tommy@sencha.com', 'rob@sencha.com', 'jamie@sencha.com', 'jacky@sencha.com'],
- lorem: [
- "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus eget neque nec sem semper cursus. Fusce ",
- "molestie nibh nec ligula gravida et porta enim luctus. Curabitur id accumsan dolor. Vestibulum ultricies ",
- "vehicula erat vel elementum. Mauris urna odio, dignissim sit amet molestie sit amet, sodales vel metus. Ut eu ",
- "volutpat nulla. Morbi ut est sed eros egestas gravida quis eget eros. Proin sit amet massa nunc. Proin congue ",
- "mollis mollis. Morbi sollicitudin nisl at diam placerat eu dignissim magna rutrum.\n",
- "Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Phasellus eu ",
- "vestibulum lectus. Fusce a eros metus. Vivamus vel aliquet neque. Ut eu purus ipsum. Nullam id leo hendrerit ",
- "augue imperdiet malesuada ac eget velit. Quisque congue turpis eget ante mollis ut sollicitudin massa dapibus. ",
- "Sed magna dolor, dictum sit amet aliquam eu, ultricies sit amet diam. Fusce tempor porta tellus vitae ",
- "pulvinar. Aenean velit ligula, fermentum non imperdiet et, suscipit sed libero. Aliquam ac ligula ut dui ",
- "pharetra dictum vel vel nunc. Phasellus semper, ligula id tristique ullamcorper, tortor diam mollis erat, sed ",
- "feugiat nisl nisi sit amet sem. Maecenas nec mi vitae ligula malesuada pellentesque.\n",
- "Quisque diam velit, suscipit sit amet ornare eu, congue sed quam. Integer rhoncus luctus mi, sed pulvinar ",
- "lectus lobortis non. Sed egestas orci nec elit sagittis eu condimentum massa volutpat. Fusce blandit congue ",
- "enim venenatis lacinia. Donec enim sapien, sollicitudin at placerat non, vehicula ut nisi. Aliquam volutpat ",
- "metus sit amet lacus condimentum fermentum. Aliquam congue scelerisque leo ut tristique."
- ].join(""),
-
- subjects: [
- "Order more widgets",
- "You're crazy",
- "Jacky is not his real name",
- "Why am I here?",
- "This is totally broken",
- "When do we ship?",
- "Top Secret",
- "There's always money in the banana stand"
- ]
- },
-
- oneOf: function(set) {
- return set[Math.floor(Math.random() * set.length)];
- },
-
- name: function() {
- return this.oneOf(this.getNames());
- },
-
- email: function() {
- return this.oneOf(this.getSubjects());
- },
-
- subject: function() {
- return this.oneOf(this.getSubjects());
- },
-
- lorem: function(paragraphs) {
- var lorem = this.getLorem();
-
- if (paragraphs) {
- return lorem.split("\n").slice(0, paragraphs).join("\n");
- } else {
- return lorem;
- }
- }
- });
- Ext.define('Ext.ux.auth.Session', {
-
- constructor: function(credentials) {
- credentials = {
- username: 'ed',
- password: 'secret'
- }
- },
-
- validate: function(options) {
- options = {
- success: function(session) {
-
- },
- failure: function(session) {
-
- },
- callback: function(session) {
-
- },
- scope: me
- }
- },
-
- destroy: function() {
-
- }
- });
- Ext.define('Ext.ux.auth.model.Session', {
- fields: ['username', 'created_at', 'expires_at'],
-
- validate: function() {
-
- },
-
- destroy: function() {
-
- }
- });
- /**
- * @private
- * Base class for iOS and Android viewports.
- */
- Ext.define('Ext.viewport.Default', {
- extend: 'Ext.Container',
- xtype: 'viewport',
- PORTRAIT: 'portrait',
- LANDSCAPE: 'landscape',
- requires: [
- 'Ext.LoadMask',
- 'Ext.layout.Card'
- ],
- /**
- * @event ready
- * Fires when the Viewport is in the DOM and ready.
- * @param {Ext.Viewport} this
- */
- /**
- * @event maximize
- * Fires when the Viewport is maximized.
- * @param {Ext.Viewport} this
- */
- /**
- * @event orientationchange
- * Fires when the Viewport orientation has changed.
- * @param {Ext.Viewport} this
- * @param {String} newOrientation The new orientation.
- * @param {Number} width The width of the Viewport.
- * @param {Number} height The height of the Viewport.
- */
- config: {
- /**
- * @cfg {Boolean} autoMaximize
- * Whether or not to always automatically maximize the viewport on first load and all subsequent orientation changes.
- *
- * This is set to `false` by default for a number of reasons:
- *
- * - Orientation change performance is drastically reduced when this is enabled, on all devices.
- * - On some devices (mostly Android) this can sometimes cause issues when the default browser zoom setting is changed.
- * - When wrapping your phone in a native shell, you may get a blank screen.
- * - When bookmarked to the homescreen (iOS), you may get a blank screen.
- *
- * @accessor
- */
- autoMaximize: false,
- /**
- * @private
- */
- autoBlurInput: true,
- /**
- * @cfg {Boolean} preventPanning
- * Whether or not to always prevent default panning behavior of the
- * browser's viewport.
- * @accessor
- */
- preventPanning: true,
- /**
- * @cfg {Boolean} preventZooming
- * `true` to attempt to stop zooming when you double tap on the screen on mobile devices,
- * typically HTC devices with HTC Sense UI.
- * @accessor
- */
- preventZooming: false,
- /**
- * @cfg
- * @private
- */
- autoRender: true,
- /**
- * @cfg {Object/String} layout Configuration for this Container's layout. Example:
- *
- * Ext.create('Ext.Container', {
- * layout: {
- * type: 'hbox',
- * align: 'middle'
- * },
- * items: [
- * {
- * xtype: 'panel',
- * flex: 1,
- * style: 'background-color: red;'
- * },
- * {
- * xtype: 'panel',
- * flex: 2,
- * style: 'background-color: green'
- * }
- * ]
- * });
- *
- * See the [layouts guide](#!/guides/layouts) for more information.
- *
- * @accessor
- */
- layout: 'card',
- /**
- * @cfg
- * @private
- */
- width: '100%',
- /**
- * @cfg
- * @private
- */
- height: '100%',
- useBodyElement: true
- },
- /**
- * @property {Boolean} isReady
- * `true` if the DOM is ready.
- */
- isReady: false,
- isViewport: true,
- isMaximizing: false,
- id: 'ext-viewport',
- isInputRegex: /^(input|textarea|select|a)$/i,
- focusedElement: null,
- /**
- * @private
- */
- fullscreenItemCls: Ext.baseCSSPrefix + 'fullscreen',
- constructor: function(config) {
- var bind = Ext.Function.bind;
- this.doPreventPanning = bind(this.doPreventPanning, this);
- this.doPreventZooming = bind(this.doPreventZooming, this);
- this.doBlurInput = bind(this.doBlurInput, this);
- this.maximizeOnEvents = ['ready', 'orientationchange'];
- this.orientation = this.determineOrientation();
- this.windowWidth = this.getWindowWidth();
- this.windowHeight = this.getWindowHeight();
- this.windowOuterHeight = this.getWindowOuterHeight();
- if (!this.stretchHeights) {
- this.stretchHeights = {};
- }
- this.callParent([config]);
- // Android is handled separately
- if (!Ext.os.is.Android || Ext.browser.name == 'ChromeMobile') {
- if (this.supportsOrientation()) {
- this.addWindowListener('orientationchange', bind(this.onOrientationChange, this));
- }
- else {
- this.addWindowListener('resize', bind(this.onResize, this));
- }
- }
- document.addEventListener('focus', bind(this.onElementFocus, this), true);
- document.addEventListener('blur', bind(this.onElementBlur, this), true);
- Ext.onDocumentReady(this.onDomReady, this);
- this.on('ready', this.onReady, this, {single: true});
- this.getEventDispatcher().addListener('component', '*', 'fullscreen', 'onItemFullscreenChange', this);
- return this;
- },
- onDomReady: function() {
- this.isReady = true;
- this.updateSize();
- this.fireEvent('ready', this);
- },
- onReady: function() {
- if (this.getAutoRender()) {
- this.render();
- }
- },
- onElementFocus: function(e) {
- this.focusedElement = e.target;
- },
- onElementBlur: function() {
- this.focusedElement = null;
- },
- render: function() {
- if (!this.rendered) {
- var body = Ext.getBody(),
- clsPrefix = Ext.baseCSSPrefix,
- classList = [],
- osEnv = Ext.os,
- osName = osEnv.name.toLowerCase(),
- browserName = Ext.browser.name.toLowerCase(),
- osMajorVersion = osEnv.version.getMajor(),
- orientation = this.getOrientation();
- this.renderTo(body);
- classList.push(clsPrefix + osEnv.deviceType.toLowerCase());
- if (osEnv.is.iPad) {
- classList.push(clsPrefix + 'ipad');
- }
- classList.push(clsPrefix + osName);
- classList.push(clsPrefix + browserName);
- if (osMajorVersion) {
- classList.push(clsPrefix + osName + '-' + osMajorVersion);
- }
- if (osEnv.is.BlackBerry) {
- classList.push(clsPrefix + 'bb');
- }
- if (Ext.browser.is.Standalone) {
- classList.push(clsPrefix + 'standalone');
- }
- classList.push(clsPrefix + orientation);
- body.addCls(classList);
- }
- },
- applyAutoBlurInput: function(autoBlurInput) {
- var touchstart = (Ext.feature.has.Touch) ? 'touchstart' : 'mousedown';
- if (autoBlurInput) {
- this.addWindowListener(touchstart, this.doBlurInput, false);
- }
- else {
- this.removeWindowListener(touchstart, this.doBlurInput, false);
- }
- return autoBlurInput;
- },
- applyAutoMaximize: function(autoMaximize) {
- if (Ext.browser.is.WebView) {
- autoMaximize = false;
- }
- if (autoMaximize) {
- this.on('ready', 'doAutoMaximizeOnReady', this, { single: true });
- this.on('orientationchange', 'doAutoMaximizeOnOrientationChange', this);
- }
- else {
- this.un('ready', 'doAutoMaximizeOnReady', this);
- this.un('orientationchange', 'doAutoMaximizeOnOrientationChange', this);
- }
- return autoMaximize;
- },
- applyPreventPanning: function(preventPanning) {
- if (preventPanning) {
- this.addWindowListener('touchmove', this.doPreventPanning, false);
- }
- else {
- this.removeWindowListener('touchmove', this.doPreventPanning, false);
- }
- return preventPanning;
- },
- applyPreventZooming: function(preventZooming) {
- var touchstart = (Ext.feature.has.Touch) ? 'touchstart' : 'mousedown';
- if (preventZooming) {
- this.addWindowListener(touchstart, this.doPreventZooming, false);
- }
- else {
- this.removeWindowListener(touchstart, this.doPreventZooming, false);
- }
- return preventZooming;
- },
- doAutoMaximizeOnReady: function() {
- var controller = arguments[arguments.length - 1];
- controller.pause();
- this.isMaximizing = true;
- this.on('maximize', function() {
- this.isMaximizing = false;
- this.updateSize();
- controller.resume();
- this.fireEvent('ready', this);
- }, this, { single: true });
- this.maximize();
- },
- doAutoMaximizeOnOrientationChange: function() {
- var controller = arguments[arguments.length - 1],
- firingArguments = controller.firingArguments;
- controller.pause();
- this.isMaximizing = true;
- this.on('maximize', function() {
- this.isMaximizing = false;
- this.updateSize();
- firingArguments[2] = this.windowWidth;
- firingArguments[3] = this.windowHeight;
- controller.resume();
- }, this, { single: true });
- this.maximize();
- },
- doBlurInput: function(e) {
- var target = e.target,
- focusedElement = this.focusedElement;
- if (focusedElement && !this.isInputRegex.test(target.tagName)) {
- delete this.focusedElement;
- focusedElement.blur();
- }
- },
- doPreventPanning: function(e) {
- e.preventDefault();
- },
- doPreventZooming: function(e) {
- // Don't prevent right mouse event
- if ('button' in e && e.button !== 0) {
- return;
- }
- var target = e.target;
- if (target && target.nodeType === 1 && !this.isInputRegex.test(target.tagName)) {
- e.preventDefault();
- }
- },
- addWindowListener: function(eventName, fn, capturing) {
- window.addEventListener(eventName, fn, Boolean(capturing));
- },
- removeWindowListener: function(eventName, fn, capturing) {
- window.removeEventListener(eventName, fn, Boolean(capturing));
- },
- doAddListener: function(eventName, fn, scope, options) {
- if (eventName === 'ready' && this.isReady && !this.isMaximizing) {
- fn.call(scope);
- return this;
- }
- return this.callSuper(arguments);
- },
- supportsOrientation: function() {
- return Ext.feature.has.Orientation;
- },
- onResize: function() {
- var oldWidth = this.windowWidth,
- oldHeight = this.windowHeight,
- width = this.getWindowWidth(),
- height = this.getWindowHeight(),
- currentOrientation = this.getOrientation(),
- newOrientation = this.determineOrientation();
- // Determine orientation change via resize. BOTH width AND height much change, otherwise
- // this is a keyboard popping up.
- if ((oldWidth !== width && oldHeight !== height) && currentOrientation !== newOrientation) {
- this.fireOrientationChangeEvent(newOrientation, currentOrientation);
- }
- },
- onOrientationChange: function() {
- var currentOrientation = this.getOrientation(),
- newOrientation = this.determineOrientation();
- if (newOrientation !== currentOrientation) {
- this.fireOrientationChangeEvent(newOrientation, currentOrientation);
- }
- },
- fireOrientationChangeEvent: function(newOrientation, oldOrientation) {
- var clsPrefix = Ext.baseCSSPrefix;
- Ext.getBody().replaceCls(clsPrefix + oldOrientation, clsPrefix + newOrientation);
- this.orientation = newOrientation;
- this.updateSize();
- this.fireEvent('orientationchange', this, newOrientation, this.windowWidth, this.windowHeight);
- },
- updateSize: function(width, height) {
- this.windowWidth = width !== undefined ? width : this.getWindowWidth();
- this.windowHeight = height !== undefined ? height : this.getWindowHeight();
- return this;
- },
- waitUntil: function(condition, onSatisfied, onTimeout, delay, timeoutDuration) {
- if (!delay) {
- delay = 50;
- }
- if (!timeoutDuration) {
- timeoutDuration = 2000;
- }
- var scope = this,
- elapse = 0;
- setTimeout(function repeat() {
- elapse += delay;
- if (condition.call(scope) === true) {
- if (onSatisfied) {
- onSatisfied.call(scope);
- }
- }
- else {
- if (elapse >= timeoutDuration) {
- if (onTimeout) {
- onTimeout.call(scope);
- }
- }
- else {
- setTimeout(repeat, delay);
- }
- }
- }, delay);
- },
- maximize: function() {
- this.fireMaximizeEvent();
- },
- fireMaximizeEvent: function() {
- this.updateSize();
- this.fireEvent('maximize', this);
- },
- doSetHeight: function(height) {
- Ext.getBody().setHeight(height);
- this.callParent(arguments);
- },
- doSetWidth: function(width) {
- Ext.getBody().setWidth(width);
- this.callParent(arguments);
- },
- scrollToTop: function() {
- window.scrollTo(0, -1);
- },
- /**
- * Retrieves the document width.
- * @return {Number} width in pixels.
- */
- getWindowWidth: function() {
- return window.innerWidth;
- },
- /**
- * Retrieves the document height.
- * @return {Number} height in pixels.
- */
- getWindowHeight: function() {
- return window.innerHeight;
- },
- getWindowOuterHeight: function() {
- return window.outerHeight;
- },
- getWindowOrientation: function() {
- return window.orientation;
- },
- /**
- * Returns the current orientation.
- * @return {String} `portrait` or `landscape`
- */
- getOrientation: function() {
- return this.orientation;
- },
- getSize: function() {
- return {
- width: this.windowWidth,
- height: this.windowHeight
- };
- },
- determineOrientation: function() {
- var portrait = this.PORTRAIT,
- landscape = this.LANDSCAPE;
- if (this.supportsOrientation()) {
- if (this.getWindowOrientation() % 180 === 0) {
- return portrait;
- }
- return landscape;
- }
- else {
- if (this.getWindowHeight() >= this.getWindowWidth()) {
- return portrait;
- }
- return landscape;
- }
- },
- onItemFullscreenChange: function(item) {
- item.addCls(this.fullscreenItemCls);
- this.add(item);
- }
- });
- /**
- * @private
- * Android version of viewport.
- */
- Ext.define('Ext.viewport.Android', {
- extend: 'Ext.viewport.Default',
- constructor: function() {
- this.on('orientationchange', 'doFireOrientationChangeEvent', this, { prepend: true });
- this.on('orientationchange', 'hideKeyboardIfNeeded', this, { prepend: true });
- this.callParent(arguments);
- this.addWindowListener('resize', Ext.Function.bind(this.onResize, this));
- },
- getDummyInput: function() {
- var input = this.dummyInput,
- focusedElement = this.focusedElement,
- box = Ext.fly(focusedElement).getPageBox();
- if (!input) {
- this.dummyInput = input = document.createElement('input');
- input.style.position = 'absolute';
- input.style.opacity = '0';
- document.body.appendChild(input);
- }
- input.style.left = box.left + 'px';
- input.style.top = box.top + 'px';
- input.style.display = '';
- return input;
- },
- doBlurInput: function(e) {
- var target = e.target,
- focusedElement = this.focusedElement,
- dummy;
- if (focusedElement && !this.isInputRegex.test(target.tagName)) {
- dummy = this.getDummyInput();
- delete this.focusedElement;
- dummy.focus();
- setTimeout(function() {
- dummy.style.display = 'none';
- }, 100);
- }
- },
- hideKeyboardIfNeeded: function() {
- var eventController = arguments[arguments.length - 1],
- focusedElement = this.focusedElement;
- if (focusedElement) {
- delete this.focusedElement;
- eventController.pause();
- if (Ext.os.version.lt('4')) {
- focusedElement.style.display = 'none';
- }
- else {
- focusedElement.blur();
- }
- setTimeout(function() {
- focusedElement.style.display = '';
- eventController.resume();
- }, 1000);
- }
- },
- doFireOrientationChangeEvent: function() {
- var eventController = arguments[arguments.length - 1];
- this.orientationChanging = true;
- eventController.pause();
- this.waitUntil(function() {
- return this.getWindowOuterHeight() !== this.windowOuterHeight;
- }, function() {
- this.windowOuterHeight = this.getWindowOuterHeight();
- this.updateSize();
- eventController.firingArguments[2] = this.windowWidth;
- eventController.firingArguments[3] = this.windowHeight;
- eventController.resume();
- this.orientationChanging = false;
- }, function() {
- //<debug error>
- Ext.Logger.error("Timeout waiting for viewport's outerHeight to change before firing orientationchange", this);
- //</debug>
- });
- return this;
- },
- applyAutoMaximize: function(autoMaximize) {
- autoMaximize = this.callParent(arguments);
- this.on('add', 'fixSize', this, { single: true });
- if (!autoMaximize) {
- this.on('ready', 'fixSize', this, { single: true });
- this.onAfter('orientationchange', 'doFixSize', this, { buffer: 100 });
- }
- else {
- this.un('ready', 'fixSize', this);
- this.unAfter('orientationchange', 'doFixSize', this);
- }
- },
- fixSize: function() {
- this.doFixSize();
- },
- doFixSize: function() {
- this.setHeight(this.getWindowHeight());
- },
- determineOrientation: function() {
- return (this.getWindowHeight() >= this.getWindowWidth()) ? this.PORTRAIT : this.LANDSCAPE;
- },
- getActualWindowOuterHeight: function() {
- return Math.round(this.getWindowOuterHeight() / window.devicePixelRatio);
- },
- maximize: function() {
- var stretchHeights = this.stretchHeights,
- orientation = this.orientation,
- height;
- height = stretchHeights[orientation];
- if (!height) {
- stretchHeights[orientation] = height = this.getActualWindowOuterHeight();
- }
- if (!this.addressBarHeight) {
- this.addressBarHeight = height - this.getWindowHeight();
- }
- this.setHeight(height);
- var isHeightMaximized = Ext.Function.bind(this.isHeightMaximized, this, [height]);
- this.scrollToTop();
- this.waitUntil(isHeightMaximized, this.fireMaximizeEvent, this.fireMaximizeEvent);
- },
- isHeightMaximized: function(height) {
- this.scrollToTop();
- return this.getWindowHeight() === height;
- }
- }, function() {
- if (!Ext.os.is.Android) {
- return;
- }
- var version = Ext.os.version,
- userAgent = Ext.browser.userAgent,
- // These Android devices have a nasty bug which causes JavaScript timers to be completely frozen
- // when the browser's viewport is being panned.
- isBuggy = /(htc|desire|incredible|ADR6300)/i.test(userAgent) && version.lt('2.3');
- if (isBuggy) {
- this.override({
- constructor: function(config) {
- if (!config) {
- config = {};
- }
- config.autoMaximize = false;
- this.watchDogTick = Ext.Function.bind(this.watchDogTick, this);
- setInterval(this.watchDogTick, 1000);
- return this.callParent([config]);
- },
- watchDogTick: function() {
- this.watchDogLastTick = Ext.Date.now();
- },
- doPreventPanning: function() {
- var now = Ext.Date.now(),
- lastTick = this.watchDogLastTick,
- deltaTime = now - lastTick;
- // Timers are frozen
- if (deltaTime >= 2000) {
- return;
- }
- return this.callParent(arguments);
- },
- doPreventZooming: function() {
- var now = Ext.Date.now(),
- lastTick = this.watchDogLastTick,
- deltaTime = now - lastTick;
- // Timers are frozen
- if (deltaTime >= 2000) {
- return;
- }
- return this.callParent(arguments);
- }
- });
- }
- if (version.match('2')) {
- this.override({
- onReady: function() {
- this.addWindowListener('resize', Ext.Function.bind(this.onWindowResize, this));
- this.callParent(arguments);
- },
- scrollToTop: function() {
- document.body.scrollTop = 100;
- },
- onWindowResize: function() {
- var oldWidth = this.windowWidth,
- oldHeight = this.windowHeight,
- width = this.getWindowWidth(),
- height = this.getWindowHeight();
- if (this.getAutoMaximize() && !this.isMaximizing && !this.orientationChanging
- && window.scrollY === 0
- && oldWidth === width
- && height < oldHeight
- && ((height >= oldHeight - this.addressBarHeight) || !this.focusedElement)) {
- this.scrollToTop();
- }
- },
- fixSize: function() {
- var orientation = this.getOrientation(),
- outerHeight = window.outerHeight,
- outerWidth = window.outerWidth,
- actualOuterHeight;
- // On some Android 2 devices such as the Kindle Fire, outerWidth and outerHeight are reported wrongly
- // when navigating from another page that has larger size.
- if (orientation === 'landscape' && (outerHeight < outerWidth)
- || orientation === 'portrait' && (outerHeight >= outerWidth)) {
- actualOuterHeight = this.getActualWindowOuterHeight();
- }
- else {
- actualOuterHeight = this.getWindowHeight();
- }
- this.waitUntil(function() {
- return actualOuterHeight > this.getWindowHeight();
- }, this.doFixSize, this.doFixSize, 50, 1000);
- }
- });
- }
- else if (version.gtEq('3.1')) {
- this.override({
- isHeightMaximized: function(height) {
- this.scrollToTop();
- return this.getWindowHeight() === height - 1;
- }
- });
- }
- else if (version.match('3')) {
- this.override({
- isHeightMaximized: function() {
- this.scrollToTop();
- return true;
- }
- })
- }
- if (version.gtEq('4')) {
- this.override({
- doBlurInput: Ext.emptyFn
- });
- }
- });
- /**
- * @private
- * iOS version of viewport.
- */
- Ext.define('Ext.viewport.Ios', {
- extend: 'Ext.viewport.Default',
- isFullscreen: function() {
- return this.isHomeScreen();
- },
- isHomeScreen: function() {
- return window.navigator.standalone === true;
- },
- constructor: function() {
- this.callParent(arguments);
- if (this.getAutoMaximize() && !this.isFullscreen()) {
- this.addWindowListener('touchstart', Ext.Function.bind(this.onTouchStart, this));
- }
- },
- maximize: function() {
- if (this.isFullscreen()) {
- return this.callParent();
- }
- var stretchHeights = this.stretchHeights,
- orientation = this.orientation,
- currentHeight = this.getWindowHeight(),
- height = stretchHeights[orientation];
- if (window.scrollY > 0) {
- this.scrollToTop();
- if (!height) {
- stretchHeights[orientation] = height = this.getWindowHeight();
- }
- this.setHeight(height);
- this.fireMaximizeEvent();
- }
- else {
- if (!height) {
- height = this.getScreenHeight();
- }
- this.setHeight(height);
- this.waitUntil(function() {
- this.scrollToTop();
- return currentHeight !== this.getWindowHeight();
- }, function() {
- if (!stretchHeights[orientation]) {
- height = stretchHeights[orientation] = this.getWindowHeight();
- this.setHeight(height);
- }
- this.fireMaximizeEvent();
- }, function() {
- //<debug error>
- Ext.Logger.error("Timeout waiting for window.innerHeight to change", this);
- //</debug>
- height = stretchHeights[orientation] = this.getWindowHeight();
- this.setHeight(height);
- this.fireMaximizeEvent();
- }, 50, 1000);
- }
- },
- getScreenHeight: function() {
- return window.screen[this.orientation === this.PORTRAIT ? 'height' : 'width'];
- },
- onElementFocus: function() {
- if (this.getAutoMaximize() && !this.isFullscreen()) {
- clearTimeout(this.scrollToTopTimer);
- }
- this.callParent(arguments);
- },
- onElementBlur: function() {
- if (this.getAutoMaximize() && !this.isFullscreen()) {
- this.scrollToTopTimer = setTimeout(this.scrollToTop, 500);
- }
- this.callParent(arguments);
- },
- onTouchStart: function() {
- if (this.focusedElement === null) {
- this.scrollToTop();
- }
- },
- scrollToTop: function() {
- window.scrollTo(0, 0);
- }
- }, function() {
- if (!Ext.os.is.iOS) {
- return;
- }
- if (Ext.os.version.lt('3.2')) {
- this.override({
- constructor: function() {
- var stretchHeights = this.stretchHeights = {};
- stretchHeights[this.PORTRAIT] = 416;
- stretchHeights[this.LANDSCAPE] = 268;
- return this.callOverridden(arguments);
- }
- });
- }
- if (Ext.os.version.lt('5')) {
- this.override({
- fieldMaskClsTest: '-field-mask',
- doPreventZooming: function(e) {
- var target = e.target;
- if (target && target.nodeType === 1 &&
- !this.isInputRegex.test(target.tagName) &&
- target.className.indexOf(this.fieldMaskClsTest) == -1) {
- e.preventDefault();
- }
- }
- });
- }
- if (Ext.os.is.iPad) {
- this.override({
- isFullscreen: function() {
- return true;
- }
- });
- }
- });
- /**
- * This class acts as a factory for environment-specific viewport implementations.
- *
- * Please refer to the {@link Ext.Viewport} documentation about using the global instance.
- * @private
- */
- Ext.define('Ext.viewport.Viewport', {
- requires: [
- 'Ext.viewport.Ios',
- 'Ext.viewport.Android'
- ],
- constructor: function(config) {
- var osName = Ext.os.name,
- viewportName, viewport;
- switch (osName) {
- case 'Android':
- viewportName = (Ext.browser.name == 'ChromeMobile') ? 'Default' : 'Android';
- break;
- case 'iOS':
- viewportName = 'Ios';
- break;
- default:
- viewportName = 'Default';
- }
- viewport = Ext.create('Ext.viewport.' + viewportName, config);
- return viewport;
- }
- });
- // Docs for the singleton instance created by above factory:
- /**
- * @class Ext.Viewport
- * @extends Ext.viewport.Default
- * @singleton
- *
- * Ext.Viewport is a instance created when you use {@link Ext#setup}. Because {@link Ext.Viewport} extends from
- * {@link Ext.Container}, it has as {@link #layout} (which defaults to {@link Ext.layout.Card}). This means you
- * can add items to it at any time, from anywhere in your code. The {@link Ext.Viewport} {@link #cfg-fullscreen}
- * configuration is `true` by default, so it will take up your whole screen.
- *
- * @example raw
- * Ext.setup({
- * onReady: function() {
- * Ext.Viewport.add({
- * xtype: 'container',
- * html: 'My new container!'
- * });
- * }
- * });
- *
- * If you want to customize anything about this {@link Ext.Viewport} instance, you can do so by adding a property
- * called `viewport` into your {@link Ext#setup} object:
- *
- * @example raw
- * Ext.setup({
- * viewport: {
- * layout: 'vbox'
- * },
- * onReady: function() {
- * //do something
- * }
- * });
- *
- * **Note** if you use {@link Ext#onReady}, this instance of {@link Ext.Viewport} will **not** be created. Though, in most cases,
- * you should **not** use {@link Ext#onReady}.
- */
|