cebec201315d611264720257b63a4782d79c96b0.svn-base 351 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214
  1. /*!
  2. * jQuery Mobile 1.3.2
  3. * Git HEAD hash: 528cf0e96940644ea644096bfeb913ed920ffaef <> Date: Fri Jul 19 2013 22:17:57 UTC
  4. * http://jquerymobile.com
  5. *
  6. * Copyright 2010, 2013 jQuery Foundation, Inc. and other contributors
  7. * Released under the MIT license.
  8. * http://jquery.org/license
  9. *
  10. */
  11. (function ( root, doc, factory ) {
  12. if ( typeof define === "function" && define.amd ) {
  13. // AMD. Register as an anonymous module.
  14. define( [ "jquery" ], function ( $ ) {
  15. factory( $, root, doc );
  16. return $.mobile;
  17. });
  18. } else {
  19. // Browser globals
  20. factory( root.jQuery, root, doc );
  21. }
  22. }( this, document, function ( jQuery, window, document, undefined ) {
  23. (function( $ ) {
  24. $.mobile = {};
  25. }( jQuery ));
  26. (function( $, window, undefined ) {
  27. var nsNormalizeDict = {};
  28. // jQuery.mobile configurable options
  29. $.mobile = $.extend($.mobile, {
  30. // Version of the jQuery Mobile Framework
  31. version: "1.3.2",
  32. // Namespace used framework-wide for data-attrs. Default is no namespace
  33. ns: "",
  34. // Define the url parameter used for referencing widget-generated sub-pages.
  35. // Translates to to example.html&ui-page=subpageIdentifier
  36. // hash segment before &ui-page= is used to make Ajax request
  37. subPageUrlKey: "ui-page",
  38. // Class assigned to page currently in view, and during transitions
  39. activePageClass: "ui-page-active",
  40. // Class used for "active" button state, from CSS framework
  41. activeBtnClass: "ui-btn-active",
  42. // Class used for "focus" form element state, from CSS framework
  43. focusClass: "ui-focus",
  44. // Automatically handle clicks and form submissions through Ajax, when same-domain
  45. ajaxEnabled: true,
  46. // Automatically load and show pages based on location.hash
  47. hashListeningEnabled: true,
  48. // disable to prevent jquery from bothering with links
  49. linkBindingEnabled: true,
  50. // Set default page transition - 'none' for no transitions
  51. defaultPageTransition: "fade",
  52. // Set maximum window width for transitions to apply - 'false' for no limit
  53. maxTransitionWidth: false,
  54. // Minimum scroll distance that will be remembered when returning to a page
  55. minScrollBack: 250,
  56. // DEPRECATED: the following property is no longer in use, but defined until 2.0 to prevent conflicts
  57. touchOverflowEnabled: false,
  58. // Set default dialog transition - 'none' for no transitions
  59. defaultDialogTransition: "pop",
  60. // Error response message - appears when an Ajax page request fails
  61. pageLoadErrorMessage: "Error Loading Page",
  62. // For error messages, which theme does the box uses?
  63. pageLoadErrorMessageTheme: "e",
  64. // replace calls to window.history.back with phonegaps navigation helper
  65. // where it is provided on the window object
  66. phonegapNavigationEnabled: false,
  67. //automatically initialize the DOM when it's ready
  68. autoInitializePage: true,
  69. pushStateEnabled: true,
  70. // allows users to opt in to ignoring content by marking a parent element as
  71. // data-ignored
  72. ignoreContentEnabled: false,
  73. // turn of binding to the native orientationchange due to android orientation behavior
  74. orientationChangeEnabled: true,
  75. buttonMarkup: {
  76. hoverDelay: 200
  77. },
  78. // define the window and the document objects
  79. window: $( window ),
  80. document: $( document ),
  81. // TODO might be useful upstream in jquery itself ?
  82. keyCode: {
  83. ALT: 18,
  84. BACKSPACE: 8,
  85. CAPS_LOCK: 20,
  86. COMMA: 188,
  87. COMMAND: 91,
  88. COMMAND_LEFT: 91, // COMMAND
  89. COMMAND_RIGHT: 93,
  90. CONTROL: 17,
  91. DELETE: 46,
  92. DOWN: 40,
  93. END: 35,
  94. ENTER: 13,
  95. ESCAPE: 27,
  96. HOME: 36,
  97. INSERT: 45,
  98. LEFT: 37,
  99. MENU: 93, // COMMAND_RIGHT
  100. NUMPAD_ADD: 107,
  101. NUMPAD_DECIMAL: 110,
  102. NUMPAD_DIVIDE: 111,
  103. NUMPAD_ENTER: 108,
  104. NUMPAD_MULTIPLY: 106,
  105. NUMPAD_SUBTRACT: 109,
  106. PAGE_DOWN: 34,
  107. PAGE_UP: 33,
  108. PERIOD: 190,
  109. RIGHT: 39,
  110. SHIFT: 16,
  111. SPACE: 32,
  112. TAB: 9,
  113. UP: 38,
  114. WINDOWS: 91 // COMMAND
  115. },
  116. // Place to store various widget extensions
  117. behaviors: {},
  118. // Scroll page vertically: scroll to 0 to hide iOS address bar, or pass a Y value
  119. silentScroll: function( ypos ) {
  120. if ( $.type( ypos ) !== "number" ) {
  121. ypos = $.mobile.defaultHomeScroll;
  122. }
  123. // prevent scrollstart and scrollstop events
  124. $.event.special.scrollstart.enabled = false;
  125. setTimeout( function() {
  126. window.scrollTo( 0, ypos );
  127. $.mobile.document.trigger( "silentscroll", { x: 0, y: ypos });
  128. }, 20 );
  129. setTimeout( function() {
  130. $.event.special.scrollstart.enabled = true;
  131. }, 150 );
  132. },
  133. // Expose our cache for testing purposes.
  134. nsNormalizeDict: nsNormalizeDict,
  135. // Take a data attribute property, prepend the namespace
  136. // and then camel case the attribute string. Add the result
  137. // to our nsNormalizeDict so we don't have to do this again.
  138. nsNormalize: function( prop ) {
  139. if ( !prop ) {
  140. return;
  141. }
  142. return nsNormalizeDict[ prop ] || ( nsNormalizeDict[ prop ] = $.camelCase( $.mobile.ns + prop ) );
  143. },
  144. // Find the closest parent with a theme class on it. Note that
  145. // we are not using $.fn.closest() on purpose here because this
  146. // method gets called quite a bit and we need it to be as fast
  147. // as possible.
  148. getInheritedTheme: function( el, defaultTheme ) {
  149. var e = el[ 0 ],
  150. ltr = "",
  151. re = /ui-(bar|body|overlay)-([a-z])\b/,
  152. c, m;
  153. while ( e ) {
  154. c = e.className || "";
  155. if ( c && ( m = re.exec( c ) ) && ( ltr = m[ 2 ] ) ) {
  156. // We found a parent with a theme class
  157. // on it so bail from this loop.
  158. break;
  159. }
  160. e = e.parentNode;
  161. }
  162. // Return the theme letter we found, if none, return the
  163. // specified default.
  164. return ltr || defaultTheme || "a";
  165. },
  166. // TODO the following $ and $.fn extensions can/probably should be moved into jquery.mobile.core.helpers
  167. //
  168. // Find the closest javascript page element to gather settings data jsperf test
  169. // http://jsperf.com/single-complex-selector-vs-many-complex-selectors/edit
  170. // possibly naive, but it shows that the parsing overhead for *just* the page selector vs
  171. // the page and dialog selector is negligable. This could probably be speed up by
  172. // doing a similar parent node traversal to the one found in the inherited theme code above
  173. closestPageData: function( $target ) {
  174. return $target
  175. .closest( ':jqmData(role="page"), :jqmData(role="dialog")' )
  176. .data( "mobile-page" );
  177. },
  178. enhanceable: function( $set ) {
  179. return this.haveParents( $set, "enhance" );
  180. },
  181. hijackable: function( $set ) {
  182. return this.haveParents( $set, "ajax" );
  183. },
  184. haveParents: function( $set, attr ) {
  185. if ( !$.mobile.ignoreContentEnabled ) {
  186. return $set;
  187. }
  188. var count = $set.length,
  189. $newSet = $(),
  190. e, $element, excluded;
  191. for ( var i = 0; i < count; i++ ) {
  192. $element = $set.eq( i );
  193. excluded = false;
  194. e = $set[ i ];
  195. while ( e ) {
  196. var c = e.getAttribute ? e.getAttribute( "data-" + $.mobile.ns + attr ) : "";
  197. if ( c === "false" ) {
  198. excluded = true;
  199. break;
  200. }
  201. e = e.parentNode;
  202. }
  203. if ( !excluded ) {
  204. $newSet = $newSet.add( $element );
  205. }
  206. }
  207. return $newSet;
  208. },
  209. getScreenHeight: function() {
  210. // Native innerHeight returns more accurate value for this across platforms,
  211. // jQuery version is here as a normalized fallback for platforms like Symbian
  212. return window.innerHeight || $.mobile.window.height();
  213. }
  214. }, $.mobile );
  215. // Mobile version of data and removeData and hasData methods
  216. // ensures all data is set and retrieved using jQuery Mobile's data namespace
  217. $.fn.jqmData = function( prop, value ) {
  218. var result;
  219. if ( typeof prop !== "undefined" ) {
  220. if ( prop ) {
  221. prop = $.mobile.nsNormalize( prop );
  222. }
  223. // undefined is permitted as an explicit input for the second param
  224. // in this case it returns the value and does not set it to undefined
  225. if( arguments.length < 2 || value === undefined ){
  226. result = this.data( prop );
  227. } else {
  228. result = this.data( prop, value );
  229. }
  230. }
  231. return result;
  232. };
  233. $.jqmData = function( elem, prop, value ) {
  234. var result;
  235. if ( typeof prop !== "undefined" ) {
  236. result = $.data( elem, prop ? $.mobile.nsNormalize( prop ) : prop, value );
  237. }
  238. return result;
  239. };
  240. $.fn.jqmRemoveData = function( prop ) {
  241. return this.removeData( $.mobile.nsNormalize( prop ) );
  242. };
  243. $.jqmRemoveData = function( elem, prop ) {
  244. return $.removeData( elem, $.mobile.nsNormalize( prop ) );
  245. };
  246. $.fn.removeWithDependents = function() {
  247. $.removeWithDependents( this );
  248. };
  249. $.removeWithDependents = function( elem ) {
  250. var $elem = $( elem );
  251. ( $elem.jqmData( 'dependents' ) || $() ).remove();
  252. $elem.remove();
  253. };
  254. $.fn.addDependents = function( newDependents ) {
  255. $.addDependents( $( this ), newDependents );
  256. };
  257. $.addDependents = function( elem, newDependents ) {
  258. var dependents = $( elem ).jqmData( 'dependents' ) || $();
  259. $( elem ).jqmData( 'dependents', $.merge( dependents, newDependents ) );
  260. };
  261. // note that this helper doesn't attempt to handle the callback
  262. // or setting of an html element's text, its only purpose is
  263. // to return the html encoded version of the text in all cases. (thus the name)
  264. $.fn.getEncodedText = function() {
  265. return $( "<div/>" ).text( $( this ).text() ).html();
  266. };
  267. // fluent helper function for the mobile namespaced equivalent
  268. $.fn.jqmEnhanceable = function() {
  269. return $.mobile.enhanceable( this );
  270. };
  271. $.fn.jqmHijackable = function() {
  272. return $.mobile.hijackable( this );
  273. };
  274. // Monkey-patching Sizzle to filter the :jqmData selector
  275. var oldFind = $.find,
  276. jqmDataRE = /:jqmData\(([^)]*)\)/g;
  277. $.find = function( selector, context, ret, extra ) {
  278. selector = selector.replace( jqmDataRE, "[data-" + ( $.mobile.ns || "" ) + "$1]" );
  279. return oldFind.call( this, selector, context, ret, extra );
  280. };
  281. $.extend( $.find, oldFind );
  282. $.find.matches = function( expr, set ) {
  283. return $.find( expr, null, null, set );
  284. };
  285. $.find.matchesSelector = function( node, expr ) {
  286. return $.find( expr, null, null, [ node ] ).length > 0;
  287. };
  288. })( jQuery, this );
  289. /*!
  290. * jQuery UI Widget v1.10.0pre - 2012-11-13 (ff055a0c353c3c8ce6e5bfa07ad7cb03e8885bc5)
  291. * http://jqueryui.com
  292. *
  293. * Copyright 2010, 2013 jQuery Foundation and other contributors
  294. * Released under the MIT license.
  295. * http://jquery.org/license
  296. *
  297. * http://api.jqueryui.com/jQuery.widget/
  298. */
  299. (function( $, undefined ) {
  300. var uuid = 0,
  301. slice = Array.prototype.slice,
  302. _cleanData = $.cleanData;
  303. $.cleanData = function( elems ) {
  304. for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
  305. try {
  306. $( elem ).triggerHandler( "remove" );
  307. // http://bugs.jquery.com/ticket/8235
  308. } catch( e ) {}
  309. }
  310. _cleanData( elems );
  311. };
  312. $.widget = function( name, base, prototype ) {
  313. var fullName, existingConstructor, constructor, basePrototype,
  314. namespace = name.split( "." )[ 0 ];
  315. name = name.split( "." )[ 1 ];
  316. fullName = namespace + "-" + name;
  317. if ( !prototype ) {
  318. prototype = base;
  319. base = $.Widget;
  320. }
  321. // create selector for plugin
  322. $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
  323. return !!$.data( elem, fullName );
  324. };
  325. $[ namespace ] = $[ namespace ] || {};
  326. existingConstructor = $[ namespace ][ name ];
  327. constructor = $[ namespace ][ name ] = function( options, element ) {
  328. // allow instantiation without "new" keyword
  329. if ( !this._createWidget ) {
  330. return new constructor( options, element );
  331. }
  332. // allow instantiation without initializing for simple inheritance
  333. // must use "new" keyword (the code above always passes args)
  334. if ( arguments.length ) {
  335. this._createWidget( options, element );
  336. }
  337. };
  338. // extend with the existing constructor to carry over any static properties
  339. $.extend( constructor, existingConstructor, {
  340. version: prototype.version,
  341. // copy the object used to create the prototype in case we need to
  342. // redefine the widget later
  343. _proto: $.extend( {}, prototype ),
  344. // track widgets that inherit from this widget in case this widget is
  345. // redefined after a widget inherits from it
  346. _childConstructors: []
  347. });
  348. basePrototype = new base();
  349. // we need to make the options hash a property directly on the new instance
  350. // otherwise we'll modify the options hash on the prototype that we're
  351. // inheriting from
  352. basePrototype.options = $.widget.extend( {}, basePrototype.options );
  353. $.each( prototype, function( prop, value ) {
  354. if ( $.isFunction( value ) ) {
  355. prototype[ prop ] = (function() {
  356. var _super = function() {
  357. return base.prototype[ prop ].apply( this, arguments );
  358. },
  359. _superApply = function( args ) {
  360. return base.prototype[ prop ].apply( this, args );
  361. };
  362. return function() {
  363. var __super = this._super,
  364. __superApply = this._superApply,
  365. returnValue;
  366. this._super = _super;
  367. this._superApply = _superApply;
  368. returnValue = value.apply( this, arguments );
  369. this._super = __super;
  370. this._superApply = __superApply;
  371. return returnValue;
  372. };
  373. })();
  374. }
  375. });
  376. constructor.prototype = $.widget.extend( basePrototype, {
  377. // TODO: remove support for widgetEventPrefix
  378. // always use the name + a colon as the prefix, e.g., draggable:start
  379. // don't prefix for widgets that aren't DOM-based
  380. widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name
  381. }, prototype, {
  382. constructor: constructor,
  383. namespace: namespace,
  384. widgetName: name,
  385. widgetFullName: fullName
  386. });
  387. // If this widget is being redefined then we need to find all widgets that
  388. // are inheriting from it and redefine all of them so that they inherit from
  389. // the new version of this widget. We're essentially trying to replace one
  390. // level in the prototype chain.
  391. if ( existingConstructor ) {
  392. $.each( existingConstructor._childConstructors, function( i, child ) {
  393. var childPrototype = child.prototype;
  394. // redefine the child widget using the same prototype that was
  395. // originally used, but inherit from the new version of the base
  396. $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
  397. });
  398. // remove the list of existing child constructors from the old constructor
  399. // so the old child constructors can be garbage collected
  400. delete existingConstructor._childConstructors;
  401. } else {
  402. base._childConstructors.push( constructor );
  403. }
  404. $.widget.bridge( name, constructor );
  405. };
  406. $.widget.extend = function( target ) {
  407. var input = slice.call( arguments, 1 ),
  408. inputIndex = 0,
  409. inputLength = input.length,
  410. key,
  411. value;
  412. for ( ; inputIndex < inputLength; inputIndex++ ) {
  413. for ( key in input[ inputIndex ] ) {
  414. value = input[ inputIndex ][ key ];
  415. if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
  416. // Clone objects
  417. if ( $.isPlainObject( value ) ) {
  418. target[ key ] = $.isPlainObject( target[ key ] ) ?
  419. $.widget.extend( {}, target[ key ], value ) :
  420. // Don't extend strings, arrays, etc. with objects
  421. $.widget.extend( {}, value );
  422. // Copy everything else by reference
  423. } else {
  424. target[ key ] = value;
  425. }
  426. }
  427. }
  428. }
  429. return target;
  430. };
  431. $.widget.bridge = function( name, object ) {
  432. var fullName = object.prototype.widgetFullName || name;
  433. $.fn[ name ] = function( options ) {
  434. var isMethodCall = typeof options === "string",
  435. args = slice.call( arguments, 1 ),
  436. returnValue = this;
  437. // allow multiple hashes to be passed on init
  438. options = !isMethodCall && args.length ?
  439. $.widget.extend.apply( null, [ options ].concat(args) ) :
  440. options;
  441. if ( isMethodCall ) {
  442. this.each(function() {
  443. var methodValue,
  444. instance = $.data( this, fullName );
  445. if ( !instance ) {
  446. return $.error( "cannot call methods on " + name + " prior to initialization; " +
  447. "attempted to call method '" + options + "'" );
  448. }
  449. if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
  450. return $.error( "no such method '" + options + "' for " + name + " widget instance" );
  451. }
  452. methodValue = instance[ options ].apply( instance, args );
  453. if ( methodValue !== instance && methodValue !== undefined ) {
  454. returnValue = methodValue && methodValue.jquery ?
  455. returnValue.pushStack( methodValue.get() ) :
  456. methodValue;
  457. return false;
  458. }
  459. });
  460. } else {
  461. this.each(function() {
  462. var instance = $.data( this, fullName );
  463. if ( instance ) {
  464. instance.option( options || {} )._init();
  465. } else {
  466. $.data( this, fullName, new object( options, this ) );
  467. }
  468. });
  469. }
  470. return returnValue;
  471. };
  472. };
  473. $.Widget = function( /* options, element */ ) {};
  474. $.Widget._childConstructors = [];
  475. $.Widget.prototype = {
  476. widgetName: "widget",
  477. widgetEventPrefix: "",
  478. defaultElement: "<div>",
  479. options: {
  480. disabled: false,
  481. // callbacks
  482. create: null
  483. },
  484. _createWidget: function( options, element ) {
  485. element = $( element || this.defaultElement || this )[ 0 ];
  486. this.element = $( element );
  487. this.uuid = uuid++;
  488. this.eventNamespace = "." + this.widgetName + this.uuid;
  489. this.options = $.widget.extend( {},
  490. this.options,
  491. this._getCreateOptions(),
  492. options );
  493. this.bindings = $();
  494. this.hoverable = $();
  495. this.focusable = $();
  496. if ( element !== this ) {
  497. $.data( element, this.widgetFullName, this );
  498. this._on( true, this.element, {
  499. remove: function( event ) {
  500. if ( event.target === element ) {
  501. this.destroy();
  502. }
  503. }
  504. });
  505. this.document = $( element.style ?
  506. // element within the document
  507. element.ownerDocument :
  508. // element is window or document
  509. element.document || element );
  510. this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
  511. }
  512. this._create();
  513. this._trigger( "create", null, this._getCreateEventData() );
  514. this._init();
  515. },
  516. _getCreateOptions: $.noop,
  517. _getCreateEventData: $.noop,
  518. _create: $.noop,
  519. _init: $.noop,
  520. destroy: function() {
  521. this._destroy();
  522. // we can probably remove the unbind calls in 2.0
  523. // all event bindings should go through this._on()
  524. this.element
  525. .unbind( this.eventNamespace )
  526. // 1.9 BC for #7810
  527. // TODO remove dual storage
  528. .removeData( this.widgetName )
  529. .removeData( this.widgetFullName )
  530. // support: jquery <1.6.3
  531. // http://bugs.jquery.com/ticket/9413
  532. .removeData( $.camelCase( this.widgetFullName ) );
  533. this.widget()
  534. .unbind( this.eventNamespace )
  535. .removeAttr( "aria-disabled" )
  536. .removeClass(
  537. this.widgetFullName + "-disabled " +
  538. "ui-state-disabled" );
  539. // clean up events and states
  540. this.bindings.unbind( this.eventNamespace );
  541. this.hoverable.removeClass( "ui-state-hover" );
  542. this.focusable.removeClass( "ui-state-focus" );
  543. },
  544. _destroy: $.noop,
  545. widget: function() {
  546. return this.element;
  547. },
  548. option: function( key, value ) {
  549. var options = key,
  550. parts,
  551. curOption,
  552. i;
  553. if ( arguments.length === 0 ) {
  554. // don't return a reference to the internal hash
  555. return $.widget.extend( {}, this.options );
  556. }
  557. if ( typeof key === "string" ) {
  558. // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
  559. options = {};
  560. parts = key.split( "." );
  561. key = parts.shift();
  562. if ( parts.length ) {
  563. curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
  564. for ( i = 0; i < parts.length - 1; i++ ) {
  565. curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
  566. curOption = curOption[ parts[ i ] ];
  567. }
  568. key = parts.pop();
  569. if ( value === undefined ) {
  570. return curOption[ key ] === undefined ? null : curOption[ key ];
  571. }
  572. curOption[ key ] = value;
  573. } else {
  574. if ( value === undefined ) {
  575. return this.options[ key ] === undefined ? null : this.options[ key ];
  576. }
  577. options[ key ] = value;
  578. }
  579. }
  580. this._setOptions( options );
  581. return this;
  582. },
  583. _setOptions: function( options ) {
  584. var key;
  585. for ( key in options ) {
  586. this._setOption( key, options[ key ] );
  587. }
  588. return this;
  589. },
  590. _setOption: function( key, value ) {
  591. this.options[ key ] = value;
  592. if ( key === "disabled" ) {
  593. this.widget()
  594. .toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
  595. .attr( "aria-disabled", value );
  596. this.hoverable.removeClass( "ui-state-hover" );
  597. this.focusable.removeClass( "ui-state-focus" );
  598. }
  599. return this;
  600. },
  601. enable: function() {
  602. return this._setOption( "disabled", false );
  603. },
  604. disable: function() {
  605. return this._setOption( "disabled", true );
  606. },
  607. _on: function( suppressDisabledCheck, element, handlers ) {
  608. var delegateElement,
  609. instance = this;
  610. // no suppressDisabledCheck flag, shuffle arguments
  611. if ( typeof suppressDisabledCheck !== "boolean" ) {
  612. handlers = element;
  613. element = suppressDisabledCheck;
  614. suppressDisabledCheck = false;
  615. }
  616. // no element argument, shuffle and use this.element
  617. if ( !handlers ) {
  618. handlers = element;
  619. element = this.element;
  620. delegateElement = this.widget();
  621. } else {
  622. // accept selectors, DOM elements
  623. element = delegateElement = $( element );
  624. this.bindings = this.bindings.add( element );
  625. }
  626. $.each( handlers, function( event, handler ) {
  627. function handlerProxy() {
  628. // allow widgets to customize the disabled handling
  629. // - disabled as an array instead of boolean
  630. // - disabled class as method for disabling individual parts
  631. if ( !suppressDisabledCheck &&
  632. ( instance.options.disabled === true ||
  633. $( this ).hasClass( "ui-state-disabled" ) ) ) {
  634. return;
  635. }
  636. return ( typeof handler === "string" ? instance[ handler ] : handler )
  637. .apply( instance, arguments );
  638. }
  639. // copy the guid so direct unbinding works
  640. if ( typeof handler !== "string" ) {
  641. handlerProxy.guid = handler.guid =
  642. handler.guid || handlerProxy.guid || $.guid++;
  643. }
  644. var match = event.match( /^(\w+)\s*(.*)$/ ),
  645. eventName = match[1] + instance.eventNamespace,
  646. selector = match[2];
  647. if ( selector ) {
  648. delegateElement.delegate( selector, eventName, handlerProxy );
  649. } else {
  650. element.bind( eventName, handlerProxy );
  651. }
  652. });
  653. },
  654. _off: function( element, eventName ) {
  655. eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
  656. element.unbind( eventName ).undelegate( eventName );
  657. },
  658. _delay: function( handler, delay ) {
  659. function handlerProxy() {
  660. return ( typeof handler === "string" ? instance[ handler ] : handler )
  661. .apply( instance, arguments );
  662. }
  663. var instance = this;
  664. return setTimeout( handlerProxy, delay || 0 );
  665. },
  666. _hoverable: function( element ) {
  667. this.hoverable = this.hoverable.add( element );
  668. this._on( element, {
  669. mouseenter: function( event ) {
  670. $( event.currentTarget ).addClass( "ui-state-hover" );
  671. },
  672. mouseleave: function( event ) {
  673. $( event.currentTarget ).removeClass( "ui-state-hover" );
  674. }
  675. });
  676. },
  677. _focusable: function( element ) {
  678. this.focusable = this.focusable.add( element );
  679. this._on( element, {
  680. focusin: function( event ) {
  681. $( event.currentTarget ).addClass( "ui-state-focus" );
  682. },
  683. focusout: function( event ) {
  684. $( event.currentTarget ).removeClass( "ui-state-focus" );
  685. }
  686. });
  687. },
  688. _trigger: function( type, event, data ) {
  689. var prop, orig,
  690. callback = this.options[ type ];
  691. data = data || {};
  692. event = $.Event( event );
  693. event.type = ( type === this.widgetEventPrefix ?
  694. type :
  695. this.widgetEventPrefix + type ).toLowerCase();
  696. // the original event may come from any element
  697. // so we need to reset the target on the new event
  698. event.target = this.element[ 0 ];
  699. // copy original event properties over to the new event
  700. orig = event.originalEvent;
  701. if ( orig ) {
  702. for ( prop in orig ) {
  703. if ( !( prop in event ) ) {
  704. event[ prop ] = orig[ prop ];
  705. }
  706. }
  707. }
  708. this.element.trigger( event, data );
  709. return !( $.isFunction( callback ) &&
  710. callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
  711. event.isDefaultPrevented() );
  712. }
  713. };
  714. $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
  715. $.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
  716. if ( typeof options === "string" ) {
  717. options = { effect: options };
  718. }
  719. var hasOptions,
  720. effectName = !options ?
  721. method :
  722. options === true || typeof options === "number" ?
  723. defaultEffect :
  724. options.effect || defaultEffect;
  725. options = options || {};
  726. if ( typeof options === "number" ) {
  727. options = { duration: options };
  728. }
  729. hasOptions = !$.isEmptyObject( options );
  730. options.complete = callback;
  731. if ( options.delay ) {
  732. element.delay( options.delay );
  733. }
  734. if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
  735. element[ method ]( options );
  736. } else if ( effectName !== method && element[ effectName ] ) {
  737. element[ effectName ]( options.duration, options.easing, callback );
  738. } else {
  739. element.queue(function( next ) {
  740. $( this )[ method ]();
  741. if ( callback ) {
  742. callback.call( element[ 0 ] );
  743. }
  744. next();
  745. });
  746. }
  747. };
  748. });
  749. })( jQuery );
  750. (function( $, undefined ) {
  751. $.widget( "mobile.widget", {
  752. // decorate the parent _createWidget to trigger `widgetinit` for users
  753. // who wish to do post post `widgetcreate` alterations/additions
  754. //
  755. // TODO create a pull request for jquery ui to trigger this event
  756. // in the original _createWidget
  757. _createWidget: function() {
  758. $.Widget.prototype._createWidget.apply( this, arguments );
  759. this._trigger( 'init' );
  760. },
  761. _getCreateOptions: function() {
  762. var elem = this.element,
  763. options = {};
  764. $.each( this.options, function( option ) {
  765. var value = elem.jqmData( option.replace( /[A-Z]/g, function( c ) {
  766. return "-" + c.toLowerCase();
  767. })
  768. );
  769. if ( value !== undefined ) {
  770. options[ option ] = value;
  771. }
  772. });
  773. return options;
  774. },
  775. enhanceWithin: function( target, useKeepNative ) {
  776. this.enhance( $( this.options.initSelector, $( target )), useKeepNative );
  777. },
  778. enhance: function( targets, useKeepNative ) {
  779. var page, keepNative, $widgetElements = $( targets ), self = this;
  780. // if ignoreContentEnabled is set to true the framework should
  781. // only enhance the selected elements when they do NOT have a
  782. // parent with the data-namespace-ignore attribute
  783. $widgetElements = $.mobile.enhanceable( $widgetElements );
  784. if ( useKeepNative && $widgetElements.length ) {
  785. // TODO remove dependency on the page widget for the keepNative.
  786. // Currently the keepNative value is defined on the page prototype so
  787. // the method is as well
  788. page = $.mobile.closestPageData( $widgetElements );
  789. keepNative = ( page && page.keepNativeSelector()) || "";
  790. $widgetElements = $widgetElements.not( keepNative );
  791. }
  792. $widgetElements[ this.widgetName ]();
  793. },
  794. raise: function( msg ) {
  795. throw "Widget [" + this.widgetName + "]: " + msg;
  796. }
  797. });
  798. })( jQuery );
  799. (function( $, window ) {
  800. // DEPRECATED
  801. // NOTE global mobile object settings
  802. $.extend( $.mobile, {
  803. // DEPRECATED Should the text be visble in the loading message?
  804. loadingMessageTextVisible: undefined,
  805. // DEPRECATED When the text is visible, what theme does the loading box use?
  806. loadingMessageTheme: undefined,
  807. // DEPRECATED default message setting
  808. loadingMessage: undefined,
  809. // DEPRECATED
  810. // Turn on/off page loading message. Theme doubles as an object argument
  811. // with the following shape: { theme: '', text: '', html: '', textVisible: '' }
  812. // NOTE that the $.mobile.loading* settings and params past the first are deprecated
  813. showPageLoadingMsg: function( theme, msgText, textonly ) {
  814. $.mobile.loading( 'show', theme, msgText, textonly );
  815. },
  816. // DEPRECATED
  817. hidePageLoadingMsg: function() {
  818. $.mobile.loading( 'hide' );
  819. },
  820. loading: function() {
  821. this.loaderWidget.loader.apply( this.loaderWidget, arguments );
  822. }
  823. });
  824. // TODO move loader class down into the widget settings
  825. var loaderClass = "ui-loader", $html = $( "html" ), $window = $.mobile.window;
  826. $.widget( "mobile.loader", {
  827. // NOTE if the global config settings are defined they will override these
  828. // options
  829. options: {
  830. // the theme for the loading message
  831. theme: "a",
  832. // whether the text in the loading message is shown
  833. textVisible: false,
  834. // custom html for the inner content of the loading message
  835. html: "",
  836. // the text to be displayed when the popup is shown
  837. text: "loading"
  838. },
  839. defaultHtml: "<div class='" + loaderClass + "'>" +
  840. "<span class='ui-icon ui-icon-loading'></span>" +
  841. "<h1></h1>" +
  842. "</div>",
  843. // For non-fixed supportin browsers. Position at y center (if scrollTop supported), above the activeBtn (if defined), or just 100px from top
  844. fakeFixLoader: function() {
  845. var activeBtn = $( "." + $.mobile.activeBtnClass ).first();
  846. this.element
  847. .css({
  848. top: $.support.scrollTop && $window.scrollTop() + $window.height() / 2 ||
  849. activeBtn.length && activeBtn.offset().top || 100
  850. });
  851. },
  852. // check position of loader to see if it appears to be "fixed" to center
  853. // if not, use abs positioning
  854. checkLoaderPosition: function() {
  855. var offset = this.element.offset(),
  856. scrollTop = $window.scrollTop(),
  857. screenHeight = $.mobile.getScreenHeight();
  858. if ( offset.top < scrollTop || ( offset.top - scrollTop ) > screenHeight ) {
  859. this.element.addClass( "ui-loader-fakefix" );
  860. this.fakeFixLoader();
  861. $window
  862. .unbind( "scroll", this.checkLoaderPosition )
  863. .bind( "scroll", $.proxy( this.fakeFixLoader, this ) );
  864. }
  865. },
  866. resetHtml: function() {
  867. this.element.html( $( this.defaultHtml ).html() );
  868. },
  869. // Turn on/off page loading message. Theme doubles as an object argument
  870. // with the following shape: { theme: '', text: '', html: '', textVisible: '' }
  871. // NOTE that the $.mobile.loading* settings and params past the first are deprecated
  872. // TODO sweet jesus we need to break some of this out
  873. show: function( theme, msgText, textonly ) {
  874. var textVisible, message, $header, loadSettings;
  875. this.resetHtml();
  876. // use the prototype options so that people can set them globally at
  877. // mobile init. Consistency, it's what's for dinner
  878. if ( $.type(theme) === "object" ) {
  879. loadSettings = $.extend( {}, this.options, theme );
  880. // prefer object property from the param then the old theme setting
  881. theme = loadSettings.theme || $.mobile.loadingMessageTheme;
  882. } else {
  883. loadSettings = this.options;
  884. // here we prefer the them value passed as a string argument, then
  885. // we prefer the global option because we can't use undefined default
  886. // prototype options, then the prototype option
  887. theme = theme || $.mobile.loadingMessageTheme || loadSettings.theme;
  888. }
  889. // set the message text, prefer the param, then the settings object
  890. // then loading message
  891. message = msgText || $.mobile.loadingMessage || loadSettings.text;
  892. // prepare the dom
  893. $html.addClass( "ui-loading" );
  894. if ( $.mobile.loadingMessage !== false || loadSettings.html ) {
  895. // boolean values require a bit more work :P, supports object properties
  896. // and old settings
  897. if ( $.mobile.loadingMessageTextVisible !== undefined ) {
  898. textVisible = $.mobile.loadingMessageTextVisible;
  899. } else {
  900. textVisible = loadSettings.textVisible;
  901. }
  902. // add the proper css given the options (theme, text, etc)
  903. // Force text visibility if the second argument was supplied, or
  904. // if the text was explicitly set in the object args
  905. this.element.attr("class", loaderClass +
  906. " ui-corner-all ui-body-" + theme +
  907. " ui-loader-" + ( textVisible || msgText || theme.text ? "verbose" : "default" ) +
  908. ( loadSettings.textonly || textonly ? " ui-loader-textonly" : "" ) );
  909. // TODO verify that jquery.fn.html is ok to use in both cases here
  910. // this might be overly defensive in preventing unknowing xss
  911. // if the html attribute is defined on the loading settings, use that
  912. // otherwise use the fallbacks from above
  913. if ( loadSettings.html ) {
  914. this.element.html( loadSettings.html );
  915. } else {
  916. this.element.find( "h1" ).text( message );
  917. }
  918. // attach the loader to the DOM
  919. this.element.appendTo( $.mobile.pageContainer );
  920. // check that the loader is visible
  921. this.checkLoaderPosition();
  922. // on scroll check the loader position
  923. $window.bind( "scroll", $.proxy( this.checkLoaderPosition, this ) );
  924. }
  925. },
  926. hide: function() {
  927. $html.removeClass( "ui-loading" );
  928. if ( $.mobile.loadingMessage ) {
  929. this.element.removeClass( "ui-loader-fakefix" );
  930. }
  931. $.mobile.window.unbind( "scroll", this.fakeFixLoader );
  932. $.mobile.window.unbind( "scroll", this.checkLoaderPosition );
  933. }
  934. });
  935. $window.bind( 'pagecontainercreate', function() {
  936. $.mobile.loaderWidget = $.mobile.loaderWidget || $( $.mobile.loader.prototype.defaultHtml ).loader();
  937. });
  938. })(jQuery, this);
  939. // Script: jQuery hashchange event
  940. //
  941. // *Version: 1.3, Last updated: 7/21/2010*
  942. //
  943. // Project Home - http://benalman.com/projects/jquery-hashchange-plugin/
  944. // GitHub - http://github.com/cowboy/jquery-hashchange/
  945. // Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js
  946. // (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8kb gzipped)
  947. //
  948. // About: License
  949. //
  950. // Copyright (c) 2010 "Cowboy" Ben Alman,
  951. // Dual licensed under the MIT and GPL licenses.
  952. // http://benalman.com/about/license/
  953. //
  954. // About: Examples
  955. //
  956. // These working examples, complete with fully commented code, illustrate a few
  957. // ways in which this plugin can be used.
  958. //
  959. // hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/
  960. // document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/
  961. //
  962. // About: Support and Testing
  963. //
  964. // Information about what version or versions of jQuery this plugin has been
  965. // tested with, what browsers it has been tested in, and where the unit tests
  966. // reside (so you can test it yourself).
  967. //
  968. // jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2
  969. // Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5,
  970. // Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5.
  971. // Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/
  972. //
  973. // About: Known issues
  974. //
  975. // While this jQuery hashchange event implementation is quite stable and
  976. // robust, there are a few unfortunate browser bugs surrounding expected
  977. // hashchange event-based behaviors, independent of any JavaScript
  978. // window.onhashchange abstraction. See the following examples for more
  979. // information:
  980. //
  981. // Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/
  982. // Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/
  983. // WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/
  984. // Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/
  985. //
  986. // Also note that should a browser natively support the window.onhashchange
  987. // event, but not report that it does, the fallback polling loop will be used.
  988. //
  989. // About: Release History
  990. //
  991. // 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more
  992. // "removable" for mobile-only development. Added IE6/7 document.title
  993. // support. Attempted to make Iframe as hidden as possible by using
  994. // techniques from http://www.paciellogroup.com/blog/?p=604. Added
  995. // support for the "shortcut" format $(window).hashchange( fn ) and
  996. // $(window).hashchange() like jQuery provides for built-in events.
  997. // Renamed jQuery.hashchangeDelay to <jQuery.fn.hashchange.delay> and
  998. // lowered its default value to 50. Added <jQuery.fn.hashchange.domain>
  999. // and <jQuery.fn.hashchange.src> properties plus document-domain.html
  1000. // file to address access denied issues when setting document.domain in
  1001. // IE6/7.
  1002. // 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin
  1003. // from a page on another domain would cause an error in Safari 4. Also,
  1004. // IE6/7 Iframe is now inserted after the body (this actually works),
  1005. // which prevents the page from scrolling when the event is first bound.
  1006. // Event can also now be bound before DOM ready, but it won't be usable
  1007. // before then in IE6/7.
  1008. // 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug
  1009. // where browser version is incorrectly reported as 8.0, despite
  1010. // inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag.
  1011. // 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special
  1012. // window.onhashchange functionality into a separate plugin for users
  1013. // who want just the basic event & back button support, without all the
  1014. // extra awesomeness that BBQ provides. This plugin will be included as
  1015. // part of jQuery BBQ, but also be available separately.
  1016. (function( $, window, undefined ) {
  1017. // Reused string.
  1018. var str_hashchange = 'hashchange',
  1019. // Method / object references.
  1020. doc = document,
  1021. fake_onhashchange,
  1022. special = $.event.special,
  1023. // Does the browser support window.onhashchange? Note that IE8 running in
  1024. // IE7 compatibility mode reports true for 'onhashchange' in window, even
  1025. // though the event isn't supported, so also test document.documentMode.
  1026. doc_mode = doc.documentMode,
  1027. supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 );
  1028. // Get location.hash (or what you'd expect location.hash to be) sans any
  1029. // leading #. Thanks for making this necessary, Firefox!
  1030. function get_fragment( url ) {
  1031. url = url || location.href;
  1032. return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' );
  1033. };
  1034. // Method: jQuery.fn.hashchange
  1035. //
  1036. // Bind a handler to the window.onhashchange event or trigger all bound
  1037. // window.onhashchange event handlers. This behavior is consistent with
  1038. // jQuery's built-in event handlers.
  1039. //
  1040. // Usage:
  1041. //
  1042. // > jQuery(window).hashchange( [ handler ] );
  1043. //
  1044. // Arguments:
  1045. //
  1046. // handler - (Function) Optional handler to be bound to the hashchange
  1047. // event. This is a "shortcut" for the more verbose form:
  1048. // jQuery(window).bind( 'hashchange', handler ). If handler is omitted,
  1049. // all bound window.onhashchange event handlers will be triggered. This
  1050. // is a shortcut for the more verbose
  1051. // jQuery(window).trigger( 'hashchange' ). These forms are described in
  1052. // the <hashchange event> section.
  1053. //
  1054. // Returns:
  1055. //
  1056. // (jQuery) The initial jQuery collection of elements.
  1057. // Allow the "shortcut" format $(elem).hashchange( fn ) for binding and
  1058. // $(elem).hashchange() for triggering, like jQuery does for built-in events.
  1059. $.fn[ str_hashchange ] = function( fn ) {
  1060. return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange );
  1061. };
  1062. // Property: jQuery.fn.hashchange.delay
  1063. //
  1064. // The numeric interval (in milliseconds) at which the <hashchange event>
  1065. // polling loop executes. Defaults to 50.
  1066. // Property: jQuery.fn.hashchange.domain
  1067. //
  1068. // If you're setting document.domain in your JavaScript, and you want hash
  1069. // history to work in IE6/7, not only must this property be set, but you must
  1070. // also set document.domain BEFORE jQuery is loaded into the page. This
  1071. // property is only applicable if you are supporting IE6/7 (or IE8 operating
  1072. // in "IE7 compatibility" mode).
  1073. //
  1074. // In addition, the <jQuery.fn.hashchange.src> property must be set to the
  1075. // path of the included "document-domain.html" file, which can be renamed or
  1076. // modified if necessary (note that the document.domain specified must be the
  1077. // same in both your main JavaScript as well as in this file).
  1078. //
  1079. // Usage:
  1080. //
  1081. // jQuery.fn.hashchange.domain = document.domain;
  1082. // Property: jQuery.fn.hashchange.src
  1083. //
  1084. // If, for some reason, you need to specify an Iframe src file (for example,
  1085. // when setting document.domain as in <jQuery.fn.hashchange.domain>), you can
  1086. // do so using this property. Note that when using this property, history
  1087. // won't be recorded in IE6/7 until the Iframe src file loads. This property
  1088. // is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7
  1089. // compatibility" mode).
  1090. //
  1091. // Usage:
  1092. //
  1093. // jQuery.fn.hashchange.src = 'path/to/file.html';
  1094. $.fn[ str_hashchange ].delay = 50;
  1095. /*
  1096. $.fn[ str_hashchange ].domain = null;
  1097. $.fn[ str_hashchange ].src = null;
  1098. */
  1099. // Event: hashchange event
  1100. //
  1101. // Fired when location.hash changes. In browsers that support it, the native
  1102. // HTML5 window.onhashchange event is used, otherwise a polling loop is
  1103. // initialized, running every <jQuery.fn.hashchange.delay> milliseconds to
  1104. // see if the hash has changed. In IE6/7 (and IE8 operating in "IE7
  1105. // compatibility" mode), a hidden Iframe is created to allow the back button
  1106. // and hash-based history to work.
  1107. //
  1108. // Usage as described in <jQuery.fn.hashchange>:
  1109. //
  1110. // > // Bind an event handler.
  1111. // > jQuery(window).hashchange( function(e) {
  1112. // > var hash = location.hash;
  1113. // > ...
  1114. // > });
  1115. // >
  1116. // > // Manually trigger the event handler.
  1117. // > jQuery(window).hashchange();
  1118. //
  1119. // A more verbose usage that allows for event namespacing:
  1120. //
  1121. // > // Bind an event handler.
  1122. // > jQuery(window).bind( 'hashchange', function(e) {
  1123. // > var hash = location.hash;
  1124. // > ...
  1125. // > });
  1126. // >
  1127. // > // Manually trigger the event handler.
  1128. // > jQuery(window).trigger( 'hashchange' );
  1129. //
  1130. // Additional Notes:
  1131. //
  1132. // * The polling loop and Iframe are not created until at least one handler
  1133. // is actually bound to the 'hashchange' event.
  1134. // * If you need the bound handler(s) to execute immediately, in cases where
  1135. // a location.hash exists on page load, via bookmark or page refresh for
  1136. // example, use jQuery(window).hashchange() or the more verbose
  1137. // jQuery(window).trigger( 'hashchange' ).
  1138. // * The event can be bound before DOM ready, but since it won't be usable
  1139. // before then in IE6/7 (due to the necessary Iframe), recommended usage is
  1140. // to bind it inside a DOM ready handler.
  1141. // Override existing $.event.special.hashchange methods (allowing this plugin
  1142. // to be defined after jQuery BBQ in BBQ's source code).
  1143. special[ str_hashchange ] = $.extend( special[ str_hashchange ], {
  1144. // Called only when the first 'hashchange' event is bound to window.
  1145. setup: function() {
  1146. // If window.onhashchange is supported natively, there's nothing to do..
  1147. if ( supports_onhashchange ) { return false; }
  1148. // Otherwise, we need to create our own. And we don't want to call this
  1149. // until the user binds to the event, just in case they never do, since it
  1150. // will create a polling loop and possibly even a hidden Iframe.
  1151. $( fake_onhashchange.start );
  1152. },
  1153. // Called only when the last 'hashchange' event is unbound from window.
  1154. teardown: function() {
  1155. // If window.onhashchange is supported natively, there's nothing to do..
  1156. if ( supports_onhashchange ) { return false; }
  1157. // Otherwise, we need to stop ours (if possible).
  1158. $( fake_onhashchange.stop );
  1159. }
  1160. });
  1161. // fake_onhashchange does all the work of triggering the window.onhashchange
  1162. // event for browsers that don't natively support it, including creating a
  1163. // polling loop to watch for hash changes and in IE 6/7 creating a hidden
  1164. // Iframe to enable back and forward.
  1165. fake_onhashchange = (function() {
  1166. var self = {},
  1167. timeout_id,
  1168. // Remember the initial hash so it doesn't get triggered immediately.
  1169. last_hash = get_fragment(),
  1170. fn_retval = function( val ) { return val; },
  1171. history_set = fn_retval,
  1172. history_get = fn_retval;
  1173. // Start the polling loop.
  1174. self.start = function() {
  1175. timeout_id || poll();
  1176. };
  1177. // Stop the polling loop.
  1178. self.stop = function() {
  1179. timeout_id && clearTimeout( timeout_id );
  1180. timeout_id = undefined;
  1181. };
  1182. // This polling loop checks every $.fn.hashchange.delay milliseconds to see
  1183. // if location.hash has changed, and triggers the 'hashchange' event on
  1184. // window when necessary.
  1185. function poll() {
  1186. var hash = get_fragment(),
  1187. history_hash = history_get( last_hash );
  1188. if ( hash !== last_hash ) {
  1189. history_set( last_hash = hash, history_hash );
  1190. $(window).trigger( str_hashchange );
  1191. } else if ( history_hash !== last_hash ) {
  1192. location.href = location.href.replace( /#.*/, '' ) + history_hash;
  1193. }
  1194. timeout_id = setTimeout( poll, $.fn[ str_hashchange ].delay );
  1195. };
  1196. // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
  1197. // vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv
  1198. // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
  1199. window.attachEvent && !window.addEventListener && !supports_onhashchange && (function() {
  1200. // Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8
  1201. // when running in "IE7 compatibility" mode.
  1202. var iframe,
  1203. iframe_src;
  1204. // When the event is bound and polling starts in IE 6/7, create a hidden
  1205. // Iframe for history handling.
  1206. self.start = function() {
  1207. if ( !iframe ) {
  1208. iframe_src = $.fn[ str_hashchange ].src;
  1209. iframe_src = iframe_src && iframe_src + get_fragment();
  1210. // Create hidden Iframe. Attempt to make Iframe as hidden as possible
  1211. // by using techniques from http://www.paciellogroup.com/blog/?p=604.
  1212. iframe = $('<iframe tabindex="-1" title="empty"/>').hide()
  1213. // When Iframe has completely loaded, initialize the history and
  1214. // start polling.
  1215. .one( 'load', function() {
  1216. iframe_src || history_set( get_fragment() );
  1217. poll();
  1218. })
  1219. // Load Iframe src if specified, otherwise nothing.
  1220. .attr( 'src', iframe_src || 'javascript:0' )
  1221. // Append Iframe after the end of the body to prevent unnecessary
  1222. // initial page scrolling (yes, this works).
  1223. .insertAfter( 'body' )[0].contentWindow;
  1224. // Whenever `document.title` changes, update the Iframe's title to
  1225. // prettify the back/next history menu entries. Since IE sometimes
  1226. // errors with "Unspecified error" the very first time this is set
  1227. // (yes, very useful) wrap this with a try/catch block.
  1228. doc.onpropertychange = function() {
  1229. try {
  1230. if ( event.propertyName === 'title' ) {
  1231. iframe.document.title = doc.title;
  1232. }
  1233. } catch(e) {}
  1234. };
  1235. }
  1236. };
  1237. // Override the "stop" method since an IE6/7 Iframe was created. Even
  1238. // if there are no longer any bound event handlers, the polling loop
  1239. // is still necessary for back/next to work at all!
  1240. self.stop = fn_retval;
  1241. // Get history by looking at the hidden Iframe's location.hash.
  1242. history_get = function() {
  1243. return get_fragment( iframe.location.href );
  1244. };
  1245. // Set a new history item by opening and then closing the Iframe
  1246. // document, *then* setting its location.hash. If document.domain has
  1247. // been set, update that as well.
  1248. history_set = function( hash, history_hash ) {
  1249. var iframe_doc = iframe.document,
  1250. domain = $.fn[ str_hashchange ].domain;
  1251. if ( hash !== history_hash ) {
  1252. // Update Iframe with any initial `document.title` that might be set.
  1253. iframe_doc.title = doc.title;
  1254. // Opening the Iframe's document after it has been closed is what
  1255. // actually adds a history entry.
  1256. iframe_doc.open();
  1257. // Set document.domain for the Iframe document as well, if necessary.
  1258. domain && iframe_doc.write( '<script>document.domain="' + domain + '"</script>' );
  1259. iframe_doc.close();
  1260. // Update the Iframe's hash, for great justice.
  1261. iframe.location.hash = hash;
  1262. }
  1263. };
  1264. })();
  1265. // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  1266. // ^^^^^^^^^^^^^^^^^^^ REMOVE IF NOT SUPPORTING IE6/7/8 ^^^^^^^^^^^^^^^^^^^
  1267. // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  1268. return self;
  1269. })();
  1270. })(jQuery,this);
  1271. (function( $, undefined ) {
  1272. /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */
  1273. window.matchMedia = window.matchMedia || (function( doc, undefined ) {
  1274. var bool,
  1275. docElem = doc.documentElement,
  1276. refNode = docElem.firstElementChild || docElem.firstChild,
  1277. // fakeBody required for <FF4 when executed in <head>
  1278. fakeBody = doc.createElement( "body" ),
  1279. div = doc.createElement( "div" );
  1280. div.id = "mq-test-1";
  1281. div.style.cssText = "position:absolute;top:-100em";
  1282. fakeBody.style.background = "none";
  1283. fakeBody.appendChild(div);
  1284. return function(q){
  1285. div.innerHTML = "&shy;<style media=\"" + q + "\"> #mq-test-1 { width: 42px; }</style>";
  1286. docElem.insertBefore( fakeBody, refNode );
  1287. bool = div.offsetWidth === 42;
  1288. docElem.removeChild( fakeBody );
  1289. return {
  1290. matches: bool,
  1291. media: q
  1292. };
  1293. };
  1294. }( document ));
  1295. // $.mobile.media uses matchMedia to return a boolean.
  1296. $.mobile.media = function( q ) {
  1297. return window.matchMedia( q ).matches;
  1298. };
  1299. })(jQuery);
  1300. (function( $, undefined ) {
  1301. var support = {
  1302. touch: "ontouchend" in document
  1303. };
  1304. $.mobile.support = $.mobile.support || {};
  1305. $.extend( $.support, support );
  1306. $.extend( $.mobile.support, support );
  1307. }( jQuery ));
  1308. (function( $, undefined ) {
  1309. $.extend( $.support, {
  1310. orientation: "orientation" in window && "onorientationchange" in window
  1311. });
  1312. }( jQuery ));
  1313. (function( $, undefined ) {
  1314. // thx Modernizr
  1315. function propExists( prop ) {
  1316. var uc_prop = prop.charAt( 0 ).toUpperCase() + prop.substr( 1 ),
  1317. props = ( prop + " " + vendors.join( uc_prop + " " ) + uc_prop ).split( " " );
  1318. for ( var v in props ) {
  1319. if ( fbCSS[ props[ v ] ] !== undefined ) {
  1320. return true;
  1321. }
  1322. }
  1323. }
  1324. var fakeBody = $( "<body>" ).prependTo( "html" ),
  1325. fbCSS = fakeBody[ 0 ].style,
  1326. vendors = [ "Webkit", "Moz", "O" ],
  1327. webos = "palmGetResource" in window, //only used to rule out scrollTop
  1328. opera = window.opera,
  1329. operamini = window.operamini && ({}).toString.call( window.operamini ) === "[object OperaMini]",
  1330. bb = window.blackberry && !propExists( "-webkit-transform" ); //only used to rule out box shadow, as it's filled opaque on BB 5 and lower
  1331. function validStyle( prop, value, check_vend ) {
  1332. var div = document.createElement( 'div' ),
  1333. uc = function( txt ) {
  1334. return txt.charAt( 0 ).toUpperCase() + txt.substr( 1 );
  1335. },
  1336. vend_pref = function( vend ) {
  1337. if( vend === "" ) {
  1338. return "";
  1339. } else {
  1340. return "-" + vend.charAt( 0 ).toLowerCase() + vend.substr( 1 ) + "-";
  1341. }
  1342. },
  1343. check_style = function( vend ) {
  1344. var vend_prop = vend_pref( vend ) + prop + ": " + value + ";",
  1345. uc_vend = uc( vend ),
  1346. propStyle = uc_vend + ( uc_vend === "" ? prop : uc( prop ) );
  1347. div.setAttribute( "style", vend_prop );
  1348. if ( !!div.style[ propStyle ] ) {
  1349. ret = true;
  1350. }
  1351. },
  1352. check_vends = check_vend ? check_vend : vendors,
  1353. ret;
  1354. for( var i = 0; i < check_vends.length; i++ ) {
  1355. check_style( check_vends[i] );
  1356. }
  1357. return !!ret;
  1358. }
  1359. function transform3dTest() {
  1360. var mqProp = "transform-3d",
  1361. // Because the `translate3d` test below throws false positives in Android:
  1362. ret = $.mobile.media( "(-" + vendors.join( "-" + mqProp + "),(-" ) + "-" + mqProp + "),(" + mqProp + ")" );
  1363. if( ret ) {
  1364. return !!ret;
  1365. }
  1366. var el = document.createElement( "div" ),
  1367. transforms = {
  1368. // We’re omitting Opera for the time being; MS uses unprefixed.
  1369. 'MozTransform':'-moz-transform',
  1370. 'transform':'transform'
  1371. };
  1372. fakeBody.append( el );
  1373. for ( var t in transforms ) {
  1374. if( el.style[ t ] !== undefined ){
  1375. el.style[ t ] = 'translate3d( 100px, 1px, 1px )';
  1376. ret = window.getComputedStyle( el ).getPropertyValue( transforms[ t ] );
  1377. }
  1378. }
  1379. return ( !!ret && ret !== "none" );
  1380. }
  1381. // Test for dynamic-updating base tag support ( allows us to avoid href,src attr rewriting )
  1382. function baseTagTest() {
  1383. var fauxBase = location.protocol + "//" + location.host + location.pathname + "ui-dir/",
  1384. base = $( "head base" ),
  1385. fauxEle = null,
  1386. href = "",
  1387. link, rebase;
  1388. if ( !base.length ) {
  1389. base = fauxEle = $( "<base>", { "href": fauxBase }).appendTo( "head" );
  1390. } else {
  1391. href = base.attr( "href" );
  1392. }
  1393. link = $( "<a href='testurl' />" ).prependTo( fakeBody );
  1394. rebase = link[ 0 ].href;
  1395. base[ 0 ].href = href || location.pathname;
  1396. if ( fauxEle ) {
  1397. fauxEle.remove();
  1398. }
  1399. return rebase.indexOf( fauxBase ) === 0;
  1400. }
  1401. // Thanks Modernizr
  1402. function cssPointerEventsTest() {
  1403. var element = document.createElement( 'x' ),
  1404. documentElement = document.documentElement,
  1405. getComputedStyle = window.getComputedStyle,
  1406. supports;
  1407. if ( !( 'pointerEvents' in element.style ) ) {
  1408. return false;
  1409. }
  1410. element.style.pointerEvents = 'auto';
  1411. element.style.pointerEvents = 'x';
  1412. documentElement.appendChild( element );
  1413. supports = getComputedStyle &&
  1414. getComputedStyle( element, '' ).pointerEvents === 'auto';
  1415. documentElement.removeChild( element );
  1416. return !!supports;
  1417. }
  1418. function boundingRect() {
  1419. var div = document.createElement( "div" );
  1420. return typeof div.getBoundingClientRect !== "undefined";
  1421. }
  1422. // non-UA-based IE version check by James Padolsey, modified by jdalton - from http://gist.github.com/527683
  1423. // allows for inclusion of IE 6+, including Windows Mobile 7
  1424. $.extend( $.mobile, { browser: {} } );
  1425. $.mobile.browser.oldIE = (function() {
  1426. var v = 3,
  1427. div = document.createElement( "div" ),
  1428. a = div.all || [];
  1429. do {
  1430. div.innerHTML = "<!--[if gt IE " + ( ++v ) + "]><br><![endif]-->";
  1431. } while( a[0] );
  1432. return v > 4 ? v : !v;
  1433. })();
  1434. function fixedPosition() {
  1435. var w = window,
  1436. ua = navigator.userAgent,
  1437. platform = navigator.platform,
  1438. // Rendering engine is Webkit, and capture major version
  1439. wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ ),
  1440. wkversion = !!wkmatch && wkmatch[ 1 ],
  1441. ffmatch = ua.match( /Fennec\/([0-9]+)/ ),
  1442. ffversion = !!ffmatch && ffmatch[ 1 ],
  1443. operammobilematch = ua.match( /Opera Mobi\/([0-9]+)/ ),
  1444. omversion = !!operammobilematch && operammobilematch[ 1 ];
  1445. if(
  1446. // iOS 4.3 and older : Platform is iPhone/Pad/Touch and Webkit version is less than 534 (ios5)
  1447. ( ( platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1 || platform.indexOf( "iPod" ) > -1 ) && wkversion && wkversion < 534 ) ||
  1448. // Opera Mini
  1449. ( w.operamini && ({}).toString.call( w.operamini ) === "[object OperaMini]" ) ||
  1450. ( operammobilematch && omversion < 7458 ) ||
  1451. //Android lte 2.1: Platform is Android and Webkit version is less than 533 (Android 2.2)
  1452. ( ua.indexOf( "Android" ) > -1 && wkversion && wkversion < 533 ) ||
  1453. // Firefox Mobile before 6.0 -
  1454. ( ffversion && ffversion < 6 ) ||
  1455. // WebOS less than 3
  1456. ( "palmGetResource" in window && wkversion && wkversion < 534 ) ||
  1457. // MeeGo
  1458. ( ua.indexOf( "MeeGo" ) > -1 && ua.indexOf( "NokiaBrowser/8.5.0" ) > -1 ) ) {
  1459. return false;
  1460. }
  1461. return true;
  1462. }
  1463. $.extend( $.support, {
  1464. cssTransitions: "WebKitTransitionEvent" in window ||
  1465. validStyle( 'transition', 'height 100ms linear', [ "Webkit", "Moz", "" ] ) &&
  1466. !$.mobile.browser.oldIE && !opera,
  1467. // Note, Chrome for iOS has an extremely quirky implementation of popstate.
  1468. // We've chosen to take the shortest path to a bug fix here for issue #5426
  1469. // See the following link for information about the regex chosen
  1470. // https://developers.google.com/chrome/mobile/docs/user-agent#chrome_for_ios_user-agent
  1471. pushState: "pushState" in history &&
  1472. "replaceState" in history &&
  1473. // When running inside a FF iframe, calling replaceState causes an error
  1474. !( window.navigator.userAgent.indexOf( "Firefox" ) >= 0 && window.top !== window ) &&
  1475. ( window.navigator.userAgent.search(/CriOS/) === -1 ),
  1476. mediaquery: $.mobile.media( "only all" ),
  1477. cssPseudoElement: !!propExists( "content" ),
  1478. touchOverflow: !!propExists( "overflowScrolling" ),
  1479. cssTransform3d: transform3dTest(),
  1480. boxShadow: !!propExists( "boxShadow" ) && !bb,
  1481. fixedPosition: fixedPosition(),
  1482. scrollTop: ("pageXOffset" in window ||
  1483. "scrollTop" in document.documentElement ||
  1484. "scrollTop" in fakeBody[ 0 ]) && !webos && !operamini,
  1485. dynamicBaseTag: baseTagTest(),
  1486. cssPointerEvents: cssPointerEventsTest(),
  1487. boundingRect: boundingRect()
  1488. });
  1489. fakeBody.remove();
  1490. // $.mobile.ajaxBlacklist is used to override ajaxEnabled on platforms that have known conflicts with hash history updates (BB5, Symbian)
  1491. // or that generally work better browsing in regular http for full page refreshes (Opera Mini)
  1492. // Note: This detection below is used as a last resort.
  1493. // We recommend only using these detection methods when all other more reliable/forward-looking approaches are not possible
  1494. var nokiaLTE7_3 = (function() {
  1495. var ua = window.navigator.userAgent;
  1496. //The following is an attempt to match Nokia browsers that are running Symbian/s60, with webkit, version 7.3 or older
  1497. return ua.indexOf( "Nokia" ) > -1 &&
  1498. ( ua.indexOf( "Symbian/3" ) > -1 || ua.indexOf( "Series60/5" ) > -1 ) &&
  1499. ua.indexOf( "AppleWebKit" ) > -1 &&
  1500. ua.match( /(BrowserNG|NokiaBrowser)\/7\.[0-3]/ );
  1501. })();
  1502. // Support conditions that must be met in order to proceed
  1503. // default enhanced qualifications are media query support OR IE 7+
  1504. $.mobile.gradeA = function() {
  1505. return ( $.support.mediaquery || $.mobile.browser.oldIE && $.mobile.browser.oldIE >= 7 ) && ( $.support.boundingRect || $.fn.jquery.match(/1\.[0-7+]\.[0-9+]?/) !== null );
  1506. };
  1507. $.mobile.ajaxBlacklist =
  1508. // BlackBerry browsers, pre-webkit
  1509. window.blackberry && !window.WebKitPoint ||
  1510. // Opera Mini
  1511. operamini ||
  1512. // Symbian webkits pre 7.3
  1513. nokiaLTE7_3;
  1514. // Lastly, this workaround is the only way we've found so far to get pre 7.3 Symbian webkit devices
  1515. // to render the stylesheets when they're referenced before this script, as we'd recommend doing.
  1516. // This simply reappends the CSS in place, which for some reason makes it apply
  1517. if ( nokiaLTE7_3 ) {
  1518. $(function() {
  1519. $( "head link[rel='stylesheet']" ).attr( "rel", "alternate stylesheet" ).attr( "rel", "stylesheet" );
  1520. });
  1521. }
  1522. // For ruling out shadows via css
  1523. if ( !$.support.boxShadow ) {
  1524. $( "html" ).addClass( "ui-mobile-nosupport-boxshadow" );
  1525. }
  1526. })( jQuery );
  1527. (function( $, undefined ) {
  1528. var $win = $.mobile.window, self, history;
  1529. $.event.special.navigate = self = {
  1530. bound: false,
  1531. pushStateEnabled: true,
  1532. originalEventName: undefined,
  1533. // If pushstate support is present and push state support is defined to
  1534. // be true on the mobile namespace.
  1535. isPushStateEnabled: function() {
  1536. return $.support.pushState &&
  1537. $.mobile.pushStateEnabled === true &&
  1538. this.isHashChangeEnabled();
  1539. },
  1540. // !! assumes mobile namespace is present
  1541. isHashChangeEnabled: function() {
  1542. return $.mobile.hashListeningEnabled === true;
  1543. },
  1544. // TODO a lot of duplication between popstate and hashchange
  1545. popstate: function( event ) {
  1546. var newEvent = new $.Event( "navigate" ),
  1547. beforeNavigate = new $.Event( "beforenavigate" ),
  1548. state = event.originalEvent.state || {},
  1549. href = location.href;
  1550. $win.trigger( beforeNavigate );
  1551. if( beforeNavigate.isDefaultPrevented() ){
  1552. return;
  1553. }
  1554. if( event.historyState ){
  1555. $.extend(state, event.historyState);
  1556. }
  1557. // Make sure the original event is tracked for the end
  1558. // user to inspect incase they want to do something special
  1559. newEvent.originalEvent = event;
  1560. // NOTE we let the current stack unwind because any assignment to
  1561. // location.hash will stop the world and run this event handler. By
  1562. // doing this we create a similar behavior to hashchange on hash
  1563. // assignment
  1564. setTimeout(function() {
  1565. $win.trigger( newEvent, {
  1566. state: state
  1567. });
  1568. }, 0);
  1569. },
  1570. hashchange: function( event, data ) {
  1571. var newEvent = new $.Event( "navigate" ),
  1572. beforeNavigate = new $.Event( "beforenavigate" );
  1573. $win.trigger( beforeNavigate );
  1574. if( beforeNavigate.isDefaultPrevented() ){
  1575. return;
  1576. }
  1577. // Make sure the original event is tracked for the end
  1578. // user to inspect incase they want to do something special
  1579. newEvent.originalEvent = event;
  1580. // Trigger the hashchange with state provided by the user
  1581. // that altered the hash
  1582. $win.trigger( newEvent, {
  1583. // Users that want to fully normalize the two events
  1584. // will need to do history management down the stack and
  1585. // add the state to the event before this binding is fired
  1586. // TODO consider allowing for the explicit addition of callbacks
  1587. // to be fired before this value is set to avoid event timing issues
  1588. state: event.hashchangeState || {}
  1589. });
  1590. },
  1591. // TODO We really only want to set this up once
  1592. // but I'm not clear if there's a beter way to achieve
  1593. // this with the jQuery special event structure
  1594. setup: function( data, namespaces ) {
  1595. if( self.bound ) {
  1596. return;
  1597. }
  1598. self.bound = true;
  1599. if( self.isPushStateEnabled() ) {
  1600. self.originalEventName = "popstate";
  1601. $win.bind( "popstate.navigate", self.popstate );
  1602. } else if ( self.isHashChangeEnabled() ){
  1603. self.originalEventName = "hashchange";
  1604. $win.bind( "hashchange.navigate", self.hashchange );
  1605. }
  1606. }
  1607. };
  1608. })( jQuery );
  1609. (function( $, undefined ) {
  1610. var path, documentBase, $base, dialogHashKey = "&ui-state=dialog";
  1611. $.mobile.path = path = {
  1612. uiStateKey: "&ui-state",
  1613. // This scary looking regular expression parses an absolute URL or its relative
  1614. // variants (protocol, site, document, query, and hash), into the various
  1615. // components (protocol, host, path, query, fragment, etc that make up the
  1616. // URL as well as some other commonly used sub-parts. When used with RegExp.exec()
  1617. // or String.match, it parses the URL into a results array that looks like this:
  1618. //
  1619. // [0]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread#msg-content
  1620. // [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread
  1621. // [2]: http://jblas:password@mycompany.com:8080/mail/inbox
  1622. // [3]: http://jblas:password@mycompany.com:8080
  1623. // [4]: http:
  1624. // [5]: //
  1625. // [6]: jblas:password@mycompany.com:8080
  1626. // [7]: jblas:password
  1627. // [8]: jblas
  1628. // [9]: password
  1629. // [10]: mycompany.com:8080
  1630. // [11]: mycompany.com
  1631. // [12]: 8080
  1632. // [13]: /mail/inbox
  1633. // [14]: /mail/
  1634. // [15]: inbox
  1635. // [16]: ?msg=1234&type=unread
  1636. // [17]: #msg-content
  1637. //
  1638. urlParseRE: /^\s*(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,
  1639. // Abstraction to address xss (Issue #4787) by removing the authority in
  1640. // browsers that auto decode it. All references to location.href should be
  1641. // replaced with a call to this method so that it can be dealt with properly here
  1642. getLocation: function( url ) {
  1643. var uri = url ? this.parseUrl( url ) : location,
  1644. hash = this.parseUrl( url || location.href ).hash;
  1645. // mimic the browser with an empty string when the hash is empty
  1646. hash = hash === "#" ? "" : hash;
  1647. // Make sure to parse the url or the location object for the hash because using location.hash
  1648. // is autodecoded in firefox, the rest of the url should be from the object (location unless
  1649. // we're testing) to avoid the inclusion of the authority
  1650. return uri.protocol + "//" + uri.host + uri.pathname + uri.search + hash;
  1651. },
  1652. parseLocation: function() {
  1653. return this.parseUrl( this.getLocation() );
  1654. },
  1655. //Parse a URL into a structure that allows easy access to
  1656. //all of the URL components by name.
  1657. parseUrl: function( url ) {
  1658. // If we're passed an object, we'll assume that it is
  1659. // a parsed url object and just return it back to the caller.
  1660. if ( $.type( url ) === "object" ) {
  1661. return url;
  1662. }
  1663. var matches = path.urlParseRE.exec( url || "" ) || [];
  1664. // Create an object that allows the caller to access the sub-matches
  1665. // by name. Note that IE returns an empty string instead of undefined,
  1666. // like all other browsers do, so we normalize everything so its consistent
  1667. // no matter what browser we're running on.
  1668. return {
  1669. href: matches[ 0 ] || "",
  1670. hrefNoHash: matches[ 1 ] || "",
  1671. hrefNoSearch: matches[ 2 ] || "",
  1672. domain: matches[ 3 ] || "",
  1673. protocol: matches[ 4 ] || "",
  1674. doubleSlash: matches[ 5 ] || "",
  1675. authority: matches[ 6 ] || "",
  1676. username: matches[ 8 ] || "",
  1677. password: matches[ 9 ] || "",
  1678. host: matches[ 10 ] || "",
  1679. hostname: matches[ 11 ] || "",
  1680. port: matches[ 12 ] || "",
  1681. pathname: matches[ 13 ] || "",
  1682. directory: matches[ 14 ] || "",
  1683. filename: matches[ 15 ] || "",
  1684. search: matches[ 16 ] || "",
  1685. hash: matches[ 17 ] || ""
  1686. };
  1687. },
  1688. //Turn relPath into an asbolute path. absPath is
  1689. //an optional absolute path which describes what
  1690. //relPath is relative to.
  1691. makePathAbsolute: function( relPath, absPath ) {
  1692. if ( relPath && relPath.charAt( 0 ) === "/" ) {
  1693. return relPath;
  1694. }
  1695. relPath = relPath || "";
  1696. absPath = absPath ? absPath.replace( /^\/|(\/[^\/]*|[^\/]+)$/g, "" ) : "";
  1697. var absStack = absPath ? absPath.split( "/" ) : [],
  1698. relStack = relPath.split( "/" );
  1699. for ( var i = 0; i < relStack.length; i++ ) {
  1700. var d = relStack[ i ];
  1701. switch ( d ) {
  1702. case ".":
  1703. break;
  1704. case "..":
  1705. if ( absStack.length ) {
  1706. absStack.pop();
  1707. }
  1708. break;
  1709. default:
  1710. absStack.push( d );
  1711. break;
  1712. }
  1713. }
  1714. return "/" + absStack.join( "/" );
  1715. },
  1716. //Returns true if both urls have the same domain.
  1717. isSameDomain: function( absUrl1, absUrl2 ) {
  1718. return path.parseUrl( absUrl1 ).domain === path.parseUrl( absUrl2 ).domain;
  1719. },
  1720. //Returns true for any relative variant.
  1721. isRelativeUrl: function( url ) {
  1722. // All relative Url variants have one thing in common, no protocol.
  1723. return path.parseUrl( url ).protocol === "";
  1724. },
  1725. //Returns true for an absolute url.
  1726. isAbsoluteUrl: function( url ) {
  1727. return path.parseUrl( url ).protocol !== "";
  1728. },
  1729. //Turn the specified realtive URL into an absolute one. This function
  1730. //can handle all relative variants (protocol, site, document, query, fragment).
  1731. makeUrlAbsolute: function( relUrl, absUrl ) {
  1732. if ( !path.isRelativeUrl( relUrl ) ) {
  1733. return relUrl;
  1734. }
  1735. if ( absUrl === undefined ) {
  1736. absUrl = this.documentBase;
  1737. }
  1738. var relObj = path.parseUrl( relUrl ),
  1739. absObj = path.parseUrl( absUrl ),
  1740. protocol = relObj.protocol || absObj.protocol,
  1741. doubleSlash = relObj.protocol ? relObj.doubleSlash : ( relObj.doubleSlash || absObj.doubleSlash ),
  1742. authority = relObj.authority || absObj.authority,
  1743. hasPath = relObj.pathname !== "",
  1744. pathname = path.makePathAbsolute( relObj.pathname || absObj.filename, absObj.pathname ),
  1745. search = relObj.search || ( !hasPath && absObj.search ) || "",
  1746. hash = relObj.hash;
  1747. return protocol + doubleSlash + authority + pathname + search + hash;
  1748. },
  1749. //Add search (aka query) params to the specified url.
  1750. addSearchParams: function( url, params ) {
  1751. var u = path.parseUrl( url ),
  1752. p = ( typeof params === "object" ) ? $.param( params ) : params,
  1753. s = u.search || "?";
  1754. return u.hrefNoSearch + s + ( s.charAt( s.length - 1 ) !== "?" ? "&" : "" ) + p + ( u.hash || "" );
  1755. },
  1756. convertUrlToDataUrl: function( absUrl ) {
  1757. var u = path.parseUrl( absUrl );
  1758. if ( path.isEmbeddedPage( u ) ) {
  1759. // For embedded pages, remove the dialog hash key as in getFilePath(),
  1760. // and remove otherwise the Data Url won't match the id of the embedded Page.
  1761. return u.hash
  1762. .split( dialogHashKey )[0]
  1763. .replace( /^#/, "" )
  1764. .replace( /\?.*$/, "" );
  1765. } else if ( path.isSameDomain( u, this.documentBase ) ) {
  1766. return u.hrefNoHash.replace( this.documentBase.domain, "" ).split( dialogHashKey )[0];
  1767. }
  1768. return window.decodeURIComponent(absUrl);
  1769. },
  1770. //get path from current hash, or from a file path
  1771. get: function( newPath ) {
  1772. if ( newPath === undefined ) {
  1773. newPath = path.parseLocation().hash;
  1774. }
  1775. return path.stripHash( newPath ).replace( /[^\/]*\.[^\/*]+$/, '' );
  1776. },
  1777. //set location hash to path
  1778. set: function( path ) {
  1779. location.hash = path;
  1780. },
  1781. //test if a given url (string) is a path
  1782. //NOTE might be exceptionally naive
  1783. isPath: function( url ) {
  1784. return ( /\// ).test( url );
  1785. },
  1786. //return a url path with the window's location protocol/hostname/pathname removed
  1787. clean: function( url ) {
  1788. return url.replace( this.documentBase.domain, "" );
  1789. },
  1790. //just return the url without an initial #
  1791. stripHash: function( url ) {
  1792. return url.replace( /^#/, "" );
  1793. },
  1794. stripQueryParams: function( url ) {
  1795. return url.replace( /\?.*$/, "" );
  1796. },
  1797. //remove the preceding hash, any query params, and dialog notations
  1798. cleanHash: function( hash ) {
  1799. return path.stripHash( hash.replace( /\?.*$/, "" ).replace( dialogHashKey, "" ) );
  1800. },
  1801. isHashValid: function( hash ) {
  1802. return ( /^#[^#]+$/ ).test( hash );
  1803. },
  1804. //check whether a url is referencing the same domain, or an external domain or different protocol
  1805. //could be mailto, etc
  1806. isExternal: function( url ) {
  1807. var u = path.parseUrl( url );
  1808. return u.protocol && u.domain !== this.documentUrl.domain ? true : false;
  1809. },
  1810. hasProtocol: function( url ) {
  1811. return ( /^(:?\w+:)/ ).test( url );
  1812. },
  1813. isEmbeddedPage: function( url ) {
  1814. var u = path.parseUrl( url );
  1815. //if the path is absolute, then we need to compare the url against
  1816. //both the this.documentUrl and the documentBase. The main reason for this
  1817. //is that links embedded within external documents will refer to the
  1818. //application document, whereas links embedded within the application
  1819. //document will be resolved against the document base.
  1820. if ( u.protocol !== "" ) {
  1821. return ( !this.isPath(u.hash) && u.hash && ( u.hrefNoHash === this.documentUrl.hrefNoHash || ( this.documentBaseDiffers && u.hrefNoHash === this.documentBase.hrefNoHash ) ) );
  1822. }
  1823. return ( /^#/ ).test( u.href );
  1824. },
  1825. squash: function( url, resolutionUrl ) {
  1826. var state, href, cleanedUrl, search, stateIndex,
  1827. isPath = this.isPath( url ),
  1828. uri = this.parseUrl( url ),
  1829. preservedHash = uri.hash,
  1830. uiState = "";
  1831. // produce a url against which we can resole the provided path
  1832. resolutionUrl = resolutionUrl || (path.isPath(url) ? path.getLocation() : path.getDocumentUrl());
  1833. // If the url is anything but a simple string, remove any preceding hash
  1834. // eg #foo/bar -> foo/bar
  1835. // #foo -> #foo
  1836. cleanedUrl = isPath ? path.stripHash( url ) : url;
  1837. // If the url is a full url with a hash check if the parsed hash is a path
  1838. // if it is, strip the #, and use it otherwise continue without change
  1839. cleanedUrl = path.isPath( uri.hash ) ? path.stripHash( uri.hash ) : cleanedUrl;
  1840. // Split the UI State keys off the href
  1841. stateIndex = cleanedUrl.indexOf( this.uiStateKey );
  1842. // store the ui state keys for use
  1843. if( stateIndex > -1 ){
  1844. uiState = cleanedUrl.slice( stateIndex );
  1845. cleanedUrl = cleanedUrl.slice( 0, stateIndex );
  1846. }
  1847. // make the cleanedUrl absolute relative to the resolution url
  1848. href = path.makeUrlAbsolute( cleanedUrl, resolutionUrl );
  1849. // grab the search from the resolved url since parsing from
  1850. // the passed url may not yield the correct result
  1851. search = this.parseUrl( href ).search;
  1852. // TODO all this crap is terrible, clean it up
  1853. if ( isPath ) {
  1854. // reject the hash if it's a path or it's just a dialog key
  1855. if( path.isPath( preservedHash ) || preservedHash.replace("#", "").indexOf( this.uiStateKey ) === 0) {
  1856. preservedHash = "";
  1857. }
  1858. // Append the UI State keys where it exists and it's been removed
  1859. // from the url
  1860. if( uiState && preservedHash.indexOf( this.uiStateKey ) === -1){
  1861. preservedHash += uiState;
  1862. }
  1863. // make sure that pound is on the front of the hash
  1864. if( preservedHash.indexOf( "#" ) === -1 && preservedHash !== "" ){
  1865. preservedHash = "#" + preservedHash;
  1866. }
  1867. // reconstruct each of the pieces with the new search string and hash
  1868. href = path.parseUrl( href );
  1869. href = href.protocol + "//" + href.host + href.pathname + search + preservedHash;
  1870. } else {
  1871. href += href.indexOf( "#" ) > -1 ? uiState : "#" + uiState;
  1872. }
  1873. return href;
  1874. },
  1875. isPreservableHash: function( hash ) {
  1876. return hash.replace( "#", "" ).indexOf( this.uiStateKey ) === 0;
  1877. }
  1878. };
  1879. path.documentUrl = path.parseLocation();
  1880. $base = $( "head" ).find( "base" );
  1881. path.documentBase = $base.length ?
  1882. path.parseUrl( path.makeUrlAbsolute( $base.attr( "href" ), path.documentUrl.href ) ) :
  1883. path.documentUrl;
  1884. path.documentBaseDiffers = (path.documentUrl.hrefNoHash !== path.documentBase.hrefNoHash);
  1885. //return the original document url
  1886. path.getDocumentUrl = function( asParsedObject ) {
  1887. return asParsedObject ? $.extend( {}, path.documentUrl ) : path.documentUrl.href;
  1888. };
  1889. //return the original document base url
  1890. path.getDocumentBase = function( asParsedObject ) {
  1891. return asParsedObject ? $.extend( {}, path.documentBase ) : path.documentBase.href;
  1892. };
  1893. })( jQuery );
  1894. (function( $, undefined ) {
  1895. var path = $.mobile.path;
  1896. $.mobile.History = function( stack, index ) {
  1897. this.stack = stack || [];
  1898. this.activeIndex = index || 0;
  1899. };
  1900. $.extend($.mobile.History.prototype, {
  1901. getActive: function() {
  1902. return this.stack[ this.activeIndex ];
  1903. },
  1904. getLast: function() {
  1905. return this.stack[ this.previousIndex ];
  1906. },
  1907. getNext: function() {
  1908. return this.stack[ this.activeIndex + 1 ];
  1909. },
  1910. getPrev: function() {
  1911. return this.stack[ this.activeIndex - 1 ];
  1912. },
  1913. // addNew is used whenever a new page is added
  1914. add: function( url, data ){
  1915. data = data || {};
  1916. //if there's forward history, wipe it
  1917. if ( this.getNext() ) {
  1918. this.clearForward();
  1919. }
  1920. // if the hash is included in the data make sure the shape
  1921. // is consistent for comparison
  1922. if( data.hash && data.hash.indexOf( "#" ) === -1) {
  1923. data.hash = "#" + data.hash;
  1924. }
  1925. data.url = url;
  1926. this.stack.push( data );
  1927. this.activeIndex = this.stack.length - 1;
  1928. },
  1929. //wipe urls ahead of active index
  1930. clearForward: function() {
  1931. this.stack = this.stack.slice( 0, this.activeIndex + 1 );
  1932. },
  1933. find: function( url, stack, earlyReturn ) {
  1934. stack = stack || this.stack;
  1935. var entry, i, length = stack.length, index;
  1936. for ( i = 0; i < length; i++ ) {
  1937. entry = stack[i];
  1938. if ( decodeURIComponent(url) === decodeURIComponent(entry.url) ||
  1939. decodeURIComponent(url) === decodeURIComponent(entry.hash) ) {
  1940. index = i;
  1941. if( earlyReturn ) {
  1942. return index;
  1943. }
  1944. }
  1945. }
  1946. return index;
  1947. },
  1948. closest: function( url ) {
  1949. var closest, a = this.activeIndex;
  1950. // First, take the slice of the history stack before the current index and search
  1951. // for a url match. If one is found, we'll avoid avoid looking through forward history
  1952. // NOTE the preference for backward history movement is driven by the fact that
  1953. // most mobile browsers only have a dedicated back button, and users rarely use
  1954. // the forward button in desktop browser anyhow
  1955. closest = this.find( url, this.stack.slice(0, a) );
  1956. // If nothing was found in backward history check forward. The `true`
  1957. // value passed as the third parameter causes the find method to break
  1958. // on the first match in the forward history slice. The starting index
  1959. // of the slice must then be added to the result to get the element index
  1960. // in the original history stack :( :(
  1961. //
  1962. // TODO this is hyper confusing and should be cleaned up (ugh so bad)
  1963. if( closest === undefined ) {
  1964. closest = this.find( url, this.stack.slice(a), true );
  1965. closest = closest === undefined ? closest : closest + a;
  1966. }
  1967. return closest;
  1968. },
  1969. direct: function( opts ) {
  1970. var newActiveIndex = this.closest( opts.url ), a = this.activeIndex;
  1971. // save new page index, null check to prevent falsey 0 result
  1972. // record the previous index for reference
  1973. if( newActiveIndex !== undefined ) {
  1974. this.activeIndex = newActiveIndex;
  1975. this.previousIndex = a;
  1976. }
  1977. // invoke callbacks where appropriate
  1978. //
  1979. // TODO this is also convoluted and confusing
  1980. if ( newActiveIndex < a ) {
  1981. ( opts.present || opts.back || $.noop )( this.getActive(), 'back' );
  1982. } else if ( newActiveIndex > a ) {
  1983. ( opts.present || opts.forward || $.noop )( this.getActive(), 'forward' );
  1984. } else if ( newActiveIndex === undefined && opts.missing ){
  1985. opts.missing( this.getActive() );
  1986. }
  1987. }
  1988. });
  1989. })( jQuery );
  1990. (function( $, undefined ) {
  1991. var path = $.mobile.path,
  1992. initialHref = location.href;
  1993. $.mobile.Navigator = function( history ) {
  1994. this.history = history;
  1995. this.ignoreInitialHashChange = true;
  1996. $.mobile.window.bind({
  1997. "popstate.history": $.proxy( this.popstate, this ),
  1998. "hashchange.history": $.proxy( this.hashchange, this )
  1999. });
  2000. };
  2001. $.extend($.mobile.Navigator.prototype, {
  2002. squash: function( url, data ) {
  2003. var state, href, hash = path.isPath(url) ? path.stripHash(url) : url;
  2004. href = path.squash( url );
  2005. // make sure to provide this information when it isn't explicitly set in the
  2006. // data object that was passed to the squash method
  2007. state = $.extend({
  2008. hash: hash,
  2009. url: href
  2010. }, data);
  2011. // replace the current url with the new href and store the state
  2012. // Note that in some cases we might be replacing an url with the
  2013. // same url. We do this anyways because we need to make sure that
  2014. // all of our history entries have a state object associated with
  2015. // them. This allows us to work around the case where $.mobile.back()
  2016. // is called to transition from an external page to an embedded page.
  2017. // In that particular case, a hashchange event is *NOT* generated by the browser.
  2018. // Ensuring each history entry has a state object means that onPopState()
  2019. // will always trigger our hashchange callback even when a hashchange event
  2020. // is not fired.
  2021. window.history.replaceState( state, state.title || document.title, href );
  2022. return state;
  2023. },
  2024. hash: function( url, href ) {
  2025. var parsed, loc, hash;
  2026. // Grab the hash for recording. If the passed url is a path
  2027. // we used the parsed version of the squashed url to reconstruct,
  2028. // otherwise we assume it's a hash and store it directly
  2029. parsed = path.parseUrl( url );
  2030. loc = path.parseLocation();
  2031. if( loc.pathname + loc.search === parsed.pathname + parsed.search ) {
  2032. // If the pathname and search of the passed url is identical to the current loc
  2033. // then we must use the hash. Otherwise there will be no event
  2034. // eg, url = "/foo/bar?baz#bang", location.href = "http://example.com/foo/bar?baz"
  2035. hash = parsed.hash ? parsed.hash : parsed.pathname + parsed.search;
  2036. } else if ( path.isPath(url) ) {
  2037. var resolved = path.parseUrl( href );
  2038. // If the passed url is a path, make it domain relative and remove any trailing hash
  2039. hash = resolved.pathname + resolved.search + (path.isPreservableHash( resolved.hash )? resolved.hash.replace( "#", "" ) : "");
  2040. } else {
  2041. hash = url;
  2042. }
  2043. return hash;
  2044. },
  2045. // TODO reconsider name
  2046. go: function( url, data, noEvents ) {
  2047. var state, href, hash, popstateEvent,
  2048. isPopStateEvent = $.event.special.navigate.isPushStateEnabled();
  2049. // Get the url as it would look squashed on to the current resolution url
  2050. href = path.squash( url );
  2051. // sort out what the hash sould be from the url
  2052. hash = this.hash( url, href );
  2053. // Here we prevent the next hash change or popstate event from doing any
  2054. // history management. In the case of hashchange we don't swallow it
  2055. // if there will be no hashchange fired (since that won't reset the value)
  2056. // and will swallow the following hashchange
  2057. if( noEvents && hash !== path.stripHash(path.parseLocation().hash) ) {
  2058. this.preventNextHashChange = noEvents;
  2059. }
  2060. // IMPORTANT in the case where popstate is supported the event will be triggered
  2061. // directly, stopping further execution - ie, interupting the flow of this
  2062. // method call to fire bindings at this expression. Below the navigate method
  2063. // there is a binding to catch this event and stop its propagation.
  2064. //
  2065. // We then trigger a new popstate event on the window with a null state
  2066. // so that the navigate events can conclude their work properly
  2067. //
  2068. // if the url is a path we want to preserve the query params that are available on
  2069. // the current url.
  2070. this.preventHashAssignPopState = true;
  2071. window.location.hash = hash;
  2072. // If popstate is enabled and the browser triggers `popstate` events when the hash
  2073. // is set (this often happens immediately in browsers like Chrome), then the
  2074. // this flag will be set to false already. If it's a browser that does not trigger
  2075. // a `popstate` on hash assignement or `replaceState` then we need avoid the branch
  2076. // that swallows the event created by the popstate generated by the hash assignment
  2077. // At the time of this writing this happens with Opera 12 and some version of IE
  2078. this.preventHashAssignPopState = false;
  2079. state = $.extend({
  2080. url: href,
  2081. hash: hash,
  2082. title: document.title
  2083. }, data);
  2084. if( isPopStateEvent ) {
  2085. popstateEvent = new $.Event( "popstate" );
  2086. popstateEvent.originalEvent = {
  2087. type: "popstate",
  2088. state: null
  2089. };
  2090. this.squash( url, state );
  2091. // Trigger a new faux popstate event to replace the one that we
  2092. // caught that was triggered by the hash setting above.
  2093. if( !noEvents ) {
  2094. this.ignorePopState = true;
  2095. $.mobile.window.trigger( popstateEvent );
  2096. }
  2097. }
  2098. // record the history entry so that the information can be included
  2099. // in hashchange event driven navigate events in a similar fashion to
  2100. // the state that's provided by popstate
  2101. this.history.add( state.url, state );
  2102. },
  2103. // This binding is intended to catch the popstate events that are fired
  2104. // when execution of the `$.navigate` method stops at window.location.hash = url;
  2105. // and completely prevent them from propagating. The popstate event will then be
  2106. // retriggered after execution resumes
  2107. //
  2108. // TODO grab the original event here and use it for the synthetic event in the
  2109. // second half of the navigate execution that will follow this binding
  2110. popstate: function( event ) {
  2111. var active, hash, state, closestIndex;
  2112. // Partly to support our test suite which manually alters the support
  2113. // value to test hashchange. Partly to prevent all around weirdness
  2114. if( !$.event.special.navigate.isPushStateEnabled() ){
  2115. return;
  2116. }
  2117. // If this is the popstate triggered by the actual alteration of the hash
  2118. // prevent it completely. History is tracked manually
  2119. if( this.preventHashAssignPopState ) {
  2120. this.preventHashAssignPopState = false;
  2121. event.stopImmediatePropagation();
  2122. return;
  2123. }
  2124. // if this is the popstate triggered after the `replaceState` call in the go
  2125. // method, then simply ignore it. The history entry has already been captured
  2126. if( this.ignorePopState ) {
  2127. this.ignorePopState = false;
  2128. return;
  2129. }
  2130. // If there is no state, and the history stack length is one were
  2131. // probably getting the page load popstate fired by browsers like chrome
  2132. // avoid it and set the one time flag to false.
  2133. // TODO: Do we really need all these conditions? Comparing location hrefs
  2134. // should be sufficient.
  2135. if( !event.originalEvent.state &&
  2136. this.history.stack.length === 1 &&
  2137. this.ignoreInitialHashChange ) {
  2138. this.ignoreInitialHashChange = false;
  2139. if ( location.href === initialHref ) {
  2140. event.preventDefault();
  2141. return;
  2142. }
  2143. }
  2144. // account for direct manipulation of the hash. That is, we will receive a popstate
  2145. // when the hash is changed by assignment, and it won't have a state associated. We
  2146. // then need to squash the hash. See below for handling of hash assignment that
  2147. // matches an existing history entry
  2148. // TODO it might be better to only add to the history stack
  2149. // when the hash is adjacent to the active history entry
  2150. hash = path.parseLocation().hash;
  2151. if( !event.originalEvent.state && hash ) {
  2152. // squash the hash that's been assigned on the URL with replaceState
  2153. // also grab the resulting state object for storage
  2154. state = this.squash( hash );
  2155. // record the new hash as an additional history entry
  2156. // to match the browser's treatment of hash assignment
  2157. this.history.add( state.url, state );
  2158. // pass the newly created state information
  2159. // along with the event
  2160. event.historyState = state;
  2161. // do not alter history, we've added a new history entry
  2162. // so we know where we are
  2163. return;
  2164. }
  2165. // If all else fails this is a popstate that comes from the back or forward buttons
  2166. // make sure to set the state of our history stack properly, and record the directionality
  2167. this.history.direct({
  2168. url: (event.originalEvent.state || {}).url || hash,
  2169. // When the url is either forward or backward in history include the entry
  2170. // as data on the event object for merging as data in the navigate event
  2171. present: function( historyEntry, direction ) {
  2172. // make sure to create a new object to pass down as the navigate event data
  2173. event.historyState = $.extend({}, historyEntry);
  2174. event.historyState.direction = direction;
  2175. }
  2176. });
  2177. },
  2178. // NOTE must bind before `navigate` special event hashchange binding otherwise the
  2179. // navigation data won't be attached to the hashchange event in time for those
  2180. // bindings to attach it to the `navigate` special event
  2181. // TODO add a check here that `hashchange.navigate` is bound already otherwise it's
  2182. // broken (exception?)
  2183. hashchange: function( event ) {
  2184. var history, hash;
  2185. // If hashchange listening is explicitly disabled or pushstate is supported
  2186. // avoid making use of the hashchange handler.
  2187. if(!$.event.special.navigate.isHashChangeEnabled() ||
  2188. $.event.special.navigate.isPushStateEnabled() ) {
  2189. return;
  2190. }
  2191. // On occasion explicitly want to prevent the next hash from propogating because we only
  2192. // with to alter the url to represent the new state do so here
  2193. if( this.preventNextHashChange ){
  2194. this.preventNextHashChange = false;
  2195. event.stopImmediatePropagation();
  2196. return;
  2197. }
  2198. history = this.history;
  2199. hash = path.parseLocation().hash;
  2200. // If this is a hashchange caused by the back or forward button
  2201. // make sure to set the state of our history stack properly
  2202. this.history.direct({
  2203. url: hash,
  2204. // When the url is either forward or backward in history include the entry
  2205. // as data on the event object for merging as data in the navigate event
  2206. present: function( historyEntry, direction ) {
  2207. // make sure to create a new object to pass down as the navigate event data
  2208. event.hashchangeState = $.extend({}, historyEntry);
  2209. event.hashchangeState.direction = direction;
  2210. },
  2211. // When we don't find a hash in our history clearly we're aiming to go there
  2212. // record the entry as new for future traversal
  2213. //
  2214. // NOTE it's not entirely clear that this is the right thing to do given that we
  2215. // can't know the users intention. It might be better to explicitly _not_
  2216. // support location.hash assignment in preference to $.navigate calls
  2217. // TODO first arg to add should be the href, but it causes issues in identifying
  2218. // embeded pages
  2219. missing: function() {
  2220. history.add( hash, {
  2221. hash: hash,
  2222. title: document.title
  2223. });
  2224. }
  2225. });
  2226. }
  2227. });
  2228. })( jQuery );
  2229. (function( $, undefined ) {
  2230. // TODO consider queueing navigation activity until previous activities have completed
  2231. // so that end users don't have to think about it. Punting for now
  2232. // TODO !! move the event bindings into callbacks on the navigate event
  2233. $.mobile.navigate = function( url, data, noEvents ) {
  2234. $.mobile.navigate.navigator.go( url, data, noEvents );
  2235. };
  2236. // expose the history on the navigate method in anticipation of full integration with
  2237. // existing navigation functionalty that is tightly coupled to the history information
  2238. $.mobile.navigate.history = new $.mobile.History();
  2239. // instantiate an instance of the navigator for use within the $.navigate method
  2240. $.mobile.navigate.navigator = new $.mobile.Navigator( $.mobile.navigate.history );
  2241. var loc = $.mobile.path.parseLocation();
  2242. $.mobile.navigate.history.add( loc.href, {hash: loc.hash} );
  2243. })( jQuery );
  2244. // This plugin is an experiment for abstracting away the touch and mouse
  2245. // events so that developers don't have to worry about which method of input
  2246. // the device their document is loaded on supports.
  2247. //
  2248. // The idea here is to allow the developer to register listeners for the
  2249. // basic mouse events, such as mousedown, mousemove, mouseup, and click,
  2250. // and the plugin will take care of registering the correct listeners
  2251. // behind the scenes to invoke the listener at the fastest possible time
  2252. // for that device, while still retaining the order of event firing in
  2253. // the traditional mouse environment, should multiple handlers be registered
  2254. // on the same element for different events.
  2255. //
  2256. // The current version exposes the following virtual events to jQuery bind methods:
  2257. // "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel"
  2258. (function( $, window, document, undefined ) {
  2259. var dataPropertyName = "virtualMouseBindings",
  2260. touchTargetPropertyName = "virtualTouchID",
  2261. virtualEventNames = "vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split( " " ),
  2262. touchEventProps = "clientX clientY pageX pageY screenX screenY".split( " " ),
  2263. mouseHookProps = $.event.mouseHooks ? $.event.mouseHooks.props : [],
  2264. mouseEventProps = $.event.props.concat( mouseHookProps ),
  2265. activeDocHandlers = {},
  2266. resetTimerID = 0,
  2267. startX = 0,
  2268. startY = 0,
  2269. didScroll = false,
  2270. clickBlockList = [],
  2271. blockMouseTriggers = false,
  2272. blockTouchTriggers = false,
  2273. eventCaptureSupported = "addEventListener" in document,
  2274. $document = $( document ),
  2275. nextTouchID = 1,
  2276. lastTouchID = 0, threshold;
  2277. $.vmouse = {
  2278. moveDistanceThreshold: 10,
  2279. clickDistanceThreshold: 10,
  2280. resetTimerDuration: 1500
  2281. };
  2282. function getNativeEvent( event ) {
  2283. while ( event && typeof event.originalEvent !== "undefined" ) {
  2284. event = event.originalEvent;
  2285. }
  2286. return event;
  2287. }
  2288. function createVirtualEvent( event, eventType ) {
  2289. var t = event.type,
  2290. oe, props, ne, prop, ct, touch, i, j, len;
  2291. event = $.Event( event );
  2292. event.type = eventType;
  2293. oe = event.originalEvent;
  2294. props = $.event.props;
  2295. // addresses separation of $.event.props in to $.event.mouseHook.props and Issue 3280
  2296. // https://github.com/jquery/jquery-mobile/issues/3280
  2297. if ( t.search( /^(mouse|click)/ ) > -1 ) {
  2298. props = mouseEventProps;
  2299. }
  2300. // copy original event properties over to the new event
  2301. // this would happen if we could call $.event.fix instead of $.Event
  2302. // but we don't have a way to force an event to be fixed multiple times
  2303. if ( oe ) {
  2304. for ( i = props.length, prop; i; ) {
  2305. prop = props[ --i ];
  2306. event[ prop ] = oe[ prop ];
  2307. }
  2308. }
  2309. // make sure that if the mouse and click virtual events are generated
  2310. // without a .which one is defined
  2311. if ( t.search(/mouse(down|up)|click/) > -1 && !event.which ) {
  2312. event.which = 1;
  2313. }
  2314. if ( t.search(/^touch/) !== -1 ) {
  2315. ne = getNativeEvent( oe );
  2316. t = ne.touches;
  2317. ct = ne.changedTouches;
  2318. touch = ( t && t.length ) ? t[0] : ( ( ct && ct.length ) ? ct[ 0 ] : undefined );
  2319. if ( touch ) {
  2320. for ( j = 0, len = touchEventProps.length; j < len; j++) {
  2321. prop = touchEventProps[ j ];
  2322. event[ prop ] = touch[ prop ];
  2323. }
  2324. }
  2325. }
  2326. return event;
  2327. }
  2328. function getVirtualBindingFlags( element ) {
  2329. var flags = {},
  2330. b, k;
  2331. while ( element ) {
  2332. b = $.data( element, dataPropertyName );
  2333. for ( k in b ) {
  2334. if ( b[ k ] ) {
  2335. flags[ k ] = flags.hasVirtualBinding = true;
  2336. }
  2337. }
  2338. element = element.parentNode;
  2339. }
  2340. return flags;
  2341. }
  2342. function getClosestElementWithVirtualBinding( element, eventType ) {
  2343. var b;
  2344. while ( element ) {
  2345. b = $.data( element, dataPropertyName );
  2346. if ( b && ( !eventType || b[ eventType ] ) ) {
  2347. return element;
  2348. }
  2349. element = element.parentNode;
  2350. }
  2351. return null;
  2352. }
  2353. function enableTouchBindings() {
  2354. blockTouchTriggers = false;
  2355. }
  2356. function disableTouchBindings() {
  2357. blockTouchTriggers = true;
  2358. }
  2359. function enableMouseBindings() {
  2360. lastTouchID = 0;
  2361. clickBlockList.length = 0;
  2362. blockMouseTriggers = false;
  2363. // When mouse bindings are enabled, our
  2364. // touch bindings are disabled.
  2365. disableTouchBindings();
  2366. }
  2367. function disableMouseBindings() {
  2368. // When mouse bindings are disabled, our
  2369. // touch bindings are enabled.
  2370. enableTouchBindings();
  2371. }
  2372. function startResetTimer() {
  2373. clearResetTimer();
  2374. resetTimerID = setTimeout( function() {
  2375. resetTimerID = 0;
  2376. enableMouseBindings();
  2377. }, $.vmouse.resetTimerDuration );
  2378. }
  2379. function clearResetTimer() {
  2380. if ( resetTimerID ) {
  2381. clearTimeout( resetTimerID );
  2382. resetTimerID = 0;
  2383. }
  2384. }
  2385. function triggerVirtualEvent( eventType, event, flags ) {
  2386. var ve;
  2387. if ( ( flags && flags[ eventType ] ) ||
  2388. ( !flags && getClosestElementWithVirtualBinding( event.target, eventType ) ) ) {
  2389. ve = createVirtualEvent( event, eventType );
  2390. $( event.target).trigger( ve );
  2391. }
  2392. return ve;
  2393. }
  2394. function mouseEventCallback( event ) {
  2395. var touchID = $.data( event.target, touchTargetPropertyName );
  2396. if ( !blockMouseTriggers && ( !lastTouchID || lastTouchID !== touchID ) ) {
  2397. var ve = triggerVirtualEvent( "v" + event.type, event );
  2398. if ( ve ) {
  2399. if ( ve.isDefaultPrevented() ) {
  2400. event.preventDefault();
  2401. }
  2402. if ( ve.isPropagationStopped() ) {
  2403. event.stopPropagation();
  2404. }
  2405. if ( ve.isImmediatePropagationStopped() ) {
  2406. event.stopImmediatePropagation();
  2407. }
  2408. }
  2409. }
  2410. }
  2411. function handleTouchStart( event ) {
  2412. var touches = getNativeEvent( event ).touches,
  2413. target, flags;
  2414. if ( touches && touches.length === 1 ) {
  2415. target = event.target;
  2416. flags = getVirtualBindingFlags( target );
  2417. if ( flags.hasVirtualBinding ) {
  2418. lastTouchID = nextTouchID++;
  2419. $.data( target, touchTargetPropertyName, lastTouchID );
  2420. clearResetTimer();
  2421. disableMouseBindings();
  2422. didScroll = false;
  2423. var t = getNativeEvent( event ).touches[ 0 ];
  2424. startX = t.pageX;
  2425. startY = t.pageY;
  2426. triggerVirtualEvent( "vmouseover", event, flags );
  2427. triggerVirtualEvent( "vmousedown", event, flags );
  2428. }
  2429. }
  2430. }
  2431. function handleScroll( event ) {
  2432. if ( blockTouchTriggers ) {
  2433. return;
  2434. }
  2435. if ( !didScroll ) {
  2436. triggerVirtualEvent( "vmousecancel", event, getVirtualBindingFlags( event.target ) );
  2437. }
  2438. didScroll = true;
  2439. startResetTimer();
  2440. }
  2441. function handleTouchMove( event ) {
  2442. if ( blockTouchTriggers ) {
  2443. return;
  2444. }
  2445. var t = getNativeEvent( event ).touches[ 0 ],
  2446. didCancel = didScroll,
  2447. moveThreshold = $.vmouse.moveDistanceThreshold,
  2448. flags = getVirtualBindingFlags( event.target );
  2449. didScroll = didScroll ||
  2450. ( Math.abs( t.pageX - startX ) > moveThreshold ||
  2451. Math.abs( t.pageY - startY ) > moveThreshold );
  2452. if ( didScroll && !didCancel ) {
  2453. triggerVirtualEvent( "vmousecancel", event, flags );
  2454. }
  2455. triggerVirtualEvent( "vmousemove", event, flags );
  2456. startResetTimer();
  2457. }
  2458. function handleTouchEnd( event ) {
  2459. if ( blockTouchTriggers ) {
  2460. return;
  2461. }
  2462. disableTouchBindings();
  2463. var flags = getVirtualBindingFlags( event.target ),
  2464. t;
  2465. triggerVirtualEvent( "vmouseup", event, flags );
  2466. if ( !didScroll ) {
  2467. var ve = triggerVirtualEvent( "vclick", event, flags );
  2468. if ( ve && ve.isDefaultPrevented() ) {
  2469. // The target of the mouse events that follow the touchend
  2470. // event don't necessarily match the target used during the
  2471. // touch. This means we need to rely on coordinates for blocking
  2472. // any click that is generated.
  2473. t = getNativeEvent( event ).changedTouches[ 0 ];
  2474. clickBlockList.push({
  2475. touchID: lastTouchID,
  2476. x: t.clientX,
  2477. y: t.clientY
  2478. });
  2479. // Prevent any mouse events that follow from triggering
  2480. // virtual event notifications.
  2481. blockMouseTriggers = true;
  2482. }
  2483. }
  2484. triggerVirtualEvent( "vmouseout", event, flags);
  2485. didScroll = false;
  2486. startResetTimer();
  2487. }
  2488. function hasVirtualBindings( ele ) {
  2489. var bindings = $.data( ele, dataPropertyName ),
  2490. k;
  2491. if ( bindings ) {
  2492. for ( k in bindings ) {
  2493. if ( bindings[ k ] ) {
  2494. return true;
  2495. }
  2496. }
  2497. }
  2498. return false;
  2499. }
  2500. function dummyMouseHandler() {}
  2501. function getSpecialEventObject( eventType ) {
  2502. var realType = eventType.substr( 1 );
  2503. return {
  2504. setup: function( data, namespace ) {
  2505. // If this is the first virtual mouse binding for this element,
  2506. // add a bindings object to its data.
  2507. if ( !hasVirtualBindings( this ) ) {
  2508. $.data( this, dataPropertyName, {} );
  2509. }
  2510. // If setup is called, we know it is the first binding for this
  2511. // eventType, so initialize the count for the eventType to zero.
  2512. var bindings = $.data( this, dataPropertyName );
  2513. bindings[ eventType ] = true;
  2514. // If this is the first virtual mouse event for this type,
  2515. // register a global handler on the document.
  2516. activeDocHandlers[ eventType ] = ( activeDocHandlers[ eventType ] || 0 ) + 1;
  2517. if ( activeDocHandlers[ eventType ] === 1 ) {
  2518. $document.bind( realType, mouseEventCallback );
  2519. }
  2520. // Some browsers, like Opera Mini, won't dispatch mouse/click events
  2521. // for elements unless they actually have handlers registered on them.
  2522. // To get around this, we register dummy handlers on the elements.
  2523. $( this ).bind( realType, dummyMouseHandler );
  2524. // For now, if event capture is not supported, we rely on mouse handlers.
  2525. if ( eventCaptureSupported ) {
  2526. // If this is the first virtual mouse binding for the document,
  2527. // register our touchstart handler on the document.
  2528. activeDocHandlers[ "touchstart" ] = ( activeDocHandlers[ "touchstart" ] || 0) + 1;
  2529. if ( activeDocHandlers[ "touchstart" ] === 1 ) {
  2530. $document.bind( "touchstart", handleTouchStart )
  2531. .bind( "touchend", handleTouchEnd )
  2532. // On touch platforms, touching the screen and then dragging your finger
  2533. // causes the window content to scroll after some distance threshold is
  2534. // exceeded. On these platforms, a scroll prevents a click event from being
  2535. // dispatched, and on some platforms, even the touchend is suppressed. To
  2536. // mimic the suppression of the click event, we need to watch for a scroll
  2537. // event. Unfortunately, some platforms like iOS don't dispatch scroll
  2538. // events until *AFTER* the user lifts their finger (touchend). This means
  2539. // we need to watch both scroll and touchmove events to figure out whether
  2540. // or not a scroll happenens before the touchend event is fired.
  2541. .bind( "touchmove", handleTouchMove )
  2542. .bind( "scroll", handleScroll );
  2543. }
  2544. }
  2545. },
  2546. teardown: function( data, namespace ) {
  2547. // If this is the last virtual binding for this eventType,
  2548. // remove its global handler from the document.
  2549. --activeDocHandlers[ eventType ];
  2550. if ( !activeDocHandlers[ eventType ] ) {
  2551. $document.unbind( realType, mouseEventCallback );
  2552. }
  2553. if ( eventCaptureSupported ) {
  2554. // If this is the last virtual mouse binding in existence,
  2555. // remove our document touchstart listener.
  2556. --activeDocHandlers[ "touchstart" ];
  2557. if ( !activeDocHandlers[ "touchstart" ] ) {
  2558. $document.unbind( "touchstart", handleTouchStart )
  2559. .unbind( "touchmove", handleTouchMove )
  2560. .unbind( "touchend", handleTouchEnd )
  2561. .unbind( "scroll", handleScroll );
  2562. }
  2563. }
  2564. var $this = $( this ),
  2565. bindings = $.data( this, dataPropertyName );
  2566. // teardown may be called when an element was
  2567. // removed from the DOM. If this is the case,
  2568. // jQuery core may have already stripped the element
  2569. // of any data bindings so we need to check it before
  2570. // using it.
  2571. if ( bindings ) {
  2572. bindings[ eventType ] = false;
  2573. }
  2574. // Unregister the dummy event handler.
  2575. $this.unbind( realType, dummyMouseHandler );
  2576. // If this is the last virtual mouse binding on the
  2577. // element, remove the binding data from the element.
  2578. if ( !hasVirtualBindings( this ) ) {
  2579. $this.removeData( dataPropertyName );
  2580. }
  2581. }
  2582. };
  2583. }
  2584. // Expose our custom events to the jQuery bind/unbind mechanism.
  2585. for ( var i = 0; i < virtualEventNames.length; i++ ) {
  2586. $.event.special[ virtualEventNames[ i ] ] = getSpecialEventObject( virtualEventNames[ i ] );
  2587. }
  2588. // Add a capture click handler to block clicks.
  2589. // Note that we require event capture support for this so if the device
  2590. // doesn't support it, we punt for now and rely solely on mouse events.
  2591. if ( eventCaptureSupported ) {
  2592. document.addEventListener( "click", function( e ) {
  2593. var cnt = clickBlockList.length,
  2594. target = e.target,
  2595. x, y, ele, i, o, touchID;
  2596. if ( cnt ) {
  2597. x = e.clientX;
  2598. y = e.clientY;
  2599. threshold = $.vmouse.clickDistanceThreshold;
  2600. // The idea here is to run through the clickBlockList to see if
  2601. // the current click event is in the proximity of one of our
  2602. // vclick events that had preventDefault() called on it. If we find
  2603. // one, then we block the click.
  2604. //
  2605. // Why do we have to rely on proximity?
  2606. //
  2607. // Because the target of the touch event that triggered the vclick
  2608. // can be different from the target of the click event synthesized
  2609. // by the browser. The target of a mouse/click event that is syntehsized
  2610. // from a touch event seems to be implementation specific. For example,
  2611. // some browsers will fire mouse/click events for a link that is near
  2612. // a touch event, even though the target of the touchstart/touchend event
  2613. // says the user touched outside the link. Also, it seems that with most
  2614. // browsers, the target of the mouse/click event is not calculated until the
  2615. // time it is dispatched, so if you replace an element that you touched
  2616. // with another element, the target of the mouse/click will be the new
  2617. // element underneath that point.
  2618. //
  2619. // Aside from proximity, we also check to see if the target and any
  2620. // of its ancestors were the ones that blocked a click. This is necessary
  2621. // because of the strange mouse/click target calculation done in the
  2622. // Android 2.1 browser, where if you click on an element, and there is a
  2623. // mouse/click handler on one of its ancestors, the target will be the
  2624. // innermost child of the touched element, even if that child is no where
  2625. // near the point of touch.
  2626. ele = target;
  2627. while ( ele ) {
  2628. for ( i = 0; i < cnt; i++ ) {
  2629. o = clickBlockList[ i ];
  2630. touchID = 0;
  2631. if ( ( ele === target && Math.abs( o.x - x ) < threshold && Math.abs( o.y - y ) < threshold ) ||
  2632. $.data( ele, touchTargetPropertyName ) === o.touchID ) {
  2633. // XXX: We may want to consider removing matches from the block list
  2634. // instead of waiting for the reset timer to fire.
  2635. e.preventDefault();
  2636. e.stopPropagation();
  2637. return;
  2638. }
  2639. }
  2640. ele = ele.parentNode;
  2641. }
  2642. }
  2643. }, true);
  2644. }
  2645. })( jQuery, window, document );
  2646. (function( $, window, undefined ) {
  2647. var $document = $( document );
  2648. // add new event shortcuts
  2649. $.each( ( "touchstart touchmove touchend " +
  2650. "tap taphold " +
  2651. "swipe swipeleft swiperight " +
  2652. "scrollstart scrollstop" ).split( " " ), function( i, name ) {
  2653. $.fn[ name ] = function( fn ) {
  2654. return fn ? this.bind( name, fn ) : this.trigger( name );
  2655. };
  2656. // jQuery < 1.8
  2657. if ( $.attrFn ) {
  2658. $.attrFn[ name ] = true;
  2659. }
  2660. });
  2661. var supportTouch = $.mobile.support.touch,
  2662. scrollEvent = "touchmove scroll",
  2663. touchStartEvent = supportTouch ? "touchstart" : "mousedown",
  2664. touchStopEvent = supportTouch ? "touchend" : "mouseup",
  2665. touchMoveEvent = supportTouch ? "touchmove" : "mousemove";
  2666. function triggerCustomEvent( obj, eventType, event ) {
  2667. var originalType = event.type;
  2668. event.type = eventType;
  2669. $.event.dispatch.call( obj, event );
  2670. event.type = originalType;
  2671. }
  2672. // also handles scrollstop
  2673. $.event.special.scrollstart = {
  2674. enabled: true,
  2675. setup: function() {
  2676. var thisObject = this,
  2677. $this = $( thisObject ),
  2678. scrolling,
  2679. timer;
  2680. function trigger( event, state ) {
  2681. scrolling = state;
  2682. triggerCustomEvent( thisObject, scrolling ? "scrollstart" : "scrollstop", event );
  2683. }
  2684. // iPhone triggers scroll after a small delay; use touchmove instead
  2685. $this.bind( scrollEvent, function( event ) {
  2686. if ( !$.event.special.scrollstart.enabled ) {
  2687. return;
  2688. }
  2689. if ( !scrolling ) {
  2690. trigger( event, true );
  2691. }
  2692. clearTimeout( timer );
  2693. timer = setTimeout( function() {
  2694. trigger( event, false );
  2695. }, 50 );
  2696. });
  2697. }
  2698. };
  2699. // also handles taphold
  2700. $.event.special.tap = {
  2701. tapholdThreshold: 750,
  2702. setup: function() {
  2703. var thisObject = this,
  2704. $this = $( thisObject );
  2705. $this.bind( "vmousedown", function( event ) {
  2706. if ( event.which && event.which !== 1 ) {
  2707. return false;
  2708. }
  2709. var origTarget = event.target,
  2710. origEvent = event.originalEvent,
  2711. timer;
  2712. function clearTapTimer() {
  2713. clearTimeout( timer );
  2714. }
  2715. function clearTapHandlers() {
  2716. clearTapTimer();
  2717. $this.unbind( "vclick", clickHandler )
  2718. .unbind( "vmouseup", clearTapTimer );
  2719. $document.unbind( "vmousecancel", clearTapHandlers );
  2720. }
  2721. function clickHandler( event ) {
  2722. clearTapHandlers();
  2723. // ONLY trigger a 'tap' event if the start target is
  2724. // the same as the stop target.
  2725. if ( origTarget === event.target ) {
  2726. triggerCustomEvent( thisObject, "tap", event );
  2727. }
  2728. }
  2729. $this.bind( "vmouseup", clearTapTimer )
  2730. .bind( "vclick", clickHandler );
  2731. $document.bind( "vmousecancel", clearTapHandlers );
  2732. timer = setTimeout( function() {
  2733. triggerCustomEvent( thisObject, "taphold", $.Event( "taphold", { target: origTarget } ) );
  2734. }, $.event.special.tap.tapholdThreshold );
  2735. });
  2736. }
  2737. };
  2738. // also handles swipeleft, swiperight
  2739. $.event.special.swipe = {
  2740. scrollSupressionThreshold: 30, // More than this horizontal displacement, and we will suppress scrolling.
  2741. durationThreshold: 1000, // More time than this, and it isn't a swipe.
  2742. horizontalDistanceThreshold: 30, // Swipe horizontal displacement must be more than this.
  2743. verticalDistanceThreshold: 75, // Swipe vertical displacement must be less than this.
  2744. start: function( event ) {
  2745. var data = event.originalEvent.touches ?
  2746. event.originalEvent.touches[ 0 ] : event;
  2747. return {
  2748. time: ( new Date() ).getTime(),
  2749. coords: [ data.pageX, data.pageY ],
  2750. origin: $( event.target )
  2751. };
  2752. },
  2753. stop: function( event ) {
  2754. var data = event.originalEvent.touches ?
  2755. event.originalEvent.touches[ 0 ] : event;
  2756. return {
  2757. time: ( new Date() ).getTime(),
  2758. coords: [ data.pageX, data.pageY ]
  2759. };
  2760. },
  2761. handleSwipe: function( start, stop ) {
  2762. if ( stop.time - start.time < $.event.special.swipe.durationThreshold &&
  2763. Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.horizontalDistanceThreshold &&
  2764. Math.abs( start.coords[ 1 ] - stop.coords[ 1 ] ) < $.event.special.swipe.verticalDistanceThreshold ) {
  2765. start.origin.trigger( "swipe" )
  2766. .trigger( start.coords[0] > stop.coords[ 0 ] ? "swipeleft" : "swiperight" );
  2767. }
  2768. },
  2769. setup: function() {
  2770. var thisObject = this,
  2771. $this = $( thisObject );
  2772. $this.bind( touchStartEvent, function( event ) {
  2773. var start = $.event.special.swipe.start( event ),
  2774. stop;
  2775. function moveHandler( event ) {
  2776. if ( !start ) {
  2777. return;
  2778. }
  2779. stop = $.event.special.swipe.stop( event );
  2780. // prevent scrolling
  2781. if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.scrollSupressionThreshold ) {
  2782. event.preventDefault();
  2783. }
  2784. }
  2785. $this.bind( touchMoveEvent, moveHandler )
  2786. .one( touchStopEvent, function() {
  2787. $this.unbind( touchMoveEvent, moveHandler );
  2788. if ( start && stop ) {
  2789. $.event.special.swipe.handleSwipe( start, stop );
  2790. }
  2791. start = stop = undefined;
  2792. });
  2793. });
  2794. }
  2795. };
  2796. $.each({
  2797. scrollstop: "scrollstart",
  2798. taphold: "tap",
  2799. swipeleft: "swipe",
  2800. swiperight: "swipe"
  2801. }, function( event, sourceEvent ) {
  2802. $.event.special[ event ] = {
  2803. setup: function() {
  2804. $( this ).bind( sourceEvent, $.noop );
  2805. }
  2806. };
  2807. });
  2808. })( jQuery, this );
  2809. // throttled resize event
  2810. (function( $ ) {
  2811. $.event.special.throttledresize = {
  2812. setup: function() {
  2813. $( this ).bind( "resize", handler );
  2814. },
  2815. teardown: function() {
  2816. $( this ).unbind( "resize", handler );
  2817. }
  2818. };
  2819. var throttle = 250,
  2820. handler = function() {
  2821. curr = ( new Date() ).getTime();
  2822. diff = curr - lastCall;
  2823. if ( diff >= throttle ) {
  2824. lastCall = curr;
  2825. $( this ).trigger( "throttledresize" );
  2826. } else {
  2827. if ( heldCall ) {
  2828. clearTimeout( heldCall );
  2829. }
  2830. // Promise a held call will still execute
  2831. heldCall = setTimeout( handler, throttle - diff );
  2832. }
  2833. },
  2834. lastCall = 0,
  2835. heldCall,
  2836. curr,
  2837. diff;
  2838. })( jQuery );
  2839. (function( $, window ) {
  2840. var win = $( window ),
  2841. event_name = "orientationchange",
  2842. special_event,
  2843. get_orientation,
  2844. last_orientation,
  2845. initial_orientation_is_landscape,
  2846. initial_orientation_is_default,
  2847. portrait_map = { "0": true, "180": true };
  2848. // It seems that some device/browser vendors use window.orientation values 0 and 180 to
  2849. // denote the "default" orientation. For iOS devices, and most other smart-phones tested,
  2850. // the default orientation is always "portrait", but in some Android and RIM based tablets,
  2851. // the default orientation is "landscape". The following code attempts to use the window
  2852. // dimensions to figure out what the current orientation is, and then makes adjustments
  2853. // to the to the portrait_map if necessary, so that we can properly decode the
  2854. // window.orientation value whenever get_orientation() is called.
  2855. //
  2856. // Note that we used to use a media query to figure out what the orientation the browser
  2857. // thinks it is in:
  2858. //
  2859. // initial_orientation_is_landscape = $.mobile.media("all and (orientation: landscape)");
  2860. //
  2861. // but there was an iPhone/iPod Touch bug beginning with iOS 4.2, up through iOS 5.1,
  2862. // where the browser *ALWAYS* applied the landscape media query. This bug does not
  2863. // happen on iPad.
  2864. if ( $.support.orientation ) {
  2865. // Check the window width and height to figure out what the current orientation
  2866. // of the device is at this moment. Note that we've initialized the portrait map
  2867. // values to 0 and 180, *AND* we purposely check for landscape so that if we guess
  2868. // wrong, , we default to the assumption that portrait is the default orientation.
  2869. // We use a threshold check below because on some platforms like iOS, the iPhone
  2870. // form-factor can report a larger width than height if the user turns on the
  2871. // developer console. The actual threshold value is somewhat arbitrary, we just
  2872. // need to make sure it is large enough to exclude the developer console case.
  2873. var ww = window.innerWidth || win.width(),
  2874. wh = window.innerHeight || win.height(),
  2875. landscape_threshold = 50;
  2876. initial_orientation_is_landscape = ww > wh && ( ww - wh ) > landscape_threshold;
  2877. // Now check to see if the current window.orientation is 0 or 180.
  2878. initial_orientation_is_default = portrait_map[ window.orientation ];
  2879. // If the initial orientation is landscape, but window.orientation reports 0 or 180, *OR*
  2880. // if the initial orientation is portrait, but window.orientation reports 90 or -90, we
  2881. // need to flip our portrait_map values because landscape is the default orientation for
  2882. // this device/browser.
  2883. if ( ( initial_orientation_is_landscape && initial_orientation_is_default ) || ( !initial_orientation_is_landscape && !initial_orientation_is_default ) ) {
  2884. portrait_map = { "-90": true, "90": true };
  2885. }
  2886. }
  2887. $.event.special.orientationchange = $.extend( {}, $.event.special.orientationchange, {
  2888. setup: function() {
  2889. // If the event is supported natively, return false so that jQuery
  2890. // will bind to the event using DOM methods.
  2891. if ( $.support.orientation && !$.event.special.orientationchange.disabled ) {
  2892. return false;
  2893. }
  2894. // Get the current orientation to avoid initial double-triggering.
  2895. last_orientation = get_orientation();
  2896. // Because the orientationchange event doesn't exist, simulate the
  2897. // event by testing window dimensions on resize.
  2898. win.bind( "throttledresize", handler );
  2899. },
  2900. teardown: function() {
  2901. // If the event is not supported natively, return false so that
  2902. // jQuery will unbind the event using DOM methods.
  2903. if ( $.support.orientation && !$.event.special.orientationchange.disabled ) {
  2904. return false;
  2905. }
  2906. // Because the orientationchange event doesn't exist, unbind the
  2907. // resize event handler.
  2908. win.unbind( "throttledresize", handler );
  2909. },
  2910. add: function( handleObj ) {
  2911. // Save a reference to the bound event handler.
  2912. var old_handler = handleObj.handler;
  2913. handleObj.handler = function( event ) {
  2914. // Modify event object, adding the .orientation property.
  2915. event.orientation = get_orientation();
  2916. // Call the originally-bound event handler and return its result.
  2917. return old_handler.apply( this, arguments );
  2918. };
  2919. }
  2920. });
  2921. // If the event is not supported natively, this handler will be bound to
  2922. // the window resize event to simulate the orientationchange event.
  2923. function handler() {
  2924. // Get the current orientation.
  2925. var orientation = get_orientation();
  2926. if ( orientation !== last_orientation ) {
  2927. // The orientation has changed, so trigger the orientationchange event.
  2928. last_orientation = orientation;
  2929. win.trigger( event_name );
  2930. }
  2931. }
  2932. // Get the current page orientation. This method is exposed publicly, should it
  2933. // be needed, as jQuery.event.special.orientationchange.orientation()
  2934. $.event.special.orientationchange.orientation = get_orientation = function() {
  2935. var isPortrait = true, elem = document.documentElement;
  2936. // prefer window orientation to the calculation based on screensize as
  2937. // the actual screen resize takes place before or after the orientation change event
  2938. // has been fired depending on implementation (eg android 2.3 is before, iphone after).
  2939. // More testing is required to determine if a more reliable method of determining the new screensize
  2940. // is possible when orientationchange is fired. (eg, use media queries + element + opacity)
  2941. if ( $.support.orientation ) {
  2942. // if the window orientation registers as 0 or 180 degrees report
  2943. // portrait, otherwise landscape
  2944. isPortrait = portrait_map[ window.orientation ];
  2945. } else {
  2946. isPortrait = elem && elem.clientWidth / elem.clientHeight < 1.1;
  2947. }
  2948. return isPortrait ? "portrait" : "landscape";
  2949. };
  2950. $.fn[ event_name ] = function( fn ) {
  2951. return fn ? this.bind( event_name, fn ) : this.trigger( event_name );
  2952. };
  2953. // jQuery < 1.8
  2954. if ( $.attrFn ) {
  2955. $.attrFn[ event_name ] = true;
  2956. }
  2957. }( jQuery, this ));
  2958. (function( $, undefined ) {
  2959. $.widget( "mobile.page", $.mobile.widget, {
  2960. options: {
  2961. theme: "c",
  2962. domCache: false,
  2963. keepNativeDefault: ":jqmData(role='none'), :jqmData(role='nojs')"
  2964. },
  2965. _create: function() {
  2966. // if false is returned by the callbacks do not create the page
  2967. if ( this._trigger( "beforecreate" ) === false ) {
  2968. return false;
  2969. }
  2970. this.element
  2971. .attr( "tabindex", "0" )
  2972. .addClass( "ui-page ui-body-" + this.options.theme );
  2973. this._on( this.element, {
  2974. pagebeforehide: "removeContainerBackground",
  2975. pagebeforeshow: "_handlePageBeforeShow"
  2976. });
  2977. },
  2978. _handlePageBeforeShow: function( e ) {
  2979. this.setContainerBackground();
  2980. },
  2981. removeContainerBackground: function() {
  2982. $.mobile.pageContainer.removeClass( "ui-overlay-" + $.mobile.getInheritedTheme( this.element.parent() ) );
  2983. },
  2984. // set the page container background to the page theme
  2985. setContainerBackground: function( theme ) {
  2986. if ( this.options.theme ) {
  2987. $.mobile.pageContainer.addClass( "ui-overlay-" + ( theme || this.options.theme ) );
  2988. }
  2989. },
  2990. keepNativeSelector: function() {
  2991. var options = this.options,
  2992. keepNativeDefined = options.keepNative && $.trim( options.keepNative );
  2993. if ( keepNativeDefined && options.keepNative !== options.keepNativeDefault ) {
  2994. return [options.keepNative, options.keepNativeDefault].join( ", " );
  2995. }
  2996. return options.keepNativeDefault;
  2997. }
  2998. });
  2999. })( jQuery );
  3000. (function( $, window, undefined ) {
  3001. var createHandler = function( sequential ) {
  3002. // Default to sequential
  3003. if ( sequential === undefined ) {
  3004. sequential = true;
  3005. }
  3006. return function( name, reverse, $to, $from ) {
  3007. var deferred = new $.Deferred(),
  3008. reverseClass = reverse ? " reverse" : "",
  3009. active = $.mobile.urlHistory.getActive(),
  3010. toScroll = active.lastScroll || $.mobile.defaultHomeScroll,
  3011. screenHeight = $.mobile.getScreenHeight(),
  3012. maxTransitionOverride = $.mobile.maxTransitionWidth !== false && $.mobile.window.width() > $.mobile.maxTransitionWidth,
  3013. none = !$.support.cssTransitions || maxTransitionOverride || !name || name === "none" || Math.max( $.mobile.window.scrollTop(), toScroll ) > $.mobile.getMaxScrollForTransition(),
  3014. toPreClass = " ui-page-pre-in",
  3015. toggleViewportClass = function() {
  3016. $.mobile.pageContainer.toggleClass( "ui-mobile-viewport-transitioning viewport-" + name );
  3017. },
  3018. scrollPage = function() {
  3019. // By using scrollTo instead of silentScroll, we can keep things better in order
  3020. // Just to be precautios, disable scrollstart listening like silentScroll would
  3021. $.event.special.scrollstart.enabled = false;
  3022. window.scrollTo( 0, toScroll );
  3023. // reenable scrollstart listening like silentScroll would
  3024. setTimeout( function() {
  3025. $.event.special.scrollstart.enabled = true;
  3026. }, 150 );
  3027. },
  3028. cleanFrom = function() {
  3029. $from
  3030. .removeClass( $.mobile.activePageClass + " out in reverse " + name )
  3031. .height( "" );
  3032. },
  3033. startOut = function() {
  3034. // if it's not sequential, call the doneOut transition to start the TO page animating in simultaneously
  3035. if ( !sequential ) {
  3036. doneOut();
  3037. }
  3038. else {
  3039. $from.animationComplete( doneOut );
  3040. }
  3041. // Set the from page's height and start it transitioning out
  3042. // Note: setting an explicit height helps eliminate tiling in the transitions
  3043. $from
  3044. .height( screenHeight + $.mobile.window.scrollTop() )
  3045. .addClass( name + " out" + reverseClass );
  3046. },
  3047. doneOut = function() {
  3048. if ( $from && sequential ) {
  3049. cleanFrom();
  3050. }
  3051. startIn();
  3052. },
  3053. startIn = function() {
  3054. // Prevent flickering in phonegap container: see comments at #4024 regarding iOS
  3055. $to.css( "z-index", -10 );
  3056. $to.addClass( $.mobile.activePageClass + toPreClass );
  3057. // Send focus to page as it is now display: block
  3058. $.mobile.focusPage( $to );
  3059. // Set to page height
  3060. $to.height( screenHeight + toScroll );
  3061. scrollPage();
  3062. // Restores visibility of the new page: added together with $to.css( "z-index", -10 );
  3063. $to.css( "z-index", "" );
  3064. if ( !none ) {
  3065. $to.animationComplete( doneIn );
  3066. }
  3067. $to
  3068. .removeClass( toPreClass )
  3069. .addClass( name + " in" + reverseClass );
  3070. if ( none ) {
  3071. doneIn();
  3072. }
  3073. },
  3074. doneIn = function() {
  3075. if ( !sequential ) {
  3076. if ( $from ) {
  3077. cleanFrom();
  3078. }
  3079. }
  3080. $to
  3081. .removeClass( "out in reverse " + name )
  3082. .height( "" );
  3083. toggleViewportClass();
  3084. // In some browsers (iOS5), 3D transitions block the ability to scroll to the desired location during transition
  3085. // This ensures we jump to that spot after the fact, if we aren't there already.
  3086. if ( $.mobile.window.scrollTop() !== toScroll ) {
  3087. scrollPage();
  3088. }
  3089. deferred.resolve( name, reverse, $to, $from, true );
  3090. };
  3091. toggleViewportClass();
  3092. if ( $from && !none ) {
  3093. startOut();
  3094. }
  3095. else {
  3096. doneOut();
  3097. }
  3098. return deferred.promise();
  3099. };
  3100. };
  3101. // generate the handlers from the above
  3102. var sequentialHandler = createHandler(),
  3103. simultaneousHandler = createHandler( false ),
  3104. defaultGetMaxScrollForTransition = function() {
  3105. return $.mobile.getScreenHeight() * 3;
  3106. };
  3107. // Make our transition handler the public default.
  3108. $.mobile.defaultTransitionHandler = sequentialHandler;
  3109. //transition handler dictionary for 3rd party transitions
  3110. $.mobile.transitionHandlers = {
  3111. "default": $.mobile.defaultTransitionHandler,
  3112. "sequential": sequentialHandler,
  3113. "simultaneous": simultaneousHandler
  3114. };
  3115. $.mobile.transitionFallbacks = {};
  3116. // If transition is defined, check if css 3D transforms are supported, and if not, if a fallback is specified
  3117. $.mobile._maybeDegradeTransition = function( transition ) {
  3118. if ( transition && !$.support.cssTransform3d && $.mobile.transitionFallbacks[ transition ] ) {
  3119. transition = $.mobile.transitionFallbacks[ transition ];
  3120. }
  3121. return transition;
  3122. };
  3123. // Set the getMaxScrollForTransition to default if no implementation was set by user
  3124. $.mobile.getMaxScrollForTransition = $.mobile.getMaxScrollForTransition || defaultGetMaxScrollForTransition;
  3125. })( jQuery, this );
  3126. (function( $, undefined ) {
  3127. //define vars for interal use
  3128. var $window = $.mobile.window,
  3129. $html = $( 'html' ),
  3130. $head = $( 'head' ),
  3131. // NOTE: path extensions dependent on core attributes. Moved here to remove deps from
  3132. // $.mobile.path definition
  3133. path = $.extend($.mobile.path, {
  3134. //return the substring of a filepath before the sub-page key, for making a server request
  3135. getFilePath: function( path ) {
  3136. var splitkey = '&' + $.mobile.subPageUrlKey;
  3137. return path && path.split( splitkey )[0].split( dialogHashKey )[0];
  3138. },
  3139. //check if the specified url refers to the first page in the main application document.
  3140. isFirstPageUrl: function( url ) {
  3141. // We only deal with absolute paths.
  3142. var u = path.parseUrl( path.makeUrlAbsolute( url, this.documentBase ) ),
  3143. // Does the url have the same path as the document?
  3144. samePath = u.hrefNoHash === this.documentUrl.hrefNoHash || ( this.documentBaseDiffers && u.hrefNoHash === this.documentBase.hrefNoHash ),
  3145. // Get the first page element.
  3146. fp = $.mobile.firstPage,
  3147. // Get the id of the first page element if it has one.
  3148. fpId = fp && fp[0] ? fp[0].id : undefined;
  3149. // The url refers to the first page if the path matches the document and
  3150. // it either has no hash value, or the hash is exactly equal to the id of the
  3151. // first page element.
  3152. return samePath && ( !u.hash || u.hash === "#" || ( fpId && u.hash.replace( /^#/, "" ) === fpId ) );
  3153. },
  3154. // Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR
  3155. // requests if the document doing the request was loaded via the file:// protocol.
  3156. // This is usually to allow the application to "phone home" and fetch app specific
  3157. // data. We normally let the browser handle external/cross-domain urls, but if the
  3158. // allowCrossDomainPages option is true, we will allow cross-domain http/https
  3159. // requests to go through our page loading logic.
  3160. isPermittedCrossDomainRequest: function( docUrl, reqUrl ) {
  3161. return $.mobile.allowCrossDomainPages &&
  3162. docUrl.protocol === "file:" &&
  3163. reqUrl.search( /^https?:/ ) !== -1;
  3164. }
  3165. }),
  3166. // used to track last vclicked element to make sure its value is added to form data
  3167. $lastVClicked = null,
  3168. //will be defined when a link is clicked and given an active class
  3169. $activeClickedLink = null,
  3170. // resolved on domready
  3171. domreadyDeferred = $.Deferred(),
  3172. //urlHistory is purely here to make guesses at whether the back or forward button was clicked
  3173. //and provide an appropriate transition
  3174. urlHistory = $.mobile.navigate.history,
  3175. //define first selector to receive focus when a page is shown
  3176. focusable = "[tabindex],a,button:visible,select:visible,input",
  3177. //queue to hold simultanious page transitions
  3178. pageTransitionQueue = [],
  3179. //indicates whether or not page is in process of transitioning
  3180. isPageTransitioning = false,
  3181. //nonsense hash change key for dialogs, so they create a history entry
  3182. dialogHashKey = "&ui-state=dialog",
  3183. //existing base tag?
  3184. $base = $head.children( "base" ),
  3185. //tuck away the original document URL minus any fragment.
  3186. documentUrl = path.documentUrl,
  3187. //if the document has an embedded base tag, documentBase is set to its
  3188. //initial value. If a base tag does not exist, then we default to the documentUrl.
  3189. documentBase = path.documentBase,
  3190. //cache the comparison once.
  3191. documentBaseDiffers = path.documentBaseDiffers,
  3192. getScreenHeight = $.mobile.getScreenHeight;
  3193. //base element management, defined depending on dynamic base tag support
  3194. var base = $.support.dynamicBaseTag ? {
  3195. //define base element, for use in routing asset urls that are referenced in Ajax-requested markup
  3196. element: ( $base.length ? $base : $( "<base>", { href: documentBase.hrefNoHash } ).prependTo( $head ) ),
  3197. //set the generated BASE element's href attribute to a new page's base path
  3198. set: function( href ) {
  3199. href = path.parseUrl(href).hrefNoHash;
  3200. base.element.attr( "href", path.makeUrlAbsolute( href, documentBase ) );
  3201. },
  3202. //set the generated BASE element's href attribute to a new page's base path
  3203. reset: function() {
  3204. base.element.attr( "href", documentBase.hrefNoSearch );
  3205. }
  3206. } : undefined;
  3207. //return the original document url
  3208. $.mobile.getDocumentUrl = path.getDocumentUrl;
  3209. //return the original document base url
  3210. $.mobile.getDocumentBase = path.getDocumentBase;
  3211. /* internal utility functions */
  3212. // NOTE Issue #4950 Android phonegap doesn't navigate back properly
  3213. // when a full page refresh has taken place. It appears that hashchange
  3214. // and replacestate history alterations work fine but we need to support
  3215. // both forms of history traversal in our code that uses backward history
  3216. // movement
  3217. $.mobile.back = function() {
  3218. var nav = window.navigator;
  3219. // if the setting is on and the navigator object is
  3220. // available use the phonegap navigation capability
  3221. if( this.phonegapNavigationEnabled &&
  3222. nav &&
  3223. nav.app &&
  3224. nav.app.backHistory ){
  3225. nav.app.backHistory();
  3226. } else {
  3227. window.history.back();
  3228. }
  3229. };
  3230. //direct focus to the page title, or otherwise first focusable element
  3231. $.mobile.focusPage = function ( page ) {
  3232. var autofocus = page.find( "[autofocus]" ),
  3233. pageTitle = page.find( ".ui-title:eq(0)" );
  3234. if ( autofocus.length ) {
  3235. autofocus.focus();
  3236. return;
  3237. }
  3238. if ( pageTitle.length ) {
  3239. pageTitle.focus();
  3240. } else{
  3241. page.focus();
  3242. }
  3243. };
  3244. //remove active classes after page transition or error
  3245. function removeActiveLinkClass( forceRemoval ) {
  3246. if ( !!$activeClickedLink && ( !$activeClickedLink.closest( "." + $.mobile.activePageClass ).length || forceRemoval ) ) {
  3247. $activeClickedLink.removeClass( $.mobile.activeBtnClass );
  3248. }
  3249. $activeClickedLink = null;
  3250. }
  3251. function releasePageTransitionLock() {
  3252. isPageTransitioning = false;
  3253. if ( pageTransitionQueue.length > 0 ) {
  3254. $.mobile.changePage.apply( null, pageTransitionQueue.pop() );
  3255. }
  3256. }
  3257. // Save the last scroll distance per page, before it is hidden
  3258. var setLastScrollEnabled = true,
  3259. setLastScroll, delayedSetLastScroll;
  3260. setLastScroll = function() {
  3261. // this barrier prevents setting the scroll value based on the browser
  3262. // scrolling the window based on a hashchange
  3263. if ( !setLastScrollEnabled ) {
  3264. return;
  3265. }
  3266. var active = $.mobile.urlHistory.getActive();
  3267. if ( active ) {
  3268. var lastScroll = $window.scrollTop();
  3269. // Set active page's lastScroll prop.
  3270. // If the location we're scrolling to is less than minScrollBack, let it go.
  3271. active.lastScroll = lastScroll < $.mobile.minScrollBack ? $.mobile.defaultHomeScroll : lastScroll;
  3272. }
  3273. };
  3274. // bind to scrollstop to gather scroll position. The delay allows for the hashchange
  3275. // event to fire and disable scroll recording in the case where the browser scrolls
  3276. // to the hash targets location (sometimes the top of the page). once pagechange fires
  3277. // getLastScroll is again permitted to operate
  3278. delayedSetLastScroll = function() {
  3279. setTimeout( setLastScroll, 100 );
  3280. };
  3281. // disable an scroll setting when a hashchange has been fired, this only works
  3282. // because the recording of the scroll position is delayed for 100ms after
  3283. // the browser might have changed the position because of the hashchange
  3284. $window.bind( $.support.pushState ? "popstate" : "hashchange", function() {
  3285. setLastScrollEnabled = false;
  3286. });
  3287. // handle initial hashchange from chrome :(
  3288. $window.one( $.support.pushState ? "popstate" : "hashchange", function() {
  3289. setLastScrollEnabled = true;
  3290. });
  3291. // wait until the mobile page container has been determined to bind to pagechange
  3292. $window.one( "pagecontainercreate", function() {
  3293. // once the page has changed, re-enable the scroll recording
  3294. $.mobile.pageContainer.bind( "pagechange", function() {
  3295. setLastScrollEnabled = true;
  3296. // remove any binding that previously existed on the get scroll
  3297. // which may or may not be different than the scroll element determined for
  3298. // this page previously
  3299. $window.unbind( "scrollstop", delayedSetLastScroll );
  3300. // determine and bind to the current scoll element which may be the window
  3301. // or in the case of touch overflow the element with touch overflow
  3302. $window.bind( "scrollstop", delayedSetLastScroll );
  3303. });
  3304. });
  3305. // bind to scrollstop for the first page as "pagechange" won't be fired in that case
  3306. $window.bind( "scrollstop", delayedSetLastScroll );
  3307. // No-op implementation of transition degradation
  3308. $.mobile._maybeDegradeTransition = $.mobile._maybeDegradeTransition || function( transition ) {
  3309. return transition;
  3310. };
  3311. //function for transitioning between two existing pages
  3312. function transitionPages( toPage, fromPage, transition, reverse ) {
  3313. if ( fromPage ) {
  3314. //trigger before show/hide events
  3315. fromPage.data( "mobile-page" )._trigger( "beforehide", null, { nextPage: toPage } );
  3316. }
  3317. toPage.data( "mobile-page" )._trigger( "beforeshow", null, { prevPage: fromPage || $( "" ) } );
  3318. //clear page loader
  3319. $.mobile.hidePageLoadingMsg();
  3320. transition = $.mobile._maybeDegradeTransition( transition );
  3321. //find the transition handler for the specified transition. If there
  3322. //isn't one in our transitionHandlers dictionary, use the default one.
  3323. //call the handler immediately to kick-off the transition.
  3324. var th = $.mobile.transitionHandlers[ transition || "default" ] || $.mobile.defaultTransitionHandler,
  3325. promise = th( transition, reverse, toPage, fromPage );
  3326. promise.done(function() {
  3327. //trigger show/hide events
  3328. if ( fromPage ) {
  3329. fromPage.data( "mobile-page" )._trigger( "hide", null, { nextPage: toPage } );
  3330. }
  3331. //trigger pageshow, define prevPage as either fromPage or empty jQuery obj
  3332. toPage.data( "mobile-page" )._trigger( "show", null, { prevPage: fromPage || $( "" ) } );
  3333. });
  3334. return promise;
  3335. }
  3336. //simply set the active page's minimum height to screen height, depending on orientation
  3337. $.mobile.resetActivePageHeight = function resetActivePageHeight( height ) {
  3338. var aPage = $( "." + $.mobile.activePageClass ),
  3339. aPagePadT = parseFloat( aPage.css( "padding-top" ) ),
  3340. aPagePadB = parseFloat( aPage.css( "padding-bottom" ) ),
  3341. aPageBorderT = parseFloat( aPage.css( "border-top-width" ) ),
  3342. aPageBorderB = parseFloat( aPage.css( "border-bottom-width" ) );
  3343. height = ( typeof height === "number" )? height : getScreenHeight();
  3344. aPage.css( "min-height", height - aPagePadT - aPagePadB - aPageBorderT - aPageBorderB );
  3345. };
  3346. //shared page enhancements
  3347. function enhancePage( $page, role ) {
  3348. // If a role was specified, make sure the data-role attribute
  3349. // on the page element is in sync.
  3350. if ( role ) {
  3351. $page.attr( "data-" + $.mobile.ns + "role", role );
  3352. }
  3353. //run page plugin
  3354. $page.page();
  3355. }
  3356. // determine the current base url
  3357. function findBaseWithDefault() {
  3358. var closestBase = ( $.mobile.activePage && getClosestBaseUrl( $.mobile.activePage ) );
  3359. return closestBase || documentBase.hrefNoHash;
  3360. }
  3361. /* exposed $.mobile methods */
  3362. //animation complete callback
  3363. $.fn.animationComplete = function( callback ) {
  3364. if ( $.support.cssTransitions ) {
  3365. return $( this ).one( 'webkitAnimationEnd animationend', callback );
  3366. }
  3367. else{
  3368. // defer execution for consistency between webkit/non webkit
  3369. setTimeout( callback, 0 );
  3370. return $( this );
  3371. }
  3372. };
  3373. //expose path object on $.mobile
  3374. $.mobile.path = path;
  3375. //expose base object on $.mobile
  3376. $.mobile.base = base;
  3377. //history stack
  3378. $.mobile.urlHistory = urlHistory;
  3379. $.mobile.dialogHashKey = dialogHashKey;
  3380. //enable cross-domain page support
  3381. $.mobile.allowCrossDomainPages = false;
  3382. $.mobile._bindPageRemove = function() {
  3383. var page = $( this );
  3384. // when dom caching is not enabled or the page is embedded bind to remove the page on hide
  3385. if ( !page.data( "mobile-page" ).options.domCache &&
  3386. page.is( ":jqmData(external-page='true')" ) ) {
  3387. page.bind( 'pagehide.remove', function( e ) {
  3388. var $this = $( this ),
  3389. prEvent = new $.Event( "pageremove" );
  3390. $this.trigger( prEvent );
  3391. if ( !prEvent.isDefaultPrevented() ) {
  3392. $this.removeWithDependents();
  3393. }
  3394. });
  3395. }
  3396. };
  3397. // Load a page into the DOM.
  3398. $.mobile.loadPage = function( url, options ) {
  3399. // This function uses deferred notifications to let callers
  3400. // know when the page is done loading, or if an error has occurred.
  3401. var deferred = $.Deferred(),
  3402. // The default loadPage options with overrides specified by
  3403. // the caller.
  3404. settings = $.extend( {}, $.mobile.loadPage.defaults, options ),
  3405. // The DOM element for the page after it has been loaded.
  3406. page = null,
  3407. // If the reloadPage option is true, and the page is already
  3408. // in the DOM, dupCachedPage will be set to the page element
  3409. // so that it can be removed after the new version of the
  3410. // page is loaded off the network.
  3411. dupCachedPage = null,
  3412. // The absolute version of the URL passed into the function. This
  3413. // version of the URL may contain dialog/subpage params in it.
  3414. absUrl = path.makeUrlAbsolute( url, findBaseWithDefault() );
  3415. // If the caller provided data, and we're using "get" request,
  3416. // append the data to the URL.
  3417. if ( settings.data && settings.type === "get" ) {
  3418. absUrl = path.addSearchParams( absUrl, settings.data );
  3419. settings.data = undefined;
  3420. }
  3421. // If the caller is using a "post" request, reloadPage must be true
  3422. if ( settings.data && settings.type === "post" ) {
  3423. settings.reloadPage = true;
  3424. }
  3425. // The absolute version of the URL minus any dialog/subpage params.
  3426. // In otherwords the real URL of the page to be loaded.
  3427. var fileUrl = path.getFilePath( absUrl ),
  3428. // The version of the Url actually stored in the data-url attribute of
  3429. // the page. For embedded pages, it is just the id of the page. For pages
  3430. // within the same domain as the document base, it is the site relative
  3431. // path. For cross-domain pages (Phone Gap only) the entire absolute Url
  3432. // used to load the page.
  3433. dataUrl = path.convertUrlToDataUrl( absUrl );
  3434. // Make sure we have a pageContainer to work with.
  3435. settings.pageContainer = settings.pageContainer || $.mobile.pageContainer;
  3436. // Check to see if the page already exists in the DOM.
  3437. // NOTE do _not_ use the :jqmData psuedo selector because parenthesis
  3438. // are a valid url char and it breaks on the first occurence
  3439. page = settings.pageContainer.children( "[data-" + $.mobile.ns +"url='" + dataUrl + "']" );
  3440. // If we failed to find the page, check to see if the url is a
  3441. // reference to an embedded page. If so, it may have been dynamically
  3442. // injected by a developer, in which case it would be lacking a data-url
  3443. // attribute and in need of enhancement.
  3444. if ( page.length === 0 && dataUrl && !path.isPath( dataUrl ) ) {
  3445. page = settings.pageContainer.children( "#" + dataUrl )
  3446. .attr( "data-" + $.mobile.ns + "url", dataUrl )
  3447. .jqmData( "url", dataUrl );
  3448. }
  3449. // If we failed to find a page in the DOM, check the URL to see if it
  3450. // refers to the first page in the application. If it isn't a reference
  3451. // to the first page and refers to non-existent embedded page, error out.
  3452. if ( page.length === 0 ) {
  3453. if ( $.mobile.firstPage && path.isFirstPageUrl( fileUrl ) ) {
  3454. // Check to make sure our cached-first-page is actually
  3455. // in the DOM. Some user deployed apps are pruning the first
  3456. // page from the DOM for various reasons, we check for this
  3457. // case here because we don't want a first-page with an id
  3458. // falling through to the non-existent embedded page error
  3459. // case. If the first-page is not in the DOM, then we let
  3460. // things fall through to the ajax loading code below so
  3461. // that it gets reloaded.
  3462. if ( $.mobile.firstPage.parent().length ) {
  3463. page = $( $.mobile.firstPage );
  3464. }
  3465. } else if ( path.isEmbeddedPage( fileUrl ) ) {
  3466. deferred.reject( absUrl, options );
  3467. return deferred.promise();
  3468. }
  3469. }
  3470. // If the page we are interested in is already in the DOM,
  3471. // and the caller did not indicate that we should force a
  3472. // reload of the file, we are done. Otherwise, track the
  3473. // existing page as a duplicated.
  3474. if ( page.length ) {
  3475. if ( !settings.reloadPage ) {
  3476. enhancePage( page, settings.role );
  3477. deferred.resolve( absUrl, options, page );
  3478. //if we are reloading the page make sure we update the base if its not a prefetch
  3479. if( base && !options.prefetch ){
  3480. base.set(url);
  3481. }
  3482. return deferred.promise();
  3483. }
  3484. dupCachedPage = page;
  3485. }
  3486. var mpc = settings.pageContainer,
  3487. pblEvent = new $.Event( "pagebeforeload" ),
  3488. triggerData = { url: url, absUrl: absUrl, dataUrl: dataUrl, deferred: deferred, options: settings };
  3489. // Let listeners know we're about to load a page.
  3490. mpc.trigger( pblEvent, triggerData );
  3491. // If the default behavior is prevented, stop here!
  3492. if ( pblEvent.isDefaultPrevented() ) {
  3493. return deferred.promise();
  3494. }
  3495. if ( settings.showLoadMsg ) {
  3496. // This configurable timeout allows cached pages a brief delay to load without showing a message
  3497. var loadMsgDelay = setTimeout(function() {
  3498. $.mobile.showPageLoadingMsg();
  3499. }, settings.loadMsgDelay ),
  3500. // Shared logic for clearing timeout and removing message.
  3501. hideMsg = function() {
  3502. // Stop message show timer
  3503. clearTimeout( loadMsgDelay );
  3504. // Hide loading message
  3505. $.mobile.hidePageLoadingMsg();
  3506. };
  3507. }
  3508. // Reset base to the default document base.
  3509. // only reset if we are not prefetching
  3510. if ( base && ( typeof options === "undefined" || typeof options.prefetch === "undefined" ) ) {
  3511. base.reset();
  3512. }
  3513. if ( !( $.mobile.allowCrossDomainPages || path.isSameDomain( documentUrl, absUrl ) ) ) {
  3514. deferred.reject( absUrl, options );
  3515. } else {
  3516. // Load the new page.
  3517. $.ajax({
  3518. url: fileUrl,
  3519. type: settings.type,
  3520. data: settings.data,
  3521. contentType: settings.contentType,
  3522. dataType: "html",
  3523. success: function( html, textStatus, xhr ) {
  3524. //pre-parse html to check for a data-url,
  3525. //use it as the new fileUrl, base path, etc
  3526. var all = $( "<div></div>" ),
  3527. //page title regexp
  3528. newPageTitle = html.match( /<title[^>]*>([^<]*)/ ) && RegExp.$1,
  3529. // TODO handle dialogs again
  3530. pageElemRegex = new RegExp( "(<[^>]+\\bdata-" + $.mobile.ns + "role=[\"']?page[\"']?[^>]*>)" ),
  3531. dataUrlRegex = new RegExp( "\\bdata-" + $.mobile.ns + "url=[\"']?([^\"'>]*)[\"']?" );
  3532. // data-url must be provided for the base tag so resource requests can be directed to the
  3533. // correct url. loading into a temprorary element makes these requests immediately
  3534. if ( pageElemRegex.test( html ) &&
  3535. RegExp.$1 &&
  3536. dataUrlRegex.test( RegExp.$1 ) &&
  3537. RegExp.$1 ) {
  3538. url = fileUrl = path.getFilePath( $( "<div>" + RegExp.$1 + "</div>" ).text() );
  3539. }
  3540. //dont update the base tag if we are prefetching
  3541. if ( base && ( typeof options === "undefined" || typeof options.prefetch === "undefined" )) {
  3542. base.set( fileUrl );
  3543. }
  3544. //workaround to allow scripts to execute when included in page divs
  3545. all.get( 0 ).innerHTML = html;
  3546. page = all.find( ":jqmData(role='page'), :jqmData(role='dialog')" ).first();
  3547. //if page elem couldn't be found, create one and insert the body element's contents
  3548. if ( !page.length ) {
  3549. page = $( "<div data-" + $.mobile.ns + "role='page'>" + ( html.split( /<\/?body[^>]*>/gmi )[1] || "" ) + "</div>" );
  3550. }
  3551. if ( newPageTitle && !page.jqmData( "title" ) ) {
  3552. if ( ~newPageTitle.indexOf( "&" ) ) {
  3553. newPageTitle = $( "<div>" + newPageTitle + "</div>" ).text();
  3554. }
  3555. page.jqmData( "title", newPageTitle );
  3556. }
  3557. //rewrite src and href attrs to use a base url
  3558. if ( !$.support.dynamicBaseTag ) {
  3559. var newPath = path.get( fileUrl );
  3560. page.find( "[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]" ).each(function() {
  3561. var thisAttr = $( this ).is( '[href]' ) ? 'href' :
  3562. $( this ).is( '[src]' ) ? 'src' : 'action',
  3563. thisUrl = $( this ).attr( thisAttr );
  3564. // XXX_jblas: We need to fix this so that it removes the document
  3565. // base URL, and then prepends with the new page URL.
  3566. //if full path exists and is same, chop it - helps IE out
  3567. thisUrl = thisUrl.replace( location.protocol + '//' + location.host + location.pathname, '' );
  3568. if ( !/^(\w+:|#|\/)/.test( thisUrl ) ) {
  3569. $( this ).attr( thisAttr, newPath + thisUrl );
  3570. }
  3571. });
  3572. }
  3573. //append to page and enhance
  3574. // TODO taging a page with external to make sure that embedded pages aren't removed
  3575. // by the various page handling code is bad. Having page handling code in many
  3576. // places is bad. Solutions post 1.0
  3577. page
  3578. .attr( "data-" + $.mobile.ns + "url", path.convertUrlToDataUrl( fileUrl ) )
  3579. .attr( "data-" + $.mobile.ns + "external-page", true )
  3580. .appendTo( settings.pageContainer );
  3581. // wait for page creation to leverage options defined on widget
  3582. page.one( 'pagecreate', $.mobile._bindPageRemove );
  3583. enhancePage( page, settings.role );
  3584. // Enhancing the page may result in new dialogs/sub pages being inserted
  3585. // into the DOM. If the original absUrl refers to a sub-page, that is the
  3586. // real page we are interested in.
  3587. if ( absUrl.indexOf( "&" + $.mobile.subPageUrlKey ) > -1 ) {
  3588. page = settings.pageContainer.children( "[data-" + $.mobile.ns +"url='" + dataUrl + "']" );
  3589. }
  3590. // Remove loading message.
  3591. if ( settings.showLoadMsg ) {
  3592. hideMsg();
  3593. }
  3594. // Add the page reference and xhr to our triggerData.
  3595. triggerData.xhr = xhr;
  3596. triggerData.textStatus = textStatus;
  3597. triggerData.page = page;
  3598. // Let listeners know the page loaded successfully.
  3599. settings.pageContainer.trigger( "pageload", triggerData );
  3600. deferred.resolve( absUrl, options, page, dupCachedPage );
  3601. },
  3602. error: function( xhr, textStatus, errorThrown ) {
  3603. //set base back to current path
  3604. if ( base ) {
  3605. base.set( path.get() );
  3606. }
  3607. // Add error info to our triggerData.
  3608. triggerData.xhr = xhr;
  3609. triggerData.textStatus = textStatus;
  3610. triggerData.errorThrown = errorThrown;
  3611. var plfEvent = new $.Event( "pageloadfailed" );
  3612. // Let listeners know the page load failed.
  3613. settings.pageContainer.trigger( plfEvent, triggerData );
  3614. // If the default behavior is prevented, stop here!
  3615. // Note that it is the responsibility of the listener/handler
  3616. // that called preventDefault(), to resolve/reject the
  3617. // deferred object within the triggerData.
  3618. if ( plfEvent.isDefaultPrevented() ) {
  3619. return;
  3620. }
  3621. // Remove loading message.
  3622. if ( settings.showLoadMsg ) {
  3623. // Remove loading message.
  3624. hideMsg();
  3625. // show error message
  3626. $.mobile.showPageLoadingMsg( $.mobile.pageLoadErrorMessageTheme, $.mobile.pageLoadErrorMessage, true );
  3627. // hide after delay
  3628. setTimeout( $.mobile.hidePageLoadingMsg, 1500 );
  3629. }
  3630. deferred.reject( absUrl, options );
  3631. }
  3632. });
  3633. }
  3634. return deferred.promise();
  3635. };
  3636. $.mobile.loadPage.defaults = {
  3637. type: "get",
  3638. data: undefined,
  3639. reloadPage: false,
  3640. role: undefined, // By default we rely on the role defined by the @data-role attribute.
  3641. showLoadMsg: false,
  3642. pageContainer: undefined,
  3643. loadMsgDelay: 50 // This delay allows loads that pull from browser cache to occur without showing the loading message.
  3644. };
  3645. // Show a specific page in the page container.
  3646. $.mobile.changePage = function( toPage, options ) {
  3647. // If we are in the midst of a transition, queue the current request.
  3648. // We'll call changePage() once we're done with the current transition to
  3649. // service the request.
  3650. if ( isPageTransitioning ) {
  3651. pageTransitionQueue.unshift( arguments );
  3652. return;
  3653. }
  3654. var settings = $.extend( {}, $.mobile.changePage.defaults, options ), isToPageString;
  3655. // Make sure we have a pageContainer to work with.
  3656. settings.pageContainer = settings.pageContainer || $.mobile.pageContainer;
  3657. // Make sure we have a fromPage.
  3658. settings.fromPage = settings.fromPage || $.mobile.activePage;
  3659. isToPageString = (typeof toPage === "string");
  3660. var mpc = settings.pageContainer,
  3661. pbcEvent = new $.Event( "pagebeforechange" ),
  3662. triggerData = { toPage: toPage, options: settings };
  3663. // NOTE: preserve the original target as the dataUrl value will be simplified
  3664. // eg, removing ui-state, and removing query params from the hash
  3665. // this is so that users who want to use query params have access to them
  3666. // in the event bindings for the page life cycle See issue #5085
  3667. if ( isToPageString ) {
  3668. // if the toPage is a string simply convert it
  3669. triggerData.absUrl = path.makeUrlAbsolute( toPage, findBaseWithDefault() );
  3670. } else {
  3671. // if the toPage is a jQuery object grab the absolute url stored
  3672. // in the loadPage callback where it exists
  3673. triggerData.absUrl = toPage.data( 'absUrl' );
  3674. }
  3675. // Let listeners know we're about to change the current page.
  3676. mpc.trigger( pbcEvent, triggerData );
  3677. // If the default behavior is prevented, stop here!
  3678. if ( pbcEvent.isDefaultPrevented() ) {
  3679. return;
  3680. }
  3681. // We allow "pagebeforechange" observers to modify the toPage in the trigger
  3682. // data to allow for redirects. Make sure our toPage is updated.
  3683. //
  3684. // We also need to re-evaluate whether it is a string, because an object can
  3685. // also be replaced by a string
  3686. toPage = triggerData.toPage;
  3687. isToPageString = (typeof toPage === "string");
  3688. // Set the isPageTransitioning flag to prevent any requests from
  3689. // entering this method while we are in the midst of loading a page
  3690. // or transitioning.
  3691. isPageTransitioning = true;
  3692. // If the caller passed us a url, call loadPage()
  3693. // to make sure it is loaded into the DOM. We'll listen
  3694. // to the promise object it returns so we know when
  3695. // it is done loading or if an error ocurred.
  3696. if ( isToPageString ) {
  3697. // preserve the original target as the dataUrl value will be simplified
  3698. // eg, removing ui-state, and removing query params from the hash
  3699. // this is so that users who want to use query params have access to them
  3700. // in the event bindings for the page life cycle See issue #5085
  3701. settings.target = toPage;
  3702. $.mobile.loadPage( toPage, settings )
  3703. .done(function( url, options, newPage, dupCachedPage ) {
  3704. isPageTransitioning = false;
  3705. options.duplicateCachedPage = dupCachedPage;
  3706. // store the original absolute url so that it can be provided
  3707. // to events in the triggerData of the subsequent changePage call
  3708. newPage.data( 'absUrl', triggerData.absUrl );
  3709. $.mobile.changePage( newPage, options );
  3710. })
  3711. .fail(function( url, options ) {
  3712. //clear out the active button state
  3713. removeActiveLinkClass( true );
  3714. //release transition lock so navigation is free again
  3715. releasePageTransitionLock();
  3716. settings.pageContainer.trigger( "pagechangefailed", triggerData );
  3717. });
  3718. return;
  3719. }
  3720. // If we are going to the first-page of the application, we need to make
  3721. // sure settings.dataUrl is set to the application document url. This allows
  3722. // us to avoid generating a document url with an id hash in the case where the
  3723. // first-page of the document has an id attribute specified.
  3724. if ( toPage[ 0 ] === $.mobile.firstPage[ 0 ] && !settings.dataUrl ) {
  3725. settings.dataUrl = documentUrl.hrefNoHash;
  3726. }
  3727. // The caller passed us a real page DOM element. Update our
  3728. // internal state and then trigger a transition to the page.
  3729. var fromPage = settings.fromPage,
  3730. url = ( settings.dataUrl && path.convertUrlToDataUrl( settings.dataUrl ) ) || toPage.jqmData( "url" ),
  3731. // The pageUrl var is usually the same as url, except when url is obscured as a dialog url. pageUrl always contains the file path
  3732. pageUrl = url,
  3733. fileUrl = path.getFilePath( url ),
  3734. active = urlHistory.getActive(),
  3735. activeIsInitialPage = urlHistory.activeIndex === 0,
  3736. historyDir = 0,
  3737. pageTitle = document.title,
  3738. isDialog = settings.role === "dialog" || toPage.jqmData( "role" ) === "dialog";
  3739. // By default, we prevent changePage requests when the fromPage and toPage
  3740. // are the same element, but folks that generate content manually/dynamically
  3741. // and reuse pages want to be able to transition to the same page. To allow
  3742. // this, they will need to change the default value of allowSamePageTransition
  3743. // to true, *OR*, pass it in as an option when they manually call changePage().
  3744. // It should be noted that our default transition animations assume that the
  3745. // formPage and toPage are different elements, so they may behave unexpectedly.
  3746. // It is up to the developer that turns on the allowSamePageTransitiona option
  3747. // to either turn off transition animations, or make sure that an appropriate
  3748. // animation transition is used.
  3749. if ( fromPage && fromPage[0] === toPage[0] && !settings.allowSamePageTransition ) {
  3750. isPageTransitioning = false;
  3751. mpc.trigger( "pagechange", triggerData );
  3752. // Even if there is no page change to be done, we should keep the urlHistory in sync with the hash changes
  3753. if ( settings.fromHashChange ) {
  3754. urlHistory.direct({ url: url });
  3755. }
  3756. return;
  3757. }
  3758. // We need to make sure the page we are given has already been enhanced.
  3759. enhancePage( toPage, settings.role );
  3760. // If the changePage request was sent from a hashChange event, check to see if the
  3761. // page is already within the urlHistory stack. If so, we'll assume the user hit
  3762. // the forward/back button and will try to match the transition accordingly.
  3763. if ( settings.fromHashChange ) {
  3764. historyDir = options.direction === "back" ? -1 : 1;
  3765. }
  3766. // Kill the keyboard.
  3767. // XXX_jblas: We need to stop crawling the entire document to kill focus. Instead,
  3768. // we should be tracking focus with a delegate() handler so we already have
  3769. // the element in hand at this point.
  3770. // Wrap this in a try/catch block since IE9 throw "Unspecified error" if document.activeElement
  3771. // is undefined when we are in an IFrame.
  3772. try {
  3773. if ( document.activeElement && document.activeElement.nodeName.toLowerCase() !== 'body' ) {
  3774. $( document.activeElement ).blur();
  3775. } else {
  3776. $( "input:focus, textarea:focus, select:focus" ).blur();
  3777. }
  3778. } catch( e ) {}
  3779. // Record whether we are at a place in history where a dialog used to be - if so, do not add a new history entry and do not change the hash either
  3780. var alreadyThere = false;
  3781. // If we're displaying the page as a dialog, we don't want the url
  3782. // for the dialog content to be used in the hash. Instead, we want
  3783. // to append the dialogHashKey to the url of the current page.
  3784. if ( isDialog && active ) {
  3785. // on the initial page load active.url is undefined and in that case should
  3786. // be an empty string. Moving the undefined -> empty string back into
  3787. // urlHistory.addNew seemed imprudent given undefined better represents
  3788. // the url state
  3789. // If we are at a place in history that once belonged to a dialog, reuse
  3790. // this state without adding to urlHistory and without modifying the hash.
  3791. // However, if a dialog is already displayed at this point, and we're
  3792. // about to display another dialog, then we must add another hash and
  3793. // history entry on top so that one may navigate back to the original dialog
  3794. if ( active.url &&
  3795. active.url.indexOf( dialogHashKey ) > -1 &&
  3796. $.mobile.activePage &&
  3797. !$.mobile.activePage.is( ".ui-dialog" ) &&
  3798. urlHistory.activeIndex > 0 ) {
  3799. settings.changeHash = false;
  3800. alreadyThere = true;
  3801. }
  3802. // Normally, we tack on a dialog hash key, but if this is the location of a stale dialog,
  3803. // we reuse the URL from the entry
  3804. url = ( active.url || "" );
  3805. // account for absolute urls instead of just relative urls use as hashes
  3806. if( !alreadyThere && url.indexOf("#") > -1 ) {
  3807. url += dialogHashKey;
  3808. } else {
  3809. url += "#" + dialogHashKey;
  3810. }
  3811. // tack on another dialogHashKey if this is the same as the initial hash
  3812. // this makes sure that a history entry is created for this dialog
  3813. if ( urlHistory.activeIndex === 0 && url === urlHistory.initialDst ) {
  3814. url += dialogHashKey;
  3815. }
  3816. }
  3817. // if title element wasn't found, try the page div data attr too
  3818. // If this is a deep-link or a reload ( active === undefined ) then just use pageTitle
  3819. var newPageTitle = ( !active )? pageTitle : toPage.jqmData( "title" ) || toPage.children( ":jqmData(role='header')" ).find( ".ui-title" ).text();
  3820. if ( !!newPageTitle && pageTitle === document.title ) {
  3821. pageTitle = newPageTitle;
  3822. }
  3823. if ( !toPage.jqmData( "title" ) ) {
  3824. toPage.jqmData( "title", pageTitle );
  3825. }
  3826. // Make sure we have a transition defined.
  3827. settings.transition = settings.transition ||
  3828. ( ( historyDir && !activeIsInitialPage ) ? active.transition : undefined ) ||
  3829. ( isDialog ? $.mobile.defaultDialogTransition : $.mobile.defaultPageTransition );
  3830. //add page to history stack if it's not back or forward
  3831. if ( !historyDir && alreadyThere ) {
  3832. urlHistory.getActive().pageUrl = pageUrl;
  3833. }
  3834. // Set the location hash.
  3835. if ( url && !settings.fromHashChange ) {
  3836. var params;
  3837. // rebuilding the hash here since we loose it earlier on
  3838. // TODO preserve the originally passed in path
  3839. if( !path.isPath( url ) && url.indexOf( "#" ) < 0 ) {
  3840. url = "#" + url;
  3841. }
  3842. // TODO the property names here are just silly
  3843. params = {
  3844. transition: settings.transition,
  3845. title: pageTitle,
  3846. pageUrl: pageUrl,
  3847. role: settings.role
  3848. };
  3849. if ( settings.changeHash !== false && $.mobile.hashListeningEnabled ) {
  3850. $.mobile.navigate( url, params, true);
  3851. } else if ( toPage[ 0 ] !== $.mobile.firstPage[ 0 ] ) {
  3852. $.mobile.navigate.history.add( url, params );
  3853. }
  3854. }
  3855. //set page title
  3856. document.title = pageTitle;
  3857. //set "toPage" as activePage
  3858. $.mobile.activePage = toPage;
  3859. // If we're navigating back in the URL history, set reverse accordingly.
  3860. settings.reverse = settings.reverse || historyDir < 0;
  3861. transitionPages( toPage, fromPage, settings.transition, settings.reverse )
  3862. .done(function( name, reverse, $to, $from, alreadyFocused ) {
  3863. removeActiveLinkClass();
  3864. //if there's a duplicateCachedPage, remove it from the DOM now that it's hidden
  3865. if ( settings.duplicateCachedPage ) {
  3866. settings.duplicateCachedPage.remove();
  3867. }
  3868. // Send focus to the newly shown page. Moved from promise .done binding in transitionPages
  3869. // itself to avoid ie bug that reports offsetWidth as > 0 (core check for visibility)
  3870. // despite visibility: hidden addresses issue #2965
  3871. // https://github.com/jquery/jquery-mobile/issues/2965
  3872. if ( !alreadyFocused ) {
  3873. $.mobile.focusPage( toPage );
  3874. }
  3875. releasePageTransitionLock();
  3876. mpc.trigger( "pagechange", triggerData );
  3877. });
  3878. };
  3879. $.mobile.changePage.defaults = {
  3880. transition: undefined,
  3881. reverse: false,
  3882. changeHash: true,
  3883. fromHashChange: false,
  3884. role: undefined, // By default we rely on the role defined by the @data-role attribute.
  3885. duplicateCachedPage: undefined,
  3886. pageContainer: undefined,
  3887. showLoadMsg: true, //loading message shows by default when pages are being fetched during changePage
  3888. dataUrl: undefined,
  3889. fromPage: undefined,
  3890. allowSamePageTransition: false
  3891. };
  3892. /* Event Bindings - hashchange, submit, and click */
  3893. function findClosestLink( ele )
  3894. {
  3895. while ( ele ) {
  3896. // Look for the closest element with a nodeName of "a".
  3897. // Note that we are checking if we have a valid nodeName
  3898. // before attempting to access it. This is because the
  3899. // node we get called with could have originated from within
  3900. // an embedded SVG document where some symbol instance elements
  3901. // don't have nodeName defined on them, or strings are of type
  3902. // SVGAnimatedString.
  3903. if ( ( typeof ele.nodeName === "string" ) && ele.nodeName.toLowerCase() === "a" ) {
  3904. break;
  3905. }
  3906. ele = ele.parentNode;
  3907. }
  3908. return ele;
  3909. }
  3910. // The base URL for any given element depends on the page it resides in.
  3911. function getClosestBaseUrl( ele )
  3912. {
  3913. // Find the closest page and extract out its url.
  3914. var url = $( ele ).closest( ".ui-page" ).jqmData( "url" ),
  3915. base = documentBase.hrefNoHash;
  3916. if ( !url || !path.isPath( url ) ) {
  3917. url = base;
  3918. }
  3919. return path.makeUrlAbsolute( url, base);
  3920. }
  3921. //The following event bindings should be bound after mobileinit has been triggered
  3922. //the following deferred is resolved in the init file
  3923. $.mobile.navreadyDeferred = $.Deferred();
  3924. $.mobile._registerInternalEvents = function() {
  3925. var getAjaxFormData = function( $form, calculateOnly ) {
  3926. var url, ret = true, formData, vclickedName, method;
  3927. if ( !$.mobile.ajaxEnabled ||
  3928. // test that the form is, itself, ajax false
  3929. $form.is( ":jqmData(ajax='false')" ) ||
  3930. // test that $.mobile.ignoreContentEnabled is set and
  3931. // the form or one of it's parents is ajax=false
  3932. !$form.jqmHijackable().length ||
  3933. $form.attr( "target" ) ) {
  3934. return false;
  3935. }
  3936. url = $form.attr( "action" );
  3937. method = ( $form.attr( "method" ) || "get" ).toLowerCase();
  3938. // If no action is specified, browsers default to using the
  3939. // URL of the document containing the form. Since we dynamically
  3940. // pull in pages from external documents, the form should submit
  3941. // to the URL for the source document of the page containing
  3942. // the form.
  3943. if ( !url ) {
  3944. // Get the @data-url for the page containing the form.
  3945. url = getClosestBaseUrl( $form );
  3946. // NOTE: If the method is "get", we need to strip off the query string
  3947. // because it will get replaced with the new form data. See issue #5710.
  3948. if ( method === "get" ) {
  3949. url = path.parseUrl( url ).hrefNoSearch;
  3950. }
  3951. if ( url === documentBase.hrefNoHash ) {
  3952. // The url we got back matches the document base,
  3953. // which means the page must be an internal/embedded page,
  3954. // so default to using the actual document url as a browser
  3955. // would.
  3956. url = documentUrl.hrefNoSearch;
  3957. }
  3958. }
  3959. url = path.makeUrlAbsolute( url, getClosestBaseUrl( $form ) );
  3960. if ( ( path.isExternal( url ) && !path.isPermittedCrossDomainRequest( documentUrl, url ) ) ) {
  3961. return false;
  3962. }
  3963. if ( !calculateOnly ) {
  3964. formData = $form.serializeArray();
  3965. if ( $lastVClicked && $lastVClicked[ 0 ].form === $form[ 0 ] ) {
  3966. vclickedName = $lastVClicked.attr( "name" );
  3967. if ( vclickedName ) {
  3968. // Make sure the last clicked element is included in the form
  3969. $.each( formData, function( key, value ) {
  3970. if ( value.name === vclickedName ) {
  3971. // Unset vclickedName - we've found it in the serialized data already
  3972. vclickedName = "";
  3973. return false;
  3974. }
  3975. });
  3976. if ( vclickedName ) {
  3977. formData.push( { name: vclickedName, value: $lastVClicked.attr( "value" ) } );
  3978. }
  3979. }
  3980. }
  3981. ret = {
  3982. url: url,
  3983. options: {
  3984. type: method,
  3985. data: $.param( formData ),
  3986. transition: $form.jqmData( "transition" ),
  3987. reverse: $form.jqmData( "direction" ) === "reverse",
  3988. reloadPage: true
  3989. }
  3990. };
  3991. }
  3992. return ret;
  3993. };
  3994. //bind to form submit events, handle with Ajax
  3995. $.mobile.document.delegate( "form", "submit", function( event ) {
  3996. var formData = getAjaxFormData( $( this ) );
  3997. if ( formData ) {
  3998. $.mobile.changePage( formData.url, formData.options );
  3999. event.preventDefault();
  4000. }
  4001. });
  4002. //add active state on vclick
  4003. $.mobile.document.bind( "vclick", function( event ) {
  4004. var $btn, btnEls, target = event.target, needClosest = false;
  4005. // if this isn't a left click we don't care. Its important to note
  4006. // that when the virtual event is generated it will create the which attr
  4007. if ( event.which > 1 || !$.mobile.linkBindingEnabled ) {
  4008. return;
  4009. }
  4010. // Record that this element was clicked, in case we need it for correct
  4011. // form submission during the "submit" handler above
  4012. $lastVClicked = $( target );
  4013. // Try to find a target element to which the active class will be applied
  4014. if ( $.data( target, "mobile-button" ) ) {
  4015. // If the form will not be submitted via AJAX, do not add active class
  4016. if ( !getAjaxFormData( $( target ).closest( "form" ), true ) ) {
  4017. return;
  4018. }
  4019. // We will apply the active state to this button widget - the parent
  4020. // of the input that was clicked will have the associated data
  4021. if ( target.parentNode ) {
  4022. target = target.parentNode;
  4023. }
  4024. } else {
  4025. target = findClosestLink( target );
  4026. if ( !( target && path.parseUrl( target.getAttribute( "href" ) || "#" ).hash !== "#" ) ) {
  4027. return;
  4028. }
  4029. // TODO teach $.mobile.hijackable to operate on raw dom elements so the
  4030. // link wrapping can be avoided
  4031. if ( !$( target ).jqmHijackable().length ) {
  4032. return;
  4033. }
  4034. }
  4035. // Avoid calling .closest by using the data set during .buttonMarkup()
  4036. // List items have the button data in the parent of the element clicked
  4037. if ( !!~target.className.indexOf( "ui-link-inherit" ) ) {
  4038. if ( target.parentNode ) {
  4039. btnEls = $.data( target.parentNode, "buttonElements" );
  4040. }
  4041. // Otherwise, look for the data on the target itself
  4042. } else {
  4043. btnEls = $.data( target, "buttonElements" );
  4044. }
  4045. // If found, grab the button's outer element
  4046. if ( btnEls ) {
  4047. target = btnEls.outer;
  4048. } else {
  4049. needClosest = true;
  4050. }
  4051. $btn = $( target );
  4052. // If the outer element wasn't found by the our heuristics, use .closest()
  4053. if ( needClosest ) {
  4054. $btn = $btn.closest( ".ui-btn" );
  4055. }
  4056. if ( $btn.length > 0 && !$btn.hasClass( "ui-disabled" ) ) {
  4057. removeActiveLinkClass( true );
  4058. $activeClickedLink = $btn;
  4059. $activeClickedLink.addClass( $.mobile.activeBtnClass );
  4060. }
  4061. });
  4062. // click routing - direct to HTTP or Ajax, accordingly
  4063. $.mobile.document.bind( "click", function( event ) {
  4064. if ( !$.mobile.linkBindingEnabled || event.isDefaultPrevented() ) {
  4065. return;
  4066. }
  4067. var link = findClosestLink( event.target ), $link = $( link ), httpCleanup;
  4068. // If there is no link associated with the click or its not a left
  4069. // click we want to ignore the click
  4070. // TODO teach $.mobile.hijackable to operate on raw dom elements so the link wrapping
  4071. // can be avoided
  4072. if ( !link || event.which > 1 || !$link.jqmHijackable().length ) {
  4073. return;
  4074. }
  4075. //remove active link class if external (then it won't be there if you come back)
  4076. httpCleanup = function() {
  4077. window.setTimeout(function() { removeActiveLinkClass( true ); }, 200 );
  4078. };
  4079. //if there's a data-rel=back attr, go back in history
  4080. if ( $link.is( ":jqmData(rel='back')" ) ) {
  4081. $.mobile.back();
  4082. return false;
  4083. }
  4084. var baseUrl = getClosestBaseUrl( $link ),
  4085. //get href, if defined, otherwise default to empty hash
  4086. href = path.makeUrlAbsolute( $link.attr( "href" ) || "#", baseUrl );
  4087. //if ajax is disabled, exit early
  4088. if ( !$.mobile.ajaxEnabled && !path.isEmbeddedPage( href ) ) {
  4089. httpCleanup();
  4090. //use default click handling
  4091. return;
  4092. }
  4093. // XXX_jblas: Ideally links to application pages should be specified as
  4094. // an url to the application document with a hash that is either
  4095. // the site relative path or id to the page. But some of the
  4096. // internal code that dynamically generates sub-pages for nested
  4097. // lists and select dialogs, just write a hash in the link they
  4098. // create. This means the actual URL path is based on whatever
  4099. // the current value of the base tag is at the time this code
  4100. // is called. For now we are just assuming that any url with a
  4101. // hash in it is an application page reference.
  4102. if ( href.search( "#" ) !== -1 ) {
  4103. href = href.replace( /[^#]*#/, "" );
  4104. if ( !href ) {
  4105. //link was an empty hash meant purely
  4106. //for interaction, so we ignore it.
  4107. event.preventDefault();
  4108. return;
  4109. } else if ( path.isPath( href ) ) {
  4110. //we have apath so make it the href we want to load.
  4111. href = path.makeUrlAbsolute( href, baseUrl );
  4112. } else {
  4113. //we have a simple id so use the documentUrl as its base.
  4114. href = path.makeUrlAbsolute( "#" + href, documentUrl.hrefNoHash );
  4115. }
  4116. }
  4117. // Should we handle this link, or let the browser deal with it?
  4118. var useDefaultUrlHandling = $link.is( "[rel='external']" ) || $link.is( ":jqmData(ajax='false')" ) || $link.is( "[target]" ),
  4119. // Some embedded browsers, like the web view in Phone Gap, allow cross-domain XHR
  4120. // requests if the document doing the request was loaded via the file:// protocol.
  4121. // This is usually to allow the application to "phone home" and fetch app specific
  4122. // data. We normally let the browser handle external/cross-domain urls, but if the
  4123. // allowCrossDomainPages option is true, we will allow cross-domain http/https
  4124. // requests to go through our page loading logic.
  4125. //check for protocol or rel and its not an embedded page
  4126. //TODO overlap in logic from isExternal, rel=external check should be
  4127. // moved into more comprehensive isExternalLink
  4128. isExternal = useDefaultUrlHandling || ( path.isExternal( href ) && !path.isPermittedCrossDomainRequest( documentUrl, href ) );
  4129. if ( isExternal ) {
  4130. httpCleanup();
  4131. //use default click handling
  4132. return;
  4133. }
  4134. //use ajax
  4135. var transition = $link.jqmData( "transition" ),
  4136. reverse = $link.jqmData( "direction" ) === "reverse" ||
  4137. // deprecated - remove by 1.0
  4138. $link.jqmData( "back" ),
  4139. //this may need to be more specific as we use data-rel more
  4140. role = $link.attr( "data-" + $.mobile.ns + "rel" ) || undefined;
  4141. $.mobile.changePage( href, { transition: transition, reverse: reverse, role: role, link: $link } );
  4142. event.preventDefault();
  4143. });
  4144. //prefetch pages when anchors with data-prefetch are encountered
  4145. $.mobile.document.delegate( ".ui-page", "pageshow.prefetch", function() {
  4146. var urls = [];
  4147. $( this ).find( "a:jqmData(prefetch)" ).each(function() {
  4148. var $link = $( this ),
  4149. url = $link.attr( "href" );
  4150. if ( url && $.inArray( url, urls ) === -1 ) {
  4151. urls.push( url );
  4152. $.mobile.loadPage( url, { role: $link.attr( "data-" + $.mobile.ns + "rel" ),prefetch: true } );
  4153. }
  4154. });
  4155. });
  4156. $.mobile._handleHashChange = function( url, data ) {
  4157. //find first page via hash
  4158. var to = path.stripHash(url),
  4159. //transition is false if it's the first page, undefined otherwise (and may be overridden by default)
  4160. transition = $.mobile.urlHistory.stack.length === 0 ? "none" : undefined,
  4161. // default options for the changPage calls made after examining the current state
  4162. // of the page and the hash, NOTE that the transition is derived from the previous
  4163. // history entry
  4164. changePageOptions = {
  4165. changeHash: false,
  4166. fromHashChange: true,
  4167. reverse: data.direction === "back"
  4168. };
  4169. $.extend( changePageOptions, data, {
  4170. transition: (urlHistory.getLast() || {}).transition || transition
  4171. });
  4172. // special case for dialogs
  4173. if ( urlHistory.activeIndex > 0 && to.indexOf( dialogHashKey ) > -1 && urlHistory.initialDst !== to ) {
  4174. // If current active page is not a dialog skip the dialog and continue
  4175. // in the same direction
  4176. if ( $.mobile.activePage && !$.mobile.activePage.is( ".ui-dialog" ) ) {
  4177. //determine if we're heading forward or backward and continue accordingly past
  4178. //the current dialog
  4179. if( data.direction === "back" ) {
  4180. $.mobile.back();
  4181. } else {
  4182. window.history.forward();
  4183. }
  4184. // prevent changePage call
  4185. return;
  4186. } else {
  4187. // if the current active page is a dialog and we're navigating
  4188. // to a dialog use the dialog objected saved in the stack
  4189. to = data.pageUrl;
  4190. var active = $.mobile.urlHistory.getActive();
  4191. // make sure to set the role, transition and reversal
  4192. // as most of this is lost by the domCache cleaning
  4193. $.extend( changePageOptions, {
  4194. role: active.role,
  4195. transition: active.transition,
  4196. reverse: data.direction === "back"
  4197. });
  4198. }
  4199. }
  4200. //if to is defined, load it
  4201. if ( to ) {
  4202. // At this point, 'to' can be one of 3 things, a cached page element from
  4203. // a history stack entry, an id, or site-relative/absolute URL. If 'to' is
  4204. // an id, we need to resolve it against the documentBase, not the location.href,
  4205. // since the hashchange could've been the result of a forward/backward navigation
  4206. // that crosses from an external page/dialog to an internal page/dialog.
  4207. to = !path.isPath( to ) ? ( path.makeUrlAbsolute( '#' + to, documentBase ) ) : to;
  4208. // If we're about to go to an initial URL that contains a reference to a non-existent
  4209. // internal page, go to the first page instead. We know that the initial hash refers to a
  4210. // non-existent page, because the initial hash did not end up in the initial urlHistory entry
  4211. if ( to === path.makeUrlAbsolute( '#' + urlHistory.initialDst, documentBase ) &&
  4212. urlHistory.stack.length && urlHistory.stack[0].url !== urlHistory.initialDst.replace( dialogHashKey, "" ) ) {
  4213. to = $.mobile.firstPage;
  4214. }
  4215. $.mobile.changePage( to, changePageOptions );
  4216. } else {
  4217. //there's no hash, go to the first page in the dom
  4218. $.mobile.changePage( $.mobile.firstPage, changePageOptions );
  4219. }
  4220. };
  4221. // TODO roll the logic here into the handleHashChange method
  4222. $window.bind( "navigate", function( e, data ) {
  4223. var url;
  4224. if ( e.originalEvent && e.originalEvent.isDefaultPrevented() ) {
  4225. return;
  4226. }
  4227. url = $.event.special.navigate.originalEventName.indexOf( "hashchange" ) > -1 ? data.state.hash : data.state.url;
  4228. if( !url ) {
  4229. url = $.mobile.path.parseLocation().hash;
  4230. }
  4231. if( !url || url === "#" || url.indexOf( "#" + $.mobile.path.uiStateKey ) === 0 ){
  4232. url = location.href;
  4233. }
  4234. $.mobile._handleHashChange( url, data.state );
  4235. });
  4236. //set page min-heights to be device specific
  4237. $.mobile.document.bind( "pageshow", $.mobile.resetActivePageHeight );
  4238. $.mobile.window.bind( "throttledresize", $.mobile.resetActivePageHeight );
  4239. };//navreadyDeferred done callback
  4240. $( function() { domreadyDeferred.resolve(); } );
  4241. $.when( domreadyDeferred, $.mobile.navreadyDeferred ).done( function() { $.mobile._registerInternalEvents(); } );
  4242. })( jQuery );
  4243. /*
  4244. * fallback transition for flip in non-3D supporting browsers (which tend to handle complex transitions poorly in general
  4245. */
  4246. (function( $, window, undefined ) {
  4247. $.mobile.transitionFallbacks.flip = "fade";
  4248. })( jQuery, this );
  4249. /*
  4250. * fallback transition for flow in non-3D supporting browsers (which tend to handle complex transitions poorly in general
  4251. */
  4252. (function( $, window, undefined ) {
  4253. $.mobile.transitionFallbacks.flow = "fade";
  4254. })( jQuery, this );
  4255. /*
  4256. * fallback transition for pop in non-3D supporting browsers (which tend to handle complex transitions poorly in general
  4257. */
  4258. (function( $, window, undefined ) {
  4259. $.mobile.transitionFallbacks.pop = "fade";
  4260. })( jQuery, this );
  4261. /*
  4262. * fallback transition for slide in non-3D supporting browsers (which tend to handle complex transitions poorly in general
  4263. */
  4264. (function( $, window, undefined ) {
  4265. // Use the simultaneous transitions handler for slide transitions
  4266. $.mobile.transitionHandlers.slide = $.mobile.transitionHandlers.simultaneous;
  4267. // Set the slide transitions's fallback to "fade"
  4268. $.mobile.transitionFallbacks.slide = "fade";
  4269. })( jQuery, this );
  4270. /*
  4271. * fallback transition for slidedown in non-3D supporting browsers (which tend to handle complex transitions poorly in general
  4272. */
  4273. (function( $, window, undefined ) {
  4274. $.mobile.transitionFallbacks.slidedown = "fade";
  4275. })( jQuery, this );
  4276. /*
  4277. * fallback transition for slidefade in non-3D supporting browsers (which tend to handle complex transitions poorly in general
  4278. */
  4279. (function( $, window, undefined ) {
  4280. // Set the slide transitions's fallback to "fade"
  4281. $.mobile.transitionFallbacks.slidefade = "fade";
  4282. })( jQuery, this );
  4283. /*
  4284. * fallback transition for slideup in non-3D supporting browsers (which tend to handle complex transitions poorly in general
  4285. */
  4286. (function( $, window, undefined ) {
  4287. $.mobile.transitionFallbacks.slideup = "fade";
  4288. })( jQuery, this );
  4289. /*
  4290. * fallback transition for turn in non-3D supporting browsers (which tend to handle complex transitions poorly in general
  4291. */
  4292. (function( $, window, undefined ) {
  4293. $.mobile.transitionFallbacks.turn = "fade";
  4294. })( jQuery, this );
  4295. (function( $, undefined ) {
  4296. $.mobile.page.prototype.options.degradeInputs = {
  4297. color: false,
  4298. date: false,
  4299. datetime: false,
  4300. "datetime-local": false,
  4301. email: false,
  4302. month: false,
  4303. number: false,
  4304. range: "number",
  4305. search: "text",
  4306. tel: false,
  4307. time: false,
  4308. url: false,
  4309. week: false
  4310. };
  4311. //auto self-init widgets
  4312. $.mobile.document.bind( "pagecreate create", function( e ) {
  4313. var page = $.mobile.closestPageData( $( e.target ) ), options;
  4314. if ( !page ) {
  4315. return;
  4316. }
  4317. options = page.options;
  4318. // degrade inputs to avoid poorly implemented native functionality
  4319. $( e.target ).find( "input" ).not( page.keepNativeSelector() ).each(function() {
  4320. var $this = $( this ),
  4321. type = this.getAttribute( "type" ),
  4322. optType = options.degradeInputs[ type ] || "text";
  4323. if ( options.degradeInputs[ type ] ) {
  4324. var html = $( "<div>" ).html( $this.clone() ).html(),
  4325. // In IE browsers, the type sometimes doesn't exist in the cloned markup, so we replace the closing tag instead
  4326. hasType = html.indexOf( " type=" ) > -1,
  4327. findstr = hasType ? /\s+type=["']?\w+['"]?/ : /\/?>/,
  4328. repstr = " type=\"" + optType + "\" data-" + $.mobile.ns + "type=\"" + type + "\"" + ( hasType ? "" : ">" );
  4329. $this.replaceWith( html.replace( findstr, repstr ) );
  4330. }
  4331. });
  4332. });
  4333. })( jQuery );
  4334. (function( $, window, undefined ) {
  4335. $.widget( "mobile.dialog", $.mobile.widget, {
  4336. options: {
  4337. closeBtn: "left",
  4338. closeBtnText: "Close",
  4339. overlayTheme: "a",
  4340. corners: true,
  4341. initSelector: ":jqmData(role='dialog')"
  4342. },
  4343. // Override the theme set by the page plugin on pageshow
  4344. _handlePageBeforeShow: function() {
  4345. this._isCloseable = true;
  4346. if ( this.options.overlayTheme ) {
  4347. this.element
  4348. .page( "removeContainerBackground" )
  4349. .page( "setContainerBackground", this.options.overlayTheme );
  4350. }
  4351. },
  4352. _handlePageBeforeHide: function() {
  4353. this._isCloseable = false;
  4354. },
  4355. _create: function() {
  4356. var self = this,
  4357. $el = this.element,
  4358. cornerClass = !!this.options.corners ? " ui-corner-all" : "",
  4359. dialogWrap = $( "<div/>", {
  4360. "role" : "dialog",
  4361. "class" : "ui-dialog-contain ui-overlay-shadow" + cornerClass
  4362. });
  4363. $el.addClass( "ui-dialog ui-overlay-" + this.options.overlayTheme );
  4364. // Class the markup for dialog styling
  4365. // Set aria role
  4366. $el.wrapInner( dialogWrap );
  4367. /* bind events
  4368. - clicks and submits should use the closing transition that the dialog opened with
  4369. unless a data-transition is specified on the link/form
  4370. - if the click was on the close button, or the link has a data-rel="back" it'll go back in history naturally
  4371. */
  4372. $el.bind( "vclick submit", function( event ) {
  4373. var $target = $( event.target ).closest( event.type === "vclick" ? "a" : "form" ),
  4374. active;
  4375. if ( $target.length && !$target.jqmData( "transition" ) ) {
  4376. active = $.mobile.urlHistory.getActive() || {};
  4377. $target.attr( "data-" + $.mobile.ns + "transition", ( active.transition || $.mobile.defaultDialogTransition ) )
  4378. .attr( "data-" + $.mobile.ns + "direction", "reverse" );
  4379. }
  4380. });
  4381. this._on( $el, {
  4382. pagebeforeshow: "_handlePageBeforeShow",
  4383. pagebeforehide: "_handlePageBeforeHide"
  4384. });
  4385. $.extend( this, {
  4386. _createComplete: false
  4387. });
  4388. this._setCloseBtn( this.options.closeBtn );
  4389. },
  4390. _setCloseBtn: function( value ) {
  4391. var self = this, btn, location;
  4392. if ( this._headerCloseButton ) {
  4393. this._headerCloseButton.remove();
  4394. this._headerCloseButton = null;
  4395. }
  4396. if ( value !== "none" ) {
  4397. // Sanitize value
  4398. location = ( value === "left" ? "left" : "right" );
  4399. btn = $( "<a href='#' class='ui-btn-" + location + "' data-" + $.mobile.ns + "icon='delete' data-" + $.mobile.ns + "iconpos='notext'>"+ this.options.closeBtnText + "</a>" );
  4400. this.element.children().find( ":jqmData(role='header')" ).first().prepend( btn );
  4401. if ( this._createComplete && $.fn.buttonMarkup ) {
  4402. btn.buttonMarkup();
  4403. }
  4404. this._createComplete = true;
  4405. // this must be an anonymous function so that select menu dialogs can replace
  4406. // the close method. This is a change from previously just defining data-rel=back
  4407. // on the button and letting nav handle it
  4408. //
  4409. // Use click rather than vclick in order to prevent the possibility of unintentionally
  4410. // reopening the dialog if the dialog opening item was directly under the close button.
  4411. btn.bind( "click", function() {
  4412. self.close();
  4413. });
  4414. this._headerCloseButton = btn;
  4415. }
  4416. },
  4417. _setOption: function( key, value ) {
  4418. if ( key === "closeBtn" ) {
  4419. this._setCloseBtn( value );
  4420. }
  4421. this._super( key, value );
  4422. },
  4423. // Close method goes back in history
  4424. close: function() {
  4425. var idx, dst, hist = $.mobile.navigate.history;
  4426. if ( this._isCloseable ) {
  4427. this._isCloseable = false;
  4428. // If the hash listening is enabled and there is at least one preceding history
  4429. // entry it's ok to go back. Initial pages with the dialog hash state are an example
  4430. // where the stack check is necessary
  4431. if ( $.mobile.hashListeningEnabled && hist.activeIndex > 0 ) {
  4432. $.mobile.back();
  4433. } else {
  4434. idx = Math.max( 0, hist.activeIndex - 1 );
  4435. dst = hist.stack[ idx ].pageUrl || hist.stack[ idx ].url;
  4436. hist.previousIndex = hist.activeIndex;
  4437. hist.activeIndex = idx;
  4438. if ( !$.mobile.path.isPath( dst ) ) {
  4439. dst = $.mobile.path.makeUrlAbsolute( "#" + dst );
  4440. }
  4441. $.mobile.changePage( dst, { direction: "back", changeHash: false, fromHashChange: true } );
  4442. }
  4443. }
  4444. }
  4445. });
  4446. //auto self-init widgets
  4447. $.mobile.document.delegate( $.mobile.dialog.prototype.options.initSelector, "pagecreate", function() {
  4448. $.mobile.dialog.prototype.enhance( this );
  4449. });
  4450. })( jQuery, this );
  4451. (function( $, undefined ) {
  4452. $.mobile.page.prototype.options.backBtnText = "Back";
  4453. $.mobile.page.prototype.options.addBackBtn = false;
  4454. $.mobile.page.prototype.options.backBtnTheme = null;
  4455. $.mobile.page.prototype.options.headerTheme = "a";
  4456. $.mobile.page.prototype.options.footerTheme = "a";
  4457. $.mobile.page.prototype.options.contentTheme = null;
  4458. // NOTE bind used to force this binding to run before the buttonMarkup binding
  4459. // which expects .ui-footer top be applied in its gigantic selector
  4460. // TODO remove the buttonMarkup giant selector and move it to the various modules
  4461. // on which it depends
  4462. $.mobile.document.bind( "pagecreate", function( e ) {
  4463. var $page = $( e.target ),
  4464. o = $page.data( "mobile-page" ).options,
  4465. pageRole = $page.jqmData( "role" ),
  4466. pageTheme = o.theme;
  4467. $( ":jqmData(role='header'), :jqmData(role='footer'), :jqmData(role='content')", $page )
  4468. .jqmEnhanceable()
  4469. .each(function() {
  4470. var $this = $( this ),
  4471. role = $this.jqmData( "role" ),
  4472. theme = $this.jqmData( "theme" ),
  4473. contentTheme = theme || o.contentTheme || ( pageRole === "dialog" && pageTheme ),
  4474. $headeranchors,
  4475. leftbtn,
  4476. rightbtn,
  4477. backBtn;
  4478. $this.addClass( "ui-" + role );
  4479. //apply theming and markup modifications to page,header,content,footer
  4480. if ( role === "header" || role === "footer" ) {
  4481. var thisTheme = theme || ( role === "header" ? o.headerTheme : o.footerTheme ) || pageTheme;
  4482. $this
  4483. //add theme class
  4484. .addClass( "ui-bar-" + thisTheme )
  4485. // Add ARIA role
  4486. .attr( "role", role === "header" ? "banner" : "contentinfo" );
  4487. if ( role === "header") {
  4488. // Right,left buttons
  4489. $headeranchors = $this.children( "a, button" );
  4490. leftbtn = $headeranchors.hasClass( "ui-btn-left" );
  4491. rightbtn = $headeranchors.hasClass( "ui-btn-right" );
  4492. leftbtn = leftbtn || $headeranchors.eq( 0 ).not( ".ui-btn-right" ).addClass( "ui-btn-left" ).length;
  4493. rightbtn = rightbtn || $headeranchors.eq( 1 ).addClass( "ui-btn-right" ).length;
  4494. }
  4495. // Auto-add back btn on pages beyond first view
  4496. if ( o.addBackBtn &&
  4497. role === "header" &&
  4498. $( ".ui-page" ).length > 1 &&
  4499. $page.jqmData( "url" ) !== $.mobile.path.stripHash( location.hash ) &&
  4500. !leftbtn ) {
  4501. backBtn = $( "<a href='javascript:void(0);' class='ui-btn-left' data-"+ $.mobile.ns +"rel='back' data-"+ $.mobile.ns +"icon='arrow-l'>"+ o.backBtnText +"</a>" )
  4502. // If theme is provided, override default inheritance
  4503. .attr( "data-"+ $.mobile.ns +"theme", o.backBtnTheme || thisTheme )
  4504. .prependTo( $this );
  4505. }
  4506. // Page title
  4507. $this.children( "h1, h2, h3, h4, h5, h6" )
  4508. .addClass( "ui-title" )
  4509. // Regardless of h element number in src, it becomes h1 for the enhanced page
  4510. .attr({
  4511. "role": "heading",
  4512. "aria-level": "1"
  4513. });
  4514. } else if ( role === "content" ) {
  4515. if ( contentTheme ) {
  4516. $this.addClass( "ui-body-" + ( contentTheme ) );
  4517. }
  4518. // Add ARIA role
  4519. $this.attr( "role", "main" );
  4520. }
  4521. });
  4522. });
  4523. })( jQuery );
  4524. (function( $, undefined ) {
  4525. // This function calls getAttribute, which should be safe for data-* attributes
  4526. var getAttrFixed = function( e, key ) {
  4527. var value = e.getAttribute( key );
  4528. return value === "true" ? true :
  4529. value === "false" ? false :
  4530. value === null ? undefined : value;
  4531. };
  4532. $.fn.buttonMarkup = function( options ) {
  4533. var $workingSet = this,
  4534. nsKey = "data-" + $.mobile.ns,
  4535. key;
  4536. // Enforce options to be of type string
  4537. options = ( options && ( $.type( options ) === "object" ) )? options : {};
  4538. for ( var i = 0; i < $workingSet.length; i++ ) {
  4539. var el = $workingSet.eq( i ),
  4540. e = el[ 0 ],
  4541. o = $.extend( {}, $.fn.buttonMarkup.defaults, {
  4542. icon: options.icon !== undefined ? options.icon : getAttrFixed( e, nsKey + "icon" ),
  4543. iconpos: options.iconpos !== undefined ? options.iconpos : getAttrFixed( e, nsKey + "iconpos" ),
  4544. theme: options.theme !== undefined ? options.theme : getAttrFixed( e, nsKey + "theme" ) || $.mobile.getInheritedTheme( el, "c" ),
  4545. inline: options.inline !== undefined ? options.inline : getAttrFixed( e, nsKey + "inline" ),
  4546. shadow: options.shadow !== undefined ? options.shadow : getAttrFixed( e, nsKey + "shadow" ),
  4547. corners: options.corners !== undefined ? options.corners : getAttrFixed( e, nsKey + "corners" ),
  4548. iconshadow: options.iconshadow !== undefined ? options.iconshadow : getAttrFixed( e, nsKey + "iconshadow" ),
  4549. mini: options.mini !== undefined ? options.mini : getAttrFixed( e, nsKey + "mini" )
  4550. }, options ),
  4551. // Classes Defined
  4552. innerClass = "ui-btn-inner",
  4553. textClass = "ui-btn-text",
  4554. buttonClass, iconClass,
  4555. hover = false,
  4556. state = "up",
  4557. // Button inner markup
  4558. buttonInner,
  4559. buttonText,
  4560. buttonIcon,
  4561. buttonElements;
  4562. for ( key in o ) {
  4563. if ( o[ key ] === undefined || o[ key ] === null ) {
  4564. el.removeAttr( nsKey + key );
  4565. } else {
  4566. e.setAttribute( nsKey + key, o[ key ] );
  4567. }
  4568. }
  4569. // Check if this element is already enhanced
  4570. buttonElements = $.data( ( ( e.tagName === "INPUT" || e.tagName === "BUTTON" ) ? e.parentNode : e ), "buttonElements" );
  4571. if ( buttonElements ) {
  4572. e = buttonElements.outer;
  4573. el = $( e );
  4574. buttonInner = buttonElements.inner;
  4575. buttonText = buttonElements.text;
  4576. // We will recreate this icon below
  4577. $( buttonElements.icon ).remove();
  4578. buttonElements.icon = null;
  4579. hover = buttonElements.hover;
  4580. state = buttonElements.state;
  4581. }
  4582. else {
  4583. buttonInner = document.createElement( o.wrapperEls );
  4584. buttonText = document.createElement( o.wrapperEls );
  4585. }
  4586. buttonIcon = o.icon ? document.createElement( "span" ) : null;
  4587. if ( attachEvents && !buttonElements ) {
  4588. attachEvents();
  4589. }
  4590. // if not, try to find closest theme container
  4591. if ( !o.theme ) {
  4592. o.theme = $.mobile.getInheritedTheme( el, "c" );
  4593. }
  4594. buttonClass = "ui-btn ";
  4595. buttonClass += ( hover ? "ui-btn-hover-" + o.theme : "" );
  4596. buttonClass += ( state ? " ui-btn-" + state + "-" + o.theme : "" );
  4597. buttonClass += o.shadow ? " ui-shadow" : "";
  4598. buttonClass += o.corners ? " ui-btn-corner-all" : "";
  4599. if ( o.mini !== undefined ) {
  4600. // Used to control styling in headers/footers, where buttons default to `mini` style.
  4601. buttonClass += o.mini === true ? " ui-mini" : " ui-fullsize";
  4602. }
  4603. if ( o.inline !== undefined ) {
  4604. // Used to control styling in headers/footers, where buttons default to `inline` style.
  4605. buttonClass += o.inline === true ? " ui-btn-inline" : " ui-btn-block";
  4606. }
  4607. if ( o.icon ) {
  4608. o.icon = "ui-icon-" + o.icon;
  4609. o.iconpos = o.iconpos || "left";
  4610. iconClass = "ui-icon " + o.icon;
  4611. if ( o.iconshadow ) {
  4612. iconClass += " ui-icon-shadow";
  4613. }
  4614. }
  4615. if ( o.iconpos ) {
  4616. buttonClass += " ui-btn-icon-" + o.iconpos;
  4617. if ( o.iconpos === "notext" && !el.attr( "title" ) ) {
  4618. el.attr( "title", el.getEncodedText() );
  4619. }
  4620. }
  4621. if ( buttonElements ) {
  4622. el.removeClass( buttonElements.bcls || "" );
  4623. }
  4624. el.removeClass( "ui-link" ).addClass( buttonClass );
  4625. buttonInner.className = innerClass;
  4626. buttonText.className = textClass;
  4627. if ( !buttonElements ) {
  4628. buttonInner.appendChild( buttonText );
  4629. }
  4630. if ( buttonIcon ) {
  4631. buttonIcon.className = iconClass;
  4632. if ( !( buttonElements && buttonElements.icon ) ) {
  4633. buttonIcon.innerHTML = "&#160;";
  4634. buttonInner.appendChild( buttonIcon );
  4635. }
  4636. }
  4637. while ( e.firstChild && !buttonElements ) {
  4638. buttonText.appendChild( e.firstChild );
  4639. }
  4640. if ( !buttonElements ) {
  4641. e.appendChild( buttonInner );
  4642. }
  4643. // Assign a structure containing the elements of this button to the elements of this button. This
  4644. // will allow us to recognize this as an already-enhanced button in future calls to buttonMarkup().
  4645. buttonElements = {
  4646. hover : hover,
  4647. state : state,
  4648. bcls : buttonClass,
  4649. outer : e,
  4650. inner : buttonInner,
  4651. text : buttonText,
  4652. icon : buttonIcon
  4653. };
  4654. $.data( e, 'buttonElements', buttonElements );
  4655. $.data( buttonInner, 'buttonElements', buttonElements );
  4656. $.data( buttonText, 'buttonElements', buttonElements );
  4657. if ( buttonIcon ) {
  4658. $.data( buttonIcon, 'buttonElements', buttonElements );
  4659. }
  4660. }
  4661. return this;
  4662. };
  4663. $.fn.buttonMarkup.defaults = {
  4664. corners: true,
  4665. shadow: true,
  4666. iconshadow: true,
  4667. wrapperEls: "span"
  4668. };
  4669. function closestEnabledButton( element ) {
  4670. var cname;
  4671. while ( element ) {
  4672. // Note that we check for typeof className below because the element we
  4673. // handed could be in an SVG DOM where className on SVG elements is defined to
  4674. // be of a different type (SVGAnimatedString). We only operate on HTML DOM
  4675. // elements, so we look for plain "string".
  4676. cname = ( typeof element.className === 'string' ) && ( element.className + ' ' );
  4677. if ( cname && cname.indexOf( "ui-btn " ) > -1 && cname.indexOf( "ui-disabled " ) < 0 ) {
  4678. break;
  4679. }
  4680. element = element.parentNode;
  4681. }
  4682. return element;
  4683. }
  4684. function updateButtonClass( $btn, classToRemove, classToAdd, hover, state ) {
  4685. var buttonElements = $.data( $btn[ 0 ], "buttonElements" );
  4686. $btn.removeClass( classToRemove ).addClass( classToAdd );
  4687. if ( buttonElements ) {
  4688. buttonElements.bcls = $( document.createElement( "div" ) )
  4689. .addClass( buttonElements.bcls + " " + classToAdd )
  4690. .removeClass( classToRemove )
  4691. .attr( "class" );
  4692. if ( hover !== undefined ) {
  4693. buttonElements.hover = hover;
  4694. }
  4695. buttonElements.state = state;
  4696. }
  4697. }
  4698. var attachEvents = function() {
  4699. var hoverDelay = $.mobile.buttonMarkup.hoverDelay, hov, foc;
  4700. $.mobile.document.bind( {
  4701. "vmousedown vmousecancel vmouseup vmouseover vmouseout focus blur scrollstart": function( event ) {
  4702. var theme,
  4703. $btn = $( closestEnabledButton( event.target ) ),
  4704. isTouchEvent = event.originalEvent && /^touch/.test( event.originalEvent.type ),
  4705. evt = event.type;
  4706. if ( $btn.length ) {
  4707. theme = $btn.attr( "data-" + $.mobile.ns + "theme" );
  4708. if ( evt === "vmousedown" ) {
  4709. if ( isTouchEvent ) {
  4710. // Use a short delay to determine if the user is scrolling before highlighting
  4711. hov = setTimeout( function() {
  4712. updateButtonClass( $btn, "ui-btn-up-" + theme, "ui-btn-down-" + theme, undefined, "down" );
  4713. }, hoverDelay );
  4714. } else {
  4715. updateButtonClass( $btn, "ui-btn-up-" + theme, "ui-btn-down-" + theme, undefined, "down" );
  4716. }
  4717. } else if ( evt === "vmousecancel" || evt === "vmouseup" ) {
  4718. updateButtonClass( $btn, "ui-btn-down-" + theme, "ui-btn-up-" + theme, undefined, "up" );
  4719. } else if ( evt === "vmouseover" || evt === "focus" ) {
  4720. if ( isTouchEvent ) {
  4721. // Use a short delay to determine if the user is scrolling before highlighting
  4722. foc = setTimeout( function() {
  4723. updateButtonClass( $btn, "ui-btn-up-" + theme, "ui-btn-hover-" + theme, true, "" );
  4724. }, hoverDelay );
  4725. } else {
  4726. updateButtonClass( $btn, "ui-btn-up-" + theme, "ui-btn-hover-" + theme, true, "" );
  4727. }
  4728. } else if ( evt === "vmouseout" || evt === "blur" || evt === "scrollstart" ) {
  4729. updateButtonClass( $btn, "ui-btn-hover-" + theme + " ui-btn-down-" + theme, "ui-btn-up-" + theme, false, "up" );
  4730. if ( hov ) {
  4731. clearTimeout( hov );
  4732. }
  4733. if ( foc ) {
  4734. clearTimeout( foc );
  4735. }
  4736. }
  4737. }
  4738. },
  4739. "focusin focus": function( event ) {
  4740. $( closestEnabledButton( event.target ) ).addClass( $.mobile.focusClass );
  4741. },
  4742. "focusout blur": function( event ) {
  4743. $( closestEnabledButton( event.target ) ).removeClass( $.mobile.focusClass );
  4744. }
  4745. });
  4746. attachEvents = null;
  4747. };
  4748. //links in bars, or those with data-role become buttons
  4749. //auto self-init widgets
  4750. $.mobile.document.bind( "pagecreate create", function( e ) {
  4751. $( ":jqmData(role='button'), .ui-bar > a, .ui-header > a, .ui-footer > a, .ui-bar > :jqmData(role='controlgroup') > a", e.target )
  4752. .jqmEnhanceable()
  4753. .not( "button, input, .ui-btn, :jqmData(role='none'), :jqmData(role='nojs')" )
  4754. .buttonMarkup();
  4755. });
  4756. })( jQuery );
  4757. (function( $, undefined ) {
  4758. $.widget( "mobile.collapsible", $.mobile.widget, {
  4759. options: {
  4760. expandCueText: " click to expand contents",
  4761. collapseCueText: " click to collapse contents",
  4762. collapsed: true,
  4763. heading: "h1,h2,h3,h4,h5,h6,legend",
  4764. collapsedIcon: "plus",
  4765. expandedIcon: "minus",
  4766. iconpos: "left",
  4767. theme: null,
  4768. contentTheme: null,
  4769. inset: true,
  4770. corners: true,
  4771. mini: false,
  4772. initSelector: ":jqmData(role='collapsible')"
  4773. },
  4774. _create: function() {
  4775. var $el = this.element,
  4776. o = this.options,
  4777. collapsible = $el.addClass( "ui-collapsible" ),
  4778. collapsibleHeading = $el.children( o.heading ).first(),
  4779. collapsibleContent = collapsible.wrapInner( "<div class='ui-collapsible-content'></div>" ).children( ".ui-collapsible-content" ),
  4780. collapsibleSet = $el.closest( ":jqmData(role='collapsible-set')" ).addClass( "ui-collapsible-set" ),
  4781. collapsibleClasses = "";
  4782. // Replace collapsibleHeading if it's a legend
  4783. if ( collapsibleHeading.is( "legend" ) ) {
  4784. collapsibleHeading = $( "<div role='heading'>"+ collapsibleHeading.html() +"</div>" ).insertBefore( collapsibleHeading );
  4785. collapsibleHeading.next().remove();
  4786. }
  4787. // If we are in a collapsible set
  4788. if ( collapsibleSet.length ) {
  4789. // Inherit the theme from collapsible-set
  4790. if ( !o.theme ) {
  4791. o.theme = collapsibleSet.jqmData( "theme" ) || $.mobile.getInheritedTheme( collapsibleSet, "c" );
  4792. }
  4793. // Inherit the content-theme from collapsible-set
  4794. if ( !o.contentTheme ) {
  4795. o.contentTheme = collapsibleSet.jqmData( "content-theme" );
  4796. }
  4797. // Get the preference for collapsed icon in the set, but override with data- attribute on the individual collapsible
  4798. o.collapsedIcon = $el.jqmData( "collapsed-icon" ) || collapsibleSet.jqmData( "collapsed-icon" ) || o.collapsedIcon;
  4799. // Get the preference for expanded icon in the set, but override with data- attribute on the individual collapsible
  4800. o.expandedIcon = $el.jqmData( "expanded-icon" ) || collapsibleSet.jqmData( "expanded-icon" ) || o.expandedIcon;
  4801. // Gets the preference icon position in the set, but override with data- attribute on the individual collapsible
  4802. o.iconpos = $el.jqmData( "iconpos" ) || collapsibleSet.jqmData( "iconpos" ) || o.iconpos;
  4803. // Inherit the preference for inset from collapsible-set or set the default value to ensure equalty within a set
  4804. if ( collapsibleSet.jqmData( "inset" ) !== undefined ) {
  4805. o.inset = collapsibleSet.jqmData( "inset" );
  4806. } else {
  4807. o.inset = true;
  4808. }
  4809. // Set corners for individual collapsibles to false when in a collapsible-set
  4810. o.corners = false;
  4811. // Gets the preference for mini in the set
  4812. if ( !o.mini ) {
  4813. o.mini = collapsibleSet.jqmData( "mini" );
  4814. }
  4815. } else {
  4816. // get inherited theme if not a set and no theme has been set
  4817. if ( !o.theme ) {
  4818. o.theme = $.mobile.getInheritedTheme( $el, "c" );
  4819. }
  4820. }
  4821. if ( !!o.inset ) {
  4822. collapsibleClasses += " ui-collapsible-inset";
  4823. if ( !!o.corners ) {
  4824. collapsibleClasses += " ui-corner-all" ;
  4825. }
  4826. }
  4827. if ( o.contentTheme ) {
  4828. collapsibleClasses += " ui-collapsible-themed-content";
  4829. collapsibleContent.addClass( "ui-body-" + o.contentTheme );
  4830. }
  4831. if ( collapsibleClasses !== "" ) {
  4832. collapsible.addClass( collapsibleClasses );
  4833. }
  4834. collapsibleHeading
  4835. //drop heading in before content
  4836. .insertBefore( collapsibleContent )
  4837. //modify markup & attributes
  4838. .addClass( "ui-collapsible-heading" )
  4839. .append( "<span class='ui-collapsible-heading-status'></span>" )
  4840. .wrapInner( "<a href='#' class='ui-collapsible-heading-toggle'></a>" )
  4841. .find( "a" )
  4842. .first()
  4843. .buttonMarkup({
  4844. shadow: false,
  4845. corners: false,
  4846. iconpos: o.iconpos,
  4847. icon: o.collapsedIcon,
  4848. mini: o.mini,
  4849. theme: o.theme
  4850. });
  4851. //events
  4852. collapsible
  4853. .bind( "expand collapse", function( event ) {
  4854. if ( !event.isDefaultPrevented() ) {
  4855. var $this = $( this ),
  4856. isCollapse = ( event.type === "collapse" );
  4857. event.preventDefault();
  4858. collapsibleHeading
  4859. .toggleClass( "ui-collapsible-heading-collapsed", isCollapse )
  4860. .find( ".ui-collapsible-heading-status" )
  4861. .text( isCollapse ? o.expandCueText : o.collapseCueText )
  4862. .end()
  4863. .find( ".ui-icon" )
  4864. .toggleClass( "ui-icon-" + o.expandedIcon, !isCollapse )
  4865. // logic or cause same icon for expanded/collapsed state would remove the ui-icon-class
  4866. .toggleClass( "ui-icon-" + o.collapsedIcon, ( isCollapse || o.expandedIcon === o.collapsedIcon ) )
  4867. .end()
  4868. .find( "a" ).first().removeClass( $.mobile.activeBtnClass );
  4869. $this.toggleClass( "ui-collapsible-collapsed", isCollapse );
  4870. collapsibleContent.toggleClass( "ui-collapsible-content-collapsed", isCollapse ).attr( "aria-hidden", isCollapse );
  4871. collapsibleContent.trigger( "updatelayout" );
  4872. }
  4873. })
  4874. .trigger( o.collapsed ? "collapse" : "expand" );
  4875. collapsibleHeading
  4876. .bind( "tap", function( event ) {
  4877. collapsibleHeading.find( "a" ).first().addClass( $.mobile.activeBtnClass );
  4878. })
  4879. .bind( "click", function( event ) {
  4880. var type = collapsibleHeading.is( ".ui-collapsible-heading-collapsed" ) ? "expand" : "collapse";
  4881. collapsible.trigger( type );
  4882. event.preventDefault();
  4883. event.stopPropagation();
  4884. });
  4885. }
  4886. });
  4887. //auto self-init widgets
  4888. $.mobile.document.bind( "pagecreate create", function( e ) {
  4889. $.mobile.collapsible.prototype.enhanceWithin( e.target );
  4890. });
  4891. })( jQuery );
  4892. (function( $, undefined ) {
  4893. $.mobile.behaviors.addFirstLastClasses = {
  4894. _getVisibles: function( $els, create ) {
  4895. var visibles;
  4896. if ( create ) {
  4897. visibles = $els.not( ".ui-screen-hidden" );
  4898. } else {
  4899. visibles = $els.filter( ":visible" );
  4900. if ( visibles.length === 0 ) {
  4901. visibles = $els.not( ".ui-screen-hidden" );
  4902. }
  4903. }
  4904. return visibles;
  4905. },
  4906. _addFirstLastClasses: function( $els, $visibles, create ) {
  4907. $els.removeClass( "ui-first-child ui-last-child" );
  4908. $visibles.eq( 0 ).addClass( "ui-first-child" ).end().last().addClass( "ui-last-child" );
  4909. if ( !create ) {
  4910. this.element.trigger( "updatelayout" );
  4911. }
  4912. }
  4913. };
  4914. })( jQuery );
  4915. (function( $, undefined ) {
  4916. $.widget( "mobile.collapsibleset", $.mobile.widget, $.extend( {
  4917. options: {
  4918. initSelector: ":jqmData(role='collapsible-set')"
  4919. },
  4920. _create: function() {
  4921. var $el = this.element.addClass( "ui-collapsible-set" ),
  4922. o = this.options;
  4923. // Inherit the theme from collapsible-set
  4924. if ( !o.theme ) {
  4925. o.theme = $.mobile.getInheritedTheme( $el, "c" );
  4926. }
  4927. // Inherit the content-theme from collapsible-set
  4928. if ( !o.contentTheme ) {
  4929. o.contentTheme = $el.jqmData( "content-theme" );
  4930. }
  4931. // Inherit the corner styling from collapsible-set
  4932. if ( !o.corners ) {
  4933. o.corners = $el.jqmData( "corners" );
  4934. }
  4935. if ( $el.jqmData( "inset" ) !== undefined ) {
  4936. o.inset = $el.jqmData( "inset" );
  4937. }
  4938. o.inset = o.inset !== undefined ? o.inset : true;
  4939. o.corners = o.corners !== undefined ? o.corners : true;
  4940. if ( !!o.corners && !!o.inset ) {
  4941. $el.addClass( "ui-corner-all" );
  4942. }
  4943. // Initialize the collapsible set if it's not already initialized
  4944. if ( !$el.jqmData( "collapsiblebound" ) ) {
  4945. $el
  4946. .jqmData( "collapsiblebound", true )
  4947. .bind( "expand", function( event ) {
  4948. var closestCollapsible = $( event.target )
  4949. .closest( ".ui-collapsible" );
  4950. if ( closestCollapsible.parent().is( ":jqmData(role='collapsible-set')" ) ) {
  4951. closestCollapsible
  4952. .siblings( ".ui-collapsible" )
  4953. .trigger( "collapse" );
  4954. }
  4955. });
  4956. }
  4957. },
  4958. _init: function() {
  4959. var $el = this.element,
  4960. collapsiblesInSet = $el.children( ":jqmData(role='collapsible')" ),
  4961. expanded = collapsiblesInSet.filter( ":jqmData(collapsed='false')" );
  4962. this._refresh( "true" );
  4963. // Because the corners are handled by the collapsible itself and the default state is collapsed
  4964. // That was causing https://github.com/jquery/jquery-mobile/issues/4116
  4965. expanded.trigger( "expand" );
  4966. },
  4967. _refresh: function( create ) {
  4968. var collapsiblesInSet = this.element.children( ":jqmData(role='collapsible')" );
  4969. $.mobile.collapsible.prototype.enhance( collapsiblesInSet.not( ".ui-collapsible" ) );
  4970. this._addFirstLastClasses( collapsiblesInSet, this._getVisibles( collapsiblesInSet, create ), create );
  4971. },
  4972. refresh: function() {
  4973. this._refresh( false );
  4974. }
  4975. }, $.mobile.behaviors.addFirstLastClasses ) );
  4976. //auto self-init widgets
  4977. $.mobile.document.bind( "pagecreate create", function( e ) {
  4978. $.mobile.collapsibleset.prototype.enhanceWithin( e.target );
  4979. });
  4980. })( jQuery );
  4981. (function( $, undefined ) {
  4982. // filter function removes whitespace between label and form element so we can use inline-block (nodeType 3 = text)
  4983. $.fn.fieldcontain = function( options ) {
  4984. return this
  4985. .addClass( "ui-field-contain ui-body ui-br" )
  4986. .contents().filter( function() {
  4987. return ( this.nodeType === 3 && !/\S/.test( this.nodeValue ) );
  4988. }).remove();
  4989. };
  4990. //auto self-init widgets
  4991. $( document ).bind( "pagecreate create", function( e ) {
  4992. $( ":jqmData(role='fieldcontain')", e.target ).jqmEnhanceable().fieldcontain();
  4993. });
  4994. })( jQuery );
  4995. (function( $, undefined ) {
  4996. $.fn.grid = function( options ) {
  4997. return this.each(function() {
  4998. var $this = $( this ),
  4999. o = $.extend({
  5000. grid: null
  5001. }, options ),
  5002. $kids = $this.children(),
  5003. gridCols = { solo:1, a:2, b:3, c:4, d:5 },
  5004. grid = o.grid,
  5005. iterator;
  5006. if ( !grid ) {
  5007. if ( $kids.length <= 5 ) {
  5008. for ( var letter in gridCols ) {
  5009. if ( gridCols[ letter ] === $kids.length ) {
  5010. grid = letter;
  5011. }
  5012. }
  5013. } else {
  5014. grid = "a";
  5015. $this.addClass( "ui-grid-duo" );
  5016. }
  5017. }
  5018. iterator = gridCols[grid];
  5019. $this.addClass( "ui-grid-" + grid );
  5020. $kids.filter( ":nth-child(" + iterator + "n+1)" ).addClass( "ui-block-a" );
  5021. if ( iterator > 1 ) {
  5022. $kids.filter( ":nth-child(" + iterator + "n+2)" ).addClass( "ui-block-b" );
  5023. }
  5024. if ( iterator > 2 ) {
  5025. $kids.filter( ":nth-child(" + iterator + "n+3)" ).addClass( "ui-block-c" );
  5026. }
  5027. if ( iterator > 3 ) {
  5028. $kids.filter( ":nth-child(" + iterator + "n+4)" ).addClass( "ui-block-d" );
  5029. }
  5030. if ( iterator > 4 ) {
  5031. $kids.filter( ":nth-child(" + iterator + "n+5)" ).addClass( "ui-block-e" );
  5032. }
  5033. });
  5034. };
  5035. })( jQuery );
  5036. (function( $, undefined ) {
  5037. $.widget( "mobile.navbar", $.mobile.widget, {
  5038. options: {
  5039. iconpos: "top",
  5040. grid: null,
  5041. initSelector: ":jqmData(role='navbar')"
  5042. },
  5043. _create: function() {
  5044. var $navbar = this.element,
  5045. $navbtns = $navbar.find( "a" ),
  5046. iconpos = $navbtns.filter( ":jqmData(icon)" ).length ?
  5047. this.options.iconpos : undefined;
  5048. $navbar.addClass( "ui-navbar ui-mini" )
  5049. .attr( "role", "navigation" )
  5050. .find( "ul" )
  5051. .jqmEnhanceable()
  5052. .grid({ grid: this.options.grid });
  5053. $navbtns.buttonMarkup({
  5054. corners: false,
  5055. shadow: false,
  5056. inline: true,
  5057. iconpos: iconpos
  5058. });
  5059. $navbar.delegate( "a", "vclick", function( event ) {
  5060. // ui-btn-inner is returned as target
  5061. var target = $( event.target ).is( "a" ) ? $( this ) : $( this ).parent( "a" );
  5062. if ( !target.is( ".ui-disabled, .ui-btn-active" ) ) {
  5063. $navbtns.removeClass( $.mobile.activeBtnClass );
  5064. $( this ).addClass( $.mobile.activeBtnClass );
  5065. // The code below is a workaround to fix #1181
  5066. var activeBtn = $( this );
  5067. $( document ).one( "pagehide", function() {
  5068. activeBtn.removeClass( $.mobile.activeBtnClass );
  5069. });
  5070. }
  5071. });
  5072. // Buttons in the navbar with ui-state-persist class should regain their active state before page show
  5073. $navbar.closest( ".ui-page" ).bind( "pagebeforeshow", function() {
  5074. $navbtns.filter( ".ui-state-persist" ).addClass( $.mobile.activeBtnClass );
  5075. });
  5076. }
  5077. });
  5078. //auto self-init widgets
  5079. $.mobile.document.bind( "pagecreate create", function( e ) {
  5080. $.mobile.navbar.prototype.enhanceWithin( e.target );
  5081. });
  5082. })( jQuery );
  5083. (function( $, undefined ) {
  5084. //Keeps track of the number of lists per page UID
  5085. //This allows support for multiple nested list in the same page
  5086. //https://github.com/jquery/jquery-mobile/issues/1617
  5087. var listCountPerPage = {};
  5088. $.widget( "mobile.listview", $.mobile.widget, $.extend( {
  5089. options: {
  5090. theme: null,
  5091. countTheme: "c",
  5092. headerTheme: "b",
  5093. dividerTheme: "b",
  5094. icon: "arrow-r",
  5095. splitIcon: "arrow-r",
  5096. splitTheme: "b",
  5097. corners: true,
  5098. shadow: true,
  5099. inset: false,
  5100. initSelector: ":jqmData(role='listview')"
  5101. },
  5102. _create: function() {
  5103. var t = this,
  5104. listviewClasses = "";
  5105. listviewClasses += t.options.inset ? " ui-listview-inset" : "";
  5106. if ( !!t.options.inset ) {
  5107. listviewClasses += t.options.corners ? " ui-corner-all" : "";
  5108. listviewClasses += t.options.shadow ? " ui-shadow" : "";
  5109. }
  5110. // create listview markup
  5111. t.element.addClass(function( i, orig ) {
  5112. return orig + " ui-listview" + listviewClasses;
  5113. });
  5114. t.refresh( true );
  5115. },
  5116. // This is a generic utility method for finding the first
  5117. // node with a given nodeName. It uses basic DOM traversal
  5118. // to be fast and is meant to be a substitute for simple
  5119. // $.fn.closest() and $.fn.children() calls on a single
  5120. // element. Note that callers must pass both the lowerCase
  5121. // and upperCase version of the nodeName they are looking for.
  5122. // The main reason for this is that this function will be
  5123. // called many times and we want to avoid having to lowercase
  5124. // the nodeName from the element every time to ensure we have
  5125. // a match. Note that this function lives here for now, but may
  5126. // be moved into $.mobile if other components need a similar method.
  5127. _findFirstElementByTagName: function( ele, nextProp, lcName, ucName ) {
  5128. var dict = {};
  5129. dict[ lcName ] = dict[ ucName ] = true;
  5130. while ( ele ) {
  5131. if ( dict[ ele.nodeName ] ) {
  5132. return ele;
  5133. }
  5134. ele = ele[ nextProp ];
  5135. }
  5136. return null;
  5137. },
  5138. _getChildrenByTagName: function( ele, lcName, ucName ) {
  5139. var results = [],
  5140. dict = {};
  5141. dict[ lcName ] = dict[ ucName ] = true;
  5142. ele = ele.firstChild;
  5143. while ( ele ) {
  5144. if ( dict[ ele.nodeName ] ) {
  5145. results.push( ele );
  5146. }
  5147. ele = ele.nextSibling;
  5148. }
  5149. return $( results );
  5150. },
  5151. _addThumbClasses: function( containers ) {
  5152. var i, img, len = containers.length;
  5153. for ( i = 0; i < len; i++ ) {
  5154. img = $( this._findFirstElementByTagName( containers[ i ].firstChild, "nextSibling", "img", "IMG" ) );
  5155. if ( img.length ) {
  5156. img.addClass( "ui-li-thumb" );
  5157. $( this._findFirstElementByTagName( img[ 0 ].parentNode, "parentNode", "li", "LI" ) ).addClass( img.is( ".ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" );
  5158. }
  5159. }
  5160. },
  5161. refresh: function( create ) {
  5162. this.parentPage = this.element.closest( ".ui-page" );
  5163. this._createSubPages();
  5164. var o = this.options,
  5165. $list = this.element,
  5166. self = this,
  5167. dividertheme = $list.jqmData( "dividertheme" ) || o.dividerTheme,
  5168. listsplittheme = $list.jqmData( "splittheme" ),
  5169. listspliticon = $list.jqmData( "spliticon" ),
  5170. listicon = $list.jqmData( "icon" ),
  5171. li = this._getChildrenByTagName( $list[ 0 ], "li", "LI" ),
  5172. ol = !!$.nodeName( $list[ 0 ], "ol" ),
  5173. jsCount = !$.support.cssPseudoElement,
  5174. start = $list.attr( "start" ),
  5175. itemClassDict = {},
  5176. item, itemClass, itemTheme,
  5177. a, last, splittheme, counter, startCount, newStartCount, countParent, icon, imgParents, img, linkIcon;
  5178. if ( ol && jsCount ) {
  5179. $list.find( ".ui-li-dec" ).remove();
  5180. }
  5181. if ( ol ) {
  5182. // Check if a start attribute has been set while taking a value of 0 into account
  5183. if ( start || start === 0 ) {
  5184. if ( !jsCount ) {
  5185. startCount = parseInt( start , 10 ) - 1;
  5186. $list.css( "counter-reset", "listnumbering " + startCount );
  5187. } else {
  5188. counter = parseInt( start , 10 );
  5189. }
  5190. } else if ( jsCount ) {
  5191. counter = 1;
  5192. }
  5193. }
  5194. if ( !o.theme ) {
  5195. o.theme = $.mobile.getInheritedTheme( this.element, "c" );
  5196. }
  5197. for ( var pos = 0, numli = li.length; pos < numli; pos++ ) {
  5198. item = li.eq( pos );
  5199. itemClass = "ui-li";
  5200. // If we're creating the element, we update it regardless
  5201. if ( create || !item.hasClass( "ui-li" ) ) {
  5202. itemTheme = item.jqmData( "theme" ) || o.theme;
  5203. a = this._getChildrenByTagName( item[ 0 ], "a", "A" );
  5204. var isDivider = ( item.jqmData( "role" ) === "list-divider" );
  5205. if ( a.length && !isDivider ) {
  5206. icon = item.jqmData( "icon" );
  5207. item.buttonMarkup({
  5208. wrapperEls: "div",
  5209. shadow: false,
  5210. corners: false,
  5211. iconpos: "right",
  5212. icon: a.length > 1 || icon === false ? false : icon || listicon || o.icon,
  5213. theme: itemTheme
  5214. });
  5215. if ( ( icon !== false ) && ( a.length === 1 ) ) {
  5216. item.addClass( "ui-li-has-arrow" );
  5217. }
  5218. a.first().removeClass( "ui-link" ).addClass( "ui-link-inherit" );
  5219. if ( a.length > 1 ) {
  5220. itemClass += " ui-li-has-alt";
  5221. last = a.last();
  5222. splittheme = listsplittheme || last.jqmData( "theme" ) || o.splitTheme;
  5223. linkIcon = last.jqmData( "icon" );
  5224. last.appendTo( item )
  5225. .attr( "title", $.trim(last.getEncodedText()) )
  5226. .addClass( "ui-li-link-alt" )
  5227. .empty()
  5228. .buttonMarkup({
  5229. shadow: false,
  5230. corners: false,
  5231. theme: itemTheme,
  5232. icon: false,
  5233. iconpos: "notext"
  5234. })
  5235. .find( ".ui-btn-inner" )
  5236. .append(
  5237. $( document.createElement( "span" ) ).buttonMarkup({
  5238. shadow: true,
  5239. corners: true,
  5240. theme: splittheme,
  5241. iconpos: "notext",
  5242. // link icon overrides list item icon overrides ul element overrides options
  5243. icon: linkIcon || icon || listspliticon || o.splitIcon
  5244. })
  5245. );
  5246. }
  5247. } else if ( isDivider ) {
  5248. itemClass += " ui-li-divider ui-bar-" + ( item.jqmData( "theme" ) || dividertheme );
  5249. item.attr( "role", "heading" );
  5250. if ( ol ) {
  5251. //reset counter when a divider heading is encountered
  5252. if ( start || start === 0 ) {
  5253. if ( !jsCount ) {
  5254. newStartCount = parseInt( start , 10 ) - 1;
  5255. item.css( "counter-reset", "listnumbering " + newStartCount );
  5256. } else {
  5257. counter = parseInt( start , 10 );
  5258. }
  5259. } else if ( jsCount ) {
  5260. counter = 1;
  5261. }
  5262. }
  5263. } else {
  5264. itemClass += " ui-li-static ui-btn-up-" + itemTheme;
  5265. }
  5266. }
  5267. if ( ol && jsCount && itemClass.indexOf( "ui-li-divider" ) < 0 ) {
  5268. countParent = itemClass.indexOf( "ui-li-static" ) > 0 ? item : item.find( ".ui-link-inherit" );
  5269. countParent.addClass( "ui-li-jsnumbering" )
  5270. .prepend( "<span class='ui-li-dec'>" + ( counter++ ) + ". </span>" );
  5271. }
  5272. // Instead of setting item class directly on the list item and its
  5273. // btn-inner at this point in time, push the item into a dictionary
  5274. // that tells us what class to set on it so we can do this after this
  5275. // processing loop is finished.
  5276. if ( !itemClassDict[ itemClass ] ) {
  5277. itemClassDict[ itemClass ] = [];
  5278. }
  5279. itemClassDict[ itemClass ].push( item[ 0 ] );
  5280. }
  5281. // Set the appropriate listview item classes on each list item
  5282. // and their btn-inner elements. The main reason we didn't do this
  5283. // in the for-loop above is because we can eliminate per-item function overhead
  5284. // by calling addClass() and children() once or twice afterwards. This
  5285. // can give us a significant boost on platforms like WP7.5.
  5286. for ( itemClass in itemClassDict ) {
  5287. $( itemClassDict[ itemClass ] ).addClass( itemClass ).children( ".ui-btn-inner" ).addClass( itemClass );
  5288. }
  5289. $list.find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" )
  5290. .end()
  5291. .find( "p, dl" ).addClass( "ui-li-desc" )
  5292. .end()
  5293. .find( ".ui-li-aside" ).each(function() {
  5294. var $this = $( this );
  5295. $this.prependTo( $this.parent() ); //shift aside to front for css float
  5296. })
  5297. .end()
  5298. .find( ".ui-li-count" ).each(function() {
  5299. $( this ).closest( "li" ).addClass( "ui-li-has-count" );
  5300. }).addClass( "ui-btn-up-" + ( $list.jqmData( "counttheme" ) || this.options.countTheme) + " ui-btn-corner-all" );
  5301. // The idea here is to look at the first image in the list item
  5302. // itself, and any .ui-link-inherit element it may contain, so we
  5303. // can place the appropriate classes on the image and list item.
  5304. // Note that we used to use something like:
  5305. //
  5306. // li.find(">img:eq(0), .ui-link-inherit>img:eq(0)").each( ... );
  5307. //
  5308. // But executing a find() like that on Windows Phone 7.5 took a
  5309. // really long time. Walking things manually with the code below
  5310. // allows the 400 listview item page to load in about 3 seconds as
  5311. // opposed to 30 seconds.
  5312. this._addThumbClasses( li );
  5313. this._addThumbClasses( $list.find( ".ui-link-inherit" ) );
  5314. this._addFirstLastClasses( li, this._getVisibles( li, create ), create );
  5315. // autodividers binds to this to redraw dividers after the listview refresh
  5316. this._trigger( "afterrefresh" );
  5317. },
  5318. //create a string for ID/subpage url creation
  5319. _idStringEscape: function( str ) {
  5320. return str.replace(/[^a-zA-Z0-9]/g, '-');
  5321. },
  5322. _createSubPages: function() {
  5323. var parentList = this.element,
  5324. parentPage = parentList.closest( ".ui-page" ),
  5325. parentUrl = parentPage.jqmData( "url" ),
  5326. parentId = parentUrl || parentPage[ 0 ][ $.expando ],
  5327. parentListId = parentList.attr( "id" ),
  5328. o = this.options,
  5329. dns = "data-" + $.mobile.ns,
  5330. self = this,
  5331. persistentFooterID = parentPage.find( ":jqmData(role='footer')" ).jqmData( "id" ),
  5332. hasSubPages;
  5333. if ( typeof listCountPerPage[ parentId ] === "undefined" ) {
  5334. listCountPerPage[ parentId ] = -1;
  5335. }
  5336. parentListId = parentListId || ++listCountPerPage[ parentId ];
  5337. $( parentList.find( "li>ul, li>ol" ).toArray().reverse() ).each(function( i ) {
  5338. var self = this,
  5339. list = $( this ),
  5340. listId = list.attr( "id" ) || parentListId + "-" + i,
  5341. parent = list.parent(),
  5342. nodeElsFull = $( list.prevAll().toArray().reverse() ),
  5343. nodeEls = nodeElsFull.length ? nodeElsFull : $( "<span>" + $.trim(parent.contents()[ 0 ].nodeValue) + "</span>" ),
  5344. title = nodeEls.first().getEncodedText(),//url limits to first 30 chars of text
  5345. id = ( parentUrl || "" ) + "&" + $.mobile.subPageUrlKey + "=" + listId,
  5346. theme = list.jqmData( "theme" ) || o.theme,
  5347. countTheme = list.jqmData( "counttheme" ) || parentList.jqmData( "counttheme" ) || o.countTheme,
  5348. newPage, anchor;
  5349. //define hasSubPages for use in later removal
  5350. hasSubPages = true;
  5351. newPage = list.detach()
  5352. .wrap( "<div " + dns + "role='page' " + dns + "url='" + id + "' " + dns + "theme='" + theme + "' " + dns + "count-theme='" + countTheme + "'><div " + dns + "role='content'></div></div>" )
  5353. .parent()
  5354. .before( "<div " + dns + "role='header' " + dns + "theme='" + o.headerTheme + "'><div class='ui-title'>" + title + "</div></div>" )
  5355. .after( persistentFooterID ? $( "<div " + dns + "role='footer' " + dns + "id='"+ persistentFooterID +"'>" ) : "" )
  5356. .parent()
  5357. .appendTo( $.mobile.pageContainer );
  5358. newPage.page();
  5359. anchor = parent.find( 'a:first' );
  5360. if ( !anchor.length ) {
  5361. anchor = $( "<a/>" ).html( nodeEls || title ).prependTo( parent.empty() );
  5362. }
  5363. anchor.attr( "href", "#" + id );
  5364. }).listview();
  5365. // on pagehide, remove any nested pages along with the parent page, as long as they aren't active
  5366. // and aren't embedded
  5367. if ( hasSubPages &&
  5368. parentPage.is( ":jqmData(external-page='true')" ) &&
  5369. parentPage.data( "mobile-page" ).options.domCache === false ) {
  5370. var newRemove = function( e, ui ) {
  5371. var nextPage = ui.nextPage, npURL,
  5372. prEvent = new $.Event( "pageremove" );
  5373. if ( ui.nextPage ) {
  5374. npURL = nextPage.jqmData( "url" );
  5375. if ( npURL.indexOf( parentUrl + "&" + $.mobile.subPageUrlKey ) !== 0 ) {
  5376. self.childPages().remove();
  5377. parentPage.trigger( prEvent );
  5378. if ( !prEvent.isDefaultPrevented() ) {
  5379. parentPage.removeWithDependents();
  5380. }
  5381. }
  5382. }
  5383. };
  5384. // unbind the original page remove and replace with our specialized version
  5385. parentPage
  5386. .unbind( "pagehide.remove" )
  5387. .bind( "pagehide.remove", newRemove);
  5388. }
  5389. },
  5390. // TODO sort out a better way to track sub pages of the listview this is brittle
  5391. childPages: function() {
  5392. var parentUrl = this.parentPage.jqmData( "url" );
  5393. return $( ":jqmData(url^='"+ parentUrl + "&" + $.mobile.subPageUrlKey + "')" );
  5394. }
  5395. }, $.mobile.behaviors.addFirstLastClasses ) );
  5396. //auto self-init widgets
  5397. $.mobile.document.bind( "pagecreate create", function( e ) {
  5398. $.mobile.listview.prototype.enhanceWithin( e.target );
  5399. });
  5400. })( jQuery );
  5401. (function( $ ) {
  5402. var meta = $( "meta[name=viewport]" ),
  5403. initialContent = meta.attr( "content" ),
  5404. disabledZoom = initialContent + ",maximum-scale=1, user-scalable=no",
  5405. enabledZoom = initialContent + ",maximum-scale=10, user-scalable=yes",
  5406. disabledInitially = /(user-scalable[\s]*=[\s]*no)|(maximum-scale[\s]*=[\s]*1)[$,\s]/.test( initialContent );
  5407. $.mobile.zoom = $.extend( {}, {
  5408. enabled: !disabledInitially,
  5409. locked: false,
  5410. disable: function( lock ) {
  5411. if ( !disabledInitially && !$.mobile.zoom.locked ) {
  5412. meta.attr( "content", disabledZoom );
  5413. $.mobile.zoom.enabled = false;
  5414. $.mobile.zoom.locked = lock || false;
  5415. }
  5416. },
  5417. enable: function( unlock ) {
  5418. if ( !disabledInitially && ( !$.mobile.zoom.locked || unlock === true ) ) {
  5419. meta.attr( "content", enabledZoom );
  5420. $.mobile.zoom.enabled = true;
  5421. $.mobile.zoom.locked = false;
  5422. }
  5423. },
  5424. restore: function() {
  5425. if ( !disabledInitially ) {
  5426. meta.attr( "content", initialContent );
  5427. $.mobile.zoom.enabled = true;
  5428. }
  5429. }
  5430. });
  5431. }( jQuery ));
  5432. (function( $, undefined ) {
  5433. $.widget( "mobile.textinput", $.mobile.widget, {
  5434. options: {
  5435. theme: null,
  5436. mini: false,
  5437. // This option defaults to true on iOS devices.
  5438. preventFocusZoom: /iPhone|iPad|iPod/.test( navigator.platform ) && navigator.userAgent.indexOf( "AppleWebKit" ) > -1,
  5439. initSelector: "input[type='text'], input[type='search'], :jqmData(type='search'), input[type='number'], :jqmData(type='number'), input[type='password'], input[type='email'], input[type='url'], input[type='tel'], textarea, input[type='time'], input[type='date'], input[type='month'], input[type='week'], input[type='datetime'], input[type='datetime-local'], input[type='color'], input:not([type]), input[type='file']",
  5440. clearBtn: false,
  5441. clearSearchButtonText: null, //deprecating for 1.3...
  5442. clearBtnText: "clear text",
  5443. disabled: false
  5444. },
  5445. _create: function() {
  5446. var self = this,
  5447. input = this.element,
  5448. o = this.options,
  5449. theme = o.theme || $.mobile.getInheritedTheme( this.element, "c" ),
  5450. themeclass = " ui-body-" + theme,
  5451. miniclass = o.mini ? " ui-mini" : "",
  5452. isSearch = input.is( "[type='search'], :jqmData(type='search')" ),
  5453. focusedEl,
  5454. clearbtn,
  5455. clearBtnText = o.clearSearchButtonText || o.clearBtnText,
  5456. clearBtnBlacklist = input.is( "textarea, :jqmData(type='range')" ),
  5457. inputNeedsClearBtn = !!o.clearBtn && !clearBtnBlacklist,
  5458. inputNeedsWrap = input.is( "input" ) && !input.is( ":jqmData(type='range')" );
  5459. function toggleClear() {
  5460. setTimeout( function() {
  5461. clearbtn.toggleClass( "ui-input-clear-hidden", !input.val() );
  5462. }, 0 );
  5463. }
  5464. $( "label[for='" + input.attr( "id" ) + "']" ).addClass( "ui-input-text" );
  5465. focusedEl = input.addClass( "ui-input-text ui-body-"+ theme );
  5466. // XXX: Temporary workaround for issue 785 (Apple bug 8910589).
  5467. // Turn off autocorrect and autocomplete on non-iOS 5 devices
  5468. // since the popup they use can't be dismissed by the user. Note
  5469. // that we test for the presence of the feature by looking for
  5470. // the autocorrect property on the input element. We currently
  5471. // have no test for iOS 5 or newer so we're temporarily using
  5472. // the touchOverflow support flag for jQM 1.0. Yes, I feel dirty. - jblas
  5473. if ( typeof input[0].autocorrect !== "undefined" && !$.support.touchOverflow ) {
  5474. // Set the attribute instead of the property just in case there
  5475. // is code that attempts to make modifications via HTML.
  5476. input[0].setAttribute( "autocorrect", "off" );
  5477. input[0].setAttribute( "autocomplete", "off" );
  5478. }
  5479. //"search" and "text" input widgets
  5480. if ( isSearch ) {
  5481. focusedEl = input.wrap( "<div class='ui-input-search ui-shadow-inset ui-btn-corner-all ui-btn-shadow ui-icon-searchfield" + themeclass + miniclass + "'></div>" ).parent();
  5482. } else if ( inputNeedsWrap ) {
  5483. focusedEl = input.wrap( "<div class='ui-input-text ui-shadow-inset ui-corner-all ui-btn-shadow" + themeclass + miniclass + "'></div>" ).parent();
  5484. }
  5485. if( inputNeedsClearBtn || isSearch ) {
  5486. clearbtn = $( "<a href='#' class='ui-input-clear' title='" + clearBtnText + "'>" + clearBtnText + "</a>" )
  5487. .bind( "click", function( event ) {
  5488. input
  5489. .val( "" )
  5490. .focus()
  5491. .trigger( "change" );
  5492. clearbtn.addClass( "ui-input-clear-hidden" );
  5493. event.preventDefault();
  5494. })
  5495. .appendTo( focusedEl )
  5496. .buttonMarkup({
  5497. icon: "delete",
  5498. iconpos: "notext",
  5499. corners: true,
  5500. shadow: true,
  5501. mini: o.mini
  5502. });
  5503. if ( !isSearch ) {
  5504. focusedEl.addClass( "ui-input-has-clear" );
  5505. }
  5506. toggleClear();
  5507. input.bind( "paste cut keyup input focus change blur", toggleClear );
  5508. }
  5509. else if ( !inputNeedsWrap && !isSearch ) {
  5510. input.addClass( "ui-corner-all ui-shadow-inset" + themeclass + miniclass );
  5511. }
  5512. input.focus(function() {
  5513. // In many situations, iOS will zoom into the input upon tap, this prevents that from happening
  5514. if ( o.preventFocusZoom ) {
  5515. $.mobile.zoom.disable( true );
  5516. }
  5517. focusedEl.addClass( $.mobile.focusClass );
  5518. })
  5519. .blur(function() {
  5520. focusedEl.removeClass( $.mobile.focusClass );
  5521. if ( o.preventFocusZoom ) {
  5522. $.mobile.zoom.enable( true );
  5523. }
  5524. });
  5525. // Autogrow
  5526. if ( input.is( "textarea" ) ) {
  5527. var extraLineHeight = 15,
  5528. keyupTimeoutBuffer = 100,
  5529. keyupTimeout;
  5530. this._keyup = function() {
  5531. var scrollHeight = input[ 0 ].scrollHeight,
  5532. clientHeight = input[ 0 ].clientHeight;
  5533. if ( clientHeight < scrollHeight ) {
  5534. var paddingTop = parseFloat( input.css( "padding-top" ) ),
  5535. paddingBottom = parseFloat( input.css( "padding-bottom" ) ),
  5536. paddingHeight = paddingTop + paddingBottom;
  5537. input.height( scrollHeight - paddingHeight + extraLineHeight );
  5538. }
  5539. };
  5540. input.on( "keyup change input paste", function() {
  5541. clearTimeout( keyupTimeout );
  5542. keyupTimeout = setTimeout( self._keyup, keyupTimeoutBuffer );
  5543. });
  5544. // binding to pagechange here ensures that for pages loaded via
  5545. // ajax the height is recalculated without user input
  5546. this._on( true, $.mobile.document, { "pagechange": "_keyup" });
  5547. // Issue 509: the browser is not providing scrollHeight properly until the styles load
  5548. if ( $.trim( input.val() ) ) {
  5549. // bind to the window load to make sure the height is calculated based on BOTH
  5550. // the DOM and CSS
  5551. this._on( true, $.mobile.window, {"load": "_keyup"});
  5552. }
  5553. }
  5554. if ( input.attr( "disabled" ) ) {
  5555. this.disable();
  5556. }
  5557. },
  5558. disable: function() {
  5559. var $el,
  5560. isSearch = this.element.is( "[type='search'], :jqmData(type='search')" ),
  5561. inputNeedsWrap = this.element.is( "input" ) && !this.element.is( ":jqmData(type='range')" ),
  5562. parentNeedsDisabled = this.element.attr( "disabled", true ) && ( inputNeedsWrap || isSearch );
  5563. if ( parentNeedsDisabled ) {
  5564. $el = this.element.parent();
  5565. } else {
  5566. $el = this.element;
  5567. }
  5568. $el.addClass( "ui-disabled" );
  5569. return this._setOption( "disabled", true );
  5570. },
  5571. enable: function() {
  5572. var $el,
  5573. isSearch = this.element.is( "[type='search'], :jqmData(type='search')" ),
  5574. inputNeedsWrap = this.element.is( "input" ) && !this.element.is( ":jqmData(type='range')" ),
  5575. parentNeedsEnabled = this.element.attr( "disabled", false ) && ( inputNeedsWrap || isSearch );
  5576. if ( parentNeedsEnabled ) {
  5577. $el = this.element.parent();
  5578. } else {
  5579. $el = this.element;
  5580. }
  5581. $el.removeClass( "ui-disabled" );
  5582. return this._setOption( "disabled", false );
  5583. }
  5584. });
  5585. //auto self-init widgets
  5586. $.mobile.document.bind( "pagecreate create", function( e ) {
  5587. $.mobile.textinput.prototype.enhanceWithin( e.target, true );
  5588. });
  5589. })( jQuery );
  5590. (function( $, undefined ) {
  5591. $.mobile.listview.prototype.options.filter = false;
  5592. $.mobile.listview.prototype.options.filterPlaceholder = "Filter items...";
  5593. $.mobile.listview.prototype.options.filterTheme = "c";
  5594. $.mobile.listview.prototype.options.filterReveal = false;
  5595. // TODO rename callback/deprecate and default to the item itself as the first argument
  5596. var defaultFilterCallback = function( text, searchValue, item ) {
  5597. return text.toString().toLowerCase().indexOf( searchValue ) === -1;
  5598. };
  5599. $.mobile.listview.prototype.options.filterCallback = defaultFilterCallback;
  5600. $.mobile.document.delegate( "ul, ol", "listviewcreate", function() {
  5601. var list = $( this ),
  5602. listview = list.data( "mobile-listview" );
  5603. if ( !listview || !listview.options.filter ) {
  5604. return;
  5605. }
  5606. if ( listview.options.filterReveal ) {
  5607. list.children().addClass( "ui-screen-hidden" );
  5608. }
  5609. var wrapper = $( "<form>", {
  5610. "class": "ui-listview-filter ui-bar-" + listview.options.filterTheme,
  5611. "role": "search"
  5612. }).submit( function( e ) {
  5613. e.preventDefault();
  5614. search.blur();
  5615. }),
  5616. onKeyUp = function( e ) {
  5617. var $this = $( this ),
  5618. val = this.value.toLowerCase(),
  5619. listItems = null,
  5620. li = list.children(),
  5621. lastval = $this.jqmData( "lastval" ) + "",
  5622. childItems = false,
  5623. itemtext = "",
  5624. item,
  5625. // Check if a custom filter callback applies
  5626. isCustomFilterCallback = listview.options.filterCallback !== defaultFilterCallback;
  5627. if ( lastval && lastval === val ) {
  5628. // Execute the handler only once per value change
  5629. return;
  5630. }
  5631. listview._trigger( "beforefilter", "beforefilter", { input: this } );
  5632. // Change val as lastval for next execution
  5633. $this.jqmData( "lastval" , val );
  5634. if ( isCustomFilterCallback || val.length < lastval.length || val.indexOf( lastval ) !== 0 ) {
  5635. // Custom filter callback applies or removed chars or pasted something totally different, check all items
  5636. listItems = list.children();
  5637. } else {
  5638. // Only chars added, not removed, only use visible subset
  5639. listItems = list.children( ":not(.ui-screen-hidden)" );
  5640. if ( !listItems.length && listview.options.filterReveal ) {
  5641. listItems = list.children( ".ui-screen-hidden" );
  5642. }
  5643. }
  5644. if ( val ) {
  5645. // This handles hiding regular rows without the text we search for
  5646. // and any list dividers without regular rows shown under it
  5647. for ( var i = listItems.length - 1; i >= 0; i-- ) {
  5648. item = $( listItems[ i ] );
  5649. itemtext = item.jqmData( "filtertext" ) || item.text();
  5650. if ( item.is( "li:jqmData(role=list-divider)" ) ) {
  5651. item.toggleClass( "ui-filter-hidequeue" , !childItems );
  5652. // New bucket!
  5653. childItems = false;
  5654. } else if ( listview.options.filterCallback( itemtext, val, item ) ) {
  5655. //mark to be hidden
  5656. item.toggleClass( "ui-filter-hidequeue" , true );
  5657. } else {
  5658. // There's a shown item in the bucket
  5659. childItems = true;
  5660. }
  5661. }
  5662. // Show items, not marked to be hidden
  5663. listItems
  5664. .filter( ":not(.ui-filter-hidequeue)" )
  5665. .toggleClass( "ui-screen-hidden", false );
  5666. // Hide items, marked to be hidden
  5667. listItems
  5668. .filter( ".ui-filter-hidequeue" )
  5669. .toggleClass( "ui-screen-hidden", true )
  5670. .toggleClass( "ui-filter-hidequeue", false );
  5671. } else {
  5672. //filtervalue is empty => show all
  5673. listItems.toggleClass( "ui-screen-hidden", !!listview.options.filterReveal );
  5674. }
  5675. listview._addFirstLastClasses( li, listview._getVisibles( li, false ), false );
  5676. },
  5677. search = $( "<input>", {
  5678. placeholder: listview.options.filterPlaceholder
  5679. })
  5680. .attr( "data-" + $.mobile.ns + "type", "search" )
  5681. .jqmData( "lastval", "" )
  5682. .bind( "keyup change input", onKeyUp )
  5683. .appendTo( wrapper )
  5684. .textinput();
  5685. if ( listview.options.inset ) {
  5686. wrapper.addClass( "ui-listview-filter-inset" );
  5687. }
  5688. wrapper.bind( "submit", function() {
  5689. return false;
  5690. })
  5691. .insertBefore( list );
  5692. });
  5693. })( jQuery );
  5694. (function( $, undefined ) {
  5695. $.mobile.listview.prototype.options.autodividers = false;
  5696. $.mobile.listview.prototype.options.autodividersSelector = function( elt ) {
  5697. // look for the text in the given element
  5698. var text = $.trim( elt.text() ) || null;
  5699. if ( !text ) {
  5700. return null;
  5701. }
  5702. // create the text for the divider (first uppercased letter)
  5703. text = text.slice( 0, 1 ).toUpperCase();
  5704. return text;
  5705. };
  5706. $.mobile.document.delegate( "ul,ol", "listviewcreate", function() {
  5707. var list = $( this ),
  5708. listview = list.data( "mobile-listview" );
  5709. if ( !listview || !listview.options.autodividers ) {
  5710. return;
  5711. }
  5712. var replaceDividers = function () {
  5713. list.find( "li:jqmData(role='list-divider')" ).remove();
  5714. var lis = list.find( 'li' ),
  5715. lastDividerText = null, li, dividerText;
  5716. for ( var i = 0; i < lis.length ; i++ ) {
  5717. li = lis[i];
  5718. dividerText = listview.options.autodividersSelector( $( li ) );
  5719. if ( dividerText && lastDividerText !== dividerText ) {
  5720. var divider = document.createElement( 'li' );
  5721. divider.appendChild( document.createTextNode( dividerText ) );
  5722. divider.setAttribute( 'data-' + $.mobile.ns + 'role', 'list-divider' );
  5723. li.parentNode.insertBefore( divider, li );
  5724. }
  5725. lastDividerText = dividerText;
  5726. }
  5727. };
  5728. var afterListviewRefresh = function () {
  5729. list.unbind( 'listviewafterrefresh', afterListviewRefresh );
  5730. replaceDividers();
  5731. listview.refresh();
  5732. list.bind( 'listviewafterrefresh', afterListviewRefresh );
  5733. };
  5734. afterListviewRefresh();
  5735. });
  5736. })( jQuery );
  5737. (function( $, undefined ) {
  5738. $( document ).bind( "pagecreate create", function( e ) {
  5739. $( ":jqmData(role='nojs')", e.target ).addClass( "ui-nojs" );
  5740. });
  5741. })( jQuery );
  5742. (function( $, undefined ) {
  5743. $.mobile.behaviors.formReset = {
  5744. _handleFormReset: function() {
  5745. this._on( this.element.closest( "form" ), {
  5746. reset: function() {
  5747. this._delay( "_reset" );
  5748. }
  5749. });
  5750. }
  5751. };
  5752. })( jQuery );
  5753. /*
  5754. * "checkboxradio" plugin
  5755. */
  5756. (function( $, undefined ) {
  5757. $.widget( "mobile.checkboxradio", $.mobile.widget, $.extend( {
  5758. options: {
  5759. theme: null,
  5760. mini: false,
  5761. initSelector: "input[type='checkbox'],input[type='radio']"
  5762. },
  5763. _create: function() {
  5764. var self = this,
  5765. input = this.element,
  5766. o = this.options,
  5767. inheritAttr = function( input, dataAttr ) {
  5768. return input.jqmData( dataAttr ) || input.closest( "form, fieldset" ).jqmData( dataAttr );
  5769. },
  5770. // NOTE: Windows Phone could not find the label through a selector
  5771. // filter works though.
  5772. parentLabel = $( input ).closest( "label" ),
  5773. label = parentLabel.length ? parentLabel : $( input ).closest( "form, fieldset, :jqmData(role='page'), :jqmData(role='dialog')" ).find( "label" ).filter( "[for='" + input[0].id + "']" ).first(),
  5774. inputtype = input[0].type,
  5775. mini = inheritAttr( input, "mini" ) || o.mini,
  5776. checkedState = inputtype + "-on",
  5777. uncheckedState = inputtype + "-off",
  5778. iconpos = inheritAttr( input, "iconpos" ),
  5779. checkedClass = "ui-" + checkedState,
  5780. uncheckedClass = "ui-" + uncheckedState;
  5781. if ( inputtype !== "checkbox" && inputtype !== "radio" ) {
  5782. return;
  5783. }
  5784. // Expose for other methods
  5785. $.extend( this, {
  5786. label: label,
  5787. inputtype: inputtype,
  5788. checkedClass: checkedClass,
  5789. uncheckedClass: uncheckedClass,
  5790. checkedicon: checkedState,
  5791. uncheckedicon: uncheckedState
  5792. });
  5793. // If there's no selected theme check the data attr
  5794. if ( !o.theme ) {
  5795. o.theme = $.mobile.getInheritedTheme( this.element, "c" );
  5796. }
  5797. label.buttonMarkup({
  5798. theme: o.theme,
  5799. icon: uncheckedState,
  5800. shadow: false,
  5801. mini: mini,
  5802. iconpos: iconpos
  5803. });
  5804. // Wrap the input + label in a div
  5805. var wrapper = document.createElement('div');
  5806. wrapper.className = 'ui-' + inputtype;
  5807. input.add( label ).wrapAll( wrapper );
  5808. label.bind({
  5809. vmouseover: function( event ) {
  5810. if ( $( this ).parent().is( ".ui-disabled" ) ) {
  5811. event.stopPropagation();
  5812. }
  5813. },
  5814. vclick: function( event ) {
  5815. if ( input.is( ":disabled" ) ) {
  5816. event.preventDefault();
  5817. return;
  5818. }
  5819. self._cacheVals();
  5820. input.prop( "checked", inputtype === "radio" && true || !input.prop( "checked" ) );
  5821. // trigger click handler's bound directly to the input as a substitute for
  5822. // how label clicks behave normally in the browsers
  5823. // TODO: it would be nice to let the browser's handle the clicks and pass them
  5824. // through to the associate input. we can swallow that click at the parent
  5825. // wrapper element level
  5826. input.triggerHandler( 'click' );
  5827. // Input set for common radio buttons will contain all the radio
  5828. // buttons, but will not for checkboxes. clearing the checked status
  5829. // of other radios ensures the active button state is applied properly
  5830. self._getInputSet().not( input ).prop( "checked", false );
  5831. self._updateAll();
  5832. return false;
  5833. }
  5834. });
  5835. input
  5836. .bind({
  5837. vmousedown: function() {
  5838. self._cacheVals();
  5839. },
  5840. vclick: function() {
  5841. var $this = $( this );
  5842. // Adds checked attribute to checked input when keyboard is used
  5843. if ( $this.is( ":checked" ) ) {
  5844. $this.prop( "checked", true);
  5845. self._getInputSet().not( $this ).prop( "checked", false );
  5846. } else {
  5847. $this.prop( "checked", false );
  5848. }
  5849. self._updateAll();
  5850. },
  5851. focus: function() {
  5852. label.addClass( $.mobile.focusClass );
  5853. },
  5854. blur: function() {
  5855. label.removeClass( $.mobile.focusClass );
  5856. }
  5857. });
  5858. this._handleFormReset();
  5859. this.refresh();
  5860. },
  5861. _cacheVals: function() {
  5862. this._getInputSet().each(function() {
  5863. $( this ).jqmData( "cacheVal", this.checked );
  5864. });
  5865. },
  5866. //returns either a set of radios with the same name attribute, or a single checkbox
  5867. _getInputSet: function() {
  5868. if ( this.inputtype === "checkbox" ) {
  5869. return this.element;
  5870. }
  5871. return this.element.closest( "form, :jqmData(role='page'), :jqmData(role='dialog')" )
  5872. .find( "input[name='" + this.element[0].name + "'][type='" + this.inputtype + "']" );
  5873. },
  5874. _updateAll: function() {
  5875. var self = this;
  5876. this._getInputSet().each(function() {
  5877. var $this = $( this );
  5878. if ( this.checked || self.inputtype === "checkbox" ) {
  5879. $this.trigger( "change" );
  5880. }
  5881. })
  5882. .checkboxradio( "refresh" );
  5883. },
  5884. _reset: function() {
  5885. this.refresh();
  5886. },
  5887. refresh: function() {
  5888. var input = this.element[ 0 ],
  5889. active = " " + $.mobile.activeBtnClass,
  5890. checkedClass = this.checkedClass + ( this.element.parents( ".ui-controlgroup-horizontal" ).length ? active : "" ),
  5891. label = this.label;
  5892. if ( input.checked ) {
  5893. label.removeClass( this.uncheckedClass + active ).addClass( checkedClass ).buttonMarkup( { icon: this.checkedicon } );
  5894. } else {
  5895. label.removeClass( checkedClass ).addClass( this.uncheckedClass ).buttonMarkup( { icon: this.uncheckedicon } );
  5896. }
  5897. if ( input.disabled ) {
  5898. this.disable();
  5899. } else {
  5900. this.enable();
  5901. }
  5902. },
  5903. disable: function() {
  5904. this.element.prop( "disabled", true ).parent().addClass( "ui-disabled" );
  5905. },
  5906. enable: function() {
  5907. this.element.prop( "disabled", false ).parent().removeClass( "ui-disabled" );
  5908. }
  5909. }, $.mobile.behaviors.formReset ) );
  5910. //auto self-init widgets
  5911. $.mobile.document.bind( "pagecreate create", function( e ) {
  5912. $.mobile.checkboxradio.prototype.enhanceWithin( e.target, true );
  5913. });
  5914. })( jQuery );
  5915. (function( $, undefined ) {
  5916. $.widget( "mobile.button", $.mobile.widget, {
  5917. options: {
  5918. theme: null,
  5919. icon: null,
  5920. iconpos: null,
  5921. corners: true,
  5922. shadow: true,
  5923. iconshadow: true,
  5924. inline: null,
  5925. mini: null,
  5926. initSelector: "button, [type='button'], [type='submit'], [type='reset']"
  5927. },
  5928. _create: function() {
  5929. var $el = this.element,
  5930. $button,
  5931. // create a copy of this.options we can pass to buttonMarkup
  5932. o = ( function( tdo ) {
  5933. var key, ret = {};
  5934. for ( key in tdo ) {
  5935. if ( tdo[ key ] !== null && key !== "initSelector" ) {
  5936. ret[ key ] = tdo[ key ];
  5937. }
  5938. }
  5939. return ret;
  5940. } )( this.options ),
  5941. classes = "",
  5942. $buttonPlaceholder;
  5943. // if this is a link, check if it's been enhanced and, if not, use the right function
  5944. if ( $el[ 0 ].tagName === "A" ) {
  5945. if ( !$el.hasClass( "ui-btn" ) ) {
  5946. $el.buttonMarkup();
  5947. }
  5948. return;
  5949. }
  5950. // get the inherited theme
  5951. // TODO centralize for all widgets
  5952. if ( !this.options.theme ) {
  5953. this.options.theme = $.mobile.getInheritedTheme( this.element, "c" );
  5954. }
  5955. // TODO: Post 1.1--once we have time to test thoroughly--any classes manually applied to the original element should be carried over to the enhanced element, with an `-enhanced` suffix. See https://github.com/jquery/jquery-mobile/issues/3577
  5956. /* if ( $el[0].className.length ) {
  5957. classes = $el[0].className;
  5958. } */
  5959. if ( !!~$el[0].className.indexOf( "ui-btn-left" ) ) {
  5960. classes = "ui-btn-left";
  5961. }
  5962. if ( !!~$el[0].className.indexOf( "ui-btn-right" ) ) {
  5963. classes = "ui-btn-right";
  5964. }
  5965. if ( $el.attr( "type" ) === "submit" || $el.attr( "type" ) === "reset" ) {
  5966. if ( classes ) {
  5967. classes += " ui-submit";
  5968. } else {
  5969. classes = "ui-submit";
  5970. }
  5971. }
  5972. $( "label[for='" + $el.attr( "id" ) + "']" ).addClass( "ui-submit" );
  5973. // Add ARIA role
  5974. this.button = $( "<div></div>" )
  5975. [ $el.html() ? "html" : "text" ]( $el.html() || $el.val() )
  5976. .insertBefore( $el )
  5977. .buttonMarkup( o )
  5978. .addClass( classes )
  5979. .append( $el.addClass( "ui-btn-hidden" ) );
  5980. $button = this.button;
  5981. $el.bind({
  5982. focus: function() {
  5983. $button.addClass( $.mobile.focusClass );
  5984. },
  5985. blur: function() {
  5986. $button.removeClass( $.mobile.focusClass );
  5987. }
  5988. });
  5989. this.refresh();
  5990. },
  5991. _setOption: function( key, value ) {
  5992. var op = {};
  5993. op[ key ] = value;
  5994. if ( key !== "initSelector" ) {
  5995. this.button.buttonMarkup( op );
  5996. // Record the option change in the options and in the DOM data-* attributes
  5997. this.element.attr( "data-" + ( $.mobile.ns || "" ) + ( key.replace( /([A-Z])/, "-$1" ).toLowerCase() ), value );
  5998. }
  5999. this._super( "_setOption", key, value );
  6000. },
  6001. enable: function() {
  6002. this.element.attr( "disabled", false );
  6003. this.button.removeClass( "ui-disabled" ).attr( "aria-disabled", false );
  6004. return this._setOption( "disabled", false );
  6005. },
  6006. disable: function() {
  6007. this.element.attr( "disabled", true );
  6008. this.button.addClass( "ui-disabled" ).attr( "aria-disabled", true );
  6009. return this._setOption( "disabled", true );
  6010. },
  6011. refresh: function() {
  6012. var $el = this.element;
  6013. if ( $el.prop("disabled") ) {
  6014. this.disable();
  6015. } else {
  6016. this.enable();
  6017. }
  6018. // Grab the button's text element from its implementation-independent data item
  6019. $( this.button.data( 'buttonElements' ).text )[ $el.html() ? "html" : "text" ]( $el.html() || $el.val() );
  6020. }
  6021. });
  6022. //auto self-init widgets
  6023. $.mobile.document.bind( "pagecreate create", function( e ) {
  6024. $.mobile.button.prototype.enhanceWithin( e.target, true );
  6025. });
  6026. })( jQuery );
  6027. (function( $, undefined ) {
  6028. $.widget( "mobile.slider", $.mobile.widget, $.extend( {
  6029. widgetEventPrefix: "slide",
  6030. options: {
  6031. theme: null,
  6032. trackTheme: null,
  6033. disabled: false,
  6034. initSelector: "input[type='range'], :jqmData(type='range'), :jqmData(role='slider')",
  6035. mini: false,
  6036. highlight: false
  6037. },
  6038. _create: function() {
  6039. // TODO: Each of these should have comments explain what they're for
  6040. var self = this,
  6041. control = this.element,
  6042. parentTheme = $.mobile.getInheritedTheme( control, "c" ),
  6043. theme = this.options.theme || parentTheme,
  6044. trackTheme = this.options.trackTheme || parentTheme,
  6045. cType = control[ 0 ].nodeName.toLowerCase(),
  6046. isSelect = this.isToggleSwitch = cType === "select",
  6047. isRangeslider = control.parent().is( ":jqmData(role='rangeslider')" ),
  6048. selectClass = ( this.isToggleSwitch ) ? "ui-slider-switch" : "",
  6049. controlID = control.attr( "id" ),
  6050. $label = $( "[for='" + controlID + "']" ),
  6051. labelID = $label.attr( "id" ) || controlID + "-label",
  6052. label = $label.attr( "id", labelID ),
  6053. min = !this.isToggleSwitch ? parseFloat( control.attr( "min" ) ) : 0,
  6054. max = !this.isToggleSwitch ? parseFloat( control.attr( "max" ) ) : control.find( "option" ).length-1,
  6055. step = window.parseFloat( control.attr( "step" ) || 1 ),
  6056. miniClass = ( this.options.mini || control.jqmData( "mini" ) ) ? " ui-mini" : "",
  6057. domHandle = document.createElement( "a" ),
  6058. handle = $( domHandle ),
  6059. domSlider = document.createElement( "div" ),
  6060. slider = $( domSlider ),
  6061. valuebg = this.options.highlight && !this.isToggleSwitch ? (function() {
  6062. var bg = document.createElement( "div" );
  6063. bg.className = "ui-slider-bg " + $.mobile.activeBtnClass + " ui-btn-corner-all";
  6064. return $( bg ).prependTo( slider );
  6065. })() : false,
  6066. options,
  6067. wrapper;
  6068. domHandle.setAttribute( "href", "#" );
  6069. domSlider.setAttribute( "role", "application" );
  6070. domSlider.className = [this.isToggleSwitch ? "ui-slider " : "ui-slider-track ",selectClass," ui-btn-down-",trackTheme," ui-btn-corner-all", miniClass].join( "" );
  6071. domHandle.className = "ui-slider-handle";
  6072. domSlider.appendChild( domHandle );
  6073. handle.buttonMarkup({ corners: true, theme: theme, shadow: true })
  6074. .attr({
  6075. "role": "slider",
  6076. "aria-valuemin": min,
  6077. "aria-valuemax": max,
  6078. "aria-valuenow": this._value(),
  6079. "aria-valuetext": this._value(),
  6080. "title": this._value(),
  6081. "aria-labelledby": labelID
  6082. });
  6083. $.extend( this, {
  6084. slider: slider,
  6085. handle: handle,
  6086. type: cType,
  6087. step: step,
  6088. max: max,
  6089. min: min,
  6090. valuebg: valuebg,
  6091. isRangeslider: isRangeslider,
  6092. dragging: false,
  6093. beforeStart: null,
  6094. userModified: false,
  6095. mouseMoved: false
  6096. });
  6097. if ( this.isToggleSwitch ) {
  6098. wrapper = document.createElement( "div" );
  6099. wrapper.className = "ui-slider-inneroffset";
  6100. for ( var j = 0, length = domSlider.childNodes.length; j < length; j++ ) {
  6101. wrapper.appendChild( domSlider.childNodes[j] );
  6102. }
  6103. domSlider.appendChild( wrapper );
  6104. // slider.wrapInner( "<div class='ui-slider-inneroffset'></div>" );
  6105. // make the handle move with a smooth transition
  6106. handle.addClass( "ui-slider-handle-snapping" );
  6107. options = control.find( "option" );
  6108. for ( var i = 0, optionsCount = options.length; i < optionsCount; i++ ) {
  6109. var side = !i ? "b" : "a",
  6110. sliderTheme = !i ? " ui-btn-down-" + trackTheme : ( " " + $.mobile.activeBtnClass ),
  6111. sliderLabel = document.createElement( "div" ),
  6112. sliderImg = document.createElement( "span" );
  6113. sliderImg.className = ["ui-slider-label ui-slider-label-", side, sliderTheme, " ui-btn-corner-all"].join( "" );
  6114. sliderImg.setAttribute( "role", "img" );
  6115. sliderImg.appendChild( document.createTextNode( options[i].innerHTML ) );
  6116. $( sliderImg ).prependTo( slider );
  6117. }
  6118. self._labels = $( ".ui-slider-label", slider );
  6119. }
  6120. label.addClass( "ui-slider" );
  6121. // monitor the input for updated values
  6122. control.addClass( this.isToggleSwitch ? "ui-slider-switch" : "ui-slider-input" );
  6123. this._on( control, {
  6124. "change": "_controlChange",
  6125. "keyup": "_controlKeyup",
  6126. "blur": "_controlBlur",
  6127. "vmouseup": "_controlVMouseUp"
  6128. });
  6129. slider.bind( "vmousedown", $.proxy( this._sliderVMouseDown, this ) )
  6130. .bind( "vclick", false );
  6131. // We have to instantiate a new function object for the unbind to work properly
  6132. // since the method itself is defined in the prototype (causing it to unbind everything)
  6133. this._on( document, { "vmousemove": "_preventDocumentDrag" });
  6134. this._on( slider.add( document ), { "vmouseup": "_sliderVMouseUp" });
  6135. slider.insertAfter( control );
  6136. // wrap in a div for styling purposes
  6137. if ( !this.isToggleSwitch && !isRangeslider ) {
  6138. wrapper = this.options.mini ? "<div class='ui-slider ui-mini'>" : "<div class='ui-slider'>";
  6139. control.add( slider ).wrapAll( wrapper );
  6140. }
  6141. // Only add focus class to toggle switch, sliders get it automatically from ui-btn
  6142. if ( this.isToggleSwitch ) {
  6143. this.handle.bind({
  6144. focus: function() {
  6145. slider.addClass( $.mobile.focusClass );
  6146. },
  6147. blur: function() {
  6148. slider.removeClass( $.mobile.focusClass );
  6149. }
  6150. });
  6151. }
  6152. // bind the handle event callbacks and set the context to the widget instance
  6153. this._on( this.handle, {
  6154. "vmousedown": "_handleVMouseDown",
  6155. "keydown": "_handleKeydown",
  6156. "keyup": "_handleKeyup"
  6157. });
  6158. this.handle.bind( "vclick", false );
  6159. this._handleFormReset();
  6160. this.refresh( undefined, undefined, true );
  6161. },
  6162. _controlChange: function( event ) {
  6163. // if the user dragged the handle, the "change" event was triggered from inside refresh(); don't call refresh() again
  6164. if ( this._trigger( "controlchange", event ) === false ) {
  6165. return false;
  6166. }
  6167. if ( !this.mouseMoved ) {
  6168. this.refresh( this._value(), true );
  6169. }
  6170. },
  6171. _controlKeyup: function( event ) { // necessary?
  6172. this.refresh( this._value(), true, true );
  6173. },
  6174. _controlBlur: function( event ) {
  6175. this.refresh( this._value(), true );
  6176. },
  6177. // it appears the clicking the up and down buttons in chrome on
  6178. // range/number inputs doesn't trigger a change until the field is
  6179. // blurred. Here we check thif the value has changed and refresh
  6180. _controlVMouseUp: function( event ) {
  6181. this._checkedRefresh();
  6182. },
  6183. // NOTE force focus on handle
  6184. _handleVMouseDown: function( event ) {
  6185. this.handle.focus();
  6186. },
  6187. _handleKeydown: function( event ) {
  6188. var index = this._value();
  6189. if ( this.options.disabled ) {
  6190. return;
  6191. }
  6192. // In all cases prevent the default and mark the handle as active
  6193. switch ( event.keyCode ) {
  6194. case $.mobile.keyCode.HOME:
  6195. case $.mobile.keyCode.END:
  6196. case $.mobile.keyCode.PAGE_UP:
  6197. case $.mobile.keyCode.PAGE_DOWN:
  6198. case $.mobile.keyCode.UP:
  6199. case $.mobile.keyCode.RIGHT:
  6200. case $.mobile.keyCode.DOWN:
  6201. case $.mobile.keyCode.LEFT:
  6202. event.preventDefault();
  6203. if ( !this._keySliding ) {
  6204. this._keySliding = true;
  6205. this.handle.addClass( "ui-state-active" );
  6206. }
  6207. break;
  6208. }
  6209. // move the slider according to the keypress
  6210. switch ( event.keyCode ) {
  6211. case $.mobile.keyCode.HOME:
  6212. this.refresh( this.min );
  6213. break;
  6214. case $.mobile.keyCode.END:
  6215. this.refresh( this.max );
  6216. break;
  6217. case $.mobile.keyCode.PAGE_UP:
  6218. case $.mobile.keyCode.UP:
  6219. case $.mobile.keyCode.RIGHT:
  6220. this.refresh( index + this.step );
  6221. break;
  6222. case $.mobile.keyCode.PAGE_DOWN:
  6223. case $.mobile.keyCode.DOWN:
  6224. case $.mobile.keyCode.LEFT:
  6225. this.refresh( index - this.step );
  6226. break;
  6227. }
  6228. }, // remove active mark
  6229. _handleKeyup: function( event ) {
  6230. if ( this._keySliding ) {
  6231. this._keySliding = false;
  6232. this.handle.removeClass( "ui-state-active" );
  6233. }
  6234. },
  6235. _sliderVMouseDown: function( event ) {
  6236. // NOTE: we don't do this in refresh because we still want to
  6237. // support programmatic alteration of disabled inputs
  6238. if ( this.options.disabled || !( event.which === 1 || event.which === 0 || event.which === undefined ) ) {
  6239. return false;
  6240. }
  6241. if ( this._trigger( "beforestart", event ) === false ) {
  6242. return false;
  6243. }
  6244. this.dragging = true;
  6245. this.userModified = false;
  6246. this.mouseMoved = false;
  6247. if ( this.isToggleSwitch ) {
  6248. this.beforeStart = this.element[0].selectedIndex;
  6249. }
  6250. this.refresh( event );
  6251. this._trigger( "start" );
  6252. return false;
  6253. },
  6254. _sliderVMouseUp: function() {
  6255. if ( this.dragging ) {
  6256. this.dragging = false;
  6257. if ( this.isToggleSwitch ) {
  6258. // make the handle move with a smooth transition
  6259. this.handle.addClass( "ui-slider-handle-snapping" );
  6260. if ( this.mouseMoved ) {
  6261. // this is a drag, change the value only if user dragged enough
  6262. if ( this.userModified ) {
  6263. this.refresh( this.beforeStart === 0 ? 1 : 0 );
  6264. } else {
  6265. this.refresh( this.beforeStart );
  6266. }
  6267. } else {
  6268. // this is just a click, change the value
  6269. this.refresh( this.beforeStart === 0 ? 1 : 0 );
  6270. }
  6271. }
  6272. this.mouseMoved = false;
  6273. this._trigger( "stop" );
  6274. return false;
  6275. }
  6276. },
  6277. _preventDocumentDrag: function( event ) {
  6278. // NOTE: we don't do this in refresh because we still want to
  6279. // support programmatic alteration of disabled inputs
  6280. if ( this._trigger( "drag", event ) === false) {
  6281. return false;
  6282. }
  6283. if ( this.dragging && !this.options.disabled ) {
  6284. // this.mouseMoved must be updated before refresh() because it will be used in the control "change" event
  6285. this.mouseMoved = true;
  6286. if ( this.isToggleSwitch ) {
  6287. // make the handle move in sync with the mouse
  6288. this.handle.removeClass( "ui-slider-handle-snapping" );
  6289. }
  6290. this.refresh( event );
  6291. // only after refresh() you can calculate this.userModified
  6292. this.userModified = this.beforeStart !== this.element[0].selectedIndex;
  6293. return false;
  6294. }
  6295. },
  6296. _checkedRefresh: function() {
  6297. if ( this.value !== this._value() ) {
  6298. this.refresh( this._value() );
  6299. }
  6300. },
  6301. _value: function() {
  6302. return this.isToggleSwitch ? this.element[0].selectedIndex : parseFloat( this.element.val() ) ;
  6303. },
  6304. _reset: function() {
  6305. this.refresh( undefined, false, true );
  6306. },
  6307. refresh: function( val, isfromControl, preventInputUpdate ) {
  6308. // NOTE: we don't return here because we want to support programmatic
  6309. // alteration of the input value, which should still update the slider
  6310. var self = this,
  6311. parentTheme = $.mobile.getInheritedTheme( this.element, "c" ),
  6312. theme = this.options.theme || parentTheme,
  6313. trackTheme = this.options.trackTheme || parentTheme,
  6314. left, width, data, tol;
  6315. self.slider[0].className = [ this.isToggleSwitch ? "ui-slider ui-slider-switch" : "ui-slider-track"," ui-btn-down-" + trackTheme,' ui-btn-corner-all', ( this.options.mini ) ? " ui-mini":""].join( "" );
  6316. if ( this.options.disabled || this.element.attr( "disabled" ) ) {
  6317. this.disable();
  6318. }
  6319. // set the stored value for comparison later
  6320. this.value = this._value();
  6321. if ( this.options.highlight && !this.isToggleSwitch && this.slider.find( ".ui-slider-bg" ).length === 0 ) {
  6322. this.valuebg = (function() {
  6323. var bg = document.createElement( "div" );
  6324. bg.className = "ui-slider-bg " + $.mobile.activeBtnClass + " ui-btn-corner-all";
  6325. return $( bg ).prependTo( self.slider );
  6326. })();
  6327. }
  6328. this.handle.buttonMarkup({ corners: true, theme: theme, shadow: true });
  6329. var pxStep, percent,
  6330. control = this.element,
  6331. isInput = !this.isToggleSwitch,
  6332. optionElements = isInput ? [] : control.find( "option" ),
  6333. min = isInput ? parseFloat( control.attr( "min" ) ) : 0,
  6334. max = isInput ? parseFloat( control.attr( "max" ) ) : optionElements.length - 1,
  6335. step = ( isInput && parseFloat( control.attr( "step" ) ) > 0 ) ? parseFloat( control.attr( "step" ) ) : 1;
  6336. if ( typeof val === "object" ) {
  6337. data = val;
  6338. // a slight tolerance helped get to the ends of the slider
  6339. tol = 8;
  6340. left = this.slider.offset().left;
  6341. width = this.slider.width();
  6342. pxStep = width/((max-min)/step);
  6343. if ( !this.dragging ||
  6344. data.pageX < left - tol ||
  6345. data.pageX > left + width + tol ) {
  6346. return;
  6347. }
  6348. if ( pxStep > 1 ) {
  6349. percent = ( ( data.pageX - left ) / width ) * 100;
  6350. } else {
  6351. percent = Math.round( ( ( data.pageX - left ) / width ) * 100 );
  6352. }
  6353. } else {
  6354. if ( val == null ) {
  6355. val = isInput ? parseFloat( control.val() || 0 ) : control[0].selectedIndex;
  6356. }
  6357. percent = ( parseFloat( val ) - min ) / ( max - min ) * 100;
  6358. }
  6359. if ( isNaN( percent ) ) {
  6360. return;
  6361. }
  6362. var newval = ( percent / 100 ) * ( max - min ) + min;
  6363. //from jQuery UI slider, the following source will round to the nearest step
  6364. var valModStep = ( newval - min ) % step;
  6365. var alignValue = newval - valModStep;
  6366. if ( Math.abs( valModStep ) * 2 >= step ) {
  6367. alignValue += ( valModStep > 0 ) ? step : ( -step );
  6368. }
  6369. var percentPerStep = 100/((max-min)/step);
  6370. // Since JavaScript has problems with large floats, round
  6371. // the final value to 5 digits after the decimal point (see jQueryUI: #4124)
  6372. newval = parseFloat( alignValue.toFixed(5) );
  6373. if ( typeof pxStep === "undefined" ) {
  6374. pxStep = width / ( (max-min) / step );
  6375. }
  6376. if ( pxStep > 1 && isInput ) {
  6377. percent = ( newval - min ) * percentPerStep * ( 1 / step );
  6378. }
  6379. if ( percent < 0 ) {
  6380. percent = 0;
  6381. }
  6382. if ( percent > 100 ) {
  6383. percent = 100;
  6384. }
  6385. if ( newval < min ) {
  6386. newval = min;
  6387. }
  6388. if ( newval > max ) {
  6389. newval = max;
  6390. }
  6391. this.handle.css( "left", percent + "%" );
  6392. this.handle[0].setAttribute( "aria-valuenow", isInput ? newval : optionElements.eq( newval ).attr( "value" ) );
  6393. this.handle[0].setAttribute( "aria-valuetext", isInput ? newval : optionElements.eq( newval ).getEncodedText() );
  6394. this.handle[0].setAttribute( "title", isInput ? newval : optionElements.eq( newval ).getEncodedText() );
  6395. if ( this.valuebg ) {
  6396. this.valuebg.css( "width", percent + "%" );
  6397. }
  6398. // drag the label widths
  6399. if ( this._labels ) {
  6400. var handlePercent = this.handle.width() / this.slider.width() * 100,
  6401. aPercent = percent && handlePercent + ( 100 - handlePercent ) * percent / 100,
  6402. bPercent = percent === 100 ? 0 : Math.min( handlePercent + 100 - aPercent, 100 );
  6403. this._labels.each(function() {
  6404. var ab = $( this ).is( ".ui-slider-label-a" );
  6405. $( this ).width( ( ab ? aPercent : bPercent ) + "%" );
  6406. });
  6407. }
  6408. if ( !preventInputUpdate ) {
  6409. var valueChanged = false;
  6410. // update control"s value
  6411. if ( isInput ) {
  6412. valueChanged = control.val() !== newval;
  6413. control.val( newval );
  6414. } else {
  6415. valueChanged = control[ 0 ].selectedIndex !== newval;
  6416. control[ 0 ].selectedIndex = newval;
  6417. }
  6418. if ( this._trigger( "beforechange", val ) === false) {
  6419. return false;
  6420. }
  6421. if ( !isfromControl && valueChanged ) {
  6422. control.trigger( "change" );
  6423. }
  6424. }
  6425. },
  6426. enable: function() {
  6427. this.element.attr( "disabled", false );
  6428. this.slider.removeClass( "ui-disabled" ).attr( "aria-disabled", false );
  6429. return this._setOption( "disabled", false );
  6430. },
  6431. disable: function() {
  6432. this.element.attr( "disabled", true );
  6433. this.slider.addClass( "ui-disabled" ).attr( "aria-disabled", true );
  6434. return this._setOption( "disabled", true );
  6435. }
  6436. }, $.mobile.behaviors.formReset ) );
  6437. //auto self-init widgets
  6438. $.mobile.document.bind( "pagecreate create", function( e ) {
  6439. $.mobile.slider.prototype.enhanceWithin( e.target, true );
  6440. });
  6441. })( jQuery );
  6442. (function( $, undefined ) {
  6443. $.widget( "mobile.rangeslider", $.mobile.widget, {
  6444. options: {
  6445. theme: null,
  6446. trackTheme: null,
  6447. disabled: false,
  6448. initSelector: ":jqmData(role='rangeslider')",
  6449. mini: false,
  6450. highlight: true
  6451. },
  6452. _create: function() {
  6453. var secondLabel,
  6454. $el = this.element,
  6455. elClass = this.options.mini ? "ui-rangeslider ui-mini" : "ui-rangeslider",
  6456. _inputFirst = $el.find( "input" ).first(),
  6457. _inputLast = $el.find( "input" ).last(),
  6458. label = $el.find( "label" ).first(),
  6459. _sliderFirst = $.data( _inputFirst.get(0), "mobileSlider" ).slider,
  6460. _sliderLast = $.data( _inputLast.get(0), "mobileSlider" ).slider,
  6461. firstHandle = $.data( _inputFirst.get(0), "mobileSlider" ).handle,
  6462. _sliders = $( "<div class=\"ui-rangeslider-sliders\" />" ).appendTo( $el );
  6463. if ( $el.find( "label" ).length > 1 ) {
  6464. secondLabel = $el.find( "label" ).last().hide();
  6465. }
  6466. _inputFirst.addClass( "ui-rangeslider-first" );
  6467. _inputLast.addClass( "ui-rangeslider-last" );
  6468. $el.addClass( elClass );
  6469. _sliderFirst.appendTo( _sliders );
  6470. _sliderLast.appendTo( _sliders );
  6471. label.prependTo( $el );
  6472. firstHandle.prependTo( _sliderLast );
  6473. $.extend( this, {
  6474. _inputFirst: _inputFirst,
  6475. _inputLast: _inputLast,
  6476. _sliderFirst: _sliderFirst,
  6477. _sliderLast: _sliderLast,
  6478. _targetVal: null,
  6479. _sliderTarget: false,
  6480. _sliders: _sliders,
  6481. _proxy: false
  6482. });
  6483. this.refresh();
  6484. this._on( this.element.find( "input.ui-slider-input" ), {
  6485. "slidebeforestart": "_slidebeforestart",
  6486. "slidestop": "_slidestop",
  6487. "slidedrag": "_slidedrag",
  6488. "slidebeforechange": "_change",
  6489. "blur": "_change",
  6490. "keyup": "_change"
  6491. });
  6492. this._on({
  6493. "mousedown":"_change"
  6494. });
  6495. this._on( this.element.closest( "form" ), {
  6496. "reset":"_handleReset"
  6497. });
  6498. this._on( firstHandle, {
  6499. "vmousedown": "_dragFirstHandle"
  6500. });
  6501. },
  6502. _handleReset: function(){
  6503. var self = this;
  6504. //we must wait for the stack to unwind before updateing other wise sliders will not have updated yet
  6505. setTimeout( function(){
  6506. self._updateHighlight();
  6507. },0);
  6508. },
  6509. _dragFirstHandle: function( event ) {
  6510. //if the first handle is dragged send the event to the first slider
  6511. $.data( this._inputFirst.get(0), "mobileSlider" ).dragging = true;
  6512. $.data( this._inputFirst.get(0), "mobileSlider" ).refresh( event );
  6513. return false;
  6514. },
  6515. _slidedrag: function( event ) {
  6516. var first = $( event.target ).is( this._inputFirst ),
  6517. otherSlider = ( first ) ? this._inputLast : this._inputFirst;
  6518. this._sliderTarget = false;
  6519. //if the drag was initiated on an extreme and the other handle is focused send the events to
  6520. //the closest handle
  6521. if ( ( this._proxy === "first" && first ) || ( this._proxy === "last" && !first ) ) {
  6522. $.data( otherSlider.get(0), "mobileSlider" ).dragging = true;
  6523. $.data( otherSlider.get(0), "mobileSlider" ).refresh( event );
  6524. return false;
  6525. }
  6526. },
  6527. _slidestop: function( event ) {
  6528. var first = $( event.target ).is( this._inputFirst );
  6529. this._proxy = false;
  6530. //this stops dragging of the handle and brings the active track to the front
  6531. //this makes clicks on the track go the the last handle used
  6532. this.element.find( "input" ).trigger( "vmouseup" );
  6533. this._sliderFirst.css( "z-index", first ? 1 : "" );
  6534. },
  6535. _slidebeforestart: function( event ) {
  6536. this._sliderTarget = false;
  6537. //if the track is the target remember this and the original value
  6538. if ( $( event.originalEvent.target ).hasClass( "ui-slider-track" ) ) {
  6539. this._sliderTarget = true;
  6540. this._targetVal = $( event.target ).val();
  6541. }
  6542. },
  6543. _setOption: function( options ) {
  6544. this._superApply( options );
  6545. this.refresh();
  6546. },
  6547. refresh: function() {
  6548. var $el = this.element,
  6549. o = this.options;
  6550. $el.find( "input" ).slider({
  6551. theme: o.theme,
  6552. trackTheme: o.trackTheme,
  6553. disabled: o.disabled,
  6554. mini: o.mini,
  6555. highlight: o.highlight
  6556. }).slider( "refresh" );
  6557. this._updateHighlight();
  6558. },
  6559. _change: function( event ) {
  6560. if ( event.type === "keyup" ) {
  6561. this._updateHighlight();
  6562. return false;
  6563. }
  6564. var self = this,
  6565. min = parseFloat( this._inputFirst.val(), 10 ),
  6566. max = parseFloat( this._inputLast.val(), 10 ),
  6567. first = $( event.target ).hasClass( "ui-rangeslider-first" ),
  6568. thisSlider = first ? this._inputFirst : this._inputLast,
  6569. otherSlider = first ? this._inputLast : this._inputFirst;
  6570. if( ( this._inputFirst.val() > this._inputLast.val() && event.type === "mousedown" && !$(event.target).hasClass("ui-slider-handle")) ){
  6571. thisSlider.blur();
  6572. } else if( event.type === "mousedown" ){
  6573. return;
  6574. }
  6575. if ( min > max && !this._sliderTarget ) {
  6576. //this prevents min from being greater then max
  6577. thisSlider.val( first ? max: min ).slider( "refresh" );
  6578. this._trigger( "normalize" );
  6579. } else if ( min > max ) {
  6580. //this makes it so clicks on the target on either extreme go to the closest handle
  6581. thisSlider.val( this._targetVal ).slider( "refresh" );
  6582. //You must wait for the stack to unwind so first slider is updated before updating second
  6583. setTimeout( function() {
  6584. otherSlider.val( first ? min: max ).slider( "refresh" );
  6585. $.data( otherSlider.get(0), "mobileSlider" ).handle.focus();
  6586. self._sliderFirst.css( "z-index", first ? "" : 1 );
  6587. self._trigger( "normalize" );
  6588. }, 0 );
  6589. this._proxy = ( first ) ? "first" : "last";
  6590. }
  6591. //fixes issue where when both _sliders are at min they cannot be adjusted
  6592. if ( min === max ) {
  6593. $.data( thisSlider.get(0), "mobileSlider" ).handle.css( "z-index", 1 );
  6594. $.data( otherSlider.get(0), "mobileSlider" ).handle.css( "z-index", 0 );
  6595. } else {
  6596. $.data( otherSlider.get(0), "mobileSlider" ).handle.css( "z-index", "" );
  6597. $.data( thisSlider.get(0), "mobileSlider" ).handle.css( "z-index", "" );
  6598. }
  6599. this._updateHighlight();
  6600. if ( min >= max ) {
  6601. return false;
  6602. }
  6603. },
  6604. _updateHighlight: function() {
  6605. var min = parseInt( $.data( this._inputFirst.get(0), "mobileSlider" ).handle.get(0).style.left, 10 ),
  6606. max = parseInt( $.data( this._inputLast.get(0), "mobileSlider" ).handle.get(0).style.left, 10 ),
  6607. width = (max - min);
  6608. this.element.find( ".ui-slider-bg" ).css({
  6609. "margin-left": min + "%",
  6610. "width": width + "%"
  6611. });
  6612. },
  6613. _destroy: function() {
  6614. this.element.removeClass( "ui-rangeslider ui-mini" ).find( "label" ).show();
  6615. this._inputFirst.after( this._sliderFirst );
  6616. this._inputLast.after( this._sliderLast );
  6617. this._sliders.remove();
  6618. this.element.find( "input" ).removeClass( "ui-rangeslider-first ui-rangeslider-last" ).slider( "destroy" );
  6619. }
  6620. });
  6621. $.widget( "mobile.rangeslider", $.mobile.rangeslider, $.mobile.behaviors.formReset );
  6622. //auto self-init widgets
  6623. $( document ).bind( "pagecreate create", function( e ) {
  6624. $.mobile.rangeslider.prototype.enhanceWithin( e.target, true );
  6625. });
  6626. })( jQuery );
  6627. (function( $, undefined ) {
  6628. $.widget( "mobile.selectmenu", $.mobile.widget, $.extend( {
  6629. options: {
  6630. theme: null,
  6631. disabled: false,
  6632. icon: "arrow-d",
  6633. iconpos: "right",
  6634. inline: false,
  6635. corners: true,
  6636. shadow: true,
  6637. iconshadow: true,
  6638. overlayTheme: "a",
  6639. dividerTheme: "b",
  6640. hidePlaceholderMenuItems: true,
  6641. closeText: "Close",
  6642. nativeMenu: true,
  6643. // This option defaults to true on iOS devices.
  6644. preventFocusZoom: /iPhone|iPad|iPod/.test( navigator.platform ) && navigator.userAgent.indexOf( "AppleWebKit" ) > -1,
  6645. initSelector: "select:not( :jqmData(role='slider') )",
  6646. mini: false
  6647. },
  6648. _button: function() {
  6649. return $( "<div/>" );
  6650. },
  6651. _setDisabled: function( value ) {
  6652. this.element.attr( "disabled", value );
  6653. this.button.attr( "aria-disabled", value );
  6654. return this._setOption( "disabled", value );
  6655. },
  6656. _focusButton : function() {
  6657. var self = this;
  6658. setTimeout( function() {
  6659. self.button.focus();
  6660. }, 40);
  6661. },
  6662. _selectOptions: function() {
  6663. return this.select.find( "option" );
  6664. },
  6665. // setup items that are generally necessary for select menu extension
  6666. _preExtension: function() {
  6667. var classes = "";
  6668. // TODO: Post 1.1--once we have time to test thoroughly--any classes manually applied to the original element should be carried over to the enhanced element, with an `-enhanced` suffix. See https://github.com/jquery/jquery-mobile/issues/3577
  6669. /* if ( $el[0].className.length ) {
  6670. classes = $el[0].className;
  6671. } */
  6672. if ( !!~this.element[0].className.indexOf( "ui-btn-left" ) ) {
  6673. classes = " ui-btn-left";
  6674. }
  6675. if ( !!~this.element[0].className.indexOf( "ui-btn-right" ) ) {
  6676. classes = " ui-btn-right";
  6677. }
  6678. this.select = this.element.removeClass( "ui-btn-left ui-btn-right" ).wrap( "<div class='ui-select" + classes + "'>" );
  6679. this.selectID = this.select.attr( "id" );
  6680. this.label = $( "label[for='"+ this.selectID +"']" ).addClass( "ui-select" );
  6681. this.isMultiple = this.select[ 0 ].multiple;
  6682. if ( !this.options.theme ) {
  6683. this.options.theme = $.mobile.getInheritedTheme( this.select, "c" );
  6684. }
  6685. },
  6686. _destroy: function() {
  6687. var wrapper = this.element.parents( ".ui-select" );
  6688. if ( wrapper.length > 0 ) {
  6689. if ( wrapper.is( ".ui-btn-left, .ui-btn-right" ) ) {
  6690. this.element.addClass( wrapper.is( ".ui-btn-left" ) ? "ui-btn-left" : "ui-btn-right" );
  6691. }
  6692. this.element.insertAfter( wrapper );
  6693. wrapper.remove();
  6694. }
  6695. },
  6696. _create: function() {
  6697. this._preExtension();
  6698. // Allows for extension of the native select for custom selects and other plugins
  6699. // see select.custom for example extension
  6700. // TODO explore plugin registration
  6701. this._trigger( "beforeCreate" );
  6702. this.button = this._button();
  6703. var self = this,
  6704. options = this.options,
  6705. inline = options.inline || this.select.jqmData( "inline" ),
  6706. mini = options.mini || this.select.jqmData( "mini" ),
  6707. iconpos = options.icon ? ( options.iconpos || this.select.jqmData( "iconpos" ) ) : false,
  6708. // IE throws an exception at options.item() function when
  6709. // there is no selected item
  6710. // select first in this case
  6711. selectedIndex = this.select[ 0 ].selectedIndex === -1 ? 0 : this.select[ 0 ].selectedIndex,
  6712. // TODO values buttonId and menuId are undefined here
  6713. button = this.button
  6714. .insertBefore( this.select )
  6715. .buttonMarkup( {
  6716. theme: options.theme,
  6717. icon: options.icon,
  6718. iconpos: iconpos,
  6719. inline: inline,
  6720. corners: options.corners,
  6721. shadow: options.shadow,
  6722. iconshadow: options.iconshadow,
  6723. mini: mini
  6724. });
  6725. this.setButtonText();
  6726. // Opera does not properly support opacity on select elements
  6727. // In Mini, it hides the element, but not its text
  6728. // On the desktop,it seems to do the opposite
  6729. // for these reasons, using the nativeMenu option results in a full native select in Opera
  6730. if ( options.nativeMenu && window.opera && window.opera.version ) {
  6731. button.addClass( "ui-select-nativeonly" );
  6732. }
  6733. // Add counter for multi selects
  6734. if ( this.isMultiple ) {
  6735. this.buttonCount = $( "<span>" )
  6736. .addClass( "ui-li-count ui-btn-up-c ui-btn-corner-all" )
  6737. .hide()
  6738. .appendTo( button.addClass('ui-li-has-count') );
  6739. }
  6740. // Disable if specified
  6741. if ( options.disabled || this.element.attr('disabled')) {
  6742. this.disable();
  6743. }
  6744. // Events on native select
  6745. this.select.change(function() {
  6746. self.refresh();
  6747. if ( !!options.nativeMenu ) {
  6748. this.blur();
  6749. }
  6750. });
  6751. this._handleFormReset();
  6752. this.build();
  6753. },
  6754. build: function() {
  6755. var self = this;
  6756. this.select
  6757. .appendTo( self.button )
  6758. .bind( "vmousedown", function() {
  6759. // Add active class to button
  6760. self.button.addClass( $.mobile.activeBtnClass );
  6761. })
  6762. .bind( "focus", function() {
  6763. self.button.addClass( $.mobile.focusClass );
  6764. })
  6765. .bind( "blur", function() {
  6766. self.button.removeClass( $.mobile.focusClass );
  6767. })
  6768. .bind( "focus vmouseover", function() {
  6769. self.button.trigger( "vmouseover" );
  6770. })
  6771. .bind( "vmousemove", function() {
  6772. // Remove active class on scroll/touchmove
  6773. self.button.removeClass( $.mobile.activeBtnClass );
  6774. })
  6775. .bind( "change blur vmouseout", function() {
  6776. self.button.trigger( "vmouseout" )
  6777. .removeClass( $.mobile.activeBtnClass );
  6778. })
  6779. .bind( "change blur", function() {
  6780. self.button.removeClass( "ui-btn-down-" + self.options.theme );
  6781. });
  6782. // In many situations, iOS will zoom into the select upon tap, this prevents that from happening
  6783. self.button.bind( "vmousedown", function() {
  6784. if ( self.options.preventFocusZoom ) {
  6785. $.mobile.zoom.disable( true );
  6786. }
  6787. });
  6788. self.label.bind( "click focus", function() {
  6789. if ( self.options.preventFocusZoom ) {
  6790. $.mobile.zoom.disable( true );
  6791. }
  6792. });
  6793. self.select.bind( "focus", function() {
  6794. if ( self.options.preventFocusZoom ) {
  6795. $.mobile.zoom.disable( true );
  6796. }
  6797. });
  6798. self.button.bind( "mouseup", function() {
  6799. if ( self.options.preventFocusZoom ) {
  6800. setTimeout(function() {
  6801. $.mobile.zoom.enable( true );
  6802. }, 0 );
  6803. }
  6804. });
  6805. self.select.bind( "blur", function() {
  6806. if ( self.options.preventFocusZoom ) {
  6807. $.mobile.zoom.enable( true );
  6808. }
  6809. });
  6810. },
  6811. selected: function() {
  6812. return this._selectOptions().filter( ":selected" );
  6813. },
  6814. selectedIndices: function() {
  6815. var self = this;
  6816. return this.selected().map(function() {
  6817. return self._selectOptions().index( this );
  6818. }).get();
  6819. },
  6820. setButtonText: function() {
  6821. var self = this,
  6822. selected = this.selected(),
  6823. text = this.placeholder,
  6824. span = $( document.createElement( "span" ) );
  6825. this.button.find( ".ui-btn-text" ).html(function() {
  6826. if ( selected.length ) {
  6827. text = selected.map(function() {
  6828. return $( this ).text();
  6829. }).get().join( ", " );
  6830. } else {
  6831. text = self.placeholder;
  6832. }
  6833. // TODO possibly aggregate multiple select option classes
  6834. return span.text( text )
  6835. .addClass( self.select.attr( "class" ) )
  6836. .addClass( selected.attr( "class" ) );
  6837. });
  6838. },
  6839. setButtonCount: function() {
  6840. var selected = this.selected();
  6841. // multiple count inside button
  6842. if ( this.isMultiple ) {
  6843. this.buttonCount[ selected.length > 1 ? "show" : "hide" ]().text( selected.length );
  6844. }
  6845. },
  6846. _reset: function() {
  6847. this.refresh();
  6848. },
  6849. refresh: function() {
  6850. this.setButtonText();
  6851. this.setButtonCount();
  6852. },
  6853. // open and close preserved in native selects
  6854. // to simplify users code when looping over selects
  6855. open: $.noop,
  6856. close: $.noop,
  6857. disable: function() {
  6858. this._setDisabled( true );
  6859. this.button.addClass( "ui-disabled" );
  6860. },
  6861. enable: function() {
  6862. this._setDisabled( false );
  6863. this.button.removeClass( "ui-disabled" );
  6864. }
  6865. }, $.mobile.behaviors.formReset ) );
  6866. //auto self-init widgets
  6867. $.mobile.document.bind( "pagecreate create", function( e ) {
  6868. $.mobile.selectmenu.prototype.enhanceWithin( e.target, true );
  6869. });
  6870. })( jQuery );
  6871. (function( $, undefined ) {
  6872. function fitSegmentInsideSegment( winSize, segSize, offset, desired ) {
  6873. var ret = desired;
  6874. if ( winSize < segSize ) {
  6875. // Center segment if it's bigger than the window
  6876. ret = offset + ( winSize - segSize ) / 2;
  6877. } else {
  6878. // Otherwise center it at the desired coordinate while keeping it completely inside the window
  6879. ret = Math.min( Math.max( offset, desired - segSize / 2 ), offset + winSize - segSize );
  6880. }
  6881. return ret;
  6882. }
  6883. function windowCoords() {
  6884. var $win = $.mobile.window;
  6885. return {
  6886. x: $win.scrollLeft(),
  6887. y: $win.scrollTop(),
  6888. cx: ( window.innerWidth || $win.width() ),
  6889. cy: ( window.innerHeight || $win.height() )
  6890. };
  6891. }
  6892. $.widget( "mobile.popup", $.mobile.widget, {
  6893. options: {
  6894. theme: null,
  6895. overlayTheme: null,
  6896. shadow: true,
  6897. corners: true,
  6898. transition: "none",
  6899. positionTo: "origin",
  6900. tolerance: null,
  6901. initSelector: ":jqmData(role='popup')",
  6902. closeLinkSelector: "a:jqmData(rel='back')",
  6903. closeLinkEvents: "click.popup",
  6904. navigateEvents: "navigate.popup",
  6905. closeEvents: "navigate.popup pagebeforechange.popup",
  6906. dismissible: true,
  6907. // NOTE Windows Phone 7 has a scroll position caching issue that
  6908. // requires us to disable popup history management by default
  6909. // https://github.com/jquery/jquery-mobile/issues/4784
  6910. //
  6911. // NOTE this option is modified in _create!
  6912. history: !$.mobile.browser.oldIE
  6913. },
  6914. _eatEventAndClose: function( e ) {
  6915. e.preventDefault();
  6916. e.stopImmediatePropagation();
  6917. if ( this.options.dismissible ) {
  6918. this.close();
  6919. }
  6920. return false;
  6921. },
  6922. // Make sure the screen size is increased beyond the page height if the popup's causes the document to increase in height
  6923. _resizeScreen: function() {
  6924. var popupHeight = this._ui.container.outerHeight( true );
  6925. this._ui.screen.removeAttr( "style" );
  6926. if ( popupHeight > this._ui.screen.height() ) {
  6927. this._ui.screen.height( popupHeight );
  6928. }
  6929. },
  6930. _handleWindowKeyUp: function( e ) {
  6931. if ( this._isOpen && e.keyCode === $.mobile.keyCode.ESCAPE ) {
  6932. return this._eatEventAndClose( e );
  6933. }
  6934. },
  6935. _expectResizeEvent: function() {
  6936. var winCoords = windowCoords();
  6937. if ( this._resizeData ) {
  6938. if ( winCoords.x === this._resizeData.winCoords.x &&
  6939. winCoords.y === this._resizeData.winCoords.y &&
  6940. winCoords.cx === this._resizeData.winCoords.cx &&
  6941. winCoords.cy === this._resizeData.winCoords.cy ) {
  6942. // timeout not refreshed
  6943. return false;
  6944. } else {
  6945. // clear existing timeout - it will be refreshed below
  6946. clearTimeout( this._resizeData.timeoutId );
  6947. }
  6948. }
  6949. this._resizeData = {
  6950. timeoutId: setTimeout( $.proxy( this, "_resizeTimeout" ), 200 ),
  6951. winCoords: winCoords
  6952. };
  6953. return true;
  6954. },
  6955. _resizeTimeout: function() {
  6956. if ( this._isOpen ) {
  6957. if ( !this._expectResizeEvent() ) {
  6958. if ( this._ui.container.hasClass( "ui-popup-hidden" ) ) {
  6959. // effectively rapid-open the popup while leaving the screen intact
  6960. this._ui.container.removeClass( "ui-popup-hidden" );
  6961. this.reposition( { positionTo: "window" } );
  6962. this._ignoreResizeEvents();
  6963. }
  6964. this._resizeScreen();
  6965. this._resizeData = null;
  6966. this._orientationchangeInProgress = false;
  6967. }
  6968. } else {
  6969. this._resizeData = null;
  6970. this._orientationchangeInProgress = false;
  6971. }
  6972. },
  6973. _ignoreResizeEvents: function() {
  6974. var self = this;
  6975. if ( this._ignoreResizeTo ) {
  6976. clearTimeout( this._ignoreResizeTo );
  6977. }
  6978. this._ignoreResizeTo = setTimeout( function() { self._ignoreResizeTo = 0; }, 1000 );
  6979. },
  6980. _handleWindowResize: function( e ) {
  6981. if ( this._isOpen && this._ignoreResizeTo === 0 ) {
  6982. if ( ( this._expectResizeEvent() || this._orientationchangeInProgress ) &&
  6983. !this._ui.container.hasClass( "ui-popup-hidden" ) ) {
  6984. // effectively rapid-close the popup while leaving the screen intact
  6985. this._ui.container
  6986. .addClass( "ui-popup-hidden" )
  6987. .removeAttr( "style" );
  6988. }
  6989. }
  6990. },
  6991. _handleWindowOrientationchange: function( e ) {
  6992. if ( !this._orientationchangeInProgress && this._isOpen && this._ignoreResizeTo === 0 ) {
  6993. this._expectResizeEvent();
  6994. this._orientationchangeInProgress = true;
  6995. }
  6996. },
  6997. // When the popup is open, attempting to focus on an element that is not a
  6998. // child of the popup will redirect focus to the popup
  6999. _handleDocumentFocusIn: function( e ) {
  7000. var tgt = e.target, $tgt, ui = this._ui;
  7001. if ( !this._isOpen ) {
  7002. return;
  7003. }
  7004. if ( tgt !== ui.container[ 0 ] ) {
  7005. $tgt = $( e.target );
  7006. if ( 0 === $tgt.parents().filter( ui.container[ 0 ] ).length ) {
  7007. $( document.activeElement ).one( "focus", function( e ) {
  7008. $tgt.blur();
  7009. });
  7010. ui.focusElement.focus();
  7011. e.preventDefault();
  7012. e.stopImmediatePropagation();
  7013. return false;
  7014. } else if ( ui.focusElement[ 0 ] === ui.container[ 0 ] ) {
  7015. ui.focusElement = $tgt;
  7016. }
  7017. }
  7018. this._ignoreResizeEvents();
  7019. },
  7020. _create: function() {
  7021. var ui = {
  7022. screen: $( "<div class='ui-screen-hidden ui-popup-screen'></div>" ),
  7023. placeholder: $( "<div style='display: none;'><!-- placeholder --></div>" ),
  7024. container: $( "<div class='ui-popup-container ui-popup-hidden'></div>" )
  7025. },
  7026. thisPage = this.element.closest( ".ui-page" ),
  7027. myId = this.element.attr( "id" ),
  7028. o = this.options,
  7029. key, value;
  7030. // We need to adjust the history option to be false if there's no AJAX nav.
  7031. // We can't do it in the option declarations because those are run before
  7032. // it is determined whether there shall be AJAX nav.
  7033. o.history = o.history && $.mobile.ajaxEnabled && $.mobile.hashListeningEnabled;
  7034. if ( thisPage.length === 0 ) {
  7035. thisPage = $( "body" );
  7036. }
  7037. // define the container for navigation event bindings
  7038. // TODO this would be nice at the the mobile widget level
  7039. o.container = o.container || $.mobile.pageContainer || thisPage;
  7040. // Apply the proto
  7041. thisPage.append( ui.screen );
  7042. ui.container.insertAfter( ui.screen );
  7043. // Leave a placeholder where the element used to be
  7044. ui.placeholder.insertAfter( this.element );
  7045. if ( myId ) {
  7046. ui.screen.attr( "id", myId + "-screen" );
  7047. ui.container.attr( "id", myId + "-popup" );
  7048. ui.placeholder.html( "<!-- placeholder for " + myId + " -->" );
  7049. }
  7050. ui.container.append( this.element );
  7051. ui.focusElement = ui.container;
  7052. // Add class to popup element
  7053. this.element.addClass( "ui-popup" );
  7054. // Define instance variables
  7055. $.extend( this, {
  7056. _scrollTop: 0,
  7057. _page: thisPage,
  7058. _ui: ui,
  7059. _fallbackTransition: "",
  7060. _currentTransition: false,
  7061. _prereqs: null,
  7062. _isOpen: false,
  7063. _tolerance: null,
  7064. _resizeData: null,
  7065. _ignoreResizeTo: 0,
  7066. _orientationchangeInProgress: false
  7067. });
  7068. // This duplicates the code from the various option setters below for
  7069. // better performance. It must be kept in sync with those setters.
  7070. this._applyTheme( this.element, o.theme, "body" );
  7071. this._applyTheme( this._ui.screen, o.overlayTheme, "overlay" );
  7072. this._applyTransition( o.transition );
  7073. this.element
  7074. .toggleClass( "ui-overlay-shadow", o.shadow )
  7075. .toggleClass( "ui-corner-all", o.corners );
  7076. this._setTolerance( o.tolerance );
  7077. ui.screen.bind( "vclick", $.proxy( this, "_eatEventAndClose" ) );
  7078. this._on( $.mobile.window, {
  7079. orientationchange: $.proxy( this, "_handleWindowOrientationchange" ),
  7080. resize: $.proxy( this, "_handleWindowResize" ),
  7081. keyup: $.proxy( this, "_handleWindowKeyUp" )
  7082. });
  7083. this._on( $.mobile.document, {
  7084. focusin: $.proxy( this, "_handleDocumentFocusIn" )
  7085. });
  7086. },
  7087. _applyTheme: function( dst, theme, prefix ) {
  7088. var classes = ( dst.attr( "class" ) || "").split( " " ),
  7089. alreadyAdded = true,
  7090. currentTheme = null,
  7091. matches,
  7092. themeStr = String( theme );
  7093. while ( classes.length > 0 ) {
  7094. currentTheme = classes.pop();
  7095. matches = ( new RegExp( "^ui-" + prefix + "-([a-z])$" ) ).exec( currentTheme );
  7096. if ( matches && matches.length > 1 ) {
  7097. currentTheme = matches[ 1 ];
  7098. break;
  7099. } else {
  7100. currentTheme = null;
  7101. }
  7102. }
  7103. if ( theme !== currentTheme ) {
  7104. dst.removeClass( "ui-" + prefix + "-" + currentTheme );
  7105. if ( ! ( theme === null || theme === "none" ) ) {
  7106. dst.addClass( "ui-" + prefix + "-" + themeStr );
  7107. }
  7108. }
  7109. },
  7110. _setTheme: function( value ) {
  7111. this._applyTheme( this.element, value, "body" );
  7112. },
  7113. _setOverlayTheme: function( value ) {
  7114. this._applyTheme( this._ui.screen, value, "overlay" );
  7115. if ( this._isOpen ) {
  7116. this._ui.screen.addClass( "in" );
  7117. }
  7118. },
  7119. _setShadow: function( value ) {
  7120. this.element.toggleClass( "ui-overlay-shadow", value );
  7121. },
  7122. _setCorners: function( value ) {
  7123. this.element.toggleClass( "ui-corner-all", value );
  7124. },
  7125. _applyTransition: function( value ) {
  7126. this._ui.container.removeClass( this._fallbackTransition );
  7127. if ( value && value !== "none" ) {
  7128. this._fallbackTransition = $.mobile._maybeDegradeTransition( value );
  7129. if ( this._fallbackTransition === "none" ) {
  7130. this._fallbackTransition = "";
  7131. }
  7132. this._ui.container.addClass( this._fallbackTransition );
  7133. }
  7134. },
  7135. _setTransition: function( value ) {
  7136. if ( !this._currentTransition ) {
  7137. this._applyTransition( value );
  7138. }
  7139. },
  7140. _setTolerance: function( value ) {
  7141. var tol = { t: 30, r: 15, b: 30, l: 15 };
  7142. if ( value !== undefined ) {
  7143. var ar = String( value ).split( "," );
  7144. $.each( ar, function( idx, val ) { ar[ idx ] = parseInt( val, 10 ); } );
  7145. switch( ar.length ) {
  7146. // All values are to be the same
  7147. case 1:
  7148. if ( !isNaN( ar[ 0 ] ) ) {
  7149. tol.t = tol.r = tol.b = tol.l = ar[ 0 ];
  7150. }
  7151. break;
  7152. // The first value denotes top/bottom tolerance, and the second value denotes left/right tolerance
  7153. case 2:
  7154. if ( !isNaN( ar[ 0 ] ) ) {
  7155. tol.t = tol.b = ar[ 0 ];
  7156. }
  7157. if ( !isNaN( ar[ 1 ] ) ) {
  7158. tol.l = tol.r = ar[ 1 ];
  7159. }
  7160. break;
  7161. // The array contains values in the order top, right, bottom, left
  7162. case 4:
  7163. if ( !isNaN( ar[ 0 ] ) ) {
  7164. tol.t = ar[ 0 ];
  7165. }
  7166. if ( !isNaN( ar[ 1 ] ) ) {
  7167. tol.r = ar[ 1 ];
  7168. }
  7169. if ( !isNaN( ar[ 2 ] ) ) {
  7170. tol.b = ar[ 2 ];
  7171. }
  7172. if ( !isNaN( ar[ 3 ] ) ) {
  7173. tol.l = ar[ 3 ];
  7174. }
  7175. break;
  7176. default:
  7177. break;
  7178. }
  7179. }
  7180. this._tolerance = tol;
  7181. },
  7182. _setOption: function( key, value ) {
  7183. var setter = "_set" + key.charAt( 0 ).toUpperCase() + key.slice( 1 );
  7184. if ( this[ setter ] !== undefined ) {
  7185. this[ setter ]( value );
  7186. }
  7187. this._super( key, value );
  7188. },
  7189. // Try and center the overlay over the given coordinates
  7190. _placementCoords: function( desired ) {
  7191. // rectangle within which the popup must fit
  7192. var
  7193. winCoords = windowCoords(),
  7194. rc = {
  7195. x: this._tolerance.l,
  7196. y: winCoords.y + this._tolerance.t,
  7197. cx: winCoords.cx - this._tolerance.l - this._tolerance.r,
  7198. cy: winCoords.cy - this._tolerance.t - this._tolerance.b
  7199. },
  7200. menuSize, ret;
  7201. // Clamp the width of the menu before grabbing its size
  7202. this._ui.container.css( "max-width", rc.cx );
  7203. menuSize = {
  7204. cx: this._ui.container.outerWidth( true ),
  7205. cy: this._ui.container.outerHeight( true )
  7206. };
  7207. // Center the menu over the desired coordinates, while not going outside
  7208. // the window tolerances. This will center wrt. the window if the popup is too large.
  7209. ret = {
  7210. x: fitSegmentInsideSegment( rc.cx, menuSize.cx, rc.x, desired.x ),
  7211. y: fitSegmentInsideSegment( rc.cy, menuSize.cy, rc.y, desired.y )
  7212. };
  7213. // Make sure the top of the menu is visible
  7214. ret.y = Math.max( 0, ret.y );
  7215. // If the height of the menu is smaller than the height of the document
  7216. // align the bottom with the bottom of the document
  7217. // fix for $.mobile.document.height() bug in core 1.7.2.
  7218. var docEl = document.documentElement, docBody = document.body,
  7219. docHeight = Math.max( docEl.clientHeight, docBody.scrollHeight, docBody.offsetHeight, docEl.scrollHeight, docEl.offsetHeight );
  7220. ret.y -= Math.min( ret.y, Math.max( 0, ret.y + menuSize.cy - docHeight ) );
  7221. return { left: ret.x, top: ret.y };
  7222. },
  7223. _createPrereqs: function( screenPrereq, containerPrereq, whenDone ) {
  7224. var self = this, prereqs;
  7225. // It is important to maintain both the local variable prereqs and self._prereqs. The local variable remains in
  7226. // the closure of the functions which call the callbacks passed in. The comparison between the local variable and
  7227. // self._prereqs is necessary, because once a function has been passed to .animationComplete() it will be called
  7228. // next time an animation completes, even if that's not the animation whose end the function was supposed to catch
  7229. // (for example, if an abort happens during the opening animation, the .animationComplete handler is not called for
  7230. // that animation anymore, but the handler remains attached, so it is called the next time the popup is opened
  7231. // - making it stale. Comparing the local variable prereqs to the widget-level variable self._prereqs ensures that
  7232. // callbacks triggered by a stale .animationComplete will be ignored.
  7233. prereqs = {
  7234. screen: $.Deferred(),
  7235. container: $.Deferred()
  7236. };
  7237. prereqs.screen.then( function() {
  7238. if ( prereqs === self._prereqs ) {
  7239. screenPrereq();
  7240. }
  7241. });
  7242. prereqs.container.then( function() {
  7243. if ( prereqs === self._prereqs ) {
  7244. containerPrereq();
  7245. }
  7246. });
  7247. $.when( prereqs.screen, prereqs.container ).done( function() {
  7248. if ( prereqs === self._prereqs ) {
  7249. self._prereqs = null;
  7250. whenDone();
  7251. }
  7252. });
  7253. self._prereqs = prereqs;
  7254. },
  7255. _animate: function( args ) {
  7256. // NOTE before removing the default animation of the screen
  7257. // this had an animate callback that would resolve the deferred
  7258. // now the deferred is resolved immediately
  7259. // TODO remove the dependency on the screen deferred
  7260. this._ui.screen
  7261. .removeClass( args.classToRemove )
  7262. .addClass( args.screenClassToAdd );
  7263. args.prereqs.screen.resolve();
  7264. if ( args.transition && args.transition !== "none" ) {
  7265. if ( args.applyTransition ) {
  7266. this._applyTransition( args.transition );
  7267. }
  7268. if ( this._fallbackTransition ) {
  7269. this._ui.container
  7270. .animationComplete( $.proxy( args.prereqs.container, "resolve" ) )
  7271. .addClass( args.containerClassToAdd )
  7272. .removeClass( args.classToRemove );
  7273. return;
  7274. }
  7275. }
  7276. this._ui.container.removeClass( args.classToRemove );
  7277. args.prereqs.container.resolve();
  7278. },
  7279. // The desired coordinates passed in will be returned untouched if no reference element can be identified via
  7280. // desiredPosition.positionTo. Nevertheless, this function ensures that its return value always contains valid
  7281. // x and y coordinates by specifying the center middle of the window if the coordinates are absent.
  7282. // options: { x: coordinate, y: coordinate, positionTo: string: "origin", "window", or jQuery selector
  7283. _desiredCoords: function( o ) {
  7284. var dst = null, offset, winCoords = windowCoords(), x = o.x, y = o.y, pTo = o.positionTo;
  7285. // Establish which element will serve as the reference
  7286. if ( pTo && pTo !== "origin" ) {
  7287. if ( pTo === "window" ) {
  7288. x = winCoords.cx / 2 + winCoords.x;
  7289. y = winCoords.cy / 2 + winCoords.y;
  7290. } else {
  7291. try {
  7292. dst = $( pTo );
  7293. } catch( e ) {
  7294. dst = null;
  7295. }
  7296. if ( dst ) {
  7297. dst.filter( ":visible" );
  7298. if ( dst.length === 0 ) {
  7299. dst = null;
  7300. }
  7301. }
  7302. }
  7303. }
  7304. // If an element was found, center over it
  7305. if ( dst ) {
  7306. offset = dst.offset();
  7307. x = offset.left + dst.outerWidth() / 2;
  7308. y = offset.top + dst.outerHeight() / 2;
  7309. }
  7310. // Make sure x and y are valid numbers - center over the window
  7311. if ( $.type( x ) !== "number" || isNaN( x ) ) {
  7312. x = winCoords.cx / 2 + winCoords.x;
  7313. }
  7314. if ( $.type( y ) !== "number" || isNaN( y ) ) {
  7315. y = winCoords.cy / 2 + winCoords.y;
  7316. }
  7317. return { x: x, y: y };
  7318. },
  7319. _reposition: function( o ) {
  7320. // We only care about position-related parameters for repositioning
  7321. o = { x: o.x, y: o.y, positionTo: o.positionTo };
  7322. this._trigger( "beforeposition", undefined, o );
  7323. this._ui.container.offset( this._placementCoords( this._desiredCoords( o ) ) );
  7324. },
  7325. reposition: function( o ) {
  7326. if ( this._isOpen ) {
  7327. this._reposition( o );
  7328. }
  7329. },
  7330. _openPrereqsComplete: function() {
  7331. this._ui.container.addClass( "ui-popup-active" );
  7332. this._isOpen = true;
  7333. this._resizeScreen();
  7334. this._ui.container.attr( "tabindex", "0" ).focus();
  7335. this._ignoreResizeEvents();
  7336. this._trigger( "afteropen" );
  7337. },
  7338. _open: function( options ) {
  7339. var o = $.extend( {}, this.options, options ),
  7340. // TODO move blacklist to private method
  7341. androidBlacklist = ( function() {
  7342. var w = window,
  7343. ua = navigator.userAgent,
  7344. // Rendering engine is Webkit, and capture major version
  7345. wkmatch = ua.match( /AppleWebKit\/([0-9\.]+)/ ),
  7346. wkversion = !!wkmatch && wkmatch[ 1 ],
  7347. androidmatch = ua.match( /Android (\d+(?:\.\d+))/ ),
  7348. andversion = !!androidmatch && androidmatch[ 1 ],
  7349. chromematch = ua.indexOf( "Chrome" ) > -1;
  7350. // Platform is Android, WebKit version is greater than 534.13 ( Android 3.2.1 ) and not Chrome.
  7351. if( androidmatch !== null && andversion === "4.0" && wkversion && wkversion > 534.13 && !chromematch ) {
  7352. return true;
  7353. }
  7354. return false;
  7355. }());
  7356. // Count down to triggering "popupafteropen" - we have two prerequisites:
  7357. // 1. The popup window animation completes (container())
  7358. // 2. The screen opacity animation completes (screen())
  7359. this._createPrereqs(
  7360. $.noop,
  7361. $.noop,
  7362. $.proxy( this, "_openPrereqsComplete" ) );
  7363. this._currentTransition = o.transition;
  7364. this._applyTransition( o.transition );
  7365. if ( !this.options.theme ) {
  7366. this._setTheme( this._page.jqmData( "theme" ) || $.mobile.getInheritedTheme( this._page, "c" ) );
  7367. }
  7368. this._ui.screen.removeClass( "ui-screen-hidden" );
  7369. this._ui.container.removeClass( "ui-popup-hidden" );
  7370. // Give applications a chance to modify the contents of the container before it appears
  7371. this._reposition( o );
  7372. if ( this.options.overlayTheme && androidBlacklist ) {
  7373. /* TODO:
  7374. The native browser on Android 4.0.X ("Ice Cream Sandwich") suffers from an issue where the popup overlay appears to be z-indexed
  7375. above the popup itself when certain other styles exist on the same page -- namely, any element set to `position: fixed` and certain
  7376. types of input. These issues are reminiscent of previously uncovered bugs in older versions of Android's native browser:
  7377. https://github.com/scottjehl/Device-Bugs/issues/3
  7378. This fix closes the following bugs ( I use "closes" with reluctance, and stress that this issue should be revisited as soon as possible ):
  7379. https://github.com/jquery/jquery-mobile/issues/4816
  7380. https://github.com/jquery/jquery-mobile/issues/4844
  7381. https://github.com/jquery/jquery-mobile/issues/4874
  7382. */
  7383. // TODO sort out why this._page isn't working
  7384. this.element.closest( ".ui-page" ).addClass( "ui-popup-open" );
  7385. }
  7386. this._animate({
  7387. additionalCondition: true,
  7388. transition: o.transition,
  7389. classToRemove: "",
  7390. screenClassToAdd: "in",
  7391. containerClassToAdd: "in",
  7392. applyTransition: false,
  7393. prereqs: this._prereqs
  7394. });
  7395. },
  7396. _closePrereqScreen: function() {
  7397. this._ui.screen
  7398. .removeClass( "out" )
  7399. .addClass( "ui-screen-hidden" );
  7400. },
  7401. _closePrereqContainer: function() {
  7402. this._ui.container
  7403. .removeClass( "reverse out" )
  7404. .addClass( "ui-popup-hidden" )
  7405. .removeAttr( "style" );
  7406. },
  7407. _closePrereqsDone: function() {
  7408. var container = this._ui.container;
  7409. container.removeAttr( "tabindex" );
  7410. // remove the global mutex for popups
  7411. $.mobile.popup.active = undefined;
  7412. // Blur elements inside the container, including the container
  7413. $( ":focus", container[ 0 ] ).add( container[ 0 ] ).blur();
  7414. // alert users that the popup is closed
  7415. this._trigger( "afterclose" );
  7416. },
  7417. _close: function( immediate ) {
  7418. this._ui.container.removeClass( "ui-popup-active" );
  7419. this._page.removeClass( "ui-popup-open" );
  7420. this._isOpen = false;
  7421. // Count down to triggering "popupafterclose" - we have two prerequisites:
  7422. // 1. The popup window reverse animation completes (container())
  7423. // 2. The screen opacity animation completes (screen())
  7424. this._createPrereqs(
  7425. $.proxy( this, "_closePrereqScreen" ),
  7426. $.proxy( this, "_closePrereqContainer" ),
  7427. $.proxy( this, "_closePrereqsDone" ) );
  7428. this._animate( {
  7429. additionalCondition: this._ui.screen.hasClass( "in" ),
  7430. transition: ( immediate ? "none" : ( this._currentTransition ) ),
  7431. classToRemove: "in",
  7432. screenClassToAdd: "out",
  7433. containerClassToAdd: "reverse out",
  7434. applyTransition: true,
  7435. prereqs: this._prereqs
  7436. });
  7437. },
  7438. _unenhance: function() {
  7439. // Put the element back to where the placeholder was and remove the "ui-popup" class
  7440. this._setTheme( "none" );
  7441. this.element
  7442. // Cannot directly insertAfter() - we need to detach() first, because
  7443. // insertAfter() will do nothing if the payload div was not attached
  7444. // to the DOM at the time the widget was created, and so the payload
  7445. // will remain inside the container even after we call insertAfter().
  7446. // If that happens and we remove the container a few lines below, we
  7447. // will cause an infinite recursion - #5244
  7448. .detach()
  7449. .insertAfter( this._ui.placeholder )
  7450. .removeClass( "ui-popup ui-overlay-shadow ui-corner-all" );
  7451. this._ui.screen.remove();
  7452. this._ui.container.remove();
  7453. this._ui.placeholder.remove();
  7454. },
  7455. _destroy: function() {
  7456. if ( $.mobile.popup.active === this ) {
  7457. this.element.one( "popupafterclose", $.proxy( this, "_unenhance" ) );
  7458. this.close();
  7459. } else {
  7460. this._unenhance();
  7461. }
  7462. },
  7463. _closePopup: function( e, data ) {
  7464. var parsedDst, toUrl, o = this.options, immediate = false;
  7465. // restore location on screen
  7466. window.scrollTo( 0, this._scrollTop );
  7467. if ( e && e.type === "pagebeforechange" && data ) {
  7468. // Determine whether we need to rapid-close the popup, or whether we can
  7469. // take the time to run the closing transition
  7470. if ( typeof data.toPage === "string" ) {
  7471. parsedDst = data.toPage;
  7472. } else {
  7473. parsedDst = data.toPage.jqmData( "url" );
  7474. }
  7475. parsedDst = $.mobile.path.parseUrl( parsedDst );
  7476. toUrl = parsedDst.pathname + parsedDst.search + parsedDst.hash;
  7477. if ( this._myUrl !== $.mobile.path.makeUrlAbsolute( toUrl ) ) {
  7478. // Going to a different page - close immediately
  7479. immediate = true;
  7480. } else {
  7481. e.preventDefault();
  7482. }
  7483. }
  7484. // remove nav bindings
  7485. o.container.unbind( o.closeEvents );
  7486. // unbind click handlers added when history is disabled
  7487. this.element.undelegate( o.closeLinkSelector, o.closeLinkEvents );
  7488. this._close( immediate );
  7489. },
  7490. // any navigation event after a popup is opened should close the popup
  7491. // NOTE the pagebeforechange is bound to catch navigation events that don't
  7492. // alter the url (eg, dialogs from popups)
  7493. _bindContainerClose: function() {
  7494. this.options.container
  7495. .one( this.options.closeEvents, $.proxy( this, "_closePopup" ) );
  7496. },
  7497. // TODO no clear deliniation of what should be here and
  7498. // what should be in _open. Seems to be "visual" vs "history" for now
  7499. open: function( options ) {
  7500. var self = this, opts = this.options, url, hashkey, activePage, currentIsDialog, hasHash, urlHistory;
  7501. // make sure open is idempotent
  7502. if( $.mobile.popup.active ) {
  7503. return;
  7504. }
  7505. // set the global popup mutex
  7506. $.mobile.popup.active = this;
  7507. this._scrollTop = $.mobile.window.scrollTop();
  7508. // if history alteration is disabled close on navigate events
  7509. // and leave the url as is
  7510. if( !( opts.history ) ) {
  7511. self._open( options );
  7512. self._bindContainerClose();
  7513. // When histoy is disabled we have to grab the data-rel
  7514. // back link clicks so we can close the popup instead of
  7515. // relying on history to do it for us
  7516. self.element
  7517. .delegate( opts.closeLinkSelector, opts.closeLinkEvents, function( e ) {
  7518. self.close();
  7519. e.preventDefault();
  7520. });
  7521. return;
  7522. }
  7523. // cache some values for min/readability
  7524. urlHistory = $.mobile.urlHistory;
  7525. hashkey = $.mobile.dialogHashKey;
  7526. activePage = $.mobile.activePage;
  7527. currentIsDialog = activePage.is( ".ui-dialog" );
  7528. this._myUrl = url = urlHistory.getActive().url;
  7529. hasHash = ( url.indexOf( hashkey ) > -1 ) && !currentIsDialog && ( urlHistory.activeIndex > 0 );
  7530. if ( hasHash ) {
  7531. self._open( options );
  7532. self._bindContainerClose();
  7533. return;
  7534. }
  7535. // if the current url has no dialog hash key proceed as normal
  7536. // otherwise, if the page is a dialog simply tack on the hash key
  7537. if ( url.indexOf( hashkey ) === -1 && !currentIsDialog ){
  7538. url = url + (url.indexOf( "#" ) > -1 ? hashkey : "#" + hashkey);
  7539. } else {
  7540. url = $.mobile.path.parseLocation().hash + hashkey;
  7541. }
  7542. // Tack on an extra hashkey if this is the first page and we've just reconstructed the initial hash
  7543. if ( urlHistory.activeIndex === 0 && url === urlHistory.initialDst ) {
  7544. url += hashkey;
  7545. }
  7546. // swallow the the initial navigation event, and bind for the next
  7547. $(window).one( "beforenavigate", function( e ) {
  7548. e.preventDefault();
  7549. self._open( options );
  7550. self._bindContainerClose();
  7551. });
  7552. this.urlAltered = true;
  7553. $.mobile.navigate( url, {role: "dialog"} );
  7554. },
  7555. close: function() {
  7556. // make sure close is idempotent
  7557. if( $.mobile.popup.active !== this ) {
  7558. return;
  7559. }
  7560. this._scrollTop = $.mobile.window.scrollTop();
  7561. if( this.options.history && this.urlAltered ) {
  7562. $.mobile.back();
  7563. this.urlAltered = false;
  7564. } else {
  7565. // simulate the nav bindings having fired
  7566. this._closePopup();
  7567. }
  7568. }
  7569. });
  7570. // TODO this can be moved inside the widget
  7571. $.mobile.popup.handleLink = function( $link ) {
  7572. var closestPage = $link.closest( ":jqmData(role='page')" ),
  7573. scope = ( ( closestPage.length === 0 ) ? $( "body" ) : closestPage ),
  7574. // NOTE make sure to get only the hash, ie7 (wp7) return the absolute href
  7575. // in this case ruining the element selection
  7576. popup = $( $.mobile.path.parseUrl($link.attr( "href" )).hash, scope[0] ),
  7577. offset;
  7578. if ( popup.data( "mobile-popup" ) ) {
  7579. offset = $link.offset();
  7580. popup.popup( "open", {
  7581. x: offset.left + $link.outerWidth() / 2,
  7582. y: offset.top + $link.outerHeight() / 2,
  7583. transition: $link.jqmData( "transition" ),
  7584. positionTo: $link.jqmData( "position-to" )
  7585. });
  7586. }
  7587. //remove after delay
  7588. setTimeout( function() {
  7589. // Check if we are in a listview
  7590. var $parent = $link.parent().parent();
  7591. if ($parent.hasClass("ui-li")) {
  7592. $link = $parent.parent();
  7593. }
  7594. $link.removeClass( $.mobile.activeBtnClass );
  7595. }, 300 );
  7596. };
  7597. // TODO move inside _create
  7598. $.mobile.document.bind( "pagebeforechange", function( e, data ) {
  7599. if ( data.options.role === "popup" ) {
  7600. $.mobile.popup.handleLink( data.options.link );
  7601. e.preventDefault();
  7602. }
  7603. });
  7604. $.mobile.document.bind( "pagecreate create", function( e ) {
  7605. $.mobile.popup.prototype.enhanceWithin( e.target, true );
  7606. });
  7607. })( jQuery );
  7608. /*
  7609. * custom "selectmenu" plugin
  7610. */
  7611. (function( $, undefined ) {
  7612. var extendSelect = function( widget ) {
  7613. var select = widget.select,
  7614. origDestroy = widget._destroy,
  7615. selectID = widget.selectID,
  7616. prefix = ( selectID ? selectID : ( ( $.mobile.ns || "" ) + "uuid-" + widget.uuid ) ),
  7617. popupID = prefix + "-listbox",
  7618. dialogID = prefix + "-dialog",
  7619. label = widget.label,
  7620. thisPage = widget.select.closest( ".ui-page" ),
  7621. selectOptions = widget._selectOptions(),
  7622. isMultiple = widget.isMultiple = widget.select[ 0 ].multiple,
  7623. buttonId = selectID + "-button",
  7624. menuId = selectID + "-menu",
  7625. menuPage = $( "<div data-" + $.mobile.ns + "role='dialog' id='" + dialogID + "' data-" +$.mobile.ns + "theme='"+ widget.options.theme +"' data-" +$.mobile.ns + "overlay-theme='"+ widget.options.overlayTheme +"'>" +
  7626. "<div data-" + $.mobile.ns + "role='header'>" +
  7627. "<div class='ui-title'>" + label.getEncodedText() + "</div>"+
  7628. "</div>"+
  7629. "<div data-" + $.mobile.ns + "role='content'></div>"+
  7630. "</div>" ),
  7631. listbox = $( "<div id='" + popupID + "' class='ui-selectmenu'>" ).insertAfter( widget.select ).popup( { theme: widget.options.overlayTheme } ),
  7632. list = $( "<ul>", {
  7633. "class": "ui-selectmenu-list",
  7634. "id": menuId,
  7635. "role": "listbox",
  7636. "aria-labelledby": buttonId
  7637. }).attr( "data-" + $.mobile.ns + "theme", widget.options.theme )
  7638. .attr( "data-" + $.mobile.ns + "divider-theme", widget.options.dividerTheme )
  7639. .appendTo( listbox ),
  7640. header = $( "<div>", {
  7641. "class": "ui-header ui-bar-" + widget.options.theme
  7642. }).prependTo( listbox ),
  7643. headerTitle = $( "<h1>", {
  7644. "class": "ui-title"
  7645. }).appendTo( header ),
  7646. menuPageContent,
  7647. menuPageClose,
  7648. headerClose;
  7649. if ( widget.isMultiple ) {
  7650. headerClose = $( "<a>", {
  7651. "text": widget.options.closeText,
  7652. "href": "#",
  7653. "class": "ui-btn-left"
  7654. }).attr( "data-" + $.mobile.ns + "iconpos", "notext" ).attr( "data-" + $.mobile.ns + "icon", "delete" ).appendTo( header ).buttonMarkup();
  7655. }
  7656. $.extend( widget, {
  7657. select: widget.select,
  7658. selectID: selectID,
  7659. buttonId: buttonId,
  7660. menuId: menuId,
  7661. popupID: popupID,
  7662. dialogID: dialogID,
  7663. thisPage: thisPage,
  7664. menuPage: menuPage,
  7665. label: label,
  7666. selectOptions: selectOptions,
  7667. isMultiple: isMultiple,
  7668. theme: widget.options.theme,
  7669. listbox: listbox,
  7670. list: list,
  7671. header: header,
  7672. headerTitle: headerTitle,
  7673. headerClose: headerClose,
  7674. menuPageContent: menuPageContent,
  7675. menuPageClose: menuPageClose,
  7676. placeholder: "",
  7677. build: function() {
  7678. var self = this,
  7679. escapeId = function( id ) {
  7680. return id.replace( /([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g, "\\$1" );
  7681. };
  7682. // Create list from select, update state
  7683. self.refresh();
  7684. if ( self._origTabIndex === undefined ) {
  7685. // Map undefined to false, because self._origTabIndex === undefined
  7686. // indicates that we have not yet checked whether the select has
  7687. // originally had a tabindex attribute, whereas false indicates that
  7688. // we have checked the select for such an attribute, and have found
  7689. // none present.
  7690. self._origTabIndex = ( self.select[ 0 ].getAttribute( "tabindex" ) === null ) ? false : self.select.attr( "tabindex" );
  7691. }
  7692. self.select.attr( "tabindex", "-1" ).focus(function() {
  7693. $( this ).blur();
  7694. self.button.focus();
  7695. });
  7696. // Button events
  7697. self.button.bind( "vclick keydown" , function( event ) {
  7698. if ( self.options.disabled || self.isOpen ) {
  7699. return;
  7700. }
  7701. if (event.type === "vclick" ||
  7702. event.keyCode && (event.keyCode === $.mobile.keyCode.ENTER ||
  7703. event.keyCode === $.mobile.keyCode.SPACE)) {
  7704. self._decideFormat();
  7705. if ( self.menuType === "overlay" ) {
  7706. self.button.attr( "href", "#" + escapeId( self.popupID ) ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "popup" );
  7707. } else {
  7708. self.button.attr( "href", "#" + escapeId( self.dialogID ) ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "dialog" );
  7709. }
  7710. self.isOpen = true;
  7711. // Do not prevent default, so the navigation may have a chance to actually open the chosen format
  7712. }
  7713. });
  7714. // Events for list items
  7715. self.list.attr( "role", "listbox" )
  7716. .bind( "focusin", function( e ) {
  7717. $( e.target )
  7718. .attr( "tabindex", "0" )
  7719. .trigger( "vmouseover" );
  7720. })
  7721. .bind( "focusout", function( e ) {
  7722. $( e.target )
  7723. .attr( "tabindex", "-1" )
  7724. .trigger( "vmouseout" );
  7725. })
  7726. .delegate( "li:not(.ui-disabled, .ui-li-divider)", "click", function( event ) {
  7727. // index of option tag to be selected
  7728. var oldIndex = self.select[ 0 ].selectedIndex,
  7729. newIndex = self.list.find( "li:not(.ui-li-divider)" ).index( this ),
  7730. option = self._selectOptions().eq( newIndex )[ 0 ];
  7731. // toggle selected status on the tag for multi selects
  7732. option.selected = self.isMultiple ? !option.selected : true;
  7733. // toggle checkbox class for multiple selects
  7734. if ( self.isMultiple ) {
  7735. $( this ).find( ".ui-icon" )
  7736. .toggleClass( "ui-icon-checkbox-on", option.selected )
  7737. .toggleClass( "ui-icon-checkbox-off", !option.selected );
  7738. }
  7739. // trigger change if value changed
  7740. if ( self.isMultiple || oldIndex !== newIndex ) {
  7741. self.select.trigger( "change" );
  7742. }
  7743. // hide custom select for single selects only - otherwise focus clicked item
  7744. // We need to grab the clicked item the hard way, because the list may have been rebuilt
  7745. if ( self.isMultiple ) {
  7746. self.list.find( "li:not(.ui-li-divider)" ).eq( newIndex )
  7747. .addClass( "ui-btn-down-" + widget.options.theme ).find( "a" ).first().focus();
  7748. }
  7749. else {
  7750. self.close();
  7751. }
  7752. event.preventDefault();
  7753. })
  7754. .keydown(function( event ) { //keyboard events for menu items
  7755. var target = $( event.target ),
  7756. li = target.closest( "li" ),
  7757. prev, next;
  7758. // switch logic based on which key was pressed
  7759. switch ( event.keyCode ) {
  7760. // up or left arrow keys
  7761. case 38:
  7762. prev = li.prev().not( ".ui-selectmenu-placeholder" );
  7763. if ( prev.is( ".ui-li-divider" ) ) {
  7764. prev = prev.prev();
  7765. }
  7766. // if there's a previous option, focus it
  7767. if ( prev.length ) {
  7768. target
  7769. .blur()
  7770. .attr( "tabindex", "-1" );
  7771. prev.addClass( "ui-btn-down-" + widget.options.theme ).find( "a" ).first().focus();
  7772. }
  7773. return false;
  7774. // down or right arrow keys
  7775. case 40:
  7776. next = li.next();
  7777. if ( next.is( ".ui-li-divider" ) ) {
  7778. next = next.next();
  7779. }
  7780. // if there's a next option, focus it
  7781. if ( next.length ) {
  7782. target
  7783. .blur()
  7784. .attr( "tabindex", "-1" );
  7785. next.addClass( "ui-btn-down-" + widget.options.theme ).find( "a" ).first().focus();
  7786. }
  7787. return false;
  7788. // If enter or space is pressed, trigger click
  7789. case 13:
  7790. case 32:
  7791. target.trigger( "click" );
  7792. return false;
  7793. }
  7794. });
  7795. // button refocus ensures proper height calculation
  7796. // by removing the inline style and ensuring page inclusion
  7797. self.menuPage.bind( "pagehide", function() {
  7798. // TODO centralize page removal binding / handling in the page plugin.
  7799. // Suggestion from @jblas to do refcounting
  7800. //
  7801. // TODO extremely confusing dependency on the open method where the pagehide.remove
  7802. // bindings are stripped to prevent the parent page from disappearing. The way
  7803. // we're keeping pages in the DOM right now sucks
  7804. //
  7805. // rebind the page remove that was unbound in the open function
  7806. // to allow for the parent page removal from actions other than the use
  7807. // of a dialog sized custom select
  7808. //
  7809. // doing this here provides for the back button on the custom select dialog
  7810. $.mobile._bindPageRemove.call( self.thisPage );
  7811. });
  7812. // Events on the popup
  7813. self.listbox.bind( "popupafterclose", function( event ) {
  7814. self.close();
  7815. });
  7816. // Close button on small overlays
  7817. if ( self.isMultiple ) {
  7818. self.headerClose.click(function() {
  7819. if ( self.menuType === "overlay" ) {
  7820. self.close();
  7821. return false;
  7822. }
  7823. });
  7824. }
  7825. // track this dependency so that when the parent page
  7826. // is removed on pagehide it will also remove the menupage
  7827. self.thisPage.addDependents( this.menuPage );
  7828. },
  7829. _isRebuildRequired: function() {
  7830. var list = this.list.find( "li" ),
  7831. options = this._selectOptions();
  7832. // TODO exceedingly naive method to determine difference
  7833. // ignores value changes etc in favor of a forcedRebuild
  7834. // from the user in the refresh method
  7835. return options.text() !== list.text();
  7836. },
  7837. selected: function() {
  7838. return this._selectOptions().filter( ":selected:not( :jqmData(placeholder='true') )" );
  7839. },
  7840. refresh: function( forceRebuild , foo ) {
  7841. var self = this,
  7842. select = this.element,
  7843. isMultiple = this.isMultiple,
  7844. indicies;
  7845. if ( forceRebuild || this._isRebuildRequired() ) {
  7846. self._buildList();
  7847. }
  7848. indicies = this.selectedIndices();
  7849. self.setButtonText();
  7850. self.setButtonCount();
  7851. self.list.find( "li:not(.ui-li-divider)" )
  7852. .removeClass( $.mobile.activeBtnClass )
  7853. .attr( "aria-selected", false )
  7854. .each(function( i ) {
  7855. if ( $.inArray( i, indicies ) > -1 ) {
  7856. var item = $( this );
  7857. // Aria selected attr
  7858. item.attr( "aria-selected", true );
  7859. // Multiple selects: add the "on" checkbox state to the icon
  7860. if ( self.isMultiple ) {
  7861. item.find( ".ui-icon" ).removeClass( "ui-icon-checkbox-off" ).addClass( "ui-icon-checkbox-on" );
  7862. } else {
  7863. if ( item.is( ".ui-selectmenu-placeholder" ) ) {
  7864. item.next().addClass( $.mobile.activeBtnClass );
  7865. } else {
  7866. item.addClass( $.mobile.activeBtnClass );
  7867. }
  7868. }
  7869. }
  7870. });
  7871. },
  7872. close: function() {
  7873. if ( this.options.disabled || !this.isOpen ) {
  7874. return;
  7875. }
  7876. var self = this;
  7877. if ( self.menuType === "page" ) {
  7878. self.menuPage.dialog( "close" );
  7879. self.list.appendTo( self.listbox );
  7880. } else {
  7881. self.listbox.popup( "close" );
  7882. }
  7883. self._focusButton();
  7884. // allow the dialog to be closed again
  7885. self.isOpen = false;
  7886. },
  7887. open: function() {
  7888. this.button.click();
  7889. },
  7890. _decideFormat: function() {
  7891. var self = this,
  7892. $window = $.mobile.window,
  7893. selfListParent = self.list.parent(),
  7894. menuHeight = selfListParent.outerHeight(),
  7895. menuWidth = selfListParent.outerWidth(),
  7896. activePage = $( "." + $.mobile.activePageClass ),
  7897. scrollTop = $window.scrollTop(),
  7898. btnOffset = self.button.offset().top,
  7899. screenHeight = $window.height(),
  7900. screenWidth = $window.width();
  7901. function focusMenuItem() {
  7902. var selector = self.list.find( "." + $.mobile.activeBtnClass + " a" );
  7903. if ( selector.length === 0 ) {
  7904. selector = self.list.find( "li.ui-btn:not( :jqmData(placeholder='true') ) a" );
  7905. }
  7906. selector.first().focus().closest( "li" ).addClass( "ui-btn-down-" + widget.options.theme );
  7907. }
  7908. if ( menuHeight > screenHeight - 80 || !$.support.scrollTop ) {
  7909. self.menuPage.appendTo( $.mobile.pageContainer ).page();
  7910. self.menuPageContent = menuPage.find( ".ui-content" );
  7911. self.menuPageClose = menuPage.find( ".ui-header a" );
  7912. // prevent the parent page from being removed from the DOM,
  7913. // otherwise the results of selecting a list item in the dialog
  7914. // fall into a black hole
  7915. self.thisPage.unbind( "pagehide.remove" );
  7916. //for WebOS/Opera Mini (set lastscroll using button offset)
  7917. if ( scrollTop === 0 && btnOffset > screenHeight ) {
  7918. self.thisPage.one( "pagehide", function() {
  7919. $( this ).jqmData( "lastScroll", btnOffset );
  7920. });
  7921. }
  7922. self.menuPage
  7923. .one( "pageshow", function() {
  7924. focusMenuItem();
  7925. })
  7926. .one( "pagehide", function() {
  7927. self.close();
  7928. });
  7929. self.menuType = "page";
  7930. self.menuPageContent.append( self.list );
  7931. self.menuPage.find("div .ui-title").text(self.label.text());
  7932. } else {
  7933. self.menuType = "overlay";
  7934. self.listbox.one( "popupafteropen", focusMenuItem );
  7935. }
  7936. },
  7937. _buildList: function() {
  7938. var self = this,
  7939. o = this.options,
  7940. placeholder = this.placeholder,
  7941. needPlaceholder = true,
  7942. optgroups = [],
  7943. lis = [],
  7944. dataIcon = self.isMultiple ? "checkbox-off" : "false";
  7945. self.list.empty().filter( ".ui-listview" ).listview( "destroy" );
  7946. var $options = self.select.find( "option" ),
  7947. numOptions = $options.length,
  7948. select = this.select[ 0 ],
  7949. dataPrefix = 'data-' + $.mobile.ns,
  7950. dataIndexAttr = dataPrefix + 'option-index',
  7951. dataIconAttr = dataPrefix + 'icon',
  7952. dataRoleAttr = dataPrefix + 'role',
  7953. dataPlaceholderAttr = dataPrefix + 'placeholder',
  7954. fragment = document.createDocumentFragment(),
  7955. isPlaceholderItem = false,
  7956. optGroup;
  7957. for (var i = 0; i < numOptions;i++, isPlaceholderItem = false) {
  7958. var option = $options[i],
  7959. $option = $( option ),
  7960. parent = option.parentNode,
  7961. text = $option.text(),
  7962. anchor = document.createElement( 'a' ),
  7963. classes = [];
  7964. anchor.setAttribute( 'href', '#' );
  7965. anchor.appendChild( document.createTextNode( text ) );
  7966. // Are we inside an optgroup?
  7967. if ( parent !== select && parent.nodeName.toLowerCase() === "optgroup" ) {
  7968. var optLabel = parent.getAttribute( 'label' );
  7969. if ( optLabel !== optGroup ) {
  7970. var divider = document.createElement( 'li' );
  7971. divider.setAttribute( dataRoleAttr, 'list-divider' );
  7972. divider.setAttribute( 'role', 'option' );
  7973. divider.setAttribute( 'tabindex', '-1' );
  7974. divider.appendChild( document.createTextNode( optLabel ) );
  7975. fragment.appendChild( divider );
  7976. optGroup = optLabel;
  7977. }
  7978. }
  7979. if ( needPlaceholder && ( !option.getAttribute( "value" ) || text.length === 0 || $option.jqmData( "placeholder" ) ) ) {
  7980. needPlaceholder = false;
  7981. isPlaceholderItem = true;
  7982. // If we have identified a placeholder, record the fact that it was
  7983. // us who have added the placeholder to the option and mark it
  7984. // retroactively in the select as well
  7985. if ( null === option.getAttribute( dataPlaceholderAttr ) ) {
  7986. this._removePlaceholderAttr = true;
  7987. }
  7988. option.setAttribute( dataPlaceholderAttr, true );
  7989. if ( o.hidePlaceholderMenuItems ) {
  7990. classes.push( "ui-selectmenu-placeholder" );
  7991. }
  7992. if ( placeholder !== text ) {
  7993. placeholder = self.placeholder = text;
  7994. }
  7995. }
  7996. var item = document.createElement('li');
  7997. if ( option.disabled ) {
  7998. classes.push( "ui-disabled" );
  7999. item.setAttribute('aria-disabled',true);
  8000. }
  8001. item.setAttribute( dataIndexAttr,i );
  8002. item.setAttribute( dataIconAttr, dataIcon );
  8003. if ( isPlaceholderItem ) {
  8004. item.setAttribute( dataPlaceholderAttr, true );
  8005. }
  8006. item.className = classes.join( " " );
  8007. item.setAttribute( 'role', 'option' );
  8008. anchor.setAttribute( 'tabindex', '-1' );
  8009. item.appendChild( anchor );
  8010. fragment.appendChild( item );
  8011. }
  8012. self.list[0].appendChild( fragment );
  8013. // Hide header if it's not a multiselect and there's no placeholder
  8014. if ( !this.isMultiple && !placeholder.length ) {
  8015. this.header.hide();
  8016. } else {
  8017. this.headerTitle.text( this.placeholder );
  8018. }
  8019. // Now populated, create listview
  8020. self.list.listview();
  8021. },
  8022. _button: function() {
  8023. return $( "<a>", {
  8024. "href": "#",
  8025. "role": "button",
  8026. // TODO value is undefined at creation
  8027. "id": this.buttonId,
  8028. "aria-haspopup": "true",
  8029. // TODO value is undefined at creation
  8030. "aria-owns": this.menuId
  8031. });
  8032. },
  8033. _destroy: function() {
  8034. this.close();
  8035. // Restore the tabindex attribute to its original value
  8036. if ( this._origTabIndex !== undefined ) {
  8037. if ( this._origTabIndex !== false ) {
  8038. this.select.attr( "tabindex", this._origTabIndex );
  8039. } else {
  8040. this.select.removeAttr( "tabindex" );
  8041. }
  8042. }
  8043. // Remove the placeholder attribute if we were the ones to add it
  8044. if ( this._removePlaceholderAttr ) {
  8045. this._selectOptions().removeAttr( "data-" + $.mobile.ns + "placeholder" );
  8046. }
  8047. // Remove the popup
  8048. this.listbox.remove();
  8049. // Remove the dialog
  8050. this.menuPage.remove();
  8051. // Chain up
  8052. origDestroy.apply( this, arguments );
  8053. }
  8054. });
  8055. };
  8056. // issue #3894 - core doesn't trigger events on disabled delegates
  8057. $.mobile.document.bind( "selectmenubeforecreate", function( event ) {
  8058. var selectmenuWidget = $( event.target ).data( "mobile-selectmenu" );
  8059. if ( !selectmenuWidget.options.nativeMenu &&
  8060. selectmenuWidget.element.parents( ":jqmData(role='popup')" ).length === 0 ) {
  8061. extendSelect( selectmenuWidget );
  8062. }
  8063. });
  8064. })( jQuery );
  8065. (function( $, undefined ) {
  8066. $.widget( "mobile.controlgroup", $.mobile.widget, $.extend( {
  8067. options: {
  8068. shadow: false,
  8069. corners: true,
  8070. excludeInvisible: true,
  8071. type: "vertical",
  8072. mini: false,
  8073. initSelector: ":jqmData(role='controlgroup')"
  8074. },
  8075. _create: function() {
  8076. var $el = this.element,
  8077. ui = {
  8078. inner: $( "<div class='ui-controlgroup-controls'></div>" ),
  8079. legend: $( "<div role='heading' class='ui-controlgroup-label'></div>" )
  8080. },
  8081. grouplegend = $el.children( "legend" ),
  8082. self = this;
  8083. // Apply the proto
  8084. $el.wrapInner( ui.inner );
  8085. if ( grouplegend.length ) {
  8086. ui.legend.append( grouplegend ).insertBefore( $el.children( 0 ) );
  8087. }
  8088. $el.addClass( "ui-corner-all ui-controlgroup" );
  8089. $.extend( this, {
  8090. _initialRefresh: true
  8091. });
  8092. $.each( this.options, function( key, value ) {
  8093. // Cause initial options to be applied by their handler by temporarily setting the option to undefined
  8094. // - the handler then sets it to the initial value
  8095. self.options[ key ] = undefined;
  8096. self._setOption( key, value, true );
  8097. });
  8098. },
  8099. _init: function() {
  8100. this.refresh();
  8101. },
  8102. _setOption: function( key, value ) {
  8103. var setter = "_set" + key.charAt( 0 ).toUpperCase() + key.slice( 1 );
  8104. if ( this[ setter ] !== undefined ) {
  8105. this[ setter ]( value );
  8106. }
  8107. this._super( key, value );
  8108. this.element.attr( "data-" + ( $.mobile.ns || "" ) + ( key.replace( /([A-Z])/, "-$1" ).toLowerCase() ), value );
  8109. },
  8110. _setType: function( value ) {
  8111. this.element
  8112. .removeClass( "ui-controlgroup-horizontal ui-controlgroup-vertical" )
  8113. .addClass( "ui-controlgroup-" + value );
  8114. this.refresh();
  8115. },
  8116. _setCorners: function( value ) {
  8117. this.element.toggleClass( "ui-corner-all", value );
  8118. },
  8119. _setShadow: function( value ) {
  8120. this.element.toggleClass( "ui-shadow", value );
  8121. },
  8122. _setMini: function( value ) {
  8123. this.element.toggleClass( "ui-mini", value );
  8124. },
  8125. container: function() {
  8126. return this.element.children( ".ui-controlgroup-controls" );
  8127. },
  8128. refresh: function() {
  8129. var els = this.element.find( ".ui-btn" ).not( ".ui-slider-handle" ),
  8130. create = this._initialRefresh;
  8131. if ( $.mobile.checkboxradio ) {
  8132. this.element.find( ":mobile-checkboxradio" ).checkboxradio( "refresh" );
  8133. }
  8134. this._addFirstLastClasses( els, this.options.excludeInvisible ? this._getVisibles( els, create ) : els, create );
  8135. this._initialRefresh = false;
  8136. }
  8137. }, $.mobile.behaviors.addFirstLastClasses ) );
  8138. // TODO: Implement a mechanism to allow widgets to become enhanced in the
  8139. // correct order when their correct enhancement depends on other widgets in
  8140. // the page being correctly enhanced already.
  8141. //
  8142. // For now, we wait until dom-ready to attach the controlgroup's enhancement
  8143. // hook, because by that time, all the other widgets' enhancement hooks should
  8144. // already be in place, ensuring that all widgets that need to be grouped will
  8145. // already have been enhanced by the time the controlgroup is created.
  8146. $( function() {
  8147. $.mobile.document.bind( "pagecreate create", function( e ) {
  8148. $.mobile.controlgroup.prototype.enhanceWithin( e.target, true );
  8149. });
  8150. });
  8151. })(jQuery);
  8152. (function( $, undefined ) {
  8153. $( document ).bind( "pagecreate create", function( e ) {
  8154. //links within content areas, tests included with page
  8155. $( e.target )
  8156. .find( "a" )
  8157. .jqmEnhanceable()
  8158. .filter( ":jqmData(rel='popup')[href][href!='']" )
  8159. .each( function() {
  8160. // Accessibility info for popups
  8161. var e = this,
  8162. href = $( this ).attr( "href" ),
  8163. idref = href.substring( 1 );
  8164. e.setAttribute( "aria-haspopup", true );
  8165. e.setAttribute( "aria-owns", idref );
  8166. e.setAttribute( "aria-expanded", false );
  8167. $( document )
  8168. .on( "popupafteropen", href, function() {
  8169. e.setAttribute( "aria-expanded", true );
  8170. })
  8171. .on( "popupafterclose", href, function() {
  8172. e.setAttribute( "aria-expanded", false );
  8173. });
  8174. })
  8175. .end()
  8176. .not( ".ui-btn, .ui-link-inherit, :jqmData(role='none'), :jqmData(role='nojs')" )
  8177. .addClass( "ui-link" );
  8178. });
  8179. })( jQuery );
  8180. (function( $, undefined ) {
  8181. $.widget( "mobile.fixedtoolbar", $.mobile.widget, {
  8182. options: {
  8183. visibleOnPageShow: true,
  8184. disablePageZoom: true,
  8185. transition: "slide", //can be none, fade, slide (slide maps to slideup or slidedown)
  8186. fullscreen: false,
  8187. tapToggle: true,
  8188. tapToggleBlacklist: "a, button, input, select, textarea, .ui-header-fixed, .ui-footer-fixed, .ui-popup, .ui-panel, .ui-panel-dismiss-open",
  8189. hideDuringFocus: "input, textarea, select",
  8190. updatePagePadding: true,
  8191. trackPersistentToolbars: true,
  8192. // Browser detection! Weeee, here we go...
  8193. // Unfortunately, position:fixed is costly, not to mention probably impossible, to feature-detect accurately.
  8194. // Some tests exist, but they currently return false results in critical devices and browsers, which could lead to a broken experience.
  8195. // Testing fixed positioning is also pretty obtrusive to page load, requiring injected elements and scrolling the window
  8196. // The following function serves to rule out some popular browsers with known fixed-positioning issues
  8197. // This is a plugin option like any other, so feel free to improve or overwrite it
  8198. supportBlacklist: function() {
  8199. return !$.support.fixedPosition;
  8200. },
  8201. initSelector: ":jqmData(position='fixed')"
  8202. },
  8203. _create: function() {
  8204. var self = this,
  8205. o = self.options,
  8206. $el = self.element,
  8207. tbtype = $el.is( ":jqmData(role='header')" ) ? "header" : "footer",
  8208. $page = $el.closest( ".ui-page" );
  8209. // Feature detecting support for
  8210. if ( o.supportBlacklist() ) {
  8211. self.destroy();
  8212. return;
  8213. }
  8214. $el.addClass( "ui-"+ tbtype +"-fixed" );
  8215. // "fullscreen" overlay positioning
  8216. if ( o.fullscreen ) {
  8217. $el.addClass( "ui-"+ tbtype +"-fullscreen" );
  8218. $page.addClass( "ui-page-" + tbtype + "-fullscreen" );
  8219. }
  8220. // If not fullscreen, add class to page to set top or bottom padding
  8221. else{
  8222. $page.addClass( "ui-page-" + tbtype + "-fixed" );
  8223. }
  8224. $.extend( this, {
  8225. _thisPage: null
  8226. });
  8227. self._addTransitionClass();
  8228. self._bindPageEvents();
  8229. self._bindToggleHandlers();
  8230. },
  8231. _addTransitionClass: function() {
  8232. var tclass = this.options.transition;
  8233. if ( tclass && tclass !== "none" ) {
  8234. // use appropriate slide for header or footer
  8235. if ( tclass === "slide" ) {
  8236. tclass = this.element.is( ".ui-header" ) ? "slidedown" : "slideup";
  8237. }
  8238. this.element.addClass( tclass );
  8239. }
  8240. },
  8241. _bindPageEvents: function() {
  8242. this._thisPage = this.element.closest( ".ui-page" );
  8243. //page event bindings
  8244. // Fixed toolbars require page zoom to be disabled, otherwise usability issues crop up
  8245. // This method is meant to disable zoom while a fixed-positioned toolbar page is visible
  8246. this._on( this._thisPage, {
  8247. "pagebeforeshow": "_handlePageBeforeShow",
  8248. "webkitAnimationStart":"_handleAnimationStart",
  8249. "animationstart":"_handleAnimationStart",
  8250. "updatelayout": "_handleAnimationStart",
  8251. "pageshow": "_handlePageShow",
  8252. "pagebeforehide": "_handlePageBeforeHide"
  8253. });
  8254. },
  8255. _handlePageBeforeShow: function() {
  8256. var o = this.options;
  8257. if ( o.disablePageZoom ) {
  8258. $.mobile.zoom.disable( true );
  8259. }
  8260. if ( !o.visibleOnPageShow ) {
  8261. this.hide( true );
  8262. }
  8263. },
  8264. _handleAnimationStart: function() {
  8265. if ( this.options.updatePagePadding ) {
  8266. this.updatePagePadding( this._thisPage );
  8267. }
  8268. },
  8269. _handlePageShow: function() {
  8270. this.updatePagePadding( this._thisPage );
  8271. if ( this.options.updatePagePadding ) {
  8272. this._on( $.mobile.window, { "throttledresize": "updatePagePadding" } );
  8273. }
  8274. },
  8275. _handlePageBeforeHide: function( e, ui ) {
  8276. var o = this.options;
  8277. if ( o.disablePageZoom ) {
  8278. $.mobile.zoom.enable( true );
  8279. }
  8280. if ( o.updatePagePadding ) {
  8281. this._off( $.mobile.window, "throttledresize" );
  8282. }
  8283. if ( o.trackPersistentToolbars ) {
  8284. var thisFooter = $( ".ui-footer-fixed:jqmData(id)", this._thisPage ),
  8285. thisHeader = $( ".ui-header-fixed:jqmData(id)", this._thisPage ),
  8286. nextFooter = thisFooter.length && ui.nextPage && $( ".ui-footer-fixed:jqmData(id='" + thisFooter.jqmData( "id" ) + "')", ui.nextPage ) || $(),
  8287. nextHeader = thisHeader.length && ui.nextPage && $( ".ui-header-fixed:jqmData(id='" + thisHeader.jqmData( "id" ) + "')", ui.nextPage ) || $();
  8288. if ( nextFooter.length || nextHeader.length ) {
  8289. nextFooter.add( nextHeader ).appendTo( $.mobile.pageContainer );
  8290. ui.nextPage.one( "pageshow", function() {
  8291. nextHeader.prependTo( this );
  8292. nextFooter.appendTo( this );
  8293. });
  8294. }
  8295. }
  8296. },
  8297. _visible: true,
  8298. // This will set the content element's top or bottom padding equal to the toolbar's height
  8299. updatePagePadding: function( tbPage ) {
  8300. var $el = this.element,
  8301. header = $el.is( ".ui-header" ),
  8302. pos = parseFloat( $el.css( header ? "top" : "bottom" ) );
  8303. // This behavior only applies to "fixed", not "fullscreen"
  8304. if ( this.options.fullscreen ) { return; }
  8305. // tbPage argument can be a Page object or an event, if coming from throttled resize.
  8306. tbPage = ( tbPage && tbPage.type === undefined && tbPage ) || this._thisPage || $el.closest( ".ui-page" );
  8307. $( tbPage ).css( "padding-" + ( header ? "top" : "bottom" ), $el.outerHeight() + pos );
  8308. },
  8309. _useTransition: function( notransition ) {
  8310. var $win = $.mobile.window,
  8311. $el = this.element,
  8312. scroll = $win.scrollTop(),
  8313. elHeight = $el.height(),
  8314. pHeight = $el.closest( ".ui-page" ).height(),
  8315. viewportHeight = $.mobile.getScreenHeight(),
  8316. tbtype = $el.is( ":jqmData(role='header')" ) ? "header" : "footer";
  8317. return !notransition &&
  8318. ( this.options.transition && this.options.transition !== "none" &&
  8319. (
  8320. ( tbtype === "header" && !this.options.fullscreen && scroll > elHeight ) ||
  8321. ( tbtype === "footer" && !this.options.fullscreen && scroll + viewportHeight < pHeight - elHeight )
  8322. ) || this.options.fullscreen
  8323. );
  8324. },
  8325. show: function( notransition ) {
  8326. var hideClass = "ui-fixed-hidden",
  8327. $el = this.element;
  8328. if ( this._useTransition( notransition ) ) {
  8329. $el
  8330. .removeClass( "out " + hideClass )
  8331. .addClass( "in" )
  8332. .animationComplete(function () {
  8333. $el.removeClass('in');
  8334. });
  8335. }
  8336. else {
  8337. $el.removeClass( hideClass );
  8338. }
  8339. this._visible = true;
  8340. },
  8341. hide: function( notransition ) {
  8342. var hideClass = "ui-fixed-hidden",
  8343. $el = this.element,
  8344. // if it's a slide transition, our new transitions need the reverse class as well to slide outward
  8345. outclass = "out" + ( this.options.transition === "slide" ? " reverse" : "" );
  8346. if( this._useTransition( notransition ) ) {
  8347. $el
  8348. .addClass( outclass )
  8349. .removeClass( "in" )
  8350. .animationComplete(function() {
  8351. $el.addClass( hideClass ).removeClass( outclass );
  8352. });
  8353. }
  8354. else {
  8355. $el.addClass( hideClass ).removeClass( outclass );
  8356. }
  8357. this._visible = false;
  8358. },
  8359. toggle: function() {
  8360. this[ this._visible ? "hide" : "show" ]();
  8361. },
  8362. _bindToggleHandlers: function() {
  8363. var self = this,
  8364. o = self.options,
  8365. $el = self.element,
  8366. delayShow, delayHide,
  8367. isVisible = true;
  8368. // tap toggle
  8369. $el.closest( ".ui-page" )
  8370. .bind( "vclick", function( e ) {
  8371. if ( o.tapToggle && !$( e.target ).closest( o.tapToggleBlacklist ).length ) {
  8372. self.toggle();
  8373. }
  8374. })
  8375. .bind( "focusin focusout", function( e ) {
  8376. //this hides the toolbars on a keyboard pop to give more screen room and prevent ios bug which
  8377. //positions fixed toolbars in the middle of the screen on pop if the input is near the top or
  8378. //bottom of the screen addresses issues #4410 Footer navbar moves up when clicking on a textbox in an Android environment
  8379. //and issue #4113 Header and footer change their position after keyboard popup - iOS
  8380. //and issue #4410 Footer navbar moves up when clicking on a textbox in an Android environment
  8381. if ( screen.width < 1025 && $( e.target ).is( o.hideDuringFocus ) && !$( e.target ).closest( ".ui-header-fixed, .ui-footer-fixed" ).length ) {
  8382. //Fix for issue #4724 Moving through form in Mobile Safari with "Next" and "Previous" system
  8383. //controls causes fixed position, tap-toggle false Header to reveal itself
  8384. // isVisible instead of self._visible because the focusin and focusout events fire twice at the same time
  8385. // Also use a delay for hiding the toolbars because on Android native browser focusin is direclty followed
  8386. // by a focusout when a native selects opens and the other way around when it closes.
  8387. if ( e.type === "focusout" && !isVisible ) {
  8388. isVisible = true;
  8389. //wait for the stack to unwind and see if we have jumped to another input
  8390. clearTimeout( delayHide );
  8391. delayShow = setTimeout( function() {
  8392. self.show();
  8393. }, 0 );
  8394. } else if ( e.type === "focusin" && !!isVisible ) {
  8395. //if we have jumped to another input clear the time out to cancel the show.
  8396. clearTimeout( delayShow );
  8397. isVisible = false;
  8398. delayHide = setTimeout( function() {
  8399. self.hide();
  8400. }, 0 );
  8401. }
  8402. }
  8403. });
  8404. },
  8405. _destroy: function() {
  8406. var $el = this.element,
  8407. header = $el.is( ".ui-header" );
  8408. $el.closest( ".ui-page" ).css( "padding-" + ( header ? "top" : "bottom" ), "" );
  8409. $el.removeClass( "ui-header-fixed ui-footer-fixed ui-header-fullscreen ui-footer-fullscreen in out fade slidedown slideup ui-fixed-hidden" );
  8410. $el.closest( ".ui-page" ).removeClass( "ui-page-header-fixed ui-page-footer-fixed ui-page-header-fullscreen ui-page-footer-fullscreen" );
  8411. }
  8412. });
  8413. //auto self-init widgets
  8414. $.mobile.document
  8415. .bind( "pagecreate create", function( e ) {
  8416. // DEPRECATED in 1.1: support for data-fullscreen=true|false on the page element.
  8417. // This line ensures it still works, but we recommend moving the attribute to the toolbars themselves.
  8418. if ( $( e.target ).jqmData( "fullscreen" ) ) {
  8419. $( $.mobile.fixedtoolbar.prototype.options.initSelector, e.target ).not( ":jqmData(fullscreen)" ).jqmData( "fullscreen", true );
  8420. }
  8421. $.mobile.fixedtoolbar.prototype.enhanceWithin( e.target );
  8422. });
  8423. })( jQuery );
  8424. (function( $, undefined ) {
  8425. $.widget( "mobile.fixedtoolbar", $.mobile.fixedtoolbar, {
  8426. _create: function() {
  8427. this._super();
  8428. this._workarounds();
  8429. },
  8430. //check the browser and version and run needed workarounds
  8431. _workarounds: function() {
  8432. var ua = navigator.userAgent,
  8433. platform = navigator.platform,
  8434. // Rendering engine is Webkit, and capture major version
  8435. wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ ),
  8436. wkversion = !!wkmatch && wkmatch[ 1 ],
  8437. os = null,
  8438. self = this;
  8439. //set the os we are working in if it dosent match one with workarounds return
  8440. if( platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1 || platform.indexOf( "iPod" ) > -1 ){
  8441. os = "ios";
  8442. } else if( ua.indexOf( "Android" ) > -1 ){
  8443. os = "android";
  8444. } else {
  8445. return;
  8446. }
  8447. //check os version if it dosent match one with workarounds return
  8448. if( os === "ios" ) {
  8449. //iOS workarounds
  8450. self._bindScrollWorkaround();
  8451. } else if( os === "android" && wkversion && wkversion < 534 ) {
  8452. //Android 2.3 run all Android 2.3 workaround
  8453. self._bindScrollWorkaround();
  8454. self._bindListThumbWorkaround();
  8455. } else {
  8456. return;
  8457. }
  8458. },
  8459. //Utility class for checking header and footer positions relative to viewport
  8460. _viewportOffset: function() {
  8461. var $el = this.element,
  8462. header = $el.is( ".ui-header" ),
  8463. offset = Math.abs($el.offset().top - $.mobile.window.scrollTop());
  8464. if( !header ) {
  8465. offset = Math.round(offset - $.mobile.window.height() + $el.outerHeight())-60;
  8466. }
  8467. return offset;
  8468. },
  8469. //bind events for _triggerRedraw() function
  8470. _bindScrollWorkaround: function() {
  8471. var self = this;
  8472. //bind to scrollstop and check if the toolbars are correctly positioned
  8473. this._on( $.mobile.window, { scrollstop: function() {
  8474. var viewportOffset = self._viewportOffset();
  8475. //check if the header is visible and if its in the right place
  8476. if( viewportOffset > 2 && self._visible) {
  8477. self._triggerRedraw();
  8478. }
  8479. }});
  8480. },
  8481. //this addresses issue #4250 Persistent footer instability in v1.1 with long select lists in Android 2.3.3
  8482. //and issue #3748 Android 2.x: Page transitions broken when fixed toolbars used
  8483. //the absolutely positioned thumbnail in a list view causes problems with fixed position buttons above in a nav bar
  8484. //setting the li's to -webkit-transform:translate3d(0,0,0); solves this problem to avoide potential issues in other
  8485. //platforms we scope this with the class ui-android-2x-fix
  8486. _bindListThumbWorkaround: function() {
  8487. this.element.closest(".ui-page").addClass( "ui-android-2x-fixed" );
  8488. },
  8489. //this addresses issues #4337 Fixed header problem after scrolling content on iOS and Android
  8490. //and device bugs project issue #1 Form elements can lose click hit area in position: fixed containers.
  8491. //this also addresses not on fixed toolbars page in docs
  8492. //adding 1px of padding to the bottom then removing it causes a "redraw"
  8493. //which positions the toolbars correctly (they will always be visually correct)
  8494. _triggerRedraw: function() {
  8495. var paddingBottom = parseFloat( $( ".ui-page-active" ).css( "padding-bottom" ) );
  8496. //trigger page redraw to fix incorrectly positioned fixed elements
  8497. $( ".ui-page-active" ).css( "padding-bottom", ( paddingBottom + 1 ) +"px" );
  8498. //if the padding is reset with out a timeout the reposition will not occure.
  8499. //this is independant of JQM the browser seems to need the time to react.
  8500. setTimeout( function() {
  8501. $( ".ui-page-active" ).css( "padding-bottom", paddingBottom + "px" );
  8502. }, 0 );
  8503. },
  8504. destroy: function() {
  8505. this._super();
  8506. //Remove the class we added to the page previously in android 2.x
  8507. this.element.closest(".ui-page-active").removeClass( "ui-android-2x-fix" );
  8508. }
  8509. });
  8510. })( jQuery );
  8511. (function( $, undefined ) {
  8512. $.widget( "mobile.panel", $.mobile.widget, {
  8513. options: {
  8514. classes: {
  8515. panel: "ui-panel",
  8516. panelOpen: "ui-panel-open",
  8517. panelClosed: "ui-panel-closed",
  8518. panelFixed: "ui-panel-fixed",
  8519. panelInner: "ui-panel-inner",
  8520. modal: "ui-panel-dismiss",
  8521. modalOpen: "ui-panel-dismiss-open",
  8522. pagePanel: "ui-page-panel",
  8523. pagePanelOpen: "ui-page-panel-open",
  8524. contentWrap: "ui-panel-content-wrap",
  8525. contentWrapOpen: "ui-panel-content-wrap-open",
  8526. contentWrapClosed: "ui-panel-content-wrap-closed",
  8527. contentFixedToolbar: "ui-panel-content-fixed-toolbar",
  8528. contentFixedToolbarOpen: "ui-panel-content-fixed-toolbar-open",
  8529. contentFixedToolbarClosed: "ui-panel-content-fixed-toolbar-closed",
  8530. animate: "ui-panel-animate"
  8531. },
  8532. animate: true,
  8533. theme: "c",
  8534. position: "left",
  8535. dismissible: true,
  8536. display: "reveal", //accepts reveal, push, overlay
  8537. initSelector: ":jqmData(role='panel')",
  8538. swipeClose: true,
  8539. positionFixed: false
  8540. },
  8541. _panelID: null,
  8542. _closeLink: null,
  8543. _page: null,
  8544. _modal: null,
  8545. _panelInner: null,
  8546. _wrapper: null,
  8547. _fixedToolbar: null,
  8548. _create: function() {
  8549. var self = this,
  8550. $el = self.element,
  8551. page = $el.closest( ":jqmData(role='page')" ),
  8552. _getPageTheme = function() {
  8553. var $theme = $.data( page[0], "mobilePage" ).options.theme,
  8554. $pageThemeClass = "ui-body-" + $theme;
  8555. return $pageThemeClass;
  8556. },
  8557. _getPanelInner = function() {
  8558. var $panelInner = $el.find( "." + self.options.classes.panelInner );
  8559. if ( $panelInner.length === 0 ) {
  8560. $panelInner = $el.children().wrapAll( '<div class="' + self.options.classes.panelInner + '" />' ).parent();
  8561. }
  8562. return $panelInner;
  8563. },
  8564. _getWrapper = function() {
  8565. var $wrapper = page.find( "." + self.options.classes.contentWrap );
  8566. if ( $wrapper.length === 0 ) {
  8567. $wrapper = page.children( ".ui-header:not(:jqmData(position='fixed')), .ui-content:not(:jqmData(role='popup')), .ui-footer:not(:jqmData(position='fixed'))" ).wrapAll( '<div class="' + self.options.classes.contentWrap + ' ' + _getPageTheme() + '" />' ).parent();
  8568. if ( $.support.cssTransform3d && !!self.options.animate ) {
  8569. $wrapper.addClass( self.options.classes.animate );
  8570. }
  8571. }
  8572. return $wrapper;
  8573. },
  8574. _getFixedToolbar = function() {
  8575. var $fixedToolbar = page.find( "." + self.options.classes.contentFixedToolbar );
  8576. if ( $fixedToolbar.length === 0 ) {
  8577. $fixedToolbar = page.find( ".ui-header:jqmData(position='fixed'), .ui-footer:jqmData(position='fixed')" ).addClass( self.options.classes.contentFixedToolbar );
  8578. if ( $.support.cssTransform3d && !!self.options.animate ) {
  8579. $fixedToolbar.addClass( self.options.classes.animate );
  8580. }
  8581. }
  8582. return $fixedToolbar;
  8583. };
  8584. // expose some private props to other methods
  8585. $.extend( this, {
  8586. _panelID: $el.attr( "id" ),
  8587. _closeLink: $el.find( ":jqmData(rel='close')" ),
  8588. _page: $el.closest( ":jqmData(role='page')" ),
  8589. _pageTheme: _getPageTheme(),
  8590. _panelInner: _getPanelInner(),
  8591. _wrapper: _getWrapper(),
  8592. _fixedToolbar: _getFixedToolbar()
  8593. });
  8594. self._addPanelClasses();
  8595. self._wrapper.addClass( this.options.classes.contentWrapClosed );
  8596. self._fixedToolbar.addClass( this.options.classes.contentFixedToolbarClosed );
  8597. // add class to page so we can set "overflow-x: hidden;" for it to fix Android zoom issue
  8598. self._page.addClass( self.options.classes.pagePanel );
  8599. // if animating, add the class to do so
  8600. if ( $.support.cssTransform3d && !!self.options.animate ) {
  8601. this.element.addClass( self.options.classes.animate );
  8602. }
  8603. self._bindUpdateLayout();
  8604. self._bindCloseEvents();
  8605. self._bindLinkListeners();
  8606. self._bindPageEvents();
  8607. if ( !!self.options.dismissible ) {
  8608. self._createModal();
  8609. }
  8610. self._bindSwipeEvents();
  8611. },
  8612. _createModal: function( options ) {
  8613. var self = this;
  8614. self._modal = $( "<div class='" + self.options.classes.modal + "' data-panelid='" + self._panelID + "'></div>" )
  8615. .on( "mousedown", function() {
  8616. self.close();
  8617. })
  8618. .appendTo( this._page );
  8619. },
  8620. _getPosDisplayClasses: function( prefix ) {
  8621. return prefix + "-position-" + this.options.position + " " + prefix + "-display-" + this.options.display;
  8622. },
  8623. _getPanelClasses: function() {
  8624. var panelClasses = this.options.classes.panel +
  8625. " " + this._getPosDisplayClasses( this.options.classes.panel ) +
  8626. " " + this.options.classes.panelClosed;
  8627. if ( this.options.theme ) {
  8628. panelClasses += " ui-body-" + this.options.theme;
  8629. }
  8630. if ( !!this.options.positionFixed ) {
  8631. panelClasses += " " + this.options.classes.panelFixed;
  8632. }
  8633. return panelClasses;
  8634. },
  8635. _addPanelClasses: function() {
  8636. this.element.addClass( this._getPanelClasses() );
  8637. },
  8638. _bindCloseEvents: function() {
  8639. var self = this;
  8640. self._closeLink.on( "click.panel" , function( e ) {
  8641. e.preventDefault();
  8642. self.close();
  8643. return false;
  8644. });
  8645. self.element.on( "click.panel" , "a:jqmData(ajax='false')", function( e ) {
  8646. self.close();
  8647. });
  8648. },
  8649. _positionPanel: function() {
  8650. var self = this,
  8651. panelInnerHeight = self._panelInner.outerHeight(),
  8652. expand = panelInnerHeight > $.mobile.getScreenHeight();
  8653. if ( expand || !self.options.positionFixed ) {
  8654. if ( expand ) {
  8655. self._unfixPanel();
  8656. $.mobile.resetActivePageHeight( panelInnerHeight );
  8657. }
  8658. self._scrollIntoView( panelInnerHeight );
  8659. } else {
  8660. self._fixPanel();
  8661. }
  8662. },
  8663. _scrollIntoView: function( panelInnerHeight ) {
  8664. if ( panelInnerHeight < $( window ).scrollTop() ) {
  8665. window.scrollTo( 0, 0 );
  8666. }
  8667. },
  8668. _bindFixListener: function() {
  8669. this._on( $( window ), { "throttledresize": "_positionPanel" });
  8670. },
  8671. _unbindFixListener: function() {
  8672. this._off( $( window ), "throttledresize" );
  8673. },
  8674. _unfixPanel: function() {
  8675. if ( !!this.options.positionFixed && $.support.fixedPosition ) {
  8676. this.element.removeClass( this.options.classes.panelFixed );
  8677. }
  8678. },
  8679. _fixPanel: function() {
  8680. if ( !!this.options.positionFixed && $.support.fixedPosition ) {
  8681. this.element.addClass( this.options.classes.panelFixed );
  8682. }
  8683. },
  8684. _bindUpdateLayout: function() {
  8685. var self = this;
  8686. self.element.on( "updatelayout", function( e ) {
  8687. if ( self._open ) {
  8688. self._positionPanel();
  8689. }
  8690. });
  8691. },
  8692. _bindLinkListeners: function() {
  8693. var self = this;
  8694. self._page.on( "click.panel" , "a", function( e ) {
  8695. if ( this.href.split( "#" )[ 1 ] === self._panelID && self._panelID !== undefined ) {
  8696. e.preventDefault();
  8697. var $link = $( this ),
  8698. $parent;
  8699. if ( ! $link.hasClass( "ui-link" ) ) {
  8700. // Check if we are in a listview
  8701. $parent = $link.parent().parent();
  8702. if ( $parent.hasClass( "ui-li" ) ) {
  8703. $link = $parent.parent();
  8704. }
  8705. $link.addClass( $.mobile.activeBtnClass );
  8706. self.element.one( "panelopen panelclose", function() {
  8707. $link.removeClass( $.mobile.activeBtnClass );
  8708. });
  8709. }
  8710. self.toggle();
  8711. return false;
  8712. }
  8713. });
  8714. },
  8715. _bindSwipeEvents: function() {
  8716. var self = this,
  8717. area = self._modal ? self.element.add( self._modal ) : self.element;
  8718. // on swipe, close the panel
  8719. if( !!self.options.swipeClose ) {
  8720. if ( self.options.position === "left" ) {
  8721. area.on( "swipeleft.panel", function( e ) {
  8722. self.close();
  8723. });
  8724. } else {
  8725. area.on( "swiperight.panel", function( e ) {
  8726. self.close();
  8727. });
  8728. }
  8729. }
  8730. },
  8731. _bindPageEvents: function() {
  8732. var self = this;
  8733. self._page
  8734. // Close the panel if another panel on the page opens
  8735. .on( "panelbeforeopen", function( e ) {
  8736. if ( self._open && e.target !== self.element[ 0 ] ) {
  8737. self.close();
  8738. }
  8739. })
  8740. // clean up open panels after page hide
  8741. .on( "pagehide", function( e ) {
  8742. if ( self._open ) {
  8743. self.close( true );
  8744. }
  8745. })
  8746. // on escape, close? might need to have a target check too...
  8747. .on( "keyup.panel", function( e ) {
  8748. if ( e.keyCode === 27 && self._open ) {
  8749. self.close();
  8750. }
  8751. });
  8752. },
  8753. // state storage of open or closed
  8754. _open: false,
  8755. _contentWrapOpenClasses: null,
  8756. _fixedToolbarOpenClasses: null,
  8757. _modalOpenClasses: null,
  8758. open: function( immediate ) {
  8759. if ( !this._open ) {
  8760. var self = this,
  8761. o = self.options,
  8762. _openPanel = function() {
  8763. self._page.off( "panelclose" );
  8764. self._page.jqmData( "panel", "open" );
  8765. if ( !immediate && $.support.cssTransform3d && !!o.animate ) {
  8766. self.element.add( self._wrapper ).on( self._transitionEndEvents, complete );
  8767. } else {
  8768. setTimeout( complete, 0 );
  8769. }
  8770. if ( self.options.theme && self.options.display !== "overlay" ) {
  8771. self._page
  8772. .removeClass( self._pageTheme )
  8773. .addClass( "ui-body-" + self.options.theme );
  8774. }
  8775. self.element.removeClass( o.classes.panelClosed ).addClass( o.classes.panelOpen );
  8776. self._positionPanel();
  8777. // Fix for IE7 min-height bug
  8778. if ( self.options.theme && self.options.display !== "overlay" ) {
  8779. self._wrapper.css( "min-height", self._page.css( "min-height" ) );
  8780. }
  8781. self._contentWrapOpenClasses = self._getPosDisplayClasses( o.classes.contentWrap );
  8782. self._wrapper
  8783. .removeClass( o.classes.contentWrapClosed )
  8784. .addClass( self._contentWrapOpenClasses + " " + o.classes.contentWrapOpen );
  8785. self._fixedToolbarOpenClasses = self._getPosDisplayClasses( o.classes.contentFixedToolbar );
  8786. self._fixedToolbar
  8787. .removeClass( o.classes.contentFixedToolbarClosed )
  8788. .addClass( self._fixedToolbarOpenClasses + " " + o.classes.contentFixedToolbarOpen );
  8789. self._modalOpenClasses = self._getPosDisplayClasses( o.classes.modal ) + " " + o.classes.modalOpen;
  8790. if ( self._modal ) {
  8791. self._modal.addClass( self._modalOpenClasses );
  8792. }
  8793. },
  8794. complete = function() {
  8795. self.element.add( self._wrapper ).off( self._transitionEndEvents, complete );
  8796. self._page.addClass( o.classes.pagePanelOpen );
  8797. self._bindFixListener();
  8798. self._trigger( "open" );
  8799. };
  8800. if ( this.element.closest( ".ui-page-active" ).length < 0 ) {
  8801. immediate = true;
  8802. }
  8803. self._trigger( "beforeopen" );
  8804. if ( self._page.jqmData('panel') === "open" ) {
  8805. self._page.on( "panelclose", function() {
  8806. _openPanel();
  8807. });
  8808. } else {
  8809. _openPanel();
  8810. }
  8811. self._open = true;
  8812. }
  8813. },
  8814. close: function( immediate ) {
  8815. if ( this._open ) {
  8816. var o = this.options,
  8817. self = this,
  8818. _closePanel = function() {
  8819. if ( !immediate && $.support.cssTransform3d && !!o.animate ) {
  8820. self.element.add( self._wrapper ).on( self._transitionEndEvents, complete );
  8821. } else {
  8822. setTimeout( complete, 0 );
  8823. }
  8824. self._page.removeClass( o.classes.pagePanelOpen );
  8825. self.element.removeClass( o.classes.panelOpen );
  8826. self._wrapper.removeClass( o.classes.contentWrapOpen );
  8827. self._fixedToolbar.removeClass( o.classes.contentFixedToolbarOpen );
  8828. if ( self._modal ) {
  8829. self._modal.removeClass( self._modalOpenClasses );
  8830. }
  8831. },
  8832. complete = function() {
  8833. if ( self.options.theme && self.options.display !== "overlay" ) {
  8834. self._page.removeClass( "ui-body-" + self.options.theme ).addClass( self._pageTheme );
  8835. // reset fix for IE7 min-height bug
  8836. self._wrapper.css( "min-height", "" );
  8837. }
  8838. self.element.add( self._wrapper ).off( self._transitionEndEvents, complete );
  8839. self.element.addClass( o.classes.panelClosed );
  8840. self._wrapper
  8841. .removeClass( self._contentWrapOpenClasses )
  8842. .addClass( o.classes.contentWrapClosed );
  8843. self._fixedToolbar
  8844. .removeClass( self._fixedToolbarOpenClasses )
  8845. .addClass( o.classes.contentFixedToolbarClosed );
  8846. self._fixPanel();
  8847. self._unbindFixListener();
  8848. $.mobile.resetActivePageHeight();
  8849. self._page.jqmRemoveData( "panel" );
  8850. self._trigger( "close" );
  8851. };
  8852. if ( this.element.closest( ".ui-page-active" ).length < 0 ) {
  8853. immediate = true;
  8854. }
  8855. self._trigger( "beforeclose" );
  8856. _closePanel();
  8857. self._open = false;
  8858. }
  8859. },
  8860. toggle: function( options ) {
  8861. this[ this._open ? "close" : "open" ]();
  8862. },
  8863. _transitionEndEvents: "webkitTransitionEnd oTransitionEnd otransitionend transitionend msTransitionEnd",
  8864. _destroy: function() {
  8865. var classes = this.options.classes,
  8866. theme = this.options.theme,
  8867. hasOtherSiblingPanels = this.element.siblings( "." + classes.panel ).length;
  8868. // create
  8869. if ( !hasOtherSiblingPanels ) {
  8870. this._wrapper.children().unwrap();
  8871. this._page.find( "a" ).unbind( "panelopen panelclose" );
  8872. this._page.removeClass( classes.pagePanel );
  8873. if ( this._open ) {
  8874. this._page.jqmRemoveData( "panel" );
  8875. this._page.removeClass( classes.pagePanelOpen );
  8876. if ( theme ) {
  8877. this._page.removeClass( "ui-body-" + theme ).addClass( this._pageTheme );
  8878. }
  8879. $.mobile.resetActivePageHeight();
  8880. }
  8881. } else if ( this._open ) {
  8882. this._wrapper.removeClass( classes.contentWrapOpen );
  8883. this._fixedToolbar.removeClass( classes.contentFixedToolbarOpen );
  8884. this._page.jqmRemoveData( "panel" );
  8885. this._page.removeClass( classes.pagePanelOpen );
  8886. if ( theme ) {
  8887. this._page.removeClass( "ui-body-" + theme ).addClass( this._pageTheme );
  8888. }
  8889. }
  8890. this._panelInner.children().unwrap();
  8891. this.element.removeClass( [ this._getPanelClasses(), classes.panelAnimate ].join( " " ) )
  8892. .off( "swipeleft.panel swiperight.panel" )
  8893. .off( "panelbeforeopen" )
  8894. .off( "panelhide" )
  8895. .off( "keyup.panel" )
  8896. .off( "updatelayout" );
  8897. this._closeLink.off( "click.panel" );
  8898. if ( this._modal ) {
  8899. this._modal.remove();
  8900. }
  8901. // open and close
  8902. this.element.off( this._transitionEndEvents )
  8903. .removeClass( [ classes.panelUnfixed, classes.panelClosed, classes.panelOpen ].join( " " ) );
  8904. }
  8905. });
  8906. //auto self-init widgets
  8907. $( document ).bind( "pagecreate create", function( e ) {
  8908. $.mobile.panel.prototype.enhanceWithin( e.target );
  8909. });
  8910. })( jQuery );
  8911. (function( $, undefined ) {
  8912. $.widget( "mobile.table", $.mobile.widget, {
  8913. options: {
  8914. classes: {
  8915. table: "ui-table"
  8916. },
  8917. initSelector: ":jqmData(role='table')"
  8918. },
  8919. _create: function() {
  8920. var self = this;
  8921. self.refresh( true );
  8922. },
  8923. refresh: function (create) {
  8924. var self = this,
  8925. trs = this.element.find( "thead tr" );
  8926. if ( create ) {
  8927. this.element.addClass( this.options.classes.table );
  8928. }
  8929. // Expose headers and allHeaders properties on the widget
  8930. // headers references the THs within the first TR in the table
  8931. self.headers = this.element.find( "tr:eq(0)" ).children();
  8932. // allHeaders references headers, plus all THs in the thead, which may include several rows, or not
  8933. self.allHeaders = self.headers.add( trs.children() );
  8934. trs.each(function(){
  8935. var coltally = 0;
  8936. $( this ).children().each(function( i ){
  8937. var span = parseInt( $( this ).attr( "colspan" ), 10 ),
  8938. sel = ":nth-child(" + ( coltally + 1 ) + ")";
  8939. $( this )
  8940. .jqmData( "colstart", coltally + 1 );
  8941. if( span ){
  8942. for( var j = 0; j < span - 1; j++ ){
  8943. coltally++;
  8944. sel += ", :nth-child(" + ( coltally + 1 ) + ")";
  8945. }
  8946. }
  8947. if ( create === undefined ) {
  8948. $(this).jqmData("cells", "");
  8949. }
  8950. // Store "cells" data on header as a reference to all cells in the same column as this TH
  8951. $( this )
  8952. .jqmData( "cells", self.element.find( "tr" ).not( trs.eq(0) ).not( this ).children( sel ) );
  8953. coltally++;
  8954. });
  8955. });
  8956. // update table modes
  8957. if ( create === undefined ) {
  8958. this.element.trigger( 'refresh' );
  8959. }
  8960. }
  8961. });
  8962. //auto self-init widgets
  8963. $.mobile.document.bind( "pagecreate create", function( e ) {
  8964. $.mobile.table.prototype.enhanceWithin( e.target );
  8965. });
  8966. })( jQuery );
  8967. (function( $, undefined ) {
  8968. $.mobile.table.prototype.options.mode = "columntoggle";
  8969. $.mobile.table.prototype.options.columnBtnTheme = null;
  8970. $.mobile.table.prototype.options.columnPopupTheme = null;
  8971. $.mobile.table.prototype.options.columnBtnText = "Columns...";
  8972. $.mobile.table.prototype.options.classes = $.extend(
  8973. $.mobile.table.prototype.options.classes,
  8974. {
  8975. popup: "ui-table-columntoggle-popup",
  8976. columnBtn: "ui-table-columntoggle-btn",
  8977. priorityPrefix: "ui-table-priority-",
  8978. columnToggleTable: "ui-table-columntoggle"
  8979. }
  8980. );
  8981. $.mobile.document.delegate( ":jqmData(role='table')", "tablecreate refresh", function( e ) {
  8982. var $table = $( this ),
  8983. self = $table.data( "mobile-table" ),
  8984. event = e.type,
  8985. o = self.options,
  8986. ns = $.mobile.ns,
  8987. id = ( $table.attr( "id" ) || o.classes.popup ) + "-popup", /* TODO BETTER FALLBACK ID HERE */
  8988. $menuButton,
  8989. $popup,
  8990. $menu,
  8991. $switchboard;
  8992. if ( o.mode !== "columntoggle" ) {
  8993. return;
  8994. }
  8995. if ( event !== "refresh" ) {
  8996. self.element.addClass( o.classes.columnToggleTable );
  8997. $menuButton = $( "<a href='#" + id + "' class='" + o.classes.columnBtn + "' data-" + ns + "rel='popup' data-" + ns + "mini='true'>" + o.columnBtnText + "</a>" ),
  8998. $popup = $( "<div data-" + ns + "role='popup' data-" + ns + "role='fieldcontain' class='" + o.classes.popup + "' id='" + id + "'></div>"),
  8999. $menu = $("<fieldset data-" + ns + "role='controlgroup'></fieldset>");
  9000. }
  9001. // create the hide/show toggles
  9002. self.headers.not( "td" ).each(function( i ) {
  9003. var priority = $( this ).jqmData( "priority" ),
  9004. $cells = $( this ).add( $( this ).jqmData( "cells" ) );
  9005. if ( priority ) {
  9006. $cells.addClass( o.classes.priorityPrefix + priority );
  9007. if ( event !== "refresh" ) {
  9008. $("<label><input type='checkbox' checked />" + $( this ).text() + "</label>" )
  9009. .appendTo( $menu )
  9010. .children( 0 )
  9011. .jqmData( "cells", $cells )
  9012. .checkboxradio({
  9013. theme: o.columnPopupTheme
  9014. });
  9015. } else {
  9016. $( '#' + id + ' fieldset div:eq(' + i +')').find('input').jqmData( 'cells', $cells );
  9017. }
  9018. }
  9019. });
  9020. if ( event !== "refresh" ) {
  9021. $menu.appendTo( $popup );
  9022. }
  9023. // bind change event listeners to inputs - TODO: move to a private method?
  9024. if ( $menu === undefined ) {
  9025. $switchboard = $('#' + id + ' fieldset');
  9026. } else {
  9027. $switchboard = $menu;
  9028. }
  9029. if ( event !== "refresh" ) {
  9030. $switchboard.on( "change", "input", function( e ){
  9031. if( this.checked ){
  9032. $( this ).jqmData( "cells" ).removeClass( "ui-table-cell-hidden" ).addClass( "ui-table-cell-visible" );
  9033. } else {
  9034. $( this ).jqmData( "cells" ).removeClass( "ui-table-cell-visible" ).addClass( "ui-table-cell-hidden" );
  9035. }
  9036. });
  9037. $menuButton
  9038. .insertBefore( $table )
  9039. .buttonMarkup({
  9040. theme: o.columnBtnTheme
  9041. });
  9042. $popup
  9043. .insertBefore( $table )
  9044. .popup();
  9045. }
  9046. // refresh method
  9047. self.update = function(){
  9048. $switchboard.find( "input" ).each( function(){
  9049. if (this.checked) {
  9050. this.checked = $( this ).jqmData( "cells" ).eq(0).css( "display" ) === "table-cell";
  9051. if (event === "refresh") {
  9052. $( this ).jqmData( "cells" ).addClass('ui-table-cell-visible');
  9053. }
  9054. } else {
  9055. $( this ).jqmData( "cells" ).addClass('ui-table-cell-hidden');
  9056. }
  9057. $( this ).checkboxradio( "refresh" );
  9058. });
  9059. };
  9060. $.mobile.window.on( "throttledresize", self.update );
  9061. self.update();
  9062. });
  9063. })( jQuery );
  9064. (function( $, undefined ) {
  9065. $.mobile.table.prototype.options.mode = "reflow";
  9066. $.mobile.table.prototype.options.classes = $.extend(
  9067. $.mobile.table.prototype.options.classes,
  9068. {
  9069. reflowTable: "ui-table-reflow",
  9070. cellLabels: "ui-table-cell-label"
  9071. }
  9072. );
  9073. $.mobile.document.delegate( ":jqmData(role='table')", "tablecreate refresh", function( e ) {
  9074. var $table = $( this ),
  9075. event = e.type,
  9076. self = $table.data( "mobile-table" ),
  9077. o = self.options;
  9078. // If it's not reflow mode, return here.
  9079. if( o.mode !== "reflow" ){
  9080. return;
  9081. }
  9082. if ( event !== "refresh" ) {
  9083. self.element.addClass( o.classes.reflowTable );
  9084. }
  9085. // get headers in reverse order so that top-level headers are appended last
  9086. var reverseHeaders = $( self.allHeaders.get().reverse() );
  9087. // create the hide/show toggles
  9088. reverseHeaders.each(function( i ){
  9089. var $cells = $( this ).jqmData( "cells" ),
  9090. colstart = $( this ).jqmData( "colstart" ),
  9091. hierarchyClass = $cells.not( this ).filter( "thead th" ).length && " ui-table-cell-label-top",
  9092. text = $(this).text();
  9093. if( text !== "" ){
  9094. if( hierarchyClass ){
  9095. var iteration = parseInt( $( this ).attr( "colspan" ), 10 ),
  9096. filter = "";
  9097. if( iteration ){
  9098. filter = "td:nth-child("+ iteration +"n + " + ( colstart ) +")";
  9099. }
  9100. $cells.filter( filter ).prepend( "<b class='" + o.classes.cellLabels + hierarchyClass + "'>" + text + "</b>" );
  9101. }
  9102. else {
  9103. $cells.prepend( "<b class='" + o.classes.cellLabels + "'>" + text + "</b>" );
  9104. }
  9105. }
  9106. });
  9107. });
  9108. })( jQuery );
  9109. (function( $, window ) {
  9110. $.mobile.iosorientationfixEnabled = true;
  9111. // This fix addresses an iOS bug, so return early if the UA claims it's something else.
  9112. var ua = navigator.userAgent;
  9113. if( !( /iPhone|iPad|iPod/.test( navigator.platform ) && /OS [1-5]_[0-9_]* like Mac OS X/i.test( ua ) && ua.indexOf( "AppleWebKit" ) > -1 ) ){
  9114. $.mobile.iosorientationfixEnabled = false;
  9115. return;
  9116. }
  9117. var zoom = $.mobile.zoom,
  9118. evt, x, y, z, aig;
  9119. function checkTilt( e ) {
  9120. evt = e.originalEvent;
  9121. aig = evt.accelerationIncludingGravity;
  9122. x = Math.abs( aig.x );
  9123. y = Math.abs( aig.y );
  9124. z = Math.abs( aig.z );
  9125. // If portrait orientation and in one of the danger zones
  9126. if ( !window.orientation && ( x > 7 || ( ( z > 6 && y < 8 || z < 8 && y > 6 ) && x > 5 ) ) ) {
  9127. if ( zoom.enabled ) {
  9128. zoom.disable();
  9129. }
  9130. } else if ( !zoom.enabled ) {
  9131. zoom.enable();
  9132. }
  9133. }
  9134. $.mobile.document.on( "mobileinit", function(){
  9135. if( $.mobile.iosorientationfixEnabled ){
  9136. $.mobile.window
  9137. .bind( "orientationchange.iosorientationfix", zoom.enable )
  9138. .bind( "devicemotion.iosorientationfix", checkTilt );
  9139. }
  9140. });
  9141. }( jQuery, this ));
  9142. (function( $, window, undefined ) {
  9143. var $html = $( "html" ),
  9144. $head = $( "head" ),
  9145. $window = $.mobile.window;
  9146. //remove initial build class (only present on first pageshow)
  9147. function hideRenderingClass() {
  9148. $html.removeClass( "ui-mobile-rendering" );
  9149. }
  9150. // trigger mobileinit event - useful hook for configuring $.mobile settings before they're used
  9151. $( window.document ).trigger( "mobileinit" );
  9152. // support conditions
  9153. // if device support condition(s) aren't met, leave things as they are -> a basic, usable experience,
  9154. // otherwise, proceed with the enhancements
  9155. if ( !$.mobile.gradeA() ) {
  9156. return;
  9157. }
  9158. // override ajaxEnabled on platforms that have known conflicts with hash history updates
  9159. // or generally work better browsing in regular http for full page refreshes (BB5, Opera Mini)
  9160. if ( $.mobile.ajaxBlacklist ) {
  9161. $.mobile.ajaxEnabled = false;
  9162. }
  9163. // Add mobile, initial load "rendering" classes to docEl
  9164. $html.addClass( "ui-mobile ui-mobile-rendering" );
  9165. // This is a fallback. If anything goes wrong (JS errors, etc), or events don't fire,
  9166. // this ensures the rendering class is removed after 5 seconds, so content is visible and accessible
  9167. setTimeout( hideRenderingClass, 5000 );
  9168. $.extend( $.mobile, {
  9169. // find and enhance the pages in the dom and transition to the first page.
  9170. initializePage: function() {
  9171. // find present pages
  9172. var path = $.mobile.path,
  9173. $pages = $( ":jqmData(role='page'), :jqmData(role='dialog')" ),
  9174. hash = path.stripHash( path.stripQueryParams(path.parseLocation().hash) ),
  9175. hashPage = document.getElementById( hash );
  9176. // if no pages are found, create one with body's inner html
  9177. if ( !$pages.length ) {
  9178. $pages = $( "body" ).wrapInner( "<div data-" + $.mobile.ns + "role='page'></div>" ).children( 0 );
  9179. }
  9180. // add dialogs, set data-url attrs
  9181. $pages.each(function() {
  9182. var $this = $( this );
  9183. // unless the data url is already set set it to the pathname
  9184. if ( !$this.jqmData( "url" ) ) {
  9185. $this.attr( "data-" + $.mobile.ns + "url", $this.attr( "id" ) || location.pathname + location.search );
  9186. }
  9187. });
  9188. // define first page in dom case one backs out to the directory root (not always the first page visited, but defined as fallback)
  9189. $.mobile.firstPage = $pages.first();
  9190. // define page container
  9191. $.mobile.pageContainer = $.mobile.firstPage.parent().addClass( "ui-mobile-viewport" );
  9192. // initialize navigation events now, after mobileinit has occurred and the page container
  9193. // has been created but before the rest of the library is alerted to that fact
  9194. $.mobile.navreadyDeferred.resolve();
  9195. // alert listeners that the pagecontainer has been determined for binding
  9196. // to events triggered on it
  9197. $window.trigger( "pagecontainercreate" );
  9198. // cue page loading message
  9199. $.mobile.showPageLoadingMsg();
  9200. //remove initial build class (only present on first pageshow)
  9201. hideRenderingClass();
  9202. // if hashchange listening is disabled, there's no hash deeplink,
  9203. // the hash is not valid (contains more than one # or does not start with #)
  9204. // or there is no page with that hash, change to the first page in the DOM
  9205. // Remember, however, that the hash can also be a path!
  9206. if ( ! ( $.mobile.hashListeningEnabled &&
  9207. $.mobile.path.isHashValid( location.hash ) &&
  9208. ( $( hashPage ).is( ':jqmData(role="page")' ) ||
  9209. $.mobile.path.isPath( hash ) ||
  9210. hash === $.mobile.dialogHashKey ) ) ) {
  9211. // Store the initial destination
  9212. if ( $.mobile.path.isHashValid( location.hash ) ) {
  9213. $.mobile.urlHistory.initialDst = hash.replace( "#", "" );
  9214. }
  9215. // make sure to set initial popstate state if it exists
  9216. // so that navigation back to the initial page works properly
  9217. if( $.event.special.navigate.isPushStateEnabled() ) {
  9218. $.mobile.navigate.navigator.squash( path.parseLocation().href );
  9219. }
  9220. $.mobile.changePage( $.mobile.firstPage, {
  9221. transition: "none",
  9222. reverse: true,
  9223. changeHash: false,
  9224. fromHashChange: true
  9225. });
  9226. } else {
  9227. // trigger hashchange or navigate to squash and record the correct
  9228. // history entry for an initial hash path
  9229. if( !$.event.special.navigate.isPushStateEnabled() ) {
  9230. $window.trigger( "hashchange", [true] );
  9231. } else {
  9232. // TODO figure out how to simplify this interaction with the initial history entry
  9233. // at the bottom js/navigate/navigate.js
  9234. $.mobile.navigate.history.stack = [];
  9235. $.mobile.navigate( $.mobile.path.isPath( location.hash ) ? location.hash : location.href );
  9236. }
  9237. }
  9238. }
  9239. });
  9240. // check which scrollTop value should be used by scrolling to 1 immediately at domready
  9241. // then check what the scroll top is. Android will report 0... others 1
  9242. // note that this initial scroll won't hide the address bar. It's just for the check.
  9243. $(function() {
  9244. window.scrollTo( 0, 1 );
  9245. // if defaultHomeScroll hasn't been set yet, see if scrollTop is 1
  9246. // it should be 1 in most browsers, but android treats 1 as 0 (for hiding addr bar)
  9247. // so if it's 1, use 0 from now on
  9248. $.mobile.defaultHomeScroll = ( !$.support.scrollTop || $.mobile.window.scrollTop() === 1 ) ? 0 : 1;
  9249. //dom-ready inits
  9250. if ( $.mobile.autoInitializePage ) {
  9251. $.mobile.initializePage();
  9252. }
  9253. // window load event
  9254. // hide iOS browser chrome on load
  9255. $window.load( $.mobile.silentScroll );
  9256. if ( !$.support.cssPointerEvents ) {
  9257. // IE and Opera don't support CSS pointer-events: none that we use to disable link-based buttons
  9258. // by adding the 'ui-disabled' class to them. Using a JavaScript workaround for those browser.
  9259. // https://github.com/jquery/jquery-mobile/issues/3558
  9260. $.mobile.document.delegate( ".ui-disabled", "vclick",
  9261. function( e ) {
  9262. e.preventDefault();
  9263. e.stopImmediatePropagation();
  9264. }
  9265. );
  9266. }
  9267. });
  9268. }( jQuery, this ));
  9269. }));