jquery.mobile.js 350 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191
  1. /*
  2. * jQuery Mobile 1.3.1
  3. * Git HEAD hash: 74b4bec049fd93e4fe40205e6157de16eb64eb46 <> Date: Mon Apr 8 2013 19:41:28 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.1",
  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.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.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. _create: function() {
  4353. var self = this,
  4354. $el = this.element,
  4355. cornerClass = !!this.options.corners ? " ui-corner-all" : "",
  4356. dialogWrap = $( "<div/>", {
  4357. "role" : "dialog",
  4358. "class" : "ui-dialog-contain ui-overlay-shadow" + cornerClass
  4359. });
  4360. $el.addClass( "ui-dialog ui-overlay-" + this.options.overlayTheme );
  4361. // Class the markup for dialog styling
  4362. // Set aria role
  4363. $el.wrapInner( dialogWrap );
  4364. /* bind events
  4365. - clicks and submits should use the closing transition that the dialog opened with
  4366. unless a data-transition is specified on the link/form
  4367. - if the click was on the close button, or the link has a data-rel="back" it'll go back in history naturally
  4368. */
  4369. $el.bind( "vclick submit", function( event ) {
  4370. var $target = $( event.target ).closest( event.type === "vclick" ? "a" : "form" ),
  4371. active;
  4372. if ( $target.length && !$target.jqmData( "transition" ) ) {
  4373. active = $.mobile.urlHistory.getActive() || {};
  4374. $target.attr( "data-" + $.mobile.ns + "transition", ( active.transition || $.mobile.defaultDialogTransition ) )
  4375. .attr( "data-" + $.mobile.ns + "direction", "reverse" );
  4376. }
  4377. });
  4378. this._on( $el, {
  4379. pagebeforeshow: "_handlePageBeforeShow"
  4380. });
  4381. $.extend( this, {
  4382. _createComplete: false
  4383. });
  4384. this._setCloseBtn( this.options.closeBtn );
  4385. },
  4386. _setCloseBtn: function( value ) {
  4387. var self = this, btn, location;
  4388. if ( this._headerCloseButton ) {
  4389. this._headerCloseButton.remove();
  4390. this._headerCloseButton = null;
  4391. }
  4392. if ( value !== "none" ) {
  4393. // Sanitize value
  4394. location = ( value === "left" ? "left" : "right" );
  4395. btn = $( "<a href='#' class='ui-btn-" + location + "' data-" + $.mobile.ns + "icon='delete' data-" + $.mobile.ns + "iconpos='notext'>"+ this.options.closeBtnText + "</a>" );
  4396. this.element.children().find( ":jqmData(role='header')" ).first().prepend( btn );
  4397. if ( this._createComplete && $.fn.buttonMarkup ) {
  4398. btn.buttonMarkup();
  4399. }
  4400. this._createComplete = true;
  4401. // this must be an anonymous function so that select menu dialogs can replace
  4402. // the close method. This is a change from previously just defining data-rel=back
  4403. // on the button and letting nav handle it
  4404. //
  4405. // Use click rather than vclick in order to prevent the possibility of unintentionally
  4406. // reopening the dialog if the dialog opening item was directly under the close button.
  4407. btn.bind( "click", function() {
  4408. self.close();
  4409. });
  4410. this._headerCloseButton = btn;
  4411. }
  4412. },
  4413. _setOption: function( key, value ) {
  4414. if ( key === "closeBtn" ) {
  4415. this._setCloseBtn( value );
  4416. }
  4417. this._super( key, value );
  4418. },
  4419. // Close method goes back in history
  4420. close: function() {
  4421. var idx, dst, hist = $.mobile.navigate.history;
  4422. if ( this._isCloseable ) {
  4423. this._isCloseable = false;
  4424. // If the hash listening is enabled and there is at least one preceding history
  4425. // entry it's ok to go back. Initial pages with the dialog hash state are an example
  4426. // where the stack check is necessary
  4427. if ( $.mobile.hashListeningEnabled && hist.activeIndex > 0 ) {
  4428. $.mobile.back();
  4429. } else {
  4430. idx = Math.max( 0, hist.activeIndex - 1 );
  4431. dst = hist.stack[ idx ].pageUrl || hist.stack[ idx ].url;
  4432. hist.previousIndex = hist.activeIndex;
  4433. hist.activeIndex = idx;
  4434. if ( !$.mobile.path.isPath( dst ) ) {
  4435. dst = $.mobile.path.makeUrlAbsolute( "#" + dst );
  4436. }
  4437. $.mobile.changePage( dst, { direction: "back", changeHash: false, fromHashChange: true } );
  4438. }
  4439. }
  4440. }
  4441. });
  4442. //auto self-init widgets
  4443. $.mobile.document.delegate( $.mobile.dialog.prototype.options.initSelector, "pagecreate", function() {
  4444. $.mobile.dialog.prototype.enhance( this );
  4445. });
  4446. })( jQuery, this );
  4447. (function( $, undefined ) {
  4448. $.mobile.page.prototype.options.backBtnText = "Back";
  4449. $.mobile.page.prototype.options.addBackBtn = false;
  4450. $.mobile.page.prototype.options.backBtnTheme = null;
  4451. $.mobile.page.prototype.options.headerTheme = "a";
  4452. $.mobile.page.prototype.options.footerTheme = "a";
  4453. $.mobile.page.prototype.options.contentTheme = null;
  4454. // NOTE bind used to force this binding to run before the buttonMarkup binding
  4455. // which expects .ui-footer top be applied in its gigantic selector
  4456. // TODO remove the buttonMarkup giant selector and move it to the various modules
  4457. // on which it depends
  4458. $.mobile.document.bind( "pagecreate", function( e ) {
  4459. var $page = $( e.target ),
  4460. o = $page.data( "mobile-page" ).options,
  4461. pageRole = $page.jqmData( "role" ),
  4462. pageTheme = o.theme;
  4463. $( ":jqmData(role='header'), :jqmData(role='footer'), :jqmData(role='content')", $page )
  4464. .jqmEnhanceable()
  4465. .each(function() {
  4466. var $this = $( this ),
  4467. role = $this.jqmData( "role" ),
  4468. theme = $this.jqmData( "theme" ),
  4469. contentTheme = theme || o.contentTheme || ( pageRole === "dialog" && pageTheme ),
  4470. $headeranchors,
  4471. leftbtn,
  4472. rightbtn,
  4473. backBtn;
  4474. $this.addClass( "ui-" + role );
  4475. //apply theming and markup modifications to page,header,content,footer
  4476. if ( role === "header" || role === "footer" ) {
  4477. var thisTheme = theme || ( role === "header" ? o.headerTheme : o.footerTheme ) || pageTheme;
  4478. $this
  4479. //add theme class
  4480. .addClass( "ui-bar-" + thisTheme )
  4481. // Add ARIA role
  4482. .attr( "role", role === "header" ? "banner" : "contentinfo" );
  4483. if ( role === "header") {
  4484. // Right,left buttons
  4485. $headeranchors = $this.children( "a, button" );
  4486. leftbtn = $headeranchors.hasClass( "ui-btn-left" );
  4487. rightbtn = $headeranchors.hasClass( "ui-btn-right" );
  4488. leftbtn = leftbtn || $headeranchors.eq( 0 ).not( ".ui-btn-right" ).addClass( "ui-btn-left" ).length;
  4489. rightbtn = rightbtn || $headeranchors.eq( 1 ).addClass( "ui-btn-right" ).length;
  4490. }
  4491. // Auto-add back btn on pages beyond first view
  4492. if ( o.addBackBtn &&
  4493. role === "header" &&
  4494. $( ".ui-page" ).length > 1 &&
  4495. $page.jqmData( "url" ) !== $.mobile.path.stripHash( location.hash ) &&
  4496. !leftbtn ) {
  4497. backBtn = $( "<a href='javascript:void(0);' class='ui-btn-left' data-"+ $.mobile.ns +"rel='back' data-"+ $.mobile.ns +"icon='arrow-l'>"+ o.backBtnText +"</a>" )
  4498. // If theme is provided, override default inheritance
  4499. .attr( "data-"+ $.mobile.ns +"theme", o.backBtnTheme || thisTheme )
  4500. .prependTo( $this );
  4501. }
  4502. // Page title
  4503. $this.children( "h1, h2, h3, h4, h5, h6" )
  4504. .addClass( "ui-title" )
  4505. // Regardless of h element number in src, it becomes h1 for the enhanced page
  4506. .attr({
  4507. "role": "heading",
  4508. "aria-level": "1"
  4509. });
  4510. } else if ( role === "content" ) {
  4511. if ( contentTheme ) {
  4512. $this.addClass( "ui-body-" + ( contentTheme ) );
  4513. }
  4514. // Add ARIA role
  4515. $this.attr( "role", "main" );
  4516. }
  4517. });
  4518. });
  4519. })( jQuery );
  4520. (function( $, undefined ) {
  4521. // This function calls getAttribute, which should be safe for data-* attributes
  4522. var getAttrFixed = function( e, key ) {
  4523. var value = e.getAttribute( key );
  4524. return value === "true" ? true :
  4525. value === "false" ? false :
  4526. value === null ? undefined : value;
  4527. };
  4528. $.fn.buttonMarkup = function( options ) {
  4529. var $workingSet = this,
  4530. nsKey = "data-" + $.mobile.ns,
  4531. key;
  4532. // Enforce options to be of type string
  4533. options = ( options && ( $.type( options ) === "object" ) )? options : {};
  4534. for ( var i = 0; i < $workingSet.length; i++ ) {
  4535. var el = $workingSet.eq( i ),
  4536. e = el[ 0 ],
  4537. o = $.extend( {}, $.fn.buttonMarkup.defaults, {
  4538. icon: options.icon !== undefined ? options.icon : getAttrFixed( e, nsKey + "icon" ),
  4539. iconpos: options.iconpos !== undefined ? options.iconpos : getAttrFixed( e, nsKey + "iconpos" ),
  4540. theme: options.theme !== undefined ? options.theme : getAttrFixed( e, nsKey + "theme" ) || $.mobile.getInheritedTheme( el, "c" ),
  4541. inline: options.inline !== undefined ? options.inline : getAttrFixed( e, nsKey + "inline" ),
  4542. shadow: options.shadow !== undefined ? options.shadow : getAttrFixed( e, nsKey + "shadow" ),
  4543. corners: options.corners !== undefined ? options.corners : getAttrFixed( e, nsKey + "corners" ),
  4544. iconshadow: options.iconshadow !== undefined ? options.iconshadow : getAttrFixed( e, nsKey + "iconshadow" ),
  4545. mini: options.mini !== undefined ? options.mini : getAttrFixed( e, nsKey + "mini" )
  4546. }, options ),
  4547. // Classes Defined
  4548. innerClass = "ui-btn-inner",
  4549. textClass = "ui-btn-text",
  4550. buttonClass, iconClass,
  4551. hover = false,
  4552. state = "up",
  4553. // Button inner markup
  4554. buttonInner,
  4555. buttonText,
  4556. buttonIcon,
  4557. buttonElements;
  4558. for ( key in o ) {
  4559. if ( o[ key ] === undefined || o[ key ] === null ) {
  4560. el.removeAttr( nsKey + key );
  4561. } else {
  4562. e.setAttribute( nsKey + key, o[ key ] );
  4563. }
  4564. }
  4565. if ( getAttrFixed( e, nsKey + "rel" ) === "popup" && el.attr( "href" ) ) {
  4566. e.setAttribute( "aria-haspopup", true );
  4567. e.setAttribute( "aria-owns", el.attr( "href" ) );
  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 ) ) {
  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. self = this;
  7029. // We need to adjust the history option to be false if there's no AJAX nav.
  7030. // We can't do it in the option declarations because those are run before
  7031. // it is determined whether there shall be AJAX nav.
  7032. this.options.history = this.options.history && $.mobile.ajaxEnabled && $.mobile.hashListeningEnabled;
  7033. if ( thisPage.length === 0 ) {
  7034. thisPage = $( "body" );
  7035. }
  7036. // define the container for navigation event bindings
  7037. // TODO this would be nice at the the mobile widget level
  7038. this.options.container = this.options.container || $.mobile.pageContainer;
  7039. // Apply the proto
  7040. thisPage.append( ui.screen );
  7041. ui.container.insertAfter( ui.screen );
  7042. // Leave a placeholder where the element used to be
  7043. ui.placeholder.insertAfter( this.element );
  7044. if ( myId ) {
  7045. ui.screen.attr( "id", myId + "-screen" );
  7046. ui.container.attr( "id", myId + "-popup" );
  7047. ui.placeholder.html( "<!-- placeholder for " + myId + " -->" );
  7048. }
  7049. ui.container.append( this.element );
  7050. ui.focusElement = ui.container;
  7051. // Add class to popup element
  7052. this.element.addClass( "ui-popup" );
  7053. // Define instance variables
  7054. $.extend( this, {
  7055. _scrollTop: 0,
  7056. _page: thisPage,
  7057. _ui: ui,
  7058. _fallbackTransition: "",
  7059. _currentTransition: false,
  7060. _prereqs: null,
  7061. _isOpen: false,
  7062. _tolerance: null,
  7063. _resizeData: null,
  7064. _ignoreResizeTo: 0,
  7065. _orientationchangeInProgress: false
  7066. });
  7067. $.each( this.options, function( key, value ) {
  7068. // Cause initial options to be applied by their handler by temporarily setting the option to undefined
  7069. // - the handler then sets it to the initial value
  7070. self.options[ key ] = undefined;
  7071. self._setOption( key, value, true );
  7072. });
  7073. ui.screen.bind( "vclick", $.proxy( this, "_eatEventAndClose" ) );
  7074. this._on( $.mobile.window, {
  7075. orientationchange: $.proxy( this, "_handleWindowOrientationchange" ),
  7076. resize: $.proxy( this, "_handleWindowResize" ),
  7077. keyup: $.proxy( this, "_handleWindowKeyUp" )
  7078. });
  7079. this._on( $.mobile.document, {
  7080. focusin: $.proxy( this, "_handleDocumentFocusIn" )
  7081. });
  7082. },
  7083. _applyTheme: function( dst, theme, prefix ) {
  7084. var classes = ( dst.attr( "class" ) || "").split( " " ),
  7085. alreadyAdded = true,
  7086. currentTheme = null,
  7087. matches,
  7088. themeStr = String( theme );
  7089. while ( classes.length > 0 ) {
  7090. currentTheme = classes.pop();
  7091. matches = ( new RegExp( "^ui-" + prefix + "-([a-z])$" ) ).exec( currentTheme );
  7092. if ( matches && matches.length > 1 ) {
  7093. currentTheme = matches[ 1 ];
  7094. break;
  7095. } else {
  7096. currentTheme = null;
  7097. }
  7098. }
  7099. if ( theme !== currentTheme ) {
  7100. dst.removeClass( "ui-" + prefix + "-" + currentTheme );
  7101. if ( ! ( theme === null || theme === "none" ) ) {
  7102. dst.addClass( "ui-" + prefix + "-" + themeStr );
  7103. }
  7104. }
  7105. },
  7106. _setTheme: function( value ) {
  7107. this._applyTheme( this.element, value, "body" );
  7108. },
  7109. _setOverlayTheme: function( value ) {
  7110. this._applyTheme( this._ui.screen, value, "overlay" );
  7111. if ( this._isOpen ) {
  7112. this._ui.screen.addClass( "in" );
  7113. }
  7114. },
  7115. _setShadow: function( value ) {
  7116. this.element.toggleClass( "ui-overlay-shadow", value );
  7117. },
  7118. _setCorners: function( value ) {
  7119. this.element.toggleClass( "ui-corner-all", value );
  7120. },
  7121. _applyTransition: function( value ) {
  7122. this._ui.container.removeClass( this._fallbackTransition );
  7123. if ( value && value !== "none" ) {
  7124. this._fallbackTransition = $.mobile._maybeDegradeTransition( value );
  7125. if ( this._fallbackTransition === "none" ) {
  7126. this._fallbackTransition = "";
  7127. }
  7128. this._ui.container.addClass( this._fallbackTransition );
  7129. }
  7130. },
  7131. _setTransition: function( value ) {
  7132. if ( !this._currentTransition ) {
  7133. this._applyTransition( value );
  7134. }
  7135. },
  7136. _setTolerance: function( value ) {
  7137. var tol = { t: 30, r: 15, b: 30, l: 15 };
  7138. if ( value !== undefined ) {
  7139. var ar = String( value ).split( "," );
  7140. $.each( ar, function( idx, val ) { ar[ idx ] = parseInt( val, 10 ); } );
  7141. switch( ar.length ) {
  7142. // All values are to be the same
  7143. case 1:
  7144. if ( !isNaN( ar[ 0 ] ) ) {
  7145. tol.t = tol.r = tol.b = tol.l = ar[ 0 ];
  7146. }
  7147. break;
  7148. // The first value denotes top/bottom tolerance, and the second value denotes left/right tolerance
  7149. case 2:
  7150. if ( !isNaN( ar[ 0 ] ) ) {
  7151. tol.t = tol.b = ar[ 0 ];
  7152. }
  7153. if ( !isNaN( ar[ 1 ] ) ) {
  7154. tol.l = tol.r = ar[ 1 ];
  7155. }
  7156. break;
  7157. // The array contains values in the order top, right, bottom, left
  7158. case 4:
  7159. if ( !isNaN( ar[ 0 ] ) ) {
  7160. tol.t = ar[ 0 ];
  7161. }
  7162. if ( !isNaN( ar[ 1 ] ) ) {
  7163. tol.r = ar[ 1 ];
  7164. }
  7165. if ( !isNaN( ar[ 2 ] ) ) {
  7166. tol.b = ar[ 2 ];
  7167. }
  7168. if ( !isNaN( ar[ 3 ] ) ) {
  7169. tol.l = ar[ 3 ];
  7170. }
  7171. break;
  7172. default:
  7173. break;
  7174. }
  7175. }
  7176. this._tolerance = tol;
  7177. },
  7178. _setOption: function( key, value ) {
  7179. var exclusions, setter = "_set" + key.charAt( 0 ).toUpperCase() + key.slice( 1 );
  7180. if ( this[ setter ] !== undefined ) {
  7181. this[ setter ]( value );
  7182. }
  7183. // TODO REMOVE FOR 1.2.1 by moving them out to a default options object
  7184. exclusions = [
  7185. "initSelector",
  7186. "closeLinkSelector",
  7187. "closeLinkEvents",
  7188. "navigateEvents",
  7189. "closeEvents",
  7190. "history",
  7191. "container"
  7192. ];
  7193. $.mobile.widget.prototype._setOption.apply( this, arguments );
  7194. if ( $.inArray( key, exclusions ) === -1 ) {
  7195. // Record the option change in the options and in the DOM data-* attributes
  7196. this.element.attr( "data-" + ( $.mobile.ns || "" ) + ( key.replace( /([A-Z])/, "-$1" ).toLowerCase() ), value );
  7197. }
  7198. },
  7199. // Try and center the overlay over the given coordinates
  7200. _placementCoords: function( desired ) {
  7201. // rectangle within which the popup must fit
  7202. var
  7203. winCoords = windowCoords(),
  7204. rc = {
  7205. x: this._tolerance.l,
  7206. y: winCoords.y + this._tolerance.t,
  7207. cx: winCoords.cx - this._tolerance.l - this._tolerance.r,
  7208. cy: winCoords.cy - this._tolerance.t - this._tolerance.b
  7209. },
  7210. menuSize, ret;
  7211. // Clamp the width of the menu before grabbing its size
  7212. this._ui.container.css( "max-width", rc.cx );
  7213. menuSize = {
  7214. cx: this._ui.container.outerWidth( true ),
  7215. cy: this._ui.container.outerHeight( true )
  7216. };
  7217. // Center the menu over the desired coordinates, while not going outside
  7218. // the window tolerances. This will center wrt. the window if the popup is too large.
  7219. ret = {
  7220. x: fitSegmentInsideSegment( rc.cx, menuSize.cx, rc.x, desired.x ),
  7221. y: fitSegmentInsideSegment( rc.cy, menuSize.cy, rc.y, desired.y )
  7222. };
  7223. // Make sure the top of the menu is visible
  7224. ret.y = Math.max( 0, ret.y );
  7225. // If the height of the menu is smaller than the height of the document
  7226. // align the bottom with the bottom of the document
  7227. // fix for $.mobile.document.height() bug in core 1.7.2.
  7228. var docEl = document.documentElement, docBody = document.body,
  7229. docHeight = Math.max( docEl.clientHeight, docBody.scrollHeight, docBody.offsetHeight, docEl.scrollHeight, docEl.offsetHeight );
  7230. ret.y -= Math.min( ret.y, Math.max( 0, ret.y + menuSize.cy - docHeight ) );
  7231. return { left: ret.x, top: ret.y };
  7232. },
  7233. _createPrereqs: function( screenPrereq, containerPrereq, whenDone ) {
  7234. var self = this, prereqs;
  7235. // It is important to maintain both the local variable prereqs and self._prereqs. The local variable remains in
  7236. // the closure of the functions which call the callbacks passed in. The comparison between the local variable and
  7237. // self._prereqs is necessary, because once a function has been passed to .animationComplete() it will be called
  7238. // next time an animation completes, even if that's not the animation whose end the function was supposed to catch
  7239. // (for example, if an abort happens during the opening animation, the .animationComplete handler is not called for
  7240. // that animation anymore, but the handler remains attached, so it is called the next time the popup is opened
  7241. // - making it stale. Comparing the local variable prereqs to the widget-level variable self._prereqs ensures that
  7242. // callbacks triggered by a stale .animationComplete will be ignored.
  7243. prereqs = {
  7244. screen: $.Deferred(),
  7245. container: $.Deferred()
  7246. };
  7247. prereqs.screen.then( function() {
  7248. if ( prereqs === self._prereqs ) {
  7249. screenPrereq();
  7250. }
  7251. });
  7252. prereqs.container.then( function() {
  7253. if ( prereqs === self._prereqs ) {
  7254. containerPrereq();
  7255. }
  7256. });
  7257. $.when( prereqs.screen, prereqs.container ).done( function() {
  7258. if ( prereqs === self._prereqs ) {
  7259. self._prereqs = null;
  7260. whenDone();
  7261. }
  7262. });
  7263. self._prereqs = prereqs;
  7264. },
  7265. _animate: function( args ) {
  7266. // NOTE before removing the default animation of the screen
  7267. // this had an animate callback that would resolve the deferred
  7268. // now the deferred is resolved immediately
  7269. // TODO remove the dependency on the screen deferred
  7270. this._ui.screen
  7271. .removeClass( args.classToRemove )
  7272. .addClass( args.screenClassToAdd );
  7273. args.prereqs.screen.resolve();
  7274. if ( args.transition && args.transition !== "none" ) {
  7275. if ( args.applyTransition ) {
  7276. this._applyTransition( args.transition );
  7277. }
  7278. if ( this._fallbackTransition ) {
  7279. this._ui.container
  7280. .animationComplete( $.proxy( args.prereqs.container, "resolve" ) )
  7281. .addClass( args.containerClassToAdd )
  7282. .removeClass( args.classToRemove );
  7283. return;
  7284. }
  7285. }
  7286. this._ui.container.removeClass( args.classToRemove );
  7287. args.prereqs.container.resolve();
  7288. },
  7289. // The desired coordinates passed in will be returned untouched if no reference element can be identified via
  7290. // desiredPosition.positionTo. Nevertheless, this function ensures that its return value always contains valid
  7291. // x and y coordinates by specifying the center middle of the window if the coordinates are absent.
  7292. // options: { x: coordinate, y: coordinate, positionTo: string: "origin", "window", or jQuery selector
  7293. _desiredCoords: function( o ) {
  7294. var dst = null, offset, winCoords = windowCoords(), x = o.x, y = o.y, pTo = o.positionTo;
  7295. // Establish which element will serve as the reference
  7296. if ( pTo && pTo !== "origin" ) {
  7297. if ( pTo === "window" ) {
  7298. x = winCoords.cx / 2 + winCoords.x;
  7299. y = winCoords.cy / 2 + winCoords.y;
  7300. } else {
  7301. try {
  7302. dst = $( pTo );
  7303. } catch( e ) {
  7304. dst = null;
  7305. }
  7306. if ( dst ) {
  7307. dst.filter( ":visible" );
  7308. if ( dst.length === 0 ) {
  7309. dst = null;
  7310. }
  7311. }
  7312. }
  7313. }
  7314. // If an element was found, center over it
  7315. if ( dst ) {
  7316. offset = dst.offset();
  7317. x = offset.left + dst.outerWidth() / 2;
  7318. y = offset.top + dst.outerHeight() / 2;
  7319. }
  7320. // Make sure x and y are valid numbers - center over the window
  7321. if ( $.type( x ) !== "number" || isNaN( x ) ) {
  7322. x = winCoords.cx / 2 + winCoords.x;
  7323. }
  7324. if ( $.type( y ) !== "number" || isNaN( y ) ) {
  7325. y = winCoords.cy / 2 + winCoords.y;
  7326. }
  7327. return { x: x, y: y };
  7328. },
  7329. _reposition: function( o ) {
  7330. // We only care about position-related parameters for repositioning
  7331. o = { x: o.x, y: o.y, positionTo: o.positionTo };
  7332. this._trigger( "beforeposition", o );
  7333. this._ui.container.offset( this._placementCoords( this._desiredCoords( o ) ) );
  7334. },
  7335. reposition: function( o ) {
  7336. if ( this._isOpen ) {
  7337. this._reposition( o );
  7338. }
  7339. },
  7340. _openPrereqsComplete: function() {
  7341. this._ui.container.addClass( "ui-popup-active" );
  7342. this._isOpen = true;
  7343. this._resizeScreen();
  7344. this._ui.container.attr( "tabindex", "0" ).focus();
  7345. this._ignoreResizeEvents();
  7346. this._trigger( "afteropen" );
  7347. },
  7348. _open: function( options ) {
  7349. var o = $.extend( {}, this.options, options ),
  7350. // TODO move blacklist to private method
  7351. androidBlacklist = ( function() {
  7352. var w = window,
  7353. ua = navigator.userAgent,
  7354. // Rendering engine is Webkit, and capture major version
  7355. wkmatch = ua.match( /AppleWebKit\/([0-9\.]+)/ ),
  7356. wkversion = !!wkmatch && wkmatch[ 1 ],
  7357. androidmatch = ua.match( /Android (\d+(?:\.\d+))/ ),
  7358. andversion = !!androidmatch && androidmatch[ 1 ],
  7359. chromematch = ua.indexOf( "Chrome" ) > -1;
  7360. // Platform is Android, WebKit version is greater than 534.13 ( Android 3.2.1 ) and not Chrome.
  7361. if( androidmatch !== null && andversion === "4.0" && wkversion && wkversion > 534.13 && !chromematch ) {
  7362. return true;
  7363. }
  7364. return false;
  7365. }());
  7366. // Count down to triggering "popupafteropen" - we have two prerequisites:
  7367. // 1. The popup window animation completes (container())
  7368. // 2. The screen opacity animation completes (screen())
  7369. this._createPrereqs(
  7370. $.noop,
  7371. $.noop,
  7372. $.proxy( this, "_openPrereqsComplete" ) );
  7373. this._currentTransition = o.transition;
  7374. this._applyTransition( o.transition );
  7375. if ( !this.options.theme ) {
  7376. this._setTheme( this._page.jqmData( "theme" ) || $.mobile.getInheritedTheme( this._page, "c" ) );
  7377. }
  7378. this._ui.screen.removeClass( "ui-screen-hidden" );
  7379. this._ui.container.removeClass( "ui-popup-hidden" );
  7380. // Give applications a chance to modify the contents of the container before it appears
  7381. this._reposition( o );
  7382. if ( this.options.overlayTheme && androidBlacklist ) {
  7383. /* TODO:
  7384. The native browser on Android 4.0.X ("Ice Cream Sandwich") suffers from an issue where the popup overlay appears to be z-indexed
  7385. above the popup itself when certain other styles exist on the same page -- namely, any element set to `position: fixed` and certain
  7386. types of input. These issues are reminiscent of previously uncovered bugs in older versions of Android's native browser:
  7387. https://github.com/scottjehl/Device-Bugs/issues/3
  7388. This fix closes the following bugs ( I use "closes" with reluctance, and stress that this issue should be revisited as soon as possible ):
  7389. https://github.com/jquery/jquery-mobile/issues/4816
  7390. https://github.com/jquery/jquery-mobile/issues/4844
  7391. https://github.com/jquery/jquery-mobile/issues/4874
  7392. */
  7393. // TODO sort out why this._page isn't working
  7394. this.element.closest( ".ui-page" ).addClass( "ui-popup-open" );
  7395. }
  7396. this._animate({
  7397. additionalCondition: true,
  7398. transition: o.transition,
  7399. classToRemove: "",
  7400. screenClassToAdd: "in",
  7401. containerClassToAdd: "in",
  7402. applyTransition: false,
  7403. prereqs: this._prereqs
  7404. });
  7405. },
  7406. _closePrereqScreen: function() {
  7407. this._ui.screen
  7408. .removeClass( "out" )
  7409. .addClass( "ui-screen-hidden" );
  7410. },
  7411. _closePrereqContainer: function() {
  7412. this._ui.container
  7413. .removeClass( "reverse out" )
  7414. .addClass( "ui-popup-hidden" )
  7415. .removeAttr( "style" );
  7416. },
  7417. _closePrereqsDone: function() {
  7418. var opts = this.options;
  7419. this._ui.container.removeAttr( "tabindex" );
  7420. // remove the global mutex for popups
  7421. $.mobile.popup.active = undefined;
  7422. // alert users that the popup is closed
  7423. this._trigger( "afterclose" );
  7424. },
  7425. _close: function( immediate ) {
  7426. this._ui.container.removeClass( "ui-popup-active" );
  7427. this._page.removeClass( "ui-popup-open" );
  7428. this._isOpen = false;
  7429. // Count down to triggering "popupafterclose" - we have two prerequisites:
  7430. // 1. The popup window reverse animation completes (container())
  7431. // 2. The screen opacity animation completes (screen())
  7432. this._createPrereqs(
  7433. $.proxy( this, "_closePrereqScreen" ),
  7434. $.proxy( this, "_closePrereqContainer" ),
  7435. $.proxy( this, "_closePrereqsDone" ) );
  7436. this._animate( {
  7437. additionalCondition: this._ui.screen.hasClass( "in" ),
  7438. transition: ( immediate ? "none" : ( this._currentTransition ) ),
  7439. classToRemove: "in",
  7440. screenClassToAdd: "out",
  7441. containerClassToAdd: "reverse out",
  7442. applyTransition: true,
  7443. prereqs: this._prereqs
  7444. });
  7445. },
  7446. _unenhance: function() {
  7447. // Put the element back to where the placeholder was and remove the "ui-popup" class
  7448. this._setTheme( "none" );
  7449. this.element
  7450. // Cannot directly insertAfter() - we need to detach() first, because
  7451. // insertAfter() will do nothing if the payload div was not attached
  7452. // to the DOM at the time the widget was created, and so the payload
  7453. // will remain inside the container even after we call insertAfter().
  7454. // If that happens and we remove the container a few lines below, we
  7455. // will cause an infinite recursion - #5244
  7456. .detach()
  7457. .insertAfter( this._ui.placeholder )
  7458. .removeClass( "ui-popup ui-overlay-shadow ui-corner-all" );
  7459. this._ui.screen.remove();
  7460. this._ui.container.remove();
  7461. this._ui.placeholder.remove();
  7462. },
  7463. _destroy: function() {
  7464. if ( $.mobile.popup.active === this ) {
  7465. this.element.one( "popupafterclose", $.proxy( this, "_unenhance" ) );
  7466. this.close();
  7467. } else {
  7468. this._unenhance();
  7469. }
  7470. },
  7471. _closePopup: function( e, data ) {
  7472. var parsedDst, toUrl, o = this.options, immediate = false;
  7473. // restore location on screen
  7474. window.scrollTo( 0, this._scrollTop );
  7475. if ( e && e.type === "pagebeforechange" && data ) {
  7476. // Determine whether we need to rapid-close the popup, or whether we can
  7477. // take the time to run the closing transition
  7478. if ( typeof data.toPage === "string" ) {
  7479. parsedDst = data.toPage;
  7480. } else {
  7481. parsedDst = data.toPage.jqmData( "url" );
  7482. }
  7483. parsedDst = $.mobile.path.parseUrl( parsedDst );
  7484. toUrl = parsedDst.pathname + parsedDst.search + parsedDst.hash;
  7485. if ( this._myUrl !== $.mobile.path.makeUrlAbsolute( toUrl ) ) {
  7486. // Going to a different page - close immediately
  7487. immediate = true;
  7488. } else {
  7489. e.preventDefault();
  7490. }
  7491. }
  7492. // remove nav bindings
  7493. o.container.unbind( o.closeEvents );
  7494. // unbind click handlers added when history is disabled
  7495. this.element.undelegate( o.closeLinkSelector, o.closeLinkEvents );
  7496. this._close( immediate );
  7497. },
  7498. // any navigation event after a popup is opened should close the popup
  7499. // NOTE the pagebeforechange is bound to catch navigation events that don't
  7500. // alter the url (eg, dialogs from popups)
  7501. _bindContainerClose: function() {
  7502. this.options.container
  7503. .one( this.options.closeEvents, $.proxy( this, "_closePopup" ) );
  7504. },
  7505. // TODO no clear deliniation of what should be here and
  7506. // what should be in _open. Seems to be "visual" vs "history" for now
  7507. open: function( options ) {
  7508. var self = this, opts = this.options, url, hashkey, activePage, currentIsDialog, hasHash, urlHistory;
  7509. // make sure open is idempotent
  7510. if( $.mobile.popup.active ) {
  7511. return;
  7512. }
  7513. // set the global popup mutex
  7514. $.mobile.popup.active = this;
  7515. this._scrollTop = $.mobile.window.scrollTop();
  7516. // if history alteration is disabled close on navigate events
  7517. // and leave the url as is
  7518. if( !( opts.history ) ) {
  7519. self._open( options );
  7520. self._bindContainerClose();
  7521. // When histoy is disabled we have to grab the data-rel
  7522. // back link clicks so we can close the popup instead of
  7523. // relying on history to do it for us
  7524. self.element
  7525. .delegate( opts.closeLinkSelector, opts.closeLinkEvents, function( e ) {
  7526. self.close();
  7527. e.preventDefault();
  7528. });
  7529. return;
  7530. }
  7531. // cache some values for min/readability
  7532. urlHistory = $.mobile.urlHistory;
  7533. hashkey = $.mobile.dialogHashKey;
  7534. activePage = $.mobile.activePage;
  7535. currentIsDialog = activePage.is( ".ui-dialog" );
  7536. this._myUrl = url = urlHistory.getActive().url;
  7537. hasHash = ( url.indexOf( hashkey ) > -1 ) && !currentIsDialog && ( urlHistory.activeIndex > 0 );
  7538. if ( hasHash ) {
  7539. self._open( options );
  7540. self._bindContainerClose();
  7541. return;
  7542. }
  7543. // if the current url has no dialog hash key proceed as normal
  7544. // otherwise, if the page is a dialog simply tack on the hash key
  7545. if ( url.indexOf( hashkey ) === -1 && !currentIsDialog ){
  7546. url = url + (url.indexOf( "#" ) > -1 ? hashkey : "#" + hashkey);
  7547. } else {
  7548. url = $.mobile.path.parseLocation().hash + hashkey;
  7549. }
  7550. // Tack on an extra hashkey if this is the first page and we've just reconstructed the initial hash
  7551. if ( urlHistory.activeIndex === 0 && url === urlHistory.initialDst ) {
  7552. url += hashkey;
  7553. }
  7554. // swallow the the initial navigation event, and bind for the next
  7555. $(window).one( "beforenavigate", function( e ) {
  7556. e.preventDefault();
  7557. self._open( options );
  7558. self._bindContainerClose();
  7559. });
  7560. this.urlAltered = true;
  7561. $.mobile.navigate( url, {role: "dialog"} );
  7562. },
  7563. close: function() {
  7564. // make sure close is idempotent
  7565. if( $.mobile.popup.active !== this ) {
  7566. return;
  7567. }
  7568. this._scrollTop = $.mobile.window.scrollTop();
  7569. if( this.options.history && this.urlAltered ) {
  7570. $.mobile.back();
  7571. this.urlAltered = false;
  7572. } else {
  7573. // simulate the nav bindings having fired
  7574. this._closePopup();
  7575. }
  7576. }
  7577. });
  7578. // TODO this can be moved inside the widget
  7579. $.mobile.popup.handleLink = function( $link ) {
  7580. var closestPage = $link.closest( ":jqmData(role='page')" ),
  7581. scope = ( ( closestPage.length === 0 ) ? $( "body" ) : closestPage ),
  7582. // NOTE make sure to get only the hash, ie7 (wp7) return the absolute href
  7583. // in this case ruining the element selection
  7584. popup = $( $.mobile.path.parseUrl($link.attr( "href" )).hash, scope[0] ),
  7585. offset;
  7586. if ( popup.data( "mobile-popup" ) ) {
  7587. offset = $link.offset();
  7588. popup.popup( "open", {
  7589. x: offset.left + $link.outerWidth() / 2,
  7590. y: offset.top + $link.outerHeight() / 2,
  7591. transition: $link.jqmData( "transition" ),
  7592. positionTo: $link.jqmData( "position-to" )
  7593. });
  7594. }
  7595. //remove after delay
  7596. setTimeout( function() {
  7597. // Check if we are in a listview
  7598. var $parent = $link.parent().parent();
  7599. if ($parent.hasClass("ui-li")) {
  7600. $link = $parent.parent();
  7601. }
  7602. $link.removeClass( $.mobile.activeBtnClass );
  7603. }, 300 );
  7604. };
  7605. // TODO move inside _create
  7606. $.mobile.document.bind( "pagebeforechange", function( e, data ) {
  7607. if ( data.options.role === "popup" ) {
  7608. $.mobile.popup.handleLink( data.options.link );
  7609. e.preventDefault();
  7610. }
  7611. });
  7612. $.mobile.document.bind( "pagecreate create", function( e ) {
  7613. $.mobile.popup.prototype.enhanceWithin( e.target, true );
  7614. });
  7615. })( jQuery );
  7616. /*
  7617. * custom "selectmenu" plugin
  7618. */
  7619. (function( $, undefined ) {
  7620. var extendSelect = function( widget ) {
  7621. var select = widget.select,
  7622. origDestroy = widget._destroy,
  7623. selectID = widget.selectID,
  7624. prefix = ( selectID ? selectID : ( ( $.mobile.ns || "" ) + "uuid-" + widget.uuid ) ),
  7625. popupID = prefix + "-listbox",
  7626. dialogID = prefix + "-dialog",
  7627. label = widget.label,
  7628. thisPage = widget.select.closest( ".ui-page" ),
  7629. selectOptions = widget._selectOptions(),
  7630. isMultiple = widget.isMultiple = widget.select[ 0 ].multiple,
  7631. buttonId = selectID + "-button",
  7632. menuId = selectID + "-menu",
  7633. menuPage = $( "<div data-" + $.mobile.ns + "role='dialog' id='" + dialogID + "' data-" +$.mobile.ns + "theme='"+ widget.options.theme +"' data-" +$.mobile.ns + "overlay-theme='"+ widget.options.overlayTheme +"'>" +
  7634. "<div data-" + $.mobile.ns + "role='header'>" +
  7635. "<div class='ui-title'>" + label.getEncodedText() + "</div>"+
  7636. "</div>"+
  7637. "<div data-" + $.mobile.ns + "role='content'></div>"+
  7638. "</div>" ),
  7639. listbox = $( "<div id='" + popupID + "' class='ui-selectmenu'>" ).insertAfter( widget.select ).popup( { theme: widget.options.overlayTheme } ),
  7640. list = $( "<ul>", {
  7641. "class": "ui-selectmenu-list",
  7642. "id": menuId,
  7643. "role": "listbox",
  7644. "aria-labelledby": buttonId
  7645. }).attr( "data-" + $.mobile.ns + "theme", widget.options.theme )
  7646. .attr( "data-" + $.mobile.ns + "divider-theme", widget.options.dividerTheme )
  7647. .appendTo( listbox ),
  7648. header = $( "<div>", {
  7649. "class": "ui-header ui-bar-" + widget.options.theme
  7650. }).prependTo( listbox ),
  7651. headerTitle = $( "<h1>", {
  7652. "class": "ui-title"
  7653. }).appendTo( header ),
  7654. menuPageContent,
  7655. menuPageClose,
  7656. headerClose;
  7657. if ( widget.isMultiple ) {
  7658. headerClose = $( "<a>", {
  7659. "text": widget.options.closeText,
  7660. "href": "#",
  7661. "class": "ui-btn-left"
  7662. }).attr( "data-" + $.mobile.ns + "iconpos", "notext" ).attr( "data-" + $.mobile.ns + "icon", "delete" ).appendTo( header ).buttonMarkup();
  7663. }
  7664. $.extend( widget, {
  7665. select: widget.select,
  7666. selectID: selectID,
  7667. buttonId: buttonId,
  7668. menuId: menuId,
  7669. popupID: popupID,
  7670. dialogID: dialogID,
  7671. thisPage: thisPage,
  7672. menuPage: menuPage,
  7673. label: label,
  7674. selectOptions: selectOptions,
  7675. isMultiple: isMultiple,
  7676. theme: widget.options.theme,
  7677. listbox: listbox,
  7678. list: list,
  7679. header: header,
  7680. headerTitle: headerTitle,
  7681. headerClose: headerClose,
  7682. menuPageContent: menuPageContent,
  7683. menuPageClose: menuPageClose,
  7684. placeholder: "",
  7685. build: function() {
  7686. var self = this;
  7687. // Create list from select, update state
  7688. self.refresh();
  7689. if ( self._origTabIndex === undefined ) {
  7690. // Map undefined to false, because self._origTabIndex === undefined
  7691. // indicates that we have not yet checked whether the select has
  7692. // originally had a tabindex attribute, whereas false indicates that
  7693. // we have checked the select for such an attribute, and have found
  7694. // none present.
  7695. self._origTabIndex = ( self.select[ 0 ].getAttribute( "tabindex" ) === null ) ? false : self.select.attr( "tabindex" );
  7696. }
  7697. self.select.attr( "tabindex", "-1" ).focus(function() {
  7698. $( this ).blur();
  7699. self.button.focus();
  7700. });
  7701. // Button events
  7702. self.button.bind( "vclick keydown" , function( event ) {
  7703. if ( self.options.disabled || self.isOpen ) {
  7704. return;
  7705. }
  7706. if (event.type === "vclick" ||
  7707. event.keyCode && (event.keyCode === $.mobile.keyCode.ENTER ||
  7708. event.keyCode === $.mobile.keyCode.SPACE)) {
  7709. self._decideFormat();
  7710. if ( self.menuType === "overlay" ) {
  7711. self.button.attr( "href", "#" + self.popupID ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "popup" );
  7712. } else {
  7713. self.button.attr( "href", "#" + self.dialogID ).attr( "data-" + ( $.mobile.ns || "" ) + "rel", "dialog" );
  7714. }
  7715. self.isOpen = true;
  7716. // Do not prevent default, so the navigation may have a chance to actually open the chosen format
  7717. }
  7718. });
  7719. // Events for list items
  7720. self.list.attr( "role", "listbox" )
  7721. .bind( "focusin", function( e ) {
  7722. $( e.target )
  7723. .attr( "tabindex", "0" )
  7724. .trigger( "vmouseover" );
  7725. })
  7726. .bind( "focusout", function( e ) {
  7727. $( e.target )
  7728. .attr( "tabindex", "-1" )
  7729. .trigger( "vmouseout" );
  7730. })
  7731. .delegate( "li:not(.ui-disabled, .ui-li-divider)", "click", function( event ) {
  7732. // index of option tag to be selected
  7733. var oldIndex = self.select[ 0 ].selectedIndex,
  7734. newIndex = self.list.find( "li:not(.ui-li-divider)" ).index( this ),
  7735. option = self._selectOptions().eq( newIndex )[ 0 ];
  7736. // toggle selected status on the tag for multi selects
  7737. option.selected = self.isMultiple ? !option.selected : true;
  7738. // toggle checkbox class for multiple selects
  7739. if ( self.isMultiple ) {
  7740. $( this ).find( ".ui-icon" )
  7741. .toggleClass( "ui-icon-checkbox-on", option.selected )
  7742. .toggleClass( "ui-icon-checkbox-off", !option.selected );
  7743. }
  7744. // trigger change if value changed
  7745. if ( self.isMultiple || oldIndex !== newIndex ) {
  7746. self.select.trigger( "change" );
  7747. }
  7748. // hide custom select for single selects only - otherwise focus clicked item
  7749. // We need to grab the clicked item the hard way, because the list may have been rebuilt
  7750. if ( self.isMultiple ) {
  7751. self.list.find( "li:not(.ui-li-divider)" ).eq( newIndex )
  7752. .addClass( "ui-btn-down-" + widget.options.theme ).find( "a" ).first().focus();
  7753. }
  7754. else {
  7755. self.close();
  7756. }
  7757. event.preventDefault();
  7758. })
  7759. .keydown(function( event ) { //keyboard events for menu items
  7760. var target = $( event.target ),
  7761. li = target.closest( "li" ),
  7762. prev, next;
  7763. // switch logic based on which key was pressed
  7764. switch ( event.keyCode ) {
  7765. // up or left arrow keys
  7766. case 38:
  7767. prev = li.prev().not( ".ui-selectmenu-placeholder" );
  7768. if ( prev.is( ".ui-li-divider" ) ) {
  7769. prev = prev.prev();
  7770. }
  7771. // if there's a previous option, focus it
  7772. if ( prev.length ) {
  7773. target
  7774. .blur()
  7775. .attr( "tabindex", "-1" );
  7776. prev.addClass( "ui-btn-down-" + widget.options.theme ).find( "a" ).first().focus();
  7777. }
  7778. return false;
  7779. // down or right arrow keys
  7780. case 40:
  7781. next = li.next();
  7782. if ( next.is( ".ui-li-divider" ) ) {
  7783. next = next.next();
  7784. }
  7785. // if there's a next option, focus it
  7786. if ( next.length ) {
  7787. target
  7788. .blur()
  7789. .attr( "tabindex", "-1" );
  7790. next.addClass( "ui-btn-down-" + widget.options.theme ).find( "a" ).first().focus();
  7791. }
  7792. return false;
  7793. // If enter or space is pressed, trigger click
  7794. case 13:
  7795. case 32:
  7796. target.trigger( "click" );
  7797. return false;
  7798. }
  7799. });
  7800. // button refocus ensures proper height calculation
  7801. // by removing the inline style and ensuring page inclusion
  7802. self.menuPage.bind( "pagehide", function() {
  7803. // TODO centralize page removal binding / handling in the page plugin.
  7804. // Suggestion from @jblas to do refcounting
  7805. //
  7806. // TODO extremely confusing dependency on the open method where the pagehide.remove
  7807. // bindings are stripped to prevent the parent page from disappearing. The way
  7808. // we're keeping pages in the DOM right now sucks
  7809. //
  7810. // rebind the page remove that was unbound in the open function
  7811. // to allow for the parent page removal from actions other than the use
  7812. // of a dialog sized custom select
  7813. //
  7814. // doing this here provides for the back button on the custom select dialog
  7815. $.mobile._bindPageRemove.call( self.thisPage );
  7816. });
  7817. // Events on the popup
  7818. self.listbox.bind( "popupafterclose", function( event ) {
  7819. self.close();
  7820. });
  7821. // Close button on small overlays
  7822. if ( self.isMultiple ) {
  7823. self.headerClose.click(function() {
  7824. if ( self.menuType === "overlay" ) {
  7825. self.close();
  7826. return false;
  7827. }
  7828. });
  7829. }
  7830. // track this dependency so that when the parent page
  7831. // is removed on pagehide it will also remove the menupage
  7832. self.thisPage.addDependents( this.menuPage );
  7833. },
  7834. _isRebuildRequired: function() {
  7835. var list = this.list.find( "li" ),
  7836. options = this._selectOptions();
  7837. // TODO exceedingly naive method to determine difference
  7838. // ignores value changes etc in favor of a forcedRebuild
  7839. // from the user in the refresh method
  7840. return options.text() !== list.text();
  7841. },
  7842. selected: function() {
  7843. return this._selectOptions().filter( ":selected:not( :jqmData(placeholder='true') )" );
  7844. },
  7845. refresh: function( forceRebuild , foo ) {
  7846. var self = this,
  7847. select = this.element,
  7848. isMultiple = this.isMultiple,
  7849. indicies;
  7850. if ( forceRebuild || this._isRebuildRequired() ) {
  7851. self._buildList();
  7852. }
  7853. indicies = this.selectedIndices();
  7854. self.setButtonText();
  7855. self.setButtonCount();
  7856. self.list.find( "li:not(.ui-li-divider)" )
  7857. .removeClass( $.mobile.activeBtnClass )
  7858. .attr( "aria-selected", false )
  7859. .each(function( i ) {
  7860. if ( $.inArray( i, indicies ) > -1 ) {
  7861. var item = $( this );
  7862. // Aria selected attr
  7863. item.attr( "aria-selected", true );
  7864. // Multiple selects: add the "on" checkbox state to the icon
  7865. if ( self.isMultiple ) {
  7866. item.find( ".ui-icon" ).removeClass( "ui-icon-checkbox-off" ).addClass( "ui-icon-checkbox-on" );
  7867. } else {
  7868. if ( item.is( ".ui-selectmenu-placeholder" ) ) {
  7869. item.next().addClass( $.mobile.activeBtnClass );
  7870. } else {
  7871. item.addClass( $.mobile.activeBtnClass );
  7872. }
  7873. }
  7874. }
  7875. });
  7876. },
  7877. close: function() {
  7878. if ( this.options.disabled || !this.isOpen ) {
  7879. return;
  7880. }
  7881. var self = this;
  7882. if ( self.menuType === "page" ) {
  7883. self.menuPage.dialog( "close" );
  7884. self.list.appendTo( self.listbox );
  7885. } else {
  7886. self.listbox.popup( "close" );
  7887. }
  7888. self._focusButton();
  7889. // allow the dialog to be closed again
  7890. self.isOpen = false;
  7891. },
  7892. open: function() {
  7893. this.button.click();
  7894. },
  7895. _decideFormat: function() {
  7896. var self = this,
  7897. $window = $.mobile.window,
  7898. selfListParent = self.list.parent(),
  7899. menuHeight = selfListParent.outerHeight(),
  7900. menuWidth = selfListParent.outerWidth(),
  7901. activePage = $( "." + $.mobile.activePageClass ),
  7902. scrollTop = $window.scrollTop(),
  7903. btnOffset = self.button.offset().top,
  7904. screenHeight = $window.height(),
  7905. screenWidth = $window.width();
  7906. function focusMenuItem() {
  7907. var selector = self.list.find( "." + $.mobile.activeBtnClass + " a" );
  7908. if ( selector.length === 0 ) {
  7909. selector = self.list.find( "li.ui-btn:not( :jqmData(placeholder='true') ) a" );
  7910. }
  7911. selector.first().focus().closest( "li" ).addClass( "ui-btn-down-" + widget.options.theme );
  7912. }
  7913. if ( menuHeight > screenHeight - 80 || !$.support.scrollTop ) {
  7914. self.menuPage.appendTo( $.mobile.pageContainer ).page();
  7915. self.menuPageContent = menuPage.find( ".ui-content" );
  7916. self.menuPageClose = menuPage.find( ".ui-header a" );
  7917. // prevent the parent page from being removed from the DOM,
  7918. // otherwise the results of selecting a list item in the dialog
  7919. // fall into a black hole
  7920. self.thisPage.unbind( "pagehide.remove" );
  7921. //for WebOS/Opera Mini (set lastscroll using button offset)
  7922. if ( scrollTop === 0 && btnOffset > screenHeight ) {
  7923. self.thisPage.one( "pagehide", function() {
  7924. $( this ).jqmData( "lastScroll", btnOffset );
  7925. });
  7926. }
  7927. self.menuPage
  7928. .one( "pageshow", function() {
  7929. focusMenuItem();
  7930. })
  7931. .one( "pagehide", function() {
  7932. self.close();
  7933. });
  7934. self.menuType = "page";
  7935. self.menuPageContent.append( self.list );
  7936. self.menuPage.find("div .ui-title").text(self.label.text());
  7937. } else {
  7938. self.menuType = "overlay";
  7939. self.listbox.one( "popupafteropen", focusMenuItem );
  7940. }
  7941. },
  7942. _buildList: function() {
  7943. var self = this,
  7944. o = this.options,
  7945. placeholder = this.placeholder,
  7946. needPlaceholder = true,
  7947. optgroups = [],
  7948. lis = [],
  7949. dataIcon = self.isMultiple ? "checkbox-off" : "false";
  7950. self.list.empty().filter( ".ui-listview" ).listview( "destroy" );
  7951. var $options = self.select.find( "option" ),
  7952. numOptions = $options.length,
  7953. select = this.select[ 0 ],
  7954. dataPrefix = 'data-' + $.mobile.ns,
  7955. dataIndexAttr = dataPrefix + 'option-index',
  7956. dataIconAttr = dataPrefix + 'icon',
  7957. dataRoleAttr = dataPrefix + 'role',
  7958. dataPlaceholderAttr = dataPrefix + 'placeholder',
  7959. fragment = document.createDocumentFragment(),
  7960. isPlaceholderItem = false,
  7961. optGroup;
  7962. for (var i = 0; i < numOptions;i++, isPlaceholderItem = false) {
  7963. var option = $options[i],
  7964. $option = $( option ),
  7965. parent = option.parentNode,
  7966. text = $option.text(),
  7967. anchor = document.createElement( 'a' ),
  7968. classes = [];
  7969. anchor.setAttribute( 'href', '#' );
  7970. anchor.appendChild( document.createTextNode( text ) );
  7971. // Are we inside an optgroup?
  7972. if ( parent !== select && parent.nodeName.toLowerCase() === "optgroup" ) {
  7973. var optLabel = parent.getAttribute( 'label' );
  7974. if ( optLabel !== optGroup ) {
  7975. var divider = document.createElement( 'li' );
  7976. divider.setAttribute( dataRoleAttr, 'list-divider' );
  7977. divider.setAttribute( 'role', 'option' );
  7978. divider.setAttribute( 'tabindex', '-1' );
  7979. divider.appendChild( document.createTextNode( optLabel ) );
  7980. fragment.appendChild( divider );
  7981. optGroup = optLabel;
  7982. }
  7983. }
  7984. if ( needPlaceholder && ( !option.getAttribute( "value" ) || text.length === 0 || $option.jqmData( "placeholder" ) ) ) {
  7985. needPlaceholder = false;
  7986. isPlaceholderItem = true;
  7987. // If we have identified a placeholder, record the fact that it was
  7988. // us who have added the placeholder to the option and mark it
  7989. // retroactively in the select as well
  7990. if ( null === option.getAttribute( dataPlaceholderAttr ) ) {
  7991. this._removePlaceholderAttr = true;
  7992. }
  7993. option.setAttribute( dataPlaceholderAttr, true );
  7994. if ( o.hidePlaceholderMenuItems ) {
  7995. classes.push( "ui-selectmenu-placeholder" );
  7996. }
  7997. if ( placeholder !== text ) {
  7998. placeholder = self.placeholder = text;
  7999. }
  8000. }
  8001. var item = document.createElement('li');
  8002. if ( option.disabled ) {
  8003. classes.push( "ui-disabled" );
  8004. item.setAttribute('aria-disabled',true);
  8005. }
  8006. item.setAttribute( dataIndexAttr,i );
  8007. item.setAttribute( dataIconAttr, dataIcon );
  8008. if ( isPlaceholderItem ) {
  8009. item.setAttribute( dataPlaceholderAttr, true );
  8010. }
  8011. item.className = classes.join( " " );
  8012. item.setAttribute( 'role', 'option' );
  8013. anchor.setAttribute( 'tabindex', '-1' );
  8014. item.appendChild( anchor );
  8015. fragment.appendChild( item );
  8016. }
  8017. self.list[0].appendChild( fragment );
  8018. // Hide header if it's not a multiselect and there's no placeholder
  8019. if ( !this.isMultiple && !placeholder.length ) {
  8020. this.header.hide();
  8021. } else {
  8022. this.headerTitle.text( this.placeholder );
  8023. }
  8024. // Now populated, create listview
  8025. self.list.listview();
  8026. },
  8027. _button: function() {
  8028. return $( "<a>", {
  8029. "href": "#",
  8030. "role": "button",
  8031. // TODO value is undefined at creation
  8032. "id": this.buttonId,
  8033. "aria-haspopup": "true",
  8034. // TODO value is undefined at creation
  8035. "aria-owns": this.menuId
  8036. });
  8037. },
  8038. _destroy: function() {
  8039. this.close();
  8040. // Restore the tabindex attribute to its original value
  8041. if ( this._origTabIndex !== undefined ) {
  8042. if ( this._origTabIndex !== false ) {
  8043. this.select.attr( "tabindex", this._origTabIndex );
  8044. } else {
  8045. this.select.removeAttr( "tabindex" );
  8046. }
  8047. }
  8048. // Remove the placeholder attribute if we were the ones to add it
  8049. if ( this._removePlaceholderAttr ) {
  8050. this._selectOptions().removeAttr( "data-" + $.mobile.ns + "placeholder" );
  8051. }
  8052. // Remove the popup
  8053. this.listbox.remove();
  8054. // Chain up
  8055. origDestroy.apply( this, arguments );
  8056. }
  8057. });
  8058. };
  8059. // issue #3894 - core doesn't trigger events on disabled delegates
  8060. $.mobile.document.bind( "selectmenubeforecreate", function( event ) {
  8061. var selectmenuWidget = $( event.target ).data( "mobile-selectmenu" );
  8062. if ( !selectmenuWidget.options.nativeMenu &&
  8063. selectmenuWidget.element.parents( ":jqmData(role='popup')" ).length === 0 ) {
  8064. extendSelect( selectmenuWidget );
  8065. }
  8066. });
  8067. })( jQuery );
  8068. (function( $, undefined ) {
  8069. $.widget( "mobile.controlgroup", $.mobile.widget, $.extend( {
  8070. options: {
  8071. shadow: false,
  8072. corners: true,
  8073. excludeInvisible: true,
  8074. type: "vertical",
  8075. mini: false,
  8076. initSelector: ":jqmData(role='controlgroup')"
  8077. },
  8078. _create: function() {
  8079. var $el = this.element,
  8080. ui = {
  8081. inner: $( "<div class='ui-controlgroup-controls'></div>" ),
  8082. legend: $( "<div role='heading' class='ui-controlgroup-label'></div>" )
  8083. },
  8084. grouplegend = $el.children( "legend" ),
  8085. self = this;
  8086. // Apply the proto
  8087. $el.wrapInner( ui.inner );
  8088. if ( grouplegend.length ) {
  8089. ui.legend.append( grouplegend ).insertBefore( $el.children( 0 ) );
  8090. }
  8091. $el.addClass( "ui-corner-all ui-controlgroup" );
  8092. $.extend( this, {
  8093. _initialRefresh: true
  8094. });
  8095. $.each( this.options, function( key, value ) {
  8096. // Cause initial options to be applied by their handler by temporarily setting the option to undefined
  8097. // - the handler then sets it to the initial value
  8098. self.options[ key ] = undefined;
  8099. self._setOption( key, value, true );
  8100. });
  8101. },
  8102. _init: function() {
  8103. this.refresh();
  8104. },
  8105. _setOption: function( key, value ) {
  8106. var setter = "_set" + key.charAt( 0 ).toUpperCase() + key.slice( 1 );
  8107. if ( this[ setter ] !== undefined ) {
  8108. this[ setter ]( value );
  8109. }
  8110. this._super( key, value );
  8111. this.element.attr( "data-" + ( $.mobile.ns || "" ) + ( key.replace( /([A-Z])/, "-$1" ).toLowerCase() ), value );
  8112. },
  8113. _setType: function( value ) {
  8114. this.element
  8115. .removeClass( "ui-controlgroup-horizontal ui-controlgroup-vertical" )
  8116. .addClass( "ui-controlgroup-" + value );
  8117. this.refresh();
  8118. },
  8119. _setCorners: function( value ) {
  8120. this.element.toggleClass( "ui-corner-all", value );
  8121. },
  8122. _setShadow: function( value ) {
  8123. this.element.toggleClass( "ui-shadow", value );
  8124. },
  8125. _setMini: function( value ) {
  8126. this.element.toggleClass( "ui-mini", value );
  8127. },
  8128. container: function() {
  8129. return this.element.children( ".ui-controlgroup-controls" );
  8130. },
  8131. refresh: function() {
  8132. var els = this.element.find( ".ui-btn" ).not( ".ui-slider-handle" ),
  8133. create = this._initialRefresh;
  8134. if ( $.mobile.checkboxradio ) {
  8135. this.element.find( ":mobile-checkboxradio" ).checkboxradio( "refresh" );
  8136. }
  8137. this._addFirstLastClasses( els, this.options.excludeInvisible ? this._getVisibles( els, create ) : els, create );
  8138. this._initialRefresh = false;
  8139. }
  8140. }, $.mobile.behaviors.addFirstLastClasses ) );
  8141. // TODO: Implement a mechanism to allow widgets to become enhanced in the
  8142. // correct order when their correct enhancement depends on other widgets in
  8143. // the page being correctly enhanced already.
  8144. //
  8145. // For now, we wait until dom-ready to attach the controlgroup's enhancement
  8146. // hook, because by that time, all the other widgets' enhancement hooks should
  8147. // already be in place, ensuring that all widgets that need to be grouped will
  8148. // already have been enhanced by the time the controlgroup is created.
  8149. $( function() {
  8150. $.mobile.document.bind( "pagecreate create", function( e ) {
  8151. $.mobile.controlgroup.prototype.enhanceWithin( e.target, true );
  8152. });
  8153. });
  8154. })(jQuery);
  8155. (function( $, undefined ) {
  8156. $( document ).bind( "pagecreate create", function( e ) {
  8157. //links within content areas, tests included with page
  8158. $( e.target )
  8159. .find( "a" )
  8160. .jqmEnhanceable()
  8161. .not( ".ui-btn, .ui-link-inherit, :jqmData(role='none'), :jqmData(role='nojs')" )
  8162. .addClass( "ui-link" );
  8163. });
  8164. })( jQuery );
  8165. (function( $, undefined ) {
  8166. $.widget( "mobile.fixedtoolbar", $.mobile.widget, {
  8167. options: {
  8168. visibleOnPageShow: true,
  8169. disablePageZoom: true,
  8170. transition: "slide", //can be none, fade, slide (slide maps to slideup or slidedown)
  8171. fullscreen: false,
  8172. tapToggle: true,
  8173. tapToggleBlacklist: "a, button, input, select, textarea, .ui-header-fixed, .ui-footer-fixed, .ui-popup, .ui-panel, .ui-panel-dismiss-open",
  8174. hideDuringFocus: "input, textarea, select",
  8175. updatePagePadding: true,
  8176. trackPersistentToolbars: true,
  8177. // Browser detection! Weeee, here we go...
  8178. // Unfortunately, position:fixed is costly, not to mention probably impossible, to feature-detect accurately.
  8179. // Some tests exist, but they currently return false results in critical devices and browsers, which could lead to a broken experience.
  8180. // Testing fixed positioning is also pretty obtrusive to page load, requiring injected elements and scrolling the window
  8181. // The following function serves to rule out some popular browsers with known fixed-positioning issues
  8182. // This is a plugin option like any other, so feel free to improve or overwrite it
  8183. supportBlacklist: function() {
  8184. return !$.support.fixedPosition;
  8185. },
  8186. initSelector: ":jqmData(position='fixed')"
  8187. },
  8188. _create: function() {
  8189. var self = this,
  8190. o = self.options,
  8191. $el = self.element,
  8192. tbtype = $el.is( ":jqmData(role='header')" ) ? "header" : "footer",
  8193. $page = $el.closest( ".ui-page" );
  8194. // Feature detecting support for
  8195. if ( o.supportBlacklist() ) {
  8196. self.destroy();
  8197. return;
  8198. }
  8199. $el.addClass( "ui-"+ tbtype +"-fixed" );
  8200. // "fullscreen" overlay positioning
  8201. if ( o.fullscreen ) {
  8202. $el.addClass( "ui-"+ tbtype +"-fullscreen" );
  8203. $page.addClass( "ui-page-" + tbtype + "-fullscreen" );
  8204. }
  8205. // If not fullscreen, add class to page to set top or bottom padding
  8206. else{
  8207. $page.addClass( "ui-page-" + tbtype + "-fixed" );
  8208. }
  8209. $.extend( this, {
  8210. _thisPage: null
  8211. });
  8212. self._addTransitionClass();
  8213. self._bindPageEvents();
  8214. self._bindToggleHandlers();
  8215. },
  8216. _addTransitionClass: function() {
  8217. var tclass = this.options.transition;
  8218. if ( tclass && tclass !== "none" ) {
  8219. // use appropriate slide for header or footer
  8220. if ( tclass === "slide" ) {
  8221. tclass = this.element.is( ".ui-header" ) ? "slidedown" : "slideup";
  8222. }
  8223. this.element.addClass( tclass );
  8224. }
  8225. },
  8226. _bindPageEvents: function() {
  8227. this._thisPage = this.element.closest( ".ui-page" );
  8228. //page event bindings
  8229. // Fixed toolbars require page zoom to be disabled, otherwise usability issues crop up
  8230. // This method is meant to disable zoom while a fixed-positioned toolbar page is visible
  8231. this._on( this._thisPage, {
  8232. "pagebeforeshow": "_handlePageBeforeShow",
  8233. "webkitAnimationStart":"_handleAnimationStart",
  8234. "animationstart":"_handleAnimationStart",
  8235. "updatelayout": "_handleAnimationStart",
  8236. "pageshow": "_handlePageShow",
  8237. "pagebeforehide": "_handlePageBeforeHide"
  8238. });
  8239. },
  8240. _handlePageBeforeShow: function() {
  8241. var o = this.options;
  8242. if ( o.disablePageZoom ) {
  8243. $.mobile.zoom.disable( true );
  8244. }
  8245. if ( !o.visibleOnPageShow ) {
  8246. this.hide( true );
  8247. }
  8248. },
  8249. _handleAnimationStart: function() {
  8250. if ( this.options.updatePagePadding ) {
  8251. this.updatePagePadding( this._thisPage );
  8252. }
  8253. },
  8254. _handlePageShow: function() {
  8255. this.updatePagePadding( this._thisPage );
  8256. if ( this.options.updatePagePadding ) {
  8257. this._on( $.mobile.window, { "throttledresize": "updatePagePadding" } );
  8258. }
  8259. },
  8260. _handlePageBeforeHide: function( e, ui ) {
  8261. var o = this.options;
  8262. if ( o.disablePageZoom ) {
  8263. $.mobile.zoom.enable( true );
  8264. }
  8265. if ( o.updatePagePadding ) {
  8266. this._off( $.mobile.window, "throttledresize" );
  8267. }
  8268. if ( o.trackPersistentToolbars ) {
  8269. var thisFooter = $( ".ui-footer-fixed:jqmData(id)", this._thisPage ),
  8270. thisHeader = $( ".ui-header-fixed:jqmData(id)", this._thisPage ),
  8271. nextFooter = thisFooter.length && ui.nextPage && $( ".ui-footer-fixed:jqmData(id='" + thisFooter.jqmData( "id" ) + "')", ui.nextPage ) || $(),
  8272. nextHeader = thisHeader.length && ui.nextPage && $( ".ui-header-fixed:jqmData(id='" + thisHeader.jqmData( "id" ) + "')", ui.nextPage ) || $();
  8273. if ( nextFooter.length || nextHeader.length ) {
  8274. nextFooter.add( nextHeader ).appendTo( $.mobile.pageContainer );
  8275. ui.nextPage.one( "pageshow", function() {
  8276. nextHeader.prependTo( this );
  8277. nextFooter.appendTo( this );
  8278. });
  8279. }
  8280. }
  8281. },
  8282. _visible: true,
  8283. // This will set the content element's top or bottom padding equal to the toolbar's height
  8284. updatePagePadding: function( tbPage ) {
  8285. var $el = this.element,
  8286. header = $el.is( ".ui-header" ),
  8287. pos = parseFloat( $el.css( header ? "top" : "bottom" ) );
  8288. // This behavior only applies to "fixed", not "fullscreen"
  8289. if ( this.options.fullscreen ) { return; }
  8290. // tbPage argument can be a Page object or an event, if coming from throttled resize.
  8291. tbPage = ( tbPage && tbPage.type === undefined && tbPage ) || this._thisPage || $el.closest( ".ui-page" );
  8292. $( tbPage ).css( "padding-" + ( header ? "top" : "bottom" ), $el.outerHeight() + pos );
  8293. },
  8294. _useTransition: function( notransition ) {
  8295. var $win = $.mobile.window,
  8296. $el = this.element,
  8297. scroll = $win.scrollTop(),
  8298. elHeight = $el.height(),
  8299. pHeight = $el.closest( ".ui-page" ).height(),
  8300. viewportHeight = $.mobile.getScreenHeight(),
  8301. tbtype = $el.is( ":jqmData(role='header')" ) ? "header" : "footer";
  8302. return !notransition &&
  8303. ( this.options.transition && this.options.transition !== "none" &&
  8304. (
  8305. ( tbtype === "header" && !this.options.fullscreen && scroll > elHeight ) ||
  8306. ( tbtype === "footer" && !this.options.fullscreen && scroll + viewportHeight < pHeight - elHeight )
  8307. ) || this.options.fullscreen
  8308. );
  8309. },
  8310. show: function( notransition ) {
  8311. var hideClass = "ui-fixed-hidden",
  8312. $el = this.element;
  8313. if ( this._useTransition( notransition ) ) {
  8314. $el
  8315. .removeClass( "out " + hideClass )
  8316. .addClass( "in" )
  8317. .animationComplete(function () {
  8318. $el.removeClass('in');
  8319. });
  8320. }
  8321. else {
  8322. $el.removeClass( hideClass );
  8323. }
  8324. this._visible = true;
  8325. },
  8326. hide: function( notransition ) {
  8327. var hideClass = "ui-fixed-hidden",
  8328. $el = this.element,
  8329. // if it's a slide transition, our new transitions need the reverse class as well to slide outward
  8330. outclass = "out" + ( this.options.transition === "slide" ? " reverse" : "" );
  8331. if( this._useTransition( notransition ) ) {
  8332. $el
  8333. .addClass( outclass )
  8334. .removeClass( "in" )
  8335. .animationComplete(function() {
  8336. $el.addClass( hideClass ).removeClass( outclass );
  8337. });
  8338. }
  8339. else {
  8340. $el.addClass( hideClass ).removeClass( outclass );
  8341. }
  8342. this._visible = false;
  8343. },
  8344. toggle: function() {
  8345. this[ this._visible ? "hide" : "show" ]();
  8346. },
  8347. _bindToggleHandlers: function() {
  8348. var self = this,
  8349. o = self.options,
  8350. $el = self.element,
  8351. delayShow, delayHide,
  8352. isVisible = true;
  8353. // tap toggle
  8354. $el.closest( ".ui-page" )
  8355. .bind( "vclick", function( e ) {
  8356. if ( o.tapToggle && !$( e.target ).closest( o.tapToggleBlacklist ).length ) {
  8357. self.toggle();
  8358. }
  8359. })
  8360. .bind( "focusin focusout", function( e ) {
  8361. //this hides the toolbars on a keyboard pop to give more screen room and prevent ios bug which
  8362. //positions fixed toolbars in the middle of the screen on pop if the input is near the top or
  8363. //bottom of the screen addresses issues #4410 Footer navbar moves up when clicking on a textbox in an Android environment
  8364. //and issue #4113 Header and footer change their position after keyboard popup - iOS
  8365. //and issue #4410 Footer navbar moves up when clicking on a textbox in an Android environment
  8366. if ( screen.width < 1025 && $( e.target ).is( o.hideDuringFocus ) && !$( e.target ).closest( ".ui-header-fixed, .ui-footer-fixed" ).length ) {
  8367. //Fix for issue #4724 Moving through form in Mobile Safari with "Next" and "Previous" system
  8368. //controls causes fixed position, tap-toggle false Header to reveal itself
  8369. // isVisible instead of self._visible because the focusin and focusout events fire twice at the same time
  8370. // Also use a delay for hiding the toolbars because on Android native browser focusin is direclty followed
  8371. // by a focusout when a native selects opens and the other way around when it closes.
  8372. if ( e.type === "focusout" && !isVisible ) {
  8373. isVisible = true;
  8374. //wait for the stack to unwind and see if we have jumped to another input
  8375. clearTimeout( delayHide );
  8376. delayShow = setTimeout( function() {
  8377. self.show();
  8378. }, 0 );
  8379. } else if ( e.type === "focusin" && !!isVisible ) {
  8380. //if we have jumped to another input clear the time out to cancel the show.
  8381. clearTimeout( delayShow );
  8382. isVisible = false;
  8383. delayHide = setTimeout( function() {
  8384. self.hide();
  8385. }, 0 );
  8386. }
  8387. }
  8388. });
  8389. },
  8390. _destroy: function() {
  8391. var $el = this.element,
  8392. header = $el.is( ".ui-header" );
  8393. $el.closest( ".ui-page" ).css( "padding-" + ( header ? "top" : "bottom" ), "" );
  8394. $el.removeClass( "ui-header-fixed ui-footer-fixed ui-header-fullscreen ui-footer-fullscreen in out fade slidedown slideup ui-fixed-hidden" );
  8395. $el.closest( ".ui-page" ).removeClass( "ui-page-header-fixed ui-page-footer-fixed ui-page-header-fullscreen ui-page-footer-fullscreen" );
  8396. }
  8397. });
  8398. //auto self-init widgets
  8399. $.mobile.document
  8400. .bind( "pagecreate create", function( e ) {
  8401. // DEPRECATED in 1.1: support for data-fullscreen=true|false on the page element.
  8402. // This line ensures it still works, but we recommend moving the attribute to the toolbars themselves.
  8403. if ( $( e.target ).jqmData( "fullscreen" ) ) {
  8404. $( $.mobile.fixedtoolbar.prototype.options.initSelector, e.target ).not( ":jqmData(fullscreen)" ).jqmData( "fullscreen", true );
  8405. }
  8406. $.mobile.fixedtoolbar.prototype.enhanceWithin( e.target );
  8407. });
  8408. })( jQuery );
  8409. (function( $, undefined ) {
  8410. $.widget( "mobile.fixedtoolbar", $.mobile.fixedtoolbar, {
  8411. _create: function() {
  8412. this._super();
  8413. this._workarounds();
  8414. },
  8415. //check the browser and version and run needed workarounds
  8416. _workarounds: function() {
  8417. var ua = navigator.userAgent,
  8418. platform = navigator.platform,
  8419. // Rendering engine is Webkit, and capture major version
  8420. wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ ),
  8421. wkversion = !!wkmatch && wkmatch[ 1 ],
  8422. os = null,
  8423. self = this;
  8424. //set the os we are working in if it dosent match one with workarounds return
  8425. if( platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1 || platform.indexOf( "iPod" ) > -1 ){
  8426. os = "ios";
  8427. } else if( ua.indexOf( "Android" ) > -1 ){
  8428. os = "android";
  8429. } else {
  8430. return;
  8431. }
  8432. //check os version if it dosent match one with workarounds return
  8433. if( os === "ios" ) {
  8434. //iOS workarounds
  8435. self._bindScrollWorkaround();
  8436. } else if( os === "android" && wkversion && wkversion < 534 ) {
  8437. //Android 2.3 run all Android 2.3 workaround
  8438. self._bindScrollWorkaround();
  8439. self._bindListThumbWorkaround();
  8440. } else {
  8441. return;
  8442. }
  8443. },
  8444. //Utility class for checking header and footer positions relative to viewport
  8445. _viewportOffset: function() {
  8446. var $el = this.element,
  8447. header = $el.is( ".ui-header" ),
  8448. offset = Math.abs($el.offset().top - $.mobile.window.scrollTop());
  8449. if( !header ) {
  8450. offset = Math.round(offset - $.mobile.window.height() + $el.outerHeight())-60;
  8451. }
  8452. return offset;
  8453. },
  8454. //bind events for _triggerRedraw() function
  8455. _bindScrollWorkaround: function() {
  8456. var self = this;
  8457. //bind to scrollstop and check if the toolbars are correctly positioned
  8458. this._on( $.mobile.window, { scrollstop: function() {
  8459. var viewportOffset = self._viewportOffset();
  8460. //check if the header is visible and if its in the right place
  8461. if( viewportOffset > 2 && self._visible) {
  8462. self._triggerRedraw();
  8463. }
  8464. }});
  8465. },
  8466. //this addresses issue #4250 Persistent footer instability in v1.1 with long select lists in Android 2.3.3
  8467. //and issue #3748 Android 2.x: Page transitions broken when fixed toolbars used
  8468. //the absolutely positioned thumbnail in a list view causes problems with fixed position buttons above in a nav bar
  8469. //setting the li's to -webkit-transform:translate3d(0,0,0); solves this problem to avoide potential issues in other
  8470. //platforms we scope this with the class ui-android-2x-fix
  8471. _bindListThumbWorkaround: function() {
  8472. this.element.closest(".ui-page").addClass( "ui-android-2x-fixed" );
  8473. },
  8474. //this addresses issues #4337 Fixed header problem after scrolling content on iOS and Android
  8475. //and device bugs project issue #1 Form elements can lose click hit area in position: fixed containers.
  8476. //this also addresses not on fixed toolbars page in docs
  8477. //adding 1px of padding to the bottom then removing it causes a "redraw"
  8478. //which positions the toolbars correctly (they will always be visually correct)
  8479. _triggerRedraw: function() {
  8480. var paddingBottom = parseFloat( $( ".ui-page-active" ).css( "padding-bottom" ) );
  8481. //trigger page redraw to fix incorrectly positioned fixed elements
  8482. $( ".ui-page-active" ).css( "padding-bottom", ( paddingBottom + 1 ) +"px" );
  8483. //if the padding is reset with out a timeout the reposition will not occure.
  8484. //this is independant of JQM the browser seems to need the time to react.
  8485. setTimeout( function() {
  8486. $( ".ui-page-active" ).css( "padding-bottom", paddingBottom + "px" );
  8487. }, 0 );
  8488. },
  8489. destroy: function() {
  8490. this._super();
  8491. //Remove the class we added to the page previously in android 2.x
  8492. this.element.closest(".ui-page-active").removeClass( "ui-android-2x-fix" );
  8493. }
  8494. });
  8495. })( jQuery );
  8496. (function( $, undefined ) {
  8497. $.widget( "mobile.panel", $.mobile.widget, {
  8498. options: {
  8499. classes: {
  8500. panel: "ui-panel",
  8501. panelOpen: "ui-panel-open",
  8502. panelClosed: "ui-panel-closed",
  8503. panelFixed: "ui-panel-fixed",
  8504. panelInner: "ui-panel-inner",
  8505. modal: "ui-panel-dismiss",
  8506. modalOpen: "ui-panel-dismiss-open",
  8507. pagePanel: "ui-page-panel",
  8508. pagePanelOpen: "ui-page-panel-open",
  8509. contentWrap: "ui-panel-content-wrap",
  8510. contentWrapOpen: "ui-panel-content-wrap-open",
  8511. contentWrapClosed: "ui-panel-content-wrap-closed",
  8512. contentFixedToolbar: "ui-panel-content-fixed-toolbar",
  8513. contentFixedToolbarOpen: "ui-panel-content-fixed-toolbar-open",
  8514. contentFixedToolbarClosed: "ui-panel-content-fixed-toolbar-closed",
  8515. animate: "ui-panel-animate"
  8516. },
  8517. animate: true,
  8518. theme: "c",
  8519. position: "left",
  8520. dismissible: true,
  8521. display: "reveal", //accepts reveal, push, overlay
  8522. initSelector: ":jqmData(role='panel')",
  8523. swipeClose: true,
  8524. positionFixed: false
  8525. },
  8526. _panelID: null,
  8527. _closeLink: null,
  8528. _page: null,
  8529. _modal: null,
  8530. _panelInner: null,
  8531. _wrapper: null,
  8532. _fixedToolbar: null,
  8533. _create: function() {
  8534. var self = this,
  8535. $el = self.element,
  8536. page = $el.closest( ":jqmData(role='page')" ),
  8537. _getPageTheme = function() {
  8538. var $theme = $.data( page[0], "mobilePage" ).options.theme,
  8539. $pageThemeClass = "ui-body-" + $theme;
  8540. return $pageThemeClass;
  8541. },
  8542. _getPanelInner = function() {
  8543. var $panelInner = $el.find( "." + self.options.classes.panelInner );
  8544. if ( $panelInner.length === 0 ) {
  8545. $panelInner = $el.children().wrapAll( '<div class="' + self.options.classes.panelInner + '" />' ).parent();
  8546. }
  8547. return $panelInner;
  8548. },
  8549. _getWrapper = function() {
  8550. var $wrapper = page.find( "." + self.options.classes.contentWrap );
  8551. if ( $wrapper.length === 0 ) {
  8552. $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();
  8553. if ( $.support.cssTransform3d && !!self.options.animate ) {
  8554. $wrapper.addClass( self.options.classes.animate );
  8555. }
  8556. }
  8557. return $wrapper;
  8558. },
  8559. _getFixedToolbar = function() {
  8560. var $fixedToolbar = page.find( "." + self.options.classes.contentFixedToolbar );
  8561. if ( $fixedToolbar.length === 0 ) {
  8562. $fixedToolbar = page.find( ".ui-header:jqmData(position='fixed'), .ui-footer:jqmData(position='fixed')" ).addClass( self.options.classes.contentFixedToolbar );
  8563. if ( $.support.cssTransform3d && !!self.options.animate ) {
  8564. $fixedToolbar.addClass( self.options.classes.animate );
  8565. }
  8566. }
  8567. return $fixedToolbar;
  8568. };
  8569. // expose some private props to other methods
  8570. $.extend( this, {
  8571. _panelID: $el.attr( "id" ),
  8572. _closeLink: $el.find( ":jqmData(rel='close')" ),
  8573. _page: $el.closest( ":jqmData(role='page')" ),
  8574. _pageTheme: _getPageTheme(),
  8575. _panelInner: _getPanelInner(),
  8576. _wrapper: _getWrapper(),
  8577. _fixedToolbar: _getFixedToolbar()
  8578. });
  8579. self._addPanelClasses();
  8580. self._wrapper.addClass( this.options.classes.contentWrapClosed );
  8581. self._fixedToolbar.addClass( this.options.classes.contentFixedToolbarClosed );
  8582. // add class to page so we can set "overflow-x: hidden;" for it to fix Android zoom issue
  8583. self._page.addClass( self.options.classes.pagePanel );
  8584. // if animating, add the class to do so
  8585. if ( $.support.cssTransform3d && !!self.options.animate ) {
  8586. this.element.addClass( self.options.classes.animate );
  8587. }
  8588. self._bindUpdateLayout();
  8589. self._bindCloseEvents();
  8590. self._bindLinkListeners();
  8591. self._bindPageEvents();
  8592. if ( !!self.options.dismissible ) {
  8593. self._createModal();
  8594. }
  8595. self._bindSwipeEvents();
  8596. },
  8597. _createModal: function( options ) {
  8598. var self = this;
  8599. self._modal = $( "<div class='" + self.options.classes.modal + "' data-panelid='" + self._panelID + "'></div>" )
  8600. .on( "mousedown", function() {
  8601. self.close();
  8602. })
  8603. .appendTo( this._page );
  8604. },
  8605. _getPosDisplayClasses: function( prefix ) {
  8606. return prefix + "-position-" + this.options.position + " " + prefix + "-display-" + this.options.display;
  8607. },
  8608. _getPanelClasses: function() {
  8609. var panelClasses = this.options.classes.panel +
  8610. " " + this._getPosDisplayClasses( this.options.classes.panel ) +
  8611. " " + this.options.classes.panelClosed;
  8612. if ( this.options.theme ) {
  8613. panelClasses += " ui-body-" + this.options.theme;
  8614. }
  8615. if ( !!this.options.positionFixed ) {
  8616. panelClasses += " " + this.options.classes.panelFixed;
  8617. }
  8618. return panelClasses;
  8619. },
  8620. _addPanelClasses: function() {
  8621. this.element.addClass( this._getPanelClasses() );
  8622. },
  8623. _bindCloseEvents: function() {
  8624. var self = this;
  8625. self._closeLink.on( "click.panel" , function( e ) {
  8626. e.preventDefault();
  8627. self.close();
  8628. return false;
  8629. });
  8630. self.element.on( "click.panel" , "a:jqmData(ajax='false')", function( e ) {
  8631. self.close();
  8632. });
  8633. },
  8634. _positionPanel: function() {
  8635. var self = this,
  8636. panelInnerHeight = self._panelInner.outerHeight(),
  8637. expand = panelInnerHeight > $.mobile.getScreenHeight();
  8638. if ( expand || !self.options.positionFixed ) {
  8639. if ( expand ) {
  8640. self._unfixPanel();
  8641. $.mobile.resetActivePageHeight( panelInnerHeight );
  8642. }
  8643. self._scrollIntoView( panelInnerHeight );
  8644. } else {
  8645. self._fixPanel();
  8646. }
  8647. },
  8648. _scrollIntoView: function( panelInnerHeight ) {
  8649. if ( panelInnerHeight < $( window ).scrollTop() ) {
  8650. window.scrollTo( 0, 0 );
  8651. }
  8652. },
  8653. _bindFixListener: function() {
  8654. this._on( $( window ), { "throttledresize": "_positionPanel" });
  8655. },
  8656. _unbindFixListener: function() {
  8657. this._off( $( window ), "throttledresize" );
  8658. },
  8659. _unfixPanel: function() {
  8660. if ( !!this.options.positionFixed && $.support.fixedPosition ) {
  8661. this.element.removeClass( this.options.classes.panelFixed );
  8662. }
  8663. },
  8664. _fixPanel: function() {
  8665. if ( !!this.options.positionFixed && $.support.fixedPosition ) {
  8666. this.element.addClass( this.options.classes.panelFixed );
  8667. }
  8668. },
  8669. _bindUpdateLayout: function() {
  8670. var self = this;
  8671. self.element.on( "updatelayout", function( e ) {
  8672. if ( self._open ) {
  8673. self._positionPanel();
  8674. }
  8675. });
  8676. },
  8677. _bindLinkListeners: function() {
  8678. var self = this;
  8679. self._page.on( "click.panel" , "a", function( e ) {
  8680. if ( this.href.split( "#" )[ 1 ] === self._panelID && self._panelID !== undefined ) {
  8681. e.preventDefault();
  8682. var $link = $( this );
  8683. if ( ! $link.hasClass( "ui-link" ) ) {
  8684. $link.addClass( $.mobile.activeBtnClass );
  8685. self.element.one( "panelopen panelclose", function() {
  8686. $link.removeClass( $.mobile.activeBtnClass );
  8687. });
  8688. }
  8689. self.toggle();
  8690. return false;
  8691. }
  8692. });
  8693. },
  8694. _bindSwipeEvents: function() {
  8695. var self = this,
  8696. area = self._modal ? self.element.add( self._modal ) : self.element;
  8697. // on swipe, close the panel
  8698. if( !!self.options.swipeClose ) {
  8699. if ( self.options.position === "left" ) {
  8700. area.on( "swipeleft.panel", function( e ) {
  8701. self.close();
  8702. });
  8703. } else {
  8704. area.on( "swiperight.panel", function( e ) {
  8705. self.close();
  8706. });
  8707. }
  8708. }
  8709. },
  8710. _bindPageEvents: function() {
  8711. var self = this;
  8712. self._page
  8713. // Close the panel if another panel on the page opens
  8714. .on( "panelbeforeopen", function( e ) {
  8715. if ( self._open && e.target !== self.element[ 0 ] ) {
  8716. self.close();
  8717. }
  8718. })
  8719. // clean up open panels after page hide
  8720. .on( "pagehide", function( e ) {
  8721. if ( self._open ) {
  8722. self.close( true );
  8723. }
  8724. })
  8725. // on escape, close? might need to have a target check too...
  8726. .on( "keyup.panel", function( e ) {
  8727. if ( e.keyCode === 27 && self._open ) {
  8728. self.close();
  8729. }
  8730. });
  8731. },
  8732. // state storage of open or closed
  8733. _open: false,
  8734. _contentWrapOpenClasses: null,
  8735. _fixedToolbarOpenClasses: null,
  8736. _modalOpenClasses: null,
  8737. open: function( immediate ) {
  8738. if ( !this._open ) {
  8739. var self = this,
  8740. o = self.options,
  8741. _openPanel = function() {
  8742. self._page.off( "panelclose" );
  8743. self._page.jqmData( "panel", "open" );
  8744. if ( !immediate && $.support.cssTransform3d && !!o.animate ) {
  8745. self.element.add( self._wrapper ).on( self._transitionEndEvents, complete );
  8746. } else {
  8747. setTimeout( complete, 0 );
  8748. }
  8749. if ( self.options.theme && self.options.display !== "overlay" ) {
  8750. self._page
  8751. .removeClass( self._pageTheme )
  8752. .addClass( "ui-body-" + self.options.theme );
  8753. }
  8754. self.element.removeClass( o.classes.panelClosed ).addClass( o.classes.panelOpen );
  8755. self._positionPanel();
  8756. // Fix for IE7 min-height bug
  8757. if ( self.options.theme && self.options.display !== "overlay" ) {
  8758. self._wrapper.css( "min-height", self._page.css( "min-height" ) );
  8759. }
  8760. self._contentWrapOpenClasses = self._getPosDisplayClasses( o.classes.contentWrap );
  8761. self._wrapper
  8762. .removeClass( o.classes.contentWrapClosed )
  8763. .addClass( self._contentWrapOpenClasses + " " + o.classes.contentWrapOpen );
  8764. self._fixedToolbarOpenClasses = self._getPosDisplayClasses( o.classes.contentFixedToolbar );
  8765. self._fixedToolbar
  8766. .removeClass( o.classes.contentFixedToolbarClosed )
  8767. .addClass( self._fixedToolbarOpenClasses + " " + o.classes.contentFixedToolbarOpen );
  8768. self._modalOpenClasses = self._getPosDisplayClasses( o.classes.modal ) + " " + o.classes.modalOpen;
  8769. if ( self._modal ) {
  8770. self._modal.addClass( self._modalOpenClasses );
  8771. }
  8772. },
  8773. complete = function() {
  8774. self.element.add( self._wrapper ).off( self._transitionEndEvents, complete );
  8775. self._page.addClass( o.classes.pagePanelOpen );
  8776. self._bindFixListener();
  8777. self._trigger( "open" );
  8778. };
  8779. if ( this.element.closest( ".ui-page-active" ).length < 0 ) {
  8780. immediate = true;
  8781. }
  8782. self._trigger( "beforeopen" );
  8783. if ( self._page.jqmData('panel') === "open" ) {
  8784. self._page.on( "panelclose", function() {
  8785. _openPanel();
  8786. });
  8787. } else {
  8788. _openPanel();
  8789. }
  8790. self._open = true;
  8791. }
  8792. },
  8793. close: function( immediate ) {
  8794. if ( this._open ) {
  8795. var o = this.options,
  8796. self = this,
  8797. _closePanel = function() {
  8798. if ( !immediate && $.support.cssTransform3d && !!o.animate ) {
  8799. self.element.add( self._wrapper ).on( self._transitionEndEvents, complete );
  8800. } else {
  8801. setTimeout( complete, 0 );
  8802. }
  8803. self._page.removeClass( o.classes.pagePanelOpen );
  8804. self.element.removeClass( o.classes.panelOpen );
  8805. self._wrapper.removeClass( o.classes.contentWrapOpen );
  8806. self._fixedToolbar.removeClass( o.classes.contentFixedToolbarOpen );
  8807. if ( self._modal ) {
  8808. self._modal.removeClass( self._modalOpenClasses );
  8809. }
  8810. },
  8811. complete = function() {
  8812. if ( self.options.theme && self.options.display !== "overlay" ) {
  8813. self._page.removeClass( "ui-body-" + self.options.theme ).addClass( self._pageTheme );
  8814. // reset fix for IE7 min-height bug
  8815. self._wrapper.css( "min-height", "" );
  8816. }
  8817. self.element.add( self._wrapper ).off( self._transitionEndEvents, complete );
  8818. self.element.addClass( o.classes.panelClosed );
  8819. self._wrapper
  8820. .removeClass( self._contentWrapOpenClasses )
  8821. .addClass( o.classes.contentWrapClosed );
  8822. self._fixedToolbar
  8823. .removeClass( self._fixedToolbarOpenClasses )
  8824. .addClass( o.classes.contentFixedToolbarClosed );
  8825. self._fixPanel();
  8826. self._unbindFixListener();
  8827. $.mobile.resetActivePageHeight();
  8828. self._page.jqmRemoveData( "panel" );
  8829. self._trigger( "close" );
  8830. };
  8831. if ( this.element.closest( ".ui-page-active" ).length < 0 ) {
  8832. immediate = true;
  8833. }
  8834. self._trigger( "beforeclose" );
  8835. _closePanel();
  8836. self._open = false;
  8837. }
  8838. },
  8839. toggle: function( options ) {
  8840. this[ this._open ? "close" : "open" ]();
  8841. },
  8842. _transitionEndEvents: "webkitTransitionEnd oTransitionEnd otransitionend transitionend msTransitionEnd",
  8843. _destroy: function() {
  8844. var classes = this.options.classes,
  8845. theme = this.options.theme,
  8846. hasOtherSiblingPanels = this.element.siblings( "." + classes.panel ).length;
  8847. // create
  8848. if ( !hasOtherSiblingPanels ) {
  8849. this._wrapper.children().unwrap();
  8850. this._page.find( "a" ).unbind( "panelopen panelclose" );
  8851. this._page.removeClass( classes.pagePanel );
  8852. if ( this._open ) {
  8853. this._page.jqmRemoveData( "panel" );
  8854. this._page.removeClass( classes.pagePanelOpen );
  8855. if ( theme ) {
  8856. this._page.removeClass( "ui-body-" + theme ).addClass( this._pageTheme );
  8857. }
  8858. $.mobile.resetActivePageHeight();
  8859. }
  8860. } else if ( this._open ) {
  8861. this._wrapper.removeClass( classes.contentWrapOpen );
  8862. this._fixedToolbar.removeClass( classes.contentFixedToolbarOpen );
  8863. this._page.jqmRemoveData( "panel" );
  8864. this._page.removeClass( classes.pagePanelOpen );
  8865. if ( theme ) {
  8866. this._page.removeClass( "ui-body-" + theme ).addClass( this._pageTheme );
  8867. }
  8868. }
  8869. this._panelInner.children().unwrap();
  8870. this.element.removeClass( [ this._getPanelClasses(), classes.panelAnimate ].join( " " ) )
  8871. .off( "swipeleft.panel swiperight.panel" )
  8872. .off( "panelbeforeopen" )
  8873. .off( "panelhide" )
  8874. .off( "keyup.panel" )
  8875. .off( "updatelayout" );
  8876. this._closeLink.off( "click.panel" );
  8877. if ( this._modal ) {
  8878. this._modal.remove();
  8879. }
  8880. // open and close
  8881. this.element.off( this._transitionEndEvents )
  8882. .removeClass( [ classes.panelUnfixed, classes.panelClosed, classes.panelOpen ].join( " " ) );
  8883. }
  8884. });
  8885. //auto self-init widgets
  8886. $( document ).bind( "pagecreate create", function( e ) {
  8887. $.mobile.panel.prototype.enhanceWithin( e.target );
  8888. });
  8889. })( jQuery );
  8890. (function( $, undefined ) {
  8891. $.widget( "mobile.table", $.mobile.widget, {
  8892. options: {
  8893. classes: {
  8894. table: "ui-table"
  8895. },
  8896. initSelector: ":jqmData(role='table')"
  8897. },
  8898. _create: function() {
  8899. var self = this;
  8900. self.refresh( true );
  8901. },
  8902. refresh: function (create) {
  8903. var self = this,
  8904. trs = this.element.find( "thead tr" );
  8905. if ( create ) {
  8906. this.element.addClass( this.options.classes.table );
  8907. }
  8908. // Expose headers and allHeaders properties on the widget
  8909. // headers references the THs within the first TR in the table
  8910. self.headers = this.element.find( "tr:eq(0)" ).children();
  8911. // allHeaders references headers, plus all THs in the thead, which may include several rows, or not
  8912. self.allHeaders = self.headers.add( trs.children() );
  8913. trs.each(function(){
  8914. var coltally = 0;
  8915. $( this ).children().each(function( i ){
  8916. var span = parseInt( $( this ).attr( "colspan" ), 10 ),
  8917. sel = ":nth-child(" + ( coltally + 1 ) + ")";
  8918. $( this )
  8919. .jqmData( "colstart", coltally + 1 );
  8920. if( span ){
  8921. for( var j = 0; j < span - 1; j++ ){
  8922. coltally++;
  8923. sel += ", :nth-child(" + ( coltally + 1 ) + ")";
  8924. }
  8925. }
  8926. if ( create === undefined ) {
  8927. $(this).jqmData("cells", "");
  8928. }
  8929. // Store "cells" data on header as a reference to all cells in the same column as this TH
  8930. $( this )
  8931. .jqmData( "cells", self.element.find( "tr" ).not( trs.eq(0) ).not( this ).children( sel ) );
  8932. coltally++;
  8933. });
  8934. });
  8935. // update table modes
  8936. if ( create === undefined ) {
  8937. this.element.trigger( 'refresh' );
  8938. }
  8939. }
  8940. });
  8941. //auto self-init widgets
  8942. $.mobile.document.bind( "pagecreate create", function( e ) {
  8943. $.mobile.table.prototype.enhanceWithin( e.target );
  8944. });
  8945. })( jQuery );
  8946. (function( $, undefined ) {
  8947. $.mobile.table.prototype.options.mode = "columntoggle";
  8948. $.mobile.table.prototype.options.columnBtnTheme = null;
  8949. $.mobile.table.prototype.options.columnPopupTheme = null;
  8950. $.mobile.table.prototype.options.columnBtnText = "Columns...";
  8951. $.mobile.table.prototype.options.classes = $.extend(
  8952. $.mobile.table.prototype.options.classes,
  8953. {
  8954. popup: "ui-table-columntoggle-popup",
  8955. columnBtn: "ui-table-columntoggle-btn",
  8956. priorityPrefix: "ui-table-priority-",
  8957. columnToggleTable: "ui-table-columntoggle"
  8958. }
  8959. );
  8960. $.mobile.document.delegate( ":jqmData(role='table')", "tablecreate refresh", function( e ) {
  8961. var $table = $( this ),
  8962. self = $table.data( "mobile-table" ),
  8963. event = e.type,
  8964. o = self.options,
  8965. ns = $.mobile.ns,
  8966. id = ( $table.attr( "id" ) || o.classes.popup ) + "-popup", /* TODO BETTER FALLBACK ID HERE */
  8967. $menuButton,
  8968. $popup,
  8969. $menu,
  8970. $switchboard;
  8971. if ( o.mode !== "columntoggle" ) {
  8972. return;
  8973. }
  8974. if ( event !== "refresh" ) {
  8975. self.element.addClass( o.classes.columnToggleTable );
  8976. $menuButton = $( "<a href='#" + id + "' class='" + o.classes.columnBtn + "' data-" + ns + "rel='popup' data-" + ns + "mini='true'>" + o.columnBtnText + "</a>" ),
  8977. $popup = $( "<div data-" + ns + "role='popup' data-" + ns + "role='fieldcontain' class='" + o.classes.popup + "' id='" + id + "'></div>"),
  8978. $menu = $("<fieldset data-" + ns + "role='controlgroup'></fieldset>");
  8979. }
  8980. // create the hide/show toggles
  8981. self.headers.not( "td" ).each(function( i ) {
  8982. var priority = $( this ).jqmData( "priority" ),
  8983. $cells = $( this ).add( $( this ).jqmData( "cells" ) );
  8984. if ( priority ) {
  8985. $cells.addClass( o.classes.priorityPrefix + priority );
  8986. if ( event !== "refresh" ) {
  8987. $("<label><input type='checkbox' checked />" + $( this ).text() + "</label>" )
  8988. .appendTo( $menu )
  8989. .children( 0 )
  8990. .jqmData( "cells", $cells )
  8991. .checkboxradio({
  8992. theme: o.columnPopupTheme
  8993. });
  8994. } else {
  8995. $( '#' + id + ' fieldset div:eq(' + i +')').find('input').jqmData( 'cells', $cells );
  8996. }
  8997. }
  8998. });
  8999. if ( event !== "refresh" ) {
  9000. $menu.appendTo( $popup );
  9001. }
  9002. // bind change event listeners to inputs - TODO: move to a private method?
  9003. if ( $menu === undefined ) {
  9004. $switchboard = $('#' + id + ' fieldset');
  9005. } else {
  9006. $switchboard = $menu;
  9007. }
  9008. if ( event !== "refresh" ) {
  9009. $switchboard.on( "change", "input", function( e ){
  9010. if( this.checked ){
  9011. $( this ).jqmData( "cells" ).removeClass( "ui-table-cell-hidden" ).addClass( "ui-table-cell-visible" );
  9012. } else {
  9013. $( this ).jqmData( "cells" ).removeClass( "ui-table-cell-visible" ).addClass( "ui-table-cell-hidden" );
  9014. }
  9015. });
  9016. $menuButton
  9017. .insertBefore( $table )
  9018. .buttonMarkup({
  9019. theme: o.columnBtnTheme
  9020. });
  9021. $popup
  9022. .insertBefore( $table )
  9023. .popup();
  9024. }
  9025. // refresh method
  9026. self.update = function(){
  9027. $switchboard.find( "input" ).each( function(){
  9028. if (this.checked) {
  9029. this.checked = $( this ).jqmData( "cells" ).eq(0).css( "display" ) === "table-cell";
  9030. if (event === "refresh") {
  9031. $( this ).jqmData( "cells" ).addClass('ui-table-cell-visible');
  9032. }
  9033. } else {
  9034. $( this ).jqmData( "cells" ).addClass('ui-table-cell-hidden');
  9035. }
  9036. $( this ).checkboxradio( "refresh" );
  9037. });
  9038. };
  9039. $.mobile.window.on( "throttledresize", self.update );
  9040. self.update();
  9041. });
  9042. })( jQuery );
  9043. (function( $, undefined ) {
  9044. $.mobile.table.prototype.options.mode = "reflow";
  9045. $.mobile.table.prototype.options.classes = $.extend(
  9046. $.mobile.table.prototype.options.classes,
  9047. {
  9048. reflowTable: "ui-table-reflow",
  9049. cellLabels: "ui-table-cell-label"
  9050. }
  9051. );
  9052. $.mobile.document.delegate( ":jqmData(role='table')", "tablecreate refresh", function( e ) {
  9053. var $table = $( this ),
  9054. event = e.type,
  9055. self = $table.data( "mobile-table" ),
  9056. o = self.options;
  9057. // If it's not reflow mode, return here.
  9058. if( o.mode !== "reflow" ){
  9059. return;
  9060. }
  9061. if ( event !== "refresh" ) {
  9062. self.element.addClass( o.classes.reflowTable );
  9063. }
  9064. // get headers in reverse order so that top-level headers are appended last
  9065. var reverseHeaders = $( self.allHeaders.get().reverse() );
  9066. // create the hide/show toggles
  9067. reverseHeaders.each(function( i ){
  9068. var $cells = $( this ).jqmData( "cells" ),
  9069. colstart = $( this ).jqmData( "colstart" ),
  9070. hierarchyClass = $cells.not( this ).filter( "thead th" ).length && " ui-table-cell-label-top",
  9071. text = $(this).text();
  9072. if( text !== "" ){
  9073. if( hierarchyClass ){
  9074. var iteration = parseInt( $( this ).attr( "colspan" ), 10 ),
  9075. filter = "";
  9076. if( iteration ){
  9077. filter = "td:nth-child("+ iteration +"n + " + ( colstart ) +")";
  9078. }
  9079. $cells.filter( filter ).prepend( "<b class='" + o.classes.cellLabels + hierarchyClass + "'>" + text + "</b>" );
  9080. }
  9081. else {
  9082. $cells.prepend( "<b class='" + o.classes.cellLabels + "'>" + text + "</b>" );
  9083. }
  9084. }
  9085. });
  9086. });
  9087. })( jQuery );
  9088. (function( $, window ) {
  9089. $.mobile.iosorientationfixEnabled = true;
  9090. // This fix addresses an iOS bug, so return early if the UA claims it's something else.
  9091. var ua = navigator.userAgent;
  9092. if( !( /iPhone|iPad|iPod/.test( navigator.platform ) && /OS [1-5]_[0-9_]* like Mac OS X/i.test( ua ) && ua.indexOf( "AppleWebKit" ) > -1 ) ){
  9093. $.mobile.iosorientationfixEnabled = false;
  9094. return;
  9095. }
  9096. var zoom = $.mobile.zoom,
  9097. evt, x, y, z, aig;
  9098. function checkTilt( e ) {
  9099. evt = e.originalEvent;
  9100. aig = evt.accelerationIncludingGravity;
  9101. x = Math.abs( aig.x );
  9102. y = Math.abs( aig.y );
  9103. z = Math.abs( aig.z );
  9104. // If portrait orientation and in one of the danger zones
  9105. if ( !window.orientation && ( x > 7 || ( ( z > 6 && y < 8 || z < 8 && y > 6 ) && x > 5 ) ) ) {
  9106. if ( zoom.enabled ) {
  9107. zoom.disable();
  9108. }
  9109. } else if ( !zoom.enabled ) {
  9110. zoom.enable();
  9111. }
  9112. }
  9113. $.mobile.document.on( "mobileinit", function(){
  9114. if( $.mobile.iosorientationfixEnabled ){
  9115. $.mobile.window
  9116. .bind( "orientationchange.iosorientationfix", zoom.enable )
  9117. .bind( "devicemotion.iosorientationfix", checkTilt );
  9118. }
  9119. });
  9120. }( jQuery, this ));
  9121. (function( $, window, undefined ) {
  9122. var $html = $( "html" ),
  9123. $head = $( "head" ),
  9124. $window = $.mobile.window;
  9125. //remove initial build class (only present on first pageshow)
  9126. function hideRenderingClass() {
  9127. $html.removeClass( "ui-mobile-rendering" );
  9128. }
  9129. // trigger mobileinit event - useful hook for configuring $.mobile settings before they're used
  9130. $( window.document ).trigger( "mobileinit" );
  9131. // support conditions
  9132. // if device support condition(s) aren't met, leave things as they are -> a basic, usable experience,
  9133. // otherwise, proceed with the enhancements
  9134. if ( !$.mobile.gradeA() ) {
  9135. return;
  9136. }
  9137. // override ajaxEnabled on platforms that have known conflicts with hash history updates
  9138. // or generally work better browsing in regular http for full page refreshes (BB5, Opera Mini)
  9139. if ( $.mobile.ajaxBlacklist ) {
  9140. $.mobile.ajaxEnabled = false;
  9141. }
  9142. // Add mobile, initial load "rendering" classes to docEl
  9143. $html.addClass( "ui-mobile ui-mobile-rendering" );
  9144. // This is a fallback. If anything goes wrong (JS errors, etc), or events don't fire,
  9145. // this ensures the rendering class is removed after 5 seconds, so content is visible and accessible
  9146. setTimeout( hideRenderingClass, 5000 );
  9147. $.extend( $.mobile, {
  9148. // find and enhance the pages in the dom and transition to the first page.
  9149. initializePage: function() {
  9150. // find present pages
  9151. var path = $.mobile.path,
  9152. $pages = $( ":jqmData(role='page'), :jqmData(role='dialog')" ),
  9153. hash = path.stripHash( path.stripQueryParams(path.parseLocation().hash) ),
  9154. hashPage = document.getElementById( hash );
  9155. // if no pages are found, create one with body's inner html
  9156. if ( !$pages.length ) {
  9157. $pages = $( "body" ).wrapInner( "<div data-" + $.mobile.ns + "role='page'></div>" ).children( 0 );
  9158. }
  9159. // add dialogs, set data-url attrs
  9160. $pages.each(function() {
  9161. var $this = $( this );
  9162. // unless the data url is already set set it to the pathname
  9163. if ( !$this.jqmData( "url" ) ) {
  9164. $this.attr( "data-" + $.mobile.ns + "url", $this.attr( "id" ) || location.pathname + location.search );
  9165. }
  9166. });
  9167. // define first page in dom case one backs out to the directory root (not always the first page visited, but defined as fallback)
  9168. $.mobile.firstPage = $pages.first();
  9169. // define page container
  9170. $.mobile.pageContainer = $.mobile.firstPage.parent().addClass( "ui-mobile-viewport" );
  9171. // alert listeners that the pagecontainer has been determined for binding
  9172. // to events triggered on it
  9173. $window.trigger( "pagecontainercreate" );
  9174. // cue page loading message
  9175. $.mobile.showPageLoadingMsg();
  9176. //remove initial build class (only present on first pageshow)
  9177. hideRenderingClass();
  9178. // if hashchange listening is disabled, there's no hash deeplink,
  9179. // the hash is not valid (contains more than one # or does not start with #)
  9180. // or there is no page with that hash, change to the first page in the DOM
  9181. // Remember, however, that the hash can also be a path!
  9182. if ( ! ( $.mobile.hashListeningEnabled &&
  9183. $.mobile.path.isHashValid( location.hash ) &&
  9184. ( $( hashPage ).is( ':jqmData(role="page")' ) ||
  9185. $.mobile.path.isPath( hash ) ||
  9186. hash === $.mobile.dialogHashKey ) ) ) {
  9187. // Store the initial destination
  9188. if ( $.mobile.path.isHashValid( location.hash ) ) {
  9189. $.mobile.urlHistory.initialDst = hash.replace( "#", "" );
  9190. }
  9191. // make sure to set initial popstate state if it exists
  9192. // so that navigation back to the initial page works properly
  9193. if( $.event.special.navigate.isPushStateEnabled() ) {
  9194. $.mobile.navigate.navigator.squash( path.parseLocation().href );
  9195. }
  9196. $.mobile.changePage( $.mobile.firstPage, {
  9197. transition: "none",
  9198. reverse: true,
  9199. changeHash: false,
  9200. fromHashChange: true
  9201. });
  9202. } else {
  9203. // trigger hashchange or navigate to squash and record the correct
  9204. // history entry for an initial hash path
  9205. if( !$.event.special.navigate.isPushStateEnabled() ) {
  9206. $window.trigger( "hashchange", [true] );
  9207. } else {
  9208. // TODO figure out how to simplify this interaction with the initial history entry
  9209. // at the bottom js/navigate/navigate.js
  9210. $.mobile.navigate.history.stack = [];
  9211. $.mobile.navigate( $.mobile.path.isPath( location.hash ) ? location.hash : location.href );
  9212. }
  9213. }
  9214. }
  9215. });
  9216. // initialize events now, after mobileinit has occurred
  9217. $.mobile.navreadyDeferred.resolve();
  9218. // check which scrollTop value should be used by scrolling to 1 immediately at domready
  9219. // then check what the scroll top is. Android will report 0... others 1
  9220. // note that this initial scroll won't hide the address bar. It's just for the check.
  9221. $(function() {
  9222. window.scrollTo( 0, 1 );
  9223. // if defaultHomeScroll hasn't been set yet, see if scrollTop is 1
  9224. // it should be 1 in most browsers, but android treats 1 as 0 (for hiding addr bar)
  9225. // so if it's 1, use 0 from now on
  9226. $.mobile.defaultHomeScroll = ( !$.support.scrollTop || $.mobile.window.scrollTop() === 1 ) ? 0 : 1;
  9227. //dom-ready inits
  9228. if ( $.mobile.autoInitializePage ) {
  9229. $.mobile.initializePage();
  9230. }
  9231. // window load event
  9232. // hide iOS browser chrome on load
  9233. $window.load( $.mobile.silentScroll );
  9234. if ( !$.support.cssPointerEvents ) {
  9235. // IE and Opera don't support CSS pointer-events: none that we use to disable link-based buttons
  9236. // by adding the 'ui-disabled' class to them. Using a JavaScript workaround for those browser.
  9237. // https://github.com/jquery/jquery-mobile/issues/3558
  9238. $.mobile.document.delegate( ".ui-disabled", "vclick",
  9239. function( e ) {
  9240. e.preventDefault();
  9241. e.stopImmediatePropagation();
  9242. }
  9243. );
  9244. }
  9245. });
  9246. }( jQuery, this ));
  9247. }));